Skip to content

Comments

fix(sdk): use string keys instead of object keys in JavaScript Maps#3145

Open
shumkov wants to merge 4 commits intov3.1-devfrom
fix/sdk/map-keys
Open

fix(sdk): use string keys instead of object keys in JavaScript Maps#3145
shumkov wants to merge 4 commits intov3.1-devfrom
fix/sdk/map-keys

Conversation

@shumkov
Copy link
Collaborator

@shumkov shumkov commented Feb 22, 2026

Issue being fixed or feature implemented

JavaScript Map uses reference equality for object keys. When Rust WASM bindings create Maps with IdentifierWasm or PlatformAddressWasm objects
as keys, the resulting JS Maps are unusable — map.get(new Identifier(sameBytes)) returns undefined because it's a different object instance.

What was done?

Converted all WASM Map keys from object instances to string primitives across wasm-sdk and wasm-dpp2:

  • Identifier keys.to_base58() string (35 locations across 10 Rust files)
  • PlatformAddress keys.to_hex() string (address.rs, addresses.rs)
  • ProTxHash keys in epoch.rs.to_hex() to match ProTxHashWasm hex convention
  • Updated all unchecked_return_type annotations from Map<Identifier, ...> / Map<PlatformAddress, ...> to Map<string, ...>
  • Updated test files to use string keys and removed redundant Identifier object creation

Rust files: token.rs (10 fixes), group.rs (7), identity.rs (4), epoch.rs (4), address.rs (2), document.rs (2), data_contract.rs
(2), votes.rs (1), addresses.rs (1), proof_result.rs (2)

How Has This Been Tested?

  • cargo check -p wasm-sdk and cargo check -p wasm-dpp2 — compile clean
  • Updated unit tests in wasm-dpp2: Group.spec.ts, ProofResult.spec.ts, TokenConfiguration.spec.ts, TokenDistributionRules.spec.ts,
    TokenPreProgrammedDistribution.spec.ts, js-value-to-json.spec.ts
  • Updated functional test in wasm-sdk: epochs-blocks.spec.ts, identities.spec.ts

Breaking Changes

Map keys returned by all wasm-sdk query methods and wasm-dpp2 proof result methods change from Identifier/PlatformAddress objects to
string. Consumers that previously called .toBase58() or .toHex() on map keys should use the key directly. Consumers that compared keys by
reference were already broken.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • API Changes

    • Many SDK methods now return JS Maps keyed by strings (base58 or hex) instead of identifier/address objects across addresses, documents, data contracts, identities, tokens, epochs, groups, and voting; map values and behavior are unchanged.
  • Tests

    • Unit and functional tests updated to use string-encoded keys (base58/hex) to match the new Map key types.

JavaScript Map uses reference equality for object keys, making lookups
fail when a new object is constructed from the same bytes. Convert all
IdentifierWasm keys to base58 strings and PlatformAddressWasm keys to
hex strings. ProTxHash-based keys use hex to match ProTxHash conventions.
Update unchecked_return_type annotations from Map<Identifier, ...> to
Map<string, ...> accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added this to the v3.1.0 milestone Feb 22, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

Map keys exposed at the WASM/JS boundary were changed from object wrappers (Identifier/PlatformAddress/ProTxHash) to string representations (base58 or hex) across wasm-dpp2, wasm-sdk, js-evo-sdk facades, and tests.

Changes

