From 51011bd6b79696a5f516958747e87637c2f17887 Mon Sep 17 00:00:00 2001 From: Brord van Wierst Date: Wed, 13 Dec 2023 09:13:41 +0100 Subject: [PATCH] Chore/bindings: cleanup is alive 2.0 (#1541) * simplify nodejs client and wallet * got -> was * fixed client on node side * wasm * removed await for client * lint n format * fixed test and client error return * fixed error and comment * Simplify nodejs and wasm bindings p. 2 (#1146) * unify error handling and responses * wasm * its something * secret manager alignment * error on callback fix * listen error in wasm, madelisten async again * final changes * removed unused PromiseString * lint * wasm update * nodejs * removed async closure * nodejs fixed with tests * fmt * some fixes * mqtt * rewrite again eh :D * client.create() * format and fmt * review and 8050 * typo * updated error handling in nodejs once again * wasm destroy rename * Update bindings/nodejs/lib/index.ts Co-authored-by: DaughterOfMars * secret manager create * typo grrr * forgot to rename create methods * Done more comment explaining :D * removed mqtt custom serde * wasm fixes * fix tests * fmt * fixed secret * promise await * fixed errors, separate eslint * wasm side * typo --------- Co-authored-by: Alex Coats Co-authored-by: Thibault Martinez Co-authored-by: /alex/ Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- bindings/core/tests/combined.rs | 6 +- bindings/nodejs/.eslintignore | 4 +- bindings/nodejs/.eslintrc.js | 2 + bindings/nodejs/README.md | 4 +- .../nodejs/examples/client/04-get-output.ts | 4 +- .../examples/client/05-get-address-balance.ts | 6 +- .../nodejs/examples/client/06-simple-block.ts | 6 +- .../examples/client/07-get-block-data.ts | 4 +- .../nodejs/examples/client/08-data-block.ts | 6 +- bindings/nodejs/examples/client/10-mqtt.ts | 4 +- .../nodejs/examples/client/11-build-output.ts | 4 +- .../examples/client/12-get-raw-block.ts | 4 +- .../client/13-build-account-output.ts | 4 +- .../client/14-build-foundry-output.ts | 4 +- .../examples/client/15-build-nft-output.ts | 4 +- .../examples/client/16-custom-plugin.ts | 4 +- .../nodejs/examples/client/getting-started.ts | 4 +- .../examples/evm/send-evm-transaction.ts | 4 +- .../examples/exchange/0-generate-mnemonic.ts | 2 +- .../examples/exchange/1-create-wallet.ts | 6 +- .../examples/exchange/3-check-balance.ts | 2 +- .../examples/exchange/4-listen-events.ts | 2 +- .../nodejs/examples/exchange/5-send-amount.ts | 2 +- .../examples/how_tos/account_output/create.ts | 2 +- .../how_tos/account_output/destroy.ts | 2 +- .../how_tos/account_wallet/request-funds.ts | 15 +- .../how_tos/account_wallet/transaction.ts | 10 +- .../accounts_and_addresses/check-balance.ts | 2 +- .../consolidate-outputs.ts | 2 +- .../accounts_and_addresses/create-mnemonic.ts | 2 +- .../accounts_and_addresses/create-wallet.ts | 4 +- .../accounts_and_addresses/list-accounts.ts | 2 +- .../accounts_and_addresses/list-outputs.ts | 2 +- .../list-transactions.ts | 2 +- .../advanced_transaction.ts | 2 +- .../claim_transaction.ts | 2 +- .../send_micro_transaction.ts | 2 +- .../examples/how_tos/client/get-health.ts | 4 +- .../examples/how_tos/client/get-info.ts | 4 +- .../examples/how_tos/client/get-outputs.ts | 4 +- .../examples/how_tos/native_tokens/burn.ts | 2 +- .../examples/how_tos/native_tokens/create.ts | 2 +- .../how_tos/native_tokens/destroy-foundry.ts | 2 +- .../examples/how_tos/native_tokens/melt.ts | 2 +- .../examples/how_tos/native_tokens/mint.ts | 2 +- .../examples/how_tos/native_tokens/send.ts | 2 +- .../nft_collection/00_mint_issuer_nft.ts | 2 +- .../nft_collection/01_mint_collection_nft.ts | 5 +- .../nodejs/examples/how_tos/nfts/burn_nft.ts | 2 +- .../nodejs/examples/how_tos/nfts/mint_nft.ts | 2 +- .../nodejs/examples/how_tos/nfts/send_nft.ts | 2 +- .../examples/how_tos/outputs/features.ts | 4 +- .../how_tos/outputs/unlock-conditions.ts | 4 +- .../sign_and_verify_ed25519/sign-ed25519.ts | 4 +- .../sign-secp256k1_ecdsa.ts | 4 +- .../simple_transaction/request-funds.ts | 10 +- .../simple_transaction/simple-transaction.ts | 2 +- .../secret_manager/generate-addresses.ts | 4 +- .../examples/secret_manager/ledger-nano.ts | 4 +- .../examples/secret_manager/stronghold.ts | 4 +- .../wallet/06-send-micro-transaction.ts | 2 +- .../wallet/17-check-unlock-conditions.ts | 2 +- bindings/nodejs/examples/wallet/events.ts | 2 +- .../nodejs/examples/wallet/getting-started.ts | 6 +- .../migrate-stronghold-snapshot-v2-to-v3.ts | 6 +- bindings/nodejs/lib/bindings.ts | 19 ++- .../lib/client/client-method-handler.ts | 47 ++++-- bindings/nodejs/lib/client/client.ts | 16 +- bindings/nodejs/lib/index.ts | 45 ++++++ .../secret-manager-method-handler.ts | 26 ++- .../lib/secret_manager/secret-manager.ts | 11 +- .../types/models/info/node-info-protocol.ts | 2 +- .../lib/wallet/wallet-method-handler.ts | 91 ++++++----- bindings/nodejs/package.json | 2 +- bindings/nodejs/src/client.rs | 74 ++++----- bindings/nodejs/src/error.rs | 33 ++++ bindings/nodejs/src/lib.rs | 60 ++----- bindings/nodejs/src/secret_manager.rs | 12 +- bindings/nodejs/src/utils.rs | 18 +++ bindings/nodejs/src/wallet.rs | 78 ++++----- .../nodejs/tests/client/addresses.spec.ts | 4 +- bindings/nodejs/tests/client/examples.spec.ts | 114 +++++++++----- .../nodejs/tests/client/infoMethods.spec.ts | 27 +++- .../tests/client/messageMethods.spec.ts | 23 +-- .../tests/client/outputBuilders.spec.ts | 28 ++-- .../tests/client/utilityMethods.spec.ts | 26 +-- .../nodejs/tests/client/utxoMethods.spec.ts | 34 ++-- bindings/nodejs/tests/wallet/wallet.spec.ts | 76 ++++++++- bindings/nodejs/tsconfig.eslint.json | 21 +++ bindings/nodejs/tsconfig.json | 2 +- bindings/python/src/wallet.rs | 8 +- bindings/wasm/.eslintignore | 3 + bindings/wasm/.eslintrc.js | 3 + bindings/wasm/README.md | 8 +- bindings/wasm/examples/node.js | 4 +- bindings/wasm/jest.config.js | 2 +- bindings/wasm/lib/bindings.ts | 6 +- bindings/wasm/package.json | 2 +- bindings/wasm/src/client.rs | 85 +++++----- bindings/wasm/src/lib.rs | 30 +++- bindings/wasm/src/secret_manager.rs | 64 ++++---- bindings/wasm/src/utils.rs | 18 ++- bindings/wasm/src/wallet.rs | 149 +++++++++--------- bindings/wasm/test/wallet.spec.ts | 48 ------ .../{test => tests}/utilityMethods.spec.ts | 2 +- bindings/wasm/tests/wallet.spec.ts | 44 ++++++ bindings/wasm/tsconfig.eslint.json | 16 ++ cli/README.md | 2 +- sdk/Cargo.toml | 2 +- sdk/examples/client/client_config.rs | 2 +- sdk/examples/client/quorum.rs | 2 +- sdk/src/client/mod.rs | 2 +- .../stronghold_backup/stronghold_snapshot.rs | 2 +- sdk/tests/client/client_builder.rs | 4 +- sdk/tests/wallet/common/constants.rs | 2 +- 115 files changed, 964 insertions(+), 673 deletions(-) create mode 100644 bindings/nodejs/src/error.rs create mode 100644 bindings/nodejs/src/utils.rs create mode 100644 bindings/nodejs/tsconfig.eslint.json delete mode 100644 bindings/wasm/test/wallet.spec.ts rename bindings/wasm/{test => tests}/utilityMethods.spec.ts (97%) create mode 100644 bindings/wasm/tests/wallet.spec.ts create mode 100644 bindings/wasm/tsconfig.eslint.json diff --git a/bindings/core/tests/combined.rs b/bindings/core/tests/combined.rs index 2f7a1f7e0e..6c2e89254a 100644 --- a/bindings/core/tests/combined.rs +++ b/bindings/core/tests/combined.rs @@ -32,7 +32,7 @@ async fn create_wallet() -> Result<()> { let client_options = r#"{ "nodes":[ { - "url":"http://localhost:14265", + "url":"http://localhost:8050", "auth":null, "disabled":false } @@ -70,7 +70,7 @@ async fn client_from_wallet() -> Result<()> { let client_options = r#"{ "nodes":[ { - "url":"http://localhost:14265", + "url":"http://localhost:8050", "auth":null, "disabled":false } @@ -112,7 +112,7 @@ async fn client_from_wallet() -> Result<()> { // "about solution utility exist rail budget vacuum major survey clerk pave ankle wealth gym gossip still medal // expect strong rely amazing inspire lazy lunar", ).unwrap(); // let client = ClientBuilder::default() -// .with_nodes(&["http://localhost:14265"]) +// .with_nodes(&["http://localhost:8050"]) // .unwrap() // .finish() // .await diff --git a/bindings/nodejs/.eslintignore b/bindings/nodejs/.eslintignore index 7167b23b2a..802a40a9ff 100644 --- a/bindings/nodejs/.eslintignore +++ b/bindings/nodejs/.eslintignore @@ -3,4 +3,6 @@ target **/node_modules/ **/out/ **/tests/ -**/dist \ No newline at end of file +**/dist +jest.config.js +.eslintrc.js \ No newline at end of file diff --git a/bindings/nodejs/.eslintrc.js b/bindings/nodejs/.eslintrc.js index a6650d13d9..9758995ca2 100644 --- a/bindings/nodejs/.eslintrc.js +++ b/bindings/nodejs/.eslintrc.js @@ -6,6 +6,7 @@ const typescriptEslintRules = { '@typescript-eslint/no-empty-interface': 'off', '@typescript-eslint/no-var-requires': 'off', // cleanest way to set dotenv path '@typescript-eslint/no-explicit-any': 'off', + "@typescript-eslint/no-floating-promises": ["error"], }; module.exports = { @@ -24,6 +25,7 @@ module.exports = { parserOptions: { ecmaVersion: 12, sourceType: 'module', + project: ["./tsconfig.eslint.json"], }, rules: typescriptEslintRules, }; diff --git a/bindings/nodejs/README.md b/bindings/nodejs/README.md index 7f4ae693a1..b501cbc592 100644 --- a/bindings/nodejs/README.md +++ b/bindings/nodejs/README.md @@ -90,7 +90,7 @@ const { Client, initLogger } = require('@iota/sdk'); async function run() { initLogger(); - const client = new Client({ + const client = await Client.create({ nodes: ['https://api.testnet.shimmer.network'], }); @@ -102,7 +102,7 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); ``` ## Wallet Usage diff --git a/bindings/nodejs/examples/client/04-get-output.ts b/bindings/nodejs/examples/client/04-get-output.ts index ec90e650cf..1b439c3f11 100644 --- a/bindings/nodejs/examples/client/04-get-output.ts +++ b/bindings/nodejs/examples/client/04-get-output.ts @@ -16,7 +16,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -30,4 +30,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/05-get-address-balance.ts b/bindings/nodejs/examples/client/05-get-address-balance.ts index 4e3bb48812..e8563c2c3f 100644 --- a/bindings/nodejs/examples/client/05-get-address-balance.ts +++ b/bindings/nodejs/examples/client/05-get-address-balance.ts @@ -17,13 +17,13 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); try { - const secretManager = new SecretManager({ + const secretManager = SecretManager.create({ mnemonic: process.env.MNEMONIC as string, }); @@ -75,4 +75,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/06-simple-block.ts b/bindings/nodejs/examples/client/06-simple-block.ts index a4f0d7ef03..4c6ccf388b 100644 --- a/bindings/nodejs/examples/client/06-simple-block.ts +++ b/bindings/nodejs/examples/client/06-simple-block.ts @@ -30,9 +30,9 @@ async function run() { mnemonic: process.env.MNEMONIC, }; - const secretManager = new SecretManager(mnemonicSecretManager); + const secretManager = SecretManager.create(mnemonicSecretManager); - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -67,4 +67,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/07-get-block-data.ts b/bindings/nodejs/examples/client/07-get-block-data.ts index f8a45ec4ec..7302d6d9e4 100644 --- a/bindings/nodejs/examples/client/07-get-block-data.ts +++ b/bindings/nodejs/examples/client/07-get-block-data.ts @@ -16,7 +16,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -38,4 +38,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/08-data-block.ts b/bindings/nodejs/examples/client/08-data-block.ts index 1bf9bbae4b..a2a9d4a772 100644 --- a/bindings/nodejs/examples/client/08-data-block.ts +++ b/bindings/nodejs/examples/client/08-data-block.ts @@ -31,9 +31,9 @@ async function run() { mnemonic: process.env.MNEMONIC, }; - const secretManager = new SecretManager(mnemonicSecretManager); + const secretManager = SecretManager.create(mnemonicSecretManager); - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -75,4 +75,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/10-mqtt.ts b/bindings/nodejs/examples/client/10-mqtt.ts index 1503d9101e..79b4133e3a 100644 --- a/bindings/nodejs/examples/client/10-mqtt.ts +++ b/bindings/nodejs/examples/client/10-mqtt.ts @@ -18,7 +18,7 @@ async function run() { } // Connecting to a MQTT broker using raw ip doesn't work with TCP. This is a limitation of rustls. - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -55,4 +55,4 @@ async function run() { }, 10000); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/11-build-output.ts b/bindings/nodejs/examples/client/11-build-output.ts index 8dbdd1f18e..fb44f9e71f 100644 --- a/bindings/nodejs/examples/client/11-build-output.ts +++ b/bindings/nodejs/examples/client/11-build-output.ts @@ -30,7 +30,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -121,4 +121,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/12-get-raw-block.ts b/bindings/nodejs/examples/client/12-get-raw-block.ts index b461ff1d4c..37788b4b81 100644 --- a/bindings/nodejs/examples/client/12-get-raw-block.ts +++ b/bindings/nodejs/examples/client/12-get-raw-block.ts @@ -16,7 +16,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -32,4 +32,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/13-build-account-output.ts b/bindings/nodejs/examples/client/13-build-account-output.ts index 99ded4bb68..f1dfcf2992 100644 --- a/bindings/nodejs/examples/client/13-build-account-output.ts +++ b/bindings/nodejs/examples/client/13-build-account-output.ts @@ -26,7 +26,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -59,4 +59,4 @@ async function run() { } } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/14-build-foundry-output.ts b/bindings/nodejs/examples/client/14-build-foundry-output.ts index 3a559b3e8a..4822f77da7 100644 --- a/bindings/nodejs/examples/client/14-build-foundry-output.ts +++ b/bindings/nodejs/examples/client/14-build-foundry-output.ts @@ -22,7 +22,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -53,4 +53,4 @@ async function run() { } } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/15-build-nft-output.ts b/bindings/nodejs/examples/client/15-build-nft-output.ts index c484c7caed..1cd9ab3f80 100644 --- a/bindings/nodejs/examples/client/15-build-nft-output.ts +++ b/bindings/nodejs/examples/client/15-build-nft-output.ts @@ -28,7 +28,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -68,4 +68,4 @@ async function run() { } } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/16-custom-plugin.ts b/bindings/nodejs/examples/client/16-custom-plugin.ts index 0eacb29501..9fcdb9d5a3 100644 --- a/bindings/nodejs/examples/client/16-custom-plugin.ts +++ b/bindings/nodejs/examples/client/16-custom-plugin.ts @@ -16,7 +16,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -41,4 +41,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/getting-started.ts b/bindings/nodejs/examples/client/getting-started.ts index 41ec7270c4..bbc1d4b0f1 100644 --- a/bindings/nodejs/examples/client/getting-started.ts +++ b/bindings/nodejs/examples/client/getting-started.ts @@ -8,7 +8,7 @@ import { Client } from '@iota/sdk'; // In this example we will get information about the node async function run() { - const client = new Client({ + const client = await Client.create({ nodes: ['https://api.testnet.shimmer.network'], }); @@ -20,4 +20,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/evm/send-evm-transaction.ts b/bindings/nodejs/examples/evm/send-evm-transaction.ts index 32e7b011c2..b2adda3005 100644 --- a/bindings/nodejs/examples/evm/send-evm-transaction.ts +++ b/bindings/nodejs/examples/evm/send-evm-transaction.ts @@ -47,7 +47,7 @@ async function run(): Promise { mnemonic: process.env.MNEMONIC as string, }; - const secretManager = new SecretManager(mnemonicSecretManager); + const secretManager = SecretManager.create(mnemonicSecretManager); const addresses = await secretManager.generateEvmAddresses({ coinType: ETHEREUM_COIN_TYPE, @@ -170,4 +170,4 @@ function padHexString(str: string): string { return str.length % 2 !== 0 ? '0' + str : str; } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/exchange/0-generate-mnemonic.ts b/bindings/nodejs/examples/exchange/0-generate-mnemonic.ts index 2a19cf623a..44dcc21ec9 100644 --- a/bindings/nodejs/examples/exchange/0-generate-mnemonic.ts +++ b/bindings/nodejs/examples/exchange/0-generate-mnemonic.ts @@ -16,4 +16,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/exchange/1-create-wallet.ts b/bindings/nodejs/examples/exchange/1-create-wallet.ts index 8f76ef8ed9..59b4ed19b3 100644 --- a/bindings/nodejs/examples/exchange/1-create-wallet.ts +++ b/bindings/nodejs/examples/exchange/1-create-wallet.ts @@ -32,7 +32,7 @@ async function run() { }, }; - const secretManager = new SecretManager(strongholdSecretManager); + const secretManager = SecretManager.create(strongholdSecretManager); // A mnemonic can be generated with `Utils.generateMnemonic()`. // Store the mnemonic in the Stronghold snapshot, this needs to be done only the first time. @@ -65,10 +65,10 @@ async function run() { // Set syncOnlyMostBasicOutputs to true if not interested in outputs that are timelocked, // have a storage deposit return, expiration or are nft/account/foundry outputs. - wallet.setDefaultSyncOptions({ syncOnlyMostBasicOutputs: true }); + await wallet.setDefaultSyncOptions({ syncOnlyMostBasicOutputs: true }); } catch (error) { console.error(error); } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/exchange/3-check-balance.ts b/bindings/nodejs/examples/exchange/3-check-balance.ts index 65c303cc4f..52dbb88de8 100644 --- a/bindings/nodejs/examples/exchange/3-check-balance.ts +++ b/bindings/nodejs/examples/exchange/3-check-balance.ts @@ -42,4 +42,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/exchange/4-listen-events.ts b/bindings/nodejs/examples/exchange/4-listen-events.ts index a1ebc497fd..09e93b7f45 100644 --- a/bindings/nodejs/examples/exchange/4-listen-events.ts +++ b/bindings/nodejs/examples/exchange/4-listen-events.ts @@ -57,4 +57,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/exchange/5-send-amount.ts b/bindings/nodejs/examples/exchange/5-send-amount.ts index bbe9665779..6d33168ecd 100644 --- a/bindings/nodejs/examples/exchange/5-send-amount.ts +++ b/bindings/nodejs/examples/exchange/5-send-amount.ts @@ -51,4 +51,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/account_output/create.ts b/bindings/nodejs/examples/how_tos/account_output/create.ts index ac832f8577..e79ae076a8 100644 --- a/bindings/nodejs/examples/how_tos/account_output/create.ts +++ b/bindings/nodejs/examples/how_tos/account_output/create.ts @@ -65,4 +65,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/account_output/destroy.ts b/bindings/nodejs/examples/how_tos/account_output/destroy.ts index 1195c3c880..f6ee3aa130 100644 --- a/bindings/nodejs/examples/how_tos/account_output/destroy.ts +++ b/bindings/nodejs/examples/how_tos/account_output/destroy.ts @@ -77,4 +77,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/account_wallet/request-funds.ts b/bindings/nodejs/examples/how_tos/account_wallet/request-funds.ts index cdf39717c2..662a79a6ca 100644 --- a/bindings/nodejs/examples/how_tos/account_wallet/request-funds.ts +++ b/bindings/nodejs/examples/how_tos/account_wallet/request-funds.ts @@ -28,7 +28,6 @@ async function run() { const wallet = await Wallet.create({ storagePath: process.env.WALLET_DB_PATH, }); - const balance = await wallet.sync(); const totalBaseTokenBalance = balance.baseCoin.total; @@ -39,14 +38,18 @@ async function run() { const accountId = balance.accounts[0]; console.log(`Account Id: ${accountId}`); + const client = await wallet.getClient(); + // Get Account address const accountAddress = Utils.accountIdToBech32( accountId, - await (await wallet.getClient()).getBech32Hrp(), + await client.getBech32Hrp(), + ); + + const faucetResponse = await client.requestFundsFromFaucet( + faucetUrl, + accountAddress, ); - const faucetResponse = await ( - await wallet.getClient() - ).requestFundsFromFaucet(faucetUrl, accountAddress); console.log(faucetResponse); await new Promise((resolve) => setTimeout(resolve, 10000)); @@ -66,4 +69,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/account_wallet/transaction.ts b/bindings/nodejs/examples/how_tos/account_wallet/transaction.ts index 4ae2f9dda7..26565cb12b 100644 --- a/bindings/nodejs/examples/how_tos/account_wallet/transaction.ts +++ b/bindings/nodejs/examples/how_tos/account_wallet/transaction.ts @@ -49,19 +49,19 @@ async function run() { const accountId = balance.accounts[0]; console.log(`Account Id: ${accountId}`); + const client = await wallet.getClient(); + // Get Account address const accountAddress = Utils.accountIdToBech32( accountId, - await (await wallet.getClient()).getBech32Hrp(), + await client.getBech32Hrp(), ); // Find first output unlockable by the account address const queryParameters = { address: accountAddress, }; - const input = ( - await (await wallet.getClient()).basicOutputIds(queryParameters) - ).items[0]; + const input = (await client.basicOutputIds(queryParameters)).items[0]; const params = [ { @@ -90,4 +90,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/check-balance.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/check-balance.ts index b324de4b9f..a0f78afda7 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/check-balance.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/check-balance.ts @@ -34,4 +34,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); 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 e897c7e0dc..34afa3f88d 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 @@ -100,4 +100,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-mnemonic.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-mnemonic.ts index 3f709534e1..480fad6e31 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-mnemonic.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-mnemonic.ts @@ -16,4 +16,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-wallet.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-wallet.ts index a873310b18..524e1a1ae1 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-wallet.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-wallet.ts @@ -37,7 +37,7 @@ async function run() { }, }; - const secretManager = new SecretManager(strongholdSecretManager); + const secretManager = SecretManager.create(strongholdSecretManager); // A mnemonic can be generated with `Utils.generateMnemonic()`. // Store the mnemonic in the Stronghold snapshot, this needs to be done only the first time. @@ -76,4 +76,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-accounts.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-accounts.ts index 2ac3de57b6..2846288afb 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-accounts.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-accounts.ts @@ -30,4 +30,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-outputs.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-outputs.ts index 60854557d7..e5327d61fc 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-outputs.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-outputs.ts @@ -38,4 +38,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-transactions.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-transactions.ts index f8b2e8eb01..70023dc2d3 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-transactions.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-transactions.ts @@ -38,4 +38,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts b/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts index 8ec65bf168..5a96d760f1 100644 --- a/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts +++ b/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts @@ -72,4 +72,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts b/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts index 86c3787664..68d78345e5 100644 --- a/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts +++ b/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts @@ -53,4 +53,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts b/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts index d17a45d345..77b7e71680 100644 --- a/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts +++ b/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts @@ -55,4 +55,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/client/get-health.ts b/bindings/nodejs/examples/how_tos/client/get-health.ts index b304f48ce4..d711d39834 100644 --- a/bindings/nodejs/examples/how_tos/client/get-health.ts +++ b/bindings/nodejs/examples/how_tos/client/get-health.ts @@ -16,7 +16,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -31,4 +31,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/client/get-info.ts b/bindings/nodejs/examples/how_tos/client/get-info.ts index cfab98e740..6f44ba72ca 100644 --- a/bindings/nodejs/examples/how_tos/client/get-info.ts +++ b/bindings/nodejs/examples/how_tos/client/get-info.ts @@ -16,7 +16,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -29,4 +29,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/client/get-outputs.ts b/bindings/nodejs/examples/how_tos/client/get-outputs.ts index ec8e880917..6382a7f5c6 100644 --- a/bindings/nodejs/examples/how_tos/client/get-outputs.ts +++ b/bindings/nodejs/examples/how_tos/client/get-outputs.ts @@ -16,7 +16,7 @@ async function run() { } } - const client = new Client({ + const client = await Client.create({ // Insert your node URL in the .env. nodes: [process.env.NODE_URL as string], }); @@ -40,4 +40,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/burn.ts b/bindings/nodejs/examples/how_tos/native_tokens/burn.ts index cc944af929..51574417d8 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/burn.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/burn.ts @@ -82,4 +82,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/create.ts b/bindings/nodejs/examples/how_tos/native_tokens/create.ts index 2e2eb3f4d1..3ebfb938f7 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/create.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/create.ts @@ -91,4 +91,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts b/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts index 3eee8d0e96..b967118f5b 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts @@ -56,4 +56,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/melt.ts b/bindings/nodejs/examples/how_tos/native_tokens/melt.ts index 99651eb28d..bb39c32635 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/melt.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/melt.ts @@ -77,4 +77,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/mint.ts b/bindings/nodejs/examples/how_tos/native_tokens/mint.ts index 34cd88b47b..45ae306088 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/mint.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/mint.ts @@ -75,4 +75,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/send.ts b/bindings/nodejs/examples/how_tos/native_tokens/send.ts index ea1649fc97..567949e077 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/send.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/send.ts @@ -84,4 +84,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); 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 f12f0b6d3b..3cf5b6c8c0 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 @@ -88,4 +88,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); 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 bee9f55639..061d6fb537 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 @@ -47,7 +47,8 @@ async function run() { // Get the id we generated with `00_mint_issuer_nft` const issuerNftId: NftId = process.argv[2]; - const bech32Hrp = await (await wallet.getClient()).getBech32Hrp(); + const client = await wallet.getClient(); + const bech32Hrp = await client.getBech32Hrp(); const issuer = Utils.nftIdToBech32(issuerNftId, bech32Hrp); const nftMintParams = []; @@ -115,4 +116,4 @@ function getImmutableMetadata(index: number) { .withCollectionName('Shimmer OG'); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts b/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts index 5b720ead82..1116a94830 100644 --- a/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts @@ -70,4 +70,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts index caf0b819be..205faf1e41 100644 --- a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts @@ -122,4 +122,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/nfts/send_nft.ts b/bindings/nodejs/examples/how_tos/nfts/send_nft.ts index b793c4a43c..7fc2d59a17 100644 --- a/bindings/nodejs/examples/how_tos/nfts/send_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/send_nft.ts @@ -88,4 +88,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/outputs/features.ts b/bindings/nodejs/examples/how_tos/outputs/features.ts index f7c8743df8..c6bb77e9ac 100644 --- a/bindings/nodejs/examples/how_tos/outputs/features.ts +++ b/bindings/nodejs/examples/how_tos/outputs/features.ts @@ -23,7 +23,7 @@ require('dotenv').config({ path: '.env' }); async function run() { initLogger(); - const client = new Client({}); + const client = await Client.create({}); try { const hexAddress = Utils.bech32ToHex( @@ -90,4 +90,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/outputs/unlock-conditions.ts b/bindings/nodejs/examples/how_tos/outputs/unlock-conditions.ts index d5aa2fd00b..4be6b6fd61 100644 --- a/bindings/nodejs/examples/how_tos/outputs/unlock-conditions.ts +++ b/bindings/nodejs/examples/how_tos/outputs/unlock-conditions.ts @@ -24,7 +24,7 @@ require('dotenv').config({ path: '.env' }); async function run() { initLogger(); - const client = new Client({}); + const client = await Client.create({}); try { const hexAddress = Utils.bech32ToHex( @@ -108,4 +108,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/sign_and_verify_ed25519/sign-ed25519.ts b/bindings/nodejs/examples/how_tos/sign_and_verify_ed25519/sign-ed25519.ts index acb827b9ae..5513d2d398 100644 --- a/bindings/nodejs/examples/how_tos/sign_and_verify_ed25519/sign-ed25519.ts +++ b/bindings/nodejs/examples/how_tos/sign_and_verify_ed25519/sign-ed25519.ts @@ -38,7 +38,7 @@ async function run() { } } - const secretManager = new SecretManager({ + const secretManager = SecretManager.create({ stronghold: { password: process.env.STRONGHOLD_PASSWORD, snapshotPath: 'sign_ed25519.stronghold', @@ -75,4 +75,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/sign_secp256k1_ecdsa/sign-secp256k1_ecdsa.ts b/bindings/nodejs/examples/how_tos/sign_secp256k1_ecdsa/sign-secp256k1_ecdsa.ts index aed2553f09..a500cd6b32 100644 --- a/bindings/nodejs/examples/how_tos/sign_secp256k1_ecdsa/sign-secp256k1_ecdsa.ts +++ b/bindings/nodejs/examples/how_tos/sign_secp256k1_ecdsa/sign-secp256k1_ecdsa.ts @@ -32,7 +32,7 @@ async function run() { } } - const secretManager = new SecretManager({ + const secretManager = SecretManager.create({ stronghold: { password: process.env.STRONGHOLD_PASSWORD, snapshotPath: 'sign_secp256k1_ecdsa.stronghold', @@ -63,4 +63,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts b/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts index 58114690a5..96ee9920f9 100644 --- a/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts +++ b/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts @@ -28,13 +28,15 @@ async function run() { const address = await wallet.address(); console.log(address); - const faucetResponse = await ( - await wallet.getClient() - ).requestFundsFromFaucet(faucetUrl, address); + const client = await wallet.getClient(); + const faucetResponse = await client.requestFundsFromFaucet( + faucetUrl, + address, + ); console.log(faucetResponse); } catch (error) { console.error('Error: ', error); } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/simple_transaction/simple-transaction.ts b/bindings/nodejs/examples/how_tos/simple_transaction/simple-transaction.ts index d0d2ad8cd7..cc76da1122 100644 --- a/bindings/nodejs/examples/how_tos/simple_transaction/simple-transaction.ts +++ b/bindings/nodejs/examples/how_tos/simple_transaction/simple-transaction.ts @@ -49,4 +49,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/secret_manager/generate-addresses.ts b/bindings/nodejs/examples/secret_manager/generate-addresses.ts index ab5ab8c823..80fc98025e 100644 --- a/bindings/nodejs/examples/secret_manager/generate-addresses.ts +++ b/bindings/nodejs/examples/secret_manager/generate-addresses.ts @@ -20,7 +20,7 @@ async function run() { mnemonic: process.env.MNEMONIC as string, }; - const secretManager = new SecretManager(mnemonicSecretManager); + const secretManager = SecretManager.create(mnemonicSecretManager); // Generate public address with default account index and range. const default_addresses = await secretManager.generateEd25519Addresses( @@ -61,4 +61,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/secret_manager/ledger-nano.ts b/bindings/nodejs/examples/secret_manager/ledger-nano.ts index 14deeafd0c..bcb395f543 100644 --- a/bindings/nodejs/examples/secret_manager/ledger-nano.ts +++ b/bindings/nodejs/examples/secret_manager/ledger-nano.ts @@ -16,7 +16,7 @@ async function run() { try { const isSimulator = false; - const ledgerNanoSecretManager = new SecretManager({ + const ledgerNanoSecretManager = SecretManager.create({ ledgerNano: isSimulator, }); @@ -39,4 +39,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/secret_manager/stronghold.ts b/bindings/nodejs/examples/secret_manager/stronghold.ts index 869204b125..26ffb3ec59 100644 --- a/bindings/nodejs/examples/secret_manager/stronghold.ts +++ b/bindings/nodejs/examples/secret_manager/stronghold.ts @@ -17,7 +17,7 @@ async function run() { throw new Error(`.env ${envVar} is not defined`); } } - const strongholdSecretManager = new SecretManager({ + const strongholdSecretManager = SecretManager.create({ stronghold: { password: process.env.STRONGHOLD_PASSWORD, snapshotPath: 'client.stronghold', @@ -46,4 +46,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts b/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts index 280e41cc18..1e2056130d 100644 --- a/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts +++ b/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts @@ -59,4 +59,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts b/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts index 2e1faa23e5..69b35296cf 100644 --- a/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts +++ b/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts @@ -57,4 +57,4 @@ async function run() { process.exit(0); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/wallet/events.ts b/bindings/nodejs/examples/wallet/events.ts index 13e7663064..f11178fc82 100644 --- a/bindings/nodejs/examples/wallet/events.ts +++ b/bindings/nodejs/examples/wallet/events.ts @@ -41,4 +41,4 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/wallet/getting-started.ts b/bindings/nodejs/examples/wallet/getting-started.ts index 4343021912..6d08c99e2d 100644 --- a/bindings/nodejs/examples/wallet/getting-started.ts +++ b/bindings/nodejs/examples/wallet/getting-started.ts @@ -25,7 +25,7 @@ const STRONGHOLD_PASSWORD = 'a-secure-password'; // The path to store the wallet snapshot. const STRONGHOLD_SNAPSHOT_PATH = 'vault.stronghold'; -async function main() { +async function run() { const strongholdSecretManager = { stronghold: { snapshotPath: STRONGHOLD_SNAPSHOT_PATH, @@ -33,7 +33,7 @@ async function main() { }, }; - const secretManager = new SecretManager(strongholdSecretManager); + const secretManager = SecretManager.create(strongholdSecretManager); // Generate a mnemonic and store its seed in the Stronghold vault. // INFO: It is best practice to back up the mnemonic somewhere secure. @@ -73,4 +73,4 @@ async function main() { process.exit(0); } -main(); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.ts b/bindings/nodejs/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.ts index 9293fe3768..2b15c23411 100644 --- a/bindings/nodejs/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.ts +++ b/bindings/nodejs/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.ts @@ -26,7 +26,7 @@ async function run() { try { // This should fail with error, migration required. - new SecretManager(strongholdSecretManager); + SecretManager.create(strongholdSecretManager); } catch (error) { console.error(error); } @@ -41,7 +41,7 @@ async function run() { ); // This shouldn't fail anymore as snapshot has been migrated. - new SecretManager(strongholdSecretManager); + SecretManager.create(strongholdSecretManager); } -run().then(() => process.exit()); +void run().then(() => process.exit()); diff --git a/bindings/nodejs/lib/bindings.ts b/bindings/nodejs/lib/bindings.ts index a709d81841..696d03c37d 100644 --- a/bindings/nodejs/lib/bindings.ts +++ b/bindings/nodejs/lib/bindings.ts @@ -5,6 +5,7 @@ import { __UtilsMethods__ } from './types/utils'; // @ts-ignore: path is set to match runtime transpiled js path import addon = require('../build/Release/index.node'); +import { errorHandle } from '.'; const { callUtilsMethodRust, @@ -19,17 +20,19 @@ const { createWallet, listenWallet, destroyWallet, - getClientFromWallet, - getSecretManagerFromWallet, + getClient, + getSecretManager, migrateStrongholdSnapshotV2ToV3, } = addon; const callUtilsMethod = (method: __UtilsMethods__): any => { - const response = JSON.parse(callUtilsMethodRust(JSON.stringify(method))); - if (response.type == 'error' || response.type == 'panic') { - throw response; - } else { + try { + const response = JSON.parse( + callUtilsMethodRust(JSON.stringify(method)), + ); return response.payload; + } catch (error: any) { + throw errorHandle(error); } }; @@ -45,8 +48,8 @@ export { callWalletMethod, destroyWallet, listenWallet, - getClientFromWallet, - getSecretManagerFromWallet, + getClient, + getSecretManager, listenMqtt, migrateStrongholdSnapshotV2ToV3, }; diff --git a/bindings/nodejs/lib/client/client-method-handler.ts b/bindings/nodejs/lib/client/client-method-handler.ts index e2066a5307..96c89759b0 100644 --- a/bindings/nodejs/lib/client/client-method-handler.ts +++ b/bindings/nodejs/lib/client/client-method-handler.ts @@ -1,6 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +import { errorHandle } from '..'; import { callClientMethod, createClient, @@ -13,22 +14,33 @@ import type { IClientOptions, __ClientMethods__ } from '../types/client'; * The MethodHandler which sends the commands to the Rust side. */ export class ClientMethodHandler { - methodHandler: ClientMethodHandler; + methodHandler: any; /** - * @param options client options or a client method handler. + * @param methodHandler The Rust method handler created in `ClientMethodHandler.create()`. */ - constructor(options: IClientOptions | ClientMethodHandler) { - // The rust client object is not extensible - if (Object.isExtensible(options)) { - this.methodHandler = createClient(JSON.stringify(options)); - } else { - this.methodHandler = options as ClientMethodHandler; + constructor(methodHandler: any) { + this.methodHandler = methodHandler; + } + + /** + * @param options The client options. + */ + static async create(options: IClientOptions): Promise { + try { + const methodHandler = await createClient(JSON.stringify(options)); + return new ClientMethodHandler(methodHandler); + } catch (error: any) { + throw errorHandle(error); } } - async destroy() { - return destroyClient(this.methodHandler); + async destroy(): Promise { + try { + destroyClient(this.methodHandler); + } catch (error: any) { + throw errorHandle(error); + } } /** @@ -38,7 +50,12 @@ export class ClientMethodHandler { * @returns A promise that resolves to a JSON string response holding the result of the client method. */ async callMethod(method: __ClientMethods__): Promise { - return callClientMethod(this.methodHandler, JSON.stringify(method)); + return callClientMethod( + this.methodHandler, + JSON.stringify(method), + ).catch((error: any) => { + throw errorHandle(error); + }); } /** @@ -47,10 +64,14 @@ export class ClientMethodHandler { * @param topics The topics to listen to. * @param callback The callback to be called when an MQTT event is received. */ - async listen( + async listenMqtt( topics: string[], callback: (error: Error, result: string) => void, ): Promise { - return listenMqtt(this.methodHandler, topics, callback); + try { + listenMqtt(this.methodHandler, topics, callback); + } catch (error: any) { + throw errorHandle(error); + } } } diff --git a/bindings/nodejs/lib/client/client.ts b/bindings/nodejs/lib/client/client.ts index fd3e60b511..d4ad1ba65a 100644 --- a/bindings/nodejs/lib/client/client.ts +++ b/bindings/nodejs/lib/client/client.ts @@ -68,13 +68,19 @@ export class Client { private methodHandler: ClientMethodHandler; /** - * @param options client options or a client method handler. + * @param methodHandler The Rust method handler created in `ClientMethodHandler.create()`. */ - constructor(options: IClientOptions | ClientMethodHandler) { - this.methodHandler = new ClientMethodHandler(options); + constructor(methodHandler: ClientMethodHandler) { + this.methodHandler = methodHandler; } - async destroy() { + /** + * @param options The client options. + */ + static async create(options: IClientOptions): Promise { + return new Client(await ClientMethodHandler.create(options)); + } + async destroy(): Promise { return this.methodHandler.destroy(); } @@ -691,7 +697,7 @@ export class Client { topics: string[], callback: (error: Error, result: string) => void, ): Promise { - return this.methodHandler.listen(topics, callback); + return this.methodHandler.listenMqtt(topics, callback); } /** diff --git a/bindings/nodejs/lib/index.ts b/bindings/nodejs/lib/index.ts index 59d996af3c..5d1abc3c96 100644 --- a/bindings/nodejs/lib/index.ts +++ b/bindings/nodejs/lib/index.ts @@ -39,3 +39,48 @@ export * from './types'; export * from './utils'; export * from './wallet'; export * from './logger'; + +// For future reference to see what we return from rust as a serialized string +export type Result = { + // "error" | "panic", or other binding "Response" enum name, we consider "ok". + type: string; + // "panic" means payload is just a string, otherwise its the object below. + payload: { + // Ok: All method names from types/bridge/__name__.name + // Not ok: all variants of iota_sdk_bindings_core::Error type i.e block/client/wallet/ + type: string; + // If "ok", json payload + payload?: string; + // If not "ok", json error + error?: string; + }; +}; + +function errorHandle(error: any): Error { + try { + const err: Result = JSON.parse(error.message); + if (!err.type) { + return error; + } + + if (err.type == 'panic') { + // Panic example: + // {"type":"panic","payload":"Client was destroyed"} + return Error(err.payload.toString()); + } else if (err.type == 'error') { + // Error example: + // {"type":"error","payload":{"type":"client","error":"no healthy node available"}} + // TODO: switch on type and create proper js errors https://github.com/iotaledger/iota-sdk/issues/1417 + return Error(err.payload.error); + } else { + return Error( + 'in ErrorHandle without a valid error object. Only call this in catch statements.', + ); + } + } catch (err: any) { + // json error, SyntaxError, we must have send a non-json error + return error; + } +} + +export { errorHandle }; diff --git a/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts b/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts index 275363c3de..ab2a540ff0 100644 --- a/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts +++ b/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts @@ -1,6 +1,7 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +import { errorHandle } from '..'; import { callSecretManagerMethod, createSecretManager, @@ -13,17 +14,24 @@ import { /** The MethodHandler which sends the commands to the Rust backend. */ export class SecretManagerMethodHandler { - methodHandler: SecretManagerMethodHandler; + methodHandler: any; + + /** + * @param methodHandler The Rust method handler created in `SecretManagerMethodHandler.create()`. + */ + constructor(methodHandler: any) { + this.methodHandler = methodHandler; + } /** * @param options A secret manager type or a secret manager method handler. */ - constructor(options: SecretManagerType | SecretManagerMethodHandler) { - // The rust secret manager object is not extensible - if (Object.isExtensible(options)) { - this.methodHandler = createSecretManager(JSON.stringify(options)); - } else { - this.methodHandler = options as SecretManagerMethodHandler; + static create(options: SecretManagerType): SecretManagerMethodHandler { + try { + const methodHandler = createSecretManager(JSON.stringify(options)); + return new SecretManagerMethodHandler(methodHandler); + } catch (error: any) { + throw errorHandle(error); } } @@ -37,7 +45,9 @@ export class SecretManagerMethodHandler { return callSecretManagerMethod( this.methodHandler, JSON.stringify(method), - ); + ).catch((error: any) => { + throw errorHandle(error); + }); } } diff --git a/bindings/nodejs/lib/secret_manager/secret-manager.ts b/bindings/nodejs/lib/secret_manager/secret-manager.ts index 5afccb4d6f..c1f4366b7c 100644 --- a/bindings/nodejs/lib/secret_manager/secret-manager.ts +++ b/bindings/nodejs/lib/secret_manager/secret-manager.ts @@ -32,8 +32,15 @@ export class SecretManager { /** * @param options A secret manager type or a secret manager method handler. */ - constructor(options: SecretManagerType | SecretManagerMethodHandler) { - this.methodHandler = new SecretManagerMethodHandler(options); + constructor(methodHandler: SecretManagerMethodHandler) { + this.methodHandler = methodHandler; + } + + /** + * @param options The secret manager options. + */ + static create(options: SecretManagerType): SecretManager { + return new SecretManager(SecretManagerMethodHandler.create(options)); } /** diff --git a/bindings/nodejs/lib/types/models/info/node-info-protocol.ts b/bindings/nodejs/lib/types/models/info/node-info-protocol.ts index 4487213f7b..84f70cd5e4 100644 --- a/bindings/nodejs/lib/types/models/info/node-info-protocol.ts +++ b/bindings/nodejs/lib/types/models/info/node-info-protocol.ts @@ -16,7 +16,7 @@ export interface ProtocolInfo { /** * The protocol parameters. */ - parameters: ProtocolParameters[]; + parameters: ProtocolParameters; } /** diff --git a/bindings/nodejs/lib/wallet/wallet-method-handler.ts b/bindings/nodejs/lib/wallet/wallet-method-handler.ts index ecaab49c9f..b91eda5834 100644 --- a/bindings/nodejs/lib/wallet/wallet-method-handler.ts +++ b/bindings/nodejs/lib/wallet/wallet-method-handler.ts @@ -6,20 +6,22 @@ import { createWallet, listenWallet, destroyWallet, - getClientFromWallet, - getSecretManagerFromWallet, + getClient, + getSecretManager, } from '../bindings'; -import type { +import { WalletEventType, WalletOptions, __WalletMethod__, Event, } from '../types/wallet'; -import { Client } from '../client'; -import { SecretManager } from '../secret_manager'; +import { Client, ClientMethodHandler } from '../client'; +import { SecretManager, SecretManagerMethodHandler } from '../secret_manager'; +import { errorHandle } from '..'; // The WalletMethodHandler class interacts with methods with the rust bindings. export class WalletMethodHandler { + // External rust object methodHandler: any; /** @@ -33,14 +35,19 @@ export class WalletMethodHandler { * @param options The wallet options. */ static async create(options: WalletOptions): Promise { - const methodHandler = await createWallet(JSON.stringify(options)); - return new WalletMethodHandler(methodHandler); + try { + const methodHandler = await createWallet(JSON.stringify(options)); + return new WalletMethodHandler(methodHandler); + } catch (error: any) { + throw errorHandle(error); + } } /** * Call a wallet method on the Rust backend. * * @param method The wallet method to call. + * @returns A promise that resolves to a JSON string response holding the result of the wallet method. */ async callMethod(method: __WalletMethod__): Promise { return callWalletMethod( @@ -53,17 +60,9 @@ export class WalletMethodHandler { return value; } }), - ).catch((error: Error) => { - try { - if (error.message !== undefined) { - error = JSON.parse(error.message).payload; - } else { - error = JSON.parse(error.toString()).payload; - } - } catch (e) { - console.error(e); - } - return Promise.reject(error); + this.methodHandler, + ).catch((error: any) => { + throw errorHandle(error); }); } @@ -77,42 +76,48 @@ export class WalletMethodHandler { eventTypes: WalletEventType[], callback: (error: Error, event: Event) => void, ): Promise { - return listenWallet(this.methodHandler, eventTypes, callback); + return listenWallet( + this.methodHandler, + eventTypes, + function (err: any, data: string) { + const parsed = JSON.parse(data); + callback( + // Send back raw error instead of parsing + err, + new Event(parsed.accountIndex, parsed.event), + ); + }, + ).catch((error: any) => { + throw errorHandle(error); + }); } async destroy(): Promise { - return destroyWallet(this.methodHandler); + try { + await destroyWallet(this.methodHandler); + } catch (error: any) { + throw errorHandle(error); + } } - /** - * Get the client associated with the wallet. - */ async getClient(): Promise { - return new Promise((resolve, reject) => { - getClientFromWallet(this.methodHandler).then((result: any) => { - if (result.message !== undefined) { - reject(JSON.parse(result.message).payload); - } else { - resolve(new Client(result)); - } - }); - }); + try { + const result = await getClient(this.methodHandler); + return new Client(new ClientMethodHandler(result)); + } catch (error: any) { + throw errorHandle(error); + } } /** * Get the secret manager associated with the wallet. */ async getSecretManager(): Promise { - return new Promise((resolve, reject) => { - getSecretManagerFromWallet(this.methodHandler).then( - (result: any) => { - if (result.message !== undefined) { - reject(JSON.parse(result.message).payload); - } else { - resolve(new SecretManager(result)); - } - }, - ); - }); + try { + const result = await getSecretManager(this.methodHandler); + return new SecretManager(new SecretManagerMethodHandler(result)); + } catch (error: any) { + throw errorHandle(error); + } } } diff --git a/bindings/nodejs/package.json b/bindings/nodejs/package.json index b80222bd6d..575a3c4fd7 100644 --- a/bindings/nodejs/package.json +++ b/bindings/nodejs/package.json @@ -18,7 +18,7 @@ "prebuild-windows-arm64": "prebuild --runtime napi --target 6 --prepack 'yarn run napi-build-windows-arm64' --strip --arch arm64", "napi-build-windows-arm64": "napi build --cargo-flags=--profile=production --target aarch64-pc-windows-msvc", "rebuild": "node scripts/build && tsc && node scripts/strip.js", - "install": "prebuild-install --runtime napi --tag-prefix=iota-sdk-nodejs-v && tsc || npm run rebuild", + "install": "prebuild-install --runtime napi --tag-prefix=iota-sdk-nodejs-v && tsc || yarn rebuild", "test": "jest", "test-webpack": "cd tests/webpack && webpack-cli build --config ./webpack.config.js ", "create-api-docs": "typedoc ./lib/index.ts --githubPages false --disableSources --excludePrivate --excludeInternal --plugin typedoc-plugin-markdown --theme markdown --hideBreadcrumbs --entryDocument api_ref.md --readme none --hideGenerator --sort source-order" diff --git a/bindings/nodejs/src/client.rs b/bindings/nodejs/src/client.rs index b84f35ff31..dabaa925ea 100644 --- a/bindings/nodejs/src/client.rs +++ b/bindings/nodejs/src/client.rs @@ -8,26 +8,22 @@ use iota_sdk_bindings_core::{ iota_sdk::client::{mqtt::Topic, Client, ClientBuilder}, listen_mqtt as rust_listen_mqtt, ClientMethod, Response, }; -use napi::{bindgen_prelude::External, threadsafe_function::ThreadsafeFunction, Error, Result, Status}; +use napi::{bindgen_prelude::External, threadsafe_function::ThreadsafeFunction, Result}; use napi_derive::napi; use tokio::sync::RwLock; -use crate::NodejsError; +use crate::{build_js_error, destroyed_err, NodejsError}; pub type ClientMethodHandler = Arc>>; #[napi(js_name = "createClient")] -pub fn create_client(options: String) -> Result> { - let runtime = tokio::runtime::Runtime::new().map_err(NodejsError::from)?; - let client = runtime - .block_on( - ClientBuilder::new() - .from_json(&options) - .map_err(NodejsError::from)? - .finish(), - ) - .map_err(NodejsError::from)?; - +pub async fn create_client(options: String) -> Result> { + let client = ClientBuilder::new() + .from_json(&options) + .map_err(NodejsError::new)? + .finish() + .await + .map_err(NodejsError::new)?; Ok(External::new(Arc::new(RwLock::new(Some(client))))) } @@ -38,23 +34,17 @@ pub async fn destroy_client(client: External) { #[napi(js_name = "callClientMethod")] pub async fn call_client_method(client: External, method: String) -> Result { - let client_method = serde_json::from_str::(&method).map_err(NodejsError::from)?; + let method = serde_json::from_str::(&method).map_err(NodejsError::new)?; - if let Some(client) = &*client.as_ref().read().await { - let res = rust_call_client_method(client, client_method).await; - if matches!(res, Response::Error(_) | Response::Panic(_)) { - return Err(Error::new( - Status::GenericFailure, - serde_json::to_string(&res).map_err(NodejsError::from)?, - )); + match &*client.as_ref().read().await { + Some(client) => { + let response = rust_call_client_method(&client, method).await; + match response { + Response::Error(_) | Response::Panic(_) => Err(build_js_error(response)), + _ => Ok(serde_json::to_string(&response).map_err(NodejsError::new)?), + } } - - Ok(serde_json::to_string(&res).map_err(NodejsError::from)?) - } else { - Err(Error::new( - Status::GenericFailure, - serde_json::to_string(&Response::Panic("Client got destroyed".to_string())).map_err(NodejsError::from)?, - )) + None => Err(destroyed_err("Client")), } } @@ -66,22 +56,20 @@ pub async fn listen_mqtt( ) -> Result<()> { let mut validated_topics = Vec::with_capacity(topics.len()); for topic_string in topics { - validated_topics.push(Topic::new(topic_string).map_err(NodejsError::from)?); + validated_topics.push(Topic::new(topic_string).map_err(NodejsError::new)?); } - if let Some(client) = &*client.as_ref().read().await { - rust_listen_mqtt(client, validated_topics, move |event_data| { - callback.call( - Ok(event_data), - napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking, - ); - }) - .await; - Ok(()) - } else { - Err(Error::new( - Status::GenericFailure, - serde_json::to_string(&Response::Panic("Client got destroyed".to_string())).map_err(NodejsError::from)?, - )) + match &*client.as_ref().read().await { + Some(client) => { + rust_listen_mqtt(client, validated_topics, move |event_data| { + callback.call( + Ok(event_data), + napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking, + ); + }) + .await; + Ok(()) + } + None => Err(destroyed_err("Client")), } } diff --git a/bindings/nodejs/src/error.rs b/bindings/nodejs/src/error.rs new file mode 100644 index 0000000000..b9016e8079 --- /dev/null +++ b/bindings/nodejs/src/error.rs @@ -0,0 +1,33 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use iota_sdk_bindings_core::Response; +use napi::Error; + +#[non_exhaustive] +pub struct NodejsError(pub(crate) iota_sdk_bindings_core::Error); + +impl NodejsError { + pub fn new(err: impl Into) -> Self { + Self(err.into()) + } +} + +impl From for NodejsError { + fn from(error: iota_sdk_bindings_core::Error) -> Self { + Self(error) + } +} + +impl From for NodejsError { + fn from(error: serde_json::error::Error) -> Self { + Self(error.into()) + } +} + +// To the specific bindings glue error. +impl From for Error { + fn from(error: NodejsError) -> Self { + crate::build_js_error(Response::Error(error.0)) + } +} diff --git a/bindings/nodejs/src/lib.rs b/bindings/nodejs/src/lib.rs index f574b947ca..b850ab5c13 100644 --- a/bindings/nodejs/src/lib.rs +++ b/bindings/nodejs/src/lib.rs @@ -2,64 +2,34 @@ // SPDX-License-Identifier: Apache-2.0 pub mod client; +pub mod error; pub mod secret_manager; +pub mod utils; pub mod wallet; -use iota_sdk_bindings_core::{ - call_utils_method as rust_call_utils_method, init_logger as rust_init_logger, Response, UtilsMethod, -}; +use iota_sdk_bindings_core::{init_logger as rust_init_logger, Response}; use napi::{Error, Result, Status}; use napi_derive::napi; -#[derive(Debug, thiserror::Error)] -#[non_exhaustive] -pub enum NodejsError { - /// Bindings core errors. - #[error(transparent)] - Core(#[from] iota_sdk_bindings_core::Error), - /// Client errors. - #[error(transparent)] - Client(#[from] iota_sdk_bindings_core::iota_sdk::client::Error), - /// Mqtt errors. - #[error(transparent)] - Mqtt(#[from] iota_sdk_bindings_core::iota_sdk::client::node_api::mqtt::Error), - /// SerdeJson errors. - #[error(transparent)] - SerdeJson(#[from] serde_json::error::Error), - /// IO error. - #[error(transparent)] - Io(#[from] std::io::Error), - /// Wallet errors. - #[error(transparent)] - Wallet(#[from] iota_sdk_bindings_core::iota_sdk::wallet::Error), -} - -impl From for Error { - fn from(error: NodejsError) -> Self { - Error::new(Status::GenericFailure, error.to_string()) - } -} +pub use self::error::NodejsError; #[napi(js_name = "initLogger")] pub fn init_logger(config: String) -> Result<()> { match rust_init_logger(config) { Ok(_) => Ok(()), - Err(err) => Err(Error::new( - Status::GenericFailure, - serde_json::to_string(&Response::Panic(err.to_string())).map_err(NodejsError::from)?, - )), + Err(err) => Err(build_js_error(Response::Panic(err.to_string()))), } } -#[napi(js_name = "callUtilsMethodRust")] -pub fn call_utils_method(method_json: String) -> Result { - let method = match serde_json::from_str::(&method_json) { - Ok(method) => method, - Err(err) => { - return Ok(serde_json::to_string(&Response::Error(err.into())).map_err(NodejsError::from)?); - } - }; - let response = rust_call_utils_method(method); +// Util fn for making the "X was destroyed" error message. +pub(crate) fn destroyed_err(instance: &str) -> Error { + build_js_error(Response::Panic(format!("{} was destroyed", instance))) +} - Ok(serde_json::to_string(&response).map_err(NodejsError::from)?) +// Serializes a bindings response and puts it in a napi Error. +pub(crate) fn build_js_error(response: Response) -> Error { + Error::new( + Status::GenericFailure, + serde_json::to_string(&response).expect("json to string error"), + ) } diff --git a/bindings/nodejs/src/secret_manager.rs b/bindings/nodejs/src/secret_manager.rs index b7c39c4071..37176953ff 100644 --- a/bindings/nodejs/src/secret_manager.rs +++ b/bindings/nodejs/src/secret_manager.rs @@ -21,8 +21,8 @@ pub type SecretManagerMethodHandler = Arc>; #[napi(js_name = "createSecretManager")] pub fn create_secret_manager(options: String) -> Result> { - let secret_manager_dto = serde_json::from_str::(&options).map_err(NodejsError::from)?; - let secret_manager = SecretManager::try_from(secret_manager_dto).map_err(NodejsError::from)?; + let secret_manager_dto = serde_json::from_str::(&options).map_err(NodejsError::new)?; + let secret_manager = SecretManager::try_from(secret_manager_dto).map_err(NodejsError::new)?; Ok(External::new(Arc::new(RwLock::new(secret_manager)))) } @@ -32,17 +32,17 @@ pub async fn call_secret_manager_method( secret_manager: External, method: String, ) -> Result { - let secret_manager_method = serde_json::from_str::(&method).map_err(NodejsError::from)?; + let secret_manager_method = serde_json::from_str::(&method).map_err(NodejsError::new)?; let res = rust_call_secret_manager_method(&*secret_manager.as_ref().read().await, secret_manager_method).await; if matches!(res, Response::Error(_) | Response::Panic(_)) { return Err(Error::new( Status::GenericFailure, - serde_json::to_string(&res).map_err(NodejsError::from)?, + serde_json::to_string(&res).map_err(NodejsError::new)?, )); } - Ok(serde_json::to_string(&res).map_err(NodejsError::from)?) + Ok(serde_json::to_string(&res).map_err(NodejsError::new)?) } #[napi(js_name = "migrateStrongholdSnapshotV2ToV3")] @@ -66,7 +66,7 @@ pub fn migrate_stronghold_snapshot_v2_to_v3( new_password, ) .map_err(iota_sdk_bindings_core::iota_sdk::client::Error::from) - .map_err(NodejsError::from)?; + .map_err(NodejsError::new)?; Ok(()) } diff --git a/bindings/nodejs/src/utils.rs b/bindings/nodejs/src/utils.rs new file mode 100644 index 0000000000..e951c2c1d6 --- /dev/null +++ b/bindings/nodejs/src/utils.rs @@ -0,0 +1,18 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use iota_sdk_bindings_core::{call_utils_method as rust_call_utils_method, Response, UtilsMethod}; +use napi::Result; +use napi_derive::napi; + +use crate::{build_js_error, NodejsError}; + +#[napi(js_name = "callUtilsMethodRust")] +pub fn call_utils_method(method: String) -> Result { + let method = serde_json::from_str::(&method).map_err(NodejsError::new)?; + let response = rust_call_utils_method(method); + match response { + Response::Error(_) | Response::Panic(_) => Err(build_js_error(response)), + _ => Ok(serde_json::to_string(&response).map_err(NodejsError::new)?), + } +} diff --git a/bindings/nodejs/src/wallet.rs b/bindings/nodejs/src/wallet.rs index 140c9ca306..2bf3f65b1d 100644 --- a/bindings/nodejs/src/wallet.rs +++ b/bindings/nodejs/src/wallet.rs @@ -8,18 +8,20 @@ use iota_sdk_bindings_core::{ iota_sdk::wallet::{events::WalletEventType, Wallet}, Response, WalletMethod, WalletOptions, }; -use napi::{bindgen_prelude::External, threadsafe_function::ThreadsafeFunction, Error, Result, Status}; +use napi::{bindgen_prelude::External, threadsafe_function::ThreadsafeFunction, Error, Result}; use napi_derive::napi; use tokio::sync::RwLock; -use crate::{client::ClientMethodHandler, secret_manager::SecretManagerMethodHandler, NodejsError}; +use crate::{ + build_js_error, client::ClientMethodHandler, destroyed_err, secret_manager::SecretManagerMethodHandler, NodejsError, +}; pub type WalletMethodHandler = Arc>>; #[napi(js_name = "createWallet")] pub async fn create_wallet(options: String) -> Result> { - let wallet_options = serde_json::from_str::(&options).map_err(NodejsError::from)?; - let wallet = wallet_options.build().await.map_err(NodejsError::from)?; + let wallet_options = serde_json::from_str::(&options).map_err(NodejsError::new)?; + let wallet = wallet_options.build().await.map_err(NodejsError::new)?; Ok(External::new(Arc::new(RwLock::new(Some(wallet))))) } @@ -31,23 +33,17 @@ pub async fn destroy_wallet(wallet: External) { #[napi(js_name = "callWalletMethod")] pub async fn call_wallet_method(wallet: External, method: String) -> Result { - let wallet_method = serde_json::from_str::(&method).map_err(NodejsError::from)?; + let method = serde_json::from_str::(&method).map_err(NodejsError::new)?; - if let Some(wallet) = &*wallet.as_ref().read().await { - let res = rust_call_wallet_method(wallet, wallet_method).await; - if matches!(res, Response::Error(_) | Response::Panic(_)) { - return Err(Error::new( - Status::GenericFailure, - serde_json::to_string(&res).map_err(NodejsError::from)?, - )); + match &*wallet.as_ref().read().await { + Some(wallet) => { + let response = rust_call_wallet_method(&wallet, method).await; + match response { + Response::Error(_) | Response::Panic(_) => Err(build_js_error(response)), + _ => Ok(serde_json::to_string(&response).map_err(NodejsError::new)?), + } } - - Ok(serde_json::to_string(&res).map_err(NodejsError::from)?) - } else { - Err(Error::new( - Status::GenericFailure, - serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())).map_err(NodejsError::from)?, - )) + None => Err(destroyed_err("Wallet")), } } @@ -59,26 +55,24 @@ pub async fn listen_wallet( ) -> Result<()> { let mut validated_event_types = Vec::with_capacity(event_types.len()); for event_type in event_types { - validated_event_types.push(WalletEventType::try_from(event_type).map_err(NodejsError::from)?); + validated_event_types.push(WalletEventType::try_from(event_type).map_err(NodejsError::new)?); } - if let Some(wallet) = &*wallet.as_ref().read().await { - wallet - .listen(validated_event_types, move |event_data| { - callback.call( - serde_json::to_string(event_data) - .map_err(NodejsError::from) - .map_err(Error::from), - napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking, - ); - }) - .await; - Ok(()) - } else { - Err(Error::new( - Status::GenericFailure, - serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())).map_err(NodejsError::from)?, - )) + match &*wallet.as_ref().read().await { + Some(wallet) => { + wallet + .listen(validated_event_types, move |event_data| { + callback.call( + serde_json::to_string(event_data) + .map_err(NodejsError::new) + .map_err(Error::from), + napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking, + ); + }) + .await; + Ok(()) + } + None => Err(destroyed_err("Wallet")), } } @@ -87,10 +81,7 @@ pub async fn get_client(wallet: External) -> Result) -> Result if let Some(wallet) = &*wallet.as_ref().read().await { Ok(External::new(wallet.get_secret_manager().clone())) } else { - Err(Error::new( - Status::GenericFailure, - serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())).map_err(NodejsError::from)?, - )) + Err(destroyed_err("Wallet")) } } diff --git a/bindings/nodejs/tests/client/addresses.spec.ts b/bindings/nodejs/tests/client/addresses.spec.ts index cb4b2bfe7a..ad0feddc42 100644 --- a/bindings/nodejs/tests/client/addresses.spec.ts +++ b/bindings/nodejs/tests/client/addresses.spec.ts @@ -17,7 +17,7 @@ describe('Address tests', () => { it('calculates addresses according a fixture', async () => { for (const test of mnemonicAddressTestCases.general.address_generations) { - const secretManager = await new SecretManager({ + const secretManager = SecretManager.create({ mnemonic: test['mnemonic'] }); @@ -41,7 +41,7 @@ describe('Address tests', () => { }); it('generates addresses', async () => { - const addresses = await new SecretManager(secretManager).generateEd25519Addresses({ + const addresses = await SecretManager.create(secretManager).generateEd25519Addresses({ accountIndex: 0, range: { start: 0, diff --git a/bindings/nodejs/tests/client/examples.spec.ts b/bindings/nodejs/tests/client/examples.spec.ts index f18566a743..365f2fd882 100644 --- a/bindings/nodejs/tests/client/examples.spec.ts +++ b/bindings/nodejs/tests/client/examples.spec.ts @@ -11,20 +11,24 @@ import { TaggedDataPayload, CommonOutput, CoinType, + Ed25519Address, } from '../../'; import '../customMatchers'; import 'dotenv/config'; import * as addressOutputs from '../fixtures/addressOutputs.json'; +import { AddressUnlockCondition } from '../../lib'; -const client = new Client({ - nodes: [ - { - url: process.env.NODE_URL || 'http://localhost:14265', - }, - ], -}); +async function makeClient(): Promise { + return await Client.create({ + nodes: [ + { + url: 'http://localhost:8050', + }, + ], + }); +} -const secretManager = new SecretManager({ +const secretManager = SecretManager.create({ mnemonic: 'endorse answer radar about source reunion marriage tag sausage weekend frost daring base attack because joke dream slender leisure group reason prepare broken river', }); @@ -33,7 +37,7 @@ const issuerId = '0x0000000000000000000000000000000000000000000000000000000000000000'; const chain = { - coinType: CoinType.Iota, + coinType: CoinType.IOTA, account: 0, change: 0, addressIndex: 0, @@ -42,10 +46,11 @@ const chain = { // Skip for CI describe.skip('Main examples', () => { it('gets info about the node', async () => { + const client = await makeClient(); const info = await client.getInfo(); expect( - info.nodeInfo.protocolParameters[0].parameters[0].bech32Hrp, + info.nodeInfo.protocolParameters[0].parameters.bech32Hrp, ).toBe('rms'); }); @@ -57,7 +62,7 @@ describe.skip('Main examples', () => { // TODO // it('generates addresses', async () => { - // const addresses = await new SecretManager( + // const addresses = await SecretManager.create( // secretManager, // ).generateEd25519Addresses({ // accountIndex: 0, @@ -76,15 +81,13 @@ describe.skip('Main examples', () => { // }); it('gets address outputs', async () => { - const outputIdsResponse = await client.basicOutputIds([ - { - address: - 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', - }, - { hasExpiration: false }, - { hasTimelock: false }, - { hasStorageDepositReturn: false }, - ]); + const client = await makeClient(); + const outputIdsResponse = await client.basicOutputIds({ + address: 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', + hasExpiration: false, + hasTimelock: false, + hasStorageDepositReturn: false + }); outputIdsResponse.items.forEach((id) => expect(id).toBeValidOutputId()); @@ -98,6 +101,7 @@ describe.skip('Main examples', () => { }); it('gets the output of a known output ID', async () => { + const client = await makeClient(); const output = await client.getOutput( '0xc1d95ac9c8c0237c6929faf427556c3562055a7155c6d336ee7891691d5525c90100', ); @@ -116,13 +120,14 @@ describe.skip('Main examples', () => { }); expect(addresses[0]).toBeValidAddress(); + const client = await makeClient(); // Get output ids of outputs that can be controlled by this address without further unlock constraints - const outputIdsResponse = await client.basicOutputIds([ - { address: addresses[0] }, - { hasExpiration: false }, - { hasTimelock: false }, - { hasStorageDepositReturn: false }, - ]); + const outputIdsResponse = await client.basicOutputIds({ + address: addresses[0], + hasExpiration: false, + hasTimelock: false, + hasStorageDepositReturn: false, + }); outputIdsResponse.items.forEach((id) => expect(id).toBeValidOutputId()); // Get outputs by their IDs @@ -139,14 +144,10 @@ describe.skip('Main examples', () => { for (const outputResponse of testOutputs) { const output = outputResponse['output']; if (output instanceof CommonOutput) { - (output as CommonOutput) - .getNativeTokens() - ?.forEach( - (token) => - (totalNativeTokens[token.id] = - (totalNativeTokens[token.id] || 0) + - Number(token.amount)), - ); + const token = (output as CommonOutput).getNativeToken(); + if (token) { + totalNativeTokens[token.id] = (totalNativeTokens[token.id] || 0) + Number(token.amount); + } } totalAmount += Number(output.getAmount()); @@ -169,10 +170,12 @@ describe.skip('Main examples', () => { // }); it('gets block data', async () => { + const client = await makeClient(); const tips = await client.getTips(); + const params = await client.getProtocolParameters(); const blockData = await client.getBlock(tips[0]); - const blockId = Utils.blockId(blockData); + const blockId = Utils.blockId(blockData, params); expect(tips[0]).toStrictEqual(blockId); const blockMetadata = await client.getBlockMetadata(tips[0]); @@ -180,6 +183,7 @@ describe.skip('Main examples', () => { }); it('sends a block with a tagged data payload', async () => { + const client = await makeClient(); const unsignedBlock = await client.buildBasicBlock( issuerId, new TaggedDataPayload(utf8ToHex('Hello'), utf8ToHex('Tangle')), @@ -189,8 +193,46 @@ describe.skip('Main examples', () => { const fetchedBlock = await client.getBlock(blockId); - expect(fetchedBlock.payload).toStrictEqual( + expect(fetchedBlock.body.asBasic().payload).toStrictEqual( new TaggedDataPayload(utf8ToHex('Hello'), utf8ToHex('Tangle')), ); }); + + it('sends a transaction', async () => { + const client = await makeClient(); + const addresses = await secretManager.generateEd25519Addresses({ + range: { + start: 1, + end: 2, + }, + }); + + const basicOutput = await client.buildBasicOutput({ + amount: BigInt(1000000), + unlockConditions: [ + new AddressUnlockCondition( + new Ed25519Address(addresses[0]), + ), + ], + }); + + //let payload = await secretManager.signTransaction(prepared); + const unsignedBlock = await client.buildBasicBlock("", undefined); + const signedBlock = await secretManager.signBlock(unsignedBlock, chain); + const blockId = await client.postBlock(signedBlock); + + expect(blockId).toBeValidBlockId(); + }); + + it('destroy', async () => { + const client = await makeClient(); + await client.destroy(); + + try { + const _info = await client.getInfo(); + throw 'Should return an error because the client was destroyed'; + } catch (err: any) { + expect(err.message).toEqual('Client was destroyed'); + } + }) }); diff --git a/bindings/nodejs/tests/client/infoMethods.spec.ts b/bindings/nodejs/tests/client/infoMethods.spec.ts index 210b5d61e4..afeeb165de 100644 --- a/bindings/nodejs/tests/client/infoMethods.spec.ts +++ b/bindings/nodejs/tests/client/infoMethods.spec.ts @@ -8,23 +8,27 @@ import 'dotenv/config'; import { Client } from '../../lib/client'; import '../customMatchers'; -const client = new Client({ - nodes: [ - { - url: process.env.NODE_URL || 'http://localhost:14265', - }, - ], -}); +async function makeClient(): Promise { + return await Client.create({ + nodes: [ + { + url: process.env.NODE_URL || 'http://localhost:8050', + }, + ], + }); +} // Skip for CI describe.skip('Client info methods', () => { it('gets a node candidate from the synced node pool', async () => { + const client = await makeClient(); const nodeInfo = await client.getNode(); expect(nodeInfo.disabled).not.toBeTruthy(); }); it('gets info about node by url', async () => { + const client = await makeClient(); const nodeInfo = await client.getNode(); const nodeInfoByUrl = await client.getNodeInfo(nodeInfo.url); @@ -33,6 +37,7 @@ describe.skip('Client info methods', () => { }); it('gets health of node with input url', async () => { + const client = await makeClient(); const nodeInfo = await client.getNode(); const nodeHealth = await client.getHealth(nodeInfo.url); @@ -40,37 +45,43 @@ describe.skip('Client info methods', () => { expect(nodeHealth).toBeTruthy(); }); - it('gets the unhealthy nodes', async () => { + it('gets the unhealty nodes', async () => { + const client = await makeClient(); const unhealthyNodes = await client.unhealthyNodes(); expect(unhealthyNodes).toBeDefined(); }); it('gets tips', async () => { + const client = await makeClient(); const tips = await client.getTips(); expect(tips.length).toBeGreaterThan(0); }); it('gets peers', async () => { + const client = await makeClient(); await expect(client.getPeers()).rejects.toMatch( 'missing or malformed jwt', ); }); it('gets networkInfo', async () => { + const client = await makeClient(); const networkInfo = await client.getNetworkInfo(); expect(networkInfo.protocolParameters.bech32Hrp).toBe('rms'); }); it('gets networkId', async () => { + const client = await makeClient(); const networkId = await client.getNetworkId(); expect(networkId).toBeDefined(); }); it('gets bech32Hrp', async () => { + const client = await makeClient(); const bech32Hrp = await client.getBech32Hrp(); expect(bech32Hrp).toBeDefined(); diff --git a/bindings/nodejs/tests/client/messageMethods.spec.ts b/bindings/nodejs/tests/client/messageMethods.spec.ts index e938689042..65420d5125 100644 --- a/bindings/nodejs/tests/client/messageMethods.spec.ts +++ b/bindings/nodejs/tests/client/messageMethods.spec.ts @@ -14,15 +14,17 @@ import { } from '../../'; import '../customMatchers'; -const client = new Client({ - nodes: [ - { - url: process.env.NODE_URL || 'http://localhost:14265', - }, - ], -}); +async function makeClient(): Promise { + return await Client.create({ + nodes: [ + { + url: process.env.NODE_URL || 'http://localhost:8050', + }, + ], + }); +} -const secretManager = new SecretManager({ +const secretManager = SecretManager.create({ mnemonic: 'endorse answer radar about source reunion marriage tag sausage weekend frost daring base attack because joke dream slender leisure group reason prepare broken river', }); @@ -31,7 +33,7 @@ const issuerId = '0x0000000000000000000000000000000000000000000000000000000000000000'; const chain = { - coinType: CoinType.Iota, + coinType: CoinType.IOTA, account: 0, change: 0, addressIndex: 0, @@ -40,6 +42,7 @@ const chain = { // Skip for CI describe.skip('Block methods', () => { it('sends a block raw', async () => { + const client = await makeClient(); const unsignedBlock = await client.buildBasicBlock( issuerId, new TaggedDataPayload(utf8ToHex('Hello'), utf8ToHex('Tangle')), @@ -52,6 +55,7 @@ describe.skip('Block methods', () => { }); it('finds blocks by block IDs', async () => { + const client = await makeClient(); const blockIds = await client.getTips(); const blocks = await client.findBlocks(blockIds); @@ -59,6 +63,7 @@ describe.skip('Block methods', () => { }); it('gets block as raw bytes', async () => { + const client = await makeClient(); const tips = await client.getTips(); const blockRaw = await client.getBlockRaw(tips[0]); diff --git a/bindings/nodejs/tests/client/outputBuilders.spec.ts b/bindings/nodejs/tests/client/outputBuilders.spec.ts index ef9b7c4879..074b69700d 100644 --- a/bindings/nodejs/tests/client/outputBuilders.spec.ts +++ b/bindings/nodejs/tests/client/outputBuilders.spec.ts @@ -5,16 +5,18 @@ import { describe, it } from '@jest/globals'; import 'reflect-metadata'; import 'dotenv/config'; -import { AddressUnlockCondition, AccountAddress, Client, SecretManager, Ed25519Address, ImmutableAccountAddressUnlockCondition, SimpleTokenScheme, Utils } from '../../'; +import { AddressUnlockCondition, AccountAddress, Client, SecretManager, Ed25519Address, ImmutableAccountAddressUnlockCondition, SimpleTokenScheme, Utils } from '../../lib'; import '../customMatchers'; -const client = new Client({ - nodes: [ - { - url: process.env.NODE_URL || 'http://localhost:14265', - }, - ], -}); +async function makeClient(): Promise { + return Client.create({ + nodes: [ + { + url: process.env.NODE_URL || 'http://localhost:8050', + }, + ], + }); +} const secretManager = { mnemonic: @@ -24,7 +26,7 @@ const secretManager = { // Skip for CI describe.skip('Output builder methods', () => { it('builds a basic output', async () => { - const addresses = await new SecretManager(secretManager).generateEd25519Addresses({ + const addresses = await SecretManager.create(secretManager).generateEd25519Addresses({ range: { start: 0, end: 1, @@ -32,6 +34,7 @@ describe.skip('Output builder methods', () => { }); const hexAddress = Utils.bech32ToHex(addresses[0]); + const client = await makeClient(); // most simple basic output const basicOutput = await client.buildBasicOutput({ @@ -47,7 +50,7 @@ describe.skip('Output builder methods', () => { }); it('builds an account output', async () => { - const addresses = await new SecretManager(secretManager).generateEd25519Addresses({ + const addresses = await SecretManager.create(secretManager).generateEd25519Addresses({ range: { start: 0, end: 1, @@ -55,6 +58,7 @@ describe.skip('Output builder methods', () => { }); const hexAddress = Utils.bech32ToHex(addresses[0]); + const client = await makeClient(); const accountId = '0xa5c28d5baa951de05e375fb19134ea51a918f03acc2d0cee011a42b298d3effa'; @@ -72,6 +76,7 @@ describe.skip('Output builder methods', () => { }); it('builds a foundry output', async () => { + const client = await makeClient(); const accountId = '0xa5c28d5baa951de05e375fb19134ea51a918f03acc2d0cee011a42b298d3effa'; @@ -97,7 +102,8 @@ describe.skip('Output builder methods', () => { }); it('builds an nft output', async () => { - const addresses = await new SecretManager(secretManager).generateEd25519Addresses({ + const client = await makeClient(); + const addresses = await SecretManager.create(secretManager).generateEd25519Addresses({ range: { start: 0, end: 1, diff --git a/bindings/nodejs/tests/client/utilityMethods.spec.ts b/bindings/nodejs/tests/client/utilityMethods.spec.ts index 7168c66669..99845edd58 100644 --- a/bindings/nodejs/tests/client/utilityMethods.spec.ts +++ b/bindings/nodejs/tests/client/utilityMethods.spec.ts @@ -9,7 +9,9 @@ import { Client, SecretManager, Utils } from '../../'; import '../customMatchers'; import { SlotCommitment } from '../../out/types/block/slot'; -const offlineClient = new Client({}); +async function makeOfflineClient(): Promise { + return await Client.create({}); +} describe('Client utility methods', () => { // Requires "stronghold" in cargo toml iota-client features @@ -23,7 +25,7 @@ describe('Client utility methods', () => { }, }; await expect( - new SecretManager(strongholdSecretManager).storeMnemonic(mnemonic), + SecretManager.create(strongholdSecretManager).storeMnemonic(mnemonic), ).resolves.toBe(null); }); @@ -32,7 +34,7 @@ describe('Client utility methods', () => { Utils.verifyMnemonic('invalid mnemonic '.repeat(12)); throw 'should error'; } catch (e: any) { - expect(e.payload.error).toContain('NoSuchWord'); + expect(e.message).toContain('NoSuchWord'); } }); @@ -43,6 +45,7 @@ describe('Client utility methods', () => { expect(hexAddress.slice(0, 2)).toBe('0x'); + let offlineClient = await makeOfflineClient(); const bech32Address = await offlineClient.hexToBech32( hexAddress, 'rms', @@ -71,12 +74,12 @@ describe('Client utility methods', () => { it('hash output id', async () => { const outputId = - '0x00000000000000000000000000000000000000000000000000000000000000000000'; + '0x0000000000000000000000000000000000000000000000000000000000000000000000000000'; const accountId = Utils.computeAccountId(outputId); expect(accountId).toBe( - '0xcf077d276686ba64c0404b9eb2d15556782113c5a1985f262b70f9964d3bbd7f', + '0x0ebc2867a240719a70faacdfc3840e857fa450b37d95297ac4f166c2f70c3345', ); }); @@ -84,6 +87,7 @@ describe('Client utility methods', () => { const accountId = '0xcf077d276686ba64c0404b9eb2d15556782113c5a1985f262b70f9964d3bbd7f'; + const offlineClient = await makeOfflineClient(); const accountAddress = await offlineClient.accountIdToBech32( accountId, 'rms', @@ -118,7 +122,7 @@ describe('Client utility methods', () => { // `IOTA` hex encoded const message = '0x494f5441'; - const signature = await new SecretManager(secretManager).signEd25519( + const signature = await SecretManager.create(secretManager).signEd25519( message, { coinType: 4218, @@ -134,14 +138,16 @@ describe('Client utility methods', () => { it('slot commitment id', async () => { let slotCommitment = new SlotCommitment( - BigInt(10), - "0x20e07a0ea344707d69a08b90be7ad14eec8326cf2b8b86c8ec23720fab8dcf8ec43a30e4a8cc3f1f", + 1, + 10, + "0x20e07a0ea344707d69a08b90be7ad14eec8326cf2b8b86c8ec23720fab8dcf8ec43a30e4", "0xcf077d276686ba64c0404b9eb2d15556782113c5a1985f262b70f9964d3bbd7f", - BigInt(5) + BigInt(5), + BigInt(6000) ); let id = Utils.computeSlotCommitmentId(slotCommitment); expect(id).toBe( - '0xb485446277cc5111d54f443b46d886945d4af64e53c2f04064a7c2ea88fa4e020a00000000000000' + '0x1d1470e10ed1c498c88002d57d6eaa0db38a31347e1aa5e957300a48967f0ca40a000000' ); }); }); diff --git a/bindings/nodejs/tests/client/utxoMethods.spec.ts b/bindings/nodejs/tests/client/utxoMethods.spec.ts index 1725585908..a3f032e791 100644 --- a/bindings/nodejs/tests/client/utxoMethods.spec.ts +++ b/bindings/nodejs/tests/client/utxoMethods.spec.ts @@ -7,50 +7,56 @@ import 'dotenv/config'; import { Client } from '../../lib/client'; import '../customMatchers'; -const client = new Client({ - nodes: [ - { - url: process.env.NODE_URL || 'http://localhost:14265', - }, - ], -}); +async function makeClient(): Promise { + return await Client.create({ + nodes: [ + { + url: process.env.NODE_URL || 'http://localhost:8050', + }, + ], + }); +} // Skip for CI describe.skip('UTXO methods', () => { it('gets accounts output IDs', async () => { - const accountsOutputIds = await client.accountOutputIds([ + const client = await makeClient(); + const accountsOutputIds = await client.accountOutputIds( { address: 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', }, - ]); + ); expect(accountsOutputIds).toBeDefined(); }); it('gets nfts output IDs', async () => { - const nftsOutputIds = await client.nftOutputIds([ + const client = await makeClient(); + const nftsOutputIds = await client.nftOutputIds( { address: 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', }, - ]); + ); expect(nftsOutputIds).toBeDefined(); }); it('gets foundries output IDs', async () => { - const foundriesOutputIds = await client.foundryOutputIds([ + const client = await makeClient(); + const foundriesOutputIds = await client.foundryOutputIds( { - hasNativeTokens: true, + hasNativeToken: true, }, - ]); + ); expect(foundriesOutputIds).toBeDefined(); }); // TODO: get valid IDs to test with it('get account/nft/foundry outputId rejects with 404 for invalid IDs', async () => { + const client = await makeClient(); await expect( client.accountOutputId( '0x03119f37e7ad40608fc7ab15db49390abc233648c95e78141ff2e298f60d7a95', diff --git a/bindings/nodejs/tests/wallet/wallet.spec.ts b/bindings/nodejs/tests/wallet/wallet.spec.ts index 5f77b5352b..43db94253b 100644 --- a/bindings/nodejs/tests/wallet/wallet.spec.ts +++ b/bindings/nodejs/tests/wallet/wallet.spec.ts @@ -18,7 +18,7 @@ describe('Wallet', () => { }, }; - const secretManager = new SecretManager(strongholdSecretManager); + const secretManager = SecretManager.create(strongholdSecretManager); await secretManager.storeMnemonic('vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim',); @@ -47,9 +47,9 @@ describe('Wallet', () => { const wallet = await Wallet.create(walletOptions); - await wallet.destroy() - removeDir(storagePath) - }, 8000); + await wallet.destroy(); + removeDir(storagePath); + }, 20000); it('recreate wallet', async () => { @@ -63,7 +63,7 @@ describe('Wallet', () => { }, }; - const secretManager = new SecretManager(strongholdSecretManager); + const secretManager = SecretManager.create(strongholdSecretManager); await secretManager.storeMnemonic('vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim',); @@ -79,7 +79,7 @@ describe('Wallet', () => { const walletOptions: WalletOptions = { address: wallet_address[0], - storagePath: './test-recreate-wallet', + storagePath, clientOptions: { nodes: ['https://api.testnet.shimmer.network'], }, @@ -98,11 +98,71 @@ describe('Wallet', () => { await wallet.destroy(); - const recreatedWallet = await Wallet.create({ storagePath: './test-recreate-wallet' }); + const recreatedWallet = await Wallet.create({ storagePath }); + + // TODO: make add test to verify wallet equality + // expect(accounts.length).toStrictEqual(0); - await recreatedWallet.destroy() + await recreatedWallet.destroy(); removeDir(storagePath) }, 20000); + + it('error after destroy', async () => { + let storagePath = 'test-error-after-destroy'; + removeDir(storagePath); + + const strongholdSecretManager = { + stronghold: { + snapshotPath: `./${storagePath}/wallet.stronghold`, + password: `A12345678*`, + }, + } + const secretManager = await SecretManager.create(strongholdSecretManager); + await secretManager.storeMnemonic( + 'vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim', + ); + + const wallet_address = await secretManager.generateEd25519Addresses({ + coinType: CoinType.IOTA, + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: 'tst', + }); + + const walletOptions = { + address: wallet_address[0], + storagePath, + clientOptions: { + nodes: ['https://api.testnet.shimmer.network'], + }, + bipPath: { + coinType: CoinType.IOTA, + }, + secretManager: strongholdSecretManager, + }; + + const wallet = await Wallet.create(walletOptions); + + await wallet.destroy(); + + try { + const _accounts = await wallet.accounts(); + throw 'Should return an error because the wallet was destroyed'; + } catch (err: any) { + expect(err.message).toEqual('Wallet was destroyed'); + } + + try { + const _client = await wallet.getClient(); + throw 'Should return an error because the wallet was destroyed'; + } catch (err: any) { + expect(err.message).toEqual('Wallet was destroyed'); + } + removeDir(storagePath); + }, 35000); }) function removeDir(storagePath: string) { diff --git a/bindings/nodejs/tsconfig.eslint.json b/bindings/nodejs/tsconfig.eslint.json new file mode 100644 index 0000000000..81377c69a7 --- /dev/null +++ b/bindings/nodejs/tsconfig.eslint.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "lib": ["es2019", "DOM"], + "declaration": true, + "declarationMap": true, + "target": "es2019", + "module": "commonjs", + "sourceMap": true, + "strict": true, + "moduleResolution": "node", + "noImplicitAny": true, + "removeComments": false, + "preserveConstEnums": true, + "outDir": "out", + "resolveJsonModule": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["lib/**/*", "tests", "examples"] +} diff --git a/bindings/nodejs/tsconfig.json b/bindings/nodejs/tsconfig.json index a0af53a7fd..4811852d47 100644 --- a/bindings/nodejs/tsconfig.json +++ b/bindings/nodejs/tsconfig.json @@ -17,5 +17,5 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true }, - "include": ["lib/**/*", "test"] + "include": ["lib/**/*"] } diff --git a/bindings/python/src/wallet.rs b/bindings/python/src/wallet.rs index 3a5e1ed57e..2dd2f39733 100644 --- a/bindings/python/src/wallet.rs +++ b/bindings/python/src/wallet.rs @@ -49,7 +49,7 @@ pub fn call_wallet_method(wallet: &Wallet, method: String) -> Result { let response = crate::block_on(async { match wallet.wallet.read().await.as_ref() { Some(wallet) => rust_call_wallet_method(wallet, method).await, - None => Response::Panic("wallet got destroyed".into()), + None => Response::Panic("wallet was destroyed".into()), } }); @@ -77,7 +77,7 @@ pub fn listen_wallet(wallet: &Wallet, events: Vec, handler: PyObject) { .read() .await .as_ref() - .expect("wallet got destroyed") + .expect("wallet was destroyed") .listen(rust_events, move |event| { let event_string = serde_json::to_string(&event).expect("json to string error"); Python::with_gil(|py| { @@ -101,7 +101,7 @@ pub fn get_client_from_wallet(wallet: &Wallet) -> Result { .map(|w| w.client().clone()) .ok_or_else(|| { Error::from( - serde_json::to_string(&Response::Panic("wallet got destroyed".into())) + serde_json::to_string(&Response::Panic("wallet was destroyed".into())) .expect("json to string error") .as_str(), ) @@ -123,7 +123,7 @@ pub fn get_secret_manager_from_wallet(wallet: &Wallet) -> Result .map(|w| w.get_secret_manager().clone()) .ok_or_else(|| { Error::from( - serde_json::to_string(&Response::Panic("wallet got destroyed".into())) + serde_json::to_string(&Response::Panic("wallet was destroyed".into())) .expect("json to string error") .as_str(), ) diff --git a/bindings/wasm/.eslintignore b/bindings/wasm/.eslintignore index d077138ac9..393108abcf 100644 --- a/bindings/wasm/.eslintignore +++ b/bindings/wasm/.eslintignore @@ -6,3 +6,6 @@ target/ **/dist node/ web/ + +jest.config.js +.eslintrc.js \ No newline at end of file diff --git a/bindings/wasm/.eslintrc.js b/bindings/wasm/.eslintrc.js index 2dfae3d82e..8e9e1b5e74 100644 --- a/bindings/wasm/.eslintrc.js +++ b/bindings/wasm/.eslintrc.js @@ -1,5 +1,6 @@ module.exports = { env: { + node: true, commonjs: true, es2019: true, }, @@ -13,6 +14,7 @@ module.exports = { parserOptions: { ecmaVersion: 12, sourceType: 'module', + project: ["./tsconfig.eslint.json"], }, rules: { '@typescript-eslint/no-var-requires': 0, @@ -21,5 +23,6 @@ module.exports = { { 'ts-ignore': 'allow-with-description' }, ], '@typescript-eslint/no-explicit-any': 'off', + "@typescript-eslint/no-floating-promises": ["error"], }, }; diff --git a/bindings/wasm/README.md b/bindings/wasm/README.md index 97b0ba2269..46dee57411 100644 --- a/bindings/wasm/README.md +++ b/bindings/wasm/README.md @@ -129,7 +129,7 @@ const {Client, initLogger} = require('@iota/sdk-wasm/node'); async function run() { initLogger(); - const client = new Client({ + const client = await Client.create({ nodes: ['https://api.testnet.shimmer.network'], localPow: true, }); @@ -142,7 +142,7 @@ async function run() { } } -run().then(() => process.exit()); +void run().then(() => process.exit()); ``` ### Web @@ -153,7 +153,7 @@ import init, {Client, initLogger} from "@iota/sdk-wasm/web"; init().then(async () => { initLogger(); - const client = new Client({ + const client = await Client.create({ nodes: ['https://api.testnet.shimmer.network'], localPow: true, }); @@ -205,7 +205,7 @@ async function run() { } catch (err) { console.error } } -run().then(() => process.exit()); +void run().then(() => process.exit()); ``` ### Web diff --git a/bindings/wasm/examples/node.js b/bindings/wasm/examples/node.js index 8ef73904bf..b937c83a28 100644 --- a/bindings/wasm/examples/node.js +++ b/bindings/wasm/examples/node.js @@ -17,7 +17,7 @@ async function run() { 'inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak', }; - const secretManager = new SecretManager(mnemonicSecretManager); + const secretManager = SecretManager.create(mnemonicSecretManager); const walletAddress = await secretManager.generateEd25519Addresses({ coinType: CoinType.IOTA, @@ -47,4 +47,4 @@ async function run() { console.log(balance); } -run(); +void run().then(() => process.exit()); diff --git a/bindings/wasm/jest.config.js b/bindings/wasm/jest.config.js index 3c4eec2148..1c570b15c4 100644 --- a/bindings/wasm/jest.config.js +++ b/bindings/wasm/jest.config.js @@ -2,7 +2,7 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', - testMatch: ['/test/**/*.(test|spec).ts'], + testMatch: ['/tests/**/*.(test|spec).ts'], moduleNameMapper: { 'index.node': '/index.node', }, diff --git a/bindings/wasm/lib/bindings.ts b/bindings/wasm/lib/bindings.ts index 15310014c5..276ecfa830 100644 --- a/bindings/wasm/lib/bindings.ts +++ b/bindings/wasm/lib/bindings.ts @@ -9,7 +9,7 @@ import { __UtilsMethods__ } from './utils'; // Import needs to be in a single line, otherwise it breaks // prettier-ignore // @ts-ignore: path is set to match runtime transpiled js path when bundled. -import { initLogger, createClient, destroyClient, createSecretManager, createWallet, callClientMethod, callSecretManagerMethod, callUtilsMethodRust, callWalletMethod, destroyWallet, listenWallet, getClientFromWallet, getSecretManagerFromWallet, listenMqtt, migrateStrongholdSnapshotV2ToV3 } from '../wasm/iota_sdk_wasm'; +import { initLogger, getClient, createClient, destroyClient, createSecretManager, createWallet, callClientMethod, callSecretManagerMethod, callUtilsMethodRust, callWalletMethod, destroyWallet, listenWallet, getSecretManager, listenMqtt, migrateStrongholdSnapshotV2ToV3 } from '../wasm/iota_sdk_wasm'; const callUtilsMethod = (method: __UtilsMethods__): any => { const response = JSON.parse(callUtilsMethodRust(JSON.stringify(method))); @@ -32,8 +32,8 @@ export { listenWallet, destroyWallet, destroyClient, - getClientFromWallet, - getSecretManagerFromWallet, + getClient, + getSecretManager, listenMqtt, migrateStrongholdSnapshotV2ToV3, }; diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index 18dae558bf..377b3b3b80 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -57,7 +57,7 @@ "typedoc": "^0.24.0", "typedoc-plugin-markdown": "^3.13.4", "typescript": "^4.7.4", - "wasm-opt": "^1.3.0" + "wasm-opt": "^1.4.0" }, "resolutions": { "decode-uri-component": "^0.2.1", diff --git a/bindings/wasm/src/client.rs b/bindings/wasm/src/client.rs index fe1dbbbbb9..ce9150c930 100644 --- a/bindings/wasm/src/client.rs +++ b/bindings/wasm/src/client.rs @@ -1,45 +1,50 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::sync::Arc; + use iota_sdk_bindings_core::{ call_client_method as rust_call_client_method, iota_sdk::client::{Client, ClientBuilder}, - ClientMethod, Response, + Response, }; -use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; -use wasm_bindgen_futures::future_to_promise; +use tokio::sync::RwLock; +use wasm_bindgen::{prelude::wasm_bindgen, JsError}; -use crate::{ArrayString, PromiseString}; +use crate::{build_js_error, destroyed_err, map_err, ArrayString}; /// The Client method handler. #[wasm_bindgen(js_name = ClientMethodHandler)] -pub struct ClientMethodHandler { - pub(crate) client: Client, +pub struct ClientMethodHandler(Arc>>); + +impl ClientMethodHandler { + pub(crate) fn new(client: Client) -> Self { + Self(Arc::new(RwLock::new(Some(client)))) + } } /// Creates a method handler with the given client options. #[wasm_bindgen(js_name = createClient)] -#[allow(non_snake_case)] -pub fn create_client(clientOptions: String) -> Result { - let runtime = tokio::runtime::Builder::new_current_thread() - .build() - .map_err(|err| err.to_string())?; +pub async fn create_client(options: String) -> Result { + let client = ClientBuilder::new() + .from_json(&options) + .map_err(map_err)? + .finish() + .await + .map_err(map_err)?; - let client = runtime.block_on(async move { - ClientBuilder::new() - .from_json(&clientOptions) - .map_err(|err| err.to_string())? - .finish() - .await - .map_err(|err| err.to_string()) - })?; - - Ok(ClientMethodHandler { client }) + Ok(ClientMethodHandler(Arc::new(RwLock::new(Some(client))))) } /// Necessary for compatibility with the node.js bindings. #[wasm_bindgen(js_name = destroyClient)] -pub async fn destroy_client(_client_method_handler: &ClientMethodHandler) -> Result<(), JsValue> { +pub async fn destroy_client(method_handler: &ClientMethodHandler) -> Result<(), JsError> { + let mut lock = method_handler.0.write().await; + if let Some(_) = &*lock { + *lock = None; + } + + // If None, was already destroyed Ok(()) } @@ -47,23 +52,19 @@ pub async fn destroy_client(_client_method_handler: &ClientMethodHandler) -> Res /// /// Returns an error if the response itself is an error or panic. #[wasm_bindgen(js_name = callClientMethod)] -#[allow(non_snake_case)] -pub fn call_client_method(methodHandler: &ClientMethodHandler, method: String) -> Result { - let client: Client = methodHandler.client.clone(); - - let promise: js_sys::Promise = future_to_promise(async move { - let method: ClientMethod = serde_json::from_str(&method).map_err(|err| err.to_string())?; - - let response = rust_call_client_method(&client, method).await; - let ser = JsValue::from(serde_json::to_string(&response).map_err(|err| err.to_string())?); - match response { - Response::Error(_) | Response::Panic(_) => Err(ser), - _ => Ok(ser), +pub async fn call_client_method(method_handler: &ClientMethodHandler, method: String) -> Result { + let method = serde_json::from_str(&method).map_err(map_err)?; + match &*method_handler.0.read().await { + Some(client) => { + let response = rust_call_client_method(&client, method).await; + let ser = serde_json::to_string(&response)?; + match response { + Response::Error(_) | Response::Panic(_) => Err(JsError::new(&ser)), + _ => Ok(ser), + } } - }); - - // WARNING: this does not validate the return type. Check carefully. - Ok(promise.unchecked_into()) + None => Err(destroyed_err("Client")), + } } /// MQTT is not supported for WebAssembly bindings. @@ -71,8 +72,8 @@ pub fn call_client_method(methodHandler: &ClientMethodHandler, method: String) - /// Throws an error if called, only included for compatibility /// with the Node.js bindings TypeScript definitions. #[wasm_bindgen(js_name = listenMqtt)] -pub fn listen_mqtt(_topics: ArrayString, _callback: &js_sys::Function) -> Result<(), JsValue> { - let js_error = js_sys::Error::new("Client MQTT not supported for WebAssembly"); - - Err(JsValue::from(js_error)) +pub async fn listen_mqtt(_topics: ArrayString, _callback: &js_sys::Function) -> Result<(), JsError> { + Err(build_js_error(Response::Panic( + "Client MQTT not supported for WebAssembly".to_string(), + ))) } diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index cf5528046a..7de7d9b3cf 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -8,12 +8,12 @@ pub mod secret_manager; pub mod utils; pub mod wallet; -use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; +use wasm_bindgen::{prelude::wasm_bindgen, JsError}; /// Initializes the console error panic hook for better panic messages. /// Gets automatically called when using wasm #[wasm_bindgen(start)] -pub fn start() -> Result<(), JsValue> { +pub fn start() -> Result<(), JsError> { console_error_panic_hook::set_once(); Ok(()) } @@ -22,7 +22,7 @@ pub fn start() -> Result<(), JsValue> { /// /// Calling this will enable all rust logs to be show #[wasm_bindgen(js_name = initLogger)] -pub async fn init_logger(_config: String) -> Result<(), JsValue> { +pub async fn init_logger(_config: String) -> Result<(), JsError> { wasm_logger::init(wasm_logger::Config::default()); Ok(()) } @@ -31,7 +31,27 @@ pub async fn init_logger(_config: String) -> Result<(), JsValue> { extern "C" { #[wasm_bindgen(typescript_type = "string[]")] pub type ArrayString; +} + +// Maps a bindings error into the proper js error +pub(crate) fn map_err(err: T) -> JsError +where + T: Into, +{ + build_js_error(iota_sdk_bindings_core::Response::Error(err.into())) +} + +pub(crate) fn destroyed_err(instance: &str) -> JsError { + build_js_error(iota_sdk_bindings_core::Response::Panic(format!( + "{} was destroyed", + instance + ))) +} - #[wasm_bindgen(typescript_type = "Promise")] - pub type PromiseString; +// Serializes a bindings response and puts it in a JsError +pub(crate) fn build_js_error(response: T) -> JsError +where + T: Into, +{ + JsError::new(&serde_json::to_string(&response.into()).expect("json to string error")) } diff --git a/bindings/wasm/src/secret_manager.rs b/bindings/wasm/src/secret_manager.rs index 65f7fb9b06..894625fe38 100644 --- a/bindings/wasm/src/secret_manager.rs +++ b/bindings/wasm/src/secret_manager.rs @@ -6,58 +6,48 @@ use std::sync::Arc; use iota_sdk_bindings_core::{ call_secret_manager_method as rust_call_secret_manager_method, iota_sdk::client::secret::{SecretManager, SecretManagerDto}, - Response, SecretManagerMethod, + Response, }; use tokio::sync::RwLock; -use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; -use wasm_bindgen_futures::future_to_promise; +use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue}; -use crate::PromiseString; +use crate::{build_js_error, map_err}; /// The SecretManager method handler. #[wasm_bindgen(js_name = SecretManagerMethodHandler)] -pub struct SecretManagerMethodHandler { - pub(crate) secret_manager: Arc>, +pub struct SecretManagerMethodHandler(Arc>); + +impl SecretManagerMethodHandler { + pub(crate) fn new(secret_manager: Arc>) -> Self { + Self(secret_manager) + } } /// Creates a method handler with the given secret_manager options. #[wasm_bindgen(js_name = createSecretManager)] -#[allow(non_snake_case)] pub fn create_secret_manager(options: String) -> Result { - let secret_manager_dto = serde_json::from_str::(&options).map_err(|err| err.to_string())?; - let secret_manager = SecretManager::try_from(secret_manager_dto).map_err(|err| err.to_string())?; + let secret_manager_dto = serde_json::from_str::(&options).map_err(map_err)?; + let secret_manager = SecretManager::try_from(secret_manager_dto).map_err(map_err)?; - Ok(SecretManagerMethodHandler { - secret_manager: Arc::new(RwLock::new(secret_manager)), - }) + Ok(SecretManagerMethodHandler(Arc::new(RwLock::new(secret_manager)))) } /// Handles a method, returns the response as a JSON-encoded string. /// /// Returns an error if the response itself is an error or panic. #[wasm_bindgen(js_name = callSecretManagerMethod)] -#[allow(non_snake_case)] -pub fn call_secret_manager_method( - methodHandler: &SecretManagerMethodHandler, +pub async fn call_secret_manager_method( + method_handler: &SecretManagerMethodHandler, method: String, -) -> Result { - let secret_manager = methodHandler.secret_manager.clone(); - let promise: js_sys::Promise = future_to_promise(async move { - let method: SecretManagerMethod = serde_json::from_str(&method).map_err(|err| err.to_string())?; - - let response = { - let secret_manager = secret_manager.read().await; - rust_call_secret_manager_method(&*secret_manager, method).await - }; - let ser = JsValue::from(serde_json::to_string(&response).map_err(|err| err.to_string())?); - match response { - Response::Error(_) | Response::Panic(_) => Err(ser), - _ => Ok(ser), - } - }); - - // WARNING: this does not validate the return type. Check carefully. - Ok(promise.unchecked_into()) +) -> Result { + let method = serde_json::from_str(&method).map_err(map_err)?; + let secret_manager = &*method_handler.0.read().await; + let response = rust_call_secret_manager_method(secret_manager, method).await; + let ser = serde_json::to_string(&response).map_err(map_err)?; + match response { + Response::Error(_) | Response::Panic(_) => Err(JsError::new(&ser)), + _ => Ok(ser), + } } /// Stronghold snapshot migration is not supported for WebAssembly bindings. @@ -72,8 +62,8 @@ pub fn migrate_stronghold_snapshot_v2_to_v3( _rounds: u32, _new_path: Option, _new_password: Option, -) -> Result<(), JsValue> { - let js_error = js_sys::Error::new("Stronghold snapshot migration is not supported for WebAssembly"); - - Err(JsValue::from(js_error)) +) -> Result<(), JsError> { + Err(build_js_error(Response::Panic( + "Stronghold snapshot migration is not supported for WebAssembly".to_string(), + ))) } diff --git a/bindings/wasm/src/utils.rs b/bindings/wasm/src/utils.rs index d2045548a4..3ee511c298 100644 --- a/bindings/wasm/src/utils.rs +++ b/bindings/wasm/src/utils.rs @@ -1,18 +1,22 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk_bindings_core::{call_utils_method as rust_call_utils_method, UtilsMethod}; -use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; +use iota_sdk_bindings_core::{call_utils_method as rust_call_utils_method, Response, UtilsMethod}; +use wasm_bindgen::{prelude::wasm_bindgen, JsError}; + +use crate::map_err; /// Handles a method, returns the response as a JSON-encoded string. /// /// Returns an error if the response itself is an error or panic. #[wasm_bindgen(js_name = callUtilsMethodRust)] #[allow(non_snake_case)] -pub fn call_utils_method(method: String) -> Result { - let method: UtilsMethod = serde_json::from_str(&method).map_err(|err| err.to_string())?; +pub fn call_utils_method(method: String) -> Result { + let method: UtilsMethod = serde_json::from_str(&method).map_err(map_err)?; let response = rust_call_utils_method(method); - Ok(JsValue::from( - serde_json::to_string(&response).map_err(|err| err.to_string())?, - )) + let ser = serde_json::to_string(&response).map_err(map_err)?; + match response { + Response::Error(_) | Response::Panic(_) => Err(JsError::new(&ser)), + _ => Ok(ser), + } } diff --git a/bindings/wasm/src/wallet.rs b/bindings/wasm/src/wallet.rs index 425b5f3fd2..a22cc5df41 100644 --- a/bindings/wasm/src/wallet.rs +++ b/bindings/wasm/src/wallet.rs @@ -9,82 +9,77 @@ use iota_sdk_bindings_core::{ events::types::{WalletEvent, WalletEventType}, Wallet, }, - Response, WalletMethod, WalletOptions, + Response, WalletOptions, }; use tokio::sync::{ mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, - Mutex, + RwLock, }; -use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; +use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue}; -use crate::{client::ClientMethodHandler, secret_manager::SecretManagerMethodHandler}; +use crate::{client::ClientMethodHandler, destroyed_err, map_err, secret_manager::SecretManagerMethodHandler}; /// The Wallet method handler. #[wasm_bindgen(js_name = WalletMethodHandler)] -pub struct WalletMethodHandler { - wallet: Arc>>, -} +pub struct WalletMethodHandler(Arc>>); /// Creates a method handler with the given options. #[wasm_bindgen(js_name = createWallet)] #[allow(non_snake_case)] -pub async fn create_wallet(options: String) -> Result { - let wallet_options = serde_json::from_str::(&options).map_err(|e| e.to_string())?; - - let wallet_method_handler = wallet_options.build().await.map_err(|e| e.to_string())?; +pub async fn create_wallet(options: String) -> Result { + let wallet_options: WalletOptions = serde_json::from_str::(&options).map_err(map_err)?; + let wallet_method_handler = wallet_options.build().await.map_err(map_err)?; - Ok(WalletMethodHandler { - wallet: Arc::new(Mutex::new(Some(wallet_method_handler))), - }) + Ok(WalletMethodHandler(Arc::new(RwLock::new(Some(wallet_method_handler))))) } #[wasm_bindgen(js_name = destroyWallet)] -pub async fn destroy_wallet(method_handler: &WalletMethodHandler) -> Result<(), JsValue> { - *method_handler.wallet.lock().await = None; +pub async fn destroy_wallet(method_handler: &WalletMethodHandler) -> Result<(), JsError> { + let mut lock = method_handler.0.write().await; + if let Some(_) = &*lock { + *lock = None; + } + + // If None, was already destroyed Ok(()) } -#[wasm_bindgen(js_name = getClientFromWallet)] -pub async fn get_client(method_handler: &WalletMethodHandler) -> Result { - let client = method_handler - .wallet - .lock() - .await - .as_ref() - .ok_or_else(|| "wallet got destroyed".to_string())? - .client() - .clone(); - - Ok(ClientMethodHandler { client }) +#[wasm_bindgen(js_name = getClient)] +pub async fn get_client(method_handler: &WalletMethodHandler) -> Result { + if let Some(wallet) = &*method_handler.0.read().await { + Ok(ClientMethodHandler::new(wallet.client().clone())) + } else { + // Notify that the wallet was destroyed + Err(destroyed_err("Wallet")) + } } -#[wasm_bindgen(js_name = getSecretManagerFromWallet)] -pub async fn get_secret_manager(method_handler: &WalletMethodHandler) -> Result { - let secret_manager = method_handler - .wallet - .lock() - .await - .as_ref() - .ok_or_else(|| "wallet got destroyed".to_string())? - .get_secret_manager() - .clone(); - - Ok(SecretManagerMethodHandler { secret_manager }) +#[wasm_bindgen(js_name = getSecretManager)] +pub async fn get_secret_manager(method_handler: &WalletMethodHandler) -> Result { + if let Some(wallet) = &*method_handler.0.read().await { + Ok(SecretManagerMethodHandler::new(wallet.get_secret_manager().clone())) + } else { + // Notify that the wallet was destroyed + Err(destroyed_err("Wallet")) + } } /// Handles a method, returns the response as a JSON-encoded string. /// /// Returns an error if the response itself is an error or panic. #[wasm_bindgen(js_name = callWalletMethod)] -pub async fn call_wallet_method(method_handler: &WalletMethodHandler, method: String) -> Result { - let wallet = method_handler.wallet.lock().await; - let method: WalletMethod = serde_json::from_str(&method).map_err(|err| err.to_string())?; - - let response = rust_call_wallet_method(wallet.as_ref().expect("wallet got destroyed"), method).await; - match response { - Response::Error(e) => Err(e.to_string().into()), - Response::Panic(p) => Err(p.into()), - _ => Ok(serde_json::to_string(&response).map_err(|e| e.to_string())?), +pub async fn call_wallet_method(method_handler: &WalletMethodHandler, method: String) -> Result { + let method = serde_json::from_str(&method).map_err(map_err)?; + match &*method_handler.0.read().await { + Some(wallet) => { + let response = rust_call_wallet_method(&wallet, method).await; + let ser = serde_json::to_string(&response)?; + match response { + Response::Error(_) | Response::Panic(_) => Err(JsError::new(&ser)), + _ => Ok(ser), + } + } + None => Err(destroyed_err("Wallet")), } } @@ -101,41 +96,43 @@ pub async fn listen_wallet( method_handler: &WalletMethodHandler, vec: js_sys::Array, callback: js_sys::Function, -) -> Result { +) -> Result { let mut event_types = Vec::with_capacity(vec.length() as _); for event_type in vec.keys() { // We know the built-in iterator for set elements won't throw // exceptions, so just unwrap the element. let event_type = event_type.unwrap().as_f64().unwrap() as u8; - let wallet_event_type = WalletEventType::try_from(event_type).map_err(|err| err.to_string())?; + let wallet_event_type = WalletEventType::try_from(event_type).map_err(map_err)?; event_types.push(wallet_event_type); } - let (tx, mut rx): (UnboundedSender, UnboundedReceiver) = unbounded_channel(); - method_handler - .wallet - .lock() - .await - .as_ref() - .expect("wallet not initialised") - .listen(event_types, move |wallet_event| { - tx.send(wallet_event.clone()).unwrap(); - }) - .await; - - // Spawn on the same thread a continuous loop to check the channel - wasm_bindgen_futures::spawn_local(async move { - while let Some(wallet_event) = rx.recv().await { - callback - .call1( + if let Some(wallet) = &*method_handler.0.read().await { + let (tx, mut rx): (UnboundedSender, UnboundedReceiver) = unbounded_channel(); + wallet + .listen(event_types, move |wallet_event| { + tx.send(wallet_event.clone()).unwrap(); + }) + .await; + + // Spawn on the same thread a continuous loop to check the channel + wasm_bindgen_futures::spawn_local(async move { + while let Some(wallet_event) = rx.recv().await { + let res = callback.call2( &JsValue::NULL, + &JsValue::UNDEFINED, &JsValue::from(serde_json::to_string(&wallet_event).unwrap()), - ) - // Safe to unwrap, our callback has no return - .unwrap(); - } - // No more links to the unbounded_channel, exit loop - }); - - Ok(JsValue::UNDEFINED) + ); + // Call callback again with the error this time, to prevent wasm crashing. + // This does mean the callback is called a second time instead of once. + if let Err(e) = res { + callback.call2(&JsValue::NULL, &e, &JsValue::UNDEFINED).unwrap(); + } + } + // No more links to the unbounded_channel, exit loop + }); + Ok(JsValue::UNDEFINED) + } else { + // Notify that the wallet was destroyed + Err(destroyed_err("Wallet")) + } } diff --git a/bindings/wasm/test/wallet.spec.ts b/bindings/wasm/test/wallet.spec.ts deleted file mode 100644 index 2ccd9cbf58..0000000000 --- a/bindings/wasm/test/wallet.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { Balance, Wallet, CoinType, SecretManager } from '../node/lib'; - -async function run() { - const mnemonicSecretManager = { - mnemonic: - 'inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak', - }; - - const secretManager = new SecretManager(mnemonicSecretManager); - - const walletAddress = await secretManager.generateEd25519Addresses({ - coinType: CoinType.IOTA, - accountIndex: 0, - range: { - start: 0, - end: 1, - }, - bech32Hrp: 'rms', - }); - - const wallet = await Wallet.create({ - address: walletAddress[0], - bipPath: { - coinType: CoinType.IOTA, - }, - clientOptions: { - nodes: ['http://localhost:8050'], - }, - secretManager: mnemonicSecretManager, - }); - - expect(await wallet.address()).toBe( - 'rms1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3utrrg7c', - ); - - const balance: Balance = await wallet.sync(); - expect(balance.baseCoin.available).not.toBeNaN(); -} - -describe('wallet tests', () => { - jest.setTimeout(10000); - it('wallet', async () => { - await run(); - }); -}); diff --git a/bindings/wasm/test/utilityMethods.spec.ts b/bindings/wasm/tests/utilityMethods.spec.ts similarity index 97% rename from bindings/wasm/test/utilityMethods.spec.ts rename to bindings/wasm/tests/utilityMethods.spec.ts index 357c222513..5c56aa27ce 100644 --- a/bindings/wasm/test/utilityMethods.spec.ts +++ b/bindings/wasm/tests/utilityMethods.spec.ts @@ -12,7 +12,7 @@ describe('Utils methods', () => { Utils.verifyMnemonic('invalid mnemonic '.repeat(12)); throw 'should error'; } catch (e) { - expect(e.payload.error).toContain('NoSuchWord'); + expect(e.message).toContain('NoSuchWord'); } }); diff --git a/bindings/wasm/tests/wallet.spec.ts b/bindings/wasm/tests/wallet.spec.ts new file mode 100644 index 0000000000..26fa8454b2 --- /dev/null +++ b/bindings/wasm/tests/wallet.spec.ts @@ -0,0 +1,44 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { Wallet, CoinType, SecretManager } from '../node/lib'; + +describe('wallet tests', () => { + jest.setTimeout(100000); + it('wallet', async () => { + const mnemonicSecretManager = { + mnemonic: + 'inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak', + }; + + const secretManager = SecretManager.create(mnemonicSecretManager); + + const walletAddress = await secretManager.generateEd25519Addresses({ + coinType: CoinType.IOTA, + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: 'rms', + }); + + const wallet = await Wallet.create({ + address: walletAddress[0], + bipPath: { + coinType: CoinType.IOTA, + }, + clientOptions: { + nodes: ['http://localhost:8050'], + }, + secretManager: mnemonicSecretManager, + }); + + const implicitAccountCreationAddress = + await wallet.implicitAccountCreationAddress(); + + expect(implicitAccountCreationAddress).toBe( + 'rms1yrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3uvx3u3m', + ); + }); +}); diff --git a/bindings/wasm/tsconfig.eslint.json b/bindings/wasm/tsconfig.eslint.json new file mode 100644 index 0000000000..93692a3820 --- /dev/null +++ b/bindings/wasm/tsconfig.eslint.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "lib": ["ES2020", "DOM"], + "declaration": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "node", + "noImplicitAny": true, + "preserveConstEnums": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "experimentalDecorators": true + }, + "include": ["out/**/*", "lib", "examples", "tests"], + "exclude": ["node_modules", "out/test/*"] +} diff --git a/cli/README.md b/cli/README.md index 35d05d6520..ba0ca8c71a 100644 --- a/cli/README.md +++ b/cli/README.md @@ -9,7 +9,7 @@ After downloading the CLI, initialize the signer for the wallet. On Mac and Linu ``` ./wallet init --node http://node.url:port --mnemonic MNEMONIC // Example: -./wallet init --node "http://localhost:14265" --mnemonic "giant dynamic museum toddler six deny defense ostrich bomb access mercy +./wallet init --node "http://localhost:8050" --mnemonic "giant dynamic museum toddler six deny defense ostrich bomb access mercy blood explain muscle shoot shallow glad autumn author calm heavy hawk abuse rally" ``` diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 250f10bb85..36bdb8bbf9 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -345,7 +345,7 @@ required-features = ["wallet", "stronghold"] [[example]] name = "implicit_account_creation" path = "examples/how_tos/account/implicit_account_creation.rs" -required-features = ["wallet"] +required-features = ["wallet", "storage"] # Outputs diff --git a/sdk/examples/client/client_config.rs b/sdk/examples/client/client_config.rs index 4ee841601b..943542e3b0 100644 --- a/sdk/examples/client/client_config.rs +++ b/sdk/examples/client/client_config.rs @@ -18,7 +18,7 @@ async fn main() -> Result<()> { r#"{ "nodes":[ { - "url":"http://localhost:14265/", + "url":"http://localhost:8050/", "auth":null, "disabled":false }, diff --git a/sdk/examples/client/quorum.rs b/sdk/examples/client/quorum.rs index 06b5233ef7..19bd29faa9 100644 --- a/sdk/examples/client/quorum.rs +++ b/sdk/examples/client/quorum.rs @@ -4,7 +4,7 @@ //! In this example we will get outputs with quorum, which will compare the responses from the nodes. //! //! Make sure to have 3 nodes available for this example to run successfully, e.g.: -//! - http://localhost:14265 +//! - http://localhost:8050 //! - http://your-vps:14265 //! - https://api.testnet.shimmer.network //! diff --git a/sdk/src/client/mod.rs b/sdk/src/client/mod.rs index 70d9a06c14..297757809d 100644 --- a/sdk/src/client/mod.rs +++ b/sdk/src/client/mod.rs @@ -14,7 +14,7 @@ //! # #[tokio::main] //! # async fn main() -> Result<(), Box> { //! let client = Client::builder() -//! .with_node("http://localhost:14265")? +//! .with_node("http://localhost:8050")? //! .finish() //! .await?; //! let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC")?)?; diff --git a/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs b/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs index 264962396b..83632aaca1 100644 --- a/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs +++ b/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs @@ -7,7 +7,7 @@ use crate::{ wallet::{ core::{WalletData, WalletDataDto}, migration::{latest_backup_migration_version, migrate, MIGRATION_VERSION_KEY}, - ClientOptions, Error as WalletError, Wallet, + ClientOptions, Wallet, }, }; diff --git a/sdk/tests/client/client_builder.rs b/sdk/tests/client/client_builder.rs index 22b07a23f1..b5f7853e87 100644 --- a/sdk/tests/client/client_builder.rs +++ b/sdk/tests/client/client_builder.rs @@ -14,7 +14,7 @@ async fn invalid_url() { #[tokio::test] async fn valid_url() { - let client = Client::builder().with_node("http://localhost:14265"); + let client = Client::builder().with_node("http://localhost:8050"); assert!(client.is_ok()); } @@ -23,7 +23,7 @@ async fn client_builder() { let client_builder_json = serde_json::json!({ "nodes": [ { - "url":"http://localhost:14265/", + "url":"http://localhost:8050/", "disabled": false } ], diff --git a/sdk/tests/wallet/common/constants.rs b/sdk/tests/wallet/common/constants.rs index d6f42ee747..b1160641c1 100644 --- a/sdk/tests/wallet/common/constants.rs +++ b/sdk/tests/wallet/common/constants.rs @@ -3,7 +3,7 @@ #![allow(dead_code)] -pub static NODE_LOCAL: &str = "http://localhost:14265"; +pub static NODE_LOCAL: &str = "http://localhost:8050"; pub static NODE_OTHER: &str = "http://some.not.default.node:14265"; pub static DEFAULT_MNEMONIC: &str = "inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak";