Skip to content

refactor(dapi,dpp)!: move dapi-client and Identifier off Buffer to Uint8Array#3680

Open
PastaPastaPasta wants to merge 9 commits into
v3.1-devfrom
claude/esm-2-uint8array
Open

refactor(dapi,dpp)!: move dapi-client and Identifier off Buffer to Uint8Array#3680
PastaPastaPasta wants to merge 9 commits into
v3.1-devfrom
claude/esm-2-uint8array

Conversation

@PastaPastaPasta
Copy link
Copy Markdown
Member

@PastaPastaPasta PastaPastaPasta commented May 19, 2026

Summary

Replaces Buffer with Uint8Array across @dashevo/dapi-client's public API. Rewrites @dashevo/wasm-dpp's Identifier to extend Uint8Array directly (instead of Buffer) and removes Buffer.from from the wasm-dpp module loader. After this PR, both packages' module-load and core construction paths are Buffer-free; only the deprecated Identifier#toBuffer() shim still requires a Buffer global at call time. The SPV path through dashcore-lib will become Buffer-free once dashpay/dashcore-lib#315 lands and dapi-client picks up the new release.

This is PR 2 of 3 in the stacked series. PR 1 (#3679) is merged.

Why

Removes Buffer from the dapi-client and wasm-dpp surfaces so consumers don't need a Buffer polyfill in browser bundles. Buffer is Node-specific; Uint8Array works everywhere.

What changes

@dashevo/dapi-client

  • New lib/utils/bytes.js: hexToBytes, bytesToHex, base64ToBytes, bytesToBase64, concatBytes, bytesEqual (CJS exports). Input-validated with informative TypeErrors; uses String.prototype.slice not the deprecated substr.
  • Exposed via DAPIClient.bytes so the migration path is reachable from the package's public surface (no deep imports).
  • All Buffer.* call sites in lib/ converted to Uint8Array per the translation table below.
  • createGrpcTransportError: explicit dual-format handling that the previous Buffer.from(x, 'base64') provided implicitly — typeof x === 'string' ? base64ToBytes(x) : new Uint8Array(x) for the three metadata-bin fields (grpc-js sends raw bytes; grpc-web sends base64 strings).
  • One production exception remains where Buffer.from is retained: BlockHeadersReadernew BlockHeader(Buffer.from(header)) because dashcore-lib's BufferReader still requires Buffer. Tracked upstream: dashpay/dashcore-lib#315. Once that lands and we bump the dep here, this wrap goes away.

@dashevo/wasm-dpp

  • Identifier extends Uint8Array instead of Buffer. Buffer.isBuffer(id) now returns false; id instanceof Uint8Array returns true.
  • New id.toBytes() returns a plain Uint8Array copy.
  • id.toBuffer() retained as @deprecated compatibility shim — calls Buffer.from(this). Requires Buffer only at call time, no longer at module load.
  • id.toString(encoding) now handles 'base58'/'base64'/'hex' explicitly via inline byte helpers — no Buffer method dependency. Throws IdentifierError: Unsupported encoding: ... for anything else.
  • Identifier.from(value, encoding) accepts Uint8Array directly and string with 'base58' (default) or 'base64'. The encoding parameter type is narrowed to only the supported values.
  • id.equals(other) byte-by-byte loop, no Buffer methods.
  • wasm-dpp module loader (lib/index.ts) replaces Buffer.from(wasmBase, 'base64') with an inline atob-based decode. loadDpp() no longer requires a Buffer global.

@dashevo/wallet-lib

  • One production callsite: IdentitySyncWorker.js:98 migrates Buffer.isBuffer(identityBuffer)identityBuffer instanceof Uint8Array. Comment updated.

Test migrations

All internal test code that called .toBuffer() on an Identifier-derived value is migrated to .toBytes() (36 test files, ~100 mechanical one-line swaps). The deprecated .toBuffer() shim is intentionally exercised in one place — wasm-dpp/test/unit/Identifier.spec.js #toBuffer describe block — with an explanatory comment.

README

New "Browser usage" section in dapi-client documenting that response fields are Uint8Array, that DAPIClient.bytes is the conversion helper, and that one internal path (BlockHeadersProvider via dashcore-lib) still requires a Buffer polyfill until upstream changes land.

Breaking changes

@dashevo/dapi-client public API

Before After
response.field.toString('hex') DAPIClient.bytes.bytesToHex(response.field)
response.field.toString('base64') DAPIClient.bytes.bytesToBase64(response.field)
Buffer.isBuffer(response.field) response.field instanceof Uint8Array
response.field.slice(a, b) response.field.subarray(a, b)

In Node, Buffer extends Uint8Array, so anything that just reads bytes (passes to crypto, writes to a stream, length checks, indexed access) continues to work identically.

@dashevo/wasm-dpp Identifier

Before After
Buffer.isBuffer(id) returned true now returns false
id instanceof Buffer returned true now returns false
id.readUInt32LE(...) etc. (Buffer methods) gone — id no longer has Buffer-only methods
id.toBuffer() still works (deprecated); prefer id.toBytes()
id.toString('hex') / id.toString('base64') still works (now implemented natively)
id.toString('utf8'), 'ascii', etc. throws IdentifierError: Unsupported encoding (was silently inherited from Buffer)
Error message 'Identifier expects Buffer' 'Identifier expects Uint8Array'

The remaining transitive Buffer dependency in wasm-dpp is bs58 (via safe-buffer) — eliminating it requires replacing bs58 with a Uint8Array-native base58 library and is out of scope here.

Test plan

  • CI: JS packages (@dashevo/dapi-client) / Tests
  • CI: JS packages (@dashevo/wasm-dpp) / Tests
  • CI: JS packages (@dashevo/wallet-lib) / Tests
  • CI: JS packages (dash) / Tests

Stack

  • PR 1 — Cleanup (merged)
  • ➡️ PR 2 — Buffer → Uint8Array (this PR)
  • PR 3 — ESM conversion (dapi-client, wallet-lib, js-dash-sdk + platform-test-suite)

Summary by CodeRabbit

  • New Features

    • Added a bytes utility for hex/base64 conversions, concatenation, and comparisons; exposed via the client.
  • Bug Fixes / Improvements

    • Improved browser compatibility by using native Uint8Array handling across the client and wasm layer.
  • Breaking Changes

    • Binary inputs/outputs are now Uint8Array-based instead of Buffer—update integrations accordingly.
  • Documentation

    • Added browser usage notes and guidance for optional Buffer polyfills.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 00393619-d3cd-408e-80b7-2a185e3f6591

📥 Commits

Reviewing files that changed from the base of the PR and between 4ed8428 and 7885bb3.

📒 Files selected for processing (7)
  • packages/js-dapi-client/lib/methods/core/getTransaction/GetTransactionResponse.js
  • packages/js-dapi-client/lib/methods/platform/getIdentity/GetIdentityResponse.js
  • packages/js-dapi-client/lib/methods/platform/getIdentityByPublicKeyHash/GetIdentityByPublicKeyHashResponse.js
  • packages/js-dapi-client/lib/methods/platform/getStatus/GetStatusResponse.js
  • packages/js-dapi-client/lib/methods/platform/response/Proof.js
  • packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js
  • packages/wallet-lib/src/transport/DAPIClientTransport/methods/getTransaction.js
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js
  • packages/js-dapi-client/lib/methods/platform/getStatus/GetStatusResponse.js
  • packages/js-dapi-client/lib/methods/platform/getIdentity/GetIdentityResponse.js
  • packages/js-dapi-client/lib/methods/platform/getIdentityByPublicKeyHash/GetIdentityByPublicKeyHashResponse.js
  • packages/js-dapi-client/lib/methods/core/getTransaction/GetTransactionResponse.js
  • packages/js-dapi-client/lib/methods/platform/response/Proof.js

📝 Walkthrough

Walkthrough

Project-wide migration from Node Buffer to Uint8Array for binary data, adding shared byte helpers, exposing DAPIClient.bytes, refactoring wasm Identifier and WASM loader, and updating tests/docs and downstream consumers.

Changes

js-dapi-client Uint8Array migration

Layer / File(s) Summary
Bytes utilities and DAPIClient export
packages/js-dapi-client/lib/utils/bytes.js, packages/js-dapi-client/lib/index.js, packages/js-dapi-client/README.md
Adds Uint8Array byte helpers, exposes as DAPIClient.bytes, and documents browser usage and Buffer polyfill notes.
Core methods and SML provider use Uint8Array
packages/js-dapi-client/lib/SimplifiedMasternodeListProvider/..., packages/js-dapi-client/lib/methods/core/*
Returns and handles Uint8Array for blocks, status, transactions, and masternode list diffs; replaces Buffer conversions with typed-array handling and uses bytes helpers.
Platform response classes return Uint8Array
packages/js-dapi-client/lib/methods/platform/*Response.js, packages/js-dapi-client/lib/methods/platform/response/Proof.js, packages/js-dapi-client/lib/methods/platform/getStatus/GetStatusResponse.js, packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/*
Stores and returns Uint8Array in response objects; serializes hex fields via bytesToHex and uses _asU8() protobuf accessors.
Platform request factories accept Uint8Array
packages/js-dapi-client/lib/methods/platform/*Factory.js, packages/js-dapi-client/lib/methods/platform/getProtocolVersionUpgradeVoteStatus/*, packages/js-dapi-client/lib/methods/core/subscribe*
Accepts/clones Uint8Array inputs and converts hex/base64 via helpers for request building; removes Buffer.isBuffer paths.
Transport error decoding and test fixtures
packages/js-dapi-client/lib/transport/GrpcTransport/createGrpcTransportError.js, packages/js-dapi-client/lib/test/fixtures/*, packages/js-dapi-client/lib/test/mocks/*
Decodes metadata as base64 or raw bytes using base64ToBytes and updates fixtures/mocks to produce Uint8Array.
Core integration/unit tests use Uint8Array
packages/js-dapi-client/test/integration/methods/core/*, packages/js-dapi-client/test/unit/methods/core/*
Rewrites tests to encode bytes with TextEncoder/Uint8Array and assert typed-array results.
Platform integration/unit tests use Uint8Array
packages/js-dapi-client/test/integration/methods/platform/*, packages/js-dapi-client/test/unit/methods/platform/*
Updates tests to pass/expect Uint8Array and use bytes helpers for hex/base64 conversions.
wallet-lib IdentitySyncWorker accepts Uint8Array
packages/wallet-lib/src/plugins/Workers/IdentitySyncWorker.js
Type guard updated to Uint8Array for identity buffer.

wasm-dpp Uint8Array refactor

Layer / File(s) Summary
Identifier class migrates from Buffer to Uint8Array
packages/wasm-dpp/lib/identifier/Identifier.ts
Identifier now stores bytes as Uint8Array, adds toBytes(), keeps deprecated toBuffer() shim, updates CBOR encoding, equality, string encodings, and from() API.
WASM loader decodes base64 to Uint8Array
packages/wasm-dpp/lib/index.ts
Adds base64ToBytes and uses it to construct wasm bytes for initialization in both browser and non-browser flows.
wasm-dpp tests switch to toBytes and Uint8Array
packages/wasm-dpp/test/**/*
Updates affected tests to compare identifiers via toBytes() and use Uint8Array inputs and expectations.

Sequence Diagram(s)

(skipped)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested labels

ready for final review

Suggested reviewers

  • QuantumExplorer
  • shumkov
  • thepastaclaw

Poem

A rabbit nibbles bytes so fine,
From Buffer fields to typed-array shine.
Hex and base64 now play,
In Uint8 songs along the way.
Identifier hops, new trails it writes—
CBOR meadows, wasm lights. 🐇✨

✨ 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 claude/esm-2-uint8array

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 19, 2026

✅ DashSDKFFI.xcframework built for this PR.

SwiftPM (host the zip at a stable URL, then use):

.binaryTarget(
  name: "DashSDKFFI",
  url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
  checksum: "bfa0ce328feabc7b4f6d66a72c6cdc47229bcc9ec155eaa86e41747b372d302b"
)

Xcode manual integration:

  • Download 'DashSDKFFI.xcframework' artifact from the run link above.
  • Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.
  • If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.

thepastaclaw

This comment was marked as resolved.

@PastaPastaPasta PastaPastaPasta force-pushed the claude/esm-2-uint8array branch from 0b8c68e to 0eb8b2c Compare May 19, 2026 19:44
@PastaPastaPasta PastaPastaPasta changed the title refactor(dapi-client)!: expose Uint8Array instead of Buffer in public API refactor(dapi)!: expose Uint8Array instead of Buffer in public API of dapi-client May 19, 2026
@PastaPastaPasta PastaPastaPasta marked this pull request as draft May 19, 2026 19:57
thepastaclaw

This comment was marked as resolved.

… API

Adds lib/utils/bytes.js helper (hexToBytes/bytesToHex/base64ToBytes/bytesToBase64/concatBytes/bytesEqual) and converts all Buffer.* call sites in dapi-client lib/ to Uint8Array, with corresponding test updates. Package stays CJS.

Production exceptions where Buffer is retained: BlockHeadersReader passes Buffer to dashcore-lib's BlockHeader (its BufferReader needs .readInt32LE), and GetIdentitiesContractKeysResponse passes Buffer to wasm-dpp's Identifier.from (it explicitly requires Node Buffer).

createGrpcTransportError now handles both raw bytes (grpc-js path) and base64 strings (grpc-web path) for drive-error-data-bin, stack-bin, and dash-serialized-consensus-error-bin metadata fields, restoring the dual-format behavior that Buffer.from(x, 'base64') used to provide implicitly.

Test updates: spec files that construct expected protobuf requests now wrap .toBuffer() with new Uint8Array(...) to match production's normalization (sinon calledOnceWithExactly distinguishes Buffer from plain Uint8Array).

Breaking change for direct consumers: response object byte fields are now Uint8Array. Callers that do response.field.toString('hex') will fail — use bytesToHex(response.field) from lib/utils/bytes instead. Buffer.isBuffer(response.field) now returns false; use response.field instanceof Uint8Array.

Test results: dapi-client 315/315, wallet-lib 377/377, js-dash-sdk 60/60 — downstream consumers continue passing without modification (they exercise dapi-client mostly via mocks).
@PastaPastaPasta PastaPastaPasta force-pushed the claude/esm-2-uint8array branch from afa4da3 to e703e90 Compare May 26, 2026 16:27
Identifier constructor and static from() now accept Uint8Array in addition to Buffer. Since Buffer extends Uint8Array in Node, existing Buffer callers continue to work unchanged — this is a widening, not a swap. Lets dapi-client and other web-facing consumers pass plain Uint8Array (e.g. from protobuf getters) without an explicit Buffer.from wrap. Error message updated from 'Identifier expects Buffer' to 'Identifier expects Uint8Array'.
wasm-dpp's Identifier.from now accepts Uint8Array directly, so the explicit Buffer.from wrap around entry.getIdentityId() is no longer needed. Removes one of two remaining Buffer escape hatches in dapi-client (the BlockHeader path through dashcore-lib remains, pending an upstream change there).
…ment

- Replace deprecated String.prototype.substr with slice in hexToBytes.
- Add light input validation to bytesToHex, base64ToBytes, bytesToBase64, concatBytes, and bytesEqual — they now throw informative TypeErrors instead of opaque runtime errors on non-bytes input.
- Export the helpers as DAPIClient.bytes so the recommended migration path (bytesToHex(field) instead of field.toString('hex')) is reachable via the package's public surface instead of a deep import into lib/utils/bytes.
- README: add a Browser usage section explaining that response fields are Uint8Array, that DAPIClient.bytes is the conversion helper, and that two internal paths (BlockHeadersProvider via dashcore-lib, Identifier.from via wasm-dpp) still construct Buffer — browser consumers must ensure Buffer is reachable at runtime via their bundler or an explicit polyfill until dashcore-lib's BufferReader accepts Uint8Array.
@PastaPastaPasta PastaPastaPasta force-pushed the claude/esm-2-uint8array branch from 55fea88 to 250a79c Compare May 26, 2026 16:57
@PastaPastaPasta PastaPastaPasta changed the title refactor(dapi)!: expose Uint8Array instead of Buffer in public API of dapi-client refactor(dapi-client,wasm-dpp)!: expose Uint8Array in dapi-client public API May 26, 2026
Identifier no longer extends Buffer at the prototype level. It now extends Uint8Array, so new Identifier(x), id.toString('hex'), id.toString('base64'), id.toString('base58'), id.toBytes(), and id.equals(other) work without a Buffer polyfill at runtime. The wasm-dpp module loader also drops Buffer.from in favor of an inline atob-based decode.

toBuffer() is retained as a @deprecated compatibility shim that returns Buffer.from(this); it still requires the Buffer global to exist at call time but no longer at module load time. External callers migrating off Buffer should prefer toBytes() going forward.

BREAKING for any code that relied on Identifier extending Buffer: Buffer.isBuffer(id), id instanceof Buffer, and Buffer-specific methods on id (.readUInt32LE, .equals, etc.) no longer work. The one such internal callsite (wallet-lib's IdentitySyncWorker) is migrated to check instanceof Uint8Array. The error message string changed from 'Identifier expects Buffer' to 'Identifier expects Uint8Array' (accepted-type check itself remains a widening — Buffer extends Uint8Array).

bs58 (via safe-buffer) is the remaining transitive Buffer dependency in wasm-dpp; eliminating it requires replacing bs58 with a Uint8Array-native base58 library and is out of scope here.
…to .toBytes()

Mechanical follow-up to the Identifier extends Uint8Array commit. All internal test code now uses the Buffer-free .toBytes() accessor on Identifier instances (.getId(), .getOwnerId(), .getContractId(), .getDataContractId(), and direct identityId/contractId/ownerId variables). The deprecated .toBuffer() method is retained for external compatibility but no longer used inside the monorepo.

Also strips redundant new Uint8Array(...) wraps around .toBytes() — toBytes already returns a fresh Uint8Array, so the wrap was a copy of a copy.
@PastaPastaPasta PastaPastaPasta changed the title refactor(dapi-client,wasm-dpp)!: expose Uint8Array in dapi-client public API refactor(dapi-client,wasm-dpp)!: move dapi-client and Identifier off Buffer to Uint8Array May 26, 2026
@PastaPastaPasta PastaPastaPasta changed the title refactor(dapi-client,wasm-dpp)!: move dapi-client and Identifier off Buffer to Uint8Array refactor(dapi)!: move dapi-client and Identifier off Buffer to Uint8Array May 26, 2026
@PastaPastaPasta PastaPastaPasta changed the title refactor(dapi)!: move dapi-client and Identifier off Buffer to Uint8Array refactor(dapi,dpp)!: move dapi-client and Identifier off Buffer to Uint8Array May 26, 2026
@PastaPastaPasta PastaPastaPasta marked this pull request as ready for review May 26, 2026 18:14
coderabbitai[bot]

This comment was marked as resolved.

thepastaclaw

This comment was marked as resolved.

Acted on real findings, dropped false positives.

Fixed:
- wallet-lib getTransaction: Buffer.from(response.getBlockHash()).toString('hex') instead of relying on Uint8Array.toString('hex') which ignores the encoding arg. After PR 2's migration, GetTransactionResponse.getBlockHash() returns a plain Uint8Array, so the previous code returned a comma-delimited decimal string instead of hex.
- GetProtocolVersionUpgradeVoteStatusResponse: use versionSignal.getProTxHash_asU8() instead of new Uint8Array(versionSignal.getProTxHash()). The default protobuf getter yields a base64 string under grpc-web; new Uint8Array(string) does not base64-decode, it iterates chars and produces zeros. _asU8 explicitly returns Uint8Array bytes in both grpc-js and grpc-web.
- wallet-lib Transport.d.ts: getIdentityByPublicKeyHash signature updated from (publicKeyHash: Buffer): Promise<Buffer[]> to (publicKeyHash: Uint8Array): Promise<Uint8Array>. Matches the post-migration runtime contract.
- wasm-dpp Document.spec.js #getDataContractId: was calling getOwnerId() and asserting against $ownerId. Now actually exercises getDataContractId.
- GetIdentityByPublicKeyHashResponse JSDoc: @param renamed from identities (plural) to identity (singular); @returns Uint8Array[] corrected to Uint8Array. Both predate this PR; cleaned up while touching the file.
- GetIdentityContractNonce.spec.js, GetIdentityNonce.spec.js: proof-only constructor-pass-through tests now use BigInt(0) (matches the actual nonce type) instead of new Uint8Array(0).
- GetIdentityKeys.spec.js: proof-only test now uses [] (matches the array-of-keys contract) instead of new Uint8Array(0).
- PlatformMethodsFacade.spec.js: getIdentityNonce second arg is now {} (options) instead of a second Uint8Array. Predates the migration.
- SimplifiedMasternodeListProvider catch block: guards bytesToHex call when simplifiedMNListDiffBytes is undefined, so the original error isn't masked by a secondary TypeError from the new bytesToHex input validation.

Skipped (false positives):
- GetDataContractResponse, GetIdentityResponse, GetIdentityByPublicKeyHashResponse createFromProto 'new Uint8Array(undefined)' findings: the existing tests explicitly assert new Uint8Array(0) for the proof-only path, and new Uint8Array(undefined) === new Uint8Array(0). Current behavior matches contract.
- wasm-dpp loadDpp atob fallback: atob has been a Node global since 16.0.0, and the package's engines.node is >=18.18 (PR 1). No fallback needed.
- validateDocumentsBatchTransitionBasicFactory.spec.js .toBuffer() nitpick: inside a describe.skip block; dead code.
coderabbitai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Both prior findings are confirmed FIXED at HEAD 4ed8428. Remaining in-scope items are: (1) inconsistent dashcore-lib boundary in wallet-lib getTransaction.js — the new Uint8Array transaction payload is passed straight into new Transaction(...), while every other in-tree call site at the same boundary still normalizes via Buffer.from(...); (2) the protobuf bytes-decoding pattern new Uint8Array(proto.getX()) used in several PR-touched response classes will silently corrupt bytes under grpc-web (the very target this PR enables) — the author already fixed one instance in GetProtocolVersionUpgradeVoteStatusResponse.js with a comment explaining the issue; (3) a JSDoc/.d.ts contract mismatch on getIdentityByPublicKeyHash.js. None are strictly blocking — codex-ffi-engineer verified dashcore-lib 0.22 accepts Uint8Array via BufferUtil — but the inconsistency and grpc-web pattern should be addressed.

🟡 2 suggestion(s) | 💬 1 nitpick(s)

1 additional finding(s) omitted (not in diff).

Out-of-scope follow-up suggestions (2)

These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.

  • Audit all dapi-client response classes for grpc-web byte-decoding compatibility — The new Uint8Array(proto.getXxx()) pattern that the author fixed in GetProtocolVersionUpgradeVoteStatusResponse.js exists across many sibling response classes (GetStatusResponse.js nodeId/proTxHash/latestBlockHash/latestAppHash/earliestBlockHash/earliestAppHash, WaitForStateTransitionResultResponse.js, response/Proof.js grovedbProof/quorumHash/signature, core/getTransaction/GetTransactionResponse.js blockHash, getBlockchainStatusFactory.js). Some of these are touched by this PR; the broader audit (and choosing between _asU8() everywhere vs. a base64-string-aware helper) is bigger than this PR.
    • Follow-up: Open a separate issue to systematically migrate all dapi-client bytes-field decoders to _asU8() getters (or a wrapper that handles both representations).
  • Verify wallet-lib → dashcore-lib byte boundaries now that responses are Uint8Arraypackages/wallet-lib/src/transport/DAPIClientTransport/methods/getBlockByHash.js and getBlockByHeight.js pass the dapi-client response (now Uint8Array) straight into new Block(...) from @dashevo/dashcore-lib. The PR explicitly preserved Buffer.from(header) at the analogous BlockHeadersReader boundary. Worth confirming whether dashcore-lib.Block accepts a Uint8Array, or whether explicit Buffer.from(...) wraps should be added for consistency with the rest of the codebase.
    • Follow-up: Audit wallet-lib → dashcore-lib byte boundaries (Block, BlockHeader, Transaction inputs) and either standardize on Buffer.from(...) wrappers or wait for upstream dashcore-lib#315 to widen the inputs.

@@ -22,7 +22,9 @@ module.exports = async function getTransaction(txid) {

return {
transaction: new Transaction(response.getTransaction()),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: getTransaction() passes raw Uint8Array to dashcore-lib.Transaction while sibling call sites still normalize via Buffer.from(...)

@dashevo/dapi-client now constructs GetTransactionResponse.transaction as a plain Uint8Array (packages/js-dapi-client/lib/methods/core/getTransaction/GetTransactionResponse.js:78), and this wrapper hands the result directly to new Transaction(response.getTransaction()). Every other raw-transaction entry point in this same package still routes through Buffer.from(...) before crossing into dashcore-lib: packages/wallet-lib/src/plugins/Workers/TransactionsSyncWorker/utils.js:56 does new Transaction(Buffer.from(rawTransaction)), and packages/wallet-lib/src/transport/FixtureTransport/methods/getTransaction.js:6 does new Transaction(Buffer.from(txFile.transaction, 'hex')). The PR also intentionally preserves Buffer.from(...) at the analogous BlockHeadersReader boundary. The dapi-client unit tests still build GetTransactionResponse with Buffer values, so the new typed-array runtime shape isn't actually exercised by tests. Either wrap with Buffer.from(...) here for consistency and safety, or drop the wraps at the other two sites with a comment.

Suggested change
transaction: new Transaction(response.getTransaction()),
transaction: new Transaction(Buffer.from(response.getTransaction())),

source: ['codex']

Comment on lines 35 to 39
return new GetIdentityResponse(
Buffer.from(proto.getV0().getIdentity()),
new Uint8Array(proto.getV0().getIdentity()),
metadata,
proof,
);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: new Uint8Array(proto.getIdentity()) will silently corrupt bytes under grpc-web — use getIdentity_asU8()

The google-protobuf JS binding returns bytes fields either as Uint8Array (grpc-js) or as a base64 string (grpc-web, depending on deserialization). The author already addressed this exact boundary at packages/js-dapi-client/lib/methods/platform/getProtocolVersionUpgradeVoteStatus/GetProtocolVersionUpgradeVoteStatusResponse.js:44-48 by switching to getProTxHash_asU8(), with a comment stating the default getter would yield a base64 string under grpc-web and new Uint8Array(string) does NOT base64-decode it. Same issue applies here and in GetIdentityByPublicKeyHashResponse.js:29-32, GetStatusResponse.js:96-106, WaitForStateTransitionResultResponse.js:41, response/Proof.js:51-53, and core/getTransaction/GetTransactionResponse.js:78-79. Since this PR's stated purpose is to enable browser/grpc-web bundles, the _asU8() accessor should be applied consistently at the bytes-field boundaries this PR touches. Granted, the pre-PR Buffer.from(string) was also wrong in this scenario, but this PR's whole point is to make these paths actually work in the browser — so the fix belongs here.

Suggested change
return new GetIdentityResponse(
Buffer.from(proto.getV0().getIdentity()),
new Uint8Array(proto.getV0().getIdentity()),
metadata,
proof,
);
return new GetIdentityResponse(
proto.getV0().getIdentity_asU8(),
metadata,
proof,
);

source: ['claude']

…fer-wrap tx

Bytes-field boundaries used new Uint8Array(getXxx()), which silently yields an empty array under grpc-web where protobuf stores bytes as a base64 string (new Uint8Array(string) does not base64-decode). Switch to the _asU8() accessors, matching the pattern already used for proTxHash in GetProtocolVersionUpgradeVoteStatusResponse.

Also wrap the raw transaction in Buffer.from before passing to dashcore-lib's Transaction in wallet-lib getTransaction, consistent with the other raw-transaction call sites. Addresses CodeRabbit review findings.
@thepastaclaw
Copy link
Copy Markdown
Collaborator

thepastaclaw commented Jun 1, 2026

✅ Review complete (commit 7885bb3)

Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

PR resolves two of three prior findings: getTransaction now Buffer-wraps the payload before handing to dashcore-lib, and six response classes (GetTransaction, GetIdentity, GetIdentityByPublicKeyHash, GetStatus, Proof, WaitForStateTransitionResult) now use _asU8(). However the sweep missed four sibling byte-decoding sites that were also touched by this PR (getBlockchainStatusFactory, getBlockByHashFactory, getBlockByHeightFactory, GetDocumentsResponse), each of which still uses the new Uint8Array(proto.getX()) pattern that silently corrupts bytes under grpc-web. JSDoc/.d.ts contract mismatch on getIdentityByPublicKeyHash.js also still valid.

🟡 4 suggestion(s) | 💬 1 nitpick(s)

1 additional finding(s) omitted (not in diff).

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/js-dapi-client/lib/methods/core/getBlockByHashFactory.js`:
- [SUGGESTION] packages/js-dapi-client/lib/methods/core/getBlockByHashFactory.js:30-32: `new Uint8Array(response.getBlock())` corrupts the block payload under grpc-web — use `getBlock_asU8()`
  This factory was modified by this PR (e703e9024f changed `Buffer.from(response.getBlock())` to `new Uint8Array(response.getBlock())`). The google-protobuf JS binding returns `bytes` fields as a `Uint8Array` under grpc-js but as a base64 `string` under grpc-web. `new Uint8Array(string)` does NOT base64-decode — it coerces the string length to a numeric and produces a zero-length / bogus typed array, silently corrupting the block bytes returned to callers. The latest delta in this PR explicitly fixed this same pattern in `GetTransactionResponse`, `GetIdentityResponse`, `GetIdentityByPublicKeyHashResponse`, `GetStatusResponse`, `Proof`, and `WaitForStateTransitionResultResponse` using the `_asU8()` accessor for exactly this reason. Apply the same fix here; the generated bindings expose `getBlock_asU8()` for this purpose.

In `packages/js-dapi-client/lib/methods/core/getBlockByHeightFactory.js`:
- [SUGGESTION] packages/js-dapi-client/lib/methods/core/getBlockByHeightFactory.js:31-33: `new Uint8Array(response.getBlock())` corrupts the block payload under grpc-web — use `getBlock_asU8()`
  Same root cause as `getBlockByHashFactory.js`: this PR (e703e9024f) replaced `Buffer.from(response.getBlock())` with `new Uint8Array(response.getBlock())`. Under grpc-web `response.getBlock()` can be a base64 `string`, and `new Uint8Array(string)` does not decode the payload — it yields garbage bytes. The PR already standardized on the `_asU8()` accessor in six sibling response classes; the same fix applies here. The generated `GetBlockResponse` exposes `getBlock_asU8()`.

In `packages/js-dapi-client/lib/methods/core/getBlockchainStatusFactory.js`:
- [SUGGESTION] packages/js-dapi-client/lib/methods/core/getBlockchainStatusFactory.js:34-46: `new Uint8Array(response.getChain().getBestBlockHash() / getChainWork())` corrupts bytes under grpc-web — use `_asU8()`
  This factory was modified by this PR (e703e9024f changed `Buffer.from(...)` to `new Uint8Array(...)` for `bestBlockHash` and `chainWork`). Under grpc-web the protobuf `bytes` getters return a base64 `string` rather than a `Uint8Array`, and `new Uint8Array(string)` does not base64-decode — it produces a zero-length or NaN-length typed array, silently corrupting the values. The latest delta in this PR (7885bb39) explicitly fixed this exact pattern in six sibling response classes with the comment "new Uint8Array(string) does NOT base64-decode, silently losing bytes." The `GetBlockchainStatusResponse` chain message exposes `getBestBlockHash_asU8()` and `getChainWork_asU8()`; this site was missed in the fix-up sweep.

In `packages/js-dapi-client/lib/methods/platform/getDocuments/GetDocumentsResponse.js`:
- [SUGGESTION] packages/js-dapi-client/lib/methods/platform/getDocuments/GetDocumentsResponse.js:31-36: `getDocumentsList().map((d) => new Uint8Array(d))` corrupts each document under grpc-web — use `getDocumentsList_asU8()`
  This response class was modified by this PR (e703e9024f). Each entry returned by `documents.getDocumentsList()` is a protobuf `bytes` value, which under grpc-web can be a base64 `string`. Wrapping each entry in `new Uint8Array(string)` does not base64-decode it; it produces garbage bytes for every document returned. The PR's latest delta already standardized on `_asU8()` accessors elsewhere for exactly this reason. The generated bindings expose `getDocumentsList_asU8()` which returns an array of `Uint8Array` regardless of grpc-js vs grpc-web representation.

Comment on lines 30 to +32
const blockBinaryArray = response.getBlock();

return Buffer.from(blockBinaryArray);
return new Uint8Array(blockBinaryArray);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: new Uint8Array(response.getBlock()) corrupts the block payload under grpc-web — use getBlock_asU8()

This factory was modified by this PR (e703e90 changed Buffer.from(response.getBlock()) to new Uint8Array(response.getBlock())). The google-protobuf JS binding returns bytes fields as a Uint8Array under grpc-js but as a base64 string under grpc-web. new Uint8Array(string) does NOT base64-decode — it coerces the string length to a numeric and produces a zero-length / bogus typed array, silently corrupting the block bytes returned to callers. The latest delta in this PR explicitly fixed this same pattern in GetTransactionResponse, GetIdentityResponse, GetIdentityByPublicKeyHashResponse, GetStatusResponse, Proof, and WaitForStateTransitionResultResponse using the _asU8() accessor for exactly this reason. Apply the same fix here; the generated bindings expose getBlock_asU8() for this purpose.

Suggested change
const blockBinaryArray = response.getBlock();
return Buffer.from(blockBinaryArray);
return new Uint8Array(blockBinaryArray);
return response.getBlock_asU8();

source: ['codex']

Comment on lines 31 to +33
const blockBinaryArray = response.getBlock();

return Buffer.from(blockBinaryArray);
return new Uint8Array(blockBinaryArray);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: new Uint8Array(response.getBlock()) corrupts the block payload under grpc-web — use getBlock_asU8()

Same root cause as getBlockByHashFactory.js: this PR (e703e90) replaced Buffer.from(response.getBlock()) with new Uint8Array(response.getBlock()). Under grpc-web response.getBlock() can be a base64 string, and new Uint8Array(string) does not decode the payload — it yields garbage bytes. The PR already standardized on the _asU8() accessor in six sibling response classes; the same fix applies here. The generated GetBlockResponse exposes getBlock_asU8().

Suggested change
const blockBinaryArray = response.getBlock();
return Buffer.from(blockBinaryArray);
return new Uint8Array(blockBinaryArray);
return response.getBlock_asU8();

source: ['codex']

Comment on lines 34 to 46
if (response.getChain()) {
if (response.getChain()
.getBestBlockHash()) {
responseObject.chain.bestBlockHash = Buffer.from(response.getChain()
responseObject.chain.bestBlockHash = new Uint8Array(response.getChain()
.getBestBlockHash());
}

if (response.getChain()
.getChainWork()) {
responseObject.chain.chainWork = Buffer.from(response.getChain()
responseObject.chain.chainWork = new Uint8Array(response.getChain()
.getChainWork());
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: new Uint8Array(response.getChain().getBestBlockHash() / getChainWork()) corrupts bytes under grpc-web — use _asU8()

This factory was modified by this PR (e703e90 changed Buffer.from(...) to new Uint8Array(...) for bestBlockHash and chainWork). Under grpc-web the protobuf bytes getters return a base64 string rather than a Uint8Array, and new Uint8Array(string) does not base64-decode — it produces a zero-length or NaN-length typed array, silently corrupting the values. The latest delta in this PR (7885bb3) explicitly fixed this exact pattern in six sibling response classes with the comment "new Uint8Array(string) does NOT base64-decode, silently losing bytes." The GetBlockchainStatusResponse chain message exposes getBestBlockHash_asU8() and getChainWork_asU8(); this site was missed in the fix-up sweep.

Suggested change
if (response.getChain()) {
if (response.getChain()
.getBestBlockHash()) {
responseObject.chain.bestBlockHash = Buffer.from(response.getChain()
responseObject.chain.bestBlockHash = new Uint8Array(response.getChain()
.getBestBlockHash());
}
if (response.getChain()
.getChainWork()) {
responseObject.chain.chainWork = Buffer.from(response.getChain()
responseObject.chain.chainWork = new Uint8Array(response.getChain()
.getChainWork());
}
}
if (response.getChain()) {
if (response.getChain()
.getBestBlockHash()) {
// Use _asU8 so we get bytes regardless of the underlying protobuf
// representation (grpc-js: Uint8Array; grpc-web: base64 string).
// new Uint8Array(string) does NOT base64-decode, silently losing bytes.
responseObject.chain.bestBlockHash = response.getChain()
.getBestBlockHash_asU8();
}
if (response.getChain()
.getChainWork()) {
responseObject.chain.chainWork = response.getChain()
.getChainWork_asU8();
}
}

source: ['claude', 'codex']

Comment on lines 31 to 36
return new GetDocumentsResponse(
documents !== undefined
? documents.getDocumentsList().map((document) => Buffer.from(document)) : [],
? documents.getDocumentsList().map((document) => new Uint8Array(document)) : [],
metadata,
proof,
);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: getDocumentsList().map((d) => new Uint8Array(d)) corrupts each document under grpc-web — use getDocumentsList_asU8()

This response class was modified by this PR (e703e90). Each entry returned by documents.getDocumentsList() is a protobuf bytes value, which under grpc-web can be a base64 string. Wrapping each entry in new Uint8Array(string) does not base64-decode it; it produces garbage bytes for every document returned. The PR's latest delta already standardized on _asU8() accessors elsewhere for exactly this reason. The generated bindings expose getDocumentsList_asU8() which returns an array of Uint8Array regardless of grpc-js vs grpc-web representation.

Suggested change
return new GetDocumentsResponse(
documents !== undefined
? documents.getDocumentsList().map((document) => Buffer.from(document)) : [],
? documents.getDocumentsList().map((document) => new Uint8Array(document)) : [],
metadata,
proof,
);
return new GetDocumentsResponse(
documents !== undefined
? documents.getDocumentsList_asU8() : [],
metadata,
proof,
);

source: ['codex']

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.

4 participants