Cohort / File(s) Summary
WASM-DPP2 Implementation
packages/wasm-dpp2/src/state_transitions/proof_result.rs
Switched map key construction for VerifiedTokenIdentitiesBalances and VerifiedDocuments to use base58 string keys via to_base58() instead of Identifier JsValues.
WASM-DPP2 Core
packages/wasm-dpp2/src/core/pro_tx_hash.rs
Added From<dpp::platform_value::Identifier> for ProTxHashWasm to support ProTxHash hex conversions used as string keys.
WASM-DPP2 Tests
packages/wasm-dpp2/tests/unit/Group.spec.ts, .../ProofResult.spec.ts, .../TokenConfiguration.spec.ts, .../TokenDistributionRules.spec.ts, .../TokenPreProgrammedDistribution.spec.ts, .../js-value-to-json.spec.ts
Tests updated to construct Maps with identifier base58 strings (e.g., id.toBase58()) as keys instead of wasm Identifier instances; comments adjusted accordingly.
WASM-SDK Queries — Addresses & State Transitions
packages/wasm-sdk/src/queries/address.rs, packages/wasm-sdk/src/state_transitions/addresses.rs
Changed wasm_bindgen unchecked_return_type annotations to Map<string, ...> and build keys using PlatformAddressWasm::to_hex() (hex strings) instead of PlatformAddress JsValues.
WASM-SDK Queries — Documents & Data Contracts
packages/wasm-sdk/src/queries/document.rs, packages/wasm-sdk/src/queries/data_contract.rs
Updated unchecked_return_type to Map<string, ...> and switched key construction to IdentifierWasm::to_base58() string keys.
WASM-SDK Queries — Epochs & ProTxHash keys
packages/wasm-sdk/src/queries/epoch.rs
Epoch-related returns changed to Map<string, bigint>; keys now created with ProTxHashWasm::to_hex() (hex strings).
WASM-SDK Queries — Group & Identity
packages/wasm-sdk/src/queries/group.rs, packages/wasm-sdk/src/queries/identity.rs
Multiple group and identity query return types switched to Map<string, ...>; key construction now uses IdentifierWasm::to_base58().
WASM-SDK Queries — Token & Voting
packages/wasm-sdk/src/queries/token.rs, packages/wasm-sdk/src/queries/voting/votes.rs
Token and voting query return types updated to Map<string, ...>; keys use base58 strings. Added internal helper prefetch_token_configuration(token_id) (pub(crate) async).
WASM-SDK State Transitions — Addresses
packages/wasm-sdk/src/state_transitions/addresses.rs
Address-related wasm bindings now return Map<string, PlatformAddressInfo> and use hex-string keys.
JS EVO SDK Facades — Addresses, Contracts, Documents, Epoch, Group, Identities, Tokens, Voting
packages/js-evo-sdk/src/.../facade.ts (multiple files as listed)
Public TypeScript method return types updated from `Map<wasm.Identifier
WASM-SDK Tests — Functional
packages/wasm-sdk/tests/functional/epochs-blocks.spec.ts, packages/wasm-sdk/tests/functional/identities.spec.ts
Test expectations/comments adjusted to reflect string map keys (hex/base58); removed redundant prior conversions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through Maps and changed each key,

From wrapped objects to base58 and hex with glee.
Tests now sip strings, bindings speak light,
My tiny paws rewired the map's night. 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: converting WASM Map keys from object instances to string primitives across the SDK, which aligns with the primary objective and changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/sdk/map-keys

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
Contributor

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/wasm-sdk/src/state_transitions/addresses.rs (1)

33-56: ⚠️ Potential issue | 🟡 Minor

Stale doc comment: still references Map<PlatformAddress, PlatformAddressInfo>.

Line 35 says "to Map<PlatformAddress, PlatformAddressInfo>" but the function now produces Map<string, PlatformAddressInfo> with hex string keys.

Proposed fix
-/// This helper handles the common pattern of converting IndexMap<PlatformAddress, Option<AddressInfo>>
-/// to Map<PlatformAddress, PlatformAddressInfo> for WASM bindings.
+/// This helper handles the common pattern of converting IndexMap<PlatformAddress, Option<AddressInfo>>
+/// to Map<string, PlatformAddressInfo> for WASM bindings, using hex string keys.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/wasm-sdk/src/state_transitions/addresses.rs` around lines 33 - 56,
Update the stale doc comment on function address_infos_to_js_map to state that
it converts IndexMap<PlatformAddress, Option<AddressInfo>> into a JavaScript
Map<string, PlatformAddressInfo> where keys are hex strings (produced by
PlatformAddressWasm::from(...).to_hex()) and values are PlatformAddressInfo
(PlatformAddressInfoWasm), instead of claiming Map<PlatformAddress,
PlatformAddressInfo>; be concise and mention that keys are hex string
representations of PlatformAddress.
packages/wasm-dpp2/src/state_transitions/proof_result.rs (1)

249-249: ⚠️ Potential issue | 🟡 Minor

Stale type comments not updated to reflect string keys

Both field comments still reference IdentifierWasm keys, but after lines 789 and 811 the runtime keys are string (base58). The comment at line 509 (Map<string(hex), ...>) shows the correct pattern.

📝 Proposed fix
 pub struct VerifiedTokenIdentitiesBalancesWasm {
-    balances: Map, // Map<IdentifierWasm, BigInt>
+    balances: Map, // Map<string, BigInt>
 }
 pub struct VerifiedDocumentsWasm {
-    documents: Map, // Map<IdentifierWasm, DocumentWasm | undefined>
+    documents: Map, // Map<string, DocumentWasm | undefined>
 }

Also applies to: 322-322

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

In `@packages/wasm-dpp2/src/state_transitions/proof_result.rs` at line 249, Update
the stale field comments in proof_result.rs to reflect that runtime map keys are
strings (base58) rather than IdentifierWasm: change the comment for the balances
field (Map, previously annotated as Map<IdentifierWasm, BigInt>) to indicate
Map<string(base58), BigInt> and make the same update for the other affected
field referenced around the 322 comment; locate the fields by name (e.g.,
balances) in the ProofResult struct and replace IdentifierWasm in comments with
the correct string(base58) key notation to match the runtime behavior shown
elsewhere (e.g., the Map<string(hex), ...> comment).
🧹 Nitpick comments (3)
packages/wasm-sdk/tests/functional/identities.spec.ts (1)

