diff --git a/.github/workflows/bindings-python.yml b/.github/workflows/bindings-python.yml index 55d85c2cca..4530a44999 100644 --- a/.github/workflows/bindings-python.yml +++ b/.github/workflows/bindings-python.yml @@ -40,7 +40,7 @@ concurrency: jobs: lint: - name: PEP8 style check + name: Python PEP8 format runs-on: ubuntu-latest strategy: fail-fast: false @@ -63,12 +63,13 @@ jobs: python3 -m pip install --upgrade setuptools pip wheel python3 -m pip install tox-gh-actions - - name: Run Tox + - name: Run tox format check working-directory: bindings/python - run: tox -e lint + run: tox -e format + test: - name: Test + name: Linter & Tests needs: lint if: ${{ ! github.event.schedule }} runs-on: ${{ matrix.os }} @@ -111,7 +112,13 @@ jobs: sudo apt-get update sudo apt-get install libudev-dev libusb-1.0-0-dev + - name: Run linter for examples + if: ${{ startsWith(matrix.os, 'ubuntu-latest') }} + working-directory: bindings/python + run: tox -e lint-examples + # TODO temporarily disabled https://github.com/iotaledger/iota-sdk/issues/647 - # - name: Run Tox + # - name: Run tests # working-directory: bindings/python # run: tox + diff --git a/Cargo.lock b/Cargo.lock index ba90c4eb2b..c21724636a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" [[package]] name = "anstyle-parse" @@ -158,7 +158,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", ] [[package]] @@ -224,9 +224,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64ct" @@ -273,7 +273,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.31", + "syn 2.0.32", ] [[package]] @@ -311,13 +311,13 @@ dependencies = [ [[package]] name = "blake2b_simd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", "arrayvec", - "constant_time_eq 0.2.6", + "constant_time_eq 0.3.0", ] [[package]] @@ -490,7 +490,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", ] [[package]] @@ -583,9 +583,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "constant_time_eq" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "core-foundation" @@ -673,9 +673,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" +checksum = "622178105f911d937a42cdb140730ba4a3ed2becd8ae6ce39c7d28b5d75d4588" dependencies = [ "cfg-if", "cpufeatures", @@ -696,42 +696,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", -] - -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", + "syn 2.0.32", ] [[package]] @@ -771,37 +736,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_builder" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_builder_macro" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" -dependencies = [ - "derive_builder_core", - "syn 1.0.109", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -925,11 +859,11 @@ dependencies = [ [[package]] name = "ed25519-zebra" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e83e509bcd060ca4b54b72bde5bb306cb2088cb01e14797ebae90a24f70f5f7" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.0", "ed25519", "hashbrown 0.14.0", "hex", @@ -1057,9 +991,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" [[package]] name = "fixed-hash" @@ -1164,7 +1098,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", ] [[package]] @@ -1462,12 +1396,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.4.0" @@ -1565,7 +1493,7 @@ dependencies = [ "aes", "aes-gcm", "autocfg", - "base64 0.21.3", + "base64 0.21.4", "blake2", "chacha20poly1305", "cipher", @@ -1591,9 +1519,9 @@ dependencies = [ [[package]] name = "iota-ledger-nano" -version = "1.0.0-alpha.4" +version = "1.0.0-alpha.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a3ecb2b7f3881d5ced64dde43ba36864191e90a249853e0dda56a9521a2a3d" +checksum = "07341d2984f6ae956f0ee0bc62b3119ded2d40441957712761a3fc5591c5e950" dependencies = [ "bech32 0.7.3", "enum-iterator", @@ -1619,7 +1547,6 @@ dependencies = [ "bech32 0.9.1", "bitflags 2.4.0", "bs58", - "derive_builder", "derive_more", "dotenvy", "fern-logger", @@ -1914,9 +1841,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" @@ -2274,7 +2201,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", ] [[package]] @@ -2358,7 +2285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.31", + "syn 2.0.32", ] [[package]] @@ -2591,7 +2518,7 @@ version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64 0.21.3", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -2721,9 +2648,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.11" +version = "0.38.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" dependencies = [ "bitflags 2.4.0", "errno", @@ -2740,7 +2667,7 @@ checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", - "rustls-webpki 0.101.4", + "rustls-webpki 0.101.5", "sct", ] @@ -2762,7 +2689,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.3", + "base64 0.21.4", ] [[package]] @@ -2777,9 +2704,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.4" +version = "0.101.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" dependencies = [ "ring", "untrusted", @@ -2921,14 +2848,14 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" dependencies = [ "itoa", "ryu", @@ -2943,7 +2870,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", ] [[package]] @@ -3061,9 +2988,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", "windows-sys 0.48.0", @@ -3183,9 +3110,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.31" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -3232,7 +3159,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", ] [[package]] @@ -3310,7 +3237,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.3", + "socket2 0.5.4", "tokio-macros", "windows-sys 0.48.0", ] @@ -3323,7 +3250,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", ] [[package]] @@ -3582,7 +3509,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", "wasm-bindgen-shared", ] @@ -3616,7 +3543,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3947,5 +3874,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.32", ] diff --git a/bindings/core/src/method/account.rs b/bindings/core/src/method/account.rs index 2831e08e54..febdce1385 100644 --- a/bindings/core/src/method/account.rs +++ b/bindings/core/src/method/account.rs @@ -129,7 +129,7 @@ pub enum AccountMethod { /// Returns all pending transactions of the account /// Expected response: [`Transactions`](crate::Response::Transactions) PendingTransactions, - /// A generic `burn()` function that can be used to burn native tokens, nfts, foundries and accounts. + /// A generic function that can be used to burn native tokens, nfts, foundries and accounts. /// /// Note that burning **native tokens** 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. diff --git a/bindings/nodejs/CHANGELOG.md b/bindings/nodejs/CHANGELOG.md index 4e7d379907..fe71daefbc 100644 --- a/bindings/nodejs/CHANGELOG.md +++ b/bindings/nodejs/CHANGELOG.md @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 1.1.0 - 2023-MM-DD + +### Added + +- `Account::{burn(), consolidateOutputs(), createAliasOutput(), meltNativeToken(), mintNativeToken(), createNativeToken(), mintNfts(), sendTransaction(), sendNativeTokens(), sendNft()}` methods; + ## 1.0.10 - 2023-mm-dd ### Fixed diff --git a/bindings/nodejs/examples/how_tos/account_output/create.ts b/bindings/nodejs/examples/how_tos/account_output/create.ts index 5a993c353f..492f8b02db 100644 --- a/bindings/nodejs/examples/how_tos/account_output/create.ts +++ b/bindings/nodejs/examples/how_tos/account_output/create.ts @@ -50,9 +50,7 @@ async function run() { console.log('Sending the create-account transaction...'); // Create an account output - const transaction = await account - .prepareCreateAccountOutput() - .then((prepared) => prepared.send()); + const transaction = await account.createAliasOutput(); console.log(`Transaction sent: ${transaction.transactionId}`); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts index 1a5f91cc22..d62bd0b231 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts @@ -67,10 +67,9 @@ async function run() { // Consolidate unspent outputs and print the consolidation transaction ID // Set `force` to true to force the consolidation even though the `output_threshold` isn't reached - const preparedTransaction = await account.prepareConsolidateOutputs({ + const transaction = await account.consolidateOutputs({ force: true, }); - const transaction = await preparedTransaction.send(); console.log('Transaction sent: %s', transaction.transactionId); // Wait for the consolidation transaction to get confirmed diff --git a/bindings/nodejs/examples/how_tos/native_tokens/melt.ts b/bindings/nodejs/examples/how_tos/native_tokens/melt.ts index fa21e00752..d2381f0888 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/melt.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/melt.ts @@ -43,9 +43,10 @@ async function run() { console.log(`Balance before melting: ${token.available}`); // Melt some of the circulating supply - const transaction = await account - .prepareMeltNativeToken(token.tokenId, MELT_AMOUNT) - .then((prepared) => prepared.send()); + const transaction = await account.meltNativeToken( + token.tokenId, + MELT_AMOUNT, + ); console.log(`Transaction sent: ${transaction.transactionId}`); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/send.ts b/bindings/nodejs/examples/how_tos/native_tokens/send.ts index fd3f546c98..4b3824eec8 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/send.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/send.ts @@ -52,9 +52,7 @@ async function run() { } console.log(`Balance before sending: ${token.available}`); - const transaction = await account - .prepareSendNativeTokens(outputs) - .then((prepared) => prepared.send()); + const transaction = await account.sendNativeTokens(outputs); console.log(`Transaction sent: ${transaction.transactionId}`); diff --git a/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts b/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts index 495a873434..7881bae316 100644 --- a/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts +++ b/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts @@ -49,9 +49,7 @@ async function run() { 'This NFT will be the issuer from the awesome NFT collection', ), }; - const prepared = await account.prepareMintNfts([params]); - - const transaction = await prepared.send(); + const transaction = await account.mintNfts([params]); // Wait for transaction to get included const blockId = await account.reissueTransactionUntilIncluded( diff --git a/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts b/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts index 2dc45d4c80..4313049e23 100644 --- a/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts +++ b/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts @@ -72,8 +72,7 @@ async function run() { i + chunk.length }/${NFT_COLLECTION_SIZE})`, ); - const prepared = await account.prepareMintNfts(chunk); - const transaction = await prepared.send(); + const transaction = await account.mintNfts(chunk); // Wait for transaction to get included const blockId = await account.reissueTransactionUntilIncluded( diff --git a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts index c19065955e..872e63a1a6 100644 --- a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts @@ -60,9 +60,7 @@ async function run() { issuer: senderAddress, immutableMetadata: NFT1_IMMUTABLE_METADATA, }; - const prepared = await account.prepareMintNfts([params]); - - let transaction = await prepared.send(); + let transaction = await account.mintNfts([params]); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included diff --git a/bindings/nodejs/examples/how_tos/nfts/send_nft.ts b/bindings/nodejs/examples/how_tos/nfts/send_nft.ts index 3cba7cdfc4..77097345c2 100644 --- a/bindings/nodejs/examples/how_tos/nfts/send_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/send_nft.ts @@ -50,9 +50,7 @@ async function run() { ]; // Send the full NFT output to the specified address - const transaction = await account - .prepareSendNft(outputs) - .then((prepared) => prepared.send()); + const transaction = await account.sendNft(outputs); console.log(`Transaction sent: ${transaction.transactionId}`); diff --git a/bindings/nodejs/lib/wallet/account.ts b/bindings/nodejs/lib/wallet/account.ts index df65a04528..670eb22514 100644 --- a/bindings/nodejs/lib/wallet/account.ts +++ b/bindings/nodejs/lib/wallet/account.ts @@ -62,10 +62,23 @@ export class Account { } /** - * A generic `burn()` function that can be used to prepare to burn native tokens, nfts, foundries and accounts. - * @param burn The outputs to burn - * @param transactionOptions The options to define a `RemainderValueStrategy` + * A generic function that can be used to burn native tokens, nfts, foundries and accounts. + * @param burn The outputs or native tokens to burn + * @param transactionOptions Additional transaction options * or custom inputs. + * @returns The transaction. + */ + async burn( + burn: Burn, + transactionOptions?: TransactionOptions, + ): Promise { + return (await this.prepareBurn(burn, transactionOptions)).send(); + } + + /** + * A generic function that can be used to prepare to burn native tokens, nfts, foundries and accounts. + * @param burn The outputs or native tokens to burn + * @param transactionOptions Additional transaction options * @returns The prepared transaction. */ async prepareBurn( @@ -97,7 +110,7 @@ export class Account { * recommended to use melting, if the foundry output is available. * @param tokenId The native token id. * @param burnAmount The to be burned amount. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -126,10 +139,11 @@ export class Account { this, ); } + /** * Burn an nft output. * @param nftId The NftId. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -182,8 +196,20 @@ export class Account { * Consolidate basic outputs with only an `AddressUnlockCondition` from an account * by sending them to an own address again if the output amount is greater or * equal to the output consolidation threshold. - * @param force Force consolidation on addresses where the threshold isn't met. - * @param outputConsolidationThreshold A default threshold is used if this is omitted. + * @param params Consolidation options. + * @returns The consolidation transaction. + */ + async consolidateOutputs( + params: ConsolidationParams, + ): Promise { + return (await this.prepareConsolidateOutputs(params)).send(); + } + + /** + * Consolidate basic outputs with only an `AddressUnlockCondition` from an account + * by sending them to an own address again if the output amount is greater or + * equal to the output consolidation threshold. + * @param params Consolidation options. * @returns The prepared consolidation transaction. */ async prepareConsolidateOutputs( @@ -208,9 +234,25 @@ export class Account { } /** - * `createAccountOutput` creates an account output + * Creates an account output. + * @param params The account output options. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async createAliasOutput( + params?: AccountOutputParams, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareCreateAccountOutput(params, transactionOptions) + ).send(); + } + + /** + * Creates an account output. * @param params The account output options. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -242,7 +284,30 @@ export class Account { * `melted_tokens` field. * @param tokenId The native token id. * @param meltAmount To be melted amount. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async meltNativeToken( + tokenId: string, + meltAmount: bigint, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareMeltNativeToken( + tokenId, + meltAmount, + transactionOptions, + ) + ).send(); + } + + /** + * Melt native tokens. This happens with the foundry output which minted them, by increasing its + * `melted_tokens` field. + * @param tokenId The native token id. + * @param meltAmount To be melted amount. + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -293,8 +358,9 @@ export class Account { /** * Destroy an account output. + * * @param accountId The AccountId. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -327,7 +393,7 @@ export class Account { * Function to destroy a foundry output with a circulating supply of 0. * Native tokens in the foundry (minted by other foundries) will be transacted to the controlling account. * @param foundryId The FoundryId. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -738,7 +804,30 @@ export class Account { * * @param tokenId The native token id. * @param mintAmount To be minted amount. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The minting transaction. + */ + async mintNativeToken( + tokenId: string, + mintAmount: bigint, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareMintNativeToken( + tokenId, + mintAmount, + transactionOptions, + ) + ).send(); + } + + /** + * Mint additional native tokens. + * + * @param tokenId The native token id. + * @param mintAmount To be minted amount. + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared minting transaction. */ @@ -772,9 +861,26 @@ export class Account { * Create a native token. * * @param params The options for creating a native token. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The created transaction. + */ + async createNativeToken( + params: CreateNativeTokenParams, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareCreateNativeToken(params, transactionOptions) + ).send(); + } + + /** + * Create a native token. + * + * @param params The options for creating a native token. + * @param transactionOptions Additional transaction options * or custom inputs. - * @returns The creating transaction and the token ID. + * @returns The created transaction and the token ID. */ async prepareCreateNativeToken( params: CreateNativeTokenParams, @@ -813,7 +919,22 @@ export class Account { * Mint NFTs. * * @param params The options for minting nfts. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The minting transaction. + */ + async mintNfts( + params: MintNftParams[], + transactionOptions?: TransactionOptions, + ): Promise { + return (await this.prepareMintNfts(params, transactionOptions)).send(); + } + + /** + * Mint NFTs. + * + * @param params The options for minting nfts. + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared minting transaction. */ @@ -851,7 +972,7 @@ export class Account { * storage deposit will be sent to the recipient. When the assets contain * an nft id, the data from the existing `NftOutput` will be used, just with * the address unlock conditions replaced. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared output. */ @@ -881,7 +1002,7 @@ export class Account { * Prepare to send base coins, useful for offline signing. * * @param params Address with amounts to send. - * @param options The options to define a `RemainderValueStrategy` + * @param options Additional transaction options * or custom inputs. * @returns The prepared transaction data. */ @@ -913,11 +1034,26 @@ export class Account { ); } + /** + * Send a transaction. + * + * @param outputs Outputs to use in the transaction. + * @param options Additional transaction options + * or custom inputs. + * @returns The transaction data. + */ + async sendTransaction( + outputs: Output[], + options?: TransactionOptions, + ): Promise { + return (await this.prepareTransaction(outputs, options)).send(); + } + /** * Prepare a transaction, useful for offline signing. * * @param outputs Outputs to use in the transaction. - * @param options The options to define a `RemainderValueStrategy` + * @param options Additional transaction options * or custom inputs. * @returns The prepared transaction data. */ @@ -993,7 +1129,7 @@ export class Account { * * @param amount Amount of coins. * @param address Receiving address. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The sent transaction. */ @@ -1024,7 +1160,7 @@ export class Account { * Send base coins with amounts from input addresses. * * @param params Addresses with amounts. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The sent transaction. */ @@ -1055,7 +1191,24 @@ export class Account { * Send native tokens. * * @param params Addresses amounts and native tokens. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async sendNativeTokens( + params: SendNativeTokensParams[], + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareSendNativeTokens(params, transactionOptions) + ).send(); + } + + /** + * Send native tokens. + * + * @param params Addresses amounts and native tokens. + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -1086,7 +1239,22 @@ export class Account { * Send NFT. * * @param params Addresses and nft ids. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async sendNft( + params: SendNftParams[], + transactionOptions?: TransactionOptions, + ): Promise { + return (await this.prepareSendNft(params, transactionOptions)).send(); + } + + /** + * Send NFT. + * + * @param params Addresses and nft ids. + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The prepared transaction. */ @@ -1117,7 +1285,7 @@ export class Account { * Send outputs in a transaction. * * @param outputs The outputs to send. - * @param transactionOptions The options to define a `RemainderValueStrategy` + * @param transactionOptions Additional transaction options * or custom inputs. * @returns The sent transaction. */ diff --git a/bindings/nodejs/src/client.rs b/bindings/nodejs/src/client.rs index 09fa6abc5b..da62dca605 100644 --- a/bindings/nodejs/src/client.rs +++ b/bindings/nodejs/src/client.rs @@ -53,7 +53,7 @@ impl ClientMethodHandler { (msg, is_err) } Err(e) => { - log::debug!("{:?}", e); + log::error!("{:?}", e); (format!("Couldn't parse to method with error - {e:?}"), true) } } diff --git a/bindings/nodejs/src/secret_manager.rs b/bindings/nodejs/src/secret_manager.rs index cf0b6d8019..7c5531638f 100644 --- a/bindings/nodejs/src/secret_manager.rs +++ b/bindings/nodejs/src/secret_manager.rs @@ -56,7 +56,7 @@ impl SecretManagerMethodHandler { (msg, is_err) } Err(e) => { - log::debug!("{:?}", e); + log::error!("{:?}", e); (format!("Couldn't parse to method with error - {e:?}"), true) } } diff --git a/bindings/nodejs/src/wallet.rs b/bindings/nodejs/src/wallet.rs index 69091ec27a..8363def0cd 100644 --- a/bindings/nodejs/src/wallet.rs +++ b/bindings/nodejs/src/wallet.rs @@ -58,7 +58,7 @@ impl WalletMethodHandler { (msg, is_err) } Err(e) => { - log::debug!("{:?}", e); + log::error!("{:?}", e); ( serde_json::to_string(&Response::Error(e.into())).expect("json to string error"), true, diff --git a/bindings/python/CHANGELOG.md b/bindings/python/CHANGELOG.md index 2f3c0ac01d..ea87791c30 100644 --- a/bindings/python/CHANGELOG.md +++ b/bindings/python/CHANGELOG.md @@ -19,6 +19,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> + +## 1.1.0 - 2023-MM-DD + +### Added + +- `ConflictReason` display implementation with an explanation of the conflict; +- `Account::{burn(), consolidate_outputs(), create_alias_output(), create_native_token(), melt_native_token(), mint_native_token(), mint_nfts(), send_transaction(), send_native_tokens(), send_nft()}` methods; + ## 1.0.2 - 2023-MM-DD ### Added diff --git a/bindings/python/examples/.pylintrc b/bindings/python/examples/.pylintrc new file mode 100644 index 0000000000..b87e66105a --- /dev/null +++ b/bindings/python/examples/.pylintrc @@ -0,0 +1,10 @@ +[MESSAGES CONTROL] +disable=missing-module-docstring, + line-too-long, # enforced by PEP8 format checker + broad-exception-raised, + invalid-name, # files that starts with number + duplicate-code, + consider-using-with, + consider-using-f-string + + diff --git a/bindings/python/examples/client/04_get_output.py b/bindings/python/examples/client/04_get_output.py index 2eaa1915a0..7c3b10e87d 100644 --- a/bindings/python/examples/client/04_get_output.py +++ b/bindings/python/examples/client/04_get_output.py @@ -1,9 +1,10 @@ -from iota_sdk import Client -from dotenv import load_dotenv - import json import os +from dotenv import load_dotenv + +from iota_sdk import Client + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/05_get_address_balance.py b/bindings/python/examples/client/05_get_address_balance.py index 1db5f9ac74..34662637bd 100644 --- a/bindings/python/examples/client/05_get_address_balance.py +++ b/bindings/python/examples/client/05_get_address_balance.py @@ -1,7 +1,9 @@ -from iota_sdk import Client, NodeIndexerAPI -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Client, NodeIndexerAPI + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') @@ -9,9 +11,9 @@ # Create a Client instance client = Client(nodes=[node_url]) -address = 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy' +ADDRESS = 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy' query_parameters = NodeIndexerAPI.QueryParameters( - address, + ADDRESS, has_expiration=False, has_timelock=False, has_storage_deposit_return=False @@ -37,4 +39,4 @@ native_tokens.append(output.native_tokens) print( - f'Outputs controlled by {address} have {total_amount} glow and native tokens: {native_tokens}') + f'Outputs controlled by {ADDRESS} have {total_amount} glow and native tokens: {native_tokens}') diff --git a/bindings/python/examples/client/07_get_block_data.py b/bindings/python/examples/client/07_get_block_data.py index 7a1280e867..595ac43fa0 100644 --- a/bindings/python/examples/client/07_get_block_data.py +++ b/bindings/python/examples/client/07_get_block_data.py @@ -1,8 +1,10 @@ -from dataclasses import asdict -from iota_sdk import Client -from dotenv import load_dotenv import json import os +from dataclasses import asdict + +from dotenv import load_dotenv + +from iota_sdk import Client load_dotenv() diff --git a/bindings/python/examples/client/build_alias.py b/bindings/python/examples/client/build_alias.py index bb39ce8eb5..daf4ae7813 100644 --- a/bindings/python/examples/client/build_alias.py +++ b/bindings/python/examples/client/build_alias.py @@ -1,8 +1,13 @@ -from iota_sdk import * -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import (Client, Ed25519Address, GovernorAddressUnlockCondition, + IssuerFeature, MetadataFeature, SenderFeature, + StateControllerAddressUnlockCondition, Utils, + utf8_to_hex) + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/build_basic.py b/bindings/python/examples/client/build_basic.py index cc35b010ae..de04e2fcaa 100644 --- a/bindings/python/examples/client/build_basic.py +++ b/bindings/python/examples/client/build_basic.py @@ -1,8 +1,13 @@ -from iota_sdk import * -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import (AddressUnlockCondition, Client, Ed25519Address, + ExpirationUnlockCondition, MetadataFeature, + SenderFeature, StorageDepositReturnUnlockCondition, + TagFeature, TimelockUnlockCondition, Utils, utf8_to_hex) + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') @@ -43,7 +48,7 @@ unlock_conditions=[ address_unlock_condition, StorageDepositReturnUnlockCondition( - return_address=Ed25519Address(hex_address), + returnAddress=Ed25519Address(hex_address), amount=1000000 ) ], @@ -56,8 +61,8 @@ unlock_conditions=[ address_unlock_condition, ExpirationUnlockCondition( - return_address=Ed25519Address(hex_address), - unix_time=1 + returnAddress=Ed25519Address(hex_address), + unixTime=1 ) ], amount=1000000, diff --git a/bindings/python/examples/client/build_foundry.py b/bindings/python/examples/client/build_foundry.py index 87fdf334a8..1be030eee2 100644 --- a/bindings/python/examples/client/build_foundry.py +++ b/bindings/python/examples/client/build_foundry.py @@ -1,8 +1,11 @@ -from iota_sdk import * -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import (AliasAddress, Client, + ImmutableAliasAddressUnlockCondition, SimpleTokenScheme) + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/build_nft.py b/bindings/python/examples/client/build_nft.py index e426085db1..be055765d2 100644 --- a/bindings/python/examples/client/build_nft.py +++ b/bindings/python/examples/client/build_nft.py @@ -1,8 +1,12 @@ -from iota_sdk import * -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import (AddressUnlockCondition, Client, Ed25519Address, + IssuerFeature, MetadataFeature, SenderFeature, + TagFeature, Utils, utf8_to_hex) + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/custom_plugin.py b/bindings/python/examples/client/custom_plugin.py index 37938d04ca..79292ce7c8 100644 --- a/bindings/python/examples/client/custom_plugin.py +++ b/bindings/python/examples/client/custom_plugin.py @@ -1,9 +1,11 @@ -from iota_sdk import Client, init_logger -from dotenv import load_dotenv - import json import os +from dotenv import load_dotenv + +# pylint: disable=no-name-in-module +from iota_sdk import Client, init_logger + load_dotenv() log_config = { 'name': 'client.log', diff --git a/bindings/python/examples/client/get_raw_block.py b/bindings/python/examples/client/get_raw_block.py index 2f5b507d81..51006f60ec 100644 --- a/bindings/python/examples/client/get_raw_block.py +++ b/bindings/python/examples/client/get_raw_block.py @@ -1,7 +1,9 @@ -from iota_sdk import Client -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Client + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/logger.py b/bindings/python/examples/client/logger.py index e40fcb2e5e..3d33d60ec8 100644 --- a/bindings/python/examples/client/logger.py +++ b/bindings/python/examples/client/logger.py @@ -1,7 +1,10 @@ -from iota_sdk import Client, init_logger -from dotenv import load_dotenv -import os import json +import os + +from dotenv import load_dotenv + +# pylint: disable=no-name-in-module +from iota_sdk import Client, init_logger load_dotenv() diff --git a/bindings/python/examples/client/mqtt.py b/bindings/python/examples/client/mqtt.py index e73e5a669c..c948af27cd 100644 --- a/bindings/python/examples/client/mqtt.py +++ b/bindings/python/examples/client/mqtt.py @@ -3,11 +3,13 @@ # This example shows how to listen to MQTT events of a node. -from iota_sdk import Client -from dotenv import load_dotenv +import json import os import threading -import json + +from dotenv import load_dotenv + +from iota_sdk import Client load_dotenv() @@ -22,8 +24,10 @@ def callback(event): + """Callback function for the MQTT listener""" event_dict = json.loads(event) print(event_dict) + # pylint: disable=global-statement global received_events received_events += 1 if received_events > 10: diff --git a/bindings/python/examples/client/post_raw_block.py b/bindings/python/examples/client/post_raw_block.py index 69f0f94396..cf341a6ab8 100644 --- a/bindings/python/examples/client/post_raw_block.py +++ b/bindings/python/examples/client/post_raw_block.py @@ -2,6 +2,9 @@ from dotenv import load_dotenv import os +from dotenv import load_dotenv + + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/client/submit_and_read_block.py b/bindings/python/examples/client/submit_and_read_block.py index 5759f73b65..f74e003cb3 100644 --- a/bindings/python/examples/client/submit_and_read_block.py +++ b/bindings/python/examples/client/submit_and_read_block.py @@ -6,6 +6,7 @@ # 2) Submit the block to the Shimmer test network # 3) Use the block ID to read the payload back from the network + # 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, TaggedDataPayload @@ -40,7 +41,7 @@ tag_hex = utf8_to_hex(tag) message_hex = utf8_to_hex(message) -print(f'\nYour prepared block content is:') +print('\nYour prepared block content is:') print(f' tag: {tag}') print(f' tag converted to hex: {tag_hex}') print(f' message: {message}') @@ -63,10 +64,10 @@ block_id = blockIdAndBlock[0] block = blockIdAndBlock[1] -print(f'\nThe block ID for your submitted block is:') +print('\nThe block ID for your submitted block is:') print(f' {block_id}') -print(f'\nMetadata for your submitted block is:') +print('\nMetadata for your submitted block is:') print(f' {block}') ######################################################## @@ -88,7 +89,7 @@ # Unpackage the payload (from hex to text) message_out = hex_to_utf8(message_hex_out) -print(f'\nYour message, read from the Shimmer network:') +print('\nYour message, read from the Shimmer network:') print(f' {message_out}') # Or see the message online, with the testnet explorer. diff --git a/bindings/python/examples/exchange/1_create_account.py b/bindings/python/examples/exchange/1_create_account.py index c7fcf281da..fd8f0bc09d 100644 --- a/bindings/python/examples/exchange/1_create_account.py +++ b/bindings/python/examples/exchange/1_create_account.py @@ -3,10 +3,13 @@ # This example creates a new database and account. -from iota_sdk import Wallet, StrongholdSecretManager, SyncOptions, CoinType, ClientOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (ClientOptions, CoinType, StrongholdSecretManager, + SyncOptions, Wallet) + # This example uses secrets in environment variables for simplicity which # should not be done in production. load_dotenv() diff --git a/bindings/python/examples/exchange/2_generate_address.py b/bindings/python/examples/exchange/2_generate_address.py index 55ed957199..afd9257e37 100644 --- a/bindings/python/examples/exchange/2_generate_address.py +++ b/bindings/python/examples/exchange/2_generate_address.py @@ -3,10 +3,12 @@ # This example generates an address for an account. -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # This example uses secrets in environment variables for simplicity which # should not be done in production. load_dotenv() @@ -23,4 +25,4 @@ account = wallet.get_account('Alice') address = account.generate_ed25519_addresses(1)[0] -print(f'Address:', address.address) +print('Address:', address.address) diff --git a/bindings/python/examples/exchange/3_check_balance.py b/bindings/python/examples/exchange/3_check_balance.py index a7d0ebd4f5..1949e38f6c 100644 --- a/bindings/python/examples/exchange/3_check_balance.py +++ b/bindings/python/examples/exchange/3_check_balance.py @@ -3,10 +3,12 @@ # This example gets the balance of an account. -from iota_sdk import Wallet, SyncOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SyncOptions, Wallet + # This example uses secrets in environment variables for simplicity which # should not be done in production. load_dotenv() @@ -19,7 +21,7 @@ account = wallet.get_account('Alice') addresses = account.addresses() -print(f'Addresses:', addresses) +print('Addresses:', addresses) # Set sync_only_most_basic_outputs to True if not interested in outputs that are timelocked, # have a storage deposit return, expiration or are nft/account/foundry outputs. diff --git a/bindings/python/examples/exchange/4_listen_events.py b/bindings/python/examples/exchange/4_listen_events.py index 68a25aa6c9..0da6091589 100644 --- a/bindings/python/examples/exchange/4_listen_events.py +++ b/bindings/python/examples/exchange/4_listen_events.py @@ -3,12 +3,15 @@ # This example listens to the NewOutput event. -from iota_sdk import Wallet, SyncOptions, WalletEventType -from dotenv import load_dotenv import json import os +import sys import time +from dotenv import load_dotenv + +from iota_sdk import SyncOptions, Wallet, WalletEventType + # This example uses secrets in environment variables for simplicity which # should not be done in production. load_dotenv() @@ -24,11 +27,13 @@ def callback(event): + """Callback function for the event listener""" event_dict = json.loads(event) print('AccountIndex:', event_dict["accountIndex"]) print('Event:', event_dict["event"]) # Exit after receiving an event. + # pylint: disable=global-statement global received_event received_event = True @@ -47,7 +52,7 @@ def callback(event): # Sync every 5 seconds until the faucet transaction gets confirmed. for _ in range(100): if received_event: - exit() + sys.exit() time.sleep(5) # Sync to detect new outputs diff --git a/bindings/python/examples/exchange/5_send_amount.py b/bindings/python/examples/exchange/5_send_amount.py index 9853dbf4d5..eb728c77e6 100644 --- a/bindings/python/examples/exchange/5_send_amount.py +++ b/bindings/python/examples/exchange/5_send_amount.py @@ -3,10 +3,12 @@ # This example sends tokens to an address. -from iota_sdk import Wallet, SyncOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SyncOptions, Wallet + # This example uses secrets in environment variables for simplicity which # should not be done in production. load_dotenv() diff --git a/bindings/python/examples/how_tos/account_output/create.py b/bindings/python/examples/how_tos/account_output/create.py index fe216b636a..c4cdb3cb96 100644 --- a/bindings/python/examples/how_tos/account_output/create.py +++ b/bindings/python/examples/how_tos/account_output/create.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will create an account output @@ -19,5 +21,5 @@ wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) # Send transaction. -transaction = account.prepare_create_account_output(None, None).send() +transaction = account.create_account_output(None, None) print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction.block_id}') diff --git a/bindings/python/examples/how_tos/account_output/destroy.py b/bindings/python/examples/how_tos/account_output/destroy.py index b45da420f7..87fe39d91e 100644 --- a/bindings/python/examples/how_tos/account_output/destroy.py +++ b/bindings/python/examples/how_tos/account_output/destroy.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will destroy an account output diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/check_balance.py b/bindings/python/examples/how_tos/accounts_and_addresses/check_balance.py index c03d55bab7..cac977d98b 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/check_balance.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/check_balance.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # This example checks the balance of an account. # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py b/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py index ec548d8188..129a53de1e 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, Utils, ConsolidationParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import ConsolidationParams, Utils, Wallet + # In this example we will consolidate basic outputs from an account with only an AddressUnlockCondition by sending # them to the same address again. @@ -45,8 +47,7 @@ # Consolidate unspent outputs and print the consolidation transaction ID # Set `force` to true to force the consolidation even though the # `output_threshold` isn't reached. -transaction = account.prepare_consolidate_outputs( - ConsolidationParams(force=True)).send() +transaction = account.consolidate_outputs(ConsolidationParams(force=True)) print('Transaction sent: ', transaction.transaction_id) # Wait for the consolidation transaction to get confirmed @@ -54,10 +55,7 @@ transaction.transaction_id) print( - 'Transaction included: {}/block/{}'.format( - os.environ['EXPLORER_URL'], - block_id - ) + f'Transaction included: {os.environ["EXPLORER_ID"]}/block/{block_id}' ) # Sync account diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py b/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py index be327ae9cd..34b26d5866 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, Wallet + load_dotenv() # This example creates a new database and account diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/create_address.py b/bindings/python/examples/how_tos/accounts_and_addresses/create_address.py index db1e5445a1..77a63e98ad 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/create_address.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/create_address.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # This example generates a new address. @@ -16,4 +18,4 @@ account = wallet.get_account('Alice') address = account.generate_ed25519_addresses(1) -print(f'Generated address:', address[0].address) +print('Generated address:', address[0].address) diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/list_accounts.py b/bindings/python/examples/how_tos/accounts_and_addresses/list_accounts.py index 2f5028b91c..abbb5756e9 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/list_accounts.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/list_accounts.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # This example lists all accounts in the wallet. # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/list_addresses.py b/bindings/python/examples/how_tos/accounts_and_addresses/list_addresses.py index 81e4f48814..38cb28430f 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/list_addresses.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/list_addresses.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # This example lists all addresses in the account. # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/list_outputs.py b/bindings/python/examples/how_tos/accounts_and_addresses/list_outputs.py index 29b08c6ced..5e809633ef 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/list_outputs.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/list_outputs.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # In this example we will get outputs stored in the account # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/list_transactions.py b/bindings/python/examples/how_tos/accounts_and_addresses/list_transactions.py index 372c429d0b..a0e881703f 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/list_transactions.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/list_transactions.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # In this example we will list transactions # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py b/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py index 56808882f6..483872e7c5 100644 --- a/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py +++ b/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py @@ -1,8 +1,18 @@ -from iota_sdk import * -from dotenv import load_dotenv +import datetime import os import time -import datetime + +from dotenv import load_dotenv + +from iota_sdk import ( + AddressUnlockCondition, + Client, + Ed25519Address, + Wallet, + Utils, + TimelockUnlockCondition, +) + load_dotenv() diff --git a/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py b/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py index 5925418935..32e9f55798 100644 --- a/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py +++ b/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will claim outputs that have additional unlock @@ -22,7 +24,7 @@ # Only the unspent outputs in the account output_ids = account.claimable_outputs('All') -print(f'Available outputs to claim:') +print('Available outputs to claim:') for output_id in output_ids: print(f'{output_id}') diff --git a/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py b/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py index f784a1bc34..f890ed95f4 100644 --- a/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py +++ b/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will send an amount below the minimum storage deposit diff --git a/bindings/python/examples/how_tos/client/get_health.py b/bindings/python/examples/how_tos/client/get_health.py index 90df03d3f5..b66fad5f44 100644 --- a/bindings/python/examples/how_tos/client/get_health.py +++ b/bindings/python/examples/how_tos/client/get_health.py @@ -1,7 +1,9 @@ -from iota_sdk import Client -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Client + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/how_tos/client/get_info.py b/bindings/python/examples/how_tos/client/get_info.py index 8dbb0babc8..ff7e6c75a0 100644 --- a/bindings/python/examples/how_tos/client/get_info.py +++ b/bindings/python/examples/how_tos/client/get_info.py @@ -1,9 +1,11 @@ -from iota_sdk import Client import dataclasses -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import Client + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/how_tos/client/get_outputs.py b/bindings/python/examples/how_tos/client/get_outputs.py index c26e07cc59..7c9428ac33 100644 --- a/bindings/python/examples/how_tos/client/get_outputs.py +++ b/bindings/python/examples/how_tos/client/get_outputs.py @@ -1,8 +1,10 @@ -from iota_sdk import Client, NodeIndexerAPI -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import Client, NodeIndexerAPI + load_dotenv() node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') diff --git a/bindings/python/examples/how_tos/native_tokens/burn.py b/bindings/python/examples/how_tos/native_tokens/burn.py index 90ff290261..dbb712e9c3 100644 --- a/bindings/python/examples/how_tos/native_tokens/burn.py +++ b/bindings/python/examples/how_tos/native_tokens/burn.py @@ -2,6 +2,10 @@ from dotenv import load_dotenv from iota_sdk import Wallet +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will burn native tokens diff --git a/bindings/python/examples/how_tos/native_tokens/create.py b/bindings/python/examples/how_tos/native_tokens/create.py index 971743e0ab..7a354cb92d 100644 --- a/bindings/python/examples/how_tos/native_tokens/create.py +++ b/bindings/python/examples/how_tos/native_tokens/create.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, utf8_to_hex, CreateNativeTokenParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import CreateNativeTokenParams, Wallet, utf8_to_hex + load_dotenv() # In this example we will create native tokens @@ -23,7 +25,7 @@ # existing one. if not balance.accounts: # If we don't have an account, we need to create one - transaction = account.prepare_create_account_output(None, None).send() + transaction = account.create_alias_output(None, None) print(f'Transaction sent: {transaction.transaction_id}') # Wait for transaction to get included diff --git a/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py b/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py index a3e631fc6c..ce2b5ecf2d 100644 --- a/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py +++ b/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py @@ -2,6 +2,10 @@ from dotenv import load_dotenv from iota_sdk import Wallet +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will destroy a foundry diff --git a/bindings/python/examples/how_tos/native_tokens/melt.py b/bindings/python/examples/how_tos/native_tokens/melt.py index 3fab10aa42..4d67341bbc 100644 --- a/bindings/python/examples/how_tos/native_tokens/melt.py +++ b/bindings/python/examples/how_tos/native_tokens/melt.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, HexStr -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will decrease the native token supply @@ -28,7 +30,7 @@ melt_amount = 10 # Send transaction. -transaction = account.prepare_melt_native_token(token_id, melt_amount).send() +transaction = account.melt_native_token(token_id, melt_amount) print(f'Transaction sent: {transaction.transaction_id}') # Wait for transaction to get included diff --git a/bindings/python/examples/how_tos/native_tokens/mint.py b/bindings/python/examples/how_tos/native_tokens/mint.py index c842cdd1d8..6f72425ce7 100644 --- a/bindings/python/examples/how_tos/native_tokens/mint.py +++ b/bindings/python/examples/how_tos/native_tokens/mint.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will mint native tokens @@ -27,8 +29,8 @@ mint_amount = 10 -# Prepare and send transaction. -transaction = account.prepare_mint_native_token(token_id, mint_amount).send() +# Send transaction. +transaction = account.mint_native_token(token_id, mint_amount) print(f'Transaction sent: {transaction.transaction_id}') # Wait for transaction to get included diff --git a/bindings/python/examples/how_tos/native_tokens/send.py b/bindings/python/examples/how_tos/native_tokens/send.py index 4e134bfaa3..5c5b6b623f 100644 --- a/bindings/python/examples/how_tos/native_tokens/send.py +++ b/bindings/python/examples/how_tos/native_tokens/send.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, SendNativeTokensParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SendNativeTokensParams, Wallet + load_dotenv() # In this example we will send native tokens @@ -30,7 +32,7 @@ )], )] -transaction = account.prepare_send_native_tokens(outputs, None).send() +transaction = account.send_native_tokens(outputs, None) print(f'Transaction sent: {transaction.transaction_id}') # Wait for transaction to get included diff --git a/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py b/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py index 91a542e00f..3d47fada8f 100644 --- a/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py +++ b/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, Utils, utf8_to_hex, MintNftParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import MintNftParams, Utils, Wallet, utf8_to_hex + load_dotenv() # In this example we will mint the issuer NFT for the NFT collection. @@ -26,8 +28,7 @@ ) -prepared = account.prepare_mint_nfts([params]) -transaction = prepared.send() +transaction = account.mint_nfts([params]) # Wait for transaction to get included block_id = account.reissue_transaction_until_included( diff --git a/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py b/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py index cf1d47df78..e3fb66af60 100644 --- a/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py +++ b/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet, Utils, utf8_to_hex, MintNftParams -from dotenv import load_dotenv +import json import os import sys -import json + +from dotenv import load_dotenv + +from iota_sdk import MintNftParams, Utils, Wallet, utf8_to_hex load_dotenv() @@ -34,7 +36,8 @@ issuer = Utils.nft_id_to_bech32(issuer_nft_id, bech32_hrp) -def get_immutable_metadata(index: int, issuer_nft_id: str) -> str: +def get_immutable_metadata(index: int, collection_id: str) -> str: + """Returns the immutable metadata for the NFT with the given index""" data = { "standard": "IRC27", "version": "v1.0", @@ -43,7 +46,7 @@ def get_immutable_metadata(index: int, issuer_nft_id: str) -> str: "name": "Shimmer OG NFT #" + str(index), "description": "The Shimmer OG NFT was handed out 1337 times by the IOTA Foundation to celebrate the official launch of the Shimmer Network.", "issuerName": "IOTA Foundation", - "collectionId": issuer_nft_id, + "collectionId": collection_id, "collectionName": "Shimmer OG" } return json.dumps(data, separators=(',', ':')) @@ -60,8 +63,7 @@ def get_immutable_metadata(index: int, issuer_nft_id: str) -> str: chunk, nft_mint_params = nft_mint_params[:NUM_NFTS_MINTED_PER_TRANSACTION], nft_mint_params[NUM_NFTS_MINTED_PER_TRANSACTION:] print( f'Minting {len(chunk)} NFTs... ({NFT_COLLECTION_SIZE-len(nft_mint_params)}/{NFT_COLLECTION_SIZE})') - prepared = account.prepare_mint_nfts(chunk) - transaction = prepared.send() + transaction = account.mint_nfts(chunk) # Wait for transaction to get included block_id = account.reissue_transaction_until_included( diff --git a/bindings/python/examples/how_tos/nfts/burn_nft.py b/bindings/python/examples/how_tos/nfts/burn_nft.py index d50e7d7e8f..948e7c41a2 100644 --- a/bindings/python/examples/how_tos/nfts/burn_nft.py +++ b/bindings/python/examples/how_tos/nfts/burn_nft.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will burn an NFT diff --git a/bindings/python/examples/how_tos/nfts/mint_nft.py b/bindings/python/examples/how_tos/nfts/mint_nft.py index 6eaf3c07ed..d45db0961d 100644 --- a/bindings/python/examples/how_tos/nfts/mint_nft.py +++ b/bindings/python/examples/how_tos/nfts/mint_nft.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, utf8_to_hex, MintNftParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import MintNftParams, Wallet, utf8_to_hex + load_dotenv() # In this example we will mint an nft @@ -22,5 +24,5 @@ immutable_metadata=utf8_to_hex("some immutable nft metadata"), )] -transaction = account.prepare_mint_nfts(outputs).send() +transaction = account.mint_nfts(outputs) print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction.block_id}') diff --git a/bindings/python/examples/how_tos/nfts/send_nft.py b/bindings/python/examples/how_tos/nfts/send_nft.py index a3f9b62b66..d980baa520 100644 --- a/bindings/python/examples/how_tos/nfts/send_nft.py +++ b/bindings/python/examples/how_tos/nfts/send_nft.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, SendNftParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SendNftParams, Wallet + load_dotenv() # In this example we will send an nft @@ -23,5 +25,5 @@ nft_id=balance.nfts[0], )] -transaction = account.prepare_send_nft(outputs).send() +transaction = account.send_nft(outputs) print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction.block_id}') diff --git a/bindings/python/examples/how_tos/outputs/features.py b/bindings/python/examples/how_tos/outputs/features.py index f7d6108485..c8fc009baa 100644 --- a/bindings/python/examples/how_tos/outputs/features.py +++ b/bindings/python/examples/how_tos/outputs/features.py @@ -1,7 +1,19 @@ -from iota_sdk import * +import json from dataclasses import asdict + from dotenv import load_dotenv -import json + +from iota_sdk import ( + AddressUnlockCondition, + Client, + Ed25519Address, + Utils, + SenderFeature, + IssuerFeature, + MetadataFeature, + TagFeature, + utf8_to_hex, +) load_dotenv() diff --git a/bindings/python/examples/how_tos/outputs/unlock_conditions.py b/bindings/python/examples/how_tos/outputs/unlock_conditions.py index 5e1447ba63..d1b8a71bf2 100644 --- a/bindings/python/examples/how_tos/outputs/unlock_conditions.py +++ b/bindings/python/examples/how_tos/outputs/unlock_conditions.py @@ -1,7 +1,21 @@ -from iota_sdk import * -from dotenv import load_dotenv import json +from dotenv import load_dotenv +from iota_sdk import ( + AddressUnlockCondition, + AliasAddress, + Client, + Ed25519Address, + Utils, + ExpirationUnlockCondition, + SimpleTokenScheme, + StorageDepositReturnUnlockCondition, + TimelockUnlockCondition, + GovernorAddressUnlockCondition, + StateControllerAddressUnlockCondition, + ImmutableAliasAddressUnlockCondition, +) + load_dotenv() client = Client() diff --git a/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py b/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py index 338b647830..0bd6e9527e 100644 --- a/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py +++ b/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py @@ -1,7 +1,10 @@ -from iota_sdk import Client, StrongholdSecretManager, SecretManager, Bip44, CoinType, Utils, utf8_to_hex -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (Bip44, CoinType, SecretManager, + StrongholdSecretManager, Utils, utf8_to_hex) + load_dotenv() # In this example we will sign with Ed25519. diff --git a/bindings/python/examples/how_tos/sign_evm/sign_evm.py b/bindings/python/examples/how_tos/sign_evm/sign_evm.py index 60691726e9..1cc85a43a1 100644 --- a/bindings/python/examples/how_tos/sign_evm/sign_evm.py +++ b/bindings/python/examples/how_tos/sign_evm/sign_evm.py @@ -1,7 +1,10 @@ -from iota_sdk import Client, StrongholdSecretManager, SecretManager, Bip44, CoinType, Utils, utf8_to_hex -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (Bip44, CoinType, SecretManager, StrongholdSecretManager, + utf8_to_hex) + load_dotenv() # In this example we will sign with secp256k1. diff --git a/bindings/python/examples/how_tos/simple_transaction/request_funds.py b/bindings/python/examples/how_tos/simple_transaction/request_funds.py index 2f530ef1f3..583a167d87 100644 --- a/bindings/python/examples/how_tos/simple_transaction/request_funds.py +++ b/bindings/python/examples/how_tos/simple_transaction/request_funds.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + # This example requests funds from the faucet load_dotenv() diff --git a/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py b/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py index 876672cc50..400667c939 100644 --- a/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py +++ b/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, SendParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SendParams, Wallet + load_dotenv() # This example sends a transaction. diff --git a/bindings/python/examples/secret_manager/generate_addresses.py b/bindings/python/examples/secret_manager/generate_addresses.py index 8ccd9daee8..899c8d12b7 100644 --- a/bindings/python/examples/secret_manager/generate_addresses.py +++ b/bindings/python/examples/secret_manager/generate_addresses.py @@ -1,7 +1,9 @@ -from iota_sdk import MnemonicSecretManager, CoinType, SecretManager -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import CoinType, MnemonicSecretManager, SecretManager + load_dotenv() if 'MNEMONIC' not in os.environ: diff --git a/bindings/python/examples/secret_manager/stronghold.py b/bindings/python/examples/secret_manager/stronghold.py index 71c29e9edc..db6abf25e1 100644 --- a/bindings/python/examples/secret_manager/stronghold.py +++ b/bindings/python/examples/secret_manager/stronghold.py @@ -1,7 +1,9 @@ -from iota_sdk import StrongholdSecretManager, SecretManager -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import SecretManager, StrongholdSecretManager + load_dotenv() if 'MNEMONIC' not in os.environ: diff --git a/bindings/python/examples/wallet/12-prepare_output.py b/bindings/python/examples/wallet/12-prepare_output.py index 905b0696ed..12265d9d60 100644 --- a/bindings/python/examples/wallet/12-prepare_output.py +++ b/bindings/python/examples/wallet/12-prepare_output.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet, OutputParams, Unlocks -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import OutputParams, Unlocks, Wallet + load_dotenv() # In this example we will prepare an output with an address and expiration diff --git a/bindings/python/examples/wallet/13-check-unlock-conditions.py b/bindings/python/examples/wallet/13-check-unlock-conditions.py index 3b0193ea21..b09d4b1b2f 100644 --- a/bindings/python/examples/wallet/13-check-unlock-conditions.py +++ b/bindings/python/examples/wallet/13-check-unlock-conditions.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, Utils, OutputParams -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import OutputParams, Utils, Wallet + load_dotenv() # In this example we check if an output has only an address unlock @@ -19,6 +21,7 @@ def hexAddress(address): + """Converts an address to hex""" return Utils.bech32_to_hex(address.address) diff --git a/bindings/python/examples/wallet/backup.py b/bindings/python/examples/wallet/backup.py index 3460a5ca3f..45f197e4ce 100644 --- a/bindings/python/examples/wallet/backup.py +++ b/bindings/python/examples/wallet/backup.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, Wallet + load_dotenv() # This example creates a new database and account @@ -31,4 +33,4 @@ accounts = wallet.create_account('Alice') wallet.backup("backup.stronghold", os.environ['STRONGHOLD_PASSWORD']) -print(f'Created backup') +print('Created backup') diff --git a/bindings/python/examples/wallet/create_alias.py b/bindings/python/examples/wallet/create_alias.py index fe216b636a..c4cdb3cb96 100644 --- a/bindings/python/examples/wallet/create_alias.py +++ b/bindings/python/examples/wallet/create_alias.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # In this example we will create an account output @@ -19,5 +21,5 @@ wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) # Send transaction. -transaction = account.prepare_create_account_output(None, None).send() +transaction = account.create_account_output(None, None) print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{transaction.block_id}') diff --git a/bindings/python/examples/wallet/get_client.py b/bindings/python/examples/wallet/get_client.py index 6ae4facc70..0e3c8bde03 100644 --- a/bindings/python/examples/wallet/get_client.py +++ b/bindings/python/examples/wallet/get_client.py @@ -1,7 +1,9 @@ -from iota_sdk import Wallet -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import Wallet + load_dotenv() # This example gets a client from the wallet. diff --git a/bindings/python/examples/wallet/getting_started.py b/bindings/python/examples/wallet/getting_started.py index 92dcc06ac3..bb001d7d75 100644 --- a/bindings/python/examples/wallet/getting_started.py +++ b/bindings/python/examples/wallet/getting_started.py @@ -1,10 +1,13 @@ # Copyright 2023 IOTA Stiftung # SPDX-License-Identifier: Apache-2.0 -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions, Utils -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (ClientOptions, CoinType, StrongholdSecretManager, Utils, + Wallet) + load_dotenv() # A name to associate with the created account. diff --git a/bindings/python/examples/wallet/logger.py b/bindings/python/examples/wallet/logger.py index a1e659104b..7a411a966c 100644 --- a/bindings/python/examples/wallet/logger.py +++ b/bindings/python/examples/wallet/logger.py @@ -1,8 +1,12 @@ -from iota_sdk import Wallet, StrongholdSecretManager, init_logger, CoinType, ClientOptions -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +# pylint: disable=no-name-in-module +from iota_sdk import (ClientOptions, CoinType, StrongholdSecretManager, Wallet, + init_logger) + load_dotenv() # This example creates a new database and account and write debug logs in diff --git a/bindings/python/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.py b/bindings/python/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.py index c7dd538497..2af7aa5aa4 100644 --- a/bindings/python/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.py +++ b/bindings/python/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.py @@ -1,8 +1,11 @@ -import iota_sdk -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +# pylint: disable=no-name-in-module +from iota_sdk import (ClientOptions, CoinType, StrongholdSecretManager, Wallet, + migrate_stronghold_snapshot_v2_to_v3) + load_dotenv() v2_path = "../../../sdk/tests/wallet/fixtures/v2.stronghold" @@ -22,7 +25,7 @@ except ValueError as e: print(e) -iota_sdk.migrate_stronghold_snapshot_v2_to_v3( +migrate_stronghold_snapshot_v2_to_v3( v2_path, "current_password", "wallet.rs", diff --git a/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py b/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py index 86e8ac4e48..cf7bf74efa 100644 --- a/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py +++ b/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py @@ -4,11 +4,13 @@ # In this example we create an account and store its addresses in a file which will be used later to find # inputs. -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, Wallet + load_dotenv() OFFLINE_WALLET_DB_PATH = "./wallet/offline_signing/example-offline-walletdb" @@ -43,6 +45,6 @@ json_data = json.dumps(list(map(lambda x: x.__dict__, addresses)), indent=4) print(f"example.addresses.json:\n{json_data}") -f = open(ADDRESSES_FILE_PATH, "w") +f = open(ADDRESSES_FILE_PATH, "w", encoding="utf-8") f.write(json_data) f.close() diff --git a/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py b/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py index 6532db58ce..36eae8c8e5 100644 --- a/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py +++ b/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py @@ -3,12 +3,15 @@ # In this example we will get inputs and prepare a transaction. -from iota_sdk import Wallet, CoinType, ClientOptions, SendParams, AccountAddress -from dotenv import load_dotenv -from dacite import from_dict import json import os +from dacite import from_dict +from dotenv import load_dotenv + +from iota_sdk import (AccountAddress, ClientOptions, CoinType, SendParams, + Wallet) + load_dotenv() ONLINE_WALLET_DB_PATH = "./wallet/offline_signing/example-online-walletdb" @@ -23,7 +26,7 @@ params = [SendParams(RECV_ADDRESS, str(SEND_AMOUNT))] # Recovers addresses from example `0_address_generation`. -addresses_data = json.load(open(ADDRESSES_FILE_PATH, "r")) +addresses_data = json.load(open(ADDRESSES_FILE_PATH, "r", encoding="utf-8")) addresses = list(map(lambda x: AccountAddress.from_dict(x), addresses_data)) if 'NODE_URL' not in os.environ: @@ -45,6 +48,6 @@ prepared_transaction.prepared_transaction_data().to_dict(), indent=4) print(f"example.prepared_transaction.json:\n{json_data}") -f = open(PREPARED_TRANSACTION_FILE_PATH, "w") +f = open(PREPARED_TRANSACTION_FILE_PATH, "w", encoding="utf-8") f.write(json_data) f.close() diff --git a/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py b/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py index 4c1a3b57b5..12c74dfe07 100644 --- a/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py +++ b/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py @@ -3,12 +3,14 @@ # In this example we sign the prepared transaction. -from iota_sdk import Wallet, PreparedTransactionData -from dotenv import load_dotenv -from dacite import from_dict import json import os +from dacite import from_dict +from dotenv import load_dotenv + +from iota_sdk import PreparedTransactionData, Wallet + load_dotenv() OFFLINE_WALLET_DB_PATH = "./wallet/offline_signing/example-offline-walletdb" @@ -18,7 +20,7 @@ prepared_transaction_data = json.load( - open(PREPARED_TRANSACTION_FILE_PATH, "r")) + open(PREPARED_TRANSACTION_FILE_PATH, "r", encoding="utf-8")) prepared_transaction_data = from_dict( PreparedTransactionData, prepared_transaction_data) @@ -39,6 +41,6 @@ json_data = json.dumps(signed_transaction_data.to_dict(), indent=4) print(f"example.signed_transaction.json:\n{json_data}") -f = open(SIGNED_TRANSACTION_FILE_PATH, "w") +f = open(SIGNED_TRANSACTION_FILE_PATH, "w", encoding="utf-8") f.write(json_data) f.close() diff --git a/bindings/python/examples/wallet/offline_signing/3_send_transaction.py b/bindings/python/examples/wallet/offline_signing/3_send_transaction.py index 02c1cba517..bfa3ff8a8a 100644 --- a/bindings/python/examples/wallet/offline_signing/3_send_transaction.py +++ b/bindings/python/examples/wallet/offline_signing/3_send_transaction.py @@ -3,12 +3,14 @@ # In this example we send the signed transaction in a block. -from iota_sdk import Wallet, SignedTransactionData -from dotenv import load_dotenv -from dacite import from_dict import json import os +from dacite import from_dict +from dotenv import load_dotenv + +from iota_sdk import SignedTransactionData, Wallet + load_dotenv() ONLINE_WALLET_DB_PATH = "./wallet/offline_signing/example-online-walletdb" @@ -22,7 +24,7 @@ account = wallet.get_account("Alice") signed_transaction_data = json.load( - open(SIGNED_TRANSACTION_FILE_PATH, "r")) + open(SIGNED_TRANSACTION_FILE_PATH, "r", encoding="utf-8")) signed_transaction_data = from_dict( SignedTransactionData, signed_transaction_data) diff --git a/bindings/python/examples/wallet/recover_accounts.py b/bindings/python/examples/wallet/recover_accounts.py index 5ed9160074..665e840fa4 100644 --- a/bindings/python/examples/wallet/recover_accounts.py +++ b/bindings/python/examples/wallet/recover_accounts.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet, StrongholdSecretManager, CoinType, ClientOptions -from dotenv import load_dotenv import json import os +from dotenv import load_dotenv + +from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, Wallet + load_dotenv() # This example searches for accounts with unspent outputs. diff --git a/bindings/python/examples/wallet/restore_backup.py b/bindings/python/examples/wallet/restore_backup.py index 11c86e85fb..4d726fea05 100644 --- a/bindings/python/examples/wallet/restore_backup.py +++ b/bindings/python/examples/wallet/restore_backup.py @@ -1,8 +1,10 @@ -from iota_sdk import Wallet, CoinType, ClientOptions -from dotenv import load_dotenv -from dataclasses import asdict import json import os +from dataclasses import asdict + +from dotenv import load_dotenv + +from iota_sdk import ClientOptions, CoinType, Wallet load_dotenv() diff --git a/bindings/python/examples/wallet/transaction_options.py b/bindings/python/examples/wallet/transaction_options.py index f4a06c167b..ad3d171c1f 100644 --- a/bindings/python/examples/wallet/transaction_options.py +++ b/bindings/python/examples/wallet/transaction_options.py @@ -1,7 +1,10 @@ -from iota_sdk import Wallet, TransactionOptions, TaggedDataPayload, utf8_to_hex, RemainderValueStrategy -from dotenv import load_dotenv import os +from dotenv import load_dotenv + +from iota_sdk import (RemainderValueStrategy, TaggedDataPayload, + TransactionOptions, Wallet, utf8_to_hex) + load_dotenv() # This example sends a transaction with a tagged data payload. diff --git a/bindings/python/iota_sdk/types/block.py b/bindings/python/iota_sdk/types/block.py index 355e8b83b1..eb6645a1fd 100644 --- a/bindings/python/iota_sdk/types/block.py +++ b/bindings/python/iota_sdk/types/block.py @@ -83,6 +83,24 @@ class ConflictReason(Enum): semanticValidationFailed = 255, +CONFLICT_REASON_STRINGS = { + ConflictReason.none: 'The block has no conflict', + ConflictReason.inputUTXOAlreadySpent: 'The referenced UTXO was already spent', + ConflictReason.inputUTXOAlreadySpentInThisMilestone: 'The referenced UTXO was already spent while confirming this milestone', + ConflictReason.inputUTXONotFound: 'The referenced UTXO cannot be found', + ConflictReason.inputOutputSumMismatch: 'The sum of the inputs and output values does not match', + ConflictReason.invalidSignature: 'The unlock block signature is invalid', + ConflictReason.invalidTimelock: 'The configured timelock is not yet expired', + ConflictReason.invalidNativeTokens: 'The native tokens are invalid', + ConflictReason.returnAmountMismatch: 'The return amount in a transaction is not fulfilled by the output side', + ConflictReason.invalidInputUnlock: 'The input unlock is invalid', + ConflictReason.invalidInputsCommitment: 'The inputs commitment is invalid', + ConflictReason.invalidSender: ' The output contains a Sender with an ident (address) which is not unlocked', + ConflictReason.invalidChainState: 'The chain state transition is invalid', + ConflictReason.semanticValidationFailed: 'The semantic validation failed' +} + + @json @dataclass class BlockMetadata: diff --git a/bindings/python/iota_sdk/wallet/account.py b/bindings/python/iota_sdk/wallet/account.py index 2de2bc9b2e..f5eb81b0e8 100644 --- a/bindings/python/iota_sdk/wallet/account.py +++ b/bindings/python/iota_sdk/wallet/account.py @@ -78,6 +78,12 @@ def get_metadata(self) -> AccountMetadata: return AccountMetadata( self.meta["alias"], self.meta["coinType"], self.meta["index"]) + def burn( + self, burn: Burn, options: Optional[TransactionOptions] = None) -> Transaction: + """A generic function that can be used to burn native tokens, nfts, foundries and aliases. + """ + return self.prepare_burn(burn, options).send() + def prepare_burn( self, burn: Burn, options: Optional[TransactionOptions] = None) -> PreparedTransaction: """A generic `prepare_burn()` function that can be used to prepare the burn of native tokens, nfts, foundries and accounts. @@ -119,6 +125,12 @@ def prepare_burn_nft(self, ) return PreparedTransaction(self, prepared) + def consolidate_outputs( + self, params: ConsolidationParams) -> Transaction: + """Consolidate outputs. + """ + return self.prepare_consolidate_outputs(params).send() + def prepare_consolidate_outputs( self, params: ConsolidationParams) -> PreparedTransaction: """Consolidate outputs. @@ -130,6 +142,13 @@ def prepare_consolidate_outputs( ) return PreparedTransaction(self, prepared) + def create_account_output(self, + params: Optional[CreateAccountOutputParams] = None, + options: Optional[TransactionOptions] = None) -> Transaction: + """Create an account output. + """ + return self.prepare_create_account_output(params, options).send() + def prepare_create_account_output(self, params: Optional[CreateAccountOutputParams] = None, options: Optional[TransactionOptions] = None) -> PreparedTransaction: @@ -272,6 +291,12 @@ def pending_transactions(self): ) return [Transaction.from_dict(tx) for tx in transactions] + def create_native_token(self, params: CreateNativeTokenParams, + options: Optional[TransactionOptions] = None) -> Transaction: + """Create native token. + """ + return self.prepare_create_native_token(params, options).send() + def prepare_create_native_token(self, params: CreateNativeTokenParams, options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Create native token. @@ -285,6 +310,16 @@ def prepare_create_native_token(self, params: CreateNativeTokenParams, return PreparedCreateTokenTransaction( account=self, prepared_transaction_data=prepared) + def melt_native_token(self, + token_id: HexStr, + melt_amount: int, + options: Optional[TransactionOptions] = None) -> Transaction: + """Melt native tokens. This happens with the foundry output which minted them, by increasing it's + `melted_tokens` field. + """ + return self.prepare_melt_native_token( + token_id, melt_amount, options).send() + def prepare_melt_native_token(self, token_id: HexStr, melt_amount: int, @@ -301,6 +336,13 @@ def prepare_melt_native_token(self, ) return PreparedTransaction(self, prepared) + def mint_native_token(self, token_id: HexStr, mint_amount: int, + options: Optional[TransactionOptions] = None) -> Transaction: + """Mint additional native tokens. + """ + return self.prepare_mint_native_token( + token_id, mint_amount, options).send() + def prepare_mint_native_token(self, token_id: HexStr, mint_amount: int, options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Mint additional native tokens. @@ -314,6 +356,12 @@ def prepare_mint_native_token(self, token_id: HexStr, mint_amount: int, ) return PreparedTransaction(self, prepared) + def mint_nfts(self, params: List[MintNftParams], + options: Optional[TransactionOptions] = None) -> Transaction: + """Mint NFTs. + """ + return self.prepare_mint_nfts(params, options).send() + def prepare_mint_nfts(self, params: List[MintNftParams], options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Mint NFTs. @@ -361,6 +409,12 @@ def prepare_send(self, params: List[SendParams], ) return PreparedTransaction(self, prepared) + def send_transaction( + self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> Transaction: + """Send a transaction. + """ + return self.prepare_transaction(outputs, options).send() + def prepare_transaction( self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Prepare transaction. @@ -420,6 +474,12 @@ def send_with_params( } )) + def send_native_tokens( + self, params: List[SendNativeTokensParams], options: Optional[TransactionOptions] = None) -> Transaction: + """Send native tokens. + """ + return self.prepare_send_native_tokens(params, options).send() + def prepare_send_native_tokens( self, params: List[SendNativeTokensParams], options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Send native tokens. @@ -432,6 +492,12 @@ def prepare_send_native_tokens( ) return PreparedTransaction(self, prepared) + def send_nft(self, params: List[SendNftParams], + options: Optional[TransactionOptions] = None) -> Transaction: + """Send nft. + """ + return self.prepare_send_nft(params, options).send() + def prepare_send_nft(self, params: List[SendNftParams], options: Optional[TransactionOptions] = None) -> PreparedTransaction: """Send nft. diff --git a/bindings/python/tox.ini b/bindings/python/tox.ini index 1fb974e377..a1a209d843 100644 --- a/bindings/python/tox.ini +++ b/bindings/python/tox.ini @@ -17,8 +17,17 @@ commands = pip install . pytest -[testenv:lint] -description = Run linter {basepython} +[testenv:format] +description = Run format checker {basepython} deps = autopep8 commands = autopep8 --exclude .venv --diff --recursive --aggressive --max-line-length 120 --exit-code . + +[testenv:lint-examples] +description = Run pylint {basepython} on examples +deps = + -r requirements-dev.txt + pylint +commands = + pip install . + pylint --rcfile=examples/.pylintrc examples/**/*.py \ No newline at end of file diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index bba6480056..76e6a33feb 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -19,6 +19,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 1.1.0 - 2023-MM-DD + +### Added + +- `WalletCommand::Accounts` variant to list all available accounts in a wallet; +- `addresses` now additionally prints the hex version of the address; +- `outputs`, `unspent-outputs` print a list that includes number and type of the output; +- `Account::switch` command to allow changing accounts quickly; + +### Changed + +- `WalletCommand::Mnemonic` now takes 2 optional arguments to avoid user interaction; +- `AccountCommand::Transaction` now accepts either an index or an ID; +- Use `CommandFactory` to print help programmatically; +- `print_wallet_help` changed to `WalletCli::print_help`; +- `print_account_help` changed to `AccountCli::print_help`; + ## 1.0.0 - 2023-07-27 First release of the `cli-wallet`. diff --git a/cli/src/account.rs b/cli/src/account.rs index d2be1cb14a..5dae5f790d 100644 --- a/cli/src/account.rs +++ b/cli/src/account.rs @@ -4,10 +4,10 @@ use clap::Parser; use colored::Colorize; use dialoguer::Input; -use iota_sdk::wallet::Account; +use iota_sdk::wallet::{Account, Wallet}; use crate::{ - account_completion::ACCOUNT_COMPLETION, + account_completion::AccountCompletion, account_history::AccountHistory, command::account::{ addresses_command, balance_command, burn_native_token_command, burn_nft_command, claim_command, @@ -20,28 +20,43 @@ use crate::{ voting_power_command, AccountCli, AccountCommand, }, error::Error, - helper::{bytes_from_hex_or_file, print_account_help}, + helper::bytes_from_hex_or_file, println_log_error, }; // loop on the account prompt -pub async fn account_prompt(account: Account) -> Result<(), Error> { +pub async fn account_prompt(wallet: &Wallet, mut account: Account) -> Result<(), Error> { let mut history = AccountHistory::default(); loop { - match account_prompt_internal(account.clone(), &mut history).await { - Ok(true) => { - return Ok(()); - } + match account_prompt_internal(wallet, &account, &mut history).await { + Ok(res) => match res { + AccountPromptResponse::Reprompt => (), + AccountPromptResponse::Done => { + return Ok(()); + } + AccountPromptResponse::Switch(new_account) => { + account = new_account; + } + }, Err(e) => { println_log_error!("{e}"); } - _ => {} } } } +pub enum AccountPromptResponse { + Reprompt, + Done, + Switch(Account), +} + // loop on the account prompt -pub async fn account_prompt_internal(account: Account, history: &mut AccountHistory) -> Result { +pub async fn account_prompt_internal( + wallet: &Wallet, + account: &Account, + history: &mut AccountHistory, +) -> Result { let alias = { let account = account.details().await; account.alias().clone() @@ -49,14 +64,23 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist let command: String = Input::new() .with_prompt(format!("Account \"{}\"", alias).green().to_string()) .history_with(history) - .completion_with(&ACCOUNT_COMPLETION) + .completion_with(&AccountCompletion) .interact_text()?; match command.as_str() { - "h" => print_account_help(), - "clear" => { + "h" | "help" => AccountCli::print_help()?, + "c" | "clear" => { // Clear console let _ = std::process::Command::new("clear").status(); } + "accounts" => { + // List all accounts + let accounts = wallet.get_accounts().await?; + println!("INDEX\tALIAS"); + for account in accounts { + let details = &*account.details().await; + println!("{}\t{}", details.index(), details.alias()); + } + } _ => { // Prepend `Account: ` so the parsing will be correct let command = format!("Account: {}", command.trim()); @@ -64,14 +88,14 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist Ok(account_cli) => account_cli, Err(err) => { println!("{err}"); - return Ok(false); + return Ok(AccountPromptResponse::Reprompt); } }; if let Err(err) = match account_cli.command { - AccountCommand::Addresses => addresses_command(&account).await, - AccountCommand::Balance { addresses } => balance_command(&account, addresses).await, + AccountCommand::Addresses => addresses_command(account).await, + AccountCommand::Balance { addresses } => balance_command(account, addresses).await, AccountCommand::BurnNativeToken { token_id, amount } => { - burn_native_token_command(&account, token_id, amount).await + burn_native_token_command(account, token_id, amount).await } AccountCommand::BurnNft { nft_id } => burn_nft_command(&account, nft_id).await, AccountCommand::Claim { output_id } => claim_command(&account, output_id).await, @@ -85,7 +109,7 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist foundry_metadata_file, } => { create_native_token_command( - &account, + account, circulating_supply, maximum_supply, bytes_from_hex_or_file(foundry_metadata_hex, foundry_metadata_file).await?, @@ -95,14 +119,14 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist AccountCommand::DestroyAccount { account_id } => destroy_account_command(&account, account_id).await, AccountCommand::DestroyFoundry { foundry_id } => destroy_foundry_command(&account, foundry_id).await, AccountCommand::Exit => { - return Ok(true); + return Ok(AccountPromptResponse::Done); } - AccountCommand::Faucet { address, url } => faucet_command(&account, address, url).await, + AccountCommand::Faucet { address, url } => faucet_command(account, address, url).await, AccountCommand::MeltNativeToken { token_id, amount } => { - melt_native_token_command(&account, token_id, amount).await + melt_native_token_command(account, token_id, amount).await } AccountCommand::MintNativeToken { token_id, amount } => { - mint_native_token(&account, token_id, amount).await + mint_native_token(account, token_id, amount).await } AccountCommand::MintNft { address, @@ -115,7 +139,7 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist issuer, } => { mint_nft_command( - &account, + account, address, bytes_from_hex_or_file(immutable_metadata_hex, immutable_metadata_file).await?, bytes_from_hex_or_file(metadata_hex, metadata_file).await?, @@ -125,10 +149,10 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist ) .await } - AccountCommand::NewAddress => new_address_command(&account).await, - AccountCommand::NodeInfo => node_info_command(&account).await, - AccountCommand::Output { output_id } => output_command(&account, output_id).await, - AccountCommand::Outputs => outputs_command(&account).await, + AccountCommand::NewAddress => new_address_command(account).await, + AccountCommand::NodeInfo => node_info_command(account).await, + AccountCommand::Output { output_id } => output_command(account, output_id).await, + AccountCommand::Outputs => outputs_command(account).await, AccountCommand::Send { address, amount, @@ -141,42 +165,37 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist } else { allow_micro_amount }; - send_command( - &account, - address, - amount, - return_address, - expiration, - allow_micro_amount, - ) - .await + send_command(account, address, amount, return_address, expiration, allow_micro_amount).await } AccountCommand::SendNativeToken { address, token_id, amount, gift_storage_deposit, - } => send_native_token_command(&account, address, token_id, amount, gift_storage_deposit).await, - AccountCommand::SendNft { address, nft_id } => send_nft_command(&account, address, nft_id).await, - AccountCommand::Sync => sync_command(&account).await, - AccountCommand::Transaction { transaction_id } => transaction_command(&account, &transaction_id).await, - AccountCommand::Transactions { show_details } => transactions_command(&account, show_details).await, - AccountCommand::UnspentOutputs => unspent_outputs_command(&account).await, - AccountCommand::Vote { event_id, answers } => vote_command(&account, event_id, answers).await, - AccountCommand::StopParticipating { event_id } => stop_participating_command(&account, event_id).await, + } => send_native_token_command(account, address, token_id, amount, gift_storage_deposit).await, + AccountCommand::SendNft { address, nft_id } => send_nft_command(account, address, nft_id).await, + AccountCommand::Switch { account_id } => { + return Ok(AccountPromptResponse::Switch(wallet.get_account(account_id).await?)); + } + AccountCommand::Sync => sync_command(account).await, + AccountCommand::Transaction { selector } => transaction_command(account, selector).await, + AccountCommand::Transactions { show_details } => transactions_command(account, show_details).await, + AccountCommand::UnspentOutputs => unspent_outputs_command(account).await, + AccountCommand::Vote { event_id, answers } => vote_command(account, event_id, answers).await, + AccountCommand::StopParticipating { event_id } => stop_participating_command(account, event_id).await, AccountCommand::ParticipationOverview { event_ids } => { let event_ids = (!event_ids.is_empty()).then_some(event_ids); - participation_overview_command(&account, event_ids).await + participation_overview_command(account, event_ids).await } - AccountCommand::VotingPower => voting_power_command(&account).await, - AccountCommand::IncreaseVotingPower { amount } => increase_voting_power_command(&account, amount).await, - AccountCommand::DecreaseVotingPower { amount } => decrease_voting_power_command(&account, amount).await, - AccountCommand::VotingOutput => voting_output_command(&account).await, + AccountCommand::VotingPower => voting_power_command(account).await, + AccountCommand::IncreaseVotingPower { amount } => increase_voting_power_command(account, amount).await, + AccountCommand::DecreaseVotingPower { amount } => decrease_voting_power_command(account, amount).await, + AccountCommand::VotingOutput => voting_output_command(account).await, } { println_log_error!("{err}"); } } } - Ok(false) + Ok(AccountPromptResponse::Reprompt) } diff --git a/cli/src/account_completion.rs b/cli/src/account_completion.rs index 1d931f449a..3eb6e14dd4 100644 --- a/cli/src/account_completion.rs +++ b/cli/src/account_completion.rs @@ -3,56 +3,53 @@ use dialoguer::Completion; -pub(crate) struct AccountCompletion<'a> { - options: [&'a str; 37], -} +pub(crate) struct AccountCompletion; -pub(crate) const ACCOUNT_COMPLETION: AccountCompletion = AccountCompletion { - options: [ - "addresses", - "balance", - "burn-native-token", - "burn-nft", - "claim", - "claimable-outputs", - "consolidate", - "create-account-output", - "create-native-token", - "destroy-account", - "destroy-foundry", - "exit", - "faucet", - "melt-native-token", - "mint-native-token", - "mint-nft", - "new-address", - "node-info", - "output", - "outputs", - "send", - "send-native-token", - "send-nft", - "sync", - "transaction", - "transactions", - "tx", - "txs", - "unspent-outputs", - "vote", - "stop-participating", - "participation-overview", - "voting-power", - "increase-voting-power", - "decrease-voting-power", - "voting-output", - "help", - ], -}; +pub(crate) const ACCOUNT_COMPLETION: &[&str] = &[ + "accounts", + "addresses", + "balance", + "burn-native-token", + "burn-nft", + "claim", + "claimable-outputs", + "consolidate", + "create-account-output", + "create-native-token", + "destroy-account", + "destroy-foundry", + "exit", + "faucet", + "melt-native-token", + "mint-native-token", + "mint-nft", + "new-address", + "node-info", + "output", + "outputs", + "send", + "send-native-token", + "send-nft", + "switch", + "sync", + "transaction", + "transactions", + "tx", + "txs", + "unspent-outputs", + "vote", + "stop-participating", + "participation-overview", + "voting-power", + "increase-voting-power", + "decrease-voting-power", + "voting-output", + "help", +]; -impl<'a> Completion for AccountCompletion<'a> { +impl Completion for AccountCompletion { fn get(&self, input: &str) -> Option { - let matches = self - .options + let matches = ACCOUNT_COMPLETION .iter() .filter(|option| option.starts_with(input)) .collect::>(); diff --git a/cli/src/command/account.rs b/cli/src/command/account.rs index bdad6e01fa..92218d63f0 100644 --- a/cli/src/command/account.rs +++ b/cli/src/command/account.rs @@ -3,7 +3,7 @@ use std::str::FromStr; -use clap::{Parser, Subcommand}; +use clap::{CommandFactory, Parser, Subcommand}; use iota_sdk::{ client::request_funds_from_faucet, types::{ @@ -20,7 +20,10 @@ use iota_sdk::{ }, }, wallet::{ - account::{types::Bip44Address, Account, ConsolidationParams, OutputsToClaim, TransactionOptions}, + account::{ + types::{AccountIdentifier, Bip44Address}, + Account, ConsolidationParams, OutputsToClaim, TransactionOptions, + }, CreateNativeTokenParams, MintNftParams, SendNativeTokensParams, SendNftParams, SendParams, }, U256, @@ -35,6 +38,13 @@ pub struct AccountCli { pub command: AccountCommand, } +impl AccountCli { + pub fn print_help() -> Result<(), Error> { + Self::command().bin_name("Account:").print_help()?; + Ok(()) + } +} + #[derive(Debug, Subcommand)] #[allow(clippy::large_enum_variant)] pub enum AccountCommand { @@ -183,7 +193,8 @@ pub enum AccountCommand { token_id: String, /// Amount to send, e.g. 1000000. amount: String, - /// Whether to gift the storage deposit for the output or not, e.g. ` true`. + /// Whether to gift the storage deposit for the output or not, e.g. `true`. + #[arg(value_parser = clap::builder::BoolishValueParser::new())] gift_storage_deposit: Option, }, /// Send an NFT. @@ -193,13 +204,19 @@ pub enum AccountCommand { /// NFT ID to be sent, e.g. 0xecadf10e6545aa82da4df2dfd2a496b457c8850d2cab49b7464cb273d3dffb07. nft_id: String, }, + /// Switch to a different account. + Switch { + /// The identifier (alias or index) of the account you want to switch to. + account_id: AccountIdentifier, + }, /// Synchronize the account. Sync, - /// Show the details of the transaction. + /// Show the details of a transaction. #[clap(visible_alias = "tx")] Transaction { - /// Transaction ID to be displayed e.g. 0x84fe6b1796bddc022c9bc40206f0a692f4536b02aa8c13140264e2e01a3b7e4b. - transaction_id: String, + /// Selector for transaction. + /// Either by ID (e.g. 0x84fe6b1796bddc022c9bc40206f0a692f4536b02aa8c13140264e2e01a3b7e4b) or index. + selector: TransactionSelector, }, /// List the account transactions. #[clap(visible_alias = "txs")] @@ -246,6 +263,25 @@ pub enum AccountCommand { VotingOutput, } +/// Select by transaction ID or list index +#[derive(Debug, Copy, Clone)] +pub enum TransactionSelector { + Id(TransactionId), + Index(usize), +} + +impl FromStr for TransactionSelector { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(if let Ok(index) = s.parse() { + Self::Index(index) + } else { + Self::Id(s.parse()?) + }) + } +} + /// `addresses` command pub async fn addresses_command(account: &Account) -> Result<(), Error> { let addresses = account.addresses().await?; @@ -635,10 +671,11 @@ pub async fn outputs_command(account: &Account) -> Result<(), Error> { if outputs.is_empty() { println_log_info!("No outputs found"); } else { - let output_ids: Vec = outputs.iter().map(|o| o.output_id).collect(); - println_log_info!("Outputs: {output_ids:#?}"); + println_log_info!("Outputs:"); + for (i, output_data) in outputs.into_iter().enumerate() { + println_log_info!("{}\t{}\t{}", i, &output_data.output_id, output_data.output.kind_str()); + } } - Ok(()) } @@ -747,15 +784,17 @@ pub async fn sync_command(account: &Account) -> Result<(), Error> { } /// `transaction` command -pub async fn transaction_command(account: &Account, transaction_id_str: &str) -> Result<(), Error> { - let transaction_id = TransactionId::from_str(transaction_id_str)?; - let maybe_transaction = account - .transactions() - .await - .into_iter() - .find(|tx| tx.transaction_id == transaction_id); - - if let Some(tx) = maybe_transaction { +pub async fn transaction_command(account: &Account, selector: TransactionSelector) -> Result<(), Error> { + let mut transactions = account.transactions().await; + let transaction = match selector { + TransactionSelector::Id(id) => transactions.into_iter().find(|tx| tx.transaction_id == id), + TransactionSelector::Index(index) => { + transactions.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); + transactions.into_iter().nth(index) + } + }; + + if let Some(tx) = transaction { println_log_info!("{:#?}", tx); } else { println_log_info!("No transaction found"); @@ -794,8 +833,10 @@ pub async fn unspent_outputs_command(account: &Account) -> Result<(), Error> { if outputs.is_empty() { println_log_info!("No outputs found"); } else { - let output_ids: Vec = outputs.iter().map(|o| o.output_id).collect(); - println_log_info!("Unspent outputs: {output_ids:#?}"); + println_log_info!("Unspent outputs:"); + for (i, output_data) in outputs.into_iter().enumerate() { + println_log_info!("{}\t{}\t{}", i, &output_data.output_id, output_data.output.kind_str()); + } } Ok(()) @@ -877,7 +918,14 @@ pub async fn voting_output_command(account: &Account) -> Result<(), Error> { } async fn print_address(account: &Account, address: &Bip44Address) -> Result<(), Error> { - let mut log = format!("Address {}: {}", address.key_index(), address.address()); + let mut log = format!( + "Address {}:\n {:<10}{}\n {:<10}{:?}", + address.key_index(), + "Bech32:", + address.address(), + "Hex:", + address.address().inner() + ); if *address.internal() { log = format!("{log}\nChange address"); diff --git a/cli/src/command/wallet.rs b/cli/src/command/wallet.rs index ff74bc9f76..06e33286fc 100644 --- a/cli/src/command/wallet.rs +++ b/cli/src/command/wallet.rs @@ -3,7 +3,7 @@ use std::path::Path; -use clap::{Args, Parser, Subcommand}; +use clap::{builder::BoolishValueParser, Args, CommandFactory, Parser, Subcommand}; use iota_sdk::{ client::{ constants::SHIMMER_COIN_TYPE, @@ -11,7 +11,7 @@ use iota_sdk::{ stronghold::StrongholdAdapter, utils::Password, }, - wallet::{ClientOptions, Wallet}, + wallet::{account::types::AccountIdentifier, ClientOptions, Wallet}, }; use log::LevelFilter; @@ -36,7 +36,7 @@ pub struct WalletCli { #[arg(long, value_name = "PATH", env = "STRONGHOLD_SNAPSHOT_PATH", default_value = DEFAULT_STRONGHOLD_SNAPSHOT_PATH)] pub stronghold_snapshot_path: String, /// Set the account to enter. - pub account: Option, + pub account: Option, /// Set the log level. #[arg(short, long, default_value = DEFAULT_LOG_LEVEL)] pub log_level: LevelFilter, @@ -44,8 +44,17 @@ pub struct WalletCli { pub command: Option, } +impl WalletCli { + pub fn print_help() -> Result<(), Error> { + Self::command().bin_name("wallet").print_help()?; + Ok(()) + } +} + #[derive(Debug, Clone, Subcommand)] pub enum WalletCommand { + /// List all accounts. + Accounts, /// Create a stronghold backup file. Backup { /// Path of the created stronghold backup file. @@ -61,7 +70,14 @@ pub enum WalletCommand { path: Option, }, /// Generate a random mnemonic. - Mnemonic, + Mnemonic { + // Output the mnemonic to the specified file. + #[arg(long)] + output_file_name: Option, + // Output the mnemonic to the stdout. + #[arg(long, num_args = 0..=1, default_missing_value = Some("true"), value_parser = BoolishValueParser::new())] + output_stdout: Option, + }, /// Create a new account. NewAccount { /// Account alias, next available account index if not provided. @@ -107,6 +123,20 @@ impl Default for InitParameters { } } +pub async fn accounts_command(storage_path: &Path, snapshot_path: &Path) -> Result<(), Error> { + let password = get_password("Stronghold password", false)?; + let wallet = unlock_wallet(storage_path, snapshot_path, password).await?; + let accounts = wallet.get_accounts().await?; + + println!("INDEX\tALIAS"); + for account in accounts { + let details = &*account.details().await; + println!("{}\t{}", details.index(), details.alias()); + } + + Ok(()) +} + pub async fn backup_command(storage_path: &Path, snapshot_path: &Path, backup_path: &Path) -> Result<(), Error> { let password = get_password("Stronghold password", !snapshot_path.exists())?; let wallet = unlock_wallet(storage_path, snapshot_path, password.clone()).await?; @@ -178,9 +208,8 @@ pub async fn migrate_stronghold_snapshot_v2_to_v3_command(path: Option) Ok(()) } -pub async fn mnemonic_command() -> Result<(), Error> { - generate_mnemonic().await?; - +pub async fn mnemonic_command(output_file_name: Option, output_stdout: Option) -> Result<(), Error> { + generate_mnemonic(output_file_name, output_stdout).await?; Ok(()) } @@ -188,7 +217,7 @@ pub async fn new_account_command( storage_path: &Path, snapshot_path: &Path, alias: Option, -) -> Result<(Wallet, String), Error> { +) -> Result<(Wallet, AccountIdentifier), Error> { let password = get_password("Stronghold password", !snapshot_path.exists())?; let wallet = unlock_wallet(storage_path, snapshot_path, password).await?; @@ -282,7 +311,7 @@ pub async fn unlock_wallet( Ok(maybe_wallet?) } -pub async fn add_account(wallet: &Wallet, alias: Option) -> Result { +pub async fn add_account(wallet: &Wallet, alias: Option) -> Result { let mut account_builder = wallet.create_account(); if let Some(alias) = alias { @@ -290,7 +319,7 @@ pub async fn add_account(wallet: &Wallet, alias: Option) -> Result Result, Error> { 1 => Ok(Some(accounts.swap_remove(0))), _ => { // fetch all available account aliases to display to the user - let aliases = wallet.get_account_aliases().await?; + let account_aliases = wallet.get_account_aliases().await?; let index = Select::with_theme(&ColorfulTheme::default()) .with_prompt("Select an account:") - .items(&aliases) + .items(&account_aliases) .default(0) .interact_on(&Term::stderr())?; @@ -90,18 +85,6 @@ pub async fn pick_account(wallet: &Wallet) -> Result, Error> { } } -pub fn print_wallet_help() { - if let Err(err) = WalletCli::try_parse_from(["Wallet:", "help"]) { - println!("{err}"); - } -} - -pub fn print_account_help() { - if let Err(err) = AccountCli::try_parse_from(["Account:", "help"]) { - println!("{err}"); - } -} - pub async fn bytes_from_hex_or_file(hex: Option, file: Option) -> Result>, Error> { Ok(if let Some(hex) = hex { Some(prefix_hex::decode(hex).map_err(|e| Error::Miscellaneous(e.to_string()))?) @@ -121,7 +104,7 @@ pub async fn enter_or_generate_mnemonic() -> Result { .interact_on(&Term::stderr())?; let mnemnonic = match selected_choice { - 0 => generate_mnemonic().await?, + 0 => generate_mnemonic(None, None).await?, 1 => enter_mnemonic()?, _ => unreachable!(), }; @@ -129,28 +112,45 @@ pub async fn enter_or_generate_mnemonic() -> Result { Ok(mnemnonic) } -pub async fn generate_mnemonic() -> Result { +pub async fn generate_mnemonic( + output_file_name: Option, + output_stdout: Option, +) -> Result { let mnemonic = iota_sdk::client::generate_mnemonic()?; println_log_info!("Mnemonic has been generated."); - let choices = [ - "Write it to the console only", - "Write it to a file only", - "Write it to the console and a file", - ]; - let selected_choice = Select::with_theme(&ColorfulTheme::default()) - .with_prompt("Select how to proceed with it") - .items(&choices) - .default(0) - .interact_on(&Term::stderr())?; + let selected_choice = match (&output_file_name, &output_stdout) { + // Undecided, we give the user a choice + (None, None) | (None, Some(false)) => { + let choices = [ + "Write it to the console only", + "Write it to a file only", + "Write it to the console and a file", + ]; + + Select::with_theme(&ColorfulTheme::default()) + .with_prompt("Select how to proceed with it") + .items(&choices) + .default(0) + .interact_on(&Term::stderr())? + } + // Only console + (None, Some(true)) => 0, + // Only file + (Some(_), Some(false)) | (Some(_), None) => 1, + // File and console + (Some(_), Some(true)) => 2, + }; if [0, 2].contains(&selected_choice) { println!("YOUR MNEMONIC:"); println!("{}", mnemonic.as_ref()); } if [1, 2].contains(&selected_choice) { - write_mnemonic_to_file(DEFAULT_MNEMONIC_FILE_PATH, &mnemonic).await?; - println_log_info!("Mnemonic has been written to '{DEFAULT_MNEMONIC_FILE_PATH}'."); + let file_path = output_file_name.unwrap_or(DEFAULT_MNEMONIC_FILE_PATH.to_string()); + + write_mnemonic_to_file(&file_path, &mnemonic).await?; + println_log_info!("Mnemonic has been written to '{file_path}'."); } println_log_info!("IMPORTANT:"); diff --git a/cli/src/main.rs b/cli/src/main.rs index d6b055e10d..a3f417f228 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -48,12 +48,9 @@ fn logger_init(cli: &WalletCli) -> Result<(), Error> { } async fn run(cli: WalletCli) -> Result<(), Error> { - let (wallet, account) = new_wallet(cli).await?; - - if let Some(wallet) = wallet { - if let Some(account) = account { - account::account_prompt(wallet.get_account(account).await?).await?; - } + if let (Some(wallet), Some(account)) = new_wallet(cli).await? { + let account = wallet.get_account(account).await?; + account::account_prompt(&wallet, account).await?; } Ok(()) diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index 07fe35f6c4..e8ce5bdf4f 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -3,25 +3,29 @@ use std::path::Path; -use iota_sdk::wallet::Wallet; +use iota_sdk::wallet::{account::types::AccountIdentifier, Wallet}; use crate::{ command::wallet::{ - add_account, backup_command, change_password_command, init_command, + accounts_command, add_account, backup_command, change_password_command, init_command, migrate_stronghold_snapshot_v2_to_v3_command, mnemonic_command, new_account_command, node_info_command, restore_command, set_node_url_command, sync_command, unlock_wallet, InitParameters, WalletCli, WalletCommand, }, error::Error, - helper::{get_account_alias, get_decision, get_password, pick_account, print_wallet_help}, + helper::{get_account_alias, get_decision, get_password, pick_account}, println_log_error, println_log_info, }; -pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option), Error> { +pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option), Error> { let storage_path = Path::new(&cli.wallet_db_path); let snapshot_path = Path::new(&cli.stronghold_snapshot_path); - let (wallet, account) = if let Some(command) = cli.command { + let (wallet, account_id) = if let Some(command) = cli.command { match command { + WalletCommand::Accounts => { + accounts_command(storage_path, snapshot_path).await?; + return Ok((None, None)); + } WalletCommand::Init(init_parameters) => { let wallet = init_command(storage_path, snapshot_path, init_parameters).await?; (Some(wallet), None) @@ -54,8 +58,11 @@ pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option { - mnemonic_command().await?; + WalletCommand::Mnemonic { + output_file_name, + output_stdout, + } => { + mnemonic_command(output_file_name, output_stdout).await?; return Ok((None, None)); } WalletCommand::NodeInfo => { @@ -74,8 +81,7 @@ pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option Result<(Option, Option Result<(Option, Option Result<(Option, Option), Error> { +async fn create_initial_account(wallet: Wallet) -> Result<(Option, Option), Error> { // Ask the user whether an initial account should be created. if get_decision("Create initial account?")? { let alias = get_account_alias("New account alias", &wallet).await?; - let alias = add_account(&wallet, Some(alias)).await?; - println_log_info!("Created initial account. Type `help` to see all available commands."); - Ok((Some(wallet), Some(alias))) + let account_id = add_account(&wallet, Some(alias)).await?; + println_log_info!("Created initial account.\nType `help` to see all available account commands."); + Ok((Some(wallet), Some(account_id))) } else { Ok((Some(wallet), None)) } diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index bda9c242be..e090cf81ce 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -19,6 +19,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 1.1.0 - 2023-MM-DD + +### Added + +- `Wallet::get_or_create_account` convenience method; +- `Output::kind_str()` method; +- `ConflictReason` display implementation with an explanation of the conflict; +- `TokenScheme` methods `is_simple` and `as_simple`; +- `Irc27Metadata` and `Irc30Metadata` helpers; + +### Changed + +- `StrongholdAdapterBuilder` updated to be slightly more ergonomic; +- `Wallet::{set_stronghold_password, change_stronghold_password, set_stronghold_password_clear_interval, store_mnemonic}` return an `Err` instead of `Ok` in case of a non-stronghold secret manager; + ## 1.0.3 - 2023-09-07 ### Added diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 43aa16f481..8e17f3879d 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -58,7 +58,6 @@ serde_json = { version = "1.0.105", default-features = false, features = [ anymap = { version = "0.12.1", default-features = false, optional = true } async-trait = { version = "0.1.73", default-features = false, optional = true } bs58 = { version = "0.5.0", default-features = false, optional = true } -derive_builder = { version = "0.12.0", default-features = false, optional = true } fern-logger = { version = "0.5.0", default-features = false, optional = true } futures = { version = "0.3.28", default-features = false, features = [ "thread-pool", @@ -139,6 +138,8 @@ tokio = { version = "1.32.0", default-features = false, features = [ [features] default = ["client", "wallet", "tls"] +irc_27 = ["url", "serde"] +irc_30 = ["url", "serde"] ledger_nano = ["iota-ledger-nano"] mqtt = ["std", "regex", "rumqttc", "dep:once_cell"] participation = ["storage"] @@ -160,7 +161,6 @@ std = [ "bitflags/std", "rand?/std_rng", "regex?/std", - "derive_builder?/std", "iota_stronghold?/std", "iota-crypto/std", "once_cell?/std", @@ -168,7 +168,6 @@ std = [ storage = ["iota-crypto/chacha", "dep:time", "dep:anymap", "dep:once_cell"] stronghold = [ "iota_stronghold", - "derive_builder", "iota-crypto/chacha", "dep:time", "dep:anymap", @@ -296,14 +295,14 @@ required-features = ["wallet", "storage", "stronghold"] [[example]] name = "mint_collection_nft" path = "examples/how_tos/nft_collection/01_mint_collection_nft.rs" -required-features = ["wallet", "stronghold"] +required-features = ["wallet", "stronghold", "irc_27"] # Native Tokens Examples [[example]] name = "create_native_token" path = "examples/how_tos/native_tokens/create.rs" -required-features = ["rocksdb", "stronghold"] +required-features = ["rocksdb", "stronghold", "irc_30"] [[example]] name = "destroy_foundry" @@ -602,7 +601,7 @@ required-features = ["wallet", "storage"] [[example]] name = "mint_nft" path = "examples/how_tos/nfts/mint_nft.rs" -required-features = ["wallet", "stronghold"] +required-features = ["wallet", "stronghold", "irc_27"] [[example]] name = "send_nft" diff --git a/sdk/examples/client/stronghold.rs b/sdk/examples/client/stronghold.rs index a16b52ad35..50ed797069 100644 --- a/sdk/examples/client/stronghold.rs +++ b/sdk/examples/client/stronghold.rs @@ -5,7 +5,7 @@ //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh -//! cargo run --release --all-features --example client_stronghold +//! cargo run --release --all-features --example stronghold //! ``` use iota_sdk::{ diff --git a/sdk/examples/how_tos/native_tokens/create.rs b/sdk/examples/how_tos/native_tokens/create.rs index 9050003bdf..0a3b8a88f3 100644 --- a/sdk/examples/how_tos/native_tokens/create.rs +++ b/sdk/examples/how_tos/native_tokens/create.rs @@ -12,6 +12,7 @@ //! ``` use iota_sdk::{ + types::block::output::feature::Irc30Metadata, wallet::{CreateNativeTokenParams, Result}, Wallet, U256, }; @@ -59,13 +60,16 @@ async fn main() -> Result<()> { println!("Account synced"); } + let metadata = + Irc30Metadata::new("My Native Token", "MNT", 10).with_description("A native token to test the iota-sdk."); + println!("Preparing transaction to create native token..."); let params = CreateNativeTokenParams { account_id: None, circulating_supply: U256::from(CIRCULATING_SUPPLY), maximum_supply: U256::from(MAXIMUM_SUPPLY), - foundry_metadata: None, + foundry_metadata: Some(metadata.to_bytes()), }; let transaction = account.create_native_token(params, None).await?; diff --git a/sdk/examples/how_tos/native_tokens/destroy_foundry.rs b/sdk/examples/how_tos/native_tokens/destroy_foundry.rs index 3d4a2d662e..04ff86e03b 100644 --- a/sdk/examples/how_tos/native_tokens/destroy_foundry.rs +++ b/sdk/examples/how_tos/native_tokens/destroy_foundry.rs @@ -12,7 +12,7 @@ //! cargo run --release --all-features --example destroy_foundry //! ``` -use iota_sdk::{wallet::Result, Wallet}; +use iota_sdk::{types::block::output::TokenId, wallet::Result, Wallet}; #[tokio::main] async fn main() -> Result<()> { @@ -29,17 +29,55 @@ async fn main() -> Result<()> { // May want to ensure the account is synced before sending a transaction. let balance = account.sync(None).await?; + let foundry_count = balance.foundries().len(); + println!("Foundries before destroying: {foundry_count}"); + // We try to destroy the first foundry in the account if let Some(foundry_id) = balance.foundries().first() { - let foundry_count = balance.foundries().len(); - println!("Foundries before destroying: {foundry_count}"); + let token_id = TokenId::from(*foundry_id); // Set the stronghold password wallet .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; + // Find the native tokens balance for this foundry if one exists. + let native_tokens = balance + .native_tokens() + .iter() + .find(|native_token| *native_token.token_id() == token_id); + if let Some(native_token) = native_tokens { + let output = account.get_foundry_output(token_id).await?; + // Check if all tokens are melted. + if native_token.available() != output.as_foundry().token_scheme().as_simple().circulating_supply() { + // We are not able to melt all tokens, because we don't own them or they are not unlocked. + println!("We don't own all remaining tokens, aborting foundry destruction."); + return Ok(()); + } + + println!("Melting remaining tokens.."); + // Melt all tokens so we can destroy the foundry. + let transaction = account + .melt_native_token(token_id, native_token.available(), None) + .await?; + println!("Transaction sent: {}", transaction.transaction_id); + + let block_id = account + .reissue_transaction_until_included(&transaction.transaction_id, None, None) + .await?; + println!( + "Block included: {}/block/{}", + std::env::var("EXPLORER_URL").unwrap(), + block_id + ); + + // Sync to make the foundry output available again, because it was used in the melting transaction. + account.sync(None).await?; + } + println!("Destroying foundry.."); + let transaction = account.burn(*foundry_id, None).await?; + println!("Transaction sent: {}", transaction.transaction_id); let block_id = account @@ -51,6 +89,9 @@ async fn main() -> Result<()> { block_id ); + // Resync to update the foundries list. + let balance = account.sync(None).await?; + let foundry_count = balance.foundries().len(); println!("Foundries after destroying: {foundry_count}"); } else { diff --git a/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs b/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs index f3b20b9ba4..456b444f40 100644 --- a/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs +++ b/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs @@ -17,7 +17,7 @@ use iota_sdk::{ types::block::{ address::{Bech32Address, NftAddress}, - output::NftId, + output::{feature::Irc27Metadata, NftId}, payload::transaction::TransactionId, }, wallet::{Account, MintNftParams, Result}, @@ -60,7 +60,7 @@ async fn main() -> Result<()> { let nft_mint_params = (0..NFT_COLLECTION_SIZE) .map(|index| { MintNftParams::new() - .with_immutable_metadata(get_immutable_metadata(index, issuer_nft_id).as_bytes().to_vec()) + .with_immutable_metadata(get_immutable_metadata(index).to_bytes()) // The NFT address from the NFT we minted in mint_issuer_nft example .with_issuer(issuer) }) @@ -89,21 +89,20 @@ async fn main() -> Result<()> { Ok(()) } -fn get_immutable_metadata(index: usize, issuer_nft_id: NftId) -> String { - // Note: we use `serde_json::from_str` to remove all unnecessary whitespace - serde_json::from_str::(&format!( - r#"{{ - "standard":"IRC27", - "version":"v1.0", - "type":"video/mp4", - "uri":"ipfs://wrongcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5Ywrong", - "name":"Shimmer OG NFT #{index}", - "description":"The Shimmer OG NFT was handed out 1337 times by the IOTA Foundation to celebrate the official launch of the Shimmer Network.", - "issuerName":"IOTA Foundation", - "collectionId":"{issuer_nft_id}", - "collectionName":"Shimmer OG" - }}"# - )).unwrap().to_string() +fn get_immutable_metadata(index: usize) -> Irc27Metadata { + Irc27Metadata::new( + "video/mp4", + "https://ipfs.io/ipfs/QmPoYcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5YjLgiT" + .parse() + .unwrap(), + format!("Shimmer OG NFT #{index}"), + ) + .with_description( + "The Shimmer OG NFT was handed out 1337 times by the IOTA Foundation \ + to celebrate the official launch of the Shimmer Network.", + ) + .with_issuer_name("IOTA Foundation") + .with_collection_name("Shimmer OG") } async fn wait_for_inclusion(transaction_id: &TransactionId, account: &Account) -> Result<()> { diff --git a/sdk/examples/how_tos/nfts/mint_nft.rs b/sdk/examples/how_tos/nfts/mint_nft.rs index 16ba504eae..9f828a72b7 100644 --- a/sdk/examples/how_tos/nfts/mint_nft.rs +++ b/sdk/examples/how_tos/nfts/mint_nft.rs @@ -13,7 +13,7 @@ use iota_sdk::{ types::block::output::{ - feature::{IssuerFeature, SenderFeature}, + feature::{Irc27Metadata, IssuerFeature, SenderFeature}, unlock_condition::AddressUnlockCondition, NftId, NftOutputBuilder, }, @@ -25,8 +25,6 @@ use iota_sdk::{ const NFT1_OWNER_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; // The metadata of the first minted NFT const NFT1_METADATA: &str = "some NFT metadata"; -// The immutable metadata of the first minted NFT -const NFT1_IMMUTABLE_METADATA: &str = "some NFT immutable metadata"; // The tag of the first minted NFT const NFT1_TAG: &str = "some NFT tag"; // The base coin amount we sent with the second NFT @@ -57,13 +55,22 @@ async fn main() -> Result<()> { .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; + let metadata = Irc27Metadata::new( + "video/mp4", + "https://ipfs.io/ipfs/QmPoYcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5YjLgiT" + .parse() + .unwrap(), + "Shimmer OG NFT", + ) + .with_description("The original Shimmer NFT"); + let nft_params = [MintNftParams::new() .try_with_address(NFT1_OWNER_ADDRESS)? .try_with_sender(sender_address)? .with_metadata(NFT1_METADATA.as_bytes().to_vec()) .with_tag(NFT1_TAG.as_bytes().to_vec()) .try_with_issuer(sender_address)? - .with_immutable_metadata(NFT1_IMMUTABLE_METADATA.as_bytes().to_vec())]; + .with_immutable_metadata(metadata.to_bytes())]; let transaction = account.mint_nfts(nft_params, None).await?; println!("Transaction sent: {}", transaction.transaction_id); diff --git a/sdk/examples/wallet/accounts.rs b/sdk/examples/wallet/accounts.rs index b7a432175a..21d9541bbe 100644 --- a/sdk/examples/wallet/accounts.rs +++ b/sdk/examples/wallet/accounts.rs @@ -17,7 +17,7 @@ use iota_sdk::{ secret::{mnemonic::MnemonicSecretManager, SecretManager}, utils::request_funds_from_faucet, }, - wallet::{Account, ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Result, Wallet}, }; // The number of addresses to generate @@ -41,11 +41,11 @@ async fn main() -> Result<()> { .await?; // Get or create first account - let _ = get_or_create_account(&wallet, "Alice").await?; + let _ = wallet.get_or_create_account("Alice").await?; // Get or create second account let alias2 = "Bob"; - let account2 = get_or_create_account(&wallet, alias2).await?; + let account2 = wallet.get_or_create_account(alias2).await?; let accounts = wallet.get_accounts().await?; println!("WALLET ACCOUNTS:"); @@ -94,12 +94,3 @@ async fn main() -> Result<()> { Ok(()) } - -async fn get_or_create_account(wallet: &Wallet, alias: &str) -> Result { - Ok(if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }) -} diff --git a/sdk/examples/wallet/background_syncing.rs b/sdk/examples/wallet/background_syncing.rs index 70e5ea3499..eda91075d0 100644 --- a/sdk/examples/wallet/background_syncing.rs +++ b/sdk/examples/wallet/background_syncing.rs @@ -34,13 +34,7 @@ async fn main() -> Result<()> { .await?; // Get or create new account - let alias = "Alice"; - let account = if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }; + let account = wallet.get_or_create_account("Alice").await?; let addresses = account.addresses().await?; // Manually sync to ensure we have the correct funds to start with diff --git a/sdk/examples/wallet/events.rs b/sdk/examples/wallet/events.rs index 476584829f..c883e0548a 100644 --- a/sdk/examples/wallet/events.rs +++ b/sdk/examples/wallet/events.rs @@ -50,13 +50,7 @@ async fn main() -> Result<()> { .await; // Get or create an account - let alias = "Alice"; - let account = if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }; + let account = wallet.get_or_create_account("Alice").await?; let balance = account.sync(None).await?; println!("Balance BEFORE:\n{:#?}", balance.base_coin()); diff --git a/sdk/examples/wallet/ledger_nano.rs b/sdk/examples/wallet/ledger_nano.rs index a96c79e295..c028310d66 100644 --- a/sdk/examples/wallet/ledger_nano.rs +++ b/sdk/examples/wallet/ledger_nano.rs @@ -49,12 +49,7 @@ async fn main() -> Result<()> { println!("{:?}", wallet.get_ledger_nano_status().await?); // Get or create a new account - let account = if let Ok(account) = wallet.get_account(ACCOUNT_ALIAS).await { - account - } else { - println!("Creating account '{ACCOUNT_ALIAS}'"); - wallet.create_account().with_alias(ACCOUNT_ALIAS).finish().await? - }; + let account = wallet.get_or_create_account(ACCOUNT_ALIAS).await?; println!("Generating {NUM_ADDRESSES_TO_GENERATE} addresses..."); let now = tokio::time::Instant::now(); diff --git a/sdk/examples/wallet/logger.rs b/sdk/examples/wallet/logger.rs index 775a89c4c0..4cb69e54ca 100644 --- a/sdk/examples/wallet/logger.rs +++ b/sdk/examples/wallet/logger.rs @@ -46,13 +46,7 @@ async fn main() -> Result<()> { .await?; // Get or create a new account - let alias = "Alice"; - let account = if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }; + let account = wallet.get_or_create_account("Alice").await?; println!("Generating {NUM_ADDRESSES_TO_GENERATE} addresses..."); let _ = account diff --git a/sdk/examples/wallet/spammer.rs b/sdk/examples/wallet/spammer.rs index 0cde91efe9..8624608603 100644 --- a/sdk/examples/wallet/spammer.rs +++ b/sdk/examples/wallet/spammer.rs @@ -45,7 +45,7 @@ async fn main() -> Result<()> { .with_coin_type(SHIMMER_COIN_TYPE) .finish() .await?; - let account = get_or_create_account(&wallet, ACCOUNT_ALIAS).await?; + let account = wallet.get_or_create_account(ACCOUNT_ALIAS).await?; let recv_address = *account.addresses().await?[0].address(); println!("Recv address: {}", recv_address); @@ -141,15 +141,6 @@ async fn main() -> Result<()> { Ok(()) } -async fn get_or_create_account(wallet: &Wallet, alias: &str) -> Result { - Ok(if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }) -} - async fn ensure_enough_funds(account: &Account, bech32_address: &Bech32Address) -> Result<()> { let balance = account.sync(None).await?; let available_funds = balance.base_coin().available(); diff --git a/sdk/examples/wallet/split_funds.rs b/sdk/examples/wallet/split_funds.rs index 260dfef249..74a8c8a2a0 100644 --- a/sdk/examples/wallet/split_funds.rs +++ b/sdk/examples/wallet/split_funds.rs @@ -43,7 +43,7 @@ async fn main() -> Result<()> { .await?; // Get account or create a new one - let account = create_account(&wallet, "Alice").await?; + let account = wallet.get_or_create_account("Alice").await?; let _ = ensure_enough_addresses(&account, ADDRESSES_TO_SPLIT_FUNDS).await?; @@ -109,15 +109,6 @@ async fn main() -> Result<()> { Ok(()) } -async fn create_account(wallet: &Wallet, alias: &str) -> Result { - Ok(if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }) -} - async fn sync_print_balance(account: &Account) -> Result<()> { let alias = account.alias().await; let now = tokio::time::Instant::now(); diff --git a/sdk/examples/wallet/storage.rs b/sdk/examples/wallet/storage.rs index d8203393b7..0fdf7c713b 100644 --- a/sdk/examples/wallet/storage.rs +++ b/sdk/examples/wallet/storage.rs @@ -37,7 +37,7 @@ async fn main() -> Result<()> { .await?; // Get account or create a new one - let account = get_or_create_account(&wallet, "Alice").await?; + let account = wallet.get_or_create_account("Alice").await?; let addresses = generate_max_addresses(&account, MAX_ADDRESSES_TO_GENERATE).await?; let bech32_addresses = addresses @@ -57,15 +57,6 @@ async fn main() -> Result<()> { Ok(()) } -async fn get_or_create_account(wallet: &Wallet, alias: &str) -> Result { - Ok(if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }) -} - async fn generate_max_addresses(account: &Account, max: usize) -> Result> { let alias = account.alias().await; if account.addresses().await?.len() < max { diff --git a/sdk/examples/wallet/wallet.rs b/sdk/examples/wallet/wallet.rs index 3c5fe3b802..9c2562b8a7 100644 --- a/sdk/examples/wallet/wallet.rs +++ b/sdk/examples/wallet/wallet.rs @@ -39,7 +39,7 @@ async fn main() -> Result<()> { let wallet = create_wallet().await?; - let account = get_or_create_account(&wallet, "Alice").await?; + let account = wallet.get_or_create_account("Alice").await?; print_accounts(&wallet).await?; generate_addresses(&account, MAX_ADDRESSES_TO_GENERATE).await?; @@ -72,16 +72,6 @@ async fn create_wallet() -> Result { .await } -async fn get_or_create_account(wallet: &Wallet, alias: &str) -> Result { - let account = if let Ok(account) = wallet.get_account(alias).await { - account - } else { - println!("Creating account '{alias}'"); - wallet.create_account().with_alias(alias).finish().await? - }; - Ok(account) -} - async fn print_accounts(wallet: &Wallet) -> Result<()> { let accounts = wallet.get_accounts().await?; println!("Accounts:"); diff --git a/sdk/src/client/api/block_builder/input_selection/transition.rs b/sdk/src/client/api/block_builder/input_selection/transition.rs index bc9fbe8dc3..dd1ae948a1 100644 --- a/sdk/src/client/api/block_builder/input_selection/transition.rs +++ b/sdk/src/client/api/block_builder/input_selection/transition.rs @@ -54,7 +54,7 @@ impl InputSelection { } // Remove potential sender feature because it will not be needed anymore as it only needs to be verified once. - let features = input.features().iter().cloned().filter(|feature| !feature.is_sender()); + let features = input.features().iter().filter(|feature| !feature.is_sender()).cloned(); let mut builder = AccountOutputBuilder::from(input) .with_account_id(account_id) @@ -101,7 +101,7 @@ impl InputSelection { } // Remove potential sender feature because it will not be needed anymore as it only needs to be verified once. - let features = input.features().iter().cloned().filter(|feature| !feature.is_sender()); + let features = input.features().iter().filter(|feature| !feature.is_sender()).cloned(); let output = NftOutputBuilder::from(input) .with_nft_id(nft_id) diff --git a/sdk/src/client/node_manager/syncing.rs b/sdk/src/client/node_manager/syncing.rs index e0b23cab16..35cccf6bd7 100644 --- a/sdk/src/client/node_manager/syncing.rs +++ b/sdk/src/client/node_manager/syncing.rs @@ -89,7 +89,7 @@ impl ClientInner { } } } else { - log::debug!("{} is not healthy: {:?}", node.url, info); + log::warn!("{} is not healthy: {:?}", node.url, info); } } Err(err) => { diff --git a/sdk/src/client/stronghold/mod.rs b/sdk/src/client/stronghold/mod.rs index 19d9b269b8..7cd0552567 100644 --- a/sdk/src/client/stronghold/mod.rs +++ b/sdk/src/client/stronghold/mod.rs @@ -52,13 +52,13 @@ mod migration; mod secret; mod storage; +use alloc::sync::Weak; use std::{ path::{Path, PathBuf}, sync::Arc, time::Duration, }; -use derive_builder::Builder; use iota_stronghold::{KeyProvider, SnapshotPath, Stronghold}; use log::{debug, error, warn}; use tokio::{ @@ -74,11 +74,9 @@ use super::{storage::StorageAdapter, utils::Password}; /// A wrapper on [Stronghold]. /// /// See the [module-level documentation](self) for more details. -#[derive(Builder, Debug)] -#[builder(pattern = "owned", build_fn(skip))] +#[derive(Debug)] pub struct StrongholdAdapter { /// A stronghold instance. - #[builder(field(type = "Option"))] stronghold: Arc>, /// A key to open the Stronghold vault. @@ -88,8 +86,6 @@ pub struct StrongholdAdapter { /// derive a key from it. /// /// [`password()`]: self::StrongholdAdapterBuilder::password() - #[builder(setter(custom))] - #[builder(field(type = "Option"))] key_provider: Arc>>, /// An interval of time, after which `key` will be cleared from the memory. @@ -98,18 +94,12 @@ pub struct StrongholdAdapter { /// timer will be spawned in the background to clear ([zeroize]) the key after `timeout`. /// /// If a [`StrongholdAdapter`] is destroyed (dropped), then the timer will stop too. - #[builder(setter(strip_option))] timeout: Option, /// A handle to the timeout task. - /// - /// Note that this field doesn't actually have a custom setter; `setter(custom)` is only for skipping the setter - /// generation. - #[builder(setter(custom))] - timeout_task: Arc>>>, + timeout_task: Arc>>, /// The path to a Stronghold snapshot file. - #[builder(setter(skip))] pub(crate) snapshot_path: PathBuf, } @@ -146,15 +136,34 @@ fn check_or_create_snapshot( Ok(()) } +#[derive(Default, Debug)] +pub struct StrongholdAdapterBuilder { + stronghold: Option, + key_provider: Option, + timeout: Option, +} + /// Extra / custom builder method implementations. impl StrongholdAdapterBuilder { - /// Use an user-input password string to derive a key to use Stronghold. - pub fn password(mut self, password: impl Into) -> Self { - let password = password.into(); + pub fn stronghold(mut self, stronghold: impl Into>) -> Self { + self.stronghold = stronghold.into(); + self + } + + pub fn key_provider(mut self, key_provider: impl Into>) -> Self { + self.key_provider = key_provider.into(); + self + } - // Note that derive_builder always adds another layer of Option. - self.key_provider = Some(self::common::key_provider_from_password(password)); + pub fn timeout(mut self, timeout: impl Into>) -> Self { + self.timeout = timeout.into(); + self + } + /// Use an user-input password string to derive a key to use Stronghold. + pub fn password(mut self, password: impl Into) -> Self { + self.key_provider + .replace(self::common::key_provider_from_password(password.into())); self } @@ -172,7 +181,7 @@ impl StrongholdAdapterBuilder { /// /// [`password()`]: Self::password() /// [`timeout()`]: Self::timeout() - pub fn build>(mut self, snapshot_path: P) -> Result { + pub fn build>(self, snapshot_path: P) -> Result { // In any case, Stronghold - as a necessary component - needs to be present at this point. let stronghold = self.stronghold.unwrap_or_default(); @@ -186,39 +195,25 @@ impl StrongholdAdapterBuilder { let has_key_provider = self.key_provider.is_some(); let key_provider = Arc::new(Mutex::new(self.key_provider)); let stronghold = Arc::new(Mutex::new(stronghold)); + let timeout_task = Arc::new(Mutex::new(None)); // If both `key` and `timeout` are set, then we spawn the task and keep its join handle. - if let (true, Some(Some(timeout))) = (has_key_provider, self.timeout) { - let timeout_task = Arc::new(Mutex::new(None)); - - // The key clearing task, with the data it owns. - let task_self = timeout_task.clone(); - let key_provider = key_provider.clone(); - - // To keep this function synchronous (`fn`), we spawn a task that spawns the key clearing task here. It'll - // however panic when this function is not in a Tokio runtime context (usually in an `async fn`), albeit it - // itself is a `fn`. There is also a small delay from the return of this function to the task actually being - // spawned and set in the `struct`. - let stronghold_clone = stronghold.clone(); - tokio::spawn(async move { - *task_self.lock().await = Some(tokio::spawn(task_key_clear( - task_self.clone(), // LHS moves task_self - stronghold_clone, - key_provider, - timeout, - ))); - }); - - // Keep the task handle in the builder; the code below checks this. - self.timeout_task = Some(timeout_task); + if let (true, Some(timeout)) = (has_key_provider, self.timeout) { + let weak = Arc::downgrade(&timeout_task); + *timeout_task.try_lock().unwrap() = Some(tokio::spawn(task_key_clear( + weak, + stronghold.clone(), + key_provider.clone(), + timeout, + ))); } // Create the adapter as per configuration and return it. Ok(StrongholdAdapter { stronghold, key_provider, - timeout: self.timeout.unwrap_or(None), - timeout_task: self.timeout_task.unwrap_or_else(|| Arc::new(Mutex::new(None))), + timeout: self.timeout, + timeout_task, snapshot_path: snapshot_path.as_ref().to_path_buf(), }) } @@ -269,12 +264,10 @@ impl StrongholdAdapter { timeout_task.abort(); } - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -329,12 +322,10 @@ impl StrongholdAdapter { // Recover: restart the key clearing task if let Some(timeout) = self.timeout { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -372,12 +363,10 @@ impl StrongholdAdapter { // Recover: restart key clearing task if let Some(timeout) = self.timeout { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -393,12 +382,10 @@ impl StrongholdAdapter { // Restart the key clearing task. if let Some(timeout) = self.timeout { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -453,12 +440,10 @@ impl StrongholdAdapter { // If a new timeout is set and the key is still in the memory, spawn a new task; otherwise we do nothing. if let (Some(_), Some(timeout)) = (self.key_provider.lock().await.as_ref(), self.timeout) { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -540,23 +525,33 @@ impl StrongholdAdapter { } } +type TaskHandle = JoinHandle<()>; + /// The asynchronous key clearing task purging `key` after `timeout` spent in Tokio. async fn task_key_clear( - task_self: Arc>>>, + task: Weak>>, stronghold: Arc>, key_provider: Arc>>, timeout: Duration, ) { tokio::time::sleep(timeout).await; - debug!("StrongholdAdapter is purging the key"); - key_provider.lock().await.take(); + // If the weak pointer cannot upgrade, that means the secret manager has been dropped, + // so we can just exit. + if let Some(task) = task.upgrade() { + // Take the join handle, but hold the lock until we're done + let mut lock = task.lock().await; + lock.take(); - // TODO handle error - stronghold.lock().await.clear().unwrap(); + debug!("StrongholdAdapter is purging the key"); + key_provider.lock().await.take(); - // Take self, but do nothing (we're exiting anyways). - task_self.lock().await.take(); + if let Err(e) = stronghold.lock().await.clear() { + log::error!("Failed to clear stronghold keys: {e}"); + } + + drop(lock); + } } #[cfg(test)] diff --git a/sdk/src/types/block/address/bech32.rs b/sdk/src/types/block/address/bech32.rs index 309e64cd7c..d23109060d 100644 --- a/sdk/src/types/block/address/bech32.rs +++ b/sdk/src/types/block/address/bech32.rs @@ -137,7 +137,7 @@ impl + Send> ConvertTo for T { } /// An address and its network type. -#[derive(Copy, Clone, Eq, PartialEq, Hash, AsRef, Deref)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, AsRef, Deref, Ord, PartialOrd)] pub struct Bech32Address { pub(crate) hrp: Hrp, #[as_ref] diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index aa768e0e9d..c8410c8edc 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -1,7 +1,7 @@ // Copyright 2021-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{boxed::Box, vec::Vec}; +use alloc::{boxed::Box, string::String, vec::Vec}; use core::{ops::RangeInclusive, str::FromStr}; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix}; @@ -19,6 +19,29 @@ pub struct MetadataFeature( pub(crate) BoxedSlicePrefix, ); +macro_rules! impl_from_vec { + ($type:ty) => { + impl TryFrom<$type> for MetadataFeature { + type Error = Error; + + fn try_from(value: $type) -> Result { + Vec::::from(value).try_into() + } + } + }; +} +impl_from_vec!(&str); +impl_from_vec!(String); +impl_from_vec!(&[u8]); + +impl TryFrom<[u8; N]> for MetadataFeature { + type Error = Error; + + fn try_from(value: [u8; N]) -> Result { + value.to_vec().try_into() + } +} + impl TryFrom> for MetadataFeature { type Error = Error; @@ -74,6 +97,334 @@ impl core::fmt::Debug for MetadataFeature { } } +#[cfg(feature = "irc_27")] +pub(crate) mod irc_27 { + use alloc::{ + borrow::ToOwned, + collections::{BTreeMap, BTreeSet}, + string::String, + }; + + use getset::Getters; + use serde::{Deserialize, Serialize}; + use url::Url; + + use super::*; + use crate::types::block::address::Bech32Address; + + /// The IRC27 NFT standard schema. + #[derive(Clone, Debug, Serialize, Deserialize, Getters, PartialEq)] + #[serde(rename_all = "camelCase")] + #[serde(tag = "standard", rename = "IRC27")] + #[getset(get = "pub")] + pub struct Irc27Metadata { + version: String, + /// The media type (MIME) of the asset. + /// + /// ## Examples + /// - Image files: `image/jpeg`, `image/png`, `image/gif`, etc. + /// - Video files: `video/x-msvideo` (avi), `video/mp4`, `video/mpeg`, etc. + /// - Audio files: `audio/mpeg`, `audio/wav`, etc. + /// - 3D Assets: `model/obj`, `model/u3d`, etc. + /// - Documents: `application/pdf`, `text/plain`, etc. + #[serde(rename = "type")] + media_type: String, + /// URL pointing to the NFT file location. + uri: Url, + /// The human-readable name of the native token. + name: String, + /// The human-readable collection name of the native token. + #[serde(default, skip_serializing_if = "Option::is_none")] + collection_name: Option, + /// Royalty payment addresses mapped to the payout percentage. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + royalties: BTreeMap, + /// The human-readable name of the native token creator. + #[serde(default, skip_serializing_if = "Option::is_none")] + issuer_name: Option, + /// The human-readable description of the token. + #[serde(default, skip_serializing_if = "Option::is_none")] + description: Option, + /// Additional attributes which follow [OpenSea Metadata standards](https://docs.opensea.io/docs/metadata-standards). + #[serde(default, skip_serializing_if = "BTreeSet::is_empty")] + attributes: BTreeSet, + } + + impl Irc27Metadata { + pub fn new(media_type: impl Into, uri: Url, name: impl Into) -> Self { + Self { + version: "v1.0".to_owned(), + media_type: media_type.into(), + uri, + name: name.into(), + collection_name: Default::default(), + royalties: Default::default(), + issuer_name: Default::default(), + description: Default::default(), + attributes: Default::default(), + } + } + + pub fn with_collection_name(mut self, collection_name: impl Into) -> Self { + self.collection_name.replace(collection_name.into()); + self + } + + pub fn add_royalty(mut self, address: Bech32Address, percentage: f64) -> Self { + self.royalties.insert(address, percentage); + self + } + + pub fn with_royalties(mut self, royalties: BTreeMap) -> Self { + self.royalties = royalties; + self + } + + pub fn with_issuer_name(mut self, issuer_name: impl Into) -> Self { + self.issuer_name.replace(issuer_name.into()); + self + } + + pub fn with_description(mut self, description: impl Into) -> Self { + self.description.replace(description.into()); + self + } + + pub fn add_attribute(mut self, attribute: Attribute) -> Self { + self.attributes.insert(attribute); + self + } + + pub fn with_attributes(mut self, attributes: BTreeSet) -> Self { + self.attributes = attributes; + self + } + + pub fn to_bytes(&self) -> Vec { + // Unwrap: Safe because this struct is known to be valid + serde_json::to_string(self).unwrap().into_bytes() + } + } + + impl TryFrom for MetadataFeature { + type Error = Error; + fn try_from(value: Irc27Metadata) -> Result { + Self::new(value.to_bytes()) + } + } + + impl From for Vec { + fn from(value: Irc27Metadata) -> Self { + value.to_bytes() + } + } + + #[derive(Clone, Debug, Serialize, Deserialize, Getters, PartialEq, Eq)] + #[getset(get = "pub")] + pub struct Attribute { + trait_type: String, + value: serde_json::Value, + #[serde(default, skip_serializing_if = "Option::is_none")] + display_type: Option, + } + + impl Attribute { + pub fn new(trait_type: impl Into, value: impl Into) -> Self { + Self { + trait_type: trait_type.into(), + display_type: None, + value: value.into(), + } + } + + pub fn with_display_type(mut self, display_type: impl Into) -> Self { + self.display_type.replace(display_type.into()); + self + } + } + + impl Ord for Attribute { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.trait_type.cmp(&other.trait_type) + } + } + impl PartialOrd for Attribute { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + impl core::hash::Hash for Attribute { + fn hash(&self, state: &mut H) { + self.trait_type.hash(state); + } + } + + #[cfg(test)] + mod test { + use super::*; + use crate::types::block::{address::ToBech32Ext, rand::address::rand_address}; + + #[test] + fn serialization() { + let metadata = Irc27Metadata::new( + "image/jpeg", + "https://mywebsite.com/my-nft-files-1.jpeg".parse().unwrap(), + "My NFT #0001", + ) + .with_collection_name("My Collection of Art") + .add_royalty(rand_address().to_bech32_unchecked("iota1"), 0.025) + .add_royalty(rand_address().to_bech32_unchecked("iota1"), 0.025) + .with_issuer_name("My Artist Name") + .with_description("A little information about my NFT collection") + .add_attribute(Attribute::new("Background", "Purple")) + .add_attribute(Attribute::new("Element", "Water")) + .add_attribute(Attribute::new("Attack", 150)) + .add_attribute(Attribute::new("Health", 500)); + let json = serde_json::json!( + { + "standard": "IRC27", + "version": metadata.version(), + "type": metadata.media_type(), + "uri": metadata.uri(), + "name": metadata.name(), + "collectionName": metadata.collection_name(), + "royalties": metadata.royalties(), + "issuerName": metadata.issuer_name(), + "description": metadata.description(), + "attributes": metadata.attributes() + } + ); + let metadata_deser = serde_json::from_value::(json.clone()).unwrap(); + + assert_eq!(metadata, metadata_deser); + assert_eq!(json, serde_json::to_value(metadata).unwrap()) + } + } +} + +#[cfg(feature = "irc_30")] +pub(crate) mod irc_30 { + use alloc::string::String; + + use getset::Getters; + use serde::{Deserialize, Serialize}; + use url::Url; + + use super::*; + + /// The IRC30 NFT standard schema. + #[derive(Clone, Debug, Serialize, Deserialize, Getters, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + #[serde(tag = "standard", rename = "IRC30")] + #[getset(get = "pub")] + pub struct Irc30Metadata { + /// The human-readable name of the native token. + name: String, + /// The symbol/ticker of the token. + symbol: String, + /// Number of decimals the token uses (divide the token amount by 10^decimals to get its user representation). + decimals: u32, + /// The human-readable description of the token. + #[serde(default, skip_serializing_if = "Option::is_none")] + description: Option, + /// URL pointing to more resources about the token. + #[serde(default, skip_serializing_if = "Option::is_none")] + url: Option, + /// URL pointing to an image resource of the token logo. + #[serde(default, skip_serializing_if = "Option::is_none")] + logo_url: Option, + /// The svg logo of the token encoded as a byte string. + #[serde(default, skip_serializing_if = "Option::is_none")] + logo: Option, + } + + impl Irc30Metadata { + pub fn new(name: impl Into, symbol: impl Into, decimals: u32) -> Self { + Self { + name: name.into(), + symbol: symbol.into(), + decimals, + description: Default::default(), + url: Default::default(), + logo_url: Default::default(), + logo: Default::default(), + } + } + + pub fn with_description(mut self, description: impl Into) -> Self { + self.description.replace(description.into()); + self + } + + pub fn with_url(mut self, url: Url) -> Self { + self.url.replace(url); + self + } + + pub fn with_logo_url(mut self, logo_url: Url) -> Self { + self.logo_url.replace(logo_url); + self + } + + pub fn with_logo(mut self, logo: impl Into) -> Self { + self.logo.replace(logo.into()); + self + } + + pub fn to_bytes(&self) -> Vec { + // Unwrap: Safe because this struct is known to be valid + serde_json::to_string(self).unwrap().into_bytes() + } + } + + impl TryFrom for MetadataFeature { + type Error = Error; + fn try_from(value: Irc30Metadata) -> Result { + Self::new(value.to_bytes()) + } + } + + impl From for Vec { + fn from(value: Irc30Metadata) -> Self { + value.to_bytes() + } + } + + #[cfg(test)] + mod test { + use super::*; + + #[test] + fn serialization() { + let description = "FooCoin is the utility and governance token of FooLand, \ + a revolutionary protocol in the play-to-earn crypto gaming field."; + let metadata = Irc30Metadata::new("FooCoin", "FOO", 3) + .with_description(description) + .with_url("https://foocoin.io/".parse().unwrap()) + .with_logo_url( + "https://ipfs.io/ipfs/QmR36VFfo1hH2RAwVs4zVJ5btkopGip5cW7ydY4jUQBrkR" + .parse() + .unwrap(), + ); + let json = serde_json::json!( + { + "standard": "IRC30", + "name": metadata.name(), + "description": metadata.description(), + "decimals": metadata.decimals(), + "symbol": metadata.symbol(), + "url": metadata.url(), + "logoUrl": metadata.logo_url() + } + ); + let metadata_deser = serde_json::from_value::(json.clone()).unwrap(); + + assert_eq!(metadata, metadata_deser); + assert_eq!(json, serde_json::to_value(metadata).unwrap()) + } + } +} + #[cfg(feature = "serde")] pub(crate) mod dto { use alloc::borrow::Cow; diff --git a/sdk/src/types/block/output/feature/mod.rs b/sdk/src/types/block/output/feature/mod.rs index 6f766fa933..1f5639c57c 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -15,6 +15,10 @@ use derive_more::{Deref, From}; use iterator_sorted::is_unique_sorted; use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; +#[cfg(feature = "irc_27")] +pub use self::metadata::irc_27::{Attribute, Irc27Metadata}; +#[cfg(feature = "irc_30")] +pub use self::metadata::irc_30::Irc30Metadata; pub use self::{ block_issuer::BlockIssuerFeature, issuer::IssuerFeature, metadata::MetadataFeature, sender::SenderFeature, staking::StakingFeature, tag::TagFeature, diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 528d958a8a..1031e17f77 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -160,6 +160,17 @@ impl Output { } } + /// Returns the output kind of an [`Output`] as a string. + pub fn kind_str(&self) -> &str { + match self { + Self::Basic(_) => "Basic", + Self::Account(_) => "Account", + Self::Foundry(_) => "Foundry", + Self::Nft(_) => "Nft", + Self::Delegation(_) => "Delegation", + } + } + /// Returns the amount of an [`Output`]. pub fn amount(&self) -> u64 { match self { diff --git a/sdk/src/types/block/output/token_scheme/mod.rs b/sdk/src/types/block/output/token_scheme/mod.rs index 186a866749..3ae41b98d4 100644 --- a/sdk/src/types/block/output/token_scheme/mod.rs +++ b/sdk/src/types/block/output/token_scheme/mod.rs @@ -32,4 +32,16 @@ impl TokenScheme { Self::Simple(_) => SimpleTokenScheme::KIND, } } + + /// Checks whether the token scheme is a [`SimpleTokenScheme`]. + pub fn is_simple(&self) -> bool { + matches!(self, Self::Simple(_)) + } + + /// Gets the token scheme as an actual [`SimpleTokenScheme`]. + /// PANIC: do not call on a non-simple token scheme. + pub fn as_simple(&self) -> &SimpleTokenScheme { + let Self::Simple(scheme) = self; + scheme + } } diff --git a/sdk/src/types/block/semantic.rs b/sdk/src/types/block/semantic.rs index fc5a48444e..7097599493 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic.rs @@ -69,6 +69,39 @@ pub enum TransactionFailureReason { SemanticValidationFailed = 255, } +// TODO bring back +// impl fmt::Display for TransactionFailureReason { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// match self { +// Self::None => write!(f, "The block has no conflict"), +// Self::InputUtxoAlreadySpent => write!(f, "The referenced UTXO was already spent"), +// Self::InputUtxoAlreadySpentInThisMilestone => write!( +// f, +// "The referenced UTXO was already spent while confirming this milestone" +// ), +// Self::InputUtxoNotFound => write!(f, "The referenced UTXO cannot be found"), +// Self::CreatedConsumedAmountMismatch => { +// write!(f, "The sum of the inputs and output values does not match") +// } +// Self::InvalidSignature => write!(f, "The unlock block signature is invalid"), +// Self::TimelockNotExpired => write!(f, "The configured timelock is not yet expired"), +// Self::InvalidNativeTokens => write!(f, "The native tokens are invalid"), +// Self::StorageDepositReturnUnfulfilled => write!( +// f, +// "The return amount in a transaction is not fulfilled by the output side" +// ), +// Self::InvalidUnlock => write!(f, "The input unlock is invalid"), +// Self::InputsCommitmentsMismatch => write!(f, "The inputs commitment is invalid"), +// Self::UnverifiedSender => write!( +// f, +// " The output contains a Sender with an ident (address) which is not unlocked" +// ), +// Self::InvalidChainStateTransition => write!(f, "The chain state transition is invalid"), +// Self::SemanticValidationFailed => write!(f, "The semantic validation failed"), +// } +// } +// } + impl TryFrom for TransactionFailureReason { type Error = Error; diff --git a/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs b/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs index bfb616aa1a..6449bdb02c 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs +++ b/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs @@ -12,7 +12,7 @@ use crate::{ pub(crate) mod melt_native_token; impl Account { - /// A generic `burn()` function that can be used to burn native tokens, nfts, foundries and accounts. + /// A generic function that can be used to burn native tokens, nfts, foundries and accounts. /// /// Note that burning **native tokens** 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 diff --git a/sdk/src/wallet/account/types/mod.rs b/sdk/src/wallet/account/types/mod.rs index 10cfb081e9..fa22cd1b14 100644 --- a/sdk/src/wallet/account/types/mod.rs +++ b/sdk/src/wallet/account/types/mod.rs @@ -309,7 +309,7 @@ impl<'de> Deserialize<'de> for AccountIdentifier { None => { let alias_or_index_str = v .as_str() - .ok_or_else(|| D::Error::custom("accountIdentifier is no number or string"))?; + .ok_or_else(|| D::Error::custom("account identifier is not a number or string"))?; Self::from(alias_or_index_str) } }) @@ -341,3 +341,12 @@ impl From for AccountIdentifier { Self::Index(value) } } + +impl core::fmt::Display for AccountIdentifier { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Alias(alias) => alias.fmt(f), + Self::Index(index) => index.fmt(f), + } + } +} diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index e48de98f89..837408de50 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -97,11 +97,11 @@ where /// Get all account aliases pub async fn get_account_aliases(&self) -> crate::wallet::Result> { let accounts = self.accounts.read().await; - let mut aliases = Vec::with_capacity(accounts.len()); + let mut account_aliases = Vec::with_capacity(accounts.len()); for handle in accounts.iter() { - aliases.push(handle.details().await.alias().clone()); + account_aliases.push(handle.details().await.alias().clone()); } - Ok(aliases) + Ok(account_aliases) } /// Removes the latest account (account with the largest account index). diff --git a/sdk/src/wallet/core/operations/get_account.rs b/sdk/src/wallet/core/operations/get_account.rs index 16a9bd7d67..463207333e 100644 --- a/sdk/src/wallet/core/operations/get_account.rs +++ b/sdk/src/wallet/core/operations/get_account.rs @@ -44,3 +44,16 @@ impl Wallet { )?)) } } + +impl Wallet +where + crate::wallet::Error: From, +{ + pub async fn get_or_create_account(&self, alias: impl Into + Send) -> crate::wallet::Result> { + let alias = alias.into(); + match self.get_account(&alias).await { + Err(crate::wallet::Error::AccountNotFound(_)) => self.create_account().with_alias(alias).finish().await, + res => res, + } + } +} diff --git a/sdk/src/wallet/core/operations/stronghold.rs b/sdk/src/wallet/core/operations/stronghold.rs index b3d997fc03..d3c72db45c 100644 --- a/sdk/src/wallet/core/operations/stronghold.rs +++ b/sdk/src/wallet/core/operations/stronghold.rs @@ -17,8 +17,10 @@ impl Wallet { if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.set_password(password).await?; + Ok(()) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } - Ok(()) } /// Change the Stronghold password to another one and also re-encrypt the values in the loaded snapshot with it. @@ -33,44 +35,50 @@ impl Wallet { if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.set_password(current_password).await?; stronghold.change_password(new_password).await?; + Ok(()) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } - Ok(()) } /// Sets the Stronghold password clear interval pub async fn set_stronghold_password_clear_interval(&self, timeout: Option) -> crate::wallet::Result<()> { if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.set_timeout(timeout).await; + Ok(()) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } - Ok(()) } /// Stores a mnemonic into the Stronghold vault pub async fn store_mnemonic(&self, mnemonic: Mnemonic) -> crate::wallet::Result<()> { if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.store_mnemonic(mnemonic).await?; + Ok(()) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } - Ok(()) } /// Clears the Stronghold password from memory. pub async fn clear_stronghold_password(&self) -> crate::wallet::Result<()> { log::debug!("[clear_stronghold_password]"); - let mut secret_manager = self.secret_manager.write().await; - match &mut *secret_manager { - SecretManager::Stronghold(stronghold) => stronghold.clear_key().await, - _ => return Err(crate::client::Error::SecretManagerMismatch.into()), + if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { + stronghold.clear_key().await; + Ok(()) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } - Ok(()) } /// Checks if the Stronghold password is available. pub async fn is_stronghold_password_available(&self) -> crate::wallet::Result { log::debug!("[is_stronghold_password_available]"); - let mut secret_manager = self.secret_manager.write().await; - match &mut *secret_manager { - SecretManager::Stronghold(stronghold) => Ok(stronghold.is_key_available().await), - _ => Err(crate::client::Error::SecretManagerMismatch.into()), + if let SecretManager::Stronghold(stronghold) = &*self.secret_manager.write().await { + Ok(stronghold.is_key_available().await) + } else { + Err(crate::client::Error::SecretManagerMismatch.into()) } } } diff --git a/sdk/tests/client/input_selection/nft_outputs.rs b/sdk/tests/client/input_selection/nft_outputs.rs index abb1a24456..7033ed3f14 100644 --- a/sdk/tests/client/input_selection/nft_outputs.rs +++ b/sdk/tests/client/input_selection/nft_outputs.rs @@ -1187,8 +1187,18 @@ fn changed_immutable_metadata() { let protocol_parameters = protocol_parameters(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); + #[cfg(feature = "irc_27")] + let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( + "image/jpeg", + "https://mywebsite.com/my-nft-files-1.jpeg".parse().unwrap(), + "file 1", + ) + .with_issuer_name("Alice"); + #[cfg(not(feature = "irc_27"))] + let metadata = [1, 2, 3]; + let nft_output = NftOutputBuilder::new_with_minimum_storage_deposit(protocol_parameters.rent_structure(), nft_id_1) - .with_immutable_features(MetadataFeature::new([1, 2, 3])) + .with_immutable_features(MetadataFeature::try_from(metadata)) .add_unlock_condition(AddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) @@ -1201,14 +1211,24 @@ fn changed_immutable_metadata() { chain: None, }]; + #[cfg(feature = "irc_27")] + let metadata = iota_sdk::types::block::output::feature::Irc27Metadata::new( + "image/jpeg", + "https://mywebsite.com/my-nft-files-2.jpeg".parse().unwrap(), + "file 2", + ) + .with_issuer_name("Alice"); + #[cfg(not(feature = "irc_27"))] + let metadata = [4, 5, 6]; + // New nft output with changed immutable metadata feature - let updated_account_output = NftOutputBuilder::from(nft_output.as_nft()) + let updated_nft_output = NftOutputBuilder::from(nft_output.as_nft()) .with_minimum_storage_deposit(protocol_parameters.rent_structure()) - .with_immutable_features(MetadataFeature::new([4, 5, 6])) + .with_immutable_features(MetadataFeature::try_from(metadata)) .finish_output(protocol_parameters.token_supply()) .unwrap(); - let outputs = [updated_account_output]; + let outputs = [updated_nft_output]; let selected = InputSelection::new( inputs,