diff --git a/bindings/core/src/method/client.rs b/bindings/core/src/method/client.rs index 0b7c9f6636..929aeee68b 100644 --- a/bindings/core/src/method/client.rs +++ b/bindings/core/src/method/client.rs @@ -5,15 +5,7 @@ use derivative::Derivative; #[cfg(feature = "mqtt")] use iota_sdk::client::mqtt::Topic; use iota_sdk::{ - client::{ - api::{ - ClientBlockBuilderOptions as BuildBlockOptions, GetAddressesOptions as GenerateAddressesOptions, - PreparedTransactionDataDto, - }, - node_api::indexer::query_parameters::QueryParameter, - node_manager::node::NodeAuth, - secret::SecretManagerDto, - }, + client::{node_api::indexer::query_parameters::QueryParameter, node_manager::node::NodeAuth}, types::block::{ address::{Bech32Address, Hrp}, output::{ @@ -28,8 +20,6 @@ use iota_sdk::{ }; use serde::{Deserialize, Serialize}; -use crate::OmittedDebug; - /// Each public client method. #[derive(Clone, Derivative, Serialize, Deserialize)] #[derivative(Debug)] @@ -97,15 +87,6 @@ pub enum ClientMethod { /// Topics for which listeners should be removed. topics: Vec, }, - /// Build and post a block - #[serde(rename_all = "camelCase")] - BuildAndPostBlock { - /// Secret manager - #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] - secret_manager: Option, - /// Options - options: Option, - }, /// Get a node candidate from the healthy node pool. GetNode, /// Gets the network related information such as network_id and min_pow_score @@ -136,24 +117,6 @@ pub enum ClientMethod { query_params: Vec, request_object: Option, }, - /// Prepare a transaction for signing - #[serde(rename_all = "camelCase")] - PrepareTransaction { - /// Secret manager - #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] - secret_manager: Option, - /// Options - options: Option, - }, - /// Sign a transaction - #[serde(rename_all = "camelCase")] - SignTransaction { - /// Secret manager - #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] - secret_manager: SecretManagerDto, - /// Prepared transaction data - prepared_transaction_data: PreparedTransactionDataDto, - }, /// Build a block containing the specified payload and post it to the network. PostBlockPayload { /// The payload to send @@ -321,16 +284,6 @@ pub enum ClientMethod { /// Maximum attempts max_attempts: Option, }, - /// Function to consolidate all funds from a range of addresses to the address with the lowest index in that range - /// Returns the address to which the funds got consolidated, if any were available - #[serde(rename_all = "camelCase")] - ConsolidateFunds { - /// Secret manager - #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] - secret_manager: SecretManagerDto, - /// Addresses generation options - generate_addresses_options: GenerateAddressesOptions, - }, /// Function to find inputs from addresses for a provided amount (useful for offline signing) FindInputs { /// Addresses diff --git a/bindings/core/src/method_handler/client.rs b/bindings/core/src/method_handler/client.rs index bb8cb0114f..70299bb985 100644 --- a/bindings/core/src/method_handler/client.rs +++ b/bindings/core/src/method_handler/client.rs @@ -4,10 +4,7 @@ #[cfg(feature = "mqtt")] use iota_sdk::client::mqtt::{MqttPayload, Topic}; use iota_sdk::{ - client::{ - api::{PreparedTransactionData, PreparedTransactionDataDto}, - request_funds_from_faucet, Client, - }, + client::{request_funds_from_faucet, Client}, types::{ api::core::response::OutputWithMetadataResponse, block::{ @@ -16,7 +13,7 @@ use iota_sdk::{ dto::{OutputBuilderAmountDto, OutputDto, OutputMetadataDto}, AliasOutput, BasicOutput, FoundryOutput, NftOutput, Output, Rent, RentStructure, }, - payload::{dto::PayloadDto, Payload}, + payload::Payload, protocol::dto::ProtocolParametersDto, Block, BlockDto, }, @@ -160,31 +157,6 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM Response::Output(OutputDto::from(&output)) } - ClientMethod::BuildAndPostBlock { - secret_manager, - options, - } => { - // Prepare transaction - let mut block_builder = client.build_block(); - - let secret_manager = match secret_manager { - Some(secret_manager) => Some(secret_manager.try_into()?), - None => None, - }; - - if let Some(secret_manager) = &secret_manager { - block_builder = block_builder.with_secret_manager(secret_manager); - } - - if let Some(options) = options { - block_builder = block_builder.set_options(options).await?; - } - - let block = block_builder.finish().await?; - let block_id = block.id(); - - Response::BlockIdWithBlock(block_id, BlockDto::from(&block)) - } #[cfg(feature = "mqtt")] ClientMethod::ClearListeners { topics } => { client.unsubscribe(topics).await?; @@ -217,55 +189,15 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM } ClientMethod::GetLocalPow => Response::Bool(client.get_local_pow().await), ClientMethod::GetFallbackToLocalPow => Response::Bool(client.get_fallback_to_local_pow().await), - ClientMethod::PrepareTransaction { - secret_manager, - options, - } => { - let mut block_builder = client.build_block(); - - let secret_manager = match secret_manager { - Some(secret_manager) => Some(secret_manager.try_into()?), - None => None, - }; - - if let Some(secret_manager) = &secret_manager { - block_builder = block_builder.with_secret_manager(secret_manager); - } - - if let Some(options) = options { - block_builder = block_builder.set_options(options).await?; - } - - Response::PreparedTransactionData(PreparedTransactionDataDto::from( - &block_builder.prepare_transaction().await?, - )) - } - ClientMethod::SignTransaction { - secret_manager, - prepared_transaction_data, - } => { - let mut block_builder = client.build_block(); - - let secret_manager = secret_manager.try_into()?; - - block_builder = block_builder.with_secret_manager(&secret_manager); - - Response::SignedTransaction(PayloadDto::from( - &block_builder - .sign_transaction(PreparedTransactionData::try_from_dto_unverified( - prepared_transaction_data, - )?) - .await?, - )) - } ClientMethod::PostBlockPayload { payload } => { - let block_builder = client.build_block(); - - let block = block_builder - .finish_block(Some(Payload::try_from_dto( - payload, - &client.get_protocol_parameters().await?, - )?)) + let block = client + .finish_block_builder( + None, + Some(Payload::try_from_dto( + payload, + &client.get_protocol_parameters().await?, + )?), + ) .await?; let block_id = block.id(); @@ -369,17 +301,6 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM .collect(); Response::RetryUntilIncludedSuccessful(res) } - ClientMethod::ConsolidateFunds { - secret_manager, - generate_addresses_options, - } => { - let secret_manager = secret_manager.try_into()?; - Response::ConsolidatedFunds( - client - .consolidate_funds(&secret_manager, generate_addresses_options) - .await?, - ) - } ClientMethod::FindInputs { addresses, amount } => Response::Inputs( client .find_inputs(addresses, amount) diff --git a/bindings/core/src/response.rs b/bindings/core/src/response.rs index 9a8bb6a215..92ace9e6a4 100644 --- a/bindings/core/src/response.rs +++ b/bindings/core/src/response.rs @@ -82,10 +82,7 @@ pub enum Response { /// - [`GetProtocolParameters`](crate::method::ClientMethod::GetProtocolParameters) ProtocolParameters(ProtocolParametersDto), /// Response for: - /// - [`PrepareTransaction`](crate::method::ClientMethod::PrepareTransaction) - PreparedTransactionData(PreparedTransactionDataDto), - /// Response for: - /// - [`SignTransaction`](crate::method::ClientMethod::SignTransaction) + /// - [`SignTransaction`](crate::method::SecretManagerMethod::SignTransaction) SignedTransaction(PayloadDto), /// Response for: /// - [`SignatureUnlock`](crate::method::SecretManagerMethod::SignatureUnlock) @@ -118,7 +115,6 @@ pub enum Response { /// - [`GetIncludedBlock`](crate::method::ClientMethod::GetIncludedBlock) Block(BlockDto), /// Response for: - /// - [`BuildAndPostBlock`](crate::method::ClientMethod::BuildAndPostBlock) /// - [`PostBlockPayload`](crate::method::ClientMethod::PostBlockPayload) /// - [`Retry`](crate::method::ClientMethod::Retry) BlockIdWithBlock(BlockId, BlockDto), @@ -159,9 +155,6 @@ pub enum Response { /// - [`RetryUntilIncluded`](crate::method::ClientMethod::RetryUntilIncluded) RetryUntilIncludedSuccessful(Vec<(BlockId, BlockDto)>), /// Response for: - /// - [`ConsolidateFunds`](crate::method::ClientMethod::ConsolidateFunds) - ConsolidatedFunds(Bech32Address), - /// Response for: /// - [`FindInputs`](crate::method::ClientMethod::FindInputs) Inputs(Vec), /// Response for: diff --git a/bindings/core/tests/secrets_debug.rs b/bindings/core/tests/secrets_debug.rs index d133a94bbd..a091b55c26 100644 --- a/bindings/core/tests/secrets_debug.rs +++ b/bindings/core/tests/secrets_debug.rs @@ -2,36 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::client::secret::SecretManagerDto; -use iota_sdk_bindings_core::{ClientMethod, Response, UtilsMethod, WalletOptions}; +use iota_sdk_bindings_core::{Response, UtilsMethod, WalletOptions}; #[test] fn method_interface_secrets_debug() { - let client_method = ClientMethod::BuildAndPostBlock { - secret_manager: None, - options: None, - }; - assert_eq!( - format!("{:?}", client_method), - "BuildAndPostBlock { secret_manager: None, options: None }" - ); - - #[cfg(feature = "ledger_nano")] - { - let client_method = ClientMethod::BuildAndPostBlock { - secret_manager: Some(SecretManagerDto::LedgerNano(false)), - options: None, - }; - assert_eq!( - format!("{:?}", client_method), - "BuildAndPostBlock { secret_manager: Some(), options: None }" - ); - } - - let client_method = UtilsMethod::MnemonicToHexSeed { + let utils_method = UtilsMethod::MnemonicToHexSeed { mnemonic: "mnemonic".to_string(), }; assert_eq!( - format!("{:?}", client_method), + format!("{:?}", utils_method), "MnemonicToHexSeed { mnemonic: }" ); diff --git a/bindings/nodejs/examples/client/06-simple-block.ts b/bindings/nodejs/examples/client/06-simple-block.ts index 221b9e280c..1a157fca45 100644 --- a/bindings/nodejs/examples/client/06-simple-block.ts +++ b/bindings/nodejs/examples/client/06-simple-block.ts @@ -1,7 +1,7 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Client, initLogger } from '@iota/sdk'; +import { Client, initLogger, TaggedDataPayload, utf8ToHex } from '@iota/sdk'; require('dotenv').config({ path: '.env' }); // Run with command: @@ -22,7 +22,10 @@ async function run() { try { // Create block with no payload - const blockIdAndBlock = await client.buildAndPostBlock(); + // TODO: have a way in the bindings to send an empty block https://github.com/iotaledger/iota-sdk/issues/647 + const blockIdAndBlock = await client.postBlockPayload( + new TaggedDataPayload(utf8ToHex('Hello'), utf8ToHex('Tangle')), + ); console.log('Block:', blockIdAndBlock, '\n'); console.log( diff --git a/bindings/nodejs/examples/client/08-data-block.ts b/bindings/nodejs/examples/client/08-data-block.ts index 96dd2f4480..2b4d3accd3 100644 --- a/bindings/nodejs/examples/client/08-data-block.ts +++ b/bindings/nodejs/examples/client/08-data-block.ts @@ -7,7 +7,6 @@ import { initLogger, TaggedDataPayload, utf8ToHex, - Utils, } from '@iota/sdk'; require('dotenv').config({ path: '.env' }); @@ -26,18 +25,10 @@ async function run() { nodes: [process.env.NODE_URL], }); - const options = { - tag: utf8ToHex('Hello'), - data: utf8ToHex('Tangle'), - }; try { - const mnemonic = Utils.generateMnemonic(); - const secretManager = { mnemonic: mnemonic }; - // Create block with tagged payload - const blockIdAndBlock = await client.buildAndPostBlock( - secretManager, - options, + const blockIdAndBlock = await client.postBlockPayload( + new TaggedDataPayload(utf8ToHex('Hello'), utf8ToHex('Tangle')), ); console.log( diff --git a/bindings/nodejs/examples/client/09-transaction.ts b/bindings/nodejs/examples/client/09-transaction.ts deleted file mode 100644 index f25e84ae16..0000000000 --- a/bindings/nodejs/examples/client/09-transaction.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2021-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { Client, SecretManager, initLogger } from '@iota/sdk'; -require('dotenv').config({ path: '.env' }); - -// Run with command: -// yarn run-example ./client/09-transaction.ts - -// In this example we will send a transaction -async function run() { - initLogger(); - if (!process.env.NODE_URL) { - throw new Error('.env NODE_URL is undefined, see .env.example'); - } - - const client = new Client({ - // Insert your node URL in the .env. - nodes: [process.env.NODE_URL], - localPow: true, - }); - - try { - if (!process.env.MNEMONIC) { - throw new Error('.env MNEMONIC is undefined, see .env.example'); - } - - // Configure your own mnemonic in ".env". Since the output amount cannot be zero, the mnemonic must contain non-zero - // balance - const secretManager = { - mnemonic: process.env.MNEMONIC, - }; - - // We generate an address from our own mnemonic so that we send the funds to ourselves - const addresses = await new SecretManager( - secretManager, - ).generateEd25519Addresses({ - range: { - start: 1, - end: 2, - }, - }); - - // We prepare the transaction - // Insert the output address and amount to spend. The amount cannot be zero. - const blockIdAndBlock = await client.buildAndPostBlock(secretManager, { - output: { - address: addresses[0], - amount: '1000000', - }, - }); - - console.log( - `Block sent: ${process.env.EXPLORER_URL}/block/${blockIdAndBlock[0]}`, - ); - } catch (error) { - console.error('Error: ', error); - } -} - -run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/offline_signing/00-address-generation.ts b/bindings/nodejs/examples/client/offline_signing/00-address-generation.ts deleted file mode 100644 index 83b01fcd15..0000000000 --- a/bindings/nodejs/examples/client/offline_signing/00-address-generation.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2021-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { - CoinType, - initLogger, - SecretManager, - SHIMMER_TESTNET_BECH32_HRP, -} from '@iota/sdk'; -import { writeFile } from 'fs/promises'; - -require('dotenv').config({ path: '.env' }); - -// From examples directory, run with: -// yarn run-example ./client/offline_signing/00-address-generation.ts - -const ADDRESS_FILE_NAME = 'offline-signing-address.json'; - -// In this example we will generate an address offline which will be used later to find inputs -async function run() { - initLogger(); - - try { - if (!process.env.MNEMONIC) { - throw new Error('.env MNEMONIC is undefined, see .env.example'); - } - - const secretManager = new SecretManager({ - mnemonic: process.env.MNEMONIC, - }); - - // Generates an address offline. - const offlineGeneratedAddress = - await secretManager.generateEd25519Addresses({ - coinType: CoinType.Shimmer, - range: { - start: 0, - end: 1, - }, - bech32Hrp: SHIMMER_TESTNET_BECH32_HRP, - }); - - await writeFile( - ADDRESS_FILE_NAME, - JSON.stringify(offlineGeneratedAddress), - ); - } catch (error) { - console.error('Error: ', error); - } -} - -run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/offline_signing/01-transaction-preparation.ts b/bindings/nodejs/examples/client/offline_signing/01-transaction-preparation.ts deleted file mode 100644 index 90e8bda286..0000000000 --- a/bindings/nodejs/examples/client/offline_signing/01-transaction-preparation.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2021-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { Client, initLogger } from '@iota/sdk'; -import { writeFile, readFile } from 'fs/promises'; - -require('dotenv').config({ path: '.env' }); - -// From examples directory, run with: -// yarn run-example ./client/offline_signing/01-transaction-preparation.ts - -const ADDRESS_FILE_NAME = 'offline-signing-address.json'; -const PREPARED_TRANSACTION_FILE_NAME = - 'offline-signing-prepared-transaction.json'; - -// In this example we will get inputs and prepare a transaction -async function run() { - initLogger(); - if (!process.env.NODE_URL) { - throw new Error('.env NODE_URL is undefined, see .env.example'); - } - const onlineClient = new Client({ - // Insert your node URL in the .env. - nodes: [process.env.NODE_URL], - localPow: true, - }); - - const address = - 'rms1qqv5avetndkxzgr3jtrswdtz5ze6mag20s0jdqvzk4fwezve8q9vkpnqlqe'; - const amount = 1000000; - try { - // Recovers the address from example `0_address_generation`. - const input_address = JSON.parse( - await readFile(ADDRESS_FILE_NAME, 'utf8'), - ); - - // Gets enough inputs related to the address to cover the amount. - const inputs = await onlineClient.findInputs(input_address, amount); - - // Prepares the transaction - const preparedTransaction = await onlineClient.prepareTransaction( - undefined, - { - inputs, - output: { address, amount: amount.toString() }, - }, - ); - - console.log(`Prepared transaction sending ${amount} to ${address}.`); - - await writeFile( - PREPARED_TRANSACTION_FILE_NAME, - JSON.stringify(preparedTransaction), - ); - } catch (error) { - console.error(error); - } -} - -run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/offline_signing/02-transaction-signing.ts b/bindings/nodejs/examples/client/offline_signing/02-transaction-signing.ts deleted file mode 100644 index 3c12f12c95..0000000000 --- a/bindings/nodejs/examples/client/offline_signing/02-transaction-signing.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2021-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { Client, initLogger } from '@iota/sdk'; -import { writeFile, readFile } from 'fs/promises'; - -require('dotenv').config({ path: '.env' }); - -// From examples directory, run with: -// yarn run-example ./client/offline_signing/02-transaction-signing.ts - -const PREPARED_TRANSACTION_FILE_NAME = - 'offline-signing-prepared-transaction.json'; -const SIGNED_TRANSACTION_FILE_NAME = 'offline-signing-signed-transaction.json'; - -// In this example we will sign the prepared transaction -async function run() { - initLogger(); - - const offlineClient = new Client({}); - - try { - if (!process.env.MNEMONIC) { - throw new Error('.env MNEMONIC is undefined, see .env.example'); - } - - const secretManager = { - mnemonic: process.env.MNEMONIC, - }; - - // Read in prepared transaction from example 2_transaction_preparation - const preparedTransaction = JSON.parse( - await readFile(PREPARED_TRANSACTION_FILE_NAME, 'utf8'), - ); - - // Signs prepared transaction offline. - const signedTransaction = await offlineClient.signTransaction( - secretManager, - preparedTransaction, - ); - - console.log('Signed transaction.'); - - await writeFile( - SIGNED_TRANSACTION_FILE_NAME, - JSON.stringify(signedTransaction), - ); - } catch (error) { - console.error(error); - } -} - -run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/offline_signing/03-send-block.ts b/bindings/nodejs/examples/client/offline_signing/03-send-block.ts deleted file mode 100644 index 5bf9912aa8..0000000000 --- a/bindings/nodejs/examples/client/offline_signing/03-send-block.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2021-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { Client, initLogger } from '@iota/sdk'; -import { readFile } from 'fs/promises'; - -require('dotenv').config({ path: '.env' }); - -// From examples directory, run with: -// yarn run-example ./client/offline_signing/03-send-block.ts - -const SIGNED_TRANSACTION_FILE_NAME = 'offline-signing-signed-transaction.json'; - -// In this example we will send the signed transaction in a block -async function run() { - initLogger(); - if (!process.env.NODE_URL) { - throw new Error('.env NODE_URL is undefined, see .env.example'); - } - const onlineClient = new Client({ - // Insert your node URL in the .env. - nodes: [process.env.NODE_URL], - localPow: true, - }); - - try { - const signedTransaction = JSON.parse( - await readFile(SIGNED_TRANSACTION_FILE_NAME, 'utf8'), - ); - - // Send block with the signed transaction as a payload - const blockIdAndBlock = await onlineClient.postBlockPayload( - signedTransaction, - ); - - console.log( - `Empty block sent: ${process.env.EXPLORER_URL}/block/${blockIdAndBlock[0]}`, - ); - } catch (error) { - console.error(error); - } -} - -run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/offline_signing/example-address.json b/bindings/nodejs/examples/client/offline_signing/example-address.json deleted file mode 100644 index 38940a9f27..0000000000 --- a/bindings/nodejs/examples/client/offline_signing/example-address.json +++ /dev/null @@ -1 +0,0 @@ -["rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy"] diff --git a/bindings/nodejs/examples/client/offline_signing/example-prepared-transaction.json b/bindings/nodejs/examples/client/offline_signing/example-prepared-transaction.json deleted file mode 100644 index 2cc0203aec..0000000000 --- a/bindings/nodejs/examples/client/offline_signing/example-prepared-transaction.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "essence": { - "type": 1, - "networkId": "6983037938332331227", - "inputs": [ - { - "type": 0, - "transactionId": "0x317841bb2f2113745cac0fdd523a7764c23eb0a3ee0b1b22e19e68cd1ca242cb", - "transactionOutputIndex": 1 - } - ], - "inputsCommitment": "0x0c60d55af808fb89ebb517ecc07869cf278f8fcf53f89257ddbcd3a077410e12", - "outputs": [ - { - "type": 3, - "amount": "1000000", - "unlockConditions": [ - { - "type": 0, - "address": { - "type": 0, - "pubKeyHash": "0x194eb32b9b6c61207192c7073562a0b3adf50a7c1f268182b552ec8999380acb" - } - } - ] - }, - { - "type": 3, - "amount": "1064765900", - "nativeTokens": [ - { - "id": "0x081e6439529b020328c08224b43172f282cb16649d50c891fa156365323667e47a0100000000", - "amount": "0x32" - } - ], - "unlockConditions": [ - { - "type": 0, - "address": { - "type": 0, - "pubKeyHash": "0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3" - } - } - ] - } - ] - }, - "inputsData": [ - { - "output": { - "type": 3, - "amount": "1065765900", - "nativeTokens": [ - { - "id": "0x081e6439529b020328c08224b43172f282cb16649d50c891fa156365323667e47a0100000000", - "amount": "0x32" - } - ], - "unlockConditions": [ - { - "type": 0, - "address": { - "type": 0, - "pubKeyHash": "0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3" - } - } - ] - }, - "outputMetadata": { - "blockId": "0xb1eb524edd14ef7ddbfb8618c51ca97a90dbe8e83be8d71e8b040a6e13eba554", - "transactionId": "0x317841bb2f2113745cac0fdd523a7764c23eb0a3ee0b1b22e19e68cd1ca242cb", - "outputIndex": 1, - "isSpent": false, - "milestoneIndexBooked": 183395, - "milestoneTimestampBooked": 1653942602, - "ledgerIndex": 183548 - }, - "chain": [ - { "hardened": true, "bs": [128, 0, 0, 44] }, - { "hardened": true, "bs": [128, 0, 16, 123] }, - { "hardened": true, "bs": [128, 0, 0, 0] }, - { "hardened": true, "bs": [128, 0, 0, 0] }, - { "hardened": true, "bs": [128, 0, 0, 0] } - ], - "bech32Address": "rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy" - } - ], - "remainder": { - "output": { - "type": 3, - "amount": "1064765900", - "nativeTokens": [ - { - "id": "0x081e6439529b020328c08224b43172f282cb16649d50c891fa156365323667e47a0100000000", - "amount": "0x32" - } - ], - "unlockConditions": [ - { - "type": 0, - "address": { - "type": 0, - "pubKeyHash": "0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3" - } - } - ] - }, - "chain": [ - { "hardened": true, "bs": [128, 0, 0, 44] }, - { "hardened": true, "bs": [128, 0, 16, 123] }, - { "hardened": true, "bs": [128, 0, 0, 0] }, - { "hardened": true, "bs": [128, 0, 0, 0] }, - { "hardened": true, "bs": [128, 0, 0, 0] } - ], - "address": { - "type": 0, - "pubKeyHash": "0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3" - } - } -} diff --git a/bindings/nodejs/examples/client/offline_signing/example-signed-transaction.json b/bindings/nodejs/examples/client/offline_signing/example-signed-transaction.json deleted file mode 100644 index bf7521311b..0000000000 --- a/bindings/nodejs/examples/client/offline_signing/example-signed-transaction.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "type": 6, - "essence": { - "type": 1, - "networkId": "6983037938332331227", - "inputs": [ - { - "type": 0, - "transactionId": "0x317841bb2f2113745cac0fdd523a7764c23eb0a3ee0b1b22e19e68cd1ca242cb", - "transactionOutputIndex": 1 - } - ], - "inputsCommitment": "0x0c60d55af808fb89ebb517ecc07869cf278f8fcf53f89257ddbcd3a077410e12", - "outputs": [ - { - "type": 3, - "amount": "1000000", - "unlockConditions": [ - { - "type": 0, - "address": { - "type": 0, - "pubKeyHash": "0x194eb32b9b6c61207192c7073562a0b3adf50a7c1f268182b552ec8999380acb" - } - } - ] - }, - { - "type": 3, - "amount": "1064765900", - "nativeTokens": [ - { - "id": "0x081e6439529b020328c08224b43172f282cb16649d50c891fa156365323667e47a0100000000", - "amount": "0x32" - } - ], - "unlockConditions": [ - { - "type": 0, - "address": { - "type": 0, - "pubKeyHash": "0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3" - } - } - ] - } - ] - }, - "unlocks": [ - { - "type": 0, - "signature": { - "type": 0, - "publicKey": "0x67b7fc3f78763c9394fc4fcdb52cf3a973b6e064bdc3defb40a6cb2c880e6f5c", - "signature": "0xbefb97e76f5c1a5b6aab16b8ab2f79895db522df16c8ab61a48129f11cd331367607cc106e15806fb19642125af06b49cfadcad51473d34f94fd1119af7fad0a" - } - } - ] -} diff --git a/bindings/nodejs/lib/client/client.ts b/bindings/nodejs/lib/client/client.ts index 318b2496db..c1aebbb3c5 100644 --- a/bindings/nodejs/lib/client/client.ts +++ b/bindings/nodejs/lib/client/client.ts @@ -4,8 +4,6 @@ import { ClientMethodHandler } from './client-method-handler'; import { IClientOptions, - IGenerateAddressesOptions, - IBuildBlockOptions, QueryParameter, PreparedTransactionData, INetworkInfo, @@ -29,6 +27,7 @@ import { BasicOutput, FoundryOutput, NftOutput, + Output, Block, BlockId, UnlockCondition, @@ -124,23 +123,6 @@ export class Client { return plainToInstance(OutputResponse, parsed.payload); } - /** Build and post a block */ - async buildAndPostBlock( - secretManager?: SecretManagerType, - options?: IBuildBlockOptions, - ): Promise<[BlockId, Block]> { - const response = await this.methodHandler.callMethod({ - name: 'buildAndPostBlock', - data: { - secretManager, - options, - }, - }); - const parsed = JSON.parse(response) as Response<[BlockId, Block]>; - const block = plainToInstance(Block, parsed.payload[1]); - return [parsed.payload[0], block]; - } - /** * Returns tips that are ideal for attaching a block. * The tips can be considered as non-lazy and are therefore ideal for attaching a block. @@ -235,27 +217,6 @@ export class Client { return plainToInstance(OutputResponse, parsed.payload); } - /** - * Prepare a transaction for signing - */ - async prepareTransaction( - secretManager?: SecretManagerType, - options?: IBuildBlockOptions, - ): Promise { - const response = await this.methodHandler.callMethod({ - name: 'prepareTransaction', - data: { - secretManager, - options, - }, - }); - - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(PreparedTransactionData, parsed.payload); - } - /** * Sign a transaction */ @@ -730,25 +691,6 @@ export class Client { return arr; } - /** - * Function to consolidate all funds from a range of addresses to the address with the lowest index in that range - * Returns the address to which the funds got consolidated, if any were available - */ - async consolidateFunds( - secretManager: SecretManagerType, - generateAddressesOptions: IGenerateAddressesOptions, - ): Promise { - const response = await this.methodHandler.callMethod({ - name: 'consolidateFunds', - data: { - secretManager, - generateAddressesOptions, - }, - }); - - return JSON.parse(response).payload; - } - /** * Reattaches blocks for provided block id. Blocks can be reattached only if they are valid and haven't been * confirmed for a while. diff --git a/bindings/nodejs/lib/types/client/bridge/client.ts b/bindings/nodejs/lib/types/client/bridge/client.ts index 94e5dbb3db..599c205b8d 100644 --- a/bindings/nodejs/lib/types/client/bridge/client.ts +++ b/bindings/nodejs/lib/types/client/bridge/client.ts @@ -5,8 +5,6 @@ import type { Bip44, SecretManagerType, } from '../../secret_manager/secret-manager'; -import type { IGenerateAddressesOptions } from '../generate-addresses-options'; -import type { IBuildBlockOptions } from '../build-block-options'; import type { Block, BlockId, Output, Payload } from '../../block'; import type { PreparedTransactionData } from '../prepared-transaction-data'; import type { @@ -54,14 +52,6 @@ export interface __PostBlockMethod__ { }; } -export interface __BuildAndPostBlockMethod__ { - name: 'buildAndPostBlock'; - data: { - secretManager?: SecretManagerType; - options?: IBuildBlockOptions; - }; -} - export interface __GetTipsMethod__ { name: 'getTips'; } @@ -100,14 +90,6 @@ export interface __FindOutputsMethod__ { }; } -export interface __PrepareTransactionMethod__ { - name: 'prepareTransaction'; - data: { - secretManager?: SecretManagerType; - options?: IBuildBlockOptions; - }; -} - export interface __SignTransactionMethod__ { name: 'signTransaction'; data: { @@ -315,14 +297,6 @@ export interface __RetryUntilIncludedMethod__ { }; } -export interface __ConsolidateFundsMethod__ { - name: 'consolidateFunds'; - data: { - secretManager: SecretManagerType; - generateAddressesOptions: IGenerateAddressesOptions; - }; -} - export interface __ReattachMethod__ { name: 'reattach'; data: { diff --git a/bindings/nodejs/lib/types/client/bridge/index.ts b/bindings/nodejs/lib/types/client/bridge/index.ts index e76d31ec29..0aa1d7dd93 100644 --- a/bindings/nodejs/lib/types/client/bridge/index.ts +++ b/bindings/nodejs/lib/types/client/bridge/index.ts @@ -7,14 +7,12 @@ import type { __GetOutputMethod__, __GetOutputsMethod__, __PostBlockMethod__, - __BuildAndPostBlockMethod__, __GetTipsMethod__, __GetNetworkInfoMethod__, __GetBlockMethod__, __GetBlockMetadataMethod__, __FindInputsMethod__, __FindOutputsMethod__, - __PrepareTransactionMethod__, __SignTransactionMethod__, __PostBlockPayloadMethod__, __GetNodeMethod__, @@ -46,7 +44,6 @@ import type { __FindBlocksMethod__, __RetryMethod__, __RetryUntilIncludedMethod__, - __ConsolidateFundsMethod__, __ReattachMethod__, __ReattachUncheckedMethod__, __PromoteMethod__, @@ -69,14 +66,12 @@ export type __ClientMethods__ = | __GetBasicOutputIdsMethod__ | __GetOutputsMethod__ | __PostBlockMethod__ - | __BuildAndPostBlockMethod__ | __GetTipsMethod__ | __GetNetworkInfoMethod__ | __GetBlockMethod__ | __GetBlockMetadataMethod__ | __FindInputsMethod__ | __FindOutputsMethod__ - | __PrepareTransactionMethod__ | __SignTransactionMethod__ | __SignatureUnlockMethod__ | __PostBlockPayloadMethod__ @@ -109,7 +104,6 @@ export type __ClientMethods__ = | __FindBlocksMethod__ | __RetryMethod__ | __RetryUntilIncludedMethod__ - | __ConsolidateFundsMethod__ | __ReattachMethod__ | __ReattachUncheckedMethod__ | __PromoteMethod__ diff --git a/bindings/nodejs/lib/types/client/build-block-options.ts b/bindings/nodejs/lib/types/client/build-block-options.ts deleted file mode 100644 index 6552d98c55..0000000000 --- a/bindings/nodejs/lib/types/client/build-block-options.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2021-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { CoinType } from '../../client'; -import type { IRange } from './range'; -import type { Burn } from './burn'; -import { UTXOInput } from '../block/input'; -import { Output } from '../block/output'; - -/** Options to build a new block, possibly with payloads */ -export interface IBuildBlockOptions { - coinType?: CoinType; - accountIndex?: number; - initialAddressIndex?: number; - inputs?: UTXOInput[]; - inputRange?: IRange; - /** Bech32 encoded output address and amount */ - output?: IClientBlockBuilderOutputAddress; - /** Hex encoded output address and amount */ - outputHex?: IClientBlockBuilderOutputAddress; - outputs?: Output[]; - customRemainderAddress?: string; - tag?: string; - data?: string; - /** Parent block IDs */ - parents?: string[]; - /** Explicit burning of aliases, nfts, foundries and native tokens */ - burn?: Burn; -} - -/** Address with base coin amount */ -export interface IClientBlockBuilderOutputAddress { - address: string; - amount: string; -} diff --git a/bindings/nodejs/lib/types/client/index.ts b/bindings/nodejs/lib/types/client/index.ts index 13de558dcc..8a65bb0b07 100644 --- a/bindings/nodejs/lib/types/client/index.ts +++ b/bindings/nodejs/lib/types/client/index.ts @@ -1,7 +1,6 @@ export * from './output_builder_params'; export * from './bridge'; -export * from './build-block-options'; export * from './burn'; export * from './client-options'; export * from './constants'; diff --git a/bindings/nodejs/tests/client/examples.spec.ts b/bindings/nodejs/tests/client/examples.spec.ts index 0e2a61cef4..dcd7d848c1 100644 --- a/bindings/nodejs/tests/client/examples.spec.ts +++ b/bindings/nodejs/tests/client/examples.spec.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, it } from '@jest/globals'; -import { Client, utf8ToHex, Utils, Block, OutputResponse, SecretManager, TaggedDataPayload, CommonOutput } from '../../'; +import { Client, utf8ToHex, Utils, OutputResponse, SecretManager, TaggedDataPayload, CommonOutput } from '../../'; import '../customMatchers'; import 'dotenv/config'; import * as addressOutputs from '../fixtures/addressOutputs.json'; @@ -139,27 +139,26 @@ describe.skip('Main examples', () => { ).toBe(200); }); - it('sends a block', async () => { - const blockIdAndBlock = await client.buildAndPostBlock(); + // TODO: have a way in the bindings to send an empty block https://github.com/iotaledger/iota-sdk/issues/647 + // it('sends a block', async () => { + // const blockIdAndBlock = await client.buildAndPostBlock(); - expect(blockIdAndBlock[0]).toBeValidBlockId(); - }); + // expect(blockIdAndBlock[0]).toBeValidBlockId(); + // }); it('gets block data', async () => { - const blockIdAndBlock = await client.buildAndPostBlock(); + const tips = await client.getTips(); - const blockData = await client.getBlock(blockIdAndBlock[0]); - const blockMetadata = await client.getBlockMetadata(blockIdAndBlock[0]); + const blockData = await client.getBlock(tips[0]); + const blockId = Utils.blockId(blockData) + expect(tips[0]).toStrictEqual(blockId); - expect(blockData).toStrictEqual(blockIdAndBlock[1]); + const blockMetadata = await client.getBlockMetadata(tips[0]); expect(blockMetadata.blockId).toBeValidBlockId(); }); it('sends a block with a tagged data payload', async () => { - const blockIdAndBlock = await client.buildAndPostBlock(secretManager, { - tag: utf8ToHex('Hello'), - data: utf8ToHex('Tangle'), - }); + const blockIdAndBlock = await client.postBlockPayload(new TaggedDataPayload( utf8ToHex('Hello'), utf8ToHex('Tangle'))); const fetchedBlock = await client.getBlock(blockIdAndBlock[0]); @@ -167,22 +166,4 @@ describe.skip('Main examples', () => { new TaggedDataPayload( utf8ToHex('Hello'), utf8ToHex('Tangle')) ); }); - - it('sends a transaction', async () => { - const addresses = await new SecretManager(secretManager).generateEd25519Addresses({ - range: { - start: 1, - end: 2, - }, - }); - - const blockIdAndBlock = await client.buildAndPostBlock(secretManager, { - output: { - address: addresses[0], - amount: '1000000', - }, - }); - - expect(blockIdAndBlock[0]).toBeValidBlockId(); - }); }); diff --git a/bindings/nodejs/tests/client/messageMethods.spec.ts b/bindings/nodejs/tests/client/messageMethods.spec.ts index d5cecaf0f1..76aefcfcba 100644 --- a/bindings/nodejs/tests/client/messageMethods.spec.ts +++ b/bindings/nodejs/tests/client/messageMethods.spec.ts @@ -5,7 +5,7 @@ import { describe, it } from '@jest/globals'; import 'reflect-metadata'; import 'dotenv/config'; -import { Client } from '../../lib/client'; +import { Client, utf8ToHex, TaggedDataPayload } from '../../'; import '../customMatchers'; const client = new Client({ @@ -20,7 +20,7 @@ const client = new Client({ // Skip for CI describe.skip('Block methods', () => { it('sends a block raw', async () => { - const blockIdAndBlock = await client.buildAndPostBlock(); + const blockIdAndBlock = await client.postBlockPayload(new TaggedDataPayload( utf8ToHex('Hello'), utf8ToHex('Tangle'))); const blockId = await client.postBlockRaw(blockIdAndBlock[1]); @@ -35,65 +35,62 @@ describe.skip('Block methods', () => { }); it('gets block as raw bytes', async () => { - const blockIdAndBlock = await client.buildAndPostBlock(); + const tips = await client.getTips(); - const blockRaw = await client.getBlockRaw(blockIdAndBlock[0]); + const blockRaw = await client.getBlockRaw(tips[0]); expect(blockRaw).toBeDefined(); }); it('promotes a block', async () => { - const blockIdAndBlock = await client.buildAndPostBlock(); + const tips = await client.getTips(); // Promote a block without checking if it should be promoted const promoteUnchecked = await client.promoteUnchecked( - blockIdAndBlock[0], + tips[0], ); - expect(promoteUnchecked[1].parents).toContain(blockIdAndBlock[0]); + expect(promoteUnchecked[1].parents).toContain(tips[0]); // Returns expected error: no need to promote or reattach. - await expect(client.promote(blockIdAndBlock[0])).rejects.toMatch( + await expect(client.promote(tips[0])).rejects.toMatch( 'NoNeedPromoteOrReattach', ); }); it('reattaches a block', async () => { - const blockIdAndBlock = await client.buildAndPostBlock(); + const tips = await client.getTips(); // Reattach a block without checking if it should be reattached const reattachUnchecked = await client.reattachUnchecked( - blockIdAndBlock[0], + tips[0], ); expect(reattachUnchecked[0]).toBeValidBlockId(); expect(reattachUnchecked[1]).toBeDefined(); // Returns expected error: no need to promote or reattach. - await expect(client.reattach(blockIdAndBlock[0])).rejects.toMatch( + await expect(client.reattach(tips[0])).rejects.toMatch( 'NoNeedPromoteOrReattach', ); }); // Skip by default, retryUntilIncluded can be slow it.skip('retries a block', async () => { - const blockIdAndBlock = await client.buildAndPostBlock(); - const blockId = await client.postBlock(blockIdAndBlock[1]); - - expect(blockIdAndBlock[0]).toBe(blockId); + const tips = await client.getTips(); // Retries (promotes or reattaches) a block for provided block id until it's included // (referenced by a milestone). Default interval is 5 seconds and max attempts is 40. const retryUntilIncluded = await client.retryUntilIncluded( - blockId, + tips[0], 2, 5, ); //Returns the included block at first position and additional reattached blocks - expect(retryUntilIncluded[0][0]).toBe(blockId); + expect(retryUntilIncluded[0][0]).toBe(tips[0]); // Returns expected error: no need to promote or reattach. - await expect(client.retry(blockId)).rejects.toMatch( + await expect(client.retry(tips[0])).rejects.toMatch( 'NoNeedPromoteOrReattach', ); }); diff --git a/bindings/nodejs/tests/client/offlineSigningExamples.spec.ts b/bindings/nodejs/tests/client/offlineSigningExamples.spec.ts deleted file mode 100644 index 206cbee70d..0000000000 --- a/bindings/nodejs/tests/client/offlineSigningExamples.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { describe, it, beforeAll, afterAll } from '@jest/globals'; -import 'reflect-metadata'; -import 'dotenv/config'; - -import { - Client, - Payload, - PreparedTransactionData, - RegularTransactionEssence, - SecretManager, - SHIMMER_TESTNET_BECH32_HRP, - TransactionPayload, - Utils -} from '../../'; -import '../customMatchers'; -import { addresses } from '../fixtures/addresses'; -import * as signedTransactionJson from '../fixtures/signedTransaction.json'; -import * as sigUnlockPreparedTx from '../fixtures/sigUnlockPreparedTx.json'; - -describe('Offline signing examples', () => { - let onlineClient: Client; - let offlineClient: Client; - - const secretManager = { - mnemonic: - 'endorse answer radar about source reunion marriage tag sausage weekend frost daring base attack because joke dream slender leisure group reason prepare broken river', - }; - - beforeAll(() => { - onlineClient = new Client({ - nodes: [ - { - url: process.env.NODE_URL || 'http://localhost:14265', - }, - ], - localPow: true, - }); - offlineClient = new Client({}) - }); - - afterAll(async () => { - await onlineClient.destroy(); - await offlineClient.destroy(); - }); - - it('generates addresses offline', async () => { - const addresses = await new SecretManager(secretManager).generateEd25519Addresses({ - range: { - start: 0, - end: 1, - }, - bech32Hrp: SHIMMER_TESTNET_BECH32_HRP, - }); - - expect(addresses.length).toBe(1); - addresses.forEach((address) => { - expect(address).toBeValidAddress(); - }); - }); - - // transaction tests disabled for workflows, because they fail if we don't have funds - it.skip('prepares and signs a transaction', async () => { - const address = - 'rms1qqv5avetndkxzgr3jtrswdtz5ze6mag20s0jdqvzk4fwezve8q9vkpnqlqe'; - const amount = 1000000; - - const inputs = await onlineClient.findInputs(addresses, amount); - - const preparedTransaction = await onlineClient.prepareTransaction( - undefined, - { - inputs, - output: { address, amount: amount.toString() }, - }, - ); - - expect(preparedTransaction.essence).toBeInstanceOf(RegularTransactionEssence); - - const signedTransaction = await offlineClient.signTransaction( - secretManager, - // Imported JSON is typed with literal types - preparedTransaction, - ); - - expect(signedTransaction).toBeInstanceOf(TransactionPayload); - }); - - // transaction tests disabled for workflows, because they fail if we don't have funds - it.skip('sends a transaction', async () => { - // Send block with the signed transaction as a payload - const blockIdAndBlock = await onlineClient.postBlockPayload( - // Imported JSON is typed with literal types - signedTransactionJson as unknown as Payload, - ); - - expect(blockIdAndBlock[1].payload).toBeDefined(); - - const blockId = Utils.blockId(blockIdAndBlock[1]); - - expect(blockId).toBe(blockIdAndBlock[0]); - expect(blockId).toBeValidBlockId; - }); - it('create a signature unlock', async () => { - // Verifies that an unlock created in Rust matches that created by the binding when the mnemonic is identical. - const secretManager = { - mnemonic: - 'good reason pipe keen price glory mystery illegal loud isolate wolf trash raise guilt inflict guide modify bachelor length galaxy lottery there mango comfort', - }; - const preparedTx = sigUnlockPreparedTx as any as PreparedTransactionData; - const txEssenceHash = Utils.hashTransactionEssence( - preparedTx.essence, - ); - - if (preparedTx.inputsData[0].chain === undefined) { - throw 'chain is undefined'; - } - - const unlock = await new SecretManager(secretManager).signatureUnlock( - txEssenceHash, - preparedTx.inputsData[0].chain, - ); - - expect(unlock).toStrictEqual({ - type: 0, - signature: { - type: 0, - publicKey: - '0xb76a23de43b8132ae18a4a479cb158563e76d89bd1e20d3ccdc7fd1db2a009d4', - signature: - '0xcd905dae45010980e95ddddaebede830d9b8d7489c67e4d91a0cbfbdb03b02d337dc8162f15582ad18ee0e953cd517e32f809d533f9ccfb4beee5cb2cba16d0c', - }, - }); - }); -}); diff --git a/bindings/python/examples/client/06_simple_block.py b/bindings/python/examples/client/06_simple_block.py index 54753d5fd4..ad69a7e7f1 100644 --- a/bindings/python/examples/client/06_simple_block.py +++ b/bindings/python/examples/client/06_simple_block.py @@ -1,4 +1,4 @@ -from iota_sdk import Client +from iota_sdk import Client, TaggedDataPayload, utf8_to_hex from dotenv import load_dotenv import os @@ -10,5 +10,6 @@ client = Client(nodes=[node_url]) # Create and post a block without payload -block = client.build_and_post_block() +# TODO: have a way in the bindings to send an empty block https://github.com/iotaledger/iota-sdk/issues/647 +block = client.submit_payload(TaggedDataPayload(utf8_to_hex("tag"), utf8_to_hex("data"))) print(f'Empty block sent: {os.environ["EXPLORER_URL"]}/block/{block[0]}') \ No newline at end of file diff --git a/bindings/python/examples/client/08_data_block.py b/bindings/python/examples/client/08_data_block.py index 1b57b74ac6..f7a7a9658c 100644 --- a/bindings/python/examples/client/08_data_block.py +++ b/bindings/python/examples/client/08_data_block.py @@ -1,4 +1,4 @@ -from iota_sdk import Client, utf8_to_hex, hex_to_utf8 +from iota_sdk import Client, utf8_to_hex, hex_to_utf8, TaggedDataPayload from dotenv import load_dotenv import json import os @@ -11,8 +11,7 @@ client = Client(nodes=[node_url]) # Create and post a block with a tagged data payload -block = client.build_and_post_block( - tag=utf8_to_hex('hello'), data=utf8_to_hex('hello')) +block = client.submit_payload(TaggedDataPayload(utf8_to_hex("tag"), utf8_to_hex("data"))) print(f'Data block sent: {os.environ["EXPLORER_URL"]}/block/{block[0]}') diff --git a/bindings/python/examples/client/09_transaction.py b/bindings/python/examples/client/09_transaction.py deleted file mode 100644 index f38942dad2..0000000000 --- a/bindings/python/examples/client/09_transaction.py +++ /dev/null @@ -1,25 +0,0 @@ -from iota_sdk import Client, MnemonicSecretManager, AddressAndAmount -from dotenv import load_dotenv -import os - -load_dotenv() - -node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') - -# Create a Client instance -client = Client(nodes=[node_url]) - -if 'MNEMONIC' not in os.environ: - raise Exception(".env MNEMONIC is undefined, see .env.example") - -secret_manager = MnemonicSecretManager( - os.environ['MNEMONIC']) - -address_and_amount = AddressAndAmount( - 'rms1qzpf0tzpf8yqej5zyhjl9k3km7y6j0xjnxxh7m2g3jtj2z5grej67sl6l46', - 1000000, -) - -# Create and post a block with a transaction -block = client.build_and_post_block(secret_manager, output=address_and_amount) -print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{block[0]}') diff --git a/bindings/python/examples/client/10_mint_nft.py b/bindings/python/examples/client/10_mint_nft.py deleted file mode 100644 index 415eb8e336..0000000000 --- a/bindings/python/examples/client/10_mint_nft.py +++ /dev/null @@ -1,36 +0,0 @@ -from iota_sdk import * -from dotenv import load_dotenv -import os - -load_dotenv() - -node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') - -# Create a Client instance -client = Client(nodes=[node_url]) - -if 'MNEMONIC' not in os.environ: - raise Exception(".env MNEMONIC is undefined, see .env.example") - -secret_manager = MnemonicSecretManager(os.environ['MNEMONIC']) - -nft_output = client.build_nft_output( - unlock_conditions=[ - AddressUnlockCondition( - Ed25519Address(Utils.bech32_to_hex( - 'rms1qzpf0tzpf8yqej5zyhjl9k3km7y6j0xjnxxh7m2g3jtj2z5grej67sl6l46')), - ) - ], - nft_id='0x0000000000000000000000000000000000000000000000000000000000000000', - amount=1000000, - immutable_features=[ - MetadataFeature(utf8_to_hex('Hello, World!')) - ], - features=[ - MetadataFeature(utf8_to_hex('Hello, World!')) - ] -) - -# Create and post a block with the nft output -block = client.build_and_post_block(secret_manager, outputs=[nft_output]) -print(f'NFT mint block sent: {os.environ["EXPLORER_URL"]}/block/{block[0]}') diff --git a/bindings/python/examples/client/post_raw_block.py b/bindings/python/examples/client/post_raw_block.py index 102bb33234..14cce00c88 100644 --- a/bindings/python/examples/client/post_raw_block.py +++ b/bindings/python/examples/client/post_raw_block.py @@ -1,4 +1,4 @@ -from iota_sdk import Client, MnemonicSecretManager +from iota_sdk import Client, TaggedDataPayload, utf8_to_hex from dotenv import load_dotenv import os @@ -10,7 +10,7 @@ client = Client(nodes=[node_url]) # Create and post a block without payload -block_id = client.build_and_post_block()[0] +block_id = client.submit_payload(TaggedDataPayload(utf8_to_hex("tag"), utf8_to_hex("data")))[0] blockBytes = client.get_block_raw(block_id) # Post raw block diff --git a/bindings/python/examples/client/submit_and_read_block.py b/bindings/python/examples/client/submit_and_read_block.py index 861f6dcc26..c21f4bd3b8 100644 --- a/bindings/python/examples/client/submit_and_read_block.py +++ b/bindings/python/examples/client/submit_and_read_block.py @@ -8,7 +8,7 @@ # Import the python iota client # Make sure you have first installed it with `pip install iota_sdk` -from iota_sdk import Client, hex_to_utf8, utf8_to_hex +from iota_sdk import Client, hex_to_utf8, utf8_to_hex, TaggedDataPayload from dotenv import load_dotenv import os @@ -51,12 +51,11 @@ # Step 2: Submit your block to the Shimmer test network ######################################################## # A block must be built, to which the payload is attached. -# The build_and_post_block function handles this task. +# The submit_payload function handles this task. # Create and post a block with a tagged data payload # The client returns your block data (blockIdAndBlock) -blockIdAndBlock = client.build_and_post_block( - secret_manager=None, tag=tag_hex, data=message_hex) +blockIdAndBlock = client.submit_payload(TaggedDataPayload(utf8_to_hex("tag"), utf8_to_hex("data"))) block_id = blockIdAndBlock[0] block = blockIdAndBlock[1] diff --git a/bindings/python/iota_sdk/client/_high_level_api.py b/bindings/python/iota_sdk/client/_high_level_api.py index a277856dbb..b5bf85b024 100644 --- a/bindings/python/iota_sdk/client/_high_level_api.py +++ b/bindings/python/iota_sdk/client/_high_level_api.py @@ -1,8 +1,6 @@ # Copyright 2023 IOTA Stiftung # SPDX-License-Identifier: Apache-2.0 -from iota_sdk.secret_manager.secret_manager import LedgerNanoSecretManager, MnemonicSecretManager, StrongholdSecretManager, SeedSecretManager -from iota_sdk.types.block import Block from iota_sdk.types.common import HexStr from iota_sdk.types.output import OutputWithMetadata from iota_sdk.types.output_id import OutputId @@ -90,22 +88,6 @@ def retry_until_included(self, block_id: HexStr, interval: Optional[int] = None, 'maxAttempts': max_attempts }) - def block_class(block_id_and_block): - block_id_and_block[1] = Block.from_dict(block_id_and_block[1]) - return block_id_and_block - blockIdsAndBlocks = [block_class(block_id_and_block) - for block_id_and_block in result] - return blockIdsAndBlocks - - def consolidate_funds(self, secret_manager: LedgerNanoSecretManager | MnemonicSecretManager | SeedSecretManager | StrongholdSecretManager, generate_addresses_options: GenerateAddressesOptions) -> str: - """Function to consolidate all funds from a range of addresses to the address with the lowest index in that range - Returns the address to which the funds got consolidated, if any were available. - """ - return self._call_method('consolidateFunds', { - 'secretManager': secret_manager, - 'generateAddressesOptions': generate_addresses_options.as_dict(), - }) - def find_inputs(self, addresses: List[str], amount: int): """Function to find inputs from addresses for a provided amount (useful for offline signing) """ diff --git a/bindings/python/iota_sdk/client/client.py b/bindings/python/iota_sdk/client/client.py index a22cc1cd45..b0cc330393 100644 --- a/bindings/python/iota_sdk/client/client.py +++ b/bindings/python/iota_sdk/client/client.py @@ -373,82 +373,6 @@ def build_nft_output(self, 'immutableFeatures': immutable_features })) - def build_and_post_block(self, - secret_manager=None, - account_index: Optional[int] = None, - coin_type: Optional[int] = None, - custom_remainder_address: Optional[str] = None, - data: Optional[HexStr] = None, - initial_address_index: Optional[int] = None, - input_range_start: Optional[int] = None, - input_range_end: Optional[int] = None, - inputs: Optional[List[Dict[str, Any]]] = None, - output: Optional[AddressAndAmount] = None, - outputs: Optional[List[Any]] = None, - tag: Optional[HexStr] = None) -> List[HexStr | Block]: - """Build and post a block. - - Parameters - ---------- - account_index : int - Account Index - coin_type : int - Coin type. The CoinType enum can be used - custom_remainder_address : string - Address to send the remainder funds to - data : str - Hex encoded data - initial_address_index : int - Initial address index - input_range_start : int - Start of the input range - input_range_end : int - End of the input range - inputs : Array of Inputs - Inputs to use - output : AddressAndAmount - Address and amount to send to - outputs : Array of Outputs - Outputs to use - tag : string - Hex encoded tag - - Returns - ------- - Block as dict - - """ - options = dict(locals()) - - del options['self'] - del options['secret_manager'] - - options = {k: v for k, v in options.items() if v != None} - - if 'output' in options: - options['output'] = options.pop('output').as_dict() - - if 'coin_type' in options: - options['coin_type'] = int(options.pop('coin_type')) - - is_start_set = 'input_range_start' in options - is_end_set = 'input_range_end' in options - if is_start_set or is_end_set: - options['range'] = {} - if is_start_set: - options['range']['start'] = options.pop('start') - if is_end_set: - options['range']['end'] = options.pop('end') - - options = humps.camelize(options) - - result = self._call_method('buildAndPostBlock', { - 'secretManager': secret_manager, - 'options': options - }) - result[1] = Block.from_dict(result[1]) - return result - def get_node(self) -> Dict[str, Any]: """Get a node candidate from the healthy node pool. """ @@ -494,14 +418,6 @@ def unhealthy_nodes(self) -> List[Dict[str, Any]]: """ return self._call_method('unhealthyNodes') - def prepare_transaction(self, secret_manager=None, options=None): - """Prepare a transaction for signing. - """ - return self._call_method('prepareTransaction', { - 'secretManager': secret_manager, - 'options': options - }) - def sign_transaction(self, secret_manager, prepared_transaction_data): """Sign a transaction. """ diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 7fda24b09c..63322533cf 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -301,38 +301,6 @@ name = "block_tagged_data" path = "examples/client/block/04_block_tagged_data.rs" required-features = [ "client" ] -[[example]] -name = "custom_inputs" -path = "examples/client/block/custom_inputs.rs" -required-features = [ "client" ] - -[[example]] -name = "output" -path = "examples/client/block/output.rs" -required-features = [ "client" ] - -[[example]] -name = "transaction" -path = "examples/client/block/transaction.rs" -required-features = [ "client" ] - -# High Level examples - -[[example]] -name = "address_consolidation" -path = "examples/client/high_level/consolidation.rs" -required-features = [ "client" ] - -[[example]] -name = "inputs_from_transaction_id" -path = "examples/client/high_level/inputs_from_transaction_id.rs" -required-features = [ "client" ] - -[[example]] -name = "search_address" -path = "examples/client/high_level/search_address.rs" -required-features = [ "client" ] - # Node API core examples [[example]] @@ -454,11 +422,6 @@ name = "client_config" path = "examples/client/client_config.rs" required-features = [ "client" ] -[[example]] -name = "custom_remainder_address" -path = "examples/client/custom_remainder_address.rs" -required-features = [ "client" ] - [[example]] name = "get_block" path = "examples/client/get_block.rs" @@ -484,41 +447,6 @@ name = "stronghold" path = "examples/client/stronghold.rs" required-features = [ "client", "stronghold" ] -[[example]] -name = "0_address_generation" -path = "examples/client/offline_signing/0_address_generation.rs" -required-features = [ "client" ] - -[[example]] -name = "1_transaction_preparation" -path = "examples/client/offline_signing/1_transaction_preparation.rs" -required-features = [ "client" ] - -[[example]] -name = "2_transaction_signing" -path = "examples/client/offline_signing/2_transaction_signing.rs" -required-features = [ "client" ] - -[[example]] -name = "3_send_block" -path = "examples/client/offline_signing/3_send_block.rs" -required-features = [ "client" ] - -[[example]] -name = "all" -path = "examples/client/output/all.rs" -required-features = [ "client" ] - -[[example]] -name = "all_automatic_input_selection" -path = "examples/client/output/all_automatic_input_selection.rs" -required-features = [ "client" ] - -[[example]] -name = "alias" -path = "examples/client/output/alias.rs" -required-features = [ "client" ] - [[example]] name = "build_alias_output" path = "examples/client/output/build_alias_output.rs" @@ -529,71 +457,11 @@ name = "build_nft_output" path = "examples/client/output/build_nft_output.rs" required-features = [ "client" ] -[[example]] -name = "recursive_alias" -path = "examples/client/output/recursive_alias.rs" -required-features = [ "client" ] - -[[example]] -name = "basic" -path = "examples/client/output/basic.rs" -required-features = [ "client" ] - [[example]] name = "build_basic_output" path = "examples/client/output/build_basic_output.rs" required-features = [ "client" ] -[[example]] -name = "expiration" -path = "examples/client/output/expiration.rs" -required-features = [ "client" ] - -[[example]] -name = "foundry" -path = "examples/client/output/foundry.rs" -required-features = [ "client" ] - -[[example]] -name = "micro_transaction" -path = "examples/client/output/micro_transaction.rs" -required-features = [ "client" ] - -[[example]] -name = "native_tokens" -path = "examples/client/output/native_tokens.rs" -required-features = [ "client" ] - -[[example]] -name = "nft" -path = "examples/client/output/nft.rs" -required-features = [ "client" ] - -[[example]] -name = "participation" -path = "examples/client/participation.rs" -required-features = [ "client", "participation" ] - -[[example]] -name = "quorum" -path = "examples/client/quorum.rs" -required-features = [ "client" ] - -[[example]] -name = "send_all" -path = "examples/client/send_all.rs" -required-features = [ "client" ] - -[[example]] -name = "client_split_funds" -path = "examples/client/split_funds.rs" -required-features = [ "client" ] - -[[example]] -name = "tagged_data_to_utf8" -path = "examples/client/tagged_data_to_utf8.rs" -required-features = [ "client" ] - ### Wallet [[example]] diff --git a/sdk/examples/client/02_address_balance.rs b/sdk/examples/client/02_address_balance.rs index e3622665a0..80d6eb4f47 100644 --- a/sdk/examples/client/02_address_balance.rs +++ b/sdk/examples/client/02_address_balance.rs @@ -56,8 +56,7 @@ async fn main() -> Result<()> { // Calculate the total amount and native tokens let mut total_amount = 0; let mut total_native_tokens = NativeTokensBuilder::new(); - for output_response in outputs_responses { - let output = output_response.output(); + for output in outputs_responses { if let Some(native_tokens) = output.native_tokens() { total_native_tokens.add_native_tokens(native_tokens.clone())?; } diff --git a/sdk/examples/client/block/00_block_no_payload.rs b/sdk/examples/client/block/00_block_no_payload.rs index 4f6a9b0f38..f8a79b8a72 100644 --- a/sdk/examples/client/block/00_block_no_payload.rs +++ b/sdk/examples/client/block/00_block_no_payload.rs @@ -21,7 +21,7 @@ async fn main() -> Result<()> { let client = Client::builder().with_node(&node_url)?.finish().await?; // Create and send the block. - let block = client.build_block().finish().await?; + let block = client.finish_block_builder(None, None).await?; println!("{block:#?}"); diff --git a/sdk/examples/client/block/01_block_confirmation_time.rs b/sdk/examples/client/block/01_block_confirmation_time.rs index b0e9f17fac..c63c5309e9 100644 --- a/sdk/examples/client/block/01_block_confirmation_time.rs +++ b/sdk/examples/client/block/01_block_confirmation_time.rs @@ -21,7 +21,7 @@ async fn main() -> Result<()> { let client = Client::builder().with_node(&node_url)?.finish().await?; // Create and send a block. - let block = client.build_block().finish().await?; + let block = client.finish_block_builder(None, None).await?; let block_id = block.id(); println!("{block:#?}"); diff --git a/sdk/examples/client/block/02_block_custom_parents.rs b/sdk/examples/client/block/02_block_custom_parents.rs index a063f0b9b5..aaea3ef1ce 100644 --- a/sdk/examples/client/block/02_block_custom_parents.rs +++ b/sdk/examples/client/block/02_block_custom_parents.rs @@ -8,7 +8,10 @@ //! cargo run --release --example block_custom_parents //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::{ + client::{Client, Result}, + types::block::parent::Parents, +}; #[tokio::main] async fn main() -> Result<()> { @@ -25,7 +28,9 @@ async fn main() -> Result<()> { println!("Custom tips:\n{tips:#?}"); // Create and send the block with custom parents. - let block = client.build_block().with_parents(tips)?.finish().await?; + let block = client + .finish_block_builder(Some(Parents::from_vec(tips)?), None) + .await?; println!("{block:#?}"); diff --git a/sdk/examples/client/block/03_block_custom_payload.rs b/sdk/examples/client/block/03_block_custom_payload.rs index 5271b52d7d..2484ba780c 100644 --- a/sdk/examples/client/block/03_block_custom_payload.rs +++ b/sdk/examples/client/block/03_block_custom_payload.rs @@ -28,8 +28,7 @@ async fn main() -> Result<()> { // Create and send the block with the custom payload. let block = client - .build_block() - .finish_block(Some(Payload::from(tagged_data_payload))) + .finish_block_builder(None, Some(Payload::from(tagged_data_payload))) .await?; println!("{block:#?}"); diff --git a/sdk/examples/client/block/04_block_tagged_data.rs b/sdk/examples/client/block/04_block_tagged_data.rs index 88a74d5616..1651feba91 100644 --- a/sdk/examples/client/block/04_block_tagged_data.rs +++ b/sdk/examples/client/block/04_block_tagged_data.rs @@ -10,7 +10,7 @@ use iota_sdk::{ client::{Client, Result}, - types::block::payload::Payload, + types::block::payload::{Payload, TaggedDataPayload}, }; #[tokio::main] @@ -20,18 +20,17 @@ async fn main() -> Result<()> { let node_url = std::env::var("NODE_URL").unwrap(); - let tag = std::env::args().nth(1).unwrap_or_else(|| "Hello".to_string()); - let data = std::env::args().nth(2).unwrap_or_else(|| "Tangle".to_string()); - // Create a node client. let client = Client::builder().with_node(&node_url)?.finish().await?; // Create and send the block with tag and data. let block = client - .build_block() - .with_tag(tag.as_bytes().to_vec()) - .with_data(data.as_bytes().to_vec()) - .finish() + .finish_block_builder( + None, + Some(Payload::TaggedData(Box::new( + TaggedDataPayload::new(b"Hello".to_vec(), b"Tangle".to_vec()).unwrap(), + ))), + ) .await?; println!("{block:#?}\n"); diff --git a/sdk/examples/client/block/custom_inputs.rs b/sdk/examples/client/block/custom_inputs.rs deleted file mode 100644 index 68e41221c9..0000000000 --- a/sdk/examples/client/block/custom_inputs.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will send a certain amount of tokens to some address using custom inputs. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example custom_inputs [AMOUNT] [ADDRESS] -//! ``` - -use iota_sdk::{ - client::{ - api::GetAddressesOptions, node_api::indexer::query_parameters::QueryParameter, request_funds_from_faucet, - secret::SecretManager, Client, Result, - }, - types::block::input::UtxoInput, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let node_url = std::env::var("NODE_URL").unwrap(); - let faucet_url = std::env::var("FAUCET_URL").unwrap(); - - let amount = std::env::args() - .nth(1) - .map(|s| s.parse::().unwrap()) - .unwrap_or(1_000_000u64); - - // Create a node client. - let client = Client::builder().with_node(&node_url)?.finish().await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - // Get the first address of the seed - let first_address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - println!("1st address: {first_address:#?}"); - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&faucet_url, &first_address).await? - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let output_ids_response = client - .basic_output_ids([QueryParameter::Address(first_address)]) - .await?; - println!("{output_ids_response:?}"); - - // If no custom address is provided, we will use the first address from the seed. - let recv_address = std::env::args() - .nth(2) - .map(|s| s.parse().unwrap()) - .unwrap_or(first_address); - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_input(UtxoInput::from(output_ids_response.items[0]))? - .with_output(recv_address, amount) - .await? - .finish() - .await?; - - println!("{block:#?}"); - - println!( - "Block with custom inputs sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - Ok(()) -} diff --git a/sdk/examples/client/block/output.rs b/sdk/examples/client/block/output.rs deleted file mode 100644 index 3be62122ac..0000000000 --- a/sdk/examples/client/block/output.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will send a certain amount of tokens to some address by specifying the outputs. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example output [AMOUNT] [ADDRESS] -//! ``` - -use iota_sdk::{ - client::{api::GetAddressesOptions, secret::SecretManager, utils::request_funds_from_faucet, Client, Result}, - types::block::output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder}, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let node_url = std::env::var("NODE_URL").unwrap(); - let faucet_url = std::env::var("FAUCET_URL").unwrap(); - - let amount = std::env::args() - .nth(1) - .map(|s| s.parse::().unwrap()) - .unwrap_or(1_000_000u64); - - // Create a node client. - let client = Client::builder().with_node(&node_url)?.finish().await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let token_supply = client.get_token_supply().await?; - - // Get the first address of the seed - let first_address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - println!("1st address: {first_address:#?}"); - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&faucet_url, &first_address).await? - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - // If no custom address is provided, we will use the first address from the seed. - let recv_address = std::env::args() - .nth(2) - .map(|s| s.parse().unwrap()) - .unwrap_or(first_address); - - let outputs = [BasicOutputBuilder::new_with_amount(amount) - .add_unlock_condition(AddressUnlockCondition::new(recv_address)) - .finish_output(token_supply)?]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!("{block:#?}"); - - println!( - "Block with custom output sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - Ok(()) -} diff --git a/sdk/examples/client/block/transaction.rs b/sdk/examples/client/block/transaction.rs deleted file mode 100644 index 18b0294209..0000000000 --- a/sdk/examples/client/block/transaction.rs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will send a certain amount of tokens to some address by using the block builder only. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example transaction [AMOUNT] [ADDRESS] -//! ``` - -use iota_sdk::client::{api::GetAddressesOptions, request_funds_from_faucet, secret::SecretManager, Client, Result}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let node_url = std::env::var("NODE_URL").unwrap(); - let faucet_url = std::env::var("FAUCET_URL").unwrap(); - - let amount = std::env::args() - .nth(1) - .map(|s| s.parse::().unwrap()) - .unwrap_or(1_000_000u64); - - // Create a node client. - let client = Client::builder().with_node(&node_url)?.finish().await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - // Get the first address of the seed - let first_address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - println!("1st address: {first_address:#?}"); - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&faucet_url, &first_address).await? - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - // If no custom address is provided, we will use the first address from the seed. - let recv_address = std::env::args() - .nth(2) - .map(|s| s.parse().unwrap()) - .unwrap_or(first_address); - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - // Insert the output address and amount to spent. The amount cannot be zero. - .with_output(recv_address, amount) - .await? - .finish() - .await?; - - println!("{block:#?}"); - - println!( - "Transaction sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - Ok(()) -} diff --git a/sdk/examples/client/custom_remainder_address.rs b/sdk/examples/client/custom_remainder_address.rs deleted file mode 100644 index cd85414994..0000000000 --- a/sdk/examples/client/custom_remainder_address.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will send a certain amount of tokens to a given receiver address and any remaining -//! tokens to a custom remainder address. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example custom_remainder_address [AMOUNT] -//! ``` - -use iota_sdk::client::{ - api::GetAddressesOptions, node_api::indexer::query_parameters::QueryParameter, request_funds_from_faucet, - secret::SecretManager, Client, Result, -}; - -#[tokio::main] -async fn main() -> Result<()> { - let amount = std::env::args() - .nth(1) - .map(|s| s.parse::().unwrap()) - .unwrap_or(9_000_000); - - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - // Create a client instance. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let addresses = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::from_client(&client) - .await? - .with_account_index(0) - .with_range(0..3), - ) - .await?; - - let sender_address = &addresses[0]; - let receiver_address = &addresses[1]; - let remainder_address = &addresses[2]; - - println!("sender address: {sender_address}"); - println!("receiver address: {receiver_address}"); - println!("remainder address: {remainder_address}"); - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), sender_address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let output_ids_response = client - .basic_output_ids([QueryParameter::Address(*sender_address)]) - .await?; - println!("{output_ids_response:#?}"); - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_output(receiver_address, amount) - .await? - .with_custom_remainder_address(remainder_address)? - .finish() - .await?; - - println!( - "Block with custom remainder sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - Ok(()) -} diff --git a/sdk/examples/client/ledger_nano_transaction.rs b/sdk/examples/client/ledger_nano_transaction.rs index 3029286067..efd0070592 100644 --- a/sdk/examples/client/ledger_nano_transaction.rs +++ b/sdk/examples/client/ledger_nano_transaction.rs @@ -23,7 +23,7 @@ use iota_sdk::client::{ Client, Result, }; -const AMOUNT: u64 = 1_000_000; +// const AMOUNT: u64 = 1_000_000; #[tokio::main] async fn main() -> Result<()> { @@ -50,22 +50,25 @@ async fn main() -> Result<()> { println!("List of generated public addresses:\n{addresses:#?}\n"); - let recv_address = addresses[1]; + todo!("build a transaction that gets signed with the ledger nano"); + // let block = client + // .block() + // .with_secret_manager(&secret_manager) + // // Insert the output address and amount to spent. The amount cannot be zero. + // .with_output( + // // We generate an address from our seed so that we send the funds to ourselves + // addresses[1], + // 1_000_000, + // ) + // .await? + // .finish() + // .await?; - println!("Sending {AMOUNT} to {recv_address}"); - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_output(recv_address, AMOUNT) - .await? - .finish() - .await?; - - println!( - "Block using ledger nano sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); + // println!( + // "Block using ledger nano sent: {}/block/{}", + // std::env::var("EXPLORER_URL").unwrap(), + // block.id() + // ); - Ok(()) + // Ok(()) } diff --git a/sdk/examples/client/node_api_core/04_post_block.rs b/sdk/examples/client/node_api_core/04_post_block.rs index 746f57e2f6..4c7c78a9cf 100644 --- a/sdk/examples/client/node_api_core/04_post_block.rs +++ b/sdk/examples/client/node_api_core/04_post_block.rs @@ -24,8 +24,7 @@ async fn main() -> Result<()> { let client = Client::builder().with_node(&node_url)?.finish().await?; // Create the block. - let block = client.build_block().finish().await?; - + let block = client.finish_block_builder(None, None).await?; // Post the block. let block_id = client.post_block(&block).await?; diff --git a/sdk/examples/client/node_api_core/05_post_block_raw.rs b/sdk/examples/client/node_api_core/05_post_block_raw.rs index f3a670ce53..def4eca144 100644 --- a/sdk/examples/client/node_api_core/05_post_block_raw.rs +++ b/sdk/examples/client/node_api_core/05_post_block_raw.rs @@ -24,8 +24,7 @@ async fn main() -> Result<()> { let client = Client::builder().with_node(&node_url)?.finish().await?; // Create the block. - let block = client.build_block().finish().await?; - + let block = client.finish_block_builder(None, None).await?; // Post the block as raw bytes. let block_id = client.post_block_raw(&block).await?; diff --git a/sdk/examples/client/offline_signing/0_address_generation.rs b/sdk/examples/client/offline_signing/0_address_generation.rs deleted file mode 100644 index a34e827889..0000000000 --- a/sdk/examples/client/offline_signing/0_address_generation.rs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we generate an address which will be used later to find inputs. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example 0_address_generation -//! ``` - -use iota_sdk::{ - client::{api::GetAddressesOptions, constants::SHIMMER_TESTNET_BECH32_HRP, secret::SecretManager, Result}, - types::block::address::Bech32Address, -}; - -const ADDRESS_FILE_NAME: &str = "examples/client/offline_signing/address.json"; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - // Generates an address offline. - let address = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::default() - .with_bech32_hrp(SHIMMER_TESTNET_BECH32_HRP) - // Currently only index 0 is supported for offline signing. - .with_range(0..1), - ) - .await?; - - write_address_to_file(ADDRESS_FILE_NAME, &address).await?; - - Ok(()) -} - -async fn write_address_to_file(path: impl AsRef, address: &[Bech32Address]) -> Result<()> { - use tokio::io::AsyncWriteExt; - - let json = serde_json::to_string_pretty(&address)?; - let mut file = tokio::io::BufWriter::new(tokio::fs::File::create(path).await.expect("failed to create file")); - - println!("{json}"); - - file.write_all(json.as_bytes()).await.expect("failed to write to file"); - - Ok(()) -} diff --git a/sdk/examples/client/offline_signing/1_transaction_preparation.rs b/sdk/examples/client/offline_signing/1_transaction_preparation.rs deleted file mode 100644 index e4f7467eb0..0000000000 --- a/sdk/examples/client/offline_signing/1_transaction_preparation.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we get inputs and prepare a transaction. -//! -//! Make sure to run `0_address_generation` before. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example 1_transaction_preparation [RECV ADDRESS] [AMOUNT] -//! ``` - -use iota_sdk::{ - client::{ - api::{PreparedTransactionData, PreparedTransactionDataDto}, - Client, Result, - }, - types::block::address::Bech32Address, -}; - -const ADDRESS_FILE_NAME: &str = "examples/client/offline_signing/address.json"; -const PREPARED_TRANSACTION_FILE_NAME: &str = "examples/client/offline_signing/prepared_transaction.json"; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let node_url = std::env::var("NODE_URL").unwrap(); - - // Address to which we want to send the amount. - let recv_address = std::env::args() - .nth(1) - .unwrap_or_else(|| "rms1qruzprxum2934lr3p77t96pzlecxv8pjzvtjrzdcgh2f5exa22n6ga0vm69".to_string()); - let recv_address = recv_address.as_str(); - - // The amount to send. - let amount = std::env::args() - .nth(2) - .map(|s| s.parse::().unwrap()) - .unwrap_or(1_000_000u64); - - // Create a node client. - let online_client = Client::builder().with_node(&node_url)?.finish().await?; - - // Recovers addresses from the previous example. - let addresses = read_addresses_from_file(ADDRESS_FILE_NAME).await?; - - // Gets enough inputs related to these addresses to cover the amount. - let inputs = online_client.find_inputs(addresses, amount).await?; - - // Prepares the transaction. - let mut transaction_builder = online_client.build_block(); - for input in inputs { - transaction_builder = transaction_builder.with_input(input)?; - } - let prepared_transaction = transaction_builder - .with_output(recv_address, amount) - .await? - .prepare_transaction() - .await?; - - println!("Prepared transaction sending {amount} to {recv_address}."); - - write_prepared_transaction_to_file(PREPARED_TRANSACTION_FILE_NAME, &prepared_transaction).await?; - - Ok(()) -} - -async fn read_addresses_from_file(path: impl AsRef) -> Result> { - use tokio::io::AsyncReadExt; - - let mut file = tokio::fs::File::open(&path).await.expect("failed to open file"); - let mut json = String::new(); - file.read_to_string(&mut json).await.expect("failed to read file"); - - Ok(serde_json::from_str(&json)?) -} - -async fn write_prepared_transaction_to_file( - path: impl AsRef, - prepared_transaction: &PreparedTransactionData, -) -> Result<()> { - use tokio::io::AsyncWriteExt; - - let json = serde_json::to_string_pretty(&PreparedTransactionDataDto::from(prepared_transaction))?; - let mut file = tokio::io::BufWriter::new(tokio::fs::File::create(path).await.expect("failed to create file")); - - println!("{json}"); - - file.write_all(json.as_bytes()).await.expect("failed to write file"); - - Ok(()) -} diff --git a/sdk/examples/client/offline_signing/2_transaction_signing.rs b/sdk/examples/client/offline_signing/2_transaction_signing.rs deleted file mode 100644 index da0e1a9a60..0000000000 --- a/sdk/examples/client/offline_signing/2_transaction_signing.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we sign the prepared transaction. -//! -//! Make sure to run `1_transaction_preparation` before. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example 2_transaction_signing -//! ``` - -use iota_sdk::{ - client::{ - api::{PreparedTransactionData, PreparedTransactionDataDto, SignedTransactionData, SignedTransactionDataDto}, - secret::{SecretManage, SecretManager}, - Result, - }, - types::block::payload::transaction::TransactionPayload, -}; - -const PREPARED_TRANSACTION_FILE_NAME: &str = "examples/client/offline_signing/prepared_transaction.json"; -const SIGNED_TRANSACTION_FILE_NAME: &str = "examples/client/offline_signing/signed_transaction.json"; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let prepared_transaction_data = read_prepared_transaction_from_file(PREPARED_TRANSACTION_FILE_NAME).await?; - - // Signs the prepared transaction offline. - let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, None) - .await?; - let signed_transaction = TransactionPayload::new(prepared_transaction_data.essence.clone(), unlocks)?; - - let signed_transaction_data = SignedTransactionData { - transaction_payload: signed_transaction, - inputs_data: prepared_transaction_data.inputs_data, - }; - - println!("Signed transaction."); - - write_signed_transaction_to_file(SIGNED_TRANSACTION_FILE_NAME, &signed_transaction_data).await?; - - Ok(()) -} - -async fn read_prepared_transaction_from_file(path: impl AsRef) -> Result { - use tokio::io::AsyncReadExt; - - let mut file = tokio::fs::File::open(&path).await.expect("failed to open file"); - let mut json = String::new(); - file.read_to_string(&mut json).await.expect("failed to read file"); - - Ok(PreparedTransactionData::try_from_dto_unverified( - serde_json::from_str::(&json)?, - )?) -} - -async fn write_signed_transaction_to_file( - path: impl AsRef, - signed_transaction_data: &SignedTransactionData, -) -> Result<()> { - use tokio::io::AsyncWriteExt; - - let dto = SignedTransactionDataDto::from(signed_transaction_data); - let json = serde_json::to_string_pretty(&dto)?; - let mut file = tokio::io::BufWriter::new(tokio::fs::File::create(path).await.expect("failed to create file")); - println!("{json}"); - file.write_all(json.as_bytes()).await.expect("failed to write file"); - - Ok(()) -} diff --git a/sdk/examples/client/offline_signing/3_send_block.rs b/sdk/examples/client/offline_signing/3_send_block.rs deleted file mode 100644 index 31f862b427..0000000000 --- a/sdk/examples/client/offline_signing/3_send_block.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we send the signed transaction in a block. -//! -//! Make sure to run `2_transaction_signing` before. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example 3_send_block -//! ``` - -use iota_sdk::{ - client::{ - api::{verify_semantic, SignedTransactionData, SignedTransactionDataDto}, - Client, Error, Result, - }, - types::block::{payload::Payload, semantic::ConflictReason}, -}; - -const SIGNED_TRANSACTION_FILE_NAME: &str = "examples/client/offline_signing/signed_transaction.json"; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let node_url = std::env::var("NODE_URL").unwrap(); - - // Create a node client. - let online_client = Client::builder().with_node(&node_url)?.finish().await?; - - let signed_transaction_payload = read_signed_transaction_from_file(SIGNED_TRANSACTION_FILE_NAME).await?; - - let current_time = online_client.get_time_checked().await?; - - let conflict = verify_semantic( - &signed_transaction_payload.inputs_data, - &signed_transaction_payload.transaction_payload, - current_time, - )?; - - if conflict != ConflictReason::None { - return Err(Error::TransactionSemantic(conflict)); - } - - // Sends the offline signed transaction online. - let block = online_client - .build_block() - .finish_block(Some(Payload::Transaction(Box::new( - signed_transaction_payload.transaction_payload, - )))) - .await?; - - println!( - "Posted block: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - Ok(()) -} - -async fn read_signed_transaction_from_file(path: impl AsRef) -> Result { - use tokio::io::AsyncReadExt; - - let mut file = tokio::fs::File::open(path).await.expect("failed to open file"); - let mut json = String::new(); - file.read_to_string(&mut json).await.expect("failed to read file"); - - let dto = serde_json::from_str::(&json)?; - - Ok(SignedTransactionData::try_from_dto_unverified(dto)?) -} diff --git a/sdk/examples/client/output/alias.rs b/sdk/examples/client/output/alias.rs deleted file mode 100644 index 6cbfbeff77..0000000000 --- a/sdk/examples/client/output/alias.rs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will create an alias output. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example alias [ALIAS AMOUNT] -//! ``` - -use iota_sdk::{ - client::{api::GetAddressesOptions, request_funds_from_faucet, secret::SecretManager, Client, Result}, - types::block::{ - output::{ - feature::{IssuerFeature, MetadataFeature, SenderFeature}, - unlock_condition::{GovernorAddressUnlockCondition, StateControllerAddressUnlockCondition}, - AliasId, AliasOutputBuilder, Output, OutputId, - }, - payload::{transaction::TransactionEssence, Payload}, - }, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain - // non-zero balance. - dotenvy::dotenv().ok(); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let token_supply = client.get_token_supply().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - ////////////////////////////////// - // create new alias output - ////////////////////////////////// - let alias_amount = std::env::args() - .nth(1) - .map(|s| s.parse::().unwrap()) - .unwrap_or(1_000_000u64); - let alias_output_builder = AliasOutputBuilder::new_with_amount(alias_amount, AliasId::null()) - .add_feature(SenderFeature::new(address)) - .add_feature(MetadataFeature::new([1, 2, 3])?) - .add_immutable_feature(IssuerFeature::new(address)) - .add_unlock_condition(StateControllerAddressUnlockCondition::new(address)) - .add_unlock_condition(GovernorAddressUnlockCondition::new(address)); - - let outputs = [alias_output_builder.clone().finish_output(token_supply)?]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Transaction with new alias output sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////// - // create second transaction with the actual AliasId (BLAKE2b-160 hash of the Output ID that created the alias) - ////////////////////////////////// - let alias_output_id = get_alias_output_id(block.payload().unwrap())?; - let alias_id = AliasId::from(&alias_output_id); - let outputs = [alias_output_builder - .with_alias_id(alias_id) - .with_state_index(1) - .finish_output(token_supply)?]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_input(alias_output_id.into())? - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Block with alias id set sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - Ok(()) -} - -// helper function to get the output id for the first alias output -fn get_alias_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Alias(_alias_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No alias output in transaction essence") - } - _ => panic!("No tx payload"), - } -} diff --git a/sdk/examples/client/output/all.rs b/sdk/examples/client/output/all.rs deleted file mode 100644 index edd63a5900..0000000000 --- a/sdk/examples/client/output/all.rs +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will create all output types in a single transaction. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example all -//! ``` - -use iota_sdk::{ - client::{ - api::GetAddressesOptions, node_api::indexer::query_parameters::QueryParameter, request_funds_from_faucet, - secret::SecretManager, Client, Result, - }, - types::block::{ - address::{AliasAddress, ToBech32Ext}, - output::{ - feature::{IssuerFeature, MetadataFeature, SenderFeature}, - unlock_condition::{ - AddressUnlockCondition, ExpirationUnlockCondition, GovernorAddressUnlockCondition, - ImmutableAliasAddressUnlockCondition, StateControllerAddressUnlockCondition, - StorageDepositReturnUnlockCondition, TimelockUnlockCondition, - }, - AliasId, AliasOutputBuilder, BasicOutputBuilder, FoundryId, FoundryOutputBuilder, NativeToken, NftId, - NftOutputBuilder, Output, OutputId, SimpleTokenScheme, TokenId, TokenScheme, - }, - payload::{transaction::TransactionEssence, Payload}, - }, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain - // non-zero balance. - dotenvy::dotenv().ok(); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let token_supply = client.get_token_supply().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - ////////////////////////////////// - // create new alias and nft output - ////////////////////////////////// - let alias_output_builder = AliasOutputBuilder::new_with_amount(2_000_000, AliasId::null()) - .add_feature(SenderFeature::new(address)) - .add_feature(MetadataFeature::new([1, 2, 3])?) - .add_immutable_feature(IssuerFeature::new(address)) - .add_unlock_condition(StateControllerAddressUnlockCondition::new(address)) - .add_unlock_condition(GovernorAddressUnlockCondition::new(address)); - // address of the owner of the NFT - let nft_output_builder = NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) - .add_unlock_condition(AddressUnlockCondition::new(address)); - let outputs = [ - alias_output_builder - .clone() - .with_state_index(0) - .with_foundry_counter(0) - .finish_output(token_supply)?, - nft_output_builder - .clone() - // address of the minter of the NFT - // .add_feature(IssuerFeature::new(address)) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Block with new nft and alias output sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - - /////////////////////////////////////////////// - // create foundry, native tokens and nft output - /////////////////////////////////////////////// - let alias_output_id = get_alias_output_id(block.payload().unwrap())?; - let alias_id = AliasId::from(&alias_output_id); - - let nft_output_id = get_nft_output_id(block.payload().unwrap())?; - let nft_id = NftId::from(&nft_output_id); - - let token_scheme = TokenScheme::Simple(SimpleTokenScheme::new(50, 0, 100)?); - let foundry_id = FoundryId::build( - &AliasAddress::from(AliasId::from(&alias_output_id)), - 1, - token_scheme.kind(), - ); - let token_id = TokenId::from(foundry_id); - - let foundry_output_builder = FoundryOutputBuilder::new_with_amount(1_000_000, 1, token_scheme) - .add_unlock_condition(ImmutableAliasAddressUnlockCondition::new(AliasAddress::from(alias_id))); - - let outputs = [ - alias_output_builder - .clone() - .with_amount(1_000_000) - .with_alias_id(alias_id) - .with_state_index(1) - .with_foundry_counter(1) - .finish_output(token_supply)?, - foundry_output_builder - .clone() - // Mint native tokens - .add_native_token(NativeToken::new(token_id, 50)?) - .finish_output(token_supply)?, - nft_output_builder - .clone() - .with_nft_id(nft_id) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_input(nft_output_id.into())? - .with_input(alias_output_id.into())? - .with_outputs(outputs)? - .finish() - .await?; - println!( - "Block with foundry output, minted tokens and nft sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////// - // create all outputs - ////////////////////////////////// - let alias_output_id = get_alias_output_id(block.payload().unwrap())?; - let foundry_output_id = get_foundry_output_id(block.payload().unwrap())?; - let nft_output_id = get_nft_output_id(block.payload().unwrap())?; - - let basic_output_builder = - BasicOutputBuilder::new_with_amount(1_000_000).add_unlock_condition(AddressUnlockCondition::new(address)); - - let outputs = [ - alias_output_builder - .with_amount(1_000_000) - .with_alias_id(alias_id) - .with_state_index(2) - .with_foundry_counter(1) - .finish_output(token_supply)?, - foundry_output_builder.finish_output(token_supply)?, - nft_output_builder.with_nft_id(nft_id).finish_output(token_supply)?, - // with native token - basic_output_builder - .clone() - .add_native_token(NativeToken::new(token_id, 50)?) - .finish_output(token_supply)?, - // with most simple output - basic_output_builder.clone().finish_output(token_supply)?, - // with metadata feature block - basic_output_builder - .clone() - .add_feature(MetadataFeature::new([13, 37])?) - .finish_output(token_supply)?, - // with storage deposit return - basic_output_builder - .clone() - .with_amount(234_100) - .add_unlock_condition(StorageDepositReturnUnlockCondition::new( - address, - 234_000, - token_supply, - )?) - .finish_output(token_supply)?, - // with expiration - basic_output_builder - .clone() - .add_unlock_condition(ExpirationUnlockCondition::new(address, 1)?) - .finish_output(token_supply)?, - // with timelock - basic_output_builder - .clone() - .add_unlock_condition(TimelockUnlockCondition::new(1)?) - .finish_output(token_supply)?, - ]; - - // get additional input for the new basic output without extra unlock conditions - let output_ids_response = client - .basic_output_ids([ - QueryParameter::Address(address.to_bech32(client.get_bech32_hrp().await?)), - QueryParameter::HasStorageDepositReturn(false), - QueryParameter::HasTimelock(false), - QueryParameter::HasExpiration(false), - ]) - .await?; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_input(output_ids_response.items[0].into())? - .with_input(nft_output_id.into())? - .with_input(alias_output_id.into())? - .with_input(foundry_output_id.into())? - .with_outputs(outputs)? - .finish() - .await?; - println!( - "Block with all outputs sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id(), - ); - - let _ = client.retry_until_included(&block.id(), None, None).await?; - - Ok(()) -} - -// helper function to get the output id for the first alias output -fn get_alias_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Alias(_alias_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No alias output in transaction essence") - } - _ => panic!("No tx payload"), - } -} - -// helper function to get the output id for the first foundry output -fn get_foundry_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Foundry(_foundry_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No foundry output in transaction essence") - } - _ => panic!("No tx payload"), - } -} - -// helper function to get the output id for the first NFT output -fn get_nft_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Nft(_nft_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No nft output in transaction essence") - } - _ => panic!("No tx payload"), - } -} diff --git a/sdk/examples/client/output/all_automatic_input_selection.rs b/sdk/examples/client/output/all_automatic_input_selection.rs deleted file mode 100644 index f70784a06d..0000000000 --- a/sdk/examples/client/output/all_automatic_input_selection.rs +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will create all output types in a single transaction. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example all_automatic_input_selection -//! ``` - -use iota_sdk::{ - client::{api::GetAddressesOptions, request_funds_from_faucet, secret::SecretManager, Client, Result}, - types::block::{ - address::AliasAddress, - output::{ - feature::{IssuerFeature, MetadataFeature, SenderFeature}, - unlock_condition::{ - AddressUnlockCondition, ExpirationUnlockCondition, GovernorAddressUnlockCondition, - ImmutableAliasAddressUnlockCondition, StateControllerAddressUnlockCondition, - StorageDepositReturnUnlockCondition, TimelockUnlockCondition, - }, - AliasId, AliasOutputBuilder, BasicOutputBuilder, FoundryId, FoundryOutputBuilder, NativeToken, NftId, - NftOutputBuilder, Output, OutputId, SimpleTokenScheme, TokenId, TokenScheme, - }, - payload::{transaction::TransactionEssence, Payload}, - }, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain - // non-zero balance. - dotenvy::dotenv().ok(); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let token_supply = client.get_token_supply().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - ////////////////////////////////// - // create new alias and nft output - ////////////////////////////////// - let alias_output_builder = AliasOutputBuilder::new_with_amount(2_000_000, AliasId::null()) - .add_feature(SenderFeature::new(address)) - .add_feature(MetadataFeature::new([1, 2, 3])?) - .add_immutable_feature(IssuerFeature::new(address)) - .add_unlock_condition(StateControllerAddressUnlockCondition::new(address)) - .add_unlock_condition(GovernorAddressUnlockCondition::new(address)); - - // address of the owner of the NFT - let nft_output_builder = NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) - .add_unlock_condition(AddressUnlockCondition::new(address)); - - let outputs = [ - alias_output_builder.clone().finish_output(token_supply)?, - nft_output_builder - .clone() - // address of the minter of the NFT - // .add_feature(IssuerFeature::new(address)) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Block with new nft and alias output sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////// - // create foundry output, native tokens and nft - ////////////////////////////////// - let alias_output_id_1 = get_alias_output_id(block.payload().unwrap())?; - let alias_id = AliasId::from(&alias_output_id_1); - let nft_output_id = get_nft_output_id(block.payload().unwrap())?; - let nft_id = NftId::from(&nft_output_id); - let token_scheme = TokenScheme::Simple(SimpleTokenScheme::new(50, 0, 100)?); - let foundry_id = FoundryId::build( - &AliasAddress::from(AliasId::from(&alias_output_id_1)), - 1, - token_scheme.kind(), - ); - let token_id = TokenId::from(foundry_id); - - let foundry_output_builder = FoundryOutputBuilder::new_with_amount(1_000_000, 1, token_scheme) - .add_unlock_condition(ImmutableAliasAddressUnlockCondition::new(AliasAddress::from(alias_id))); - - let outputs = [ - alias_output_builder - .clone() - .with_amount(1_000_000) - .with_alias_id(alias_id) - .with_state_index(1) - .with_foundry_counter(1) - .finish_output(token_supply)?, - foundry_output_builder - .clone() - // Mint native tokens - .add_native_token(NativeToken::new(token_id, 50)?) - .finish_output(token_supply)?, - nft_output_builder - .clone() - .with_nft_id(nft_id) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - println!( - "Block with alias id, foundry output with minted native tokens, and nfts sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id(), - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////// - // create all outputs - ////////////////////////////////// - - let basic_output_builder = - BasicOutputBuilder::new_with_amount(1_000_000).add_unlock_condition(AddressUnlockCondition::new(address)); - - let outputs = [ - alias_output_builder - .with_amount(1_000_000) - .with_alias_id(alias_id) - .with_state_index(2) - .with_foundry_counter(1) - .finish_output(token_supply)?, - foundry_output_builder.finish_output(token_supply)?, - nft_output_builder.with_nft_id(nft_id).finish_output(token_supply)?, - // with native token - basic_output_builder - .clone() - .add_native_token(NativeToken::new(token_id, 50)?) - .finish_output(token_supply)?, - // with most simple output - basic_output_builder.clone().finish_output(token_supply)?, - // with metadata feature block - basic_output_builder - .clone() - .add_feature(MetadataFeature::new([13, 37])?) - .finish_output(token_supply)?, - // with storage deposit return - basic_output_builder - .clone() - .with_amount(234_100) - .add_unlock_condition(StorageDepositReturnUnlockCondition::new( - address, - 234_000, - token_supply, - )?) - .finish_output(token_supply)?, - // with expiration - basic_output_builder - .clone() - .add_unlock_condition(ExpirationUnlockCondition::new(address, 1)?) - .finish_output(token_supply)?, - // with timelock - basic_output_builder - .clone() - .add_unlock_condition(TimelockUnlockCondition::new(1)?) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - println!( - "Block with all outputs sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - - Ok(()) -} - -// helper function to get the output id for the first alias output -fn get_alias_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Alias(_alias_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No alias output in transaction essence") - } - _ => panic!("No tx payload"), - } -} - -// helper function to get the output id for the first NFT output -fn get_nft_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Nft(_nft_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No nft output in transaction essence") - } - _ => panic!("No tx payload"), - } -} diff --git a/sdk/examples/client/output/basic.rs b/sdk/examples/client/output/basic.rs deleted file mode 100644 index 782363aa13..0000000000 --- a/sdk/examples/client/output/basic.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will send basic outputs with different feature blocks. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example basic -//! ``` - -use iota_sdk::{ - client::{api::GetAddressesOptions, secret::SecretManager, utils::request_funds_from_faucet, Client, Result}, - types::block::output::{ - feature::MetadataFeature, - unlock_condition::{ - AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, - TimelockUnlockCondition, - }, - BasicOutputBuilder, - }, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain - // non-zero balance. - dotenvy::dotenv().ok(); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let token_supply = client.get_token_supply().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let basic_output_builder = - BasicOutputBuilder::new_with_amount(1_000_000).add_unlock_condition(AddressUnlockCondition::new(address)); - - let outputs = [ - // most simple output - basic_output_builder.clone().finish_output(token_supply)?, - // with metadata feature block - basic_output_builder - .clone() - .add_feature(MetadataFeature::new([13, 37])?) - .finish_output(token_supply)?, - // with storage deposit return - basic_output_builder - .clone() - .with_amount(234_100) - .add_unlock_condition(StorageDepositReturnUnlockCondition::new( - address, - 234_000, - token_supply, - )?) - .finish_output(token_supply)?, - // with expiration - basic_output_builder - .clone() - .add_unlock_condition(ExpirationUnlockCondition::new(address, 1)?) - .finish_output(token_supply)?, - // with timelock - basic_output_builder - .clone() - .add_unlock_condition(TimelockUnlockCondition::new(1)?) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Basic outputs block sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - Ok(()) -} diff --git a/sdk/examples/client/output/expiration.rs b/sdk/examples/client/output/expiration.rs deleted file mode 100644 index c64ed4b2b8..0000000000 --- a/sdk/examples/client/output/expiration.rs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will create a basic output with an expiration unlock condition. -//! -//! When the receiver (address in AddressUnlockCondition) doesn't consume the output before it gets expired, the sender -//! (address in ExpirationUnlockCondition) will get the full control back. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example expiration -//! ``` - -use iota_sdk::{ - client::{api::GetAddressesOptions, request_funds_from_faucet, secret::SecretManager, Client, Result}, - types::block::output::{ - unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition}, - BasicOutputBuilder, - }, -}; - -const AMOUNT: u64 = 255_100; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain - // non-zero balance. - dotenvy::dotenv().ok(); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let addresses = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..2)) - .await?; - let sender_address = addresses[0]; - let receiver_address = addresses[1]; - - let token_supply = client.get_token_supply().await?; - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &sender_address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let tomorrow = (std::time::SystemTime::now() + std::time::Duration::from_secs(24 * 3600)) - .duration_since(std::time::UNIX_EPOCH) - .expect("clock went backwards") - .as_secs() - .try_into() - .unwrap(); - - let outputs = [ - // with storage deposit return - BasicOutputBuilder::new_with_amount(AMOUNT) - .add_unlock_condition(AddressUnlockCondition::new(receiver_address)) - // If the receiver does not consume this output, we Unlock after a day to avoid - // locking our funds forever. - .add_unlock_condition(ExpirationUnlockCondition::new(sender_address, tomorrow)?) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Block with ExpirationUnlockCondition transaction sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - Ok(()) -} diff --git a/sdk/examples/client/output/foundry.rs b/sdk/examples/client/output/foundry.rs deleted file mode 100644 index d7501ae74e..0000000000 --- a/sdk/examples/client/output/foundry.rs +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will create a foundry output. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example foundry -//! ``` - -use iota_sdk::{ - client::{ - api::{input_selection::Burn, GetAddressesOptions}, - node_api::indexer::query_parameters::QueryParameter, - request_funds_from_faucet, - secret::SecretManager, - Client, Result, - }, - types::block::{ - address::{AliasAddress, ToBech32Ext}, - output::{ - feature::{IssuerFeature, MetadataFeature, SenderFeature}, - unlock_condition::{ - AddressUnlockCondition, GovernorAddressUnlockCondition, ImmutableAliasAddressUnlockCondition, - StateControllerAddressUnlockCondition, - }, - AliasId, AliasOutputBuilder, BasicOutputBuilder, FoundryId, FoundryOutputBuilder, NativeToken, Output, - OutputId, SimpleTokenScheme, TokenId, TokenScheme, - }, - payload::{transaction::TransactionEssence, Payload}, - }, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain - // non-zero balance. - dotenvy::dotenv().ok(); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let token_supply = client.get_token_supply().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - ////////////////////////////////// - // create new alias output - ////////////////////////////////// - - let alias_output_builder = AliasOutputBuilder::new_with_amount(2_000_000, AliasId::null()) - .add_feature(SenderFeature::new(address)) - .add_feature(MetadataFeature::new([1, 2, 3])?) - .add_immutable_feature(IssuerFeature::new(address)) - .add_unlock_condition(StateControllerAddressUnlockCondition::new(address)) - .add_unlock_condition(GovernorAddressUnlockCondition::new(address)); - - let outputs = [alias_output_builder.clone().finish_output(token_supply)?]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Block with new alias output sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////////////////////// - // create foundry output and mint 70 native tokens - ////////////////////////////////////////////////// - - let alias_output_id = get_alias_output_id(block.payload().unwrap())?; - let alias_id = AliasId::from(&alias_output_id); - let token_scheme = TokenScheme::Simple(SimpleTokenScheme::new(70, 0, 100)?); - let foundry_id = FoundryId::build( - &AliasAddress::from(AliasId::from(&alias_output_id)), - 1, - token_scheme.kind(), - ); - let token_id = TokenId::from(foundry_id); - let outputs = [ - alias_output_builder - .clone() - .with_amount(1_000_000) - .with_alias_id(alias_id) - .with_state_index(1) - .with_foundry_counter(1) - .finish_output(token_supply)?, - FoundryOutputBuilder::new_with_amount(1_000_000, 1, token_scheme) - .add_native_token(NativeToken::new(token_id, 70)?) - .add_unlock_condition(ImmutableAliasAddressUnlockCondition::new(AliasAddress::from(alias_id))) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_input(alias_output_id.into())? - .with_outputs(outputs)? - .finish() - .await?; - println!( - "Block with foundry output sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////// - // melt 20 native token - ////////////////////////////////// - - let foundry_output_builder = - FoundryOutputBuilder::new_with_amount(1_000_000, 1, TokenScheme::Simple(SimpleTokenScheme::new(70, 20, 100)?)) - .add_unlock_condition(ImmutableAliasAddressUnlockCondition::new(AliasAddress::from(alias_id))); - - let alias_output_id = get_alias_output_id(block.payload().unwrap())?; - let foundry_output_id = get_foundry_output_id(block.payload().unwrap())?; - let outputs = [ - alias_output_builder - .clone() - .with_amount(1_000_000) - .with_alias_id(alias_id) - .with_state_index(2) - .with_foundry_counter(1) - .finish_output(token_supply)?, - foundry_output_builder - .clone() - .add_native_token(NativeToken::new(token_id, 50)?) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_input(alias_output_id.into())? - .with_input(foundry_output_id.into())? - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Block with native tokens burnt sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////// - // send native token - ////////////////////////////////// - - let basic_output_builder = - BasicOutputBuilder::new_with_amount(57_700).add_unlock_condition(AddressUnlockCondition::new(address)); - - let alias_output_id = get_alias_output_id(block.payload().unwrap())?; - let foundry_output_id = get_foundry_output_id(block.payload().unwrap())?; - let outputs = [ - alias_output_builder - .clone() - .with_amount(57_700) - .with_alias_id(alias_id) - .with_state_index(3) - .with_foundry_counter(1) - .finish_output(token_supply)?, - foundry_output_builder.finish_output(token_supply)?, - basic_output_builder - .clone() - .add_native_token(NativeToken::new(token_id, 50)?) - .finish_output(token_supply)?, - ]; - - // get additional input for the new basic output - let output_ids_response = client - .basic_output_ids([QueryParameter::Address( - address.to_bech32(client.get_bech32_hrp().await?), - )]) - .await?; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_input(output_ids_response.items[0].into())? - .with_input(alias_output_id.into())? - .with_input(foundry_output_id.into())? - .with_outputs(outputs)? - .finish() - .await?; - println!( - "Block with native tokens sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////// - // send native token without foundry - ////////////////////////////////// - - let basic_output_id = get_basic_output_id_with_native_tokens(block.payload().unwrap())?; - let outputs = [basic_output_builder - .clone() - .add_native_token(NativeToken::new(token_id, 50)?) - .finish_output(token_supply)?]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_input(basic_output_id.into())? - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Second block with native tokens sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////// - // burn native token without foundry - ////////////////////////////////// - - let basic_output_id = get_basic_output_id_with_native_tokens(block.payload().unwrap())?; - let outputs = [basic_output_builder - .add_native_token(NativeToken::new(token_id, 30)?) - .finish_output(token_supply)?]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_burn(Burn::new().add_native_token(token_id, 20)) - .with_input(basic_output_id.into())? - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Third block with native tokens burned sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - let _ = client.retry_until_included(&block.id(), None, None).await?; - - Ok(()) -} - -// helper function to get the output id for the first alias output -fn get_alias_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Alias(_alias_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No alias output in transaction essence") - } - _ => panic!("No tx payload"), - } -} - -// helper function to get the output id for the first foundry output -fn get_foundry_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Foundry(_foundry_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No foundry output in transaction essence") - } - _ => panic!("No tx payload"), - } -} - -// helper function to get the output id for the first basic output with native tokens -fn get_basic_output_id_with_native_tokens(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Basic(basic_output) = output { - if !basic_output.native_tokens().is_empty() { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - } - panic!("No basic output with native tokens in transaction essence") - } - _ => panic!("No tx payload"), - } -} diff --git a/sdk/examples/client/output/micro_transaction.rs b/sdk/examples/client/output/micro_transaction.rs deleted file mode 100644 index 6b6894f6e1..0000000000 --- a/sdk/examples/client/output/micro_transaction.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will do a micro transaction using unlock conditions to an output. -//! -//! Due to the required storage deposit, it is not possible to send a small amount of tokens. -//! However, it is possible to send a large amount and ask a slightly smaller amount in return to -//! effectively transfer a small amount. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example microtransaction -//! ``` - -use iota_sdk::{ - client::{api::GetAddressesOptions, request_funds_from_faucet, secret::SecretManager, Client, Result}, - types::block::output::{ - unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition}, - BasicOutputBuilder, - }, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain - // non-zero balance. - dotenvy::dotenv().ok(); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let addresses = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..2)) - .await?; - let sender_address = addresses[0]; - let receiver_address = addresses[1]; - - let token_supply = client.get_token_supply().await?; - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &sender_address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let tomorrow = (std::time::SystemTime::now() + std::time::Duration::from_secs(24 * 3600)) - .duration_since(std::time::UNIX_EPOCH) - .expect("clock went backwards") - .as_secs() - .try_into() - .unwrap(); - - let outputs = [ - // with storage deposit return - BasicOutputBuilder::new_with_amount(255_100) - .add_unlock_condition(AddressUnlockCondition::new(receiver_address)) - // Return 100 less than the original amount. - .add_unlock_condition(StorageDepositReturnUnlockCondition::new( - sender_address, - 255_000, - token_supply, - )?) - // If the receiver does not consume this output, we Unlock after a day to avoid - // locking our funds forever. - .add_unlock_condition(ExpirationUnlockCondition::new(sender_address, tomorrow)?) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Block with micro amount sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - let _ = client.retry_until_included(&block.id(), None, None).await?; - - Ok(()) -} diff --git a/sdk/examples/client/output/native_tokens.rs b/sdk/examples/client/output/native_tokens.rs deleted file mode 100644 index 18c0828bb2..0000000000 --- a/sdk/examples/client/output/native_tokens.rs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will send basic outputs with native tokens in two ways: -//! 1. receiver gets the full output amount + native tokens -//! 2. receiver needs to claim the output to get the native tokens, but has to send the amount back -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example native_tokens [TOKEN ID] -//! ``` - -use iota_sdk::{ - client::{api::GetAddressesOptions, secret::SecretManager, utils::request_funds_from_faucet, Client, Result}, - types::block::output::{ - unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition}, - BasicOutputBuilder, NativeToken, TokenId, - }, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain - // non-zero balance. - dotenvy::dotenv().ok(); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let addresses = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..2)) - .await?; - let sender_address = addresses[0]; - let receiver_address = addresses[1]; - - let token_supply = client.get_token_supply().await?; - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &sender_address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let tomorrow = (std::time::SystemTime::now() + std::time::Duration::from_secs(24 * 3600)) - .duration_since(std::time::UNIX_EPOCH) - .expect("clock went backwards") - .as_secs() - .try_into() - .unwrap(); - - // Replace with the token ID of native tokens you own. - let token_id = std::env::args() - .nth(1) - .unwrap_or("0x08e68f7616cd4948efebc6a77c4f935eaed770ac53869cba56d104f2b472a8836d0100000000".to_string()); - let token_id: [u8; 38] = prefix_hex::decode(token_id)?; - - let outputs = [ - // Without StorageDepositReturnUnlockCondition, the receiver will get the amount of the output and the native - // tokens - BasicOutputBuilder::new_with_amount(1_000_000) - .add_unlock_condition(AddressUnlockCondition::new(receiver_address)) - .add_native_token(NativeToken::new(TokenId::new(token_id), 10)?) - .finish_output(token_supply)?, - // With StorageDepositReturnUnlockCondition, the receiver can consume the output to get the native tokens, but - // he needs to send the amount back - BasicOutputBuilder::new_with_amount(1_000_000) - .add_unlock_condition(AddressUnlockCondition::new(receiver_address)) - .add_native_token(NativeToken::new(TokenId::new(token_id), 10)?) - // Return the full amount. - .add_unlock_condition(StorageDepositReturnUnlockCondition::new( - sender_address, - 1_000_000, - token_supply, - )?) - // If the receiver does not consume this output, we unlock after a day to avoid - // locking our funds forever. - .add_unlock_condition(ExpirationUnlockCondition::new(sender_address, tomorrow)?) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Block with native tokens sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - Ok(()) -} diff --git a/sdk/examples/client/output/nft.rs b/sdk/examples/client/output/nft.rs deleted file mode 100644 index 3f01558e02..0000000000 --- a/sdk/examples/client/output/nft.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will create an NFT output. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example nft -//! ``` - -use iota_sdk::{ - client::{ - api::GetAddressesOptions, node_api::indexer::query_parameters::QueryParameter, request_funds_from_faucet, - secret::SecretManager, Client, Result, - }, - types::block::{ - address::{Bech32Address, NftAddress}, - output::{ - unlock_condition::AddressUnlockCondition, BasicOutputBuilder, NftId, NftOutputBuilder, Output, OutputId, - }, - payload::{transaction::TransactionEssence, Payload}, - }, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain - // non-zero balance. - dotenvy::dotenv().ok(); - - let node_url = std::env::var("NODE_URL").unwrap(); - let explorer_url = std::env::var("EXPLORER_URL").unwrap(); - let faucet_url = std::env::var("FAUCET_URL").unwrap(); - - // Create a node client. - let client = Client::builder().with_node(&node_url)?.finish().await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let token_supply = client.get_token_supply().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&faucet_url, &address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - ////////////////////////////////// - // create new nft output - ////////////////////////////////// - - let outputs = [ - // address of the owner of the NFT - NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) - .add_unlock_condition(AddressUnlockCondition::new(address)) - // address of the minter of the NFT - // .add_feature(IssuerFeature::new(address)) - .finish_output(token_supply)?, - ]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!("Block with new NFT output sent: {}/block/{}", explorer_url, block.id()); - - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////// - // move funds from an NFT address - ////////////////////////////////// - - let nft_output_id = get_nft_output_id(block.payload().unwrap())?; - let nft_id = NftId::from(&nft_output_id); - - let nft_address = NftAddress::new(nft_id); - let bech32_nft_address = Bech32Address::new(client.get_bech32_hrp().await?, nft_address); - println!("bech32_nft_address {bech32_nft_address}"); - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&faucet_url, &bech32_nft_address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let output_ids_response = client - .basic_output_ids([QueryParameter::Address(bech32_nft_address)]) - .await?; - let output = client.get_output(&output_ids_response.items[0]).await?; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_input(nft_output_id.into())? - .with_input(output_ids_response.items[0].into())? - .with_outputs([NftOutputBuilder::new_with_amount(1_000_000 + output.amount(), nft_id) - .add_unlock_condition(AddressUnlockCondition::new(bech32_nft_address)) - .finish_output(token_supply)?])? - .finish() - .await?; - - println!( - "Block with input (basic output) to NFT output sent: {}/block/{}", - explorer_url, - block.id() - ); - - let _ = client.retry_until_included(&block.id(), None, None).await?; - - ////////////////////////////////// - // burn NFT - ////////////////////////////////// - - let nft_output_id = get_nft_output_id(block.payload().unwrap())?; - let output = client.get_output(&nft_output_id).await?; - let outputs = [BasicOutputBuilder::new_with_amount(output.amount()) - .add_unlock_condition(AddressUnlockCondition::new(bech32_nft_address)) - .finish_output(token_supply)?]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_input(nft_output_id.into())? - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Block with burn transaction sent: {}/block/{}", - explorer_url, - block.id() - ); - - let _ = client.retry_until_included(&block.id(), None, None).await?; - - Ok(()) -} - -// helper function to get the output id for the first NFT output -fn get_nft_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Nft(_nft_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No nft output in transaction essence") - } - _ => panic!("No tx payload"), - } -} diff --git a/sdk/examples/client/output/recursive_alias.rs b/sdk/examples/client/output/recursive_alias.rs deleted file mode 100644 index 2f8b94e789..0000000000 --- a/sdk/examples/client/output/recursive_alias.rs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will create three alias outputs, where the first one can control the other two (recursively). -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example recursive_alias -//! ``` - -use iota_sdk::{ - client::{api::GetAddressesOptions, request_funds_from_faucet, secret::SecretManager, Client, Result}, - types::block::{ - address::{Address, AliasAddress}, - output::{ - feature::{IssuerFeature, SenderFeature}, - unlock_condition::{GovernorAddressUnlockCondition, StateControllerAddressUnlockCondition}, - AliasId, AliasOutputBuilder, Output, OutputId, - }, - payload::{transaction::TransactionEssence, Payload}, - }, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain - // non-zero balance. - dotenvy::dotenv().ok(); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &address).await?, - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let rent_structure = client.get_rent_structure().await?; - let token_supply = client.get_token_supply().await?; - - ////////////////////////////////// - // create three new alias outputs - ////////////////////////////////// - let alias_output_builder = AliasOutputBuilder::new_with_minimum_storage_deposit(rent_structure, AliasId::null()) - .add_feature(SenderFeature::new(address)) - .with_state_metadata([1, 2, 3]) - .add_immutable_feature(IssuerFeature::new(address)) - .add_unlock_condition(StateControllerAddressUnlockCondition::new(address)) - .add_unlock_condition(GovernorAddressUnlockCondition::new(address)); - - let outputs = vec![alias_output_builder.clone().finish_output(token_supply)?; 3]; - - let block_1 = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - - println!( - "Block with new alias outputs sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block_1.id() - ); - - let _ = client.retry_until_included(&block_1.id(), None, None).await?; - - ////////////////////////////////// - // create second transaction with the actual AliasId (BLAKE2b-160 hash of the Output ID that created the alias) and - // make both alias outputs controlled by the first one - ////////////////////////////////// - let alias_output_ids = get_new_alias_output_ids(block_1.payload().unwrap())?; - let alias_id_0 = AliasId::from(&alias_output_ids[0]); - let alias_id_1 = AliasId::from(&alias_output_ids[1]); - let alias_id_2 = AliasId::from(&alias_output_ids[2]); - - let alias_0_address = Address::Alias(AliasAddress::new(alias_id_0)); - let alias_1_address = Address::Alias(AliasAddress::new(alias_id_1)); - - let outputs = [ - // make second alias output be controlled by the first one - alias_output_builder - .clone() - .with_alias_id(alias_id_1) - // add a sender feature with the first alias - .replace_feature(SenderFeature::new(alias_0_address)) - .with_state_index(0) - .replace_unlock_condition(StateControllerAddressUnlockCondition::new(alias_0_address)) - .replace_unlock_condition(GovernorAddressUnlockCondition::new(alias_0_address)) - .finish_output(token_supply)?, - // make third alias output be controlled by the second one (indirectly also by the first one) - alias_output_builder - .clone() - .with_alias_id(alias_id_2) - .with_state_index(0) - .replace_unlock_condition(StateControllerAddressUnlockCondition::new(alias_1_address)) - .replace_unlock_condition(GovernorAddressUnlockCondition::new(alias_1_address)) - .finish_output(token_supply)?, - ]; - - let block_2 = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - println!( - "Block with alias id set and ownership assigned to the first alias sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block_2.id() - ); - let _ = client.retry_until_included(&block_2.id(), None, None).await?; - - ////////////////////////////////// - // create third transaction with the third alias output updated - ////////////////////////////////// - let outputs = [alias_output_builder - .clone() - .with_alias_id(alias_id_2) - .with_state_index(1) - .with_state_metadata([3, 2, 1]) - .replace_unlock_condition(StateControllerAddressUnlockCondition::new(alias_1_address)) - .replace_unlock_condition(GovernorAddressUnlockCondition::new(alias_1_address)) - .finish_output(token_supply)?]; - - let block_3 = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - println!( - "Block with state metadata of the third alias updated sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block_3.id() - ); - - let _ = client.retry_until_included(&block_3.id(), None, None).await?; - - ////////////////////////////////// - // create fourth transaction with the third alias output updated again - ////////////////////////////////// - let outputs = [alias_output_builder - .with_alias_id(alias_id_2) - .with_state_index(2) - .with_state_metadata([2, 1, 3]) - .replace_unlock_condition(StateControllerAddressUnlockCondition::new(alias_1_address)) - .replace_unlock_condition(GovernorAddressUnlockCondition::new(alias_1_address)) - .finish_output(token_supply)?]; - - let block_3 = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .finish() - .await?; - println!( - "Another block with state metadata of the third alias updated sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block_3.id() - ); - - let _ = client.retry_until_included(&block_3.id(), None, None).await?; - Ok(()) -} - -// helper function to get the output ids for new created alias outputs (alias id is null) -fn get_new_alias_output_ids(payload: &Payload) -> Result> { - let mut output_ids = Vec::new(); - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Alias(alias_output) = output { - if alias_output.alias_id().is_null() { - output_ids.push(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - } - } - _ => panic!("No tx payload"), - } - Ok(output_ids) -} diff --git a/sdk/examples/client/participation.rs b/sdk/examples/client/participation.rs deleted file mode 100644 index 4ba263235f..0000000000 --- a/sdk/examples/client/participation.rs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! This example shows how to participate in voting events. -//! -//! Command to create an event, when your node is located on the same machine: -//! curl -X POST http://localhost:14265/api/participation/v1/admin/events -H 'Content-Type: application/json' -d '{"name":"Shimmer Proposal","milestoneIndexCommence":580,"milestoneIndexStart":600,"milestoneIndexEnd":700,"payload":{"type":0,"questions":[{"text":"Should we be on CMC rank #1 eoy?","answers":[{"value":1,"text":"Yes","additionalInfo":""},{"value":2,"text":"No","additionalInfo":""}],"additionalInfo":""}]},"additionalInfo":"Nothing needed here"}' -//! Command to delete an event: -//! curl -X DELETE http://localhost:14265/api/participation/v1/admin/events/ -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --all-features --example participation -//! ``` - -use iota_sdk::{ - client::{ - api::GetAddressesOptions, node_api::indexer::query_parameters::QueryParameter, request_funds_from_faucet, - secret::SecretManager, Client, Result, - }, - types::{ - api::plugins::participation::types::{Participation, ParticipationEventId, Participations, PARTICIPATION_TAG}, - block::output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder}, - }, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - // Get the participation events. - let events = client.events(None).await?; - - println!("{events:#?}"); - - for event_id in &events.event_ids { - let event_info = client.event(event_id).await?; - println!("{event_info:#?}"); - let event_status = client.event_status(event_id, None).await?; - println!("{event_status:#?}"); - } - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - - let faucet_url = std::env::var("FAUCET_URL").unwrap(); - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&faucet_url, &address).await? - ); - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let address_participation = client.address_staking_status(address).await?; - println!("{address_participation:#?}"); - - let address_output_ids = client.address_participation_output_ids(address).await?; - println!("{address_output_ids:#?}"); - - for (output_id, _) in address_output_ids.outputs.into_iter() { - let output_status = client.output_status(&output_id).await?; - println!("{output_status:#?}"); - } - - // Get outputs for address and request if they're participating - let output_ids_response = client - .basic_output_ids([ - QueryParameter::Address(address), - QueryParameter::HasExpiration(false), - QueryParameter::HasTimelock(false), - QueryParameter::HasStorageDepositReturn(false), - ]) - .await?; - - for output_id in output_ids_response.items { - if let Ok(output_status) = client.output_status(&output_id).await { - println!("{output_status:#?}"); - } - } - - // Participate with one answer set to `1` for the first event - participate( - &client, - events.event_ids.first().expect("No event available").to_owned(), - ) - .await?; - Ok(()) -} - -async fn participate(client: &Client, event_id: ParticipationEventId) -> Result<()> { - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let token_supply = client.get_token_supply().await?; - let rent_structure = client.get_rent_structure().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(client).await?.with_range(0..1)) - .await?[0]; - - let outputs = [BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) - .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_output(token_supply)?]; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(outputs)? - .with_tag(PARTICIPATION_TAG.as_bytes().to_vec()) - .with_data( - Participations { - participations: vec![Participation { - event_id, - answers: vec![1], - }], - } - .to_bytes()?, - ) - .finish() - .await?; - - println!("{block:#?}"); - - println!( - "Block with participation data sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - Ok(()) -} diff --git a/sdk/src/client/api/block_builder/input_selection/automatic.rs b/sdk/src/client/api/block_builder/input_selection/automatic.rs deleted file mode 100644 index 46cd8a36a6..0000000000 --- a/sdk/src/client/api/block_builder/input_selection/automatic.rs +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! Automatic input selection for transactions - -use std::collections::HashSet; - -use crypto::keys::bip44::Bip44; -use itertools::Itertools; - -use crate::{ - client::{ - api::{ - block_builder::input_selection::core::{Error as InputSelectionError, InputSelection, Selected}, - input_selection::is_alias_transition, - ClientBlockBuilder, GetAddressesOptions, ADDRESS_GAP_RANGE, - }, - node_api::indexer::query_parameters::QueryParameter, - secret::types::InputSigningData, - Error, Result, - }, - types::block::{address::Bech32Address, output::OutputWithMetadata, protocol::ProtocolParameters, ConvertTo}, - utils::unix_timestamp_now, -}; - -impl<'a> ClientBlockBuilder<'a> { - // Get basic outputs for an address without storage deposit return unlock condition - pub(crate) async fn basic_address_outputs( - &self, - address: impl ConvertTo, - ) -> Result> { - let address = address.convert()?; - let mut output_ids = Vec::new(); - - // First request to get all basic outputs that can directly be unlocked by the address. - output_ids.extend( - self.client - .basic_output_ids([ - QueryParameter::Address(address), - QueryParameter::HasStorageDepositReturn(false), - ]) - .await? - .items, - ); - - // Second request to get all basic outputs that can be unlocked by the address through the expiration condition. - output_ids.extend( - self.client - .basic_output_ids([ - QueryParameter::ExpirationReturnAddress(address), - QueryParameter::HasExpiration(true), - QueryParameter::HasStorageDepositReturn(false), - // Ignore outputs that aren't expired yet - QueryParameter::ExpiresBefore(unix_timestamp_now().as_secs() as u32), - ]) - .await? - .items, - ); - - self.client.get_outputs_with_metadata(&output_ids).await - } - - /// Searches inputs for provided outputs, by requesting the outputs from the account addresses or for - /// alias/foundry/nft outputs get the latest state with their alias/nft id. Forwards to [try_select_inputs()]. - pub(crate) async fn get_inputs(&self, protocol_parameters: &ProtocolParameters) -> Result { - log::debug!("[get_inputs]"); - - let account_index = self.account_index; - let mut gap_index = self.initial_address_index; - let mut empty_address_count: u64 = 0; - let mut cached_error = None; - - log::debug!("[get_inputs from utxo chains]"); - - // First get inputs for utxo chains (Alias, Foundry, NFT outputs). - let mut available_inputs = self.get_utxo_chains_inputs(self.outputs.iter()).await?; - - let required_inputs_for_sender_or_issuer = self.get_inputs_for_sender_and_issuer(&available_inputs).await?; - let required_inputs_for_sender_or_issuer_ids = required_inputs_for_sender_or_issuer - .iter() - .map(|input| *input.output_id()) - .collect::>(); - - available_inputs.extend(required_inputs_for_sender_or_issuer); - available_inputs.sort_unstable_by_key(|input| *input.output_id()); - available_inputs.dedup_by_key(|input| *input.output_id()); - - let current_time = self.client.get_time_checked().await?; - // Assume that we own the addresses for inputs that are required for the provided outputs - let mut available_input_addresses = Vec::new(); - for input in &available_inputs { - let alias_transition = - is_alias_transition(&input.output, *input.output_id(), &self.outputs, self.burn.as_ref()); - let (required_unlock_address, unlocked_alias_or_nft_address) = - input - .output - .required_and_unlocked_address(current_time, input.output_id(), alias_transition)?; - available_input_addresses.push(required_unlock_address); - if let Some(unlocked_alias_or_nft_address) = unlocked_alias_or_nft_address { - available_input_addresses.push(unlocked_alias_or_nft_address); - } - } - - // Try to select inputs with required inputs for utxo chains alone before requesting more inputs from addresses. - let mut input_selection = InputSelection::new( - available_inputs.clone(), - self.outputs.clone(), - available_input_addresses.clone(), - protocol_parameters.clone(), - ) - .required_inputs(required_inputs_for_sender_or_issuer_ids.clone()) - .timestamp(current_time); - - if let Some(address) = self.custom_remainder_address { - input_selection = input_selection.remainder_address(address); - } - - if let Ok(selected_transaction_data) = input_selection.select() { - return Ok(selected_transaction_data); - } - - log::debug!("[get_inputs from addresses]"); - - // Then select inputs with outputs from addresses. - let selected_transaction_data = 'input_selection: loop { - // Get the addresses in the BIP path/index ~ path/index+20. - let opts = GetAddressesOptions::from_client(self.client) - .await? - .with_coin_type(self.coin_type) - .with_account_index(account_index) - .with_range(gap_index..gap_index + ADDRESS_GAP_RANGE); - let secret_manager = self - .secret_manager - .ok_or(crate::client::Error::MissingParameter("secret manager"))?; - let public = secret_manager.generate_ed25519_addresses(opts.clone()).await?; - let internal = secret_manager.generate_ed25519_addresses(opts.internal()).await?; - - available_input_addresses.extend(public.iter().map(|bech32_address| bech32_address.inner)); - available_input_addresses.extend(internal.iter().map(|bech32_address| bech32_address.inner)); - - // Have public and internal addresses with the index ascending ordered. - let public_and_internal_addresses = public - .iter() - .map(|a| (a, false)) - .interleave(internal.iter().map(|a| (a, true))); - - // For each address, get the address outputs. - let mut address_index = gap_index; - - for (index, (bech32_address, internal)) in public_and_internal_addresses.enumerate() { - let address_outputs = self.basic_address_outputs(*bech32_address).await?; - - // If there are more than 20 (ADDRESS_GAP_RANGE) consecutive empty addresses, then we stop - // looking up the addresses belonging to the seed. Note that we don't - // really count the exact 20 consecutive empty addresses, which is - // unnecessary. We just need to check the address range, - // (index * ADDRESS_GAP_RANGE, index * ADDRESS_GAP_RANGE + ADDRESS_GAP_RANGE), where index is - // natural number, and to see if the outputs are all empty. - if address_outputs.is_empty() { - // Accumulate the empty_address_count for each run of output address searching - empty_address_count += 1; - } else { - // Reset counter if there is an output - empty_address_count = 0; - - for output_with_meta in address_outputs { - // We can ignore the unlocked_alias_or_nft_address, since we only requested basic outputs - let (required_unlock_address, _unlocked_alias_or_nft_address) = - output_with_meta.output().required_and_unlocked_address( - current_time, - output_with_meta.metadata().output_id(), - None, - )?; - if &required_unlock_address == bech32_address.inner() { - available_inputs.push(InputSigningData { - output: output_with_meta.output, - output_metadata: output_with_meta.metadata, - chain: Some( - Bip44::new() - .with_coin_type(self.coin_type) - .with_account(account_index) - .with_change(internal as _) - .with_address_index(address_index), - ), - }); - } - } - - available_inputs.sort_unstable_by_key(|input| *input.output_id()); - available_inputs.dedup_by_key(|input| *input.output_id()); - - let mut input_selection = InputSelection::new( - available_inputs.clone(), - self.outputs.clone(), - available_input_addresses.clone(), - protocol_parameters.clone(), - ) - .required_inputs(required_inputs_for_sender_or_issuer_ids.clone()) - .timestamp(current_time); - - if let Some(address) = self.custom_remainder_address { - input_selection = input_selection.remainder_address(address); - } - - let selected_transaction_data = match input_selection.select() { - Ok(r) => r, - // for these errors, just try again in the next round with more addresses which might have more - // outputs. - Err(err @ InputSelectionError::InsufficientAmount { .. }) => { - cached_error.replace(Error::from(err)); - continue; - } - Err(err @ InputSelectionError::InsufficientNativeTokenAmount { .. }) => { - cached_error.replace(Error::from(err)); - continue; - } - Err(err @ InputSelectionError::NoAvailableInputsProvided { .. }) => { - cached_error.replace(Error::from(err)); - continue; - } - // Not enough balance for a remainder. - Err(InputSelectionError::Block(block_error)) => match block_error { - crate::types::block::Error::InvalidStorageDepositAmount { .. } => { - cached_error.replace(Error::from(InputSelectionError::Block(block_error))); - continue; - } - _ => return Err(block_error.into()), - }, - Err(e) => return Err(e)?, - }; - - break 'input_selection selected_transaction_data; - } - - // if we just processed an even index, increase the address index - // (because the list has public and internal addresses) - if index % 2 == 1 { - address_index += 1; - } - } - - gap_index += ADDRESS_GAP_RANGE; - - // The gap limit is 20 and use reference 40 here because there's public and internal addresses - if empty_address_count >= (ADDRESS_GAP_RANGE * 2) as u64 { - // returned last cached error - return Err(cached_error.unwrap_or_else(|| Error::from(InputSelectionError::NoAvailableInputsProvided))); - } - }; - - Ok(selected_transaction_data) - } -} diff --git a/sdk/src/client/api/block_builder/input_selection/manual.rs b/sdk/src/client/api/block_builder/input_selection/manual.rs deleted file mode 100644 index 9c5b8e1e4d..0000000000 --- a/sdk/src/client/api/block_builder/input_selection/manual.rs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! Manual input selection for transactions - -use std::collections::HashSet; - -use crypto::keys::bip44::Bip44; - -use crate::{ - client::{ - api::{ - address::search_address, - block_builder::input_selection::{Burn, InputSelection, Selected}, - input_selection::is_alias_transition, - ClientBlockBuilder, - }, - secret::types::InputSigningData, - Result, - }, - types::block::{address::Address, protocol::ProtocolParameters}, -}; - -impl<'a> ClientBlockBuilder<'a> { - /// If custom inputs are provided we check if they are unspent, get the balance and search the Ed25519 addresses for - /// them with the provided input_range so we can later sign them. - /// Forwards to [try_select_inputs()] with all inputs in `mandatory_inputs`, so they will all be included in the - /// transaction, even if not required for the provided outputs. - pub(crate) async fn get_custom_inputs( - &self, - protocol_parameters: &ProtocolParameters, - burn: Option, - ) -> Result { - log::debug!("[get_custom_inputs]"); - - let mut inputs_data = Vec::new(); - let current_time = self.client.get_time_checked().await?; - - if let Some(inputs) = &self.inputs { - for input in inputs { - let output_with_meta = self.client.get_output_with_metadata(input.output_id()).await?; - - if !output_with_meta.metadata().is_spent() { - let alias_transition = is_alias_transition( - output_with_meta.output(), - *input.output_id(), - &self.outputs, - burn.as_ref(), - ); - let (unlock_address, _) = output_with_meta.output().required_and_unlocked_address( - current_time, - input.output_id(), - alias_transition, - )?; - - let bech32_hrp = self.client.get_bech32_hrp().await?; - let address_index_internal = match self.secret_manager { - Some(secret_manager) => { - match unlock_address { - Address::Ed25519(_) => Some( - search_address( - secret_manager, - bech32_hrp, - self.coin_type, - self.account_index, - self.input_range.clone(), - &unlock_address, - ) - .await?, - ), - // Alias and NFT addresses can't be generated from a private key. - _ => None, - } - } - // Assuming default for offline signing. - None => Some((0, false)), - }; - - inputs_data.push(InputSigningData { - output: output_with_meta.output, - output_metadata: output_with_meta.metadata, - chain: address_index_internal.map(|(address_index, internal)| { - Bip44::new() - .with_coin_type(self.coin_type) - .with_account(self.account_index) - .with_change(internal as _) - .with_address_index(address_index) - }), - }); - } - } - } - - let required_inputs = inputs_data - .iter() - .map(|input| *input.output_id()) - .collect::>(); - - // Assume that we own the addresses for inputs that are provided - let mut available_input_addresses = Vec::new(); - for input in &inputs_data { - let alias_transition = is_alias_transition(&input.output, *input.output_id(), &self.outputs, burn.as_ref()); - let (required_unlock_address, unlocked_alias_or_nft_address) = - input - .output - .required_and_unlocked_address(current_time, input.output_id(), alias_transition)?; - available_input_addresses.push(required_unlock_address); - if let Some(unlocked_alias_or_nft_address) = unlocked_alias_or_nft_address { - available_input_addresses.push(unlocked_alias_or_nft_address); - } - } - - inputs_data.sort_unstable_by_key(|input| *input.output_id()); - inputs_data.dedup_by_key(|input| *input.output_id()); - - let mut input_selection = InputSelection::new( - inputs_data, - self.outputs.clone(), - available_input_addresses, - protocol_parameters.clone(), - ) - .required_inputs(required_inputs) - .timestamp(current_time); - - if let Some(address) = self.custom_remainder_address { - input_selection = input_selection.remainder_address(address); - } - - if let Some(burn) = burn { - input_selection = input_selection.burn(burn); - } - - Ok(input_selection.select()?) - } -} diff --git a/sdk/src/client/api/block_builder/input_selection/mod.rs b/sdk/src/client/api/block_builder/input_selection/mod.rs index 25b60b82e9..af10b0bf7c 100644 --- a/sdk/src/client/api/block_builder/input_selection/mod.rs +++ b/sdk/src/client/api/block_builder/input_selection/mod.rs @@ -3,11 +3,7 @@ //! Input selection for transactions -mod automatic; mod core; -mod manual; -mod sender_issuer; -mod utxo_chains; pub(crate) use self::core::is_alias_transition; pub use self::core::{Burn, BurnDto, Error, InputSelection, Requirement, Selected}; diff --git a/sdk/src/client/api/block_builder/input_selection/sender_issuer.rs b/sdk/src/client/api/block_builder/input_selection/sender_issuer.rs deleted file mode 100644 index 46ca0b8b06..0000000000 --- a/sdk/src/client/api/block_builder/input_selection/sender_issuer.rs +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! sender and issuer features input selection - -use std::collections::HashSet; - -use crypto::keys::bip44::Bip44; - -use crate::{ - client::{ - api::{ - address::search_address, - block_builder::input_selection::core::{ - error::Error as InputSelectionError, requirement::alias::is_alias_transition, - }, - ClientBlockBuilder, - }, - secret::types::InputSigningData, - Error, Result, - }, - types::block::{ - address::{Address, ToBech32Ext}, - output::{feature::Features, Output}, - }, -}; - -impl<'a> ClientBlockBuilder<'a> { - pub(crate) async fn get_inputs_for_sender_and_issuer( - &self, - utxo_chain_inputs: &[InputSigningData], - ) -> Result> { - log::debug!("[get_inputs_for_sender_and_issuer]"); - - let mut required_inputs = Vec::new(); - let bech32_hrp = self.client.get_bech32_hrp().await?; - let current_time = self.client.get_time_checked().await?; - - let required_sender_or_issuer_addresses = - get_required_addresses_for_sender_and_issuer(&[], &self.outputs, current_time)?; - - for sender_or_issuer_address in required_sender_or_issuer_addresses { - match sender_or_issuer_address { - Address::Ed25519(_) => { - // Check if the address is derived from the seed - let (address_index, internal) = search_address( - self.secret_manager.ok_or(Error::MissingParameter("secret manager"))?, - bech32_hrp, - self.coin_type, - self.account_index, - self.input_range.clone(), - &sender_or_issuer_address, - ) - .await?; - let address_outputs = self - .basic_address_outputs(sender_or_issuer_address.to_bech32(bech32_hrp)) - .await?; - - let mut found_output = false; - for output_with_meta in address_outputs { - // We can ignore the unlocked_alias_or_nft_address, since we only requested basic outputs - let (required_unlock_address, _unlocked_alias_or_nft_address) = - output_with_meta.output().required_and_unlocked_address( - current_time, - output_with_meta.metadata().output_id(), - None, - )?; - - if required_unlock_address == sender_or_issuer_address { - required_inputs.push(InputSigningData { - output: output_with_meta.output().to_owned(), - output_metadata: output_with_meta.metadata().to_owned(), - chain: Some( - Bip44::new() - .with_coin_type(self.coin_type) - .with_account(self.account_index) - .with_change(internal as _) - .with_address_index(address_index), - ), - }); - found_output = true; - break; - } - } - - if !found_output { - return Err(InputSelectionError::MissingInputWithEd25519Address)?; - } - } - Address::Alias(alias_address) => { - // Check if output is alias address. - let alias_id = alias_address.alias_id(); - - // check if already found or request new. - if !utxo_chain_inputs.iter().chain(required_inputs.iter()).any(|input| { - if let Output::Alias(alias_output) = &input.output { - *alias_id == alias_output.alias_id_non_null(input.output_id()) - } else { - false - } - }) { - let output_id = self.client.alias_output_id(*alias_id).await?; - let output_with_meta = self.client.get_output_with_metadata(&output_id).await?; - if let Output::Alias(alias_output) = output_with_meta.output() { - // State transition if we add them to inputs - let unlock_address = alias_output.state_controller_address(); - let address_index_internal = match self.secret_manager { - Some(secret_manager) => { - match unlock_address { - Address::Ed25519(_) => Some( - search_address( - secret_manager, - bech32_hrp, - self.coin_type, - self.account_index, - self.input_range.clone(), - unlock_address, - ) - .await?, - ), - // Alias and NFT addresses can't be generated from a private key - _ => None, - } - } - // Assuming default for offline signing - None => Some((0, false)), - }; - - required_inputs.push(InputSigningData { - output: output_with_meta.output().to_owned(), - output_metadata: output_with_meta.metadata().to_owned(), - chain: address_index_internal.map(|(address_index, internal)| { - Bip44::new() - .with_coin_type(self.coin_type) - .with_account(self.account_index) - .with_change(internal as _) - .with_address_index(address_index) - }), - }); - } - } - } - Address::Nft(nft_address) => { - // Check if output is nft address. - let nft_id = nft_address.nft_id(); - - // Check if already found or request new. - if !utxo_chain_inputs.iter().chain(required_inputs.iter()).any(|input| { - if let Output::Nft(nft_output) = &input.output { - nft_id == nft_output.nft_id() - } else { - false - } - }) { - let output_id = self.client.nft_output_id(*nft_id).await?; - let output_with_meta = self.client.get_output_with_metadata(&output_id).await?; - if let Output::Nft(nft_output) = output_with_meta.output() { - let unlock_address = nft_output - .unlock_conditions() - .locked_address(nft_output.address(), current_time); - - let address_index_internal = match self.secret_manager { - Some(secret_manager) => { - match unlock_address { - Address::Ed25519(_) => Some( - search_address( - secret_manager, - bech32_hrp, - self.coin_type, - self.account_index, - self.input_range.clone(), - unlock_address, - ) - .await?, - ), - // Alias and NFT addresses can't be generated from a private key. - _ => None, - } - } - // Assuming default for offline signing. - None => Some((0, false)), - }; - - required_inputs.push(InputSigningData { - output: output_with_meta.output, - output_metadata: output_with_meta.metadata, - chain: address_index_internal.map(|(address_index, internal)| { - Bip44::new() - .with_coin_type(self.coin_type) - .with_account(self.account_index) - .with_change(internal as _) - .with_address_index(address_index) - }), - }); - } - } - } - } - } - - // Check required Alias and NFT outputs with new added outputs. - // No need to check for sender and issuer again, since these outputs already exist and we don't set new features - // for them. - let utxo_chain_inputs = self - .get_utxo_chains_inputs(required_inputs.iter().map(|i| &i.output)) - .await?; - required_inputs.extend(utxo_chain_inputs); - - Ok(required_inputs) - } -} - -// Returns required addresses for sender and issuer features that aren't already unlocked with the selected_inputs -fn get_required_addresses_for_sender_and_issuer( - selected_inputs: &[InputSigningData], - outputs: &Vec, - current_time: u32, -) -> crate::client::Result> { - log::debug!("[get_required_addresses_for_sender_and_issuer]"); - - // Addresses in the inputs that will be unlocked in the transaction - let mut unlocked_addresses = HashSet::new(); - for input_signing_data in selected_inputs { - let alias_transition = is_alias_transition( - &input_signing_data.output, - *input_signing_data.output_id(), - outputs, - None, - ); - let (required_unlock_address, unlocked_alias_or_nft_address) = input_signing_data - .output - .required_and_unlocked_address(current_time, input_signing_data.output_id(), alias_transition)?; - unlocked_addresses.insert(required_unlock_address); - if let Some(unlocked_alias_or_nft_address) = unlocked_alias_or_nft_address { - unlocked_addresses.insert(unlocked_alias_or_nft_address); - } - } - - let mut required_sender_or_issuer_addresses = HashSet::new(); - - for output in outputs { - if let Some(sender_feature) = output.features().and_then(Features::sender) { - if !required_sender_or_issuer_addresses.contains(sender_feature.address()) { - // Only add if not already present in the selected inputs. - if !unlocked_addresses.contains(sender_feature.address()) { - required_sender_or_issuer_addresses.insert(*sender_feature.address()); - } - } - } - - // Issuer address only needs to be unlocked when the utxo chain is newly created. - let utxo_chain_creation = match &output { - Output::Alias(alias_output) => alias_output.alias_id().is_null(), - Output::Nft(nft_output) => nft_output.nft_id().is_null(), - _ => false, - }; - if utxo_chain_creation { - if let Some(issuer_feature) = output.immutable_features().and_then(Features::issuer) { - if !required_sender_or_issuer_addresses.contains(issuer_feature.address()) { - // Only add if not already present in the selected inputs. - if !unlocked_addresses.contains(issuer_feature.address()) { - required_sender_or_issuer_addresses.insert(*issuer_feature.address()); - } - } - } - } - } - - Ok(required_sender_or_issuer_addresses) -} diff --git a/sdk/src/client/api/block_builder/input_selection/utxo_chains.rs b/sdk/src/client/api/block_builder/input_selection/utxo_chains.rs deleted file mode 100644 index 978537b332..0000000000 --- a/sdk/src/client/api/block_builder/input_selection/utxo_chains.rs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! input selection for utxo chains - -use crypto::keys::bip44::Bip44; - -use crate::{ - client::{ - api::{block_builder::ClientBlockBuilder, search_address}, - secret::types::InputSigningData, - Client, Result, - }, - types::block::{ - address::Address, - output::{Output, OutputWithMetadata}, - }, -}; - -/// Get recursively owned alias and nft outputs and add them to the utxo_chains -pub(crate) async fn get_alias_and_nft_outputs_recursively( - client: &Client, - utxo_chains: &mut Vec<(Address, OutputWithMetadata)>, -) -> Result<()> { - log::debug!("[get_alias_and_nft_outputs_recursively]"); - let current_time = client.get_time_checked().await?; - - let mut processed_alias_nft_addresses = std::collections::HashSet::new(); - - // Add addresses for alias and nft outputs we already have - for (_unlock_address, output_with_meta) in utxo_chains.iter() { - match output_with_meta.output() { - Output::Alias(alias_output) => { - processed_alias_nft_addresses.insert(Address::Alias( - alias_output.alias_address(output_with_meta.metadata().output_id()), - )); - } - Output::Nft(nft_output) => { - processed_alias_nft_addresses.insert(Address::Nft( - nft_output.nft_address(output_with_meta.metadata().output_id()), - )); - } - _ => {} - } - } - - let mut processed_utxo_chains = Vec::new(); - - // Make the outputs response optional, because we don't know it yet for new required outputs - let mut utxo_chain_optional_response: Vec<(Address, Option)> = - utxo_chains.iter_mut().map(|(a, o)| (*a, Some(o.clone()))).collect(); - - // Get alias or nft addresses when needed or just add the input again - while let Some((unlock_address, output_response)) = utxo_chain_optional_response.pop() { - // Don't request outputs for addresses where we already have the output - if processed_alias_nft_addresses.insert(unlock_address) { - match unlock_address { - Address::Alias(address) => { - let input_id = client.alias_output_id(*address.alias_id()).await?; - let input = client.get_output_with_metadata(&input_id).await?; - if let Output::Alias(alias_input) = input.output() { - // State transition if we add them to inputs - let alias_unlock_address = alias_input.state_controller_address(); - // Add address to unprocessed_alias_nft_addresses so we get the required output there - // also - if alias_unlock_address.is_alias() || alias_unlock_address.is_nft() { - utxo_chain_optional_response.push((*alias_unlock_address, None)); - } - processed_utxo_chains.push((*alias_unlock_address, input)); - } - } - Address::Nft(address) => { - let input_id = client.nft_output_id(*address.nft_id()).await?; - let input = client.get_output_with_metadata(&input_id).await?; - if let Output::Nft(nft_input) = input.output() { - let unlock_address = nft_input - .unlock_conditions() - .locked_address(nft_input.address(), current_time); - // Add address to unprocessed_alias_nft_addresses so we get the required output there also - if unlock_address.is_alias() || unlock_address.is_nft() { - utxo_chain_optional_response.push((*unlock_address, None)); - } - processed_utxo_chains.push((*unlock_address, input)); - } - } - _ => {} - } - } - - // Add if the output_response is available - if let Some(output_response) = output_response { - processed_utxo_chains.push((unlock_address, output_response)); - } - } - - *utxo_chains = processed_utxo_chains; - - Ok(()) -} - -impl<'a> ClientBlockBuilder<'a> { - /// Get inputs for utxo chains - pub(crate) async fn get_utxo_chains_inputs( - &self, - outputs: impl Iterator + Clone + Send, - ) -> Result> { - log::debug!("[get_utxo_chains_inputs]"); - let client = self.client; - let bech32_hrp = client.get_bech32_hrp().await?; - let current_time = self.client.get_time_checked().await?; - - let mut utxo_chains: Vec<(Address, OutputWithMetadata)> = Vec::new(); - for output in outputs { - match output { - Output::Alias(alias_output) => { - // if the alias id is null then there can't be a previous output and it can also not be a - // governance transition - if !alias_output.alias_id().is_null() { - // Check if the transaction is a governance_transition, by checking if the new index is the same - // as the previous index - let output_id = client.alias_output_id(*alias_output.alias_id()).await?; - let input = client.get_output_with_metadata(&output_id).await?; - if let Output::Alias(alias_input) = input.output() { - // A governance transition is identified by an unchanged State Index in next - // state. - if alias_output.state_index() == alias_input.state_index() { - utxo_chains.push((*alias_input.governor_address(), input)); - } else { - utxo_chains.push((*alias_input.state_controller_address(), input)); - } - } - } - } - Output::Nft(nft_output) => { - // If the id is null then this output creates it and we can't have a previous output - if !nft_output.nft_id().is_null() { - let output_id = client.nft_output_id(*nft_output.nft_id()).await?; - let input = client.get_output_with_metadata(&output_id).await?; - if let Output::Nft(nft_input) = input.output() { - let unlock_address = nft_input - .unlock_conditions() - .locked_address(nft_output.address(), current_time); - - utxo_chains.push((*unlock_address, input)); - } - } - } - Output::Foundry(foundry_output) => { - // if it's the first foundry output, then we can't have it as input - if let Ok(output_id) = client.foundry_output_id(foundry_output.id()).await { - let input = client.get_output_with_metadata(&output_id).await?; - if let Output::Foundry(foundry_input_output) = input.output() { - utxo_chains.push((Address::Alias(*foundry_input_output.alias_address()), input)); - } - } - } - _ => {} - } - } - - // Get recursively owned alias or nft outputs - get_alias_and_nft_outputs_recursively(self.client, &mut utxo_chains).await?; - - let mut utxo_chain_inputs = Vec::new(); - for (unlock_address, output_with_meta) in utxo_chains { - let address_index_internal = match self.secret_manager { - Some(secret_manager) => { - match unlock_address { - Address::Ed25519(_) => Some( - search_address( - secret_manager, - bech32_hrp, - self.coin_type, - self.account_index, - self.input_range.clone(), - &unlock_address, - ) - .await?, - ), - // Alias and NFT addresses can't be generated from a private key - _ => None, - } - } - // Assuming default for offline signing - None => Some((0, false)), - }; - - utxo_chain_inputs.push(InputSigningData { - output: output_with_meta.output, - output_metadata: output_with_meta.metadata, - chain: address_index_internal.map(|(address_index, internal)| { - Bip44::new() - .with_coin_type(self.coin_type) - .with_account(self.account_index) - .with_change(internal as _) - .with_address_index(address_index) - }), - }); - } - - Ok(utxo_chain_inputs) - } -} diff --git a/sdk/src/client/api/block_builder/mod.rs b/sdk/src/client/api/block_builder/mod.rs index 078b86a489..bca6aa051e 100644 --- a/sdk/src/client/api/block_builder/mod.rs +++ b/sdk/src/client/api/block_builder/mod.rs @@ -5,375 +5,4 @@ pub mod input_selection; pub mod pow; pub mod transaction; -use std::ops::Range; - -use packable::bounded::TryIntoBoundedU16Error; -use serde::{Deserialize, Serialize}; - -pub use self::transaction::verify_semantic; -use crate::{ - client::{ - api::block_builder::input_selection::Burn, constants::SHIMMER_COIN_TYPE, secret::SecretManager, Client, Error, - Result, - }, - types::block::{ - address::{Address, Bech32Address, Ed25519Address}, - input::{dto::UtxoInputDto, UtxoInput, INPUT_COUNT_MAX}, - output::{ - dto::OutputDto, unlock_condition::AddressUnlockCondition, BasicOutputBuilder, Output, OUTPUT_COUNT_RANGE, - }, - parent::Parents, - payload::{Payload, TaggedDataPayload}, - Block, BlockId, ConvertTo, - }, -}; - -/// Builder of the block API -#[must_use] -pub struct ClientBlockBuilder<'a> { - client: &'a Client, - secret_manager: Option<&'a SecretManager>, - coin_type: u32, - account_index: u32, - initial_address_index: u32, - inputs: Option>, - input_range: Range, - outputs: Vec, - custom_remainder_address: Option
, - tag: Option>, - data: Option>, - parents: Option, - burn: Option, -} - -/// Block output address -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ClientBlockBuilderOutputAddress { - /// Address - pub address: String, - /// Amount - // Using a String to prevent overflow issues in other languages - pub amount: String, -} - -/// Options for generating block -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ClientBlockBuilderOptions { - /// Coin type - pub coin_type: Option, - /// Account index - pub account_index: Option, - /// Initial address index - pub initial_address_index: Option, - /// Inputs - pub inputs: Option>, - /// Input range - pub input_range: Option>, - /// Bech32 encoded output address and amount - pub output: Option, - /// Hex encoded output address and amount - pub output_hex: Option, - /// Outputs - pub outputs: Option>, - /// Custom remainder address - pub custom_remainder_address: Option, - /// Hex encoded tag - pub tag: Option, - /// Hex encoded data - pub data: Option, - /// Parents - pub parents: Option>, - /// Explicit burning of aliases, nfts, foundries and native tokens - pub burn: Option, -} - -impl<'a> ClientBlockBuilder<'a> { - /// Create block builder - pub fn new(client: &'a Client) -> Self { - Self { - client, - secret_manager: None, - coin_type: SHIMMER_COIN_TYPE, - account_index: 0, - initial_address_index: 0, - inputs: None, - input_range: 0..100, - outputs: Vec::new(), - custom_remainder_address: None, - tag: None, - data: None, - parents: None, - burn: None, - } - } - - /// Sets explicit burning of aliases, nfts, foundries and native tokens. - pub fn with_burn(mut self, burn: impl Into>) -> Self { - self.burn = burn.into(); - self - } - - /// Sets the seed. - pub fn with_secret_manager(mut self, manager: &'a SecretManager) -> Self { - self.secret_manager.replace(manager); - self - } - - /// Sets the coin type. - pub fn with_coin_type(mut self, coin_type: u32) -> Self { - self.coin_type = coin_type; - self - } - - /// Sets the account index. - pub fn with_account_index(mut self, account_index: u32) -> Self { - self.account_index = account_index; - self - } - - /// Sets the index of the address to start looking for balance. - pub fn with_initial_address_index(mut self, initial_address_index: u32) -> Self { - self.initial_address_index = initial_address_index; - self - } - - /// Set a custom input(transaction output) - pub fn with_input(mut self, input: UtxoInput) -> Result { - let inputs = self.inputs.get_or_insert_with(Vec::new); - // 128 is the maximum input amount - if inputs.len() >= INPUT_COUNT_MAX as _ { - return Err(Error::ConsolidationRequired(inputs.len())); - } - inputs.push(input); - Ok(self) - } - - /// Set a custom range in which to search for addresses for custom provided inputs. Default: 0..100 - pub fn with_input_range(mut self, range: Range) -> Self { - self.input_range = range; - self - } - - /// Set a transfer to the builder - pub async fn with_output( - mut self, - address: impl ConvertTo, - amount: u64, - ) -> Result> { - let address = address.convert()?; - self.client.bech32_hrp_matches(address.hrp()).await?; - - let output = BasicOutputBuilder::new_with_amount(amount) - .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_output(self.client.get_token_supply().await?)?; - self.outputs.push(output); - if !OUTPUT_COUNT_RANGE.contains(&(self.outputs.len() as u16)) { - return Err(crate::client::Error::Block( - crate::types::block::Error::InvalidOutputCount(TryIntoBoundedU16Error::Truncated(self.outputs.len())), - )); - } - Ok(self) - } - - /// Set outputs to the builder - pub fn with_outputs(mut self, outputs: impl IntoIterator) -> Result { - self.outputs.extend(outputs); - if !OUTPUT_COUNT_RANGE.contains(&(self.outputs.len() as u16)) { - return Err(crate::client::Error::Block( - crate::types::block::Error::InvalidOutputCount(TryIntoBoundedU16Error::Truncated(self.outputs.len())), - )); - } - Ok(self) - } - - /// Set a transfer to the builder, address needs to be hex encoded - pub async fn with_output_hex(mut self, address: &str, amount: u64) -> Result> { - let output = BasicOutputBuilder::new_with_amount(amount) - .add_unlock_condition(AddressUnlockCondition::new(address.parse::()?)) - .finish_output(self.client.get_token_supply().await?)?; - self.outputs.push(output); - if !OUTPUT_COUNT_RANGE.contains(&(self.outputs.len() as u16)) { - return Err(crate::client::Error::Block( - crate::types::block::Error::InvalidOutputCount(TryIntoBoundedU16Error::Truncated(self.outputs.len())), - )); - } - Ok(self) - } - - /// Set a custom remainder address - pub fn with_custom_remainder_address(mut self, address: impl ConvertTo) -> Result { - self.custom_remainder_address.replace(address.convert()?.into()); - Ok(self) - } - - /// Set tagged_data to the builder - pub fn with_tag(mut self, tag: impl Into>>) -> Self { - self.tag = tag.into(); - self - } - - /// Set data to the builder - pub fn with_data(mut self, data: impl Into>>) -> Self { - self.data = data.into(); - self - } - - /// Set 1-8 custom parent block ids - pub fn with_parents(mut self, parent_ids: impl Into>>) -> Result { - self.parents = parent_ids.into().map(Parents::from_vec).transpose()?; - Ok(self) - } - - /// Set multiple options from client block builder options type - /// Useful for bindings - pub async fn set_options(mut self, options: ClientBlockBuilderOptions) -> Result> { - if let Some(coin_type) = options.coin_type { - self = self.with_coin_type(coin_type); - } - - if let Some(account_index) = options.account_index { - self = self.with_account_index(account_index); - } - - if let Some(initial_address_index) = options.initial_address_index { - self = self.with_initial_address_index(initial_address_index); - } - - if let Some(inputs) = options.inputs { - for input in inputs { - self = self.with_input(UtxoInput::try_from(input)?)?; - } - } - - if let Some(input_range) = options.input_range { - self = self.with_input_range(input_range); - } - - if let Some(output) = options.output { - self = self - .with_output( - &output.address, - output - .amount - .parse::() - .map_err(|_| Error::InvalidAmount(output.amount))?, - ) - .await?; - } - - if let Some(output_hex) = options.output_hex { - self = self - .with_output_hex( - &output_hex.address, - output_hex - .amount - .parse::() - .map_err(|_| Error::InvalidAmount(output_hex.amount))?, - ) - .await?; - } - - if let Some(outputs) = options.outputs { - let token_supply = self.client.get_token_supply().await?; - - self = self.with_outputs( - outputs - .into_iter() - .map(|o| Ok(Output::try_from_dto(o, token_supply)?)) - .collect::>>()?, - )?; - } - - if let Some(custom_remainder_address) = options.custom_remainder_address { - self = self.with_custom_remainder_address(&custom_remainder_address)?; - } - - if let Some(tag) = options.tag { - self = self.with_tag(prefix_hex::decode::>(tag)?); - } - - if let Some(data) = options.data { - self = self.with_data(prefix_hex::decode::>(data)?); - } - - if let Some(parents) = options.parents { - self = self.with_parents(parents)?; - } - if let Some(burn) = options.burn { - self = self.with_burn(burn); - } - - Ok(self) - } - - /// Consume the builder, post the block to the node and get the API result - pub async fn finish(self) -> Result { - // tagged_data payload requires an tagged_data tag - if self.data.is_some() && self.tag.is_none() { - return Err(Error::MissingParameter("tag")); - } - if self.inputs.is_some() && self.outputs.is_empty() { - return Err(Error::MissingParameter("output")); - } - if !self.outputs.is_empty() { - if self.secret_manager.is_none() && self.inputs.is_none() { - return Err(Error::MissingParameter("seed")); - } - // Send block with transaction - let prepared_transaction_data = self.prepare_transaction().await?; - let tx_payload = self.sign_transaction(prepared_transaction_data).await?; - self.finish_block(Some(tx_payload)).await - } else if self.tag.is_some() { - // Send block with tagged_data payload - self.finish_tagged_data().await - } else { - // Send block without payload - self.finish_block(None).await - } - } - - /// Consume the builder and get the API result - pub async fn finish_tagged_data(mut self) -> Result { - let payload: Payload; - { - let index = &self.tag.as_ref(); - let data = self.data.take().unwrap_or_default(); - - // build tagged_data - let index = TaggedDataPayload::new(index.expect("no tagged_data tag").to_vec(), data) - .map_err(|e| Error::TaggedData(e.to_string()))?; - payload = Payload::from(index); - } - - // building block - self.finish_block(Some(payload)).await - } - - /// Builds the final block and posts it to the node - pub async fn finish_block(self, payload: Option) -> Result { - // Do not replace parents with the latest tips if they are set explicitly, - // necessary for block promotion. - let final_block = self.client.finish_block_builder(self.parents, payload).await?; - - let block_id = self.client.post_block_raw(&final_block).await?; - // Get block if we use remote PoW, because the node will change parents and nonce - if self.client.get_local_pow().await { - Ok(final_block) - } else { - // Request block multiple times because the node maybe didn't process it completely in this time - // or a node balancer could be used which forwards the request to different node than we published - for time in 1..3 { - if let Ok(block) = self.client.get_block(&block_id).await { - return Ok(block); - } - #[cfg(not(target_family = "wasm"))] - tokio::time::sleep(std::time::Duration::from_millis(time * 50)).await; - #[cfg(target_family = "wasm")] - gloo_timers::future::TimeoutFuture::new((time * 50).try_into().unwrap()).await; - } - self.client.get_block(&block_id).await - } - } -} +pub use transaction::verify_semantic; diff --git a/sdk/src/client/api/block_builder/transaction.rs b/sdk/src/client/api/block_builder/transaction.rs index 91278922b6..34a5e48815 100644 --- a/sdk/src/client/api/block_builder/transaction.rs +++ b/sdk/src/client/api/block_builder/transaction.rs @@ -6,18 +6,10 @@ use packable::PackableExt; use crate::{ - client::{ - api::{types::PreparedTransactionData, ClientBlockBuilder}, - secret::{types::InputSigningData, SecretManage}, - Error, Result, - }, + client::{secret::types::InputSigningData, Error, Result}, types::block::{ - input::{Input, UtxoInput}, - output::{InputsCommitment, Output, OutputId}, - payload::{ - transaction::{RegularTransactionEssence, TransactionEssence, TransactionPayload}, - Payload, TaggedDataPayload, - }, + output::{Output, OutputId}, + payload::transaction::{RegularTransactionEssence, TransactionEssence, TransactionPayload}, semantic::{semantic_validation, ConflictReason, ValidationContext}, signature::Ed25519Signature, Block, BlockId, @@ -31,86 +23,6 @@ const SINGLE_UNLOCK_LENGTH: usize = 1 + 1 + Ed25519Signature::PUBLIC_KEY_LENGTH // Type + reference index const REFERENCE_ALIAS_NFT_UNLOCK_LENGTH: usize = 1 + 2; -impl<'a> ClientBlockBuilder<'a> { - /// Prepare a transaction - pub async fn prepare_transaction(&self) -> Result { - log::debug!("[prepare_transaction]"); - let protocol_parameters = self.client.get_protocol_parameters().await?; - let token_supply = self.client.get_token_supply().await?; - - for output in &self.outputs { - // Check if the outputs have enough amount to cover the storage deposit - output.verify_storage_deposit(*protocol_parameters.rent_structure(), token_supply)?; - } - - // Input selection - let selected_transaction_data = if self.inputs.is_some() { - self.get_custom_inputs(&protocol_parameters, self.burn.clone()).await? - } else { - self.get_inputs(&protocol_parameters).await? - }; - - // Build transaction payload - let inputs_commitment = InputsCommitment::new(selected_transaction_data.inputs.iter().map(|i| &i.output)); - - let mut essence = RegularTransactionEssence::builder(self.client.get_network_id().await?, inputs_commitment); - let inputs = selected_transaction_data - .inputs - .iter() - .map(|i| { - Ok(Input::Utxo(UtxoInput::new( - *i.output_metadata.transaction_id(), - i.output_metadata.output_index(), - )?)) - }) - .collect::>>()?; - essence = essence.with_inputs(inputs); - - essence = essence.with_outputs(selected_transaction_data.outputs); - - // Add tagged data payload if tag set - if let Some(index) = self.tag.clone() { - let tagged_data_payload = TaggedDataPayload::new(index.to_vec(), self.data.clone().unwrap_or_default())?; - essence = essence.with_payload(tagged_data_payload); - } - - let regular_essence = essence.finish(&self.client.get_protocol_parameters().await?)?; - - validate_regular_transaction_essence_length(®ular_essence)?; - - let essence = TransactionEssence::Regular(regular_essence); - - Ok(PreparedTransactionData { - essence, - inputs_data: selected_transaction_data.inputs, - remainder: selected_transaction_data.remainder, - }) - } - - /// Sign the transaction - pub async fn sign_transaction(&self, prepared_transaction_data: PreparedTransactionData) -> Result { - log::debug!("[sign_transaction] {:?}", prepared_transaction_data); - let secret_manager = self.secret_manager.ok_or(Error::MissingParameter("secret manager"))?; - let current_time = self.client.get_time_checked().await?; - - let unlocks = secret_manager - .sign_transaction_essence(&prepared_transaction_data, Some(current_time)) - .await?; - let tx_payload = TransactionPayload::new(prepared_transaction_data.essence.clone(), unlocks)?; - - validate_transaction_payload_length(&tx_payload)?; - - let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?; - - if conflict != ConflictReason::None { - log::debug!("[sign_transaction] conflict: {conflict:?} for {:#?}", tx_payload); - return Err(Error::TransactionSemantic(conflict)); - } - - Ok(Payload::from(tx_payload)) - } -} - /// Verifies the semantic of a prepared transaction. pub fn verify_semantic( input_signing_data: &[InputSigningData], diff --git a/sdk/src/client/api/consolidation.rs b/sdk/src/client/api/consolidation.rs deleted file mode 100644 index 5f9790a862..0000000000 --- a/sdk/src/client/api/consolidation.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use super::GetAddressesOptions; -use crate::{ - client::{node_api::indexer::query_parameters::QueryParameter, secret::SecretManager, Client, Result}, - types::block::{ - address::Bech32Address, - input::{UtxoInput, INPUT_COUNT_MAX}, - output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder, NativeTokensBuilder}, - }, -}; - -impl Client { - /// Function to consolidate all funds and native tokens from a range of addresses to the address with the lowest - /// index in that range. Returns the address to which the funds got consolidated, if any were available - pub async fn consolidate_funds( - &self, - secret_manager: &SecretManager, - options: GetAddressesOptions, - ) -> Result { - let token_supply = self.get_token_supply().await?; - let mut last_transfer_index = options.range.start; - // use the start index as offset - let offset = last_transfer_index; - - let addresses = secret_manager.generate_ed25519_addresses(options).await?; - - let consolidation_address = addresses[0]; - - 'consolidation: loop { - let mut block_ids = Vec::new(); - // Iterate over addresses reversed so the funds end up on the first address in the range - for (index, address) in addresses.iter().enumerate().rev() { - let index = index as u32; - // add the offset so the index matches the address index also for higher start indexes - let index = index + offset; - - // Get output ids of outputs that can be controlled by this address without further unlock constraints - let output_ids_response = self - .basic_output_ids([ - QueryParameter::Address(*address), - QueryParameter::HasExpiration(false), - QueryParameter::HasTimelock(false), - QueryParameter::HasStorageDepositReturn(false), - ]) - .await?; - - let basic_outputs_responses = self.get_outputs_with_metadata(&output_ids_response.items).await?; - - if !basic_outputs_responses.is_empty() { - // If we reach the same index again - if last_transfer_index == index { - if basic_outputs_responses.len() < 2 { - break 'consolidation; - } - } else { - last_transfer_index = index; - } - } - - let outputs_chunks = basic_outputs_responses.chunks(INPUT_COUNT_MAX.into()); - - self.bech32_hrp_matches(consolidation_address.hrp()).await?; - - for chunk in outputs_chunks { - let mut block_builder = self.build_block().with_secret_manager(secret_manager); - let mut total_amount = 0; - let mut total_native_tokens = NativeTokensBuilder::new(); - - for output_with_meta in chunk { - block_builder = block_builder - .with_input(UtxoInput::from(output_with_meta.metadata().output_id().to_owned()))?; - - if let Some(native_tokens) = output_with_meta.output().native_tokens() { - total_native_tokens.add_native_tokens(native_tokens.clone())?; - } - total_amount += output_with_meta.output().amount(); - } - - let consolidation_output = BasicOutputBuilder::new_with_amount(total_amount) - .add_unlock_condition(AddressUnlockCondition::new(consolidation_address)) - .with_native_tokens(total_native_tokens.finish()?) - .finish_output(token_supply)?; - - let block = block_builder - .with_input_range(index..index + 1) - .with_outputs([consolidation_output])? - .with_initial_address_index(0) - .finish() - .await?; - block_ids.push(block.id()); - } - } - - if block_ids.is_empty() { - break 'consolidation; - } - // Wait for txs to get confirmed so we don't create conflicting txs - for block_id in block_ids { - let _ = self.retry_until_included(&block_id, None, None).await?; - } - } - Ok(consolidation_address) - } -} diff --git a/sdk/src/client/api/high_level.rs b/sdk/src/client/api/high_level.rs index 9f7fee2ba2..ae69cea96a 100644 --- a/sdk/src/client/api/high_level.rs +++ b/sdk/src/client/api/high_level.rs @@ -7,7 +7,7 @@ use futures::{StreamExt, TryStreamExt}; use crate::{ client::{ - api::{input_selection::Error as InputSelectionError, ClientBlockBuilder}, + api::input_selection::Error as InputSelectionError, constants::{ DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL, DEFAULT_RETRY_UNTIL_INCLUDED_MAX_AMOUNT, FIVE_MINUTES_IN_SECONDS, }, @@ -56,11 +56,6 @@ impl Client { self.get_outputs_with_metadata(&input_ids).await } - /// Get a builder that can be used to construct a block in parts. - pub fn build_block(&self) -> ClientBlockBuilder<'_> { - ClientBlockBuilder::new(self) - } - /// Find all blocks by provided block IDs. pub async fn find_blocks(&self, block_ids: &[BlockId]) -> Result> { // Use a `HashSet` to prevent duplicate block_ids. diff --git a/sdk/src/client/api/mod.rs b/sdk/src/client/api/mod.rs index f91e4b2151..45d2073fea 100644 --- a/sdk/src/client/api/mod.rs +++ b/sdk/src/client/api/mod.rs @@ -5,7 +5,6 @@ mod address; mod block_builder; -mod consolidation; mod high_level; mod types; diff --git a/sdk/src/wallet/account/operations/retry.rs b/sdk/src/wallet/account/operations/retry.rs index d24767a215..b43936fbd9 100644 --- a/sdk/src/wallet/account/operations/retry.rs +++ b/sdk/src/wallet/account/operations/retry.rs @@ -69,8 +69,7 @@ where Some(block_id) => block_id, None => self .client() - .build_block() - .finish_block(Some(Payload::Transaction(Box::new(transaction.payload.clone())))) + .finish_block_builder(None, Some(Payload::Transaction(Box::new(transaction.payload.clone())))) .await? .id(), }; @@ -110,8 +109,10 @@ where } else if block_metadata.should_reattach.unwrap_or(false) { let reattached_block = self .client() - .build_block() - .finish_block(Some(Payload::Transaction(Box::new(transaction.payload.clone())))) + .finish_block_builder( + None, + Some(Payload::Transaction(Box::new(transaction.payload.clone()))), + ) .await?; block_ids.push(reattached_block.id()); } diff --git a/sdk/tests/client/common/mod.rs b/sdk/tests/client/common/mod.rs index 5a0ff66a34..a456f95e08 100644 --- a/sdk/tests/client/common/mod.rs +++ b/sdk/tests/client/common/mod.rs @@ -3,11 +3,12 @@ mod constants; -use crypto::keys::bip39::Mnemonic; -use iota_sdk::client::{ - api::GetAddressesOptions, constants::SHIMMER_COIN_TYPE, node_api::indexer::query_parameters::QueryParameter, - request_funds_from_faucet, secret::SecretManager, Client, Result, -}; +// use crypto::keys::bip39::Mnemonic; +// use iota_sdk::client::{ +// api::GetAddressesOptions, constants::SHIMMER_COIN_TYPE, node_api::indexer::query_parameters::QueryParameter, +// request_funds_from_faucet, secret::SecretManager, Client, Result, +// }; +use iota_sdk::client::Client; pub use self::constants::{FAUCET_URL, NODE_LOCAL}; @@ -22,41 +23,41 @@ pub async fn setup_client_with_node_health_ignored() -> Client { .unwrap() } -/// Create a client with `DEFAULT_DEVNET_NODE_URL` and a random mnemonic, request funds from the faucet to the first -/// address and wait until they arrived. -pub async fn create_client_and_secret_manager_with_funds( - mnemonic: Option, -) -> Result<(Client, SecretManager)> { - let client = Client::builder().with_node(NODE_LOCAL)?.finish().await?; - - let secret_manager = SecretManager::try_from_mnemonic(mnemonic.unwrap_or(Client::generate_mnemonic().unwrap()))?; - - let address = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::from_client(&client) - .await? - .with_coin_type(SHIMMER_COIN_TYPE) - .with_range(0..1), - ) - .await?[0]; - - request_funds_from_faucet(FAUCET_URL, &address).await?; - - // Continue only after funds are received - for _ in 0..30 { - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - let output_ids_response = client - .basic_output_ids([ - QueryParameter::Address(address), - QueryParameter::HasExpiration(false), - QueryParameter::HasTimelock(false), - QueryParameter::HasStorageDepositReturn(false), - ]) - .await?; - - if !output_ids_response.is_empty() { - return Ok((client, secret_manager)); - } - } - panic!("Faucet no longer wants to hand over coins"); -} +// TODO uncomment +// /// Create a client with `DEFAULT_DEVNET_NODE_URL` and a random mnemonic, request funds from the faucet to the first +// /// address and wait until they arrived. +// pub(crate) async fn create_client_and_secret_manager_with_funds( +// mnemonic: Option, +// ) -> Result<(Client, SecretManager)> { let client = Client::builder().with_node(NODE_LOCAL)?.finish().await?; + +// let secret_manager = SecretManager::try_from_mnemonic(mnemonic.unwrap_or(Client::generate_mnemonic().unwrap()))?; + +// let address = secret_manager +// .generate_ed25519_addresses( +// GetAddressesOptions::from_client(&client) +// .await? +// .with_coin_type(SHIMMER_COIN_TYPE) +// .with_range(0..1), +// ) +// .await?[0]; + +// request_funds_from_faucet(FAUCET_URL, &address).await?; + +// // Continue only after funds are received +// for _ in 0..30 { +// tokio::time::sleep(std::time::Duration::from_secs(2)).await; +// let output_ids_response = client +// .basic_output_ids([ +// QueryParameter::Address(address), +// QueryParameter::HasExpiration(false), +// QueryParameter::HasTimelock(false), +// QueryParameter::HasStorageDepositReturn(false), +// ]) +// .await?; + +// if !output_ids_response.is_empty() { +// return Ok((client, secret_manager)); +// } +// } +// panic!("Faucet no longer wants to hand over coins"); +// } diff --git a/sdk/tests/client/consolidation.rs b/sdk/tests/client/consolidation.rs deleted file mode 100644 index 644fd44271..0000000000 --- a/sdk/tests/client/consolidation.rs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -// These are E2E test samples, so they are ignored by default. - -use iota_sdk::{ - client::{api::GetAddressesOptions, node_api::indexer::query_parameters::QueryParameter, Result}, - types::block::{ - address::ToBech32Ext, - output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder}, - }, -}; - -use crate::client::common::create_client_and_secret_manager_with_funds; - -#[ignore] -#[tokio::test] -async fn consolidate_outputs() -> Result<()> { - let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; - - let addresses = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..3)) - .await?; - - let bech32_hrp = client.get_bech32_hrp().await?; - let output_ids_response = client - .basic_output_ids([ - QueryParameter::Address(addresses[0].to_bech32(bech32_hrp)), - QueryParameter::HasExpiration(false), - QueryParameter::HasTimelock(false), - QueryParameter::HasStorageDepositReturn(false), - ]) - .await?; - assert_eq!(output_ids_response.items.len(), 1); - - let initial_output = client.get_output(&output_ids_response.items[0]).await?; - let initial_base_coin_amount = initial_output.amount(); - - // First split funds to multiple addresses - let token_supply = client.get_token_supply().await?; - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs([ - BasicOutputBuilder::new_with_amount(1_000_000) - .add_unlock_condition(AddressUnlockCondition::new(addresses[1])) - .finish_output(token_supply)?, - BasicOutputBuilder::new_with_amount(1_000_000) - .add_unlock_condition(AddressUnlockCondition::new(addresses[2])) - .finish_output(token_supply)?, - ])? - .finish() - .await?; - client.retry_until_included(&block.id(), None, None).await?; - - // Here all funds will be send to the address with the lowest index in the range - let address_range = 0u32..5; - let address = client - .consolidate_funds( - &secret_manager, - GetAddressesOptions::from_client(&client) - .await? - .with_range(address_range), - ) - .await?; - assert_eq!(addresses[0], address); - - let output_ids_response = client - .basic_output_ids([ - QueryParameter::Address(address.to_bech32(bech32_hrp)), - QueryParameter::HasExpiration(false), - QueryParameter::HasTimelock(false), - QueryParameter::HasStorageDepositReturn(false), - ]) - .await?; - // There is only one output at the end - assert_eq!(output_ids_response.items.len(), 1); - - let final_output = client.get_output(&output_ids_response.items[0]).await?; - let final_base_coin_amount = final_output.amount(); - // The output has the full amount again - assert_eq!(final_base_coin_amount, initial_base_coin_amount); - - Ok(()) -} diff --git a/sdk/tests/client/mod.rs b/sdk/tests/client/mod.rs index 3fb3c42d93..d80e2a9c9e 100644 --- a/sdk/tests/client/mod.rs +++ b/sdk/tests/client/mod.rs @@ -4,7 +4,6 @@ mod addresses; mod client_builder; mod common; -mod consolidation; mod error; mod input_selection; mod input_signing_data; @@ -14,7 +13,6 @@ mod mqtt; mod node_api; mod secret_manager; mod signing; -mod transactions; use std::{ collections::{BTreeSet, HashMap}, diff --git a/sdk/tests/client/node_api/core.rs b/sdk/tests/client/node_api/core.rs index 9353237bee..482f1ec2de 100644 --- a/sdk/tests/client/node_api/core.rs +++ b/sdk/tests/client/node_api/core.rs @@ -144,7 +144,7 @@ async fn test_get_output_raw() { let client = setup_client_with_node_health_ignored().await; - let output = client.get_output(&output_id).await.unwrap().into_output(); + let output = client.get_output(&output_id).await.unwrap(); let output_raw = Output::unpack_verified( client.get_output_raw(&output_id).await.unwrap(), &client.get_protocol_parameters().await.unwrap(), diff --git a/sdk/tests/client/node_api/indexer.rs b/sdk/tests/client/node_api/indexer.rs index 30435d108c..0095cc4cdf 100644 --- a/sdk/tests/client/node_api/indexer.rs +++ b/sdk/tests/client/node_api/indexer.rs @@ -1,151 +1,153 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use iota_sdk::{ - client::{api::GetAddressesOptions, Result}, - types::block::{ - address::AliasAddress, - output::{ - unlock_condition::{ - AddressUnlockCondition, GovernorAddressUnlockCondition, ImmutableAliasAddressUnlockCondition, - StateControllerAddressUnlockCondition, UnlockCondition, - }, - AliasId, AliasOutputBuilder, FoundryId, FoundryOutputBuilder, NftId, NftOutputBuilder, SimpleTokenScheme, - TokenScheme, - }, - }, -}; - -use super::{get_alias_output_id, get_foundry_output_id, get_nft_output_id}; -use crate::client::common::create_client_and_secret_manager_with_funds; - -#[ignore] -#[tokio::test] -async fn get_alias_output_id_test() -> Result<()> { - let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; - let protocol_parameters = client.get_protocol_parameters().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..3)) - .await?[0]; - - let alias_output = - AliasOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), AliasId::null()) - .with_state_metadata([1, 2, 3]) - .add_unlock_condition(StateControllerAddressUnlockCondition::new(address)) - .add_unlock_condition(GovernorAddressUnlockCondition::new(address)) - .finish_output(protocol_parameters.token_supply())?; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs([alias_output])? - .finish() - .await?; - - client.retry_until_included(&block.id(), None, None).await?; - - let output_id_0 = get_alias_output_id(block.payload().unwrap())?; - let output_id_1 = client.alias_output_id(AliasId::from(&output_id_0)).await?; - - assert_eq!(output_id_0, output_id_1); - - Ok(()) -} - -#[ignore] -#[tokio::test] -async fn get_nft_output_id_test() -> Result<()> { - let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; - let protocol_parameters = client.get_protocol_parameters().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..3)) - .await?[0]; - - let nft_output = - NftOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), NftId::null()) - .with_unlock_conditions([UnlockCondition::Address(AddressUnlockCondition::new(address))]) - .finish_output(protocol_parameters.token_supply())?; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs([nft_output])? - .finish() - .await?; - - client.retry_until_included(&block.id(), None, None).await?; - - let output_id_0 = get_nft_output_id(block.payload().unwrap())?; - let output_id_1 = client.nft_output_id(NftId::from(&output_id_0)).await?; - - assert_eq!(output_id_0, output_id_1); - - Ok(()) -} - -#[ignore] -#[tokio::test] -async fn get_foundry_output_id_test() -> Result<()> { - let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; - let protocol_parameters = client.get_protocol_parameters().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..3)) - .await?[0]; - - let alias_output_0 = - AliasOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), AliasId::null()) - .with_state_metadata([1, 2, 3]) - .add_unlock_condition(StateControllerAddressUnlockCondition::new(address)) - .add_unlock_condition(GovernorAddressUnlockCondition::new(address)) - .finish_output(protocol_parameters.token_supply())?; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs([alias_output_0.clone()])? - .finish() - .await?; - - client.retry_until_included(&block.id(), None, None).await?; - - let alias_id = AliasId::from(&get_alias_output_id(block.payload().unwrap())?); - - let alias_output_1 = AliasOutputBuilder::from(alias_output_0.as_alias()) - .with_alias_id(alias_id) - .with_state_index(alias_output_0.as_alias().state_index() + 1) - .with_foundry_counter(alias_output_0.as_alias().foundry_counter() + 1) - .finish_output(protocol_parameters.token_supply())?; - - let foundry_id = FoundryId::build( - &AliasAddress::new(alias_id), - alias_output_0.as_alias().foundry_counter() + 1, - SimpleTokenScheme::KIND, - ); - - let foundry_output = FoundryOutputBuilder::new_with_minimum_storage_deposit( - *protocol_parameters.rent_structure(), - alias_output_0.as_alias().foundry_counter() + 1, - TokenScheme::Simple(SimpleTokenScheme::new(100, 0, 500)?), - ) - .add_unlock_condition(ImmutableAliasAddressUnlockCondition::new(AliasAddress::from(alias_id))) - .finish_output(protocol_parameters.token_supply())?; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs([alias_output_1, foundry_output])? - .finish() - .await?; - - client.retry_until_included(&block.id(), None, None).await?; - - let output_id_0 = get_foundry_output_id(block.payload().unwrap())?; - let output_id_1 = client.foundry_output_id(foundry_id).await?; - - assert_eq!(output_id_0, output_id_1); - - Ok(()) -} +// // Copyright 2023 IOTA Stiftung +// // SPDX-License-Identifier: Apache-2.0 + +// TODO uncomment + +// use iota_sdk::{ +// client::{api::GetAddressesOptions, Result}, +// types::block::{ +// address::AliasAddress, +// output::{ +// unlock_condition::{ +// AddressUnlockCondition, GovernorAddressUnlockCondition, ImmutableAliasAddressUnlockCondition, +// StateControllerAddressUnlockCondition, UnlockCondition, +// }, +// AliasId, AliasOutputBuilder, FoundryId, FoundryOutputBuilder, NftId, NftOutputBuilder, SimpleTokenScheme, +// TokenScheme, +// }, +// }, +// }; + +// use super::{get_alias_output_id, get_foundry_output_id, get_nft_output_id}; +// use crate::client::common::create_client_and_secret_manager_with_funds; + +// #[ignore] +// #[tokio::test] +// async fn get_alias_output_id_test() -> Result<()> { +// let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; +// let protocol_parameters = client.get_protocol_parameters().await?; + +// let address = secret_manager +// .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..3)) +// .await?[0]; + +// let alias_output = +// AliasOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), AliasId::null()) +// .with_state_metadata([1, 2, 3]) +// .add_unlock_condition(StateControllerAddressUnlockCondition::new(address)) +// .add_unlock_condition(GovernorAddressUnlockCondition::new(address)) +// .finish_output(protocol_parameters.token_supply())?; + +// let block = client +// .build_block() +// .with_secret_manager(&secret_manager) +// .with_outputs([alias_output])? +// .finish() +// .await?; + +// client.retry_until_included(&block.id(), None, None).await?; + +// let output_id_0 = get_alias_output_id(block.payload().unwrap())?; +// let output_id_1 = client.alias_output_id(AliasId::from(&output_id_0)).await?; + +// assert_eq!(output_id_0, output_id_1); + +// Ok(()) +// } + +// #[ignore] +// #[tokio::test] +// async fn get_nft_output_id_test() -> Result<()> { +// let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; +// let protocol_parameters = client.get_protocol_parameters().await?; + +// let address = secret_manager +// .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..3)) +// .await?[0]; + +// let nft_output = +// NftOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), NftId::null()) +// .with_unlock_conditions([UnlockCondition::Address(AddressUnlockCondition::new(address))]) +// .finish_output(protocol_parameters.token_supply())?; + +// let block = client +// .build_block() +// .with_secret_manager(&secret_manager) +// .with_outputs([nft_output])? +// .finish() +// .await?; + +// client.retry_until_included(&block.id(), None, None).await?; + +// let output_id_0 = get_nft_output_id(block.payload().unwrap())?; +// let output_id_1 = client.nft_output_id(NftId::from(&output_id_0)).await?; + +// assert_eq!(output_id_0, output_id_1); + +// Ok(()) +// } + +// #[ignore] +// #[tokio::test] +// async fn get_foundry_output_id_test() -> Result<()> { +// let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; +// let protocol_parameters = client.get_protocol_parameters().await?; + +// let address = secret_manager +// .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..3)) +// .await?[0]; + +// let alias_output_0 = +// AliasOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), AliasId::null()) +// .with_state_metadata([1, 2, 3]) +// .add_unlock_condition(StateControllerAddressUnlockCondition::new(address)) +// .add_unlock_condition(GovernorAddressUnlockCondition::new(address)) +// .finish_output(protocol_parameters.token_supply())?; + +// let block = client +// .build_block() +// .with_secret_manager(&secret_manager) +// .with_outputs([alias_output_0.clone()])? +// .finish() +// .await?; + +// client.retry_until_included(&block.id(), None, None).await?; + +// let alias_id = AliasId::from(&get_alias_output_id(block.payload().unwrap())?); + +// let alias_output_1 = AliasOutputBuilder::from(alias_output_0.as_alias()) +// .with_alias_id(alias_id) +// .with_state_index(alias_output_0.as_alias().state_index() + 1) +// .with_foundry_counter(alias_output_0.as_alias().foundry_counter() + 1) +// .finish_output(protocol_parameters.token_supply())?; + +// let foundry_id = FoundryId::build( +// &AliasAddress::new(alias_id), +// alias_output_0.as_alias().foundry_counter() + 1, +// SimpleTokenScheme::KIND, +// ); + +// let foundry_output = FoundryOutputBuilder::new_with_minimum_storage_deposit( +// *protocol_parameters.rent_structure(), +// alias_output_0.as_alias().foundry_counter() + 1, +// TokenScheme::Simple(SimpleTokenScheme::new(100, 0, 500)?), +// ) +// .add_unlock_condition(ImmutableAliasAddressUnlockCondition::new(AliasAddress::from(alias_id))) +// .finish_output(protocol_parameters.token_supply())?; + +// let block = client +// .build_block() +// .with_secret_manager(&secret_manager) +// .with_outputs([alias_output_1, foundry_output])? +// .finish() +// .await?; + +// client.retry_until_included(&block.id(), None, None).await?; + +// let output_id_0 = get_foundry_output_id(block.payload().unwrap())?; +// let output_id_1 = client.foundry_output_id(foundry_id).await?; + +// assert_eq!(output_id_0, output_id_1); + +// Ok(()) +// } diff --git a/sdk/tests/client/node_api/mod.rs b/sdk/tests/client/node_api/mod.rs index be4bc67a5e..3d2891e91b 100644 --- a/sdk/tests/client/node_api/mod.rs +++ b/sdk/tests/client/node_api/mod.rs @@ -8,16 +8,11 @@ mod mqtt; use iota_sdk::{ client::{ - api::GetAddressesOptions, bech32_to_hex, node_api::indexer::query_parameters::QueryParameter, - request_funds_from_faucet, secret::SecretManager, Result, + api::GetAddressesOptions, node_api::indexer::query_parameters::QueryParameter, request_funds_from_faucet, + secret::SecretManager, }, types::block::{ - address::ToBech32Ext, - output::{Output, OutputId}, - payload::{ - transaction::{TransactionEssence, TransactionId}, - Payload, - }, + payload::{tagged_data::TaggedDataPayload, transaction::TransactionId, Payload}, BlockId, }, }; @@ -32,10 +27,12 @@ async fn setup_tagged_data_block() -> BlockId { let client = setup_client_with_node_health_ignored().await; client - .build_block() - .with_tag(b"Hello".to_vec()) - .with_data(b"Tangle".to_vec()) - .finish() + .finish_block_builder( + None, + Some(Payload::TaggedData(Box::new( + TaggedDataPayload::new(b"Hello".to_vec(), b"Tangle".to_vec()).unwrap(), + ))), + ) .await .unwrap() .id() @@ -65,7 +62,12 @@ async fn setup_transaction_block() -> (BlockId, TransactionId) { ); // Continue only after funds are received - for _ in 0..30 { + let mut round = 0; + let output_id = loop { + round += 1; + if round > 30 { + panic!("got no funds from faucet") + } tokio::time::sleep(std::time::Duration::from_secs(1)).await; let output_ids_response = client .basic_output_ids([ @@ -78,25 +80,11 @@ async fn setup_transaction_block() -> (BlockId, TransactionId) { .unwrap(); if !output_ids_response.items.is_empty() { - break; + break output_ids_response.items[0]; } - } - - let block_id = client - .build_block() - .with_secret_manager(&secret_manager) - .with_output_hex( - // Send funds back to the sender. - &bech32_to_hex(addresses[1].to_bech32(client.get_bech32_hrp().await.unwrap())).unwrap(), - // The amount to spend, cannot be zero. - 1_000_000, - ) - .await - .unwrap() - .finish() - .await - .unwrap() - .id(); + }; + + let block_id = *client.get_output_metadata(&output_id).await.unwrap().block_id(); let block = setup_client_with_node_health_ignored() .await @@ -114,50 +102,52 @@ async fn setup_transaction_block() -> (BlockId, TransactionId) { (block_id, transaction_id) } -// helper function to get the output id for the first alias output -fn get_alias_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Alias(_alias_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No alias output in transaction essence") - } - _ => panic!("No tx payload"), - } -} - -// helper function to get the output id for the first foundry output -fn get_foundry_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Foundry(_foundry_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No foundry output in transaction essence") - } - _ => panic!("No tx payload"), - } -} - -// helper function to get the output id for the first NFT output -fn get_nft_output_id(payload: &Payload) -> Result { - match payload { - Payload::Transaction(tx_payload) => { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Nft(_nft_output) = output { - return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); - } - } - panic!("No nft output in transaction essence") - } - _ => panic!("No tx payload"), - } -} +// TODO uncomment + +// // helper function to get the output id for the first alias output +// fn get_alias_output_id(payload: &Payload) -> Result { +// match payload { +// Payload::Transaction(tx_payload) => { +// let TransactionEssence::Regular(regular) = tx_payload.essence(); +// for (index, output) in regular.outputs().iter().enumerate() { +// if let Output::Alias(_alias_output) = output { +// return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); +// } +// } +// panic!("No alias output in transaction essence") +// } +// _ => panic!("No tx payload"), +// } +// } + +// // helper function to get the output id for the first foundry output +// fn get_foundry_output_id(payload: &Payload) -> Result { +// match payload { +// Payload::Transaction(tx_payload) => { +// let TransactionEssence::Regular(regular) = tx_payload.essence(); +// for (index, output) in regular.outputs().iter().enumerate() { +// if let Output::Foundry(_foundry_output) = output { +// return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); +// } +// } +// panic!("No foundry output in transaction essence") +// } +// _ => panic!("No tx payload"), +// } +// } + +// // helper function to get the output id for the first NFT output +// fn get_nft_output_id(payload: &Payload) -> Result { +// match payload { +// Payload::Transaction(tx_payload) => { +// let TransactionEssence::Regular(regular) = tx_payload.essence(); +// for (index, output) in regular.outputs().iter().enumerate() { +// if let Output::Nft(_nft_output) = output { +// return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?); +// } +// } +// panic!("No nft output in transaction essence") +// } +// _ => panic!("No tx payload"), +// } +// } diff --git a/sdk/tests/client/transactions.rs b/sdk/tests/client/transactions.rs deleted file mode 100644 index dc1fe71d96..0000000000 --- a/sdk/tests/client/transactions.rs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -// These are E2E test samples, so they are ignored by default. - -use iota_sdk::{ - client::{api::GetAddressesOptions, node_api::indexer::query_parameters::QueryParameter, Result}, - types::block::{ - address::ToBech32Ext, - input::{Input, UtxoInput}, - output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder, OutputId}, - payload::{transaction::TransactionEssence, Payload}, - }, -}; - -use crate::client::common::create_client_and_secret_manager_with_funds; - -#[ignore] -#[tokio::test] -async fn send_basic_output() -> Result<()> { - let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; - - let token_supply = client.get_token_supply().await?; - - let second_address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(1..2)) - .await?[0]; - - let output = BasicOutputBuilder::new_with_amount(1_000_000) - .add_unlock_condition(AddressUnlockCondition::new(second_address)) - .finish_output(token_supply)?; - - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs([output.clone()])? - .finish() - .await?; - - let output_id = if let Payload::Transaction(tx_payload) = block.payload().unwrap() { - let TransactionEssence::Regular(essence) = tx_payload.essence(); - // only one input from the faucet - assert_eq!(essence.inputs().len(), 1); - // provided output + remainder output - assert_eq!(essence.outputs().len(), 2); - // first output == provided output - assert_eq!(essence.outputs()[0], output); - - OutputId::new(tx_payload.id(), 0)? - } else { - panic!("missing transaction payload") - }; - - client.retry_until_included(&block.id(), None, None).await?; - - let bech32_hrp = client.get_bech32_hrp().await?; - - // output can be fetched from the second address - let output_ids_response = client - .basic_output_ids([ - QueryParameter::Address(second_address.to_bech32(bech32_hrp)), - QueryParameter::HasExpiration(false), - QueryParameter::HasTimelock(false), - QueryParameter::HasStorageDepositReturn(false), - ]) - .await?; - - assert_eq!(output_ids_response.items, [output_id]); - - Ok(()) -} - -#[ignore] -#[tokio::test] -async fn custom_input() -> Result<()> { - let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; - - let token_supply = client.get_token_supply().await?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - - let bech32_hrp = client.get_bech32_hrp().await?; - - // First create multiple outputs on our address, so there is even the possibility to select another input - let output = BasicOutputBuilder::new_with_amount(1_000_000) - .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_output(token_supply)?; - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs(vec![output; 10])? - .finish() - .await?; - client.retry_until_included(&block.id(), None, None).await?; - - let output_ids_response = client - .basic_output_ids([ - QueryParameter::Address(address.to_bech32(bech32_hrp)), - QueryParameter::HasExpiration(false), - QueryParameter::HasTimelock(false), - QueryParameter::HasStorageDepositReturn(false), - ]) - .await?; - - // The first output would be picked when using automatic input selection, so use the second one instead - let input_id = output_ids_response.items[1]; - - let input_amount = client.get_output(&input_id).await?.output().amount(); - - let output = BasicOutputBuilder::new_with_amount(input_amount) - .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_output(token_supply)?; - - let utxo_input = UtxoInput::from(input_id); - let block = client - .build_block() - .with_secret_manager(&secret_manager) - .with_outputs([output.clone()])? - .with_input(utxo_input)? - .finish() - .await?; - - client.retry_until_included(&block.id(), None, None).await?; - - if let Payload::Transaction(tx_payload) = block.payload().unwrap() { - let TransactionEssence::Regular(essence) = tx_payload.essence(); - // only the provided input is used - assert_eq!(essence.inputs().len(), 1); - assert_eq!(essence.inputs()[0], Input::Utxo(utxo_input)); - } else { - panic!("missing transaction payload") - }; - - Ok(()) -}