From c3100b7ad63661d7a5b508106e6d43e3df114dce Mon Sep 17 00:00:00 2001 From: thephez Date: Tue, 17 Mar 2026 16:08:54 -0400 Subject: [PATCH 1/5] chore: remove old sdk files --- .../identity-retrieve-account-ids.js | 14 ------ .../identity-update-add-key.js | 45 ------------------- .../identity-update-disable-key.js | 27 ----------- 3 files changed, 86 deletions(-) delete mode 100644 1-Identities-and-Names/identity-retrieve-account-ids.js delete mode 100644 1-Identities-and-Names/identity-update-add-key.js delete mode 100644 1-Identities-and-Names/identity-update-disable-key.js diff --git a/1-Identities-and-Names/identity-retrieve-account-ids.js b/1-Identities-and-Names/identity-retrieve-account-ids.js deleted file mode 100644 index ea14451..0000000 --- a/1-Identities-and-Names/identity-retrieve-account-ids.js +++ /dev/null @@ -1,14 +0,0 @@ -// See https://docs.dash.org/projects/platform/en/stable/docs/tutorials/identities-and-names/retrieve-an-accounts-identities.html -const setupDashClient = require('../setupDashClient'); - -const client = setupDashClient(); - -const retrieveIdentityIds = async () => { - const account = await client.getWalletAccount(); - return account.identities.getIdentityIds(); -}; - -retrieveIdentityIds() - .then((d) => console.log('Mnemonic identities:\n', d)) - .catch((e) => console.error('Something went wrong:\n', e)) - .finally(() => client.disconnect()); diff --git a/1-Identities-and-Names/identity-update-add-key.js b/1-Identities-and-Names/identity-update-add-key.js deleted file mode 100644 index c2672a8..0000000 --- a/1-Identities-and-Names/identity-update-add-key.js +++ /dev/null @@ -1,45 +0,0 @@ -// See https://docs.dash.org/projects/platform/en/stable/docs/tutorials/identities-and-names/update-an-identity.html -const Dash = require('dash'); -const { - PlatformProtocol: { IdentityPublicKey, IdentityPublicKeyWithWitness }, -} = Dash; -const setupDashClient = require('../setupDashClient'); - -const client = setupDashClient(); - -const updateIdentityAddKey = async () => { - const identityId = process.env.IDENTITY_ID; - const existingIdentity = await client.platform.identities.get(identityId); - const newKeyId = existingIdentity.toJSON().publicKeys.length; - - // Get an unused identity index - const account = await client.platform.client.getWalletAccount(); - const identityIndex = await account.getUnusedIdentityIndex(); - - // Get unused private key and construct new identity public key - const { privateKey: identityPrivateKey } = - account.identities.getIdentityHDKeyByIndex(identityIndex, 0); - - const identityPublicKey = identityPrivateKey.toPublicKey().toBuffer(); - - const newPublicKey = new IdentityPublicKeyWithWitness(1); - newPublicKey.setId(newKeyId); - newPublicKey.setSecurityLevel(IdentityPublicKey.SECURITY_LEVELS.HIGH); - newPublicKey.setData(identityPublicKey); - - const updateAdd = { - add: [newPublicKey], - }; - - // Submit the update signed with the new key - await client.platform.identities.update(existingIdentity, updateAdd, { - [newPublicKey.getId()]: identityPrivateKey, - }); - - return client.platform.identities.get(identityId); -}; - -updateIdentityAddKey() - .then((d) => console.log('Identity updated:\n', d.toJSON())) - .catch((e) => console.error('Something went wrong:\n', e)) - .finally(() => client.disconnect()); diff --git a/1-Identities-and-Names/identity-update-disable-key.js b/1-Identities-and-Names/identity-update-disable-key.js deleted file mode 100644 index 10bf494..0000000 --- a/1-Identities-and-Names/identity-update-disable-key.js +++ /dev/null @@ -1,27 +0,0 @@ -// See https://docs.dash.org/projects/platform/en/stable/docs/tutorials/identities-and-names/update-an-identity.html -const setupDashClient = require('../setupDashClient'); - -const client = setupDashClient(); - -const updateIdentityDisableKey = async () => { - const identityId = process.env.IDENTITY_ID; - const keyId = 4; // One of the identity's public key IDs - - // Retrieve the identity to be updated and the public key to disable - const existingIdentity = await client.platform.identities.get(identityId); - // console.log(existingIdentity.toJSON()) - const publicKeyToDisable = existingIdentity.getPublicKeyById(keyId); - // console.log(publicKeyToDisable) - - const updateDisable = { - disable: [publicKeyToDisable], - }; - - await client.platform.identities.update(existingIdentity, updateDisable); - return client.platform.identities.get(identityId); -}; - -updateIdentityDisableKey() - .then((d) => console.log('Identity updated:\n', d.toJSON())) - .catch((e) => console.error('Something went wrong:\n', e)) - .finally(() => client.disconnect()); From a9c41cd5117f0f07f713d7305a5e67f4df1c8d2b Mon Sep 17 00:00:00 2001 From: thephez Date: Tue, 17 Mar 2026 16:09:15 -0400 Subject: [PATCH 2/5] chore: fix main in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d0021e2..836e051 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "platform-tutorials", "version": "3.1-dev", "description": "Tutorial code for https://docs.dash.org/platform", - "main": "connect.js", + "main": "connect.mjs", "scripts": { "fmt": "npx prettier@2 --write '**/*.{md,js,mjs}'", "lint": "npx -p typescript@4 tsc", From 7cc136e5e2012de873afbb34f1a4c3984c7ec418 Mon Sep 17 00:00:00 2001 From: thephez Date: Tue, 17 Mar 2026 16:12:41 -0400 Subject: [PATCH 3/5] fix: use || instead of ?? for env var defaults to handle empty strings ?? only falls back on null/undefined, so an empty env var (e.g. DATA_CONTRACT_ID='') would be used as-is instead of the intended default. --- 1-Identities-and-Names/identity-transfer-credits.mjs | 2 +- 1-Identities-and-Names/identity-update-disable-key.mjs | 2 +- 1-Identities-and-Names/identity-withdraw-credits.mjs | 2 +- 1-Identities-and-Names/name-register.mjs | 2 +- 2-Contracts-and-Documents/contract-retrieve-history.mjs | 2 +- 2-Contracts-and-Documents/contract-retrieve.mjs | 2 +- 2-Contracts-and-Documents/contract-update-history.mjs | 2 +- 2-Contracts-and-Documents/contract-update-minimal.mjs | 2 +- 2-Contracts-and-Documents/document-delete.mjs | 4 ++-- 2-Contracts-and-Documents/document-retrieve.mjs | 2 +- 2-Contracts-and-Documents/document-submit.mjs | 2 +- 2-Contracts-and-Documents/document-update.mjs | 4 ++-- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/1-Identities-and-Names/identity-transfer-credits.mjs b/1-Identities-and-Names/identity-transfer-credits.mjs index e765b69..2ce639f 100644 --- a/1-Identities-and-Names/identity-transfer-credits.mjs +++ b/1-Identities-and-Names/identity-transfer-credits.mjs @@ -6,7 +6,7 @@ const { identity, signer } = await keyManager.getTransfer(); // Default recipient (testnet). Replace or override via RECIPIENT_ID. const recipientId = - process.env.RECIPIENT_ID ?? '7XcruVSsGQVSgTcmPewaE4tXLutnW1F6PXxwMbo8GYQC'; + process.env.RECIPIENT_ID || '7XcruVSsGQVSgTcmPewaE4tXLutnW1F6PXxwMbo8GYQC'; const transferAmount = 100000n; // Credits to transfer try { diff --git a/1-Identities-and-Names/identity-update-disable-key.mjs b/1-Identities-and-Names/identity-update-disable-key.mjs index 0b6a0ec..e676dff 100644 --- a/1-Identities-and-Names/identity-update-disable-key.mjs +++ b/1-Identities-and-Names/identity-update-disable-key.mjs @@ -5,7 +5,7 @@ const { sdk, keyManager } = await setupDashClient(); const { identity, signer } = await keyManager.getMaster(); // Replace with one of the identity's existing public key IDs -const DISABLE_KEY_ID = Number(process.env.DISABLE_KEY_ID ?? 99); +const DISABLE_KEY_ID = Number(process.env.DISABLE_KEY_ID || 99); console.log( `Disabling key ${DISABLE_KEY_ID} on identity ${keyManager.identityId}...`, diff --git a/1-Identities-and-Names/identity-withdraw-credits.mjs b/1-Identities-and-Names/identity-withdraw-credits.mjs index 4ae1b24..a614c4f 100644 --- a/1-Identities-and-Names/identity-withdraw-credits.mjs +++ b/1-Identities-and-Names/identity-withdraw-credits.mjs @@ -8,7 +8,7 @@ console.log('Identity balance before withdrawal:', identity.balance); // Default: testnet faucet address. Replace or override via WITHDRAWAL_ADDRESS. const toAddress = - process.env.WITHDRAWAL_ADDRESS ?? 'yXWJGWuD4VBRMp9n2MtXQbGpgSeWyTRHme'; + process.env.WITHDRAWAL_ADDRESS || 'yXWJGWuD4VBRMp9n2MtXQbGpgSeWyTRHme'; const amount = 190000n; // Credits to withdraw const amountDash = Number(amount) / (1000 * 100000000); diff --git a/1-Identities-and-Names/name-register.mjs b/1-Identities-and-Names/name-register.mjs index bfdfb83..1288a69 100644 --- a/1-Identities-and-Names/name-register.mjs +++ b/1-Identities-and-Names/name-register.mjs @@ -5,7 +5,7 @@ const { sdk, keyManager } = await setupDashClient(); const { identity, identityKey, signer } = await keyManager.getAuth(); // ⚠️ Change this to a unique name to register -const NAME_LABEL = process.env.NAME_LABEL ?? 'alice'; +const NAME_LABEL = process.env.NAME_LABEL || 'alice'; try { // Register a DPNS name for the identity diff --git a/2-Contracts-and-Documents/contract-retrieve-history.mjs b/2-Contracts-and-Documents/contract-retrieve-history.mjs index 04ea3ac..2549808 100644 --- a/2-Contracts-and-Documents/contract-retrieve-history.mjs +++ b/2-Contracts-and-Documents/contract-retrieve-history.mjs @@ -4,7 +4,7 @@ const { sdk } = await setupDashClient(); // Default tutorial contract with history (testnet). Replace or override via DATA_CONTRACT_ID. const DATA_CONTRACT_ID = - process.env.DATA_CONTRACT_ID ?? + process.env.DATA_CONTRACT_ID || '5J4VPym1Bnc2Ap9bbo9wNw6fZLGsCzDM7ZScdzcggN1r'; try { diff --git a/2-Contracts-and-Documents/contract-retrieve.mjs b/2-Contracts-and-Documents/contract-retrieve.mjs index 8c0df0e..612d934 100644 --- a/2-Contracts-and-Documents/contract-retrieve.mjs +++ b/2-Contracts-and-Documents/contract-retrieve.mjs @@ -5,7 +5,7 @@ const { sdk } = await setupDashClient(); // Default tutorial contract (testnet). Replace or override via DATA_CONTRACT_ID. const DATA_CONTRACT_ID = - process.env.DATA_CONTRACT_ID ?? + process.env.DATA_CONTRACT_ID || 'FW3DHrQiG24VqzPY4ARenMgjEPpBNuEQTZckV8hbVCG4'; try { diff --git a/2-Contracts-and-Documents/contract-update-history.mjs b/2-Contracts-and-Documents/contract-update-history.mjs index 17a4d80..52cb201 100644 --- a/2-Contracts-and-Documents/contract-update-history.mjs +++ b/2-Contracts-and-Documents/contract-update-history.mjs @@ -7,7 +7,7 @@ const { identityKey, signer } = await keyManager.getAuth(); // Edit these values for your environment // Your contract ID from the Register a Data Contract tutorial const DATA_CONTRACT_ID = - process.env.DATA_CONTRACT_ID ?? 'YOUR_DATA_CONTRACT_ID'; + process.env.DATA_CONTRACT_ID || 'YOUR_DATA_CONTRACT_ID'; const DOCUMENT_TYPE = 'note'; if (!DATA_CONTRACT_ID || DATA_CONTRACT_ID === 'YOUR_DATA_CONTRACT_ID') { diff --git a/2-Contracts-and-Documents/contract-update-minimal.mjs b/2-Contracts-and-Documents/contract-update-minimal.mjs index 55ba32f..df0ca11 100644 --- a/2-Contracts-and-Documents/contract-update-minimal.mjs +++ b/2-Contracts-and-Documents/contract-update-minimal.mjs @@ -7,7 +7,7 @@ const { identityKey, signer } = await keyManager.getAuth(); // Edit these values for your environment // Your contract ID from the Register a Data Contract tutorial const DATA_CONTRACT_ID = - process.env.DATA_CONTRACT_ID ?? 'YOUR_DATA_CONTRACT_ID'; + process.env.DATA_CONTRACT_ID || 'YOUR_DATA_CONTRACT_ID'; const DOCUMENT_TYPE = 'note'; if (!DATA_CONTRACT_ID || DATA_CONTRACT_ID === 'YOUR_DATA_CONTRACT_ID') { diff --git a/2-Contracts-and-Documents/document-delete.mjs b/2-Contracts-and-Documents/document-delete.mjs index 3d6c59d..55bff9f 100644 --- a/2-Contracts-and-Documents/document-delete.mjs +++ b/2-Contracts-and-Documents/document-delete.mjs @@ -6,11 +6,11 @@ const { identity, identityKey, signer } = await keyManager.getAuth(); // Default tutorial contract (testnet). Replace or override via DATA_CONTRACT_ID. const DATA_CONTRACT_ID = - process.env.DATA_CONTRACT_ID ?? + process.env.DATA_CONTRACT_ID || 'FW3DHrQiG24VqzPY4ARenMgjEPpBNuEQTZckV8hbVCG4'; // Replace with your existing document ID -const DOCUMENT_ID = process.env.DOCUMENT_ID ?? 'YOUR_DOCUMENT_ID'; +const DOCUMENT_ID = process.env.DOCUMENT_ID || 'YOUR_DOCUMENT_ID'; try { // Delete the document from the platform diff --git a/2-Contracts-and-Documents/document-retrieve.mjs b/2-Contracts-and-Documents/document-retrieve.mjs index e37c0c0..3a8a13a 100644 --- a/2-Contracts-and-Documents/document-retrieve.mjs +++ b/2-Contracts-and-Documents/document-retrieve.mjs @@ -5,7 +5,7 @@ const { sdk } = await setupDashClient(); // Default tutorial contract (testnet). Replace or override via DATA_CONTRACT_ID. const DATA_CONTRACT_ID = - process.env.DATA_CONTRACT_ID ?? + process.env.DATA_CONTRACT_ID || 'FW3DHrQiG24VqzPY4ARenMgjEPpBNuEQTZckV8hbVCG4'; try { diff --git a/2-Contracts-and-Documents/document-submit.mjs b/2-Contracts-and-Documents/document-submit.mjs index c671420..9ae2564 100644 --- a/2-Contracts-and-Documents/document-submit.mjs +++ b/2-Contracts-and-Documents/document-submit.mjs @@ -7,7 +7,7 @@ const { identity, identityKey, signer } = await keyManager.getAuth(); // Default tutorial contract (testnet). Replace or override via DATA_CONTRACT_ID. const DATA_CONTRACT_ID = - process.env.DATA_CONTRACT_ID ?? + process.env.DATA_CONTRACT_ID || 'FW3DHrQiG24VqzPY4ARenMgjEPpBNuEQTZckV8hbVCG4'; try { diff --git a/2-Contracts-and-Documents/document-update.mjs b/2-Contracts-and-Documents/document-update.mjs index 683fe45..10db88f 100644 --- a/2-Contracts-and-Documents/document-update.mjs +++ b/2-Contracts-and-Documents/document-update.mjs @@ -6,11 +6,11 @@ const { identity, identityKey, signer } = await keyManager.getAuth(); // Default tutorial contract (testnet). Replace or override via DATA_CONTRACT_ID. const DATA_CONTRACT_ID = - process.env.DATA_CONTRACT_ID ?? + process.env.DATA_CONTRACT_ID || 'FW3DHrQiG24VqzPY4ARenMgjEPpBNuEQTZckV8hbVCG4'; // Replace with your existing document ID from the Submit Documents tutorial -const DOCUMENT_ID = process.env.DOCUMENT_ID ?? 'YOUR_DOCUMENT_ID'; +const DOCUMENT_ID = process.env.DOCUMENT_ID || 'YOUR_DOCUMENT_ID'; try { // Fetch the existing document to get current revision From b3cc55e30799dfb59a552d6e7d7fcf060adcccc6 Mon Sep 17 00:00:00 2001 From: thephez Date: Tue, 17 Mar 2026 16:19:15 -0400 Subject: [PATCH 4/5] chore: fmt --- README.md | 11 +++++----- package.json | 2 +- test/setupDashClient.test.mjs | 39 +++++++++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 5eda876..3069ac0 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,12 @@ Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/das The included dev container provides a ready-to-use environment with Node.js, dependencies, and editor tooling pre-configured. Open the repo in [GitHub Codespaces](https://codespaces.new/dashpay/platform-tutorials) or locally with the [VS Code Dev -Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). +Containers +extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). On first launch the container installs dependencies and creates a starter `.env` file from -`.env.example`. Run `node create-wallet.mjs` to generate a mnemonic, then set `PLATFORM_MNEMONIC` -in your `.env` file to begin the tutorials. +`.env.example`. Run `node create-wallet.mjs` to generate a mnemonic, then set `PLATFORM_MNEMONIC` in +your `.env` file to begin the tutorials. ## Install @@ -63,8 +64,8 @@ Some client configuration options are included as comments in ## Testing -Tests run each tutorial as a subprocess and validate its output. No test framework -dependencies are required — tests use the Node.js built-in test runner. +Tests run each tutorial as a subprocess and validate its output. No test framework dependencies are +required — tests use the Node.js built-in test runner. Ensure your `.env` file is configured (see [`.env.example`](./.env.example)) before running tests. diff --git a/package.json b/package.json index 836e051..a1e5b3e 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Tutorial code for https://docs.dash.org/platform", "main": "connect.mjs", "scripts": { - "fmt": "npx prettier@2 --write '**/*.{md,js,mjs}'", + "fmt": "npx prettier@3.8.1 --write '**/*.{js,mjs}'", "lint": "npx -p typescript@4 tsc", "test": "node --test --test-timeout=120000 test/read-only.test.mjs", "test:read-only": "node --test --test-timeout=120000 test/read-only.test.mjs", diff --git a/test/setupDashClient.test.mjs b/test/setupDashClient.test.mjs index ac0be42..706bc81 100644 --- a/test/setupDashClient.test.mjs +++ b/test/setupDashClient.test.mjs @@ -517,7 +517,10 @@ describe('IdentityKeyManager', function suite() { it('should skip occupied indices and return first unused', async function () { const fakeSdk = await fakeSdkWithOccupiedIndices([0, 1]); - const idx = await IdentityKeyManager.findNextIndex(fakeSdk, TEST_MNEMONIC); + const idx = await IdentityKeyManager.findNextIndex( + fakeSdk, + TEST_MNEMONIC, + ); expect(idx).to.equal(2); }); }); @@ -619,7 +622,10 @@ describe('AddressKeyManager', function suite() { let queriedAddress; const fakeSdk = { addresses: { - get: async (addr) => { queriedAddress = addr; return fakeInfo; }, + get: async (addr) => { + queriedAddress = addr; + return fakeInfo; + }, }, }; const mgr = new AddressKeyManager( @@ -649,7 +655,10 @@ describe('AddressKeyManager', function suite() { let queriedAddress; const fakeSdk = { addresses: { - get: async (addr) => { queriedAddress = addr; return fakeInfo; }, + get: async (addr) => { + queriedAddress = addr; + return fakeInfo; + }, }, }; const mgr = new AddressKeyManager( @@ -756,7 +765,9 @@ describe('setupDashClient()', function () { expect.fail('should have thrown'); } catch (err) { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.not.include('Cannot read properties of undefined'); + expect(err.message).to.not.include( + 'Cannot read properties of undefined', + ); } try { @@ -764,7 +775,9 @@ describe('setupDashClient()', function () { expect.fail('should have thrown'); } catch (err) { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.not.include('Cannot read properties of undefined'); + expect(err.message).to.not.include( + 'Cannot read properties of undefined', + ); } } finally { clientConfig.mnemonic = saved; @@ -814,8 +827,14 @@ describe('setupDashClient()', function () { // proving identityIndex is forwarded. We just need it not to crash // before reaching the identity lookup. // Use requireIdentity: false to avoid the lookup and verify the index is stored. - const r0 = await setupDashClient({ requireIdentity: false, identityIndex: 0 }); - const r1 = await setupDashClient({ requireIdentity: false, identityIndex: 1 }); + const r0 = await setupDashClient({ + requireIdentity: false, + identityIndex: 0, + }); + const r1 = await setupDashClient({ + requireIdentity: false, + identityIndex: 1, + }); expect(r0.keyManager.identityIndex).to.equal(0); expect(r1.keyManager.identityIndex).to.equal(1); // Different indices must produce different keys @@ -832,7 +851,10 @@ describe('setupDashClient()', function () { const saved = clientConfig.mnemonic; try { clientConfig.mnemonic = TEST_MNEMONIC; - const result = await setupDashClient({ requireIdentity: false, identityIndex: 42 }); + const result = await setupDashClient({ + requireIdentity: false, + identityIndex: 42, + }); expect(result.keyManager).to.be.an.instanceOf(IdentityKeyManager); expect(result.keyManager.identityIndex).to.equal(42); expect(result.keyManager.identityId).to.be.null; @@ -916,4 +938,3 @@ describe('dip13KeyPath()', function () { expect(path).to.equal("m/9'/5'/5'/0'/0'/0'/0'"); }); }); - From 3b539e315e176e15d948820a8a5616d36b87e375 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 18 Mar 2026 11:21:22 -0400 Subject: [PATCH 5/5] chore: fix tsconfig to check setupDashClient with correct ESM settings (#64) * chore: fix tsconfig to check setupDashClient with correct ESM settings Update tsconfig.json to use module/moduleResolution node16 for proper .mjs ESM support, scope include to setupDashClient.mjs only, and set maxNodeModuleJsDepth to 0. Add @types/node and @types/mocha devDeps. Add JSDoc type annotations to setupDashClient.mjs to pass strict tsc check. Co-Authored-By: Claude Opus 4.6 * chore: replace any types with precise SDK types in setupDashClient Add JSDoc typedef imports for Identity, IdentityPublicKey, PlatformAddress, PlatformAddressInfo, and NetworkLike. Define DerivedKeyEntry and AddressEntry typedefs. Replace all any params/returns with real SDK types. Add missing @returns to create(), convenience signer methods, and exported functions. Add identity-not-found guard in getSigner() with corresponding test. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- package-lock.json | 47 +++++++++++++++ package.json | 2 + setupDashClient.mjs | 106 ++++++++++++++++++++++++++++------ test/setupDashClient.test.mjs | 20 +++++++ tsconfig.json | 8 +-- 5 files changed, 160 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f2de92..96cba4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,8 @@ "dotenv": "17.3.1" }, "devDependencies": { + "@types/mocha": "10.0.10", + "@types/node": "25.5.0", "chai": "6.2.2", "eslint": "8.45.0", "mocha": "11.7.5" @@ -306,6 +308,23 @@ "node": ">=14" } }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -1737,6 +1756,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2078,6 +2104,21 @@ "dev": true, "optional": true }, + "@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true + }, + "@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "requires": { + "undici-types": "~7.18.0" + } + }, "acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -3060,6 +3101,12 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index a1e5b3e..3d13c50 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "dotenv": "17.3.1" }, "devDependencies": { + "@types/mocha": "10.0.10", + "@types/node": "25.5.0", "chai": "6.2.2", "eslint": "8.45.0", "mocha": "11.7.5" diff --git a/setupDashClient.mjs b/setupDashClient.mjs index be07600..3c91a8f 100644 --- a/setupDashClient.mjs +++ b/setupDashClient.mjs @@ -21,6 +21,27 @@ try { /* dotenv not installed */ } +/** @typedef {import('@dashevo/evo-sdk').Identity} Identity */ +/** @typedef {import('@dashevo/evo-sdk').IdentityPublicKey} IdentityPublicKey */ +/** @typedef {import('@dashevo/evo-sdk').PlatformAddress} PlatformAddress */ +/** @typedef {import('@dashevo/evo-sdk').PlatformAddressInfo} PlatformAddressInfo */ +/** @typedef {import('@dashevo/evo-sdk').NetworkLike} NetworkLike */ + +/** + * @typedef {Object} DerivedKeyEntry + * @property {number} keyId + * @property {string} privateKeyWif + * @property {string} [publicKey] - Only present via createForNewIdentity() + */ + +/** + * @typedef {Object} AddressEntry + * @property {PlatformAddress} address + * @property {string} bech32m + * @property {string} privateKeyWif + * @property {string} path + */ + // ⚠️ Tutorial helper — holds WIFs in memory for convenience. // Do not use this pattern as-is for production key management. @@ -44,6 +65,10 @@ const clientConfig = { * Build a DIP-13 identity key derivation path. * Returns the full 7-level hardened path: * m/9'/{coin}'/5'/0'/0'/{identityIndex}'/{keyIndex}' + * @param {string} network + * @param {number} identityIndex + * @param {number} keyIndex + * @returns {Promise} */ export async function dip13KeyPath(network, identityIndex, keyIndex) { const base = @@ -57,12 +82,18 @@ export async function dip13KeyPath(network, identityIndex, keyIndex) { // SDK client helpers // --------------------------------------------------------------------------- +/** + * Create and connect an EvoSDK client for the selected network. + * + * @param {string} [network='testnet'] + * @returns {Promise} + */ export async function createClient(network = 'testnet') { - const factories = { + const factories = /** @type {Record EvoSDK>} */ ({ testnet: () => EvoSDK.testnetTrusted(), mainnet: () => EvoSDK.mainnetTrusted(), local: () => EvoSDK.localTrusted(), - }; + }); const factory = factories[network]; if (!factory) { @@ -71,7 +102,7 @@ export async function createClient(network = 'testnet') { ); } - const sdk = factory(); + const sdk = /** @type {EvoSDK} */ (factory()); await sdk.connect(); return sdk; } @@ -125,6 +156,12 @@ const KEY_SPECS = [ * Key 4 = ENCRYPTION MEDIUM (encrypted messaging/data) */ class IdentityKeyManager { + /** + * @param {EvoSDK} sdk + * @param {string|null|undefined} identityId + * @param {Record} keys + * @param {number} identityIndex + */ constructor(sdk, identityId, keys, identityIndex) { this.sdk = sdk; this.id = identityId; @@ -141,12 +178,13 @@ class IdentityKeyManager { * Derives all standard identity keys using DIP-9 paths. * * @param {object} opts - * @param {object} opts.sdk - Connected EvoSDK instance + * @param {EvoSDK} opts.sdk - Connected EvoSDK instance * @param {string} [opts.identityId] - Identity ID. If omitted, auto-resolved * from the mnemonic by looking up the master key's public key hash on-chain. * @param {string} opts.mnemonic - BIP39 mnemonic * @param {string} [opts.network='testnet'] - 'testnet' or 'mainnet' * @param {number} [opts.identityIndex=0] - Which identity derived from this mnemonic + * @returns {Promise} */ static async create({ sdk, @@ -155,7 +193,7 @@ class IdentityKeyManager { network = 'testnet', identityIndex = 0, }) { - const derive = async (keyIndex) => + const derive = async (/** @type {number} */ keyIndex) => wallet.deriveKeyFromSeedWithPath({ mnemonic, path: await dip13KeyPath(network, identityIndex, keyIndex), @@ -211,7 +249,7 @@ class IdentityKeyManager { * Find the first unused DIP-9 identity index for a mnemonic. * Scans indices starting at 0 until no on-chain identity is found. * - * @param {object} sdk - Connected EvoSDK instance + * @param {EvoSDK} sdk - Connected EvoSDK instance * @param {string} mnemonic - BIP39 mnemonic * @param {string} [network='testnet'] - 'testnet' or 'mainnet' * @returns {Promise} The first unused identity index @@ -240,7 +278,7 @@ class IdentityKeyManager { * If identityIndex is omitted, auto-selects the next unused index. * * @param {object} opts - * @param {object} opts.sdk - Connected EvoSDK instance + * @param {EvoSDK} opts.sdk - Connected EvoSDK instance * @param {string} opts.mnemonic - BIP39 mnemonic * @param {string} [opts.network='testnet'] - 'testnet' or 'mainnet' * @param {number} [opts.identityIndex] - Identity index (auto-scanned if omitted) @@ -255,7 +293,7 @@ class IdentityKeyManager { const idx = identityIndex ?? (await IdentityKeyManager.findNextIndex(sdk, mnemonic, network)); - const derive = async (keyIndex) => + const derive = async (/** @type {number} */ keyIndex) => wallet.deriveKeyFromSeedWithPath({ mnemonic, path: await dip13KeyPath(network, idx, keyIndex), @@ -339,7 +377,7 @@ class IdentityKeyManager { /** * Fetch identity and build { identity, identityKey, signer } for a given key. * @param {string} keyName - One of: master, auth, authHigh, transfer, encryption - * @returns {{ identity, identityKey, signer }} + * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>} */ async getSigner(keyName) { if (!this.id) { @@ -348,35 +386,52 @@ class IdentityKeyManager { 'or create/register the identity first and then set the ID.', ); } - const key = this.keys[keyName]; + const key = /** @type {Record} */ (this.keys)[ + keyName + ]; if (!key) { throw new Error( `Unknown key "${keyName}". Use: ${Object.keys(this.keys).join(', ')}`, ); } const identity = await this.sdk.identities.fetch(this.id); + if (!identity) { + throw new Error(`Identity "${this.id}" not found on-chain.`); + } const identityKey = identity.getPublicKeyById(key.keyId); const signer = new IdentitySigner(); signer.addKeyFromWif(key.privateKeyWif); return { identity, identityKey, signer }; } - /** CRITICAL auth (key 2) — contracts, documents, names. */ + /** + * CRITICAL auth (key 2) — contracts, documents, names. + * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>} + */ async getAuth() { return this.getSigner('auth'); } - /** HIGH auth (key 1) — documents, names. */ + /** + * HIGH auth (key 1) — documents, names. + * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>} + */ async getAuthHigh() { return this.getSigner('authHigh'); } - /** TRANSFER — credit transfers, withdrawals. */ + /** + * TRANSFER — credit transfers, withdrawals. + * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>} + */ async getTransfer() { return this.getSigner('transfer'); } - /** ENCRYPTION MEDIUM — encrypted messaging/data. */ + /** + * ENCRYPTION MEDIUM — encrypted messaging/data. + * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>} + */ async getEncryption() { return this.getSigner('encryption'); } @@ -384,6 +439,7 @@ class IdentityKeyManager { /** * MASTER — identity updates (add/disable keys). * @param {string[]} [additionalKeyWifs] - WIFs for new keys being added + * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>} */ async getMaster(additionalKeyWifs) { const result = await this.getSigner('master'); @@ -409,6 +465,11 @@ class IdentityKeyManager { * that hold credits directly, independent of identities. */ class AddressKeyManager { + /** + * @param {EvoSDK} sdk + * @param {AddressEntry[]} addresses + * @param {string} network + */ constructor(sdk, addresses, network) { this.sdk = sdk; this.addresses = addresses; // [{ address, bech32m, privateKeyWif, path }] @@ -425,10 +486,11 @@ class AddressKeyManager { * Derives platform address keys using BIP44 paths. * * @param {object} opts - * @param {object} opts.sdk - Connected EvoSDK instance + * @param {EvoSDK} opts.sdk - Connected EvoSDK instance * @param {string} opts.mnemonic - BIP39 mnemonic * @param {string} [opts.network='testnet'] - 'testnet' or 'mainnet' * @param {number} [opts.count=1] - Number of addresses to derive + * @returns {Promise} */ static async create({ sdk, mnemonic, network = 'testnet', count = 1 }) { const addresses = []; @@ -452,7 +514,9 @@ class AddressKeyManager { addresses.push({ address: platformAddress, - bech32m: platformAddress.toBech32m(network), + bech32m: platformAddress.toBech32m( + /** @type {NetworkLike} */ (network), + ), privateKeyWif: obj.privateKeyWif, path, }); @@ -488,7 +552,7 @@ class AddressKeyManager { /** * Fetch current balance and nonce for the primary address. - * @returns {Promise} + * @returns {Promise} */ async getInfo() { return this.sdk.addresses.get(this.primaryAddress.bech32m); @@ -497,7 +561,7 @@ class AddressKeyManager { /** * Fetch current balance and nonce for an address by index. * @param {number} index - Address index - * @returns {Promise} + * @returns {Promise} */ async getInfoAt(index) { const entry = this.addresses[index]; @@ -514,9 +578,13 @@ class AddressKeyManager { // setupDashClient — convenience wrapper // --------------------------------------------------------------------------- +/** + * @param {{requireIdentity?: boolean, identityIndex?: number}} opts + * @returns {Promise<{ sdk: EvoSDK, keyManager: IdentityKeyManager | undefined, addressKeyManager: AddressKeyManager | undefined }>} + */ export async function setupDashClient({ requireIdentity = true, - identityIndex, + identityIndex = undefined, } = {}) { const { network, mnemonic } = clientConfig; diff --git a/test/setupDashClient.test.mjs b/test/setupDashClient.test.mjs index 706bc81..595e075 100644 --- a/test/setupDashClient.test.mjs +++ b/test/setupDashClient.test.mjs @@ -444,6 +444,26 @@ describe('IdentityKeyManager', function suite() { expect(err.message).to.include('Unknown key "bogus"'); } }); + + it('should throw when identity is not found on-chain', async function () { + const fakeSdk = { + identities: { + fetch: async () => undefined, + byPublicKeyHash: async () => ({ id: 'fake-id' }), + }, + }; + const km = await IdentityKeyManager.create({ + sdk: fakeSdk, + identityId: 'nonexistent-id', + mnemonic: TEST_MNEMONIC, + }); + try { + await km.getAuth(); + expect.fail('should have thrown'); + } catch (err) { + expect(err.message).to.include('not found on-chain'); + } + }); }); describe('create() error paths', function () { diff --git a/tsconfig.json b/tsconfig.json index d8e8abf..eb0eebf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ /* Visit https://aka.ms/tsconfig.json to read more about this file */ "target": "es2021" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, - "module": "commonjs", + "module": "node16", // "lib": [], /* Specify library files to be included in the compilation. */ "allowJs": true /* Allow javascript files to be compiled. */, "checkJs": true /* Report errors in .js files. */, @@ -42,7 +42,7 @@ // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "moduleResolution": "node16", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ @@ -54,7 +54,7 @@ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, "preserveSymlinks": false /* Do not resolve the real path of symlinks. */, - "maxNodeModuleJsDepth": 20, + "maxNodeModuleJsDepth": 0, // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ /* Source Map Options */ @@ -71,6 +71,6 @@ "skipLibCheck": true /* Skip type checking of declaration files. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, - "include": ["./types.js", "server.js", "lib/**/*.js"], + "include": ["./setupDashClient.mjs"], "exclude": ["node_modules"] }