Skip to content

refactor: split setupDashClient into browser-safe core and Node wrapper#68

Merged
thephez merged 2 commits intomainfrom
refactor-sdk-setup
Apr 22, 2026
Merged

refactor: split setupDashClient into browser-safe core and Node wrapper#68
thephez merged 2 commits intomainfrom
refactor-sdk-setup

Conversation

@thephez
Copy link
Copy Markdown
Collaborator

@thephez thephez commented Apr 21, 2026

  • Extract the runtime-agnostic pieces of setupDashClient.mjscreateClient, IdentityKeyManager, AddressKeyManager, dip13KeyPath, and KEY_SPECS — into a new setupDashClient-core.mjs that has no Node-only dependencies.
  • Keep setupDashClient.mjs as the Node convenience wrapper: it loads .env via dotenv, reads PLATFORM_MNEMONIC / NETWORK from process.env, and exposes the setupDashClient() one-call helper used by tutorials.
  • Re-export the core API from setupDashClient.mjs so existing tutorial imports (import { IdentityKeyManager } from './setupDashClient.mjs') keep working with no changes.
  • Replace Buffer.from(hex, 'hex') with a small hexToBytes helper in the core so browser apps can consume IdentityKeyManager without a Buffer polyfill.

No behavior change — this is a pure reorganization to let browser apps (docs/ interactive runner, future in-browser tutorials) share the same key manager code as the Node tutorials instead of copy-pasting it.

Summary by CodeRabbit

  • New Features

    • Browser-safe core shipped: unified primitives for client creation, key derivation, and signing across runtimes.
    • Identity key manager: derive/manage identity keys, create or locate identities, produce signers for specific roles.
    • Address key manager: derive platform addresses, expose primary address and signers.
    • Exposed standard KEY_SPECS for identity keys.
  • Bug Fixes

    • Strict hex validation for public key data to prevent malformed input.
  • Tests

    • Added regression test ensuring hex validation errors are surfaced.

Extract createClient, IdentityKeyManager, AddressKeyManager, dip13KeyPath, and
KEY_SPECS into setupDashClient-core.mjs so browser apps can import them without
pulling in dotenv or process.env. setupDashClient.mjs keeps the Node-only
convenience layer and re-exports the core API so existing tutorial imports
continue to work unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

Adds a browser-safe core module setupDashClient-core.mjs that implements key derivation/signing utilities, KEY_SPECS, IdentityKeyManager, AddressKeyManager, and exports createClient and dip13KeyPath. setupDashClient.mjs becomes a thin Node wrapper that re-exports these symbols. A regression test validating strict hex decoding is added.

Changes

Cohort / File(s) Summary
Core module
setupDashClient-core.mjs
New browser-safe implementation: hexToBytes, dip13KeyPath, createClient, KEY_SPECS, IdentityKeyManager, AddressKeyManager. Implements identity/address derivation, on-chain lookup (findNextIndex), signer assembly, and exports utilities and classes. Attention: strict hex validation and identity-creation flows.
Wrapper / Node entry
setupDashClient.mjs
Refactored into a thin Node-focused wrapper that loads env (optional) and re-exports createClient, dip13KeyPath, KEY_SPECS, IdentityKeyManager, AddressKeyManager, and clientConfig from the core. Removed in-file class/function implementations.
Tests
test/setupDashClient.test.mjs
New regression test asserting IdentityKeyManager.getKeysInCreation() throws on invalid hex in publicKey (expects hexToBytes per-byte validation error).

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Client
  participant IDM as IdentityKeyManager
  participant KD as KeyDerivation (hexToBytes/dip13)
  participant SDK as EvoSDK (on-chain)

  Caller->>IDM: create / createForNewIdentity(mnemonic, network)
  IDM->>KD: derive keys, call dip13KeyPath / hexToBytes
  KD-->>IDM: derived pubkeys / bytes (or throw on invalid hex)
  IDM->>SDK: query identity existence (findNextIndex / lookup)
  SDK-->>IDM: identity found? / identity data
  IDM-->>Caller: IdentityKeyManager instance / signer / error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through bytes and derivation trees,
