diff --git a/bindings/nodejs/examples/wallet/13-burn-native-token.ts b/bindings/nodejs/examples/how_tos/native_tokens/burn.ts similarity index 60% rename from bindings/nodejs/examples/wallet/13-burn-native-token.ts rename to bindings/nodejs/examples/how_tos/native_tokens/burn.ts index 712f126585..0a94e0f90a 100644 --- a/bindings/nodejs/examples/wallet/13-burn-native-token.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/burn.ts @@ -1,14 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { getUnlockedWallet } from './common'; +import { getUnlockedWallet } from '../../wallet/common'; -// The native token id. Replace it with a TokenId that is available in the account, the foundry output which minted it, -// also needs to be available. You can check this by running the `get_balance` example. You can mint a new native token -// by running the `mint_native_token` example. -// eslint-disable-next-line prefer-const -let TOKEN_ID = - '0x08dc44610c24f32f26330440f3f0d4afb562a8dfd81afe7c2f79024f8f1b9e21940100000000'; // The minimum available native token amount to search for in the account, 11 hex encoded. const MIN_AVAILABLE_AMOUNT = '0xB'; // The amount of the native token to burn, 1 hex encoded. @@ -25,15 +19,6 @@ const BURN_AMOUNT = '0x1'; // yarn run-example ./wallet/13-burn-native-token.ts async function run() { try { - if ( - TOKEN_ID == - '0x086f7011adb53642e8ed7db230c2307fe980f4aff2685c22f7c84a61ec558f691b0200000000' - ) { - throw new Error( - 'You need to change the TOKEN_ID constant before you can run this example successfully!', - ); - } - // Create the wallet const wallet = await getUnlockedWallet(); @@ -43,21 +28,25 @@ async function run() { // May want to ensure the account is synced before sending a transaction. let balance = await account.sync(); + // Get a token with sufficient balance + const tokenId = balance.nativeTokens.find( + (t) => Number(t.available) >= Number(MIN_AVAILABLE_AMOUNT), + )?.tokenId; + let token = balance.nativeTokens.find( (nativeToken) => - nativeToken.tokenId == TOKEN_ID && + nativeToken.tokenId == tokenId && Number(nativeToken.available) >= Number(MIN_AVAILABLE_AMOUNT), ); if (!token) { throw new Error( - `"Native token '${TOKEN_ID}' doesn't exist or there's not at least '${Number( + `Native token '${tokenId}' doesn't exist or there's not at least '${Number( MIN_AVAILABLE_AMOUNT, - )}' tokens of it in account 'Alice'"`, + )}' tokens of it in account 'Alice'`, ); } - console.log(`Balance BEFORE burning:\n`, token); - console.log(`Sending the burning transaction...`); + console.log(`Balance before burning: ${parseInt(token.available)}`); // Burn a native token const transaction = await account @@ -72,20 +61,16 @@ async function run() { ); console.log( - `Transaction included: ${process.env.EXPLORER_URL}/block/${blockId}`, - ); - console.log( - `Burned ${Number(BURN_AMOUNT)} native token(s) (${token.tokenId})`, + `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); balance = await account.sync(); - console.log(`Balance AFTER burning:`); token = balance.nativeTokens.find( - (nativeToken) => nativeToken.tokenId == TOKEN_ID, + (nativeToken) => nativeToken.tokenId == tokenId, ); if (token) { - console.log(token); + console.log(`Balance after burning: ${parseInt(token.available)}`); } else { console.log(`No remaining tokens`); } diff --git a/bindings/nodejs/examples/wallet/11-decrease-native-token-supply.ts b/bindings/nodejs/examples/how_tos/native_tokens/decrease_supply.ts similarity index 54% rename from bindings/nodejs/examples/wallet/11-decrease-native-token-supply.ts rename to bindings/nodejs/examples/how_tos/native_tokens/decrease_supply.ts index 3ad33da852..2fc824a32f 100644 --- a/bindings/nodejs/examples/wallet/11-decrease-native-token-supply.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/decrease_supply.ts @@ -1,15 +1,9 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { getUnlockedWallet } from './common'; +import { getUnlockedWallet } from '../../wallet/common'; -// The native token id. Replace it with a TokenId that is available in the account, the foundry output which minted it, -// also needs to be available. You can check this by running the `get-balance` example. You can mint a new native token -// by running the `mint-native-token` example. -// eslint-disable-next-line prefer-const -let TOKEN_ID = - '0x08847bd287c912fadedb6bf38900bda9f2d377b75b2a0bece8738699f56ebca4130100000000'; -// The amount of native tokens to melt, 10 hex encoded. +// The amount of native tokens to melt, 10 hex encoded. TODO Convert to int const MELT_AMOUNT = '0xA'; // In this example we will melt an existing native token with its foundry. @@ -21,16 +15,6 @@ const MELT_AMOUNT = '0xA'; // yarn run-example ./wallet/11-decrease-native-token-supply.ts async function run() { try { - if ( - TOKEN_ID == - '0x08847bd287c912fadedb6bf38900bda9f2d377b75b2a0bece8738699f56ebca4130100000000' - ) { - console.log( - 'You need to change the TOKEN_ID constant before you can run this example successfully!', - ); - return; - } - // Create the wallet const wallet = await getUnlockedWallet(); @@ -40,18 +24,19 @@ async function run() { // May want to ensure the account is synced before sending a transaction. let balance = await account.sync(); + // Find first foundry and corresponding token id + const tokenId = balance.foundries[0]; + let token = balance.nativeTokens.find( - (nativeToken) => nativeToken.tokenId == TOKEN_ID, + (nativeToken) => nativeToken.tokenId == tokenId, ); if (token == null) { throw new Error( - `Couldn't find native token '${TOKEN_ID}' in the account`, + `Couldn't find native token '${tokenId}' in the account`, ); } - console.log(`Balance BEFORE melting:\n`, token); - - console.log('Sending the melting transaction...'); + console.log(`Balance before melting:`, parseInt(token.available)); // Melt some of the circulating supply const transaction = await account @@ -66,17 +51,19 @@ async function run() { ); console.log( - `Transaction included: ${process.env.EXPLORER_URL}/block/${blockId}`, - ); - console.log( - `Melted ${Number(MELT_AMOUNT)} native tokens (${token.tokenId})`, + `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); balance = await account.sync(); token = balance.nativeTokens.find( - (nativeToken) => nativeToken.tokenId == TOKEN_ID, + (nativeToken) => nativeToken.tokenId == tokenId, ); - console.log(`Balance AFTER melting:\n`, token); + if (token == null) { + throw new Error( + `Couldn't find native token '${tokenId}' in the account`, + ); + } + console.log(`Balance after melting:`, parseInt(token.available)); } catch (error) { console.log('Error: ', error); } diff --git a/bindings/nodejs/examples/wallet/15-destroy-foundry.ts b/bindings/nodejs/examples/how_tos/native_tokens/destroy_foundry.ts similarity index 84% rename from bindings/nodejs/examples/wallet/15-destroy-foundry.ts rename to bindings/nodejs/examples/how_tos/native_tokens/destroy_foundry.ts index 539f0dc1c0..d97883a9ca 100644 --- a/bindings/nodejs/examples/wallet/15-destroy-foundry.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/destroy_foundry.ts @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { getUnlockedWallet } from './common'; +import { getUnlockedWallet } from '../../wallet/common'; // In this example we will try to destroy the first foundry there is in the account. This is only possible if its // circulating supply is 0 and no native tokens were burned. @@ -28,8 +28,7 @@ async function run() { // We try to destroy the first foundry in the account const foundry = balance.foundries[0]; - console.log(`Foundries BEFORE destroying:\n`, balance.foundries); - console.log('Sending the destroy-foundry transaction...'); + console.log(`Foundries before destroying: ${balance.foundries.length}`); // Burn a foundry const transaction = await account @@ -45,10 +44,9 @@ async function run() { console.log( `Transaction included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); - console.log(`Destroyed foundry ${foundry}`); balance = await account.sync(); - console.log(`Foundries AFTER destroying:\n`, balance.foundries); + console.log(`Foundries after destroying: ${balance.foundries.length}`); } catch (error) { console.log('Error: ', error); } diff --git a/bindings/nodejs/examples/wallet/12-increase-native-token-supply.ts b/bindings/nodejs/examples/how_tos/native_tokens/increase_supply.ts similarity index 56% rename from bindings/nodejs/examples/wallet/12-increase-native-token-supply.ts rename to bindings/nodejs/examples/how_tos/native_tokens/increase_supply.ts index 5e78bb339f..cc03bb22e2 100644 --- a/bindings/nodejs/examples/wallet/12-increase-native-token-supply.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/increase_supply.ts @@ -1,14 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { getUnlockedWallet } from './common'; +import { getUnlockedWallet } from '../../wallet/common'; -// The native token id. Replace it with a TokenId that is available in the account, the foundry output which minted it, -// also needs to be available. You can check this by running the `get-balance` example. You can mint a new native token -// by running the `mint-native-token` example. -// eslint-disable-next-line prefer-const -let TOKEN_ID = - '0x086a62922fd743b541c987020d2cb2942cf789bcefe41572854119180cb8e037a90100000000'; // The amount of native tokens to mint, 10 hex encoded. const MINT_AMOUNT = '0xA'; @@ -21,16 +15,6 @@ const MINT_AMOUNT = '0xA'; // yarn run-example ./wallet/12-increase-native-token-supply.ts async function run() { try { - if ( - TOKEN_ID == - '0x08847bd287c912fadedb6bf38900bda9f2d377b75b2a0bece8738699f56ebca4130100000000' - ) { - console.log( - 'You need to change the TOKEN_ID constant before you can run this example successfully!', - ); - return; - } - // Create the wallet const wallet = await getUnlockedWallet(); @@ -40,18 +24,19 @@ async function run() { // May want to ensure the account is synced before sending a transaction. let balance = await account.sync(); + // Find first foundry and corresponding token id + const tokenId = balance.foundries[0]; + let token = balance.nativeTokens.find( - (nativeToken) => nativeToken.tokenId == TOKEN_ID, + (nativeToken) => nativeToken.tokenId == tokenId, ); if (token == null) { throw new Error( - `Couldn't find native token '${TOKEN_ID}' in the account`, + `Couldn't find native token '${tokenId}' in the account`, ); } - console.log(`Balance BEFORE minting:\n`, token); - - console.log('Sending the minting transaction...'); + console.log(`Balance before minting:`, parseInt(token.available)); // Mint some more native tokens const transaction = await account @@ -66,17 +51,19 @@ async function run() { ); console.log( - `Transaction included: ${process.env.EXPLORER_URL}/block/${blockId}`, - ); - console.log( - `Minted ${Number(MINT_AMOUNT)} native tokens (${token.tokenId})`, + `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); balance = await account.sync(); token = balance.nativeTokens.find( - (nativeToken) => nativeToken.tokenId == TOKEN_ID, + (nativeToken) => nativeToken.tokenId == tokenId, ); - console.log(`Balance AFTER minting:\n`, token); + if (token == null) { + throw new Error( + `Couldn't find native token '${tokenId}' in the account`, + ); + } + console.log(`Balance after minting:`, parseInt(token.available)); } catch (error) { console.log('Error: ', error); } diff --git a/bindings/nodejs/examples/wallet/09-mint-native-token.ts b/bindings/nodejs/examples/how_tos/native_tokens/mint.ts similarity index 54% rename from bindings/nodejs/examples/wallet/09-mint-native-token.ts rename to bindings/nodejs/examples/how_tos/native_tokens/mint.ts index 2071642118..3c98eb443e 100644 --- a/bindings/nodejs/examples/wallet/09-mint-native-token.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/mint.ts @@ -1,9 +1,9 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { MintNativeTokenParams } from '@iota/sdk'; +import { MintNativeTokenParams, utf8ToHex } from '@iota/sdk'; -import { getUnlockedWallet } from './common'; +import { getUnlockedWallet } from '../../wallet/common'; // The circulating supply of the native token. `100` hex encoded const CIRCULATING_SUPPLY = '0x64'; @@ -25,46 +25,50 @@ async function run() { // Get the account we generated with `01-create-wallet` const account = await wallet.getAccount('Alice'); - console.log('Sending alias output transaction...'); + const balance = await account.sync(); - // First create an alias output, this needs to be done only once, because an alias can have many foundry outputs - let transaction = await account - .prepareCreateAliasOutput() - .then((prepared) => prepared.send()); - console.log(`Transaction sent: ${transaction.transactionId}`); + // We can first check if we already have an alias in our account, because an alias can have many foundry outputs and therefore we can reuse an existing one + if (balance.aliases.length > 0) { + // If we don't have an alias, we need to create one + const transaction = await account + .prepareCreateAliasOutput() + .then((prepared) => prepared.send()); + console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get included - let blockId = await account.retryTransactionUntilIncluded( - transaction.transactionId, - ); + // Wait for transaction to get included + const blockId = await account.retryTransactionUntilIncluded( + transaction.transactionId, + ); - console.log( - `Transaction included: ${process.env.EXPLORER_URL}/block/${blockId}`, - ); + console.log( + `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, + ); - await account.sync(); - console.log('Account synced'); + await account.sync(); + console.log('Account synced'); + } - console.log('Sending the minting transaction...'); + console.log('Preparing minting transaction...'); // If we omit the AccountAddress field the first address of the account is used by default const params: MintNativeTokenParams = { circulatingSupply: CIRCULATING_SUPPLY, maximumSupply: MAXIMUM_SUPPLY, + foundryMetadata: utf8ToHex('Hello, World!'), }; const prepared = await account.prepareMintNativeToken(params); - transaction = await prepared.send(); + const transaction = await prepared.send(); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - blockId = await account.retryTransactionUntilIncluded( + const blockId = await account.retryTransactionUntilIncluded( transaction.transactionId, ); console.log( - `Transaction included: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); console.log(`Minted token: ${prepared.tokenId()}`); diff --git a/bindings/nodejs/examples/wallet/07-send-native-tokens.ts b/bindings/nodejs/examples/how_tos/native_tokens/send.ts similarity index 51% rename from bindings/nodejs/examples/wallet/07-send-native-tokens.ts rename to bindings/nodejs/examples/how_tos/native_tokens/send.ts index b7988673d9..aba8901708 100644 --- a/bindings/nodejs/examples/wallet/07-send-native-tokens.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/send.ts @@ -1,14 +1,9 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { - AddressUnlockCondition, - BasicOutputBuilderParams, - Ed25519Address, - SendNativeTokensParams, -} from '@iota/sdk'; +import { SendNativeTokensParams } from '@iota/sdk'; -import { getUnlockedWallet } from './common'; +import { getUnlockedWallet } from '../../wallet/common'; // The native token amount to send, `10` hex encoded const SEND_NATIVE_TOKEN_AMOUNT = '0xA'; @@ -32,7 +27,7 @@ async function run() { const account = await wallet.getAccount('Alice'); // May want to ensure the account is synced before sending a transaction. - const balance = await account.sync(); + let balance = await account.sync(); // Get a token with sufficient balance // TODO: use BigNumber library @@ -48,62 +43,41 @@ async function run() { }, ]; - console.log( - `Sending '${Number( - SEND_NATIVE_TOKEN_AMOUNT, - )}' coin(s) to '${RECV_ADDRESS}'...`, + let token = balance.nativeTokens.find( + (nativeToken) => nativeToken.tokenId == tokenId, ); - - let transaction = await account + if (token == null) { + throw new Error( + `Couldn't find native token '${tokenId}' in the account`, + ); + } + console.log(`Balance before sending:`, parseInt(token.available)); + + const transaction = await account .prepareSendNativeTokens(outputs) .then((prepared) => prepared.send()); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - let blockId = await account.retryTransactionUntilIncluded( + const blockId = await account.retryTransactionUntilIncluded( transaction.transactionId, ); console.log( - `Transaction included: ${process.env.EXPLORER_URL}/block/${blockId}`, - ); - - await account.sync(); - console.log('Account synced'); - - console.log('Sending basic output transaction...'); - - // Send native tokens together with the required storage deposit - const client = await wallet.getClient(); - - const basicOutput: BasicOutputBuilderParams = { - unlockConditions: [ - new AddressUnlockCondition( - new Ed25519Address(RECV_ADDRESS), - ), - ], - nativeTokens: [ - { - id: tokenId, - amount: SEND_NATIVE_TOKEN_AMOUNT, - }, - ], - }; - - const output = await client.buildBasicOutput(basicOutput); - transaction = await account.sendOutputs([output]); - - console.log(`Transaction sent: ${transaction.transactionId}`); - - // Wait for transaction to get included - blockId = await account.retryTransactionUntilIncluded( - transaction.transactionId, + `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); - console.log( - `Transaction included: ${process.env.EXPLORER_URL}/block/${blockId}`, + balance = await account.sync(); + token = balance.nativeTokens.find( + (nativeToken) => nativeToken.tokenId == tokenId, ); + if (token == null) { + throw new Error( + `Couldn't find native token '${tokenId}' in the account`, + ); + } + console.log(`Balance after sending:`, parseInt(token.available)); } } catch (error) { console.log('Error: ', error); diff --git a/bindings/python/examples/how_tos/native_tokens/burn.py b/bindings/python/examples/how_tos/native_tokens/burn.py new file mode 100644 index 0000000000..e8b363e7de --- /dev/null +++ b/bindings/python/examples/how_tos/native_tokens/burn.py @@ -0,0 +1,37 @@ +from iota_sdk import Wallet, HexStr +from dotenv import load_dotenv +import os + +load_dotenv() + +# In this example we will burn native tokens + +wallet = Wallet('./alice-database') + +account = wallet.get_account('Alice') + +# Sync account with the node +balance = account.sync() + +if 'STRONGHOLD_PASSWORD' not in os.environ: + raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") + +wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) + +# Find native token with enough balance +token = [native_balance for native_balance in balance['nativeTokens'] if int(native_balance['available'], 0) >= 10][0] +print(f'Balance before burning: {int(token["available"], 0)}') + +burn_amount = 1 + +# Send transaction. +transaction = account.prepare_burn_native_token(token["tokenId"], burn_amount).send() +print(f'Transaction sent: {transaction["transactionId"]}') + +# Wait for transaction to get included +blockId = account.retry_transaction_until_included(transaction['transactionId']) +print(f'Block included: {os.environ["EXPLORER_URL"]}/block/{blockId}') + +balance = account.sync() +available_balance = int([native_balance for native_balance in balance['nativeTokens'] if native_balance['tokenId'] == token["tokenId"]][0]['available'], 0) +print(f'Balance after burning: {available_balance}') diff --git a/bindings/python/examples/how_tos/native_tokens/decrease_supply.py b/bindings/python/examples/how_tos/native_tokens/decrease_supply.py new file mode 100644 index 0000000000..95a51093a1 --- /dev/null +++ b/bindings/python/examples/how_tos/native_tokens/decrease_supply.py @@ -0,0 +1,40 @@ +from iota_sdk import Wallet, HexStr +from dotenv import load_dotenv +import os +import json + +load_dotenv() + +# In this example we will decrease the native token supply + +wallet = Wallet('./alice-database') + +account = wallet.get_account('Alice') + +# Sync account with the node +balance = account.sync() + +# Find first foundry and corresponding token id +token_id = balance['foundries'][0] + +available_balance = int([native_balance for native_balance in balance['nativeTokens'] if native_balance['tokenId'] == token_id][0]['available'], 0) +print(f'Balance before melting: {available_balance}') + +if 'STRONGHOLD_PASSWORD' not in os.environ: + raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") + +wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) + +melt_amount = 10 + +# Send transaction. +transaction = account.prepare_decrease_native_token_supply(token_id, melt_amount).send() +print(f'Transaction sent: {transaction["transactionId"]}') + +# Wait for transaction to get included +blockId = account.retry_transaction_until_included(transaction['transactionId']) +print(f'Block included: {os.environ["EXPLORER_URL"]}/block/{blockId}') + +balance = account.sync() +available_balance = int([native_balance for native_balance in balance['nativeTokens'] if native_balance['tokenId'] == token_id][0]['available'], 0) +print(f'Balance after melting: {available_balance}') diff --git a/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py b/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py new file mode 100644 index 0000000000..cc047edad8 --- /dev/null +++ b/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py @@ -0,0 +1,35 @@ +from iota_sdk import Wallet, HexStr +from dotenv import load_dotenv +import os +import json + +load_dotenv() + +# In this example we will destroy a foundry + +wallet = Wallet('./alice-database') + +account = wallet.get_account('Alice') + +# Sync account with the node +balance = account.sync() +print(f'Foundries before destroying: {len(balance["foundries"])}') + +if 'STRONGHOLD_PASSWORD' not in os.environ: + raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") + +wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) + +# We try to destroy the first foundry in the account +foundry_id = balance['foundries'][0] + +# Send transaction. +transaction = account.prepare_destroy_foundry(foundry_id).send() +print(f'Transaction sent: {transaction["transactionId"]}') + +# Wait for transaction to get included +blockId = account.retry_transaction_until_included(transaction['transactionId']) +print(f'Block included: {os.environ["EXPLORER_URL"]}/block/{blockId}') + +balance = account.sync() +print(f'Foundries after destroying: {len(balance["foundries"])}') diff --git a/bindings/python/examples/how_tos/native_tokens/increase_supply.py b/bindings/python/examples/how_tos/native_tokens/increase_supply.py new file mode 100644 index 0000000000..4fb6b43af4 --- /dev/null +++ b/bindings/python/examples/how_tos/native_tokens/increase_supply.py @@ -0,0 +1,39 @@ +from iota_sdk import Wallet +from dotenv import load_dotenv +import os + +load_dotenv() + +# In this example we will decrease the native token supply + +wallet = Wallet('./alice-database') + +account = wallet.get_account('Alice') + +# Sync account with the node +balance = account.sync() + +# Find first foundry and corresponding token id +token_id = balance['foundries'][0] + +available_balance = int([native_balance for native_balance in balance['nativeTokens'] if native_balance['tokenId'] == token_id][0]['available'], 0) +print(f'Balance before minting: {available_balance}') + +if 'STRONGHOLD_PASSWORD' not in os.environ: + raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") + +wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) + +mint_amount = 10 + +# Prepare and send transaction. +transaction = account.prepare_increase_native_token_supply(token_id, mint_amount).send() +print(f'Transaction sent: {transaction["transactionId"]}') + +# Wait for transaction to get included +blockId = account.retry_transaction_until_included(transaction['transactionId']) +print(f'Block included: {os.environ["EXPLORER_URL"]}/block/{blockId}') + +balance = account.sync() +available_balance = int([native_balance for native_balance in balance['nativeTokens'] if native_balance['tokenId'] == token_id][0]['available'], 0) +print(f'Balance after minting: {available_balance}') diff --git a/bindings/python/examples/how_tos/native_tokens/mint.py b/bindings/python/examples/how_tos/native_tokens/mint.py new file mode 100644 index 0000000000..1a5fab69c3 --- /dev/null +++ b/bindings/python/examples/how_tos/native_tokens/mint.py @@ -0,0 +1,55 @@ +from iota_sdk import Wallet, utf8_to_hex +from dotenv import load_dotenv +import time +import os + +load_dotenv() + +# In this example we will mint native tokens + +wallet = Wallet('./alice-database') + +account = wallet.get_account('Alice') + +if 'STRONGHOLD_PASSWORD' not in os.environ: + raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") + +wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) + +# Sync account with the node +response = account.sync() + +# We can first check if we already have an alias in our account, because an alias can have many foundry outputs and therefore we can reuse an existing one +if len(account.aliases) == 0: + # If we don't have an alias, we need to create one + transaction = account.prepare_create_alias_output(None, None).send() + print(f'Transaction sent: {transaction["transactionId"]}') + + # Wait for transaction to get included + blockId = account.retry_transaction_until_included(transaction['transactionId']) + print(f'Block included: {os.environ["EXPLORER_URL"]}/block/{blockId}') + + account.sync() + print("Account synced") + +print('Preparing minting transaction...') + +params = { + "circulatingSupply": hex(100), + "maximumSupply": hex(100), + "foundryMetadata": utf8_to_hex('Hello, World!'), +} + +prepared_transaction = account.prepare_mint_native_token(params, None) +transaction = prepared_transaction.send() +print(f'Transaction sent: {transaction["transactionId"]}') + +# Wait for transaction to get included +blockId = account.retry_transaction_until_included(transaction['transactionId']) +print(f'Block included: {os.environ["EXPLORER_URL"]}/block/{blockId}') + +print(f'Minted token: {prepared_transaction.token_id()}') + +# Ensure the account is synced after minting. +account.sync() +print('Account synced') diff --git a/bindings/python/examples/how_tos/native_tokens/send.py b/bindings/python/examples/how_tos/native_tokens/send.py new file mode 100644 index 0000000000..2402233c6f --- /dev/null +++ b/bindings/python/examples/how_tos/native_tokens/send.py @@ -0,0 +1,42 @@ +from iota_sdk import Wallet +from dotenv import load_dotenv +import os +import json + +load_dotenv() + +# In this example we will send native tokens + +wallet = Wallet('./alice-database') + +account = wallet.get_account('Alice') + +# Sync account with the node +balance = account.sync() + +token = [native_balance for native_balance in balance['nativeTokens'] if int(native_balance['available'], 0) >= 10][0] +print(f'Balance before sending: {int(token["available"], 0)}') + +if 'STRONGHOLD_PASSWORD' not in os.environ: + raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") + +wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) + +outputs = [{ + "address": "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu", + "nativeTokens": [( + token["tokenId"], + hex(10) + )], +}] + +transaction = account.prepare_send_native_tokens(outputs, None).send() +print(f'Transaction sent: {transaction["transactionId"]}') + +# Wait for transaction to get included +blockId = account.retry_transaction_until_included(transaction['transactionId']) +print(f'Block included: {os.environ["EXPLORER_URL"]}/block/{blockId}') + +balance = account.sync() +available_balance = int([native_balance for native_balance in balance['nativeTokens'] if native_balance['tokenId'] == token["tokenId"]][0]['available'], 0) +print(f'Balance after sending: {available_balance}') diff --git a/bindings/python/examples/wallet/5-send-native-tokens.py b/bindings/python/examples/wallet/5-send-native-tokens.py deleted file mode 100644 index 3fb7109fb6..0000000000 --- a/bindings/python/examples/wallet/5-send-native-tokens.py +++ /dev/null @@ -1,30 +0,0 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv -import os - -load_dotenv() - -# In this example we will send native tokens - -wallet = Wallet('./alice-database') - -account = wallet.get_account('Alice') - -# Sync account with the node -response = account.sync() - -if 'STRONGHOLD_PASSWORD' not in os.environ: - raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") - -wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) - -outputs = [{ - "address": "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu", - "nativeTokens": [( - "0x08a5526c4a15558b709340822edf00cb348d8606a27e2e59b00432a0afe8afb74d0100000000", - hex(10) - )], -}]; - -transaction = account.prepare_send_native_tokens(outputs, None).send() -print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction["blockId"]}') diff --git a/bindings/python/examples/wallet/7-mint-native-tokens.py b/bindings/python/examples/wallet/7-mint-native-tokens.py deleted file mode 100644 index 5d9a14538a..0000000000 --- a/bindings/python/examples/wallet/7-mint-native-tokens.py +++ /dev/null @@ -1,40 +0,0 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv -import time -import os - -load_dotenv() - -# In this example we will mint native tokens - -wallet = Wallet('./alice-database') - -account = wallet.get_account('Alice') - -if 'STRONGHOLD_PASSWORD' not in os.environ: - raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") - -wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) - -# Sync account with the node -response = account.sync() - -transaction = account.prepare_create_alias_output(None, None).send() - -# Wait a few seconds for the transaction to get confirmed -time.sleep(7) - -account.sync() - -params = { - # 1000 hex encoded - "circulatingSupply": "0x3e8", - "maximumSupply": "0x3e8", - "foundryMetadata": "0xab", -} - -transaction = account.prepare_mint_native_token(params, None) -print(f'Token id: {transaction.token_id()}') - -transaction = transaction.send() -print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction["blockId"]}') diff --git a/bindings/python/examples/wallet/burn_native_tokens.py b/bindings/python/examples/wallet/burn_native_tokens.py deleted file mode 100644 index 05ba03fcaf..0000000000 --- a/bindings/python/examples/wallet/burn_native_tokens.py +++ /dev/null @@ -1,27 +0,0 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv -import os - -load_dotenv() - -# In this example we will burn native tokens - -wallet = Wallet('./alice-database') - -account = wallet.get_account('Alice') - -# Sync account with the node -response = account.sync() - -if 'STRONGHOLD_PASSWORD' not in os.environ: - raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") - -wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) - -# TODO: replace with your own values. -token_id = "0x08429fe5864378ce70699fc2d22bb144cb86a3c4833d136e3b95c5dadfd6ba0cef0300000000" -burn_amount = "0x5" - -# Send transaction. -transaction = account.prepare_burn_native_token(token_id, burn_amount).send() -print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction["blockId"]}') diff --git a/bindings/python/examples/wallet/decrease-native-tokens-supply.py b/bindings/python/examples/wallet/decrease-native-tokens-supply.py deleted file mode 100644 index 6313b04e0d..0000000000 --- a/bindings/python/examples/wallet/decrease-native-tokens-supply.py +++ /dev/null @@ -1,27 +0,0 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv -import os - -load_dotenv() - -# In this example we will decrease the native token supply - -wallet = Wallet('./alice-database') - -account = wallet.get_account('Alice') - -# Sync account with the node -account.sync() - -if 'STRONGHOLD_PASSWORD' not in os.environ: - raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") - -wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) - -# TODO: replace with your own values. -token_id = "0x08429fe5864378ce70699fc2d22bb144cb86a3c4833d136e3b95c5dadfd6ba0cef0500000000" -melt_amount = 32 - -# Send transaction. -transaction = account.prepare_decrease_native_token_supply(token_id, melt_amount).send() -print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction["blockId"]}') diff --git a/bindings/python/examples/wallet/destroy_foundry.py b/bindings/python/examples/wallet/destroy_foundry.py deleted file mode 100644 index 1b9aab3ae8..0000000000 --- a/bindings/python/examples/wallet/destroy_foundry.py +++ /dev/null @@ -1,27 +0,0 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv -import os - -load_dotenv() - -# In this example we will destroy a foundry - -wallet = Wallet('./alice-database') - -account = wallet.get_account('Alice') - -# Sync account with the node -response = account.sync() - -if 'STRONGHOLD_PASSWORD' not in os.environ: - raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") - -wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) - -# TODO: replace with your own values. -foundry_id = "0x08429fe5864378ce70699fc2d22bb144cb86a3c4833d136e3b95c5dadfd6ba0cef0500000000" - -# Send transaction. -transaction = account.prepare_destroy_foundry(foundry_id).send() -print( - f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction["blockId"]}') diff --git a/bindings/python/examples/wallet/transaction_options.py b/bindings/python/examples/wallet/transaction_options.py new file mode 100644 index 0000000000..6d91e220bb --- /dev/null +++ b/bindings/python/examples/wallet/transaction_options.py @@ -0,0 +1,30 @@ +from iota_sdk import Wallet, TransactionOptions, TaggedDataPayload, utf8_to_hex, RemainderValueStrategy +from dotenv import load_dotenv +import os + +load_dotenv() + +# This example sends a transaction with a tagged data payload. + +wallet = Wallet('./alice-database') + +account = wallet.get_account('Alice') + +# Sync account with the node +response = account.sync() + +if 'STRONGHOLD_PASSWORD' not in os.environ: + raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") + +wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) + +outputs = [{ + "address": "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu", + "amount": "1000000", +}] + +transaction = account.send_amount(outputs, TransactionOptions(remainder_value_strategy=RemainderValueStrategy.ReuseAddress, + note="my first tx", tagged_data_payload=TaggedDataPayload(utf8_to_hex("tag"), utf8_to_hex("data")))) +print(transaction) +print( + f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction["blockId"]}') diff --git a/bindings/python/iota_sdk/__init__.py b/bindings/python/iota_sdk/__init__.py index 610afe11d4..aa815b24bb 100644 --- a/bindings/python/iota_sdk/__init__.py +++ b/bindings/python/iota_sdk/__init__.py @@ -14,5 +14,7 @@ from .types.feature import * from .types.native_token import * from .types.output_id import * +from .types.payload import * from .types.token_scheme import * +from .types.transaction_options import * from .types.unlock_condition import * diff --git a/bindings/python/iota_sdk/types/payload.py b/bindings/python/iota_sdk/types/payload.py new file mode 100644 index 0000000000..6b5c5634c0 --- /dev/null +++ b/bindings/python/iota_sdk/types/payload.py @@ -0,0 +1,67 @@ +# Copyright 2023 IOTA Stiftung +# SPDX-License-Identifier: Apache-2.0 + +from iota_sdk.types.common import HexStr +from enum import Enum +from typing import Any, Optional + + +class PayloadType(Enum): + TreasuryTransaction = 4 + TaggedData = 5 + Transaction = 6 + Milestone = 7 + + +class Payload(): + def __init__(self, type, milestone: Optional[Any] = None, tagged_data=None, transaction=None, treasury_transaction: Optional[Any] = None): + """Initialize a payload + """ + self.type = type + self.milestone = milestone + self.tagged_data = tagged_data + self.transaction = transaction + self.treasury_transaction = treasury_transaction + + def as_dict(self): + config = {k: v for k, v in self.__dict__.items() if v != None} + + if "milestone" in config: + del config["milestone"] + if "tagged_data" in config: + del config["tagged_data"] + if "transaction" in config: + del config["transaction"] + if "treasury_transaction" in config: + del config["treasury_transaction"] + + config['type'] = config['type'].value + + return config + + +class MilestonePayload(Payload): + def __init__(self, essence, signatures): + """Initialize a MilestonePayload + """ + self.essence = essence + self.signatures = signatures + super().__init__(PayloadType.Milestone, milestone=self) + + +class TaggedDataPayload(Payload): + def __init__(self, tag: HexStr, data: HexStr): + """Initialize a TaggedDataPayload + """ + self.tag = tag + self.data = data + super().__init__(PayloadType.TaggedData, tagged_data=self) + + +class TransactionPayload(Payload): + def __init__(self, essence, unlocks): + """Initialize a TransactionPayload + """ + self.essence = essence + self.unlocks = unlocks + super().__init__(PayloadType.Transaction, transaction=self) diff --git a/bindings/python/iota_sdk/types/transaction_options.py b/bindings/python/iota_sdk/types/transaction_options.py new file mode 100644 index 0000000000..bf8b72f9c5 --- /dev/null +++ b/bindings/python/iota_sdk/types/transaction_options.py @@ -0,0 +1,53 @@ +# Copyright 2023 IOTA Stiftung +# SPDX-License-Identifier: Apache-2.0 + +from iota_sdk.types.burn import Burn +from iota_sdk.types.output_id import OutputId +from iota_sdk.types.payload import TaggedDataPayload +from enum import Enum +from typing import Optional, List + + +class RemainderValueStrategyCustomAddress: + def __init__(self, + address: str, + key_index: int, + internal: bool, + used: bool): + self.address = address + self.keyIndex = key_index + self.internal = internal + self.used = used + + def as_dict(self): + return dict({"strategy": "CustomAddress", "value": self.__dict__}) + + +class RemainderValueStrategy(Enum): + ChangeAddress = None, + ReuseAddress = None, + + def as_dict(self): + return dict({"strategy": self.name, "value": self.value[0]}) + + +class TransactionOptions(): + def __init__(self, remainder_value_strategy: Optional[RemainderValueStrategy | RemainderValueStrategyCustomAddress] = None, + tagged_data_payload: Optional[TaggedDataPayload] = None, + custom_inputs: Optional[List[OutputId]] = None, + mandatory_inputs: Optional[List[OutputId]] = None, + burn: Optional[Burn] = None, + note: Optional[str] = None, + allow_micro_amount: Optional[bool] = None): + """Initialize TransactionOptions + """ + self.remainder_value_strategy = remainder_value_strategy + self.tagged_data_payload = tagged_data_payload + self.custom_inputs = custom_inputs + self.mandatory_inputs = mandatory_inputs + self.burn = burn + self.note = note + self.allow_micro_amount = allow_micro_amount + + def as_dict(self): + return dict(self.__dict__) diff --git a/bindings/python/iota_sdk/wallet/account.py b/bindings/python/iota_sdk/wallet/account.py index 1cd582366e..c9f25dce4b 100644 --- a/bindings/python/iota_sdk/wallet/account.py +++ b/bindings/python/iota_sdk/wallet/account.py @@ -7,6 +7,7 @@ from iota_sdk.types.common import HexStr from iota_sdk.types.native_token import NativeToken from iota_sdk.types.output_id import OutputId +from iota_sdk.types.transaction_options import TransactionOptions from typing import List, Optional @@ -41,7 +42,7 @@ def _call_account_method(self, method, data=None): return message - def prepare_burn(self, burn: Burn, options=None): + def prepare_burn(self, burn: Burn, options: Optional[TransactionOptions] = None): """ A generic `prepare_burn()` function that can be used to prepare the burn of native tokens, nfts, foundries and aliases. """ @@ -54,9 +55,9 @@ def prepare_burn(self, burn: Burn, options=None): return PreparedTransactionData(self, prepared) def prepare_burn_native_token(self, - token_id: HexStr, - burn_amount: int, - options=None): + token_id: HexStr, + burn_amount: int, + options: Optional[TransactionOptions] = None): """Burn native tokens. This doesn't require the foundry output which minted them, but will not increase the foundries `melted_tokens` field, which makes it impossible to destroy the foundry output. Therefore it's recommended to use melting, if the foundry output is available. @@ -70,8 +71,8 @@ def prepare_burn_native_token(self, return PreparedTransactionData(self, prepared) def prepare_burn_nft(self, - nft_id: HexStr, - options=None): + nft_id: HexStr, + options: Optional[TransactionOptions] = None): """Burn an nft output. """ prepared = self._call_account_method( @@ -83,8 +84,8 @@ def prepare_burn_nft(self, return PreparedTransactionData(self, prepared) def prepare_consolidate_outputs(self, - force: bool, - output_consolidation_threshold: Optional[int] = None): + force: bool, + output_consolidation_threshold: Optional[int] = None): """Consolidate outputs. """ prepared = self._call_account_method( @@ -96,8 +97,8 @@ def prepare_consolidate_outputs(self, return PreparedTransactionData(self, prepared) def prepare_create_alias_output(self, - params, - options): + params, + options: Optional[TransactionOptions] = None): """Create an alias output. """ prepared = self._call_account_method( @@ -109,8 +110,8 @@ def prepare_create_alias_output(self, return PreparedTransactionData(self, prepared) def prepare_destroy_alias(self, - alias_id: HexStr, - options=None): + alias_id: HexStr, + options: Optional[TransactionOptions] = None): """Destroy an alias output. """ @@ -123,8 +124,8 @@ def prepare_destroy_alias(self, return PreparedTransactionData(self, prepared) def prepare_destroy_foundry(self, - foundry_id: HexStr, - options=None): + foundry_id: HexStr, + options: Optional[TransactionOptions] = None): """Destroy a foundry output with a circulating supply of 0. """ prepared = self._call_account_method( @@ -226,9 +227,9 @@ def pending_transactions(self): ) def prepare_decrease_native_token_supply(self, - token_id: HexStr, - melt_amount: int, - options=None): + token_id: HexStr, + melt_amount: int, + options: Optional[TransactionOptions] = None): """Melt native tokens. This happens with the foundry output which minted them, by increasing it's `melted_tokens` field. """ @@ -241,7 +242,7 @@ def prepare_decrease_native_token_supply(self, ) return PreparedTransactionData(self, prepared) - def prepare_increase_native_token_supply(self, token_id: HexStr, mint_amount: int, options=None): + def prepare_increase_native_token_supply(self, token_id: HexStr, mint_amount: int, options: Optional[TransactionOptions] = None): """Mint more native token. """ prepared = self._call_account_method( @@ -253,7 +254,7 @@ def prepare_increase_native_token_supply(self, token_id: HexStr, mint_amount: in ) return PreparedMintTokenTransaction(account=self, prepared_transaction_data=prepared) - def prepare_mint_native_token(self, params, options=None): + def prepare_mint_native_token(self, params, options: Optional[TransactionOptions] = None): """Mint native token. """ prepared = self._call_account_method( @@ -273,7 +274,7 @@ def minimum_required_storage_deposit(self, output): } ) - def prepare_mint_nfts(self, params, options=None): + def prepare_mint_nfts(self, params, options: Optional[TransactionOptions] = None): """Mint nfts. """ prepared = self._call_account_method( @@ -291,7 +292,7 @@ def get_balance(self): 'getBalance' ) - def prepare_output(self, output_options, transaction_options=None): + def prepare_output(self, output_options, transaction_options: Optional[TransactionOptions] = None): """Prepare an output for sending If the amount is below the minimum required storage deposit, by default the remaining amount will automatically be added with a StorageDepositReturn UnlockCondition, when setting the ReturnStrategy to `gift`, the full @@ -306,7 +307,7 @@ def prepare_output(self, output_options, transaction_options=None): } ) - def prepare_send_amount(self, params, options=None): + def prepare_send_amount(self, params, options: Optional[TransactionOptions] = None): """Prepare send amount. """ prepared = self._call_account_method( @@ -317,7 +318,7 @@ def prepare_send_amount(self, params, options=None): ) return PreparedTransactionData(self, prepared) - def prepare_transaction(self, outputs, options=None): + def prepare_transaction(self, outputs, options: Optional[TransactionOptions] = None): """Prepare transaction. """ prepared = self._call_account_method( @@ -351,7 +352,7 @@ def sync(self, options=None): } ) - def send_amount(self, params, options=None): + def send_amount(self, params, options: Optional[TransactionOptions] = None): """Send amount. """ return self._call_account_method( @@ -361,7 +362,7 @@ def send_amount(self, params, options=None): } ) - def prepare_send_native_tokens(self, params, options=None): + def prepare_send_native_tokens(self, params, options: Optional[TransactionOptions] = None): """Send native tokens. """ prepared = self._call_account_method( @@ -372,7 +373,7 @@ def prepare_send_native_tokens(self, params, options=None): ) return PreparedTransactionData(self, prepared) - def prepare_send_nft(self, params, options=None): + def prepare_send_nft(self, params, options: Optional[TransactionOptions] = None): """Send nft. """ prepared = self._call_account_method( @@ -438,7 +439,7 @@ def claim_outputs(self, output_ids_to_claim: List[OutputId]): } ) - def send_outputs(self, outputs, options=None): + def send_outputs(self, outputs, options: Optional[TransactionOptions] = None): """Send outputs in a transaction. """ return self._call_account_method( diff --git a/bindings/python/iota_sdk/wallet/common.py b/bindings/python/iota_sdk/wallet/common.py index 0a7d5b45bf..d334172554 100644 --- a/bindings/python/iota_sdk/wallet/common.py +++ b/bindings/python/iota_sdk/wallet/common.py @@ -2,17 +2,53 @@ # SPDX-License-Identifier: Apache-2.0 from iota_sdk import call_wallet_method +import humps import json -from json import dumps +from json import dumps, JSONEncoder +from enum import Enum def _call_method_routine(func): """The routine of dump json string and call call_wallet_method() """ def wrapper(*args, **kwargs): + class MyEncoder(JSONEncoder): + def default(self, obj): + as_dict_method = getattr(obj, "as_dict", None) + if callable(as_dict_method): + return obj.as_dict() + if isinstance(obj, str): + return obj + if isinstance(obj, Enum): + return obj.__dict__ + if isinstance(obj, dict): + return obj + if hasattr(obj, "__dict__"): + obj_dict = obj.__dict__ + + items_method = getattr(self, "items", None) + if callable(items_method): + for k, v in obj_dict.items(): + obj_dict[k] = dumps(v, cls=MyEncoder) + return obj_dict + return obj_dict + return obj message = func(*args, **kwargs) - message = dumps(message) + message = dumps(list(message.values()), cls=MyEncoder) + deserialized = json.loads(message) + + def remove_none(obj): + if isinstance(obj, (list, tuple, set)): + return type(obj)(remove_none(x) for x in obj if x is not None) + elif isinstance(obj, dict): + return type(obj)((remove_none(k), remove_none(v)) + for k, v in obj.items() if k is not None and v is not None) + else: + return obj + deserialized_null_filtered = remove_none(deserialized) + + message = dumps(humps.camelize(deserialized_null_filtered)) # Send message to the Rust library response = call_wallet_method(args[0].handle, message) diff --git a/cli/src/command/wallet.rs b/cli/src/command/wallet.rs index e0f8132741..75691cd16e 100644 --- a/cli/src/command/wallet.rs +++ b/cli/src/command/wallet.rs @@ -17,7 +17,7 @@ use log::LevelFilter; use crate::{ error::Error, - helper::{enter_or_generate_mnemonic, generate_mnemonic, get_password, import_mnemonic}, + helper::{check_file_exists, enter_or_generate_mnemonic, generate_mnemonic, get_password, import_mnemonic}, println_log_info, }; @@ -208,6 +208,8 @@ pub async fn node_info_command(storage_path: &Path) -> Result { } pub async fn restore_command(storage_path: &Path, snapshot_path: &Path, backup_path: &Path) -> Result { + check_file_exists(backup_path).await?; + let password = get_password("Stronghold password", false)?; let secret_manager = SecretManager::Stronghold( StrongholdSecretManager::builder() @@ -226,6 +228,11 @@ pub async fn restore_command(storage_path: &Path, snapshot_path: &Path, backup_p wallet.restore_backup(backup_path.into(), password, None, None).await?; + println_log_info!( + "Wallet has been restored from the backup file \"{}\".", + backup_path.display() + ); + Ok(wallet) } diff --git a/cli/src/helper.rs b/cli/src/helper.rs index 1ab3e36a1f..f6e61012fb 100644 --- a/cli/src/helper.rs +++ b/cli/src/helper.rs @@ -1,6 +1,8 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::path::Path; + use chrono::{DateTime, NaiveDateTime, Utc}; use clap::Parser; use dialoguer::{console::Term, theme::ColorfulTheme, Input, Select}; @@ -9,7 +11,7 @@ use iota_sdk::{ wallet::{Account, Wallet}, }; use tokio::{ - fs::OpenOptions, + fs::{self, OpenOptions}, io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, }; @@ -244,3 +246,18 @@ pub fn to_utc_date_time(ts_millis: u128) -> Result, Error> { Ok(DateTime::from_utc(naive_time, Utc)) } + +pub async fn check_file_exists(path: &Path) -> Result<(), Error> { + if !fs::try_exists(path).await.map_err(|e| { + Error::Miscellaneous(format!( + "Error while accessing the file '{path}': '{e}'", + path = path.display() + )) + })? { + return Err(Error::Miscellaneous(format!( + "File '{path}' does not exist.", + path = path.display() + ))); + } + Ok(()) +} diff --git a/documentation/sdk/docs/_admonitions/_destroy_foundry.md b/documentation/sdk/docs/_admonitions/_destroy_foundry.md new file mode 100644 index 0000000000..fb95df9f4c --- /dev/null +++ b/documentation/sdk/docs/_admonitions/_destroy_foundry.md @@ -0,0 +1,5 @@ +:::warning Destroying Foundry + +A foundry can only be destroyed (and its storage deposit be claimed) if the circulating supply is zero. Therefore if you control the foundry and want to destroy it in the future, you should consider [melting your native tokens](../how_tos/native_tokens/decrease_supply.mdx) instead of [burning](../how_tos/native_tokens/burn.mdx) them. + +::: \ No newline at end of file diff --git a/documentation/sdk/docs/how_tos/native_tokens/burn.mdx b/documentation/sdk/docs/how_tos/native_tokens/burn.mdx new file mode 100644 index 0000000000..4b9f7290ea --- /dev/null +++ b/documentation/sdk/docs/how_tos/native_tokens/burn.mdx @@ -0,0 +1,64 @@ +--- +title: Burn Native Tokens +sidebar_label: Burn +description: 'How to burn native tokens' +image: /img/logo/iota_mark_light.png +keywords: +- how to +- burn +- burn native token +- foundry +- nodejs +- python +- rust +--- + +import CodeBlock from '@theme/CodeBlock'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import NodejsCode from '!!raw-loader!../../../../../bindings/nodejs/examples/how_tos/native_tokens/burn.ts'; +import PythonCode from '!!raw-loader!../../../../../bindings/python/examples/how_tos/native_tokens/burn.py'; +import RustCode from '!!raw-loader!../../../../../sdk/examples/how_tos/native_tokens/burn.rs'; +import DestroyFoundry from '../../_admonitions/_destroy_foundry.md'; + +You may want to burn some native tokens. To do so, you will need to call the +`Account.burn(burn, options)` function. + + + +## Code Example + +The following example will: + +1. Create a wallet. +2. Get Alice's account which was [created in the first guide](../accounts_and_addresses/create_account.mdx). +3. [Get the account's balance](../accounts_and_addresses/check_balance.mdx). +4. Burn 1 native token of the first ID with enough funds. + + + + + + {RustCode} + + + + + {NodejsCode} + + + + + {PythonCode} + + + + +## Expected Output + +```plaintext +Balance before burning: 30 +Transaction sent: 0x8c93b86b003b0476266f1d4d0c486dfcbbb72d4991eb5d63d5466a6cad93e9f2 +Block included: https://explorer.shimmer.network/testnet/block/0x49e2ec7ac5a88b1b0ffcde20bff437b53ef74f38368c28af402435f45dd5b138 +Balance after burning: 29 +``` diff --git a/documentation/sdk/docs/how_tos/native_tokens/decrease_supply.mdx b/documentation/sdk/docs/how_tos/native_tokens/decrease_supply.mdx new file mode 100644 index 0000000000..59564ad87d --- /dev/null +++ b/documentation/sdk/docs/how_tos/native_tokens/decrease_supply.mdx @@ -0,0 +1,61 @@ +--- +title: Melt Native Tokens +sidebar_label: Decrease Circulating Supply +description: 'How to melt native tokens' +image: /img/logo/iota_mark_light.png +keywords: +- how to +- melt native token +- foundry +- nodejs +- python +- rust +--- + +import CodeBlock from '@theme/CodeBlock'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import NodejsCode from '!!raw-loader!../../../../../bindings/nodejs/examples/how_tos/native_tokens/decrease_supply.ts'; +import PythonCode from '!!raw-loader!../../../../../bindings/python/examples/how_tos/native_tokens/decrease_supply.py'; +import RustCode from '!!raw-loader!../../../../../sdk/examples/how_tos/native_tokens/decrease_supply.rs'; + +You may want to melt some of your circulating native tokens. To do so, you will need to call the +`Account.decrease_native_token_supply(token_id, melt_amount, options)` function. + +If you melt your native tokens, you can still destroy the foundry that created them. + +## Code Example + +The following example will: + +1. Create a wallet. +2. Get Alice's account which was [created in the first guide](../accounts_and_addresses/create_account.mdx). +3. Get the account's balance. +4. Melt 10 native tokens of the first ID. + + + + + {RustCode} + + + + + {NodejsCode} + + + + + {PythonCode} + + + + +## Expected Output + +```plaintext +Balance before melting: 100 +Transaction sent: 0x7b3b152127d3cf2921891ad1ea279ae484d559e4040a0d3ca8940c89cb64b34e +Block included: https://explorer.shimmer.network/testnet/block/0x70d34d37e9a30d19b641ac24529c2fc512f8c9169ffa3521be4b05b7fefe8933 +Balance after melting: 90 +``` diff --git a/documentation/sdk/docs/how_tos/native_tokens/destroy_foundry.mdx b/documentation/sdk/docs/how_tos/native_tokens/destroy_foundry.mdx new file mode 100644 index 0000000000..bcc63de221 --- /dev/null +++ b/documentation/sdk/docs/how_tos/native_tokens/destroy_foundry.mdx @@ -0,0 +1,69 @@ +--- +title: Destroy a Foundry +sidebar_label: Destroy a Foundry +description: 'How to destroy a foundry using iota-sdk' +image: /img/logo/iota_mark_light.png +keywords: +- how to +- destroy +- destroy foundry +- foundry +- nodejs +- python +- rust +--- + +import CodeBlock from '@theme/CodeBlock'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import NodejsCode from '!!raw-loader!../../../../../bindings/nodejs/examples/how_tos/native_tokens/destroy_foundry.ts'; +import PythonCode from '!!raw-loader!../../../../../bindings/python/examples/how_tos/native_tokens/destroy_foundry.py'; +import RustCode from '!!raw-loader!../../../../../sdk/examples/how_tos/native_tokens/destroy_foundry.rs'; +import DestroyFoundry from '../../_admonitions/_destroy_foundry.md'; + +You can destroy a foundry by calling the `Account.burn(burn, options)` function. The function will +destroy a foundry output as long as its circulating +[native token](https://wiki.iota.org/shimmer/introduction/explanations/ledger/foundry) supply is zero. + + + +## Code Example + +Before you run the example you should make sure the first foundy on your account has zero circulating supply. If you have no available +foundries, you can create one by [minting a native token](mint.mdx). If you've already minted your +tokens but need to empty the foundry, you can decrease your native token supply by [melting them](decrease_supply.mdx). + +The following example will: + +1. Create a wallet. +2. Get Alice's account which was [created in the first guide](../accounts_and_addresses/create_account.mdx). +3. [Get the account's balance](./../accounts_and_addresses/check_balance.mdx) and show the amount of foundries. +4. Try to destroy the first [foundry](https://wiki.iota.org/shimmer/introduction/explanations/ledger/foundry) output. +5. Get the account's foundry count again to show the difference after step 4. + + + + + {RustCode} + + + + + {NodejsCode} + + + + + {PythonCode} + + + + +## Expected Output + +```plaintext +Foundries before destroying: 51 +Transaction sent: 0xe0930e445aa9e78f59e3979744102bb0980fe6332950c7bb4785700dcb24fe8e +Block included: https://explorer.shimmer.network/testnet/block/0x31d08663ae1d175d798d0f2925f41fa97f0eee8f9e233a4f5dacedb1398ed544 +Foundries after destroying: 50 +``` \ No newline at end of file diff --git a/documentation/sdk/docs/how_tos/native_tokens/increase_supply.mdx b/documentation/sdk/docs/how_tos/native_tokens/increase_supply.mdx new file mode 100644 index 0000000000..1d27d75a5f --- /dev/null +++ b/documentation/sdk/docs/how_tos/native_tokens/increase_supply.mdx @@ -0,0 +1,59 @@ +--- +title: Increase Native Token Circulating Supply +sidebar_label: Increase Circulating Supply +description: 'How to increase the circulating supply of native tokens' +image: /img/logo/iota_mark_light.png +keywords: +- how to +- increase native token circulating supply +- foundry +- nodejs +- python +- rust +--- + +import CodeBlock from '@theme/CodeBlock'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import NodejsCode from '!!raw-loader!../../../../../bindings/nodejs/examples/how_tos/native_tokens/increase_supply.ts'; +import PythonCode from '!!raw-loader!../../../../../bindings/python/examples/how_tos/native_tokens/increase_supply.py'; +import RustCode from '!!raw-loader!../../../../../sdk/examples/how_tos/native_tokens/increase_supply.rs'; + +You may want to increase the circulating native token supply. To do so, you will need to call the +`Account.increase_native_token_supply(token_id, mint_amount, options)` function. + +## Code Example + +The following example will: + +1. Create a wallet. +2. Get Alice's account which was [created in the first guide](../accounts_and_addresses/create_account.mdx). +3. Get the account's balance. +4. Mint 10 native tokens of the first ID. + + + + + {RustCode} + + + + + {NodejsCode} + + + + + {PythonCode} + + + + +## Expected Output + +``` +Balance before minting: 90 +Transaction sent: 0x6dbec7c73ef511c3945eda6265d3de29a4f572a3bd95e39221cff18135e18ca6 +Block included: https://explorer.shimmer.network/testnet/block/0x7b677c0562d8b2a7fdc1fd6182bb6364af517d41ec38a9dab9b29a1c7422f574 +Balance after minting: 100 +``` diff --git a/documentation/sdk/docs/how_tos/native_tokens/mint.mdx b/documentation/sdk/docs/how_tos/native_tokens/mint.mdx new file mode 100644 index 0000000000..c2887df7bd --- /dev/null +++ b/documentation/sdk/docs/how_tos/native_tokens/mint.mdx @@ -0,0 +1,68 @@ +--- +title: Mint Native Tokens +sidebar_label: Mint +description: 'How to mint native tokens using wallet.rs.' +image: /img/logo/iota_mark_light.png +keywords: +- how to +- mint native tokens +- mint +- nodejs +- python +- java +- rust +--- + +import CodeBlock from '@theme/CodeBlock'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import NodejsCode from '!!raw-loader!../../../../../bindings/nodejs/examples/how_tos/native_tokens/mint.ts'; +import PythonCode from '!!raw-loader!../../../../../bindings/python/examples/how_tos/native_tokens/mint.py'; +import RustCode from '!!raw-loader!../../../../../sdk/examples/how_tos/native_tokens/mint.rs'; +import TipFaucet from '../../_admonitions/_tip_faucet.md'; + +The stardust update allows you to create your own [native tokens](https://wiki.iota.org/shimmer/introduction/explanations/what_is_stardust/tokenization#native-tokens) +Native tokens are minted by a so-called Foundry. The Foundry allows you to specify a max supply once and change the circulating supply. You can also specify some custom metadata in the foundry output or follow a standard like [IRC30](https://wiki.iota.org/tips/tips/TIP-0030/). +To create a Foundry you need an Alias Output which owns it. This Alias Output only needs to be generated once and can own multiple foundries. + + + +## Code Example + +The following example will: + +1. Create a wallet. +2. Get Alice's which was [created in the first guide](../accounts_and_addresses/create_a_wallet_account.mdx). +3. Create the `MintNativeTokenParams`. +4. Mint the native token by calling the `Account.mint_native_token(params, options)` function. + + + + + {RustCode} + + + + + {NodejsCode} + + + + + {PythonCode} + + + + +## Expected Output + +```plaintext +Transaction sent: 0x3e7fe86a2b01a7006fe037249be51852d298d32df69507140d3b41c1a6d7fb90 +Block included: https://explorer.shimmer.network/testnet/block/0xca4f8150014bc548214be6b02bed3b7d43041adef8838965815a367f73c7272d +Account synced +Preparing minting transaction... +Transaction sent: 0xcfc5ffae0c7be8cfb7c334d6f6bb39a2ddff612c9a9048c88e3d9158f7cc4832 +Block included: https://explorer.shimmer.network/testnet/block/0x558c0dcede60609bbb641e9f1d9a7802b48a3c3d5827328a054385140d447436 +Minted token: 0x089dc1b964591b15819ef1912fab48c001fed4558b37766dfa5daa512495d5b25a0100000000 +Account synced +``` diff --git a/documentation/sdk/docs/how_tos/native_tokens/send.mdx b/documentation/sdk/docs/how_tos/native_tokens/send.mdx new file mode 100644 index 0000000000..99801dd6ed --- /dev/null +++ b/documentation/sdk/docs/how_tos/native_tokens/send.mdx @@ -0,0 +1,65 @@ +--- +title: Send Native Tokens +sidebar_label: Send +description: 'How to send native tokens' +image: /img/logo/iota_mark_light.png +keywords: +- how to +- send native token +- send +- native token +- nodejs +- python +- rust +--- + +import CodeBlock from '@theme/CodeBlock'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import NodejsCode from '!!raw-loader!../../../../../bindings/nodejs/examples/how_tos/native_tokens/send.ts'; +import PythonCode from '!!raw-loader!../../../../../bindings/python/examples/how_tos/native_tokens/send.py'; +import RustCode from '!!raw-loader!../../../../../sdk/examples/how_tos/native_tokens/send.rs'; + +After you have [minted](mint.mdx) a +[native token](https://wiki.iota.org/shimmer/introduction/explanations/what_is_stardust/tokenization#native-tokens), +you can easily send it by calling the `Account.send_native_tokens(params, options)` function. + +As with any output, you can set [output unlock conditions](https://wiki.iota.org/shimmer/introduction/explanations/what_is_stardust/unlock_conditions). +Keep in mind that if you set unlock conditions, whoever you send the native tokens to may need to +claim them. + +## Code Example + +The following example will: + +1. Create an account manager. +2. Get Alice's account which was [created in the first guide](../accounts_and_addresses/create_account.mdx). +3. Define the type of native token and amount to send. +4. Send the native tokens calling the `Account.send_native_tokens(params, options)` function. + + + + + {RustCode} + + + + + {NodejsCode} + + + + + {PythonCode} + + + + +## Expected Output + +```plaintext +Balance before sending: 50 +Transaction sent: 0x9a1ea93962b5d4ba3a27053f4727dcc95b29ee35dc85c43d9d6be4635ab10463 +Block included: https://explorer.shimmer.network/testnet/block/0xb22cba455a3176ef62352f907b0fc5e5e49b4797563826141f8057cfa960a640 +Balance after sending: 40 +``` diff --git a/documentation/sdk/sidebars.js b/documentation/sdk/sidebars.js index 90696a8ee5..d4a7f8153c 100644 --- a/documentation/sdk/sidebars.js +++ b/documentation/sdk/sidebars.js @@ -104,6 +104,18 @@ module.exports = { } ] }, + { + type: "category", + label: 'Native Tokens', + items: [ + 'how_tos/native_tokens/mint', + 'how_tos/native_tokens/decrease_supply', + 'how_tos/native_tokens/increase_supply', + 'how_tos/native_tokens/send', + 'how_tos/native_tokens/burn', + 'how_tos/native_tokens/destroy_foundry', + ] + }, { type: "category", label: 'Sign And Verify Ed25519', diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 7b45fc6519..884e46d874 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> -## 0.4.0 - 2023-06-14 +## 0.4.0 - 2023-xx-xx ### Added @@ -124,6 +124,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `NativeTokensBalanceDto` and `BalanceDto`; - `RentStructureBuilder`; - `PlaceholderSecretManager`; +- `block::Error::{InvalidControllerKind, MigratedFundsNotSorted, MissingPayload, MissingRequiredSenderBlock}` variants; +- `client::Error::InvalidBIP32ChainData`; ### Fixed diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 2f8d6bbab1..4a0be7fb99 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -179,6 +179,38 @@ name = "claim_transaction" path = "examples/how_tos/advanced_transactions/claim_transaction.rs" required-features = [ "rocksdb", "stronghold" ] +# Native Tokens Examples + +[[example]] +name = "mint_native_token" +path = "examples/how_tos/native_tokens/mint.rs" +required-features = [ "rocksdb", "stronghold" ] + +[[example]] +name = "destroy_foundry" +path = "examples/how_tos/native_tokens/destroy_foundry.rs" +required-features = [ "rocksdb", "stronghold" ] + +[[example]] +name = "burn_native_token" +path = "examples/how_tos/native_tokens/burn.rs" +required-features = [ "rocksdb", "stronghold" ] + +[[example]] +name = "decrease_native_token_supply" +path = "examples/how_tos/native_tokens/decrease_supply.rs" +required-features = [ "rocksdb", "stronghold" ] + +[[example]] +name = "increase_native_token_supply" +path = "examples/how_tos/native_tokens/increase_supply.rs" +required-features = [ "rocksdb", "stronghold" ] + +[[example]] +name = "send_native_tokens" +path = "examples/how_tos/native_tokens/send.rs" +required-features = [ "rocksdb", "stronghold" ] + # Alias wallet example [[example]] @@ -623,51 +655,21 @@ name = "3_send_transaction" path = "examples/wallet/offline_signing/3_send_transaction.rs" required-features = [ "wallet", "storage" ] -[[example]] -name = "send_native_tokens" -path = "examples/wallet/07_send_native_tokens.rs" -required-features = [ "wallet", "stronghold" ] - [[example]] name = "send_nft" path = "examples/wallet/08_send_nft.rs" required-features = [ "wallet", "stronghold" ] -[[example]] -name = "mint_native_token" -path = "examples/wallet/09_mint_native_token.rs" -required-features = [ "wallet", "stronghold" ] - [[example]] name = "mint_nft" path = "examples/wallet/10_mint_nft.rs" required-features = [ "wallet", "stronghold" ] -[[example]] -name = "decrease_native_token_supply" -path = "examples/wallet/11_decrease_native_token_supply.rs" -required-features = [ "wallet", "stronghold" ] - -[[example]] -name = "increase_native_token_supply" -path = "examples/wallet/12_increase_native_token_supply.rs" -required-features = [ "wallet", "stronghold" ] - -[[example]] -name = "burn_native_token" -path = "examples/wallet/13_burn_native_token.rs" -required-features = [ "wallet", "stronghold" ] - [[example]] name = "burn_nft" path = "examples/wallet/14_burn_nft.rs" required-features = [ "wallet", "stronghold" ] -[[example]] -name = "destroy_foundry" -path = "examples/wallet/15_destroy_foundry.rs" -required-features = [ "wallet", "stronghold" ] - [[example]] name = "create_alias" path = "examples/wallet/create_alias.rs" diff --git a/sdk/examples/how_tos/native_tokens/burn.rs b/sdk/examples/how_tos/native_tokens/burn.rs new file mode 100644 index 0000000000..ba2e4ebc93 --- /dev/null +++ b/sdk/examples/how_tos/native_tokens/burn.rs @@ -0,0 +1,92 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! In this example we will burn an existing native token, this will not increase the melted supply in the foundry, +//! therefore the foundry output is also not required. But this will also make it impossible to destroy the foundry +//! output that minted it. +//! +//! Make sure that `example.stronghold` and `example.walletdb` already exist by +//! running the `create_account` example! +//! +//! Rename `.env.example` to `.env` first, then run the command: +//! ```sh +//! cargo run --release --all-features --example burn_native_token +//! ``` + +use iota_sdk::{types::block::output::NativeToken, wallet::Result, Wallet, U256}; + +// The minimum available native token amount to search for in the account +const MIN_AVAILABLE_AMOUNT: u64 = 11; +// The amount of the native token to burn +const BURN_AMOUNT: u64 = 1; + +#[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 wallet = Wallet::builder() + .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) + .finish() + .await?; + let alias = "Alice"; + let account = wallet.get_account(alias.to_string()).await?; + + // May want to ensure the account is synced before sending a transaction. + let balance = account.sync(None).await?; + + // Get a token with sufficient balance + if let Some(token_id) = balance + .native_tokens() + .iter() + .find(|t| t.available() >= U256::from(MIN_AVAILABLE_AMOUNT)) + .map(|t| t.token_id()) + { + let available_balance = balance + .native_tokens() + .iter() + .find(|t| t.token_id() == token_id) + .unwrap() + .available(); + println!("Balance before burning: {available_balance:?}"); + + // Set the stronghold password + wallet + .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) + .await?; + + // Burn a native token + let burn_amount = U256::from(BURN_AMOUNT); + let transaction = account.burn(NativeToken::new(*token_id, burn_amount)?, None).await?; + println!("Transaction sent: {}", transaction.transaction_id); + + let block_id = account + .retry_transaction_until_included(&transaction.transaction_id, None, None) + .await?; + println!( + "Block included: {}/block/{}", + std::env::var("EXPLORER_URL").unwrap(), + block_id + ); + + let balance = account.sync(None).await?; + + print!("Balance after burning: "); + if let Some(native_token_balance) = balance + .native_tokens() + .iter() + .find(|native_token| native_token.token_id() == token_id) + { + let available_balance = native_token_balance.available(); + println!("{available_balance}"); + } else { + println!("No remaining tokens"); + } + } else { + println!( + "No native token exist or there's not at least '{MIN_AVAILABLE_AMOUNT}' tokens of it in account '{alias}'" + ); + } + + Ok(()) +} diff --git a/sdk/examples/wallet/11_decrease_native_token_supply.rs b/sdk/examples/how_tos/native_tokens/decrease_supply.rs similarity index 64% rename from sdk/examples/wallet/11_decrease_native_token_supply.rs rename to sdk/examples/how_tos/native_tokens/decrease_supply.rs index ec484672bc..e1a50bd00c 100644 --- a/sdk/examples/wallet/11_decrease_native_token_supply.rs +++ b/sdk/examples/how_tos/native_tokens/decrease_supply.rs @@ -11,22 +11,13 @@ //! cargo run --release --all-features --example decrease_native_token_supply //! ``` -use iota_sdk::{wallet::Result, Wallet, U256}; +use iota_sdk::{types::block::output::TokenId, wallet::Result, Wallet, U256}; -// The native token id. Replace it with a TokenId that is available in the account, the foundry output which minted it, -// also needs to be available. You can check this by running the `get_balance` example. You can mint a new native token -// by running the `mint_native_token` example. -const TOKEN_ID: &str = "0x08847bd287c912fadedb6bf38900bda9f2d377b75b2a0bece8738699f56ebca4130100000000"; // The amount of native tokens to melt const MELT_AMOUNT: u64 = 10; #[tokio::main] async fn main() -> Result<()> { - if TOKEN_ID == "0x08847bd287c912fadedb6bf38900bda9f2d377b75b2a0bece8738699f56ebca4130100000000" { - println!("You need to change the TOKEN_ID constant before you can run this example successfully!"); - return Ok(()); - } - // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -36,21 +27,23 @@ async fn main() -> Result<()> { .await?; let account = wallet.get_account("Alice").await?; - let token_id = TOKEN_ID.parse()?; - // May want to ensure the account is synced before sending a transaction. account.sync(None).await?; let balance = account.balance().await?; + // Find first foundry and corresponding token id + let token_id = TokenId::from(*balance.foundries().first().unwrap()); + if let Some(native_token_balance) = balance .native_tokens() .iter() .find(|native_token| native_token.token_id() == &token_id) { - println!("Balance BEFORE melting:\n{native_token_balance:#?}"); + let available_balance = native_token_balance.available(); + println!("Balance before melting: {available_balance}"); } else { - println!("Couldn't find native token '{TOKEN_ID}' in the account"); + println!("Couldn't find native token '{token_id}' in the account"); return Ok(()); } @@ -59,8 +52,6 @@ async fn main() -> Result<()> { .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - println!("Sending the melting transaction..."); - // Melt some of the circulating supply let melt_amount = U256::from(MELT_AMOUNT); let transaction = account @@ -73,19 +64,20 @@ async fn main() -> Result<()> { .await?; println!( - "Transaction included: {}/block/{}", + "Block included: {}/block/{}", std::env::var("EXPLORER_URL").unwrap(), block_id ); println!("Melted {} native tokens ({})", melt_amount, token_id); let balance = account.sync(None).await?; - let native_token_balance = balance + let available_balance = balance .native_tokens() .iter() - .find(|native_token| native_token.token_id() == &token_id) - .unwrap(); - println!("Balance AFTER melting:\n{native_token_balance:#?}"); + .find(|t| t.token_id() == &token_id) + .unwrap() + .available(); + println!("Balance after melting: {available_balance}",); Ok(()) } diff --git a/sdk/examples/wallet/15_destroy_foundry.rs b/sdk/examples/how_tos/native_tokens/destroy_foundry.rs similarity index 80% rename from sdk/examples/wallet/15_destroy_foundry.rs rename to sdk/examples/how_tos/native_tokens/destroy_foundry.rs index 2e7bcf5894..7c45703c84 100644 --- a/sdk/examples/wallet/15_destroy_foundry.rs +++ b/sdk/examples/how_tos/native_tokens/destroy_foundry.rs @@ -31,16 +31,14 @@ async fn main() -> Result<()> { // We try to destroy the first foundry in the account if let Some(foundry_id) = balance.foundries().first() { - let foundries_before = balance.foundries(); - println!("Foundries BEFORE destroying:\n{foundries_before:#?}",); + let foundry_count = balance.foundries().len(); + println!("Foundries before destroying: {foundry_count}"); // Set the stronghold password wallet .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - println!("Sending foundry burn transaction..."); - let transaction = account.burn(*foundry_id, None).await?; println!("Transaction sent: {}", transaction.transaction_id); @@ -48,16 +46,13 @@ async fn main() -> Result<()> { .retry_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( - "Transaction included: {}/block/{}", + "Block included: {}/block/{}", std::env::var("EXPLORER_URL").unwrap(), block_id ); - println!("Burned Foundry '{}'", foundry_id); - - let balance = account.sync(None).await?; - let foundries_after = balance.foundries(); - println!("Foundries AFTER destroying:\n{foundries_after:#?}",); + let foundry_count = balance.foundries().len(); + println!("Foundries after destroying: {foundry_count}"); } else { println!("No Foundry available in account '{alias}'"); } diff --git a/sdk/examples/wallet/12_increase_native_token_supply.rs b/sdk/examples/how_tos/native_tokens/increase_supply.rs similarity index 58% rename from sdk/examples/wallet/12_increase_native_token_supply.rs rename to sdk/examples/how_tos/native_tokens/increase_supply.rs index ed50459ec5..f1087941e5 100644 --- a/sdk/examples/wallet/12_increase_native_token_supply.rs +++ b/sdk/examples/how_tos/native_tokens/increase_supply.rs @@ -11,22 +11,13 @@ //! cargo run --release --all-features --example increase_native_token_supply //! ``` -use iota_sdk::{wallet::Result, Wallet, U256}; +use iota_sdk::{types::block::output::TokenId, wallet::Result, Wallet, U256}; -// The native token id. Replace it with a TokenId that is available in the account, the foundry output which minted it, -// also needs to be available. You can check this by running the `get_balance` example. You can mint a new native token -// by running the `mint_native_token` example. -const TOKEN_ID: &str = "0x08847bd287c912fadedb6bf38900bda9f2d377b75b2a0bece8738699f56ebca4130100000000"; // The amount of native tokens to mint const MINT_AMOUNT: u64 = 10; #[tokio::main] async fn main() -> Result<()> { - if TOKEN_ID == "0x08847bd287c912fadedb6bf38900bda9f2d377b75b2a0bece8738699f56ebca4130100000000" { - println!("You need to change the TOKEN_ID constant before you can run this example successfully!"); - return Ok(()); - } - // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -36,28 +27,24 @@ async fn main() -> Result<()> { .await?; let account = wallet.get_account("Alice").await?; - let token_id = TOKEN_ID.parse()?; - // May want to ensure the account is synced before sending a transaction. let balance = account.sync(None).await?; - if let Some(native_token_balance) = balance - .native_tokens() - .iter() - .find(|native_token| native_token.token_id() == &token_id) - { - println!("Balance BEFORE minting:\n{native_token_balance:#?}"); - } else { - println!("Couldn't find native token '{TOKEN_ID}' in the account"); - return Ok(()); - } - // Set the stronghold password wallet .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - println!("Sending the minting transaction..."); + // Find first foundry and corresponding token id + let token_id = TokenId::from(*balance.foundries().first().unwrap()); + + let available_balance = balance + .native_tokens() + .iter() + .find(|t| t.token_id() == &token_id) + .unwrap() + .available(); + println!("Balance before minting: {available_balance}",); // Mint some more native tokens let mint_amount = U256::from(MINT_AMOUNT); @@ -70,19 +57,20 @@ async fn main() -> Result<()> { .retry_transaction_until_included(&transaction.transaction.transaction_id, None, None) .await?; println!( - "Transaction included: {}/block/{}", + "Block included: {}/block/{}", std::env::var("EXPLORER_URL").unwrap(), block_id ); println!("Minted {} native tokens ({})", mint_amount, transaction.token_id); let balance = account.sync(None).await?; - let native_token_balance = balance + let available_balance = balance .native_tokens() .iter() - .find(|native_token| native_token.token_id() == &token_id) - .unwrap(); - println!("Balance AFTER minting:\n{native_token_balance:#?}"); + .find(|t| t.token_id() == &token_id) + .unwrap() + .available(); + println!("Balance after minting: {available_balance:?}",); Ok(()) } diff --git a/sdk/examples/wallet/09_mint_native_token.rs b/sdk/examples/how_tos/native_tokens/mint.rs similarity index 65% rename from sdk/examples/wallet/09_mint_native_token.rs rename to sdk/examples/how_tos/native_tokens/mint.rs index 0d2293b9e6..636d9ce8ed 100644 --- a/sdk/examples/wallet/09_mint_native_token.rs +++ b/sdk/examples/how_tos/native_tokens/mint.rs @@ -31,32 +31,35 @@ async fn main() -> Result<()> { .finish() .await?; let account = wallet.get_account("Alice").await?; + let balance = account.sync(None).await?; // Set the stronghold password wallet .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - println!("Sending alias output transaction..."); - - // First create an alias output, this needs to be done only once, because an alias can have many foundry outputs - let transaction = account.create_alias_output(None, None).await?; - println!("Transaction sent: {}", transaction.transaction_id); - - // Wait for transaction to get included - let block_id = account - .retry_transaction_until_included(&transaction.transaction_id, None, None) - .await?; - println!( - "Transaction included: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block_id - ); - - account.sync(None).await?; - println!("Account synced"); - - println!("Sending the minting transaction..."); + // We can first check if we already have an alias in our account, because an alias can have many foundry outputs and + // therefore we can reuse an existing one + if balance.aliases().is_empty() { + // If we don't have an alias, we need to create one + let transaction = account.create_alias_output(None, None).await?; + println!("Transaction sent: {}", transaction.transaction_id); + + // Wait for transaction to get included + let block_id = account + .retry_transaction_until_included(&transaction.transaction_id, None, None) + .await?; + println!( + "Block included: {}/block/{}", + std::env::var("EXPLORER_URL").unwrap(), + block_id + ); + + account.sync(None).await?; + println!("Account synced"); + } + + println!("Preparing minting transaction..."); let params = MintNativeTokenParams { alias_id: None, @@ -73,11 +76,11 @@ async fn main() -> Result<()> { .retry_transaction_until_included(&transaction.transaction.transaction_id, None, None) .await?; println!( - "Transaction included: {}/block/{}", + "Block included: {}/block/{}", std::env::var("EXPLORER_URL").unwrap(), block_id ); - println!("Minted token: {} ", transaction.token_id); + println!("Minted token: {}", transaction.token_id); // Ensure the account is synced after minting. account.sync(None).await?; diff --git a/sdk/examples/wallet/07_send_native_tokens.rs b/sdk/examples/how_tos/native_tokens/send.rs similarity index 62% rename from sdk/examples/wallet/07_send_native_tokens.rs rename to sdk/examples/how_tos/native_tokens/send.rs index 150b85f653..012f98b0df 100644 --- a/sdk/examples/wallet/07_send_native_tokens.rs +++ b/sdk/examples/how_tos/native_tokens/send.rs @@ -12,10 +12,7 @@ //! ``` use iota_sdk::{ - types::block::{ - address::Bech32Address, - output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder, NativeToken}, - }, + types::block::address::Bech32Address, wallet::{Result, SendNativeTokensParams}, Wallet, }; @@ -47,6 +44,14 @@ async fn main() -> Result<()> { .find(|t| t.available() >= U256::from(SEND_NATIVE_TOKEN_AMOUNT)) .map(|t| t.token_id()) { + let available_balance = balance + .native_tokens() + .iter() + .find(|t| t.token_id() == token_id) + .unwrap() + .available(); + println!("Balance before sending: {available_balance}"); + // Set the stronghold password wallet .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) @@ -59,11 +64,6 @@ async fn main() -> Result<()> { [(*token_id, U256::from(SEND_NATIVE_TOKEN_AMOUNT))], )?]; - println!( - "Sending '{}' native tokens to '{}'...", - SEND_NATIVE_TOKEN_AMOUNT, bech32_address - ); - let transaction = account.send_native_tokens(outputs, None).await?; println!("Transaction sent: {}", transaction.transaction_id); @@ -72,37 +72,20 @@ async fn main() -> Result<()> { .retry_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( - "Transaction included: {}/block/{}", + "Block included: {}/block/{}", std::env::var("EXPLORER_URL").unwrap(), block_id ); - account.sync(None).await?; - println!("Account synced"); - - println!("Sending basic output transaction..."); - - // Send native tokens together with the required storage deposit - let rent_structure = account.client().get_rent_structure().await?; - - let outputs = [BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) - .add_unlock_condition(AddressUnlockCondition::new(bech32_address)) - .with_native_tokens([NativeToken::new(*token_id, U256::from(SEND_NATIVE_TOKEN_AMOUNT))?]) - .finish_output(account.client().get_token_supply().await?)?]; - - let transaction = account.send(outputs, None).await?; - println!("Transaction sent: {}", transaction.transaction_id); - - // Wait for transaction to get included - let block_id = account - .retry_transaction_until_included(&transaction.transaction_id, None, None) - .await?; + let balance = account.sync(None).await?; - println!( - "Transaction included: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block_id - ); + let available_balance = balance + .native_tokens() + .iter() + .find(|t| t.token_id() == token_id) + .unwrap() + .available(); + println!("Balance after sending: {available_balance}",); } else { println!("Insufficient native token funds"); } diff --git a/sdk/src/client/api/block_builder/input_selection/helpers.rs b/sdk/src/client/api/block_builder/input_selection/helpers.rs index 14c6e1bc5d..7b8a8c04b2 100644 --- a/sdk/src/client/api/block_builder/input_selection/helpers.rs +++ b/sdk/src/client/api/block_builder/input_selection/helpers.rs @@ -13,27 +13,6 @@ use crate::{ }, }; -// Dedup inputs by output id, because other data could be different, even if it's the same output -// TODO remove ? -// pub(crate) fn dedup_inputs( -// mandatory_inputs: &mut Vec, -// additional_inputs: &mut Vec, -// ) { -// // Sorting inputs by OutputId so duplicates can be safely removed. -// mandatory_inputs.sort_by_key(|input| *input.output_metadata.output_id()); -// mandatory_inputs.dedup_by_key(|input| *input.output_metadata.output_id()); -// additional_inputs.sort_by_key(|input| *input.output_metadata.output_id()); -// additional_inputs.dedup_by_key(|input| *input.output_metadata.output_id()); - -// // Remove additional inputs that are already mandatory. -// // TODO: could be done more efficiently with itertools unique? -// additional_inputs.retain(|input| { -// !mandatory_inputs -// .iter() -// .any(|mandatory_input| input.output_metadata.output_id() == mandatory_input.output_metadata.output_id()) -// }); -// } - /// Computes the minimum storage deposit amount that a basic output needs to have with an [AddressUnlockCondition] and /// optional [NativeTokens]. pub fn minimum_storage_deposit_basic_output( diff --git a/sdk/src/client/error.rs b/sdk/src/client/error.rs index dc54a3fe95..f16a0890da 100644 --- a/sdk/src/client/error.rs +++ b/sdk/src/client/error.rs @@ -56,9 +56,6 @@ pub enum Error { /// Invalid amount in API response #[error("invalid amount in API response: {0}")] InvalidAmount(String), - /// Invalid BIP32 chain data - #[error("invalid BIP32 chain data")] - InvalidBIP32ChainData, /// Invalid mnemonic error #[error("invalid mnemonic {0}")] InvalidMnemonic(String), diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index 9cf4ae6d6f..67034dfcbf 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -39,7 +39,6 @@ pub enum Error { InvalidAddress, InvalidAddressKind(u8), InvalidAliasIndex(>::Error), - InvalidControllerKind(u8), InvalidStorageDepositAmount(u64), // The above is used by `Packable` to denote out-of-range values. The following denotes the actual amount. InsufficientStorageDepositAmount { amount: u64, required: u64 }, @@ -93,15 +92,12 @@ pub enum Error { InvalidUnlockConditionCount(>::Error), InvalidUnlockConditionKind(u8), InvalidFoundryZeroSerialNumber, - MigratedFundsNotSorted, MilestoneInvalidSignatureCount(>::Error), MilestonePublicKeysSignaturesCountMismatch { key_count: usize, sig_count: usize }, MilestoneOptionsNotUniqueSorted, MilestoneSignaturesNotUniqueSorted, MissingAddressUnlockCondition, MissingGovernorUnlockCondition, - MissingPayload, - MissingRequiredSenderBlock, MissingStateControllerUnlockCondition, NativeTokensNotUniqueSorted, NativeTokensNullAmount, @@ -165,7 +161,6 @@ impl fmt::Display for Error { Self::InvalidBinaryParametersLength(length) => { write!(f, "invalid binary parameters length: {length}") } - Self::InvalidControllerKind(k) => write!(f, "invalid controller kind: {k}"), Self::InvalidStorageDepositAmount(amount) => { write!(f, "invalid storage deposit amount: {amount}") } @@ -258,9 +253,6 @@ impl fmt::Display for Error { Self::InvalidUnlockConditionCount(count) => write!(f, "invalid unlock condition count: {count}"), Self::InvalidUnlockConditionKind(k) => write!(f, "invalid unlock condition kind: {k}"), Self::InvalidFoundryZeroSerialNumber => write!(f, "invalid foundry zero serial number"), - Self::MigratedFundsNotSorted => { - write!(f, "migrated funds are not sorted") - } Self::MilestoneInvalidSignatureCount(count) => { write!(f, "invalid milestone signature count: {count}") } @@ -278,8 +270,6 @@ impl fmt::Display for Error { } Self::MissingAddressUnlockCondition => write!(f, "missing address unlock condition"), Self::MissingGovernorUnlockCondition => write!(f, "missing governor unlock condition"), - Self::MissingPayload => write!(f, "missing payload"), - Self::MissingRequiredSenderBlock => write!(f, "missing required sender block"), Self::MissingStateControllerUnlockCondition => write!(f, "missing state controller unlock condition"), Self::NativeTokensNotUniqueSorted => write!(f, "native tokens are not unique and/or sorted"), Self::NativeTokensNullAmount => write!(f, "native tokens null amount"), diff --git a/sdk/src/wallet/wallet/builder.rs b/sdk/src/wallet/wallet/builder.rs index 975e1efa41..26ef0e0242 100644 --- a/sdk/src/wallet/wallet/builder.rs +++ b/sdk/src/wallet/wallet/builder.rs @@ -9,7 +9,9 @@ use std::sync::{ use std::{collections::HashSet, path::PathBuf, sync::atomic::Ordering}; use futures::{future::try_join_all, FutureExt}; -use serde::{Deserialize, Serialize}; +#[cfg(feature = "storage")] +use serde::Deserialize; +use serde::Serialize; use tokio::sync::RwLock; use super::operations::storage::SaveLoadWallet; @@ -53,9 +55,9 @@ impl Default for WalletBuilder { } } -#[derive(Debug, Clone, Serialize, Deserialize)] #[cfg(feature = "storage")] #[cfg_attr(docsrs, doc(cfg(feature = "storage")))] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct StorageOptions { pub(crate) storage_path: PathBuf, pub(crate) storage_file_name: Option, diff --git a/sdk/tests/client/client_builder.rs b/sdk/tests/client/client_builder.rs index 26bdb4c59d..5288663f33 100644 --- a/sdk/tests/client/client_builder.rs +++ b/sdk/tests/client/client_builder.rs @@ -49,8 +49,7 @@ async fn client_builder() { "rentStructure":{ "vByteCost":100, "vByteFactorKey":10, - "vByteFactorData":1, - "vByteOffset":380 + "vByteFactorData":1 }, "tokenSupply":1813620509061365 }, @@ -104,8 +103,7 @@ async fn client_builder() { "rentStructure":{ "vByteCost":100, "vByteFactorKey":10, - "vByteFactorData":1, - "vByteOffset":380 + "vByteFactorData":1 }, "tokenSupply":1813620509061365 }, diff --git a/sdk/tests/client/error.rs b/sdk/tests/client/error.rs index d544164405..ace934cc9f 100644 --- a/sdk/tests/client/error.rs +++ b/sdk/tests/client/error.rs @@ -23,10 +23,10 @@ fn stringified_error() { "{\"type\":\"timeNotSynced\",\"error\":\"local time 0 doesn't match the time of the latest milestone timestamp: 10000\"}" ); - let error = Error::InvalidBIP32ChainData; + let error = Error::PlaceholderSecretManager; assert_eq!( &serde_json::to_string(&error).unwrap(), - "{\"type\":\"invalidBIP32ChainData\",\"error\":\"invalid BIP32 chain data\"}" + "{\"type\":\"placeholderSecretManager\",\"error\":\"placeholderSecretManager can't be used for address generation or signing\"}" ); let error = Error::InputSelection(IsaError::InsufficientAmount {