172-179: Add an assertion on the Map key to actually exercise the PR's fix.

The updated comment correctly documents that keys are now string (base58), but the test body never touches the keys — it only reads balances.values(). A regression where keys revert to Identifier objects would silently pass this test.

Since TEST_IDENTITY is already a base58 string, the cleanest fix is to look up via balances.get(TEST_IDENTITY) directly:

✅ Proposed assertion improvement
-      // getIdentitiesBalances returns Map<string, bigint | null> where keys are base58-encoded identifiers
       const balances = await client.getIdentitiesBalances([TEST_IDENTITY]);
       expect(balances).to.be.instanceOf(Map);
       expect(balances.size).to.equal(1);
+      // Keys must be base58 strings — string lookup must succeed (core fix in this PR)
+      expect(Array.from(balances.keys())[0]).to.be.a('string');
+      expect(Array.from(balances.keys())[0]).to.equal(TEST_IDENTITY);
+      // Retrieve by string key directly
+      const balance = balances.get(TEST_IDENTITY);
-      // For existing identity, balance should be present
-      const balance = Array.from(balances.values())[0];
       expect(typeof balance).to.equal('bigint');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/wasm-sdk/tests/functional/identities.spec.ts` around lines 172 -
179, The test currently only inspects values and misses verifying that Map keys
are base58 strings; update the assertions around balances (returned by
client.getIdentitiesBalances) to directly assert the Map contains the key
TEST_IDENTITY (e.g., use balances.get(TEST_IDENTITY) or
balances.has(TEST_IDENTITY)) and then assert the retrieved value is a bigint;
ensure you still check balances is a Map and its size is 1 but replace the
Array.from(balances.values())[0] lookup with a direct lookup using TEST_IDENTITY
so the test fails if keys are not the expected base58 string.
packages/wasm-sdk/src/queries/token.rs (1)

408-408: Minor: duplicated price schedule → (base_price, current_price) extraction logic.

The match schedule { SinglePrice(..) => ..., SetPrices(..) => ... } block appears identically in get_token_price_by_contract (Lines 204–222), get_token_direct_purchase_prices (Lines 429–446), and get_token_direct_purchase_prices_with_proof_info (Lines 767–784). Consider extracting a small helper to avoid this triplication.

Also applies to: 448-452

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

In `@packages/wasm-sdk/src/queries/token.rs` at line 408, Duplicate logic
extracting (base_price, current_price) from a PriceSchedule enum is repeated in
get_token_price_by_contract, get_token_direct_purchase_prices, and
get_token_direct_purchase_prices_with_proof_info; factor it into a small helper
like fn extract_prices(schedule: &PriceSchedule) -> (PriceType, PriceType) (use
the actual price numeric type in the crate) that matches on SinglePrice and
SetPrices and returns (base_price, current_price), then replace the repeated
match blocks in those functions with calls to extract_prices and update any
imports/visibility accordingly.
packages/wasm-dpp2/tests/unit/ProofResult.spec.ts (1)

370-395: Consider asserting value retrieval by string key

The test currently only checks size === 1 after the round-trip. Adding a get assertion would directly validate the core fix — that lookups by base58 string actually return the expected value from the map produced by the WASM binding.

✅ Suggested addition
       const result = wasm.VerifiedTokenIdentitiesBalances.fromObject(data);

       expect(result.balances).to.be.instanceOf(Map);
       expect(result.balances.size).to.equal(1);
+      expect(result.balances.get(id1.toBase58())).to.equal(999000n);
       const result = wasm.VerifiedDocuments.fromObject(data);

       expect(result.documents).to.be.instanceOf(Map);
       expect(result.documents.size).to.equal(1);
+      expect(result.documents.has(id1.toBase58())).to.equal(true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/wasm-dpp2/tests/unit/ProofResult.spec.ts` around lines 370 - 395,
The test should assert that map lookups by the base58 key return the expected
value: after creating id1 (Identifier) and setting balancesMap with
id1.toBase58() -> 999000n, add assertions that
result.balances.get(id1.toBase58()) === 999000n and that after serializing with
result.toObject() and rehydrating via
wasm.VerifiedTokenIdentitiesBalances.fromObject(obj) the
roundtrip.balances.get(id1.toBase58()) === 999000n so you verify
fromObject/toObject preserves key lookup behavior for
VerifiedTokenIdentitiesBalances.balances.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/wasm-sdk/src/queries/epoch.rs`:
- Around line 521-525: The map that builds ProTxHash keys in the proof-info
variants uses IdentifierWasm::from(identifier).to_base58(), causing inconsistent
key formats versus the non-proof variants; update those occurrences (the closure
building the Map from counts.0.into_iter() in the proof-info methods such as
getEvonodesProposedEpochBlocksByIdsWithProofInfo and the other proof-info
helper) to use .to_hex() instead of .to_base58() so ProTxHash keys match the
ProTxHashWasm hex convention used elsewhere.

In `@packages/wasm-sdk/tests/functional/epochs-blocks.spec.ts`:
- Around line 103-107: The comment is incorrect: byRange keys are hex strings
(created via IdentifierWasm::from(identifier).to_hex()), not base58. Update the
comment near byRange.keys() to state the keys are hex, and when you pass that
key (testProTxHash) into getEvonodesProposedEpochBlocksByIdsWithProofInfo (which
expects base58 keys) convert the hex key to base58 first (e.g., decode the hex
Identifier and call its to_base58 / toBase58 equivalent) so the key formats
match between getEvonodesProposedEpochBlocksByRange and
getEvonodesProposedEpochBlocksByIdsWithProofInfo.

---

Outside diff comments:
In `@packages/wasm-dpp2/src/state_transitions/proof_result.rs`:
- Line 249: Update the stale field comments in proof_result.rs to reflect that
runtime map keys are strings (base58) rather than IdentifierWasm: change the
comment for the balances field (Map, previously annotated as Map<IdentifierWasm,
BigInt>) to indicate Map<string(base58), BigInt> and make the same update for
the other affected field referenced around the 322 comment; locate the fields by
name (e.g., balances) in the ProofResult struct and replace IdentifierWasm in
comments with the correct string(base58) key notation to match the runtime
behavior shown elsewhere (e.g., the Map<string(hex), ...> comment).

In `@packages/wasm-sdk/src/state_transitions/addresses.rs`:
- Around line 33-56: Update the stale doc comment on function
address_infos_to_js_map to state that it converts IndexMap<PlatformAddress,
Option<AddressInfo>> into a JavaScript Map<string, PlatformAddressInfo> where
keys are hex strings (produced by PlatformAddressWasm::from(...).to_hex()) and
values are PlatformAddressInfo (PlatformAddressInfoWasm), instead of claiming
Map<PlatformAddress, PlatformAddressInfo>; be concise and mention that keys are
hex string representations of PlatformAddress.

---

Nitpick comments:
In `@packages/wasm-dpp2/tests/unit/ProofResult.spec.ts`:
- Around line 370-395: The test should assert that map lookups by the base58 key
return the expected value: after creating id1 (Identifier) and setting
balancesMap with id1.toBase58() -> 999000n, add assertions that
result.balances.get(id1.toBase58()) === 999000n and that after serializing with
result.toObject() and rehydrating via
wasm.VerifiedTokenIdentitiesBalances.fromObject(obj) the
roundtrip.balances.get(id1.toBase58()) === 999000n so you verify
fromObject/toObject preserves key lookup behavior for
VerifiedTokenIdentitiesBalances.balances.

In `@packages/wasm-sdk/src/queries/token.rs`:
- Line 408: Duplicate logic extracting (base_price, current_price) from a
PriceSchedule enum is repeated in get_token_price_by_contract,
get_token_direct_purchase_prices, and
get_token_direct_purchase_prices_with_proof_info; factor it into a small helper
like fn extract_prices(schedule: &PriceSchedule) -> (PriceType, PriceType) (use
the actual price numeric type in the crate) that matches on SinglePrice and
SetPrices and returns (base_price, current_price), then replace the repeated
match blocks in those functions with calls to extract_prices and update any
imports/visibility accordingly.

In `@packages/wasm-sdk/tests/functional/identities.spec.ts`:
- Around line 172-179: The test currently only inspects values and misses
verifying that Map keys are base58 strings; update the assertions around
balances (returned by client.getIdentitiesBalances) to directly assert the Map
contains the key TEST_IDENTITY (e.g., use balances.get(TEST_IDENTITY) or
balances.has(TEST_IDENTITY)) and then assert the retrieved value is a bigint;
ensure you still check balances is a Map and its size is 1 but replace the
Array.from(balances.values())[0] lookup with a direct lookup using TEST_IDENTITY
so the test fails if keys are not the expected base58 string.

- Use ProTxHashWasm for ProTxHash map keys in epoch.rs instead of
  IdentifierWasm, with hex encoding matching ProTxHash conventions
- Add From<Identifier> for ProTxHashWasm conversion
- Fix stale doc comments in addresses.rs, proof_result.rs, and
  epochs-blocks.spec.ts to reflect string key types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@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 (2)
packages/wasm-sdk/src/state_transitions/addresses.rs (1)

219-222: addressInfos getters on result structs are missing unchecked_return_type after the key-type change.

The PR updates unchecked_return_type for the three methods that return a Map directly (addressFundsTransfer, addressFundsWithdraw, addressFundingFromAssetLock), but the addressInfos getters on IdentityTopUpFromAddressesResultWasm (line 219), IdentityTransferToAddressesResultWasm (line 564), and IdentityCreateFromAddressesResultWasm (line 808) all call the same address_infos_to_js_map helper and now serve Map<string, PlatformAddressInfo> without advertising that to TypeScript consumers.

♻️ Proposed additions for all three getters
-    #[wasm_bindgen(getter = "addressInfos")]
+    #[wasm_bindgen(getter = "addressInfos", unchecked_return_type = "Map<string, PlatformAddressInfo>")]
     pub fn address_infos(&self) -> Map {

Apply identically at lines 219, 564, and 808.

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

In `@packages/wasm-sdk/src/state_transitions/addresses.rs` around lines 219 - 222,
The addressInfos getters on IdentityTopUpFromAddressesResultWasm,
IdentityTransferToAddressesResultWasm, and IdentityCreateFromAddressesResultWasm
call address_infos_to_js_map and now return a Map<string, PlatformAddressInfo>
but lack the unchecked_return_type annotation; update each getter's attribute to
#[wasm_bindgen(getter = "addressInfos", unchecked_return_type)] so the
TypeScript signature reflects the actual keyed value type (apply the change to
the address_infos method on each of those three structs).
packages/wasm-dpp2/src/core/pro_tx_hash.rs (1)

160-164: Consider adding a doc comment clarifying the semantic constraint.

This From<Identifier> impl silently reinterprets any 32-byte Identifier as a ProTxHash. It is semantically valid only when the Identifier wraps actual ProTxHash bytes (e.g., evonode IDs from ProposerBlockCountById). Without a comment, future callers may invoke this conversion for non-ProTxHash identifiers and get a well-typed but semantically wrong value.

🔧 Suggested clarifying doc comment
+/// Reinterprets the raw 32-byte buffer of an `Identifier` as a `ProTxHash`.
+///
+/// # Safety / Correctness
+/// This conversion is only semantically meaningful when the `Identifier` actually
+/// represents an evonode ProTxHash (e.g., keys from `ProposerBlockCountById`).
+/// Using it for arbitrary identifiers (document IDs, contract IDs, etc.) will
+/// produce a structurally valid but meaningless `ProTxHashWasm`.
 impl From<dpp::platform_value::Identifier> for ProTxHashWasm {
     fn from(id: dpp::platform_value::Identifier) -> Self {
         ProTxHashWasm(ProTxHash::from_byte_array(id.to_buffer()))
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/wasm-dpp2/src/core/pro_tx_hash.rs` around lines 160 - 164, Add a doc
comment on the impl From<dpp::platform_value::Identifier> for ProTxHashWasm
explaining that the conversion simply reinterprets the 32-byte Identifier bytes
via ProTxHash::from_byte_array and is only semantically valid when the
Identifier actually contains ProTxHash bytes (e.g., evonode IDs from
ProposerBlockCountById); indicate that using this conversion on other Identifier
values produces a well-typed but semantically incorrect ProTxHash and callers
should validate the identifier source before converting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/wasm-dpp2/src/core/pro_tx_hash.rs`:
- Around line 160-164: Add a doc comment on the impl
From<dpp::platform_value::Identifier> for ProTxHashWasm explaining that the
conversion simply reinterprets the 32-byte Identifier bytes via
ProTxHash::from_byte_array and is only semantically valid when the Identifier
actually contains ProTxHash bytes (e.g., evonode IDs from
ProposerBlockCountById); indicate that using this conversion on other Identifier
values produces a well-typed but semantically incorrect ProTxHash and callers
should validate the identifier source before converting.

In `@packages/wasm-sdk/src/state_transitions/addresses.rs`:
- Around line 219-222: The addressInfos getters on
IdentityTopUpFromAddressesResultWasm, IdentityTransferToAddressesResultWasm, and
IdentityCreateFromAddressesResultWasm call address_infos_to_js_map and now
return a Map<string, PlatformAddressInfo> but lack the unchecked_return_type
annotation; update each getter's attribute to #[wasm_bindgen(getter =
"addressInfos", unchecked_return_type)] so the TypeScript signature reflects the
actual keyed value type (apply the change to the address_infos method on each of
those three structs).

@shumkov shumkov changed the title fix(wasm): use string keys instead of object keys in JavaScript Maps fix(sdk): use string keys instead of object keys in JavaScript Maps Feb 22, 2026
@shumkov shumkov requested a review from lklimek February 23, 2026 07:46
@shumkov
Copy link
Collaborator Author

shumkov commented Feb 23, 2026

@thepastaclaw can you fix these and make PR on my PR?

$ tsc -p tsconfig.json (@dashevo/evo-sdk)
Error: [@dashevo/evo-sdk::tsc] src/addresses/facade.ts(45,5): error TS2322: Type 'Map<string, PlatformAddressInfo | undefined>' is not assignable to type 'Map<PlatformAddress, PlatformAddressInfo | undefined>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'PlatformAddress'.
Error: [@dashevo/evo-sdk::tsc] src/addresses/facade.ts(89,5): error TS2322: Type 'Map<string, PlatformAddressInfo>' is not assignable to type 'Map<PlatformAddress, PlatformAddressInfo>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'PlatformAddress'.
Error: [@dashevo/evo-sdk::tsc] src/addresses/facade.ts(153,5): error TS2322: Type 'Map<string, PlatformAddressInfo>' is not assignable to type 'Map<PlatformAddress, PlatformAddressInfo>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'PlatformAddress'.
Error: [@dashevo/evo-sdk::tsc] src/addresses/facade.ts(226,5): error TS2322: Type 'Map<string, PlatformAddressInfo>' is not assignable to type 'Map<PlatformAddress, PlatformAddressInfo>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'PlatformAddress'.
Error: [@dashevo/evo-sdk::tsc] src/contracts/facade.ts(36,5): error TS2322: Type 'Map<string, DataContract | undefined>' is not assignable to type 'Map<Identifier, DataContract | undefined>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/documents/facade.ts(14,5): error TS2322: Type 'Map<string, Document | undefined>' is not assignable to type 'Map<Identifier, Document | undefined>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/epoch/facade.ts(53,5): error TS2322: Type 'Map<string, bigint>' is not assignable to type 'Map<Identifier, bigint>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/epoch/facade.ts(64,5): error TS2322: Type 'Map<string, bigint>' is not assignable to type 'Map<Identifier, bigint>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/group/facade.ts(35,5): error TS2322: Type 'Map<string, bigint>' is not assignable to type 'Map<Identifier, bigint>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/group/facade.ts(59,5): error TS2322: Type 'Map<string, GroupAction | undefined>' is not assignable to type 'Map<Identifier, GroupAction | undefined>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/group/facade.ts(73,5): error TS2322: Type 'Map<string, bigint>' is not assignable to type 'Map<Identifier, bigint>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/group/facade.ts(87,5): error TS2322: Type 'Map<string, Map<number, Group | undefined>>' is not assignable to type 'Map<Identifier, Map<number, Group | undefined>>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/identities/facade.ts(79,5): error TS2322: Type 'Map<string, bigint | undefined>' is not assignable to type 'Map<Identifier, bigint | undefined>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/identities/facade.ts(152,5): error TS2322: Type 'Map<string, bigint>' is not assignable to type 'Map<Identifier, bigint>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/tokens/facade.ts(36,5): error TS2322: Type 'Map<string, TokenStatus>' is not assignable to type 'Map<Identifier, TokenStatus>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/tokens/facade.ts(51,5): error TS2322: Type 'Map<string, bigint>' is not assignable to type 'Map<Identifier, bigint>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/tokens/facade.ts(65,5): error TS2322: Type 'Map<string, bigint>' is not assignable to type 'Map<Identifier, bigint>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/tokens/facade.ts(79,5): error TS2322: Type 'Map<string, IdentityTokenInfo>' is not assignable to type 'Map<Identifier, IdentityTokenInfo>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/tokens/facade.ts(85,5): error TS2322: Type 'Map<string, IdentityTokenInfo>' is not assignable to type 'Map<Identifier, IdentityTokenInfo>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/tokens/facade.ts(110,5): error TS2322: Type 'Map<string, TokenPriceInfo>' is not assignable to type 'Map<Identifier, TokenPriceInfo>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
Error: [@dashevo/evo-sdk::tsc] src/voting/facade.ts(28,5): error TS2322: Type 'Map<string, ResourceVote>' is not assignable to type 'Map<Identifier, ResourceVote>'.
[@dashevo/evo-sdk::tsc]   Type 'string' is not assignable to type 'Identifier'.
error 
error Command node failed with exit code 2
Error: Process completed with exit code 1.

@thepastaclaw
Copy link
Contributor

@shumkov I pushed a branch with the TS facade key-type fixes requested in your comment:

It updates @dashevo/evo-sdk facade signatures from object-keyed maps to Map<string, ...> to match the wasm SDK changes in this PR.

I could not run a fully clean local @dashevo/evo-sdk typecheck in this worktree due unrelated wasm module/export resolution issues (@dashevo/wasm-sdk/compressed / missing wasm exports), but the specific TS2322 map-key mismatches from your snippet are addressed in this patch.

Copy link
Contributor

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/js-evo-sdk/src/addresses/facade.ts (2)

35-59: ⚠️ Potential issue | 🟡 Minor

Stale @returns JSDoc — map keys are now string, not PlatformAddress.

Lines 39–40 and 52–53 still describe the map keys as PlatformAddress, but the actual return type is now Map<string, ...> (hex-encoded address). Consumers relying on the docs to understand key lookup will be misled.

📝 Suggested doc fix
-   * `@returns` Map of PlatformAddress to PlatformAddressInfo (or undefined for unfunded addresses)
+   * `@returns` Map of hex-encoded PlatformAddress string to PlatformAddressInfo (or undefined for unfunded addresses)
-   * `@returns` ProofMetadataResponse containing Map of PlatformAddress to PlatformAddressInfo
+   * `@returns` ProofMetadataResponse containing Map of hex-encoded PlatformAddress string to PlatformAddressInfo
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/js-evo-sdk/src/addresses/facade.ts` around lines 35 - 59, The JSDoc
for getMany and getManyWithProof incorrectly states the Map keys are
PlatformAddress while the functions return Map<string, ...> (hex-encoded
addresses); update the `@returns` descriptions in the JSDoc for both getMany and
getManyWithProof to say "Map<string, PlatformAddressInfo | undefined>" and
"ProofMetadataResponse containing Map<string, PlatformAddressInfo | undefined>"
(or equivalent wording) so docs match the actual return types of getMany and
getManyWithProof.