Pulled core pieces out with nimble ease,
Kept the wrapper light, the core robust,
Now keys and addresses bloom from trust,
A tiny carrot of code, tidy and free.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: split setupDashClient into browser-safe core and Node wrapper' directly and clearly describes the main change: extracting runtime-agnostic code into a new core module while converting the original file into a Node wrapper. This accurately reflects the primary objective and is specific enough for code history scanning.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-sdk-setup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@setupDashClient-core.mjs`:
- Around line 64-77: The hexToBytes helper currently uses parseInt and substr
which can accept invalid inputs; update hexToBytes to strictly validate each
two-character chunk before conversion (e.g., ensure each chunk matches
/^[0-9A-Fa-f]{2}$/) and reject on any non-hex or odd-length input, then convert
validated pairs to bytes; also replace legacy substr usage with slice when
extracting chunks. Ensure errors reference hexToBytes so callers get clear
failure context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc818023-f6c7-413e-8b74-1dd48f0b1adf

📥 Commits

Reviewing files that changed from the base of the PR and between e7f7364 and 461e98d.

📒 Files selected for processing (2)
  • setupDashClient-core.mjs
  • setupDashClient.mjs

Comment thread setupDashClient-core.mjs
parseInt(chunk, 16) silently accepted inputs like "1z" as 1, so the
NaN guard let non-hex characters through and produced wrong bytes.
Validate each 2-char chunk against /^[0-9A-Fa-f]{2}$/ before
conversion and replace the deprecated substr with slice. Adds a
regression test that poisons publicKey with "1z" and asserts the
hexToBytes error references the bad offset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
test/setupDashClient.test.mjs (1)

415-474: Regression test covers the intended path, but the 4 valid-key blocks are copy-pasted.

The assertion (/hexToBytes.*offset 2/) correctly targets the master key (first in KEY_SPECS) with its poisoned '1z' at byte offset 2, so the test is meaningful. The four authHigh/auth/transfer/encryption entries duplicate the same Buffer.from(PrivateKey.fromWIF(...).getPublicKey().toBytes()).toString('hex') boilerplate; since getKeysInCreation throws on the first bad key, the other four publicKey fields are never actually read — you could omit them entirely or use a tiny helper to keep the test focused. Optional.

♻️ Optional simplification
-      const km = new IdentityKeyManager(
-        sdk,
-        null,
-        {
-          master: { ...base.keys.master, publicKey: poisonedHex },
-          authHigh: {
-            ...base.keys.authHigh,
-            publicKey: Buffer.from(
-              PrivateKey.fromWIF(base.keys.authHigh.privateKeyWif)
-                .getPublicKey()
-                .toBytes(),
-            ).toString('hex'),
-          },
-          auth: {
-            ...base.keys.auth,
-            publicKey: Buffer.from(
-              PrivateKey.fromWIF(base.keys.auth.privateKeyWif)
-                .getPublicKey()
-                .toBytes(),
-            ).toString('hex'),
-          },
-          transfer: {
-            ...base.keys.transfer,
-            publicKey: Buffer.from(
-              PrivateKey.fromWIF(base.keys.transfer.privateKeyWif)
-                .getPublicKey()
-                .toBytes(),
-            ).toString('hex'),
-          },
-          encryption: {
-            ...base.keys.encryption,
-            publicKey: Buffer.from(
-              PrivateKey.fromWIF(base.keys.encryption.privateKeyWif)
-                .getPublicKey()
-                .toBytes(),
-            ).toString('hex'),
-          },
-        },
-        0,
-      );
+      const pubHex = (wif) =>
+        Buffer.from(PrivateKey.fromWIF(wif).getPublicKey().toBytes()).toString(
+          'hex',
+        );
+      const km = new IdentityKeyManager(
+        sdk,
+        null,
+        {
+          master: { ...base.keys.master, publicKey: poisonedHex },
+          authHigh: { ...base.keys.authHigh, publicKey: pubHex(base.keys.authHigh.privateKeyWif) },
+          auth: { ...base.keys.auth, publicKey: pubHex(base.keys.auth.privateKeyWif) },
+          transfer: { ...base.keys.transfer, publicKey: pubHex(base.keys.transfer.privateKeyWif) },
+          encryption: { ...base.keys.encryption, publicKey: pubHex(base.keys.encryption.privateKeyWif) },
+        },
+        0,
+      );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/setupDashClient.test.mjs` around lines 415 - 474, The test duplicates
identical publicKey construction for authHigh/auth/transfer/encryption even
though getKeysInCreation() will fail on the poisoned master key; simplify by
removing those four heavy inline
Buffer.from(PrivateKey.fromWIF(...).getPublicKey().toBytes()).toString('hex')
blocks or replace them with a small helper/variable that computes a single valid
publicKeyHex (e.g., validHex) and reuse it for
authHigh/auth/transfer/encryption, keeping the poisonedHex only for master so
the assertion against getKeysInCreation() and the /hexToBytes.*offset 2/ message
stays focused and the test is easier to read.
setupDashClient-core.mjs (2)

277-293: findNextIndex is an unbounded for (;;) loop.

If sdk.identities.byPublicKeyHash ever returned a truthy value for every index (e.g., a buggy SDK or exotic test stub), this would loop forever and block the event loop between awaits. Not a real-world hazard for tutorials against testnet, but a cheap safety net (e.g., cap at a few hundred indices and throw) would make the helper more robust as it’s now exposed from the browser-safe core too. Leaving as-is is also reasonable for an educational module.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@setupDashClient-core.mjs` around lines 277 - 293, The helper findNextIndex
contains an unbounded for(;;) loop; add a safe iteration cap (e.g., MAX_INDEX =
1000 or configurable) and after that many attempts throw a clear Error
indicating no free index found to avoid infinite looping; update the loop in
findNextIndex to break/throw when i >= MAX_INDEX and include the MAX_INDEX
constant and an explanatory error message referencing
sdk.identities.byPublicKeyHash and dip13KeyPath so callers can detect the
failure.