122-227: ⚠️ Potential issue | 🟡 Minor

Same stale @returns JSDoc in withdraw (line 126) and fundFromAssetLock (line 194–195).

Both still say "Map of PlatformAddress to PlatformAddressInfo" while the return type is now Map<string, wasm.PlatformAddressInfo> with hex-encoded keys.

📝 Suggested doc fix
-   * `@returns` Promise resolving to Map of PlatformAddress to PlatformAddressInfo
+   * `@returns` Promise resolving to Map of hex-encoded PlatformAddress string to PlatformAddressInfo

(apply to both withdraw and fundFromAssetLock JSDoc blocks)

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

In `@packages/js-evo-sdk/src/addresses/facade.ts` around lines 122 - 227, Update
the `@returns` JSDoc for withdraw and fundFromAssetLock to reflect the actual
return type Map<string, wasm.PlatformAddressInfo> (hex-encoded string keys)
instead of "Map of PlatformAddress to PlatformAddressInfo"; mention that the map
keys are hex-encoded PlatformAddress strings to avoid confusion (update the
JSDoc blocks above the withdraw method and the fundFromAssetLock method).
🧹 Nitpick comments (1)
packages/js-evo-sdk/src/epoch/facade.ts (1)

50-72: Consider documenting the string key encoding format across all facades.

Map<string, ...> is now the return type across all facades, but the string encoding differs:

  • Most Identifier-keyed maps → base58 (e.g., identities, tokens, documents, contracts, groups)
  • PlatformAddress maps → hex (addresses/facade.ts)
  • ProTxHash maps → hex (this file)

Without this documented, a consumer calling map.get(id.toHex()) on a base58-keyed map will silently miss every entry. A brief JSDoc @returns note or a descriptive type alias (e.g., type Base58String = string) on the changed methods would make the encoding self-documenting.

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

In `@packages/js-evo-sdk/src/epoch/facade.ts` around lines 50 - 72, Update the
facade methods (evonodesProposedBlocksByIds,
evonodesProposedBlocksByIdsWithProof, evonodesProposedBlocksByRange,
evonodesProposedBlocksByRangeWithProof) to document the string-key encoding used
for map keys: add a concise JSDoc `@returns` note indicating that the Map keys are
hex-encoded ProTxHash strings (or define and use a descriptive type alias like
HexString = string and reference it in the JSDoc return type), so consumers know
to call map.get(id.toHex()) rather than base58 variants.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/js-evo-sdk/src/addresses/facade.ts`:
- Around line 35-59: The JSDoc for getMany and getManyWithProof incorrectly
states the Map keys are PlatformAddress while the functions return Map<string,
...> (hex-encoded addresses); update the `@returns` descriptions in the JSDoc for
both getMany and getManyWithProof to say "Map<string, PlatformAddressInfo |
undefined>" and "ProofMetadataResponse containing Map<string,
PlatformAddressInfo | undefined>" (or equivalent wording) so docs match the
actual return types of getMany and getManyWithProof.
- Around line 122-227: Update the `@returns` JSDoc for withdraw and
fundFromAssetLock to reflect the actual return type Map<string,
wasm.PlatformAddressInfo> (hex-encoded string keys) instead of "Map of
PlatformAddress to PlatformAddressInfo"; mention that the map keys are
hex-encoded PlatformAddress strings to avoid confusion (update the JSDoc blocks
above the withdraw method and the fundFromAssetLock method).

---

Nitpick comments:
In `@packages/js-evo-sdk/src/epoch/facade.ts`:
- Around line 50-72: Update the facade methods (evonodesProposedBlocksByIds,
evonodesProposedBlocksByIdsWithProof, evonodesProposedBlocksByRange,
evonodesProposedBlocksByRangeWithProof) to document the string-key encoding used
for map keys: add a concise JSDoc `@returns` note indicating that the Map keys are
hex-encoded ProTxHash strings (or define and use a descriptive type alias like
HexString = string and reference it in the JSDoc return type), so consumers know
to call map.get(id.toHex()) rather than base58 variants.
ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 839e7c1 and 844f866.

📒 Files selected for processing (8)
  • packages/js-evo-sdk/src/addresses/facade.ts
  • packages/js-evo-sdk/src/contracts/facade.ts
  • packages/js-evo-sdk/src/documents/facade.ts
  • packages/js-evo-sdk/src/epoch/facade.ts
  • packages/js-evo-sdk/src/group/facade.ts
  • packages/js-evo-sdk/src/identities/facade.ts
  • packages/js-evo-sdk/src/tokens/facade.ts
  • packages/js-evo-sdk/src/voting/facade.ts

@lklimek
Copy link
Contributor

lklimek commented Feb 23, 2026

🎭 Audit Summary

Reviewed by: Claude Code with a 2-agent team:

  • code-reviewer (Opus) — consistency, completeness, test coverage
  • security-engineer (Opus) — encoding safety, key collisions, WASM boundary

Verdict: ✅ Approve

No blockers. No correctness issues. The PR solves a real JS Map usability problem — reference equality on object keys made lookups silently fail — by converting all WASM Map keys to string primitives. The fix is applied with perfect consistency across all 27 files.

Findings

# Severity Location Description
1 MEDIUM wasm-dpp2/tests/unit/js-value-to-json.spec.ts:165-168 Test still uses Identifier objects as Map keys despite comment claiming Map<string, bigint>. Passes incidentally but no longer simulates actual runtime scenario. See inline comment.
2 MEDIUM All Map<string, ...> types Three different encodings (base58 for Identifiers, hex for ProTxHash, hex for PlatformAddress) all typed as Map<string, ...>. A consumer who mismatches the encoding gets silent undefined. Consider documenting the encoding convention in JSDoc or a central location (e.g., module-level doc in wasm-sdk/src/queries/mod.rs).
3 LOW wasm-dpp2/src/core/pro_tx_hash.rs:160-163 New From<Identifier> for ProTxHashWasm is correct (matches rs-drive-abci patterns), but a doc comment explaining when this conversion is valid would help. See inline comment.
4 LOW PR metadata PR title uses fix(sdk) but the squash commit correctly uses refactor!:. The refactor!: is more accurate for a breaking API change.

Pre-existing / Outside-diff

No pre-existing issues found within the scope of this change (CodeRabbit already flagged the stale doc comment in addresses.rs).

Confirmed Safe

  • No key collisions — base58 and hex are bijective encodings
  • No missed locations — zero remaining JsValue::from(IdentifierWasm::from(...)) in Map key positions
  • TypeScript types match runtime — all facades updated to Map<string, ...>
  • All unchecked_return_type annotations updated
  • No WASM memory safety issues — strings are native JS values, no dangling heap references
  • Breaking change properly documented with clear migration path
  • Test coverage adequate — 8 test files updated

Positive Observations

  • The epochs-blocks.spec.ts cleanup is particularly nice — that ugly manual byte-to-hex conversion replaced by just using the key directly
  • Encoding convention is 100% consistent: not a single deviation across 35+ conversion sites
  • The insert_member helper in group.rs centralizes the member key conversion — good factoring

🤖 Generated with Claude Code

Copy link
Contributor

@lklimek lklimek left a comment

Choose a reason for hiding this comment

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

Fix the test please, otherwise LGTM.

const id2 = wasm.Identifier.fromBase58('ckBqfQe7LU7vwrwXopyCB4n5phZShjA16BGhNGpsD5U');

// This simulates getEvonodesProposedEpochBlocksByIds which returns Map<Identifier, bigint>
// This simulates getEvonodesProposedEpochBlocksByIds which returns Map<string, bigint>
Copy link
Contributor

Choose a reason for hiding this comment

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

[MEDIUM] The comment was updated to say Map<string, bigint>, but the test still constructs the Map with Identifier objects as keys:

const map = new Map([
  [id1, 100n],   // id1 is an Identifier object, not a string
  [id2, 200n],
]);

This passes incidentally because testJsValueToJson calls .toString() on any key, but it no longer simulates the actual runtime scenario it claims to simulate.

        // This simulates getEvonodesProposedEpochBlocksByIds which returns Map<string, bigint>
        const map = new Map([
          [id1.toBase58(), 100n],
          [id2.toBase58(), 200n],

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.

3 participants