364-381: Minor: O(n²) lookup inside getKeysInCreation.

Object.values(this.keys).find(...) runs on every KEY_SPECS iteration. With 5 keys this is negligible, but since this.keys is already keyed by role (master/authHigh/auth/transfer/encryption) and the corresponding keyIds are fixed, you could build a Map<keyId, entry> once, or key this.keys by keyId directly, and drop the .find. Purely a readability nit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@setupDashClient-core.mjs` around lines 364 - 381, getKeysInCreation performs
an O(n²) lookup by calling Object.values(this.keys).find(...) for each KEY_SPECS
entry; refactor to build a single lookup by keyId once and reuse it: create a
Map or object keyed by key.keyId from this.keys before iterating KEY_SPECS
(referencing getKeysInCreation, KEY_SPECS, this.keys, and keyId), then inside
the KEY_SPECS.map replace the find with a direct lookup from that Map and keep
the rest of the creation logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@setupDashClient-core.mjs`:
- Around line 277-293: The helper findNextIndex contains an unbounded for(;;)
loop; add a safe iteration cap (e.g., MAX_INDEX = 1000 or configurable) and
after that many attempts throw a clear Error indicating no free index found to
avoid infinite looping; update the loop in findNextIndex to break/throw when i
>= MAX_INDEX and include the MAX_INDEX constant and an explanatory error message
referencing sdk.identities.byPublicKeyHash and dip13KeyPath so callers can
detect the failure.
- Around line 364-381: getKeysInCreation performs an O(n²) lookup by calling
Object.values(this.keys).find(...) for each KEY_SPECS entry; refactor to build a
single lookup by keyId once and reuse it: create a Map or object keyed by
key.keyId from this.keys before iterating KEY_SPECS (referencing
getKeysInCreation, KEY_SPECS, this.keys, and keyId), then inside the
KEY_SPECS.map replace the find with a direct lookup from that Map and keep the
rest of the creation logic unchanged.

In `@test/setupDashClient.test.mjs`:
- Around line 415-474: The test duplicates identical publicKey construction for
authHigh/auth/transfer/encryption even though getKeysInCreation() will fail on
the poisoned master key; simplify by removing those four heavy inline
Buffer.from(PrivateKey.fromWIF(...).getPublicKey().toBytes()).toString('hex')
blocks or replace them with a small helper/variable that computes a single valid
publicKeyHex (e.g., validHex) and reuse it for
authHigh/auth/transfer/encryption, keeping the poisonedHex only for master so
the assertion against getKeysInCreation() and the /hexToBytes.*offset 2/ message
stays focused and the test is easier to read.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e7f593c7-e8bb-439f-bb31-81b410d4d58b

📥 Commits

Reviewing files that changed from the base of the PR and between 461e98d and 128bc93.

📒 Files selected for processing (2)
  • setupDashClient-core.mjs
  • test/setupDashClient.test.mjs

@thephez thephez merged commit 95277f5 into main Apr 22, 2026
3 checks passed
@thephez thephez deleted the refactor-sdk-setup branch April 22, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant