Skip to content

Conversation

HashEngineering
Copy link
Collaborator

@HashEngineering HashEngineering commented Feb 4, 2025

Updates to support tx metadata changes

  1. System Contract
  2. protobuf

Summary by CodeRabbit

  • New Features

    • Protobuf-based TxMetadata batching and publishing with progress tracking.
    • Decryption supports both CBOR and Protobuf formats with version detection.
    • Added wallet-utils app/contract and a utility to list registered names with balances.
    • New examples for creating and displaying TxMetadata.
  • Improvements

    • Selectable encryption key index; stronger decryption validation.
    • Queries now order/filter by updatedAt.
    • System IDs updated (zero-hash owners); identity-verify document type renamed.
    • TxMetadataItem gains JSON/Protobuf conversions.
  • Documentation

    • README dependency versions updated.
  • Chores

    • Version bumps and SDK version logging.
    • Safer error handling when building document queries.

@HashEngineering HashEngineering self-assigned this Feb 4, 2025
Copy link
Member

@Syn-McJ Syn-McJ left a comment

Choose a reason for hiding this comment

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

Looks good

@HashEngineering
Copy link
Collaborator Author

merge with main

Copy link
Member

@Syn-McJ Syn-McJ left a comment

Choose a reason for hiding this comment

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

Looks good

@HashEngineering HashEngineering changed the title Feat/improve txmetadata feat: improve txmetadata features Jul 6, 2025
Copy link

coderabbitai bot commented Oct 8, 2025

Walkthrough

Version updates across Gradle and README. Significant SWIG/FFI remapping of clone/ignore surfaces. New wallet-utils protobuf schema and migration of TxMetadata to support versioned CBOR/Protobuf batching, encryption, publishing, and decryption. Platform app IDs updated and new wallet-utils IDs added. Tests and examples expanded. Rust SDK adds error handling and adjusts export.

Changes

Cohort / File(s) Summary
Version bumps
README.md, build.gradle
Updated DPP and DashJ versions; README snippet bumped to 2.0.1.
SWIG/FFI remap & build
dash-sdk-java/build.gradle, dash-sdk-java/src/main/cpp/clone.h, dash-sdk-java/src/main/swig/generics/result.i, dash-sdk-java/src/main/swig/ignore.i
Interpolated SWIG include path; extensive redefinition of clone signatures to new platform_mobile/... types; large overhaul of SWIG ignore surface; removed debug prints in result handling.
Java test updates
dash-sdk-java/src/test/java/.../DataContractTest.java, dash-sdk-java/src/test/java/.../ValueTest.java
Strengthened Optional presence checks; adjusted expected doc_types count; removed short-based PlatformValue test path.
Wallet-utils protobuf + TxMetadata migration
dpp/src/main/proto/wallet-utils.proto, dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt, .../TxMetadataDocument.kt, .../TxMetadataItem.kt
Added protobuf schema (TxMetadataItem, TxMetadataBatch). Introduced versioned buffer building (CBOR/Protobuf), new publish method, updated queries to use updatedAt. TxMetadataDocument detects/decrypts by version. TxMetadataItem moved package, added to/from protobuf and JSON mapping; removed version field.
DashPay identity and metadata flow
dpp/src/main/java/org/dashj/platform/dashpay/BlockchainIdentity.kt
Replaced legacy single-doc flow with batching: create, encrypt, and publish multiple documents; added progress support; updated retrieval and decryption to support protobuf versioning.
System/app IDs
dpp/src/main/java/org/dashj/platform/dapiclient/SystemIds.kt, .../sdk/platform/Platform.kt, .../wallet/IdentityVerify.kt
Switched owner IDs to zero-hash for DPNS/DashPay; added wallet-utils IDs; updated app mappings to identity-verify; changed IdentityVerify document type string.
Kotlin tests
dpp/src/test/kotlin/.../TxMetaDataTests.kt, dpp/src/test/kotlin/.../TxMetadataWalletTest.kt
Added protobuf round-trip/size tests; expanded item sets; new wallet-level tests for publishing, decryption, error paths, and key derivation validation.
Examples
examples/src/main/kotlin/dashj/org/platform/CreateTxMetadata.kt, .../CreateTxMetadataTest.kt, .../DisplayTxMetadata.kt, .../RegisteredNamesWithBalance.kt
Updated to new publish signature and item package; added comprehensive TxMetadata test harness; input handling tweak; new utility to list DPNS names with balances and pagination.
Rust SDK (platform-mobile)
platform-mobile/src/fetch_document.rs, platform-mobile/src/sdk.rs
Wrapped DocumentQuery creation with error return instead of unwrap; removed export attribute from update function; added version tracing on SDK creation.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant BI as BlockchainIdentity
  participant TM as TxMetadata
  participant Doc as Documents API
  participant DC as Data Contract (wallet-utils)

  App->>BI: publishTxMetaData(items, keyParam?, encKeyIndex, version, progress?)
  BI->>BI: createTxMetadata(items, keyParam?, encKeyIndex, version)
  note right of BI: Build versioned payloads (CBOR/Protobuf)<br/>Select encryption key by index<br/>Encrypt per document
  BI->>Doc: publish(Document for DC)
  loop for each document
    Doc-->>BI: ack with stored Document
    BI-->>App: progress?(i)
  end
  BI-->>App: List<Document> (published)
Loading
sequenceDiagram
  autonumber
  participant App
  participant BI as BlockchainIdentity
  participant TMD as TxMetadataDocument
  participant Dec as Decrypt

  App->>BI: decryptTxMetadata(doc, keyParam?)
  BI->>TMD: decrypt(keyParam?) 
  TMD->>Dec: decrypt payload
  alt first byte == VERSION_CBOR
    Note right of TMD: Set txMetadataVersion = CBOR
    TMD-->>BI: items = Cbor.decodeList(...)
  else first byte == VERSION_PROTOBUF
    Note right of TMD: Set txMetadataVersion = Protobuf
    TMD-->>BI: items = TxMetadataBatch.parseFrom(...)
  else
    TMD-->>BI: error (unknown version)
  end
  BI-->>App: List<TxMetadataItem>
Loading
sequenceDiagram
  autonumber
  participant Caller
  participant FD as fetch_document.rs
  Caller->>FD: build all_docs_query
  alt DocumentQuery::new Ok
    FD-->>Caller: proceed with with_where(...)
  else Err(e)
    Note right of FD: Return error instead of panic
    FD-->>Caller: Err(String)
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Poem

In burrows of code where prototypes play,
I nibble on bytes, then hop a new way—
CBOR or Protobuf, I sniff and decide,
Encrypt little carrots, batch them with pride.
IDs realigned, queries don’t crash,
With whiskers aflutter, I commit and I stash.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.85% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “feat: improve txmetadata features” indicates a feature change around transaction metadata but is too generic to convey the specific enhancements like system contract updates and protobuf support introduced in this PR. It partially relates to the main domain but lacks the detail needed for clear understanding by someone scanning the history. Consider revising the title to explicitly highlight the key changes, for example: “feat: add protobuf-based tx metadata batching and system contract integration” so that readers immediately grasp the primary enhancements.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/improve-txmetadata

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

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

Caution

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

⚠️ Outside diff range comments (1)
README.md (1)

55-60: Fix README version to match published artifact.

Line 55 advertises dppVersion = "2.0.1", but the build currently ships as 2.0.1-TX-SNAPSHOT (see build.gradle Line 2). Anyone following the README will pull a non-existent artifact and break their build. Please update the snippet (or release a 2.0.1 artifact) so the instructions resolve correctly.

🧹 Nitpick comments (3)
dash-sdk-java/src/test/java/org/dashj/platform/sdk/ValueTest.java (1)

42-47: Remove the commented dead code.

The commented //v2.delete() on line 43 should be removed entirely, as the v2 variable no longer exists in this test.

Apply this diff to clean up the commented code:

     v1.delete();
-    //v2.delete();
     v3.delete();
dpp/src/main/proto/wallet-utils.proto (1)

9-9: Consider fixed-point representation for financial fields.

The use of double for exchangeRate (line 9) and originalPrice (line 17) can lead to precision issues with financial calculations due to floating-point representation limitations.

Consider using one of these alternatives:

  • Option 1 (Recommended): Use string representation with a documented format (e.g., "123.45" for currency amounts), allowing the application layer to parse with appropriate decimal precision.
  • Option 2: Use fixed-point representation with separate integer fields for the whole and fractional parts, or use an integer representing the smallest currency unit (e.g., cents).

Example for Option 1:

-  optional double exchangeRate = 4;
+  optional string exchangeRate = 4;  // decimal string, e.g., "1.2345"
-  optional double originalPrice = 12;
+  optional string originalPrice = 12;  // decimal string, e.g., "99.99"

Also applies to: 17-17

dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetaDataTests.kt (1)

226-229: Remove the empty test stub.

publishToPlatform() contains no assertions or behavior. Keeping an empty test just adds noise and trips detekt’s empty-block rule. Drop the method or flesh it out with real coverage.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b32d9f1 and 04bd06b.

⛔ Files ignored due to path filters (3)
  • dash-sdk-android/src/main/rust/Cargo.lock is excluded by !**/*.lock
  • dash-sdk-bindings/Cargo.lock is excluded by !**/*.lock
  • platform-mobile/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • README.md (1 hunks)
  • build.gradle (1 hunks)
  • dash-sdk-java/build.gradle (1 hunks)
  • dash-sdk-java/src/main/cpp/clone.h (2 hunks)
  • dash-sdk-java/src/main/swig/generics/result.i (0 hunks)
  • dash-sdk-java/src/main/swig/ignore.i (5 hunks)
  • dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java (3 hunks)
  • dash-sdk-java/src/test/java/org/dashj/platform/sdk/ValueTest.java (1 hunks)
  • dpp/src/main/java/org/dashj/platform/dapiclient/SystemIds.kt (1 hunks)
  • dpp/src/main/java/org/dashj/platform/dashpay/BlockchainIdentity.kt (6 hunks)
  • dpp/src/main/java/org/dashj/platform/sdk/platform/Platform.kt (1 hunks)
  • dpp/src/main/java/org/dashj/platform/wallet/IdentityVerify.kt (1 hunks)
  • dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt (3 hunks)
  • dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt (3 hunks)
  • dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt (4 hunks)
  • dpp/src/main/proto/wallet-utils.proto (1 hunks)
  • dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetaDataTests.kt (5 hunks)
  • dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetadataWalletTest.kt (1 hunks)
  • examples/src/main/kotlin/dashj/org/platform/CreateTxMetadata.kt (2 hunks)
  • examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt (1 hunks)
  • examples/src/main/kotlin/dashj/org/platform/DisplayTxMetadata.kt (2 hunks)
  • examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt (1 hunks)
  • platform-mobile/src/fetch_document.rs (1 hunks)
  • platform-mobile/src/sdk.rs (2 hunks)
💤 Files with no reviewable changes (1)
  • dash-sdk-java/src/main/swig/generics/result.i
🧰 Additional context used
🧬 Code graph analysis (1)
dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt (2)
dpp/src/main/java/org/dashj/platform/dashpay/Profiles.kt (2)
  • get (166-168)
  • get (170-185)
dpp/src/main/java/org/dashj/platform/sdk/platform/Documents.kt (2)
  • get (151-167)
  • get (169-187)
🪛 Buf (1.57.2)
dpp/src/main/proto/wallet-utils.proto

3-3: Files with package "org.dashj.platform.wallet" must be within a directory "org/dashj/platform/wallet" relative to root but were in directory "dpp/src/main/proto".

(PACKAGE_DIRECTORY_MATCH)

🪛 detekt (1.23.8)
examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt

[warning] 74-74: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetaDataTests.kt

[warning] 227-229: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🔇 Additional comments (20)
platform-mobile/src/fetch_document.rs (1)

106-109: LGTM! Improved error handling.

The change from .unwrap() to explicit error matching prevents potential panics and provides better error propagation, which is consistent with the function's error handling pattern.

dash-sdk-java/src/test/java/org/dashj/platform/sdk/ValueTest.java (1)

23-48: LGTM! Redundant test code appropriately removed.

The removal of the v2 test code for I16 is appropriate since I16 is already comprehensively tested in createPlatformValuePrimitiveTest() at line 148. This cleanup reduces code duplication without affecting test coverage.

dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java (2)

20-22: LGTM: Improved null-safety.

The defensive pattern of checking isPresent() before calling get() prevents potential NoSuchElementException if the Optional is empty.


34-36: LGTM: Improved null-safety.

Consistent with the pattern above—checking isPresent() before calling get() prevents potential runtime exceptions.

platform-mobile/src/sdk.rs (2)

225-225: LGTM! Good observability improvement.

Adding the SDK version log is consistent with the existing pattern in create_dash_sdk_with_context (line 173) and improves observability.


62-92: No external FFI bindings reference update_sdk_with_address_list; removing its export attribute is safe.

dpp/src/main/java/org/dashj/platform/dapiclient/SystemIds.kt (1)

10-19: LGTM! Wallet-utils identifiers added consistently.

The addition of wallet-utils identifiers and the refactoring of existing IDs to use Sha256Hash.ZERO_HASH follow a consistent pattern and align with the broader wallet-utils integration across the codebase.

dpp/src/main/java/org/dashj/platform/sdk/platform/Platform.kt (1)

80-91: LGTM! App definitions updated to support wallet-utils and identity-verify.

The addition of the wallet-utils app and the environment-specific identity-verify app definitions are consistent with the system-wide migration to wallet-utils identifiers introduced in SystemIds.kt.

examples/src/main/kotlin/dashj/org/platform/DisplayTxMetadata.kt (2)

29-38: LGTM! Improved argument handling with default identity support.

The refactored argument parsing now supports both interactive phrase input and default identity seeds, which improves the utility's flexibility.


55-55: LGTM! Consistent with updated metadata ordering.

The change from createdAt to updatedAt aligns with the broader shift in TxMetadata.kt where ordering and filtering now use $updatedAt instead of $createdAt.

dpp/src/main/java/org/dashj/platform/wallet/IdentityVerify.kt (1)

30-30: LGTM! Document constant updated to reflect identity-verify app.

The DOCUMENT constant change from "dashwallet.identityVerify" to "identity-verify.identityVerify" is consistent with the Platform.kt updates where the identity-verify app definition replaces dashwallet in environment mappings.

examples/src/main/kotlin/dashj/org/platform/CreateTxMetadata.kt (2)

10-10: LGTM! Import updated to reflect TxMetadataItem package relocation.

The import change from org.dashj.platform.contracts.wallet.TxMetadataItem to org.dashj.platform.wallet.TxMetadataItem is consistent with the broader reorganization of wallet-related types into the wallet package.


47-47: LGTM! Updated to use versioned metadata publishing.

The addition of the keyIndex parameter (1) and version parameter (TxMetadataDocument.VERSION_PROTOBUF) aligns with the new versioned metadata handling introduced in TxMetadataDocument.kt and the updated publishTxMetaData signature.

dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt (4)

69-91: LGTM! New publish method follows established patterns.

The new publish() method correctly retrieves the HIGH security level key, calls the platform SDK with appropriate parameters, and returns a Document. The implementation is consistent with the existing create() method pattern.


113-121: LGTM! Version-aware buffer serialization implemented correctly.

The getBuffer() method properly handles both CBOR and Protobuf serialization formats based on the version parameter, with appropriate error handling for invalid versions.


37-37: LGTM! Document constant updated for wallet-utils migration.

The DOCUMENT constant change from "dashwallet.tx_metadata" to "wallet-utils.txMetadata" is consistent with the broader migration to wallet-utils identifiers and data contracts.


130-133: ```bash
#!/bin/bash

Show get() signature in TxMetadata.kt

rg -n 'fun get' -g 'dpp/src/main/java/org/dashj/platform/wallet/TxMetadata.kt' -C3

Find direct calls to TxMetadata(...).get(

rg -nP 'TxMetadata([^)]).get(' -g '.kt'

Find instantiations of TxMetadata

rg -nP 'val\s+\w+\s*=\sTxMetadata(' -g '.kt'


</blockquote></details>
<details>
<summary>dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt (1)</summary><blockquote>

`27-39`: **LGTM! Version tracking added with good encapsulation.**

The addition of version constants and the `txMetadataVersion` field with a private setter provides clean version tracking for metadata documents. The field is appropriately initialized to `VERSION_UNKNOWN` and updated during decryption.

</blockquote></details>
<details>
<summary>dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt (2)</summary><blockquote>

`8-8`: **LGTM: Package reorganization and new imports.**

The package move to `org.dashj.platform.wallet` and the addition of protobuf/formatting imports appropriately support the new proto conversion features.




Also applies to: 11-12, 15-15

---

`281-281`: **Note: toString() now uses locale-dependent formatting.**

The toString() method now delegates to `toJson()`, which means numeric fields (exchangeRate, originalPrice) will be displayed with locale-specific currency formatting. This is acceptable for debugging/logging but be aware that the string representation will vary across locales.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +38 to +39
assertEquals(3, dataContract.getDoc_types().size());
assertTrue("didn't find txMetadata", dataContract.getDoc_types().stream().allMatch(type -> type.equals("txMetadata")));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix the assertion logic.

Line 39 uses allMatch(type -> type.equals("txMetadata")), which checks that every doc_type is named "txMetadata". With 3 doc_types, this assertion would only pass if all three have identical names, which violates the principle that data contract doc types should have distinct names.

Additionally, the error message "didn't find txMetadata" is misleading—it suggests checking for existence, but allMatch verifies that all types are "txMetadata".

Did you intend to:

  • Verify that "txMetadata" exists among the doc_types? Use anyMatch:
    -assertTrue("didn't find txMetadata", dataContract.getDoc_types().stream().allMatch(type -> type.equals("txMetadata")));
    +assertTrue("didn't find txMetadata", dataContract.getDoc_types().stream().anyMatch(type -> type.equals("txMetadata")));
  • Or verify specific doc_type names (e.g., three distinct types)? Assert the exact set:
    Set<String> expectedTypes = Set.of("txMetadata", "typeA", "typeB");
    assertEquals(expectedTypes, new HashSet<>(dataContract.getDoc_types()));

Please clarify the intended assertion and apply the appropriate fix.

🤖 Prompt for AI Agents
In dash-sdk-java/src/test/java/org/dashj/platform/sdk/DataContractTest.java
around lines 38-39, the test uses allMatch which wrongly asserts every doc_type
equals "txMetadata". Replace that with anyMatch to assert existence (and
keep/update the failure message to "didn't find txMetadata"), OR if the intent
is to verify all three specific distinct types, assert the exact set of names by
comparing a Set of expected names to new HashSet<>(dataContract.getDoc_types())
so the test validates the exact distinct types.

Comment on lines +64 to +78
val version = decryptedData.copyOfRange(0, 1)[0].toInt() and 0xFF
return when (version) {
VERSION_CBOR -> {
val list = Cbor.decodeList(decryptedData)
this.txMetadataVersion = VERSION_CBOR
// use .map to convert to List<TxMetadataItem>
list.map { TxMetadataItem(it as Map<String, Any?>) }
}
VERSION_PROTOBUF -> {
val batch = TxMetadataBatch.parser().parseFrom(decryptedData, 1, decryptedData.size - 1)
txMetadataVersion = VERSION_PROTOBUF
batch.itemsList.map { TxMetadataItem(it) }
}
else -> error("")
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Improve error message for invalid version.

The version-aware decryption logic correctly handles both CBOR and Protobuf formats, but the error message on line 77 is empty, making debugging difficult if an invalid version is encountered.

Apply this diff to provide a more informative error message:

-            else -> error("")
+            else -> error("Unknown metadata version: $version. Expected $VERSION_CBOR (CBOR) or $VERSION_PROTOBUF (Protobuf)")
🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt around
lines 64 to 78, the else branch currently calls error("") which provides no
context; change it to throw an informative exception that includes the invalid
version value (e.g., "Unsupported tx metadata version: X") and optionally lists
supported versions (VERSION_CBOR, VERSION_PROTOBUF), so the runtime log shows
the actual version byte encountered for easier debugging.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify version byte format consistency.

The decrypt logic assumes the first byte of decrypted data is a version byte (line 64), and the Protobuf parsing starts at offset 1 (line 73). Ensure this format is consistent with how TxMetadata.getBuffer() serializes the data.

Run the following script to verify that getBuffer() prepends a version byte to the serialized data:


🏁 Script executed:

#!/bin/bash
# Description: Verify that TxMetadata.getBuffer() prepends version byte to match decrypt expectations

# Search for getBuffer implementation and any version byte prepending logic
ast-grep --pattern 'fun getBuffer($$$) {
  $$$
}'

# Also search for any references to version byte handling in serialization
rg -nP --type=kt -B 5 -A 10 'VERSION_CBOR|VERSION_PROTOBUF' dpp/src/main/java/org/dashj/platform/wallet/

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Locate the Kotlin file defining TxMetadata
file=$(find . -type f -name 'TxMetadata.kt' | head -n1)
if [ -z "$file" ]; then
  echo "ERROR: TxMetadata.kt not found"
  exit 1
fi
echo "Inspecting: $file"

# Show getBuffer implementation
echo -e "\n---- getBuffer() definition ----"
rg -n 'fun getBuffer' "$file" -C5

# Show where version bytes are prepended
echo -e "\n---- VERSION_CBOR / VERSION_PROTOBUF usage ----"
rg -n 'VERSION_CBOR|VERSION_PROTOBUF' "$file" -C5

Length of output: 1290


Prefix version byte in getBuffer()
In TxMetadata.kt (line 113), getBuffer() returns only the raw payload, yet TxMetadataDocument.kt (lines 64–78) reads the first byte of decrypted data as the version. Update getBuffer() to prepend the version byte, e.g.:

val payload = when (version) {
  VERSION_CBOR    -> Cbor.encode(items.map { it.toObject() })
  VERSION_PROTOBUF -> TxMetadataBatch.newBuilder()…
}
return byteArrayOf(version.toByte()) + payload
🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataDocument.kt around
lines 64 to 78, the code expects the first byte of the decrypted buffer to be a
version byte but getBuffer() currently returns only the raw payload; update the
getBuffer() implementation (in TxMetadata.kt) so it prepends the version byte to
the serialized payload: serialize payload based on version (CBOR or PROTOBUF)
then return a new byte array composed of the single version byte followed by the
payload bytes, ensuring callers can read the version from index 0 and the
payload from index 1; keep existing serialization logic unchanged and just
concatenate the version byte in front.

Comment on lines +78 to +94
constructor(protoTxMetadata: WalletUtils.TxMetadataItem) : this(
protoTxMetadata.txId.toByteArray(),
if (protoTxMetadata.timestamp != 0L) protoTxMetadata.timestamp else null,
if (protoTxMetadata.memo != "") protoTxMetadata.memo else null,
if (protoTxMetadata.exchangeRate != 0.0) protoTxMetadata.exchangeRate else null,
if (protoTxMetadata.currencyCode != "") protoTxMetadata.currencyCode else null,
if (protoTxMetadata.taxCategory != "") protoTxMetadata.taxCategory else null,
if (protoTxMetadata.service != "") protoTxMetadata.service else null,
if (protoTxMetadata.customIconUrl != "") protoTxMetadata.customIconUrl else null,
if (protoTxMetadata.giftCardNumber != "") protoTxMetadata.giftCardNumber else null,
if (protoTxMetadata.giftCardPin != "") protoTxMetadata.giftCardPin else null,
if (protoTxMetadata.merchantName != "") protoTxMetadata.merchantName else null,
if (protoTxMetadata.originalPrice != 0.00) protoTxMetadata.originalPrice else null,
if (protoTxMetadata.barcodeValue != "") protoTxMetadata.barcodeValue else null,
if (protoTxMetadata.barcodeFormat != "") protoTxMetadata.barcodeFormat else null,
if (protoTxMetadata.merchantUrl != "") protoTxMetadata.merchantUrl else null
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Consider more robust default handling for protobuf fields.

The constructor relies on empty-string and zero-value checks to map protobuf defaults to null. This approach is fragile:

  • Line 90: Floating-point equality check (!= 0.00) is imprecise
  • If protobuf defaults change or if legitimate zero/empty values need representation, this logic breaks

Consider using protobuf's hasField() methods or explicit presence indicators to distinguish between "not set" and "set to zero/empty".

🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt around lines 78
to 94, the constructor treats protobuf default values (empty strings and zeros)
as "unset" by comparing to "" and 0.00 which is fragile and incorrect for
floating-point and when legitimate zero/empty values are intended; update the
mapping to use protobuf presence checks (e.g. hasTimestamp(), hasMemo(),
hasExchangeRate(), or the generated oneof/optional presence methods) or switch
the proto schema to wrapper/optional types and call those presence methods so
you only map to null when the field is actually unset, avoiding direct equality
checks for doubles and empty-string heuristics.

Comment on lines +100 to +102
timestamp?.let {
map["timestamp"] to it
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Assignment operator missing.

Line 101 uses the to infix operator, which creates a Pair<String, Long> but does not assign it to the map. The timestamp will never be added to the returned map.

Apply this diff to fix the assignment:

     timestamp?.let {
-        map["timestamp"] to it
+        map["timestamp"] = it
     }
🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt around lines
100 to 102, the timestamp?.let block uses the `to` infix operator which creates
a Pair but does not insert it into the map; change the block to assign the value
into the map (e.g., use map["timestamp"] = it or map.put("timestamp", it)) so
the timestamp is actually added to the returned map.

Comment on lines +163 to +165
timestamp?.let {
map["timestamp"] to it
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Assignment operator missing.

Line 164 has the same bug as in toObject() - using to instead of = for map assignment.

Apply this diff:

     timestamp?.let {
-        map["timestamp"] to it
+        map["timestamp"] = it
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
timestamp?.let {
map["timestamp"] to it
}
timestamp?.let {
map["timestamp"] = it
}
🤖 Prompt for AI Agents
In dpp/src/main/java/org/dashj/platform/wallet/TxMetadataItem.kt around lines
163 to 165, the map assignment uses the Kotlin pair operator "to" instead of the
assignment operator; replace the expression map["timestamp"] to it with an
actual assignment map["timestamp"] = it (same fix as in toObject()), so the
timestamp is stored into the map rather than creating a Pair.

@@ -0,0 +1,26 @@
syntax = "proto3";

package org.dashj.platform.wallet;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Address package directory mismatch.

The static analysis tool flags that files with package org.dashj.platform.wallet should be in directory org/dashj/platform/wallet relative to root, but this file is in dpp/src/main/proto. This mismatch may cause issues with protobuf tooling or build systems that expect strict package-to-directory mapping.

Consider either:

  1. Moving this file to dpp/src/main/proto/org/dashj/platform/wallet/wallet-utils.proto, or
  2. Adjusting the package name to match the directory structure, or
  3. Documenting why the mismatch is acceptable in this codebase (if there's a build system that handles this correctly).
🧰 Tools
🪛 Buf (1.57.2)

3-3: Files with package "org.dashj.platform.wallet" must be within a directory "org/dashj/platform/wallet" relative to root but were in directory "dpp/src/main/proto".

(PACKAGE_DIRECTORY_MATCH)

🤖 Prompt for AI Agents
In dpp/src/main/proto/wallet-utils.proto around line 3, the proto package is
declared as org.dashj.platform.wallet but the file lives in dpp/src/main/proto
which violates the expected package-to-directory mapping; fix by either moving
the file to dpp/src/main/proto/org/dashj/platform/wallet/wallet-utils.proto so
the path matches the package, or change the package declaration to reflect its
current directory, or add a short repository-level comment/doc explaining and
referencing the build config that intentionally handles this mismatch if you
choose to leave it as-is.

Comment on lines +142 to +144
// println("before: $balanceBefore")
// println("after: $balanceAfter")
// }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix off-by-one in loop.

IntRange(0, count) is inclusive, so you enqueue count + 1 metadata items. Publishing extra documents unnecessarily spends credits and skews tests. Use repeat(count) or for (i in 0 until count) instead.

Apply:

-        for (i in IntRange(0, count)) {
+        repeat(count) {
             txMetadataList.add(txMetadata)
         }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
dpp/src/test/kotlin/org/dashj/platform/contracts/wallet/TxMetadataWalletTest.kt
around lines 142 to 144, the loop uses IntRange(0, count) which is inclusive and
thus enqueues count + 1 metadata items; change the loop to produce exactly count
items by replacing IntRange(0, count) with either repeat(count) or for (i in 0
until count) so you publish the intended number of documents (and remove any
now-unnecessary commented debug prints if desired).

Comment on lines +142 to +144
for (i in IntRange(0, count)) {
txMetadataList.add(txMetadata)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Avoid off-by-one when duplicating items.

IntRange(0, count) produces count + 1 iterations because the end value is inclusive. That means you publish one more metadata entry than requested, which can skew balance checks and waste credits. Switch to repeat(count) or for (i in 0 until count) to honor the intended count.

Suggested fix:

-        for (i in IntRange(0, count)) {
+        repeat(count) {
             txMetadataList.add(txMetadata)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (i in IntRange(0, count)) {
txMetadataList.add(txMetadata)
}
repeat(count) {
txMetadataList.add(txMetadata)
}
🤖 Prompt for AI Agents
In examples/src/main/kotlin/dashj/org/platform/CreateTxMetadataTest.kt around
lines 142-144, the loop uses IntRange(0, count) which is inclusive and produces
count+1 iterations (off-by-one); replace it with either repeat(count) {
txMetadataList.add(txMetadata) } or for (i in 0 until count) {
txMetadataList.add(txMetadata) } so exactly count copies are added.

Comment on lines +56 to +63
lastItem = documents.last().id
if (documents.size == 100) {
queryOpts = DocumentQuery.Builder()
.startAt(lastItem)
.orderBy("normalizedLabel")
.limit(100)
.build()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard empty pages before dereferencing last()
If the first page (or any subsequent page) returns zero documents, documents.last() throws NoSuchElementException, crashing the example. Break out of the loop when the page is empty before dereferencing lastItem.

-                    documents = platform.documents.get("dpns.domain", queryOpts)
-                    allDocuments.addAll(documents)
-                    requests += 1
-
-                    lastItem = documents.last().id
-                    if (documents.size == 100) {
+                    documents = platform.documents.get("dpns.domain", queryOpts)
+                    allDocuments.addAll(documents)
+                    requests += 1
+
+                    if (documents.isEmpty()) {
+                        break
+                    }
+
+                    lastItem = documents.last().id
+                    if (documents.size == limit) {
                         queryOpts = DocumentQuery.Builder()
                             .startAt(lastItem)
                             .orderBy("normalizedLabel")
-                            .limit(100)
+                            .limit(limit)
                             .build()
                     }
```<!-- review_comment_end -->

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
                    documents = platform.documents.get("dpns.domain", queryOpts)
                    allDocuments.addAll(documents)
                    requests += 1

                    if (documents.isEmpty()) {
                        break
                    }

                    lastItem = documents.last().id
                    if (documents.size == limit) {
                        queryOpts = DocumentQuery.Builder()
                            .startAt(lastItem)
                            .orderBy("normalizedLabel")
                            .limit(limit)
                            .build()
                    }
🤖 Prompt for AI Agents
In examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt
around lines 56 to 63, guard against empty pages before calling
documents.last(): check if documents.isEmpty() and break out of the paging loop
(or return) before dereferencing last(); only compute lastItem and build the
next DocumentQuery when documents is not empty and documents.size == 100,
ensuring you don't call last() on an empty list which causes
NoSuchElementException.

Comment on lines +72 to +82
allDocuments.forEachIndexed { i, document ->
val nameDocument = DomainDocument(document)
val balance = try { platform.client.getIdentityBalance(nameDocument.dashUniqueIdentityId!!) } catch (e: Exception) { - 2 }
nameBalanceMap[nameDocument.normalizedLabel] = balance
}
println("username count: ${allDocuments.size}")
allDocuments.forEachIndexed { i, document ->
val nameDocument = DomainDocument(document)
val balance = nameBalanceMap[nameDocument.normalizedLabel]
println("$i. ${nameDocument.label}: ${Coin.valueOf((balance ?: -1) / 1000).toFriendlyString()}")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t swallow balance-fetch failures
Catching every Exception and inserting -2 causes failed lookups to render as “0 DASH”, hiding real errors. Log the failure and skip (or mark as unavailable) so the output stays truthful.

-                val balance = try { platform.client.getIdentityBalance(nameDocument.dashUniqueIdentityId!!) } catch (e: Exception) { - 2 }
-                nameBalanceMap[nameDocument.normalizedLabel] = balance
+                val balance = try {
+                    platform.client.getIdentityBalance(nameDocument.dashUniqueIdentityId!!)
+                } catch (e: Exception) {
+                    println("Failed to fetch balance for ${nameDocument.label}: ${e.message}")
+                    null
+                } ?: return@forEachIndexed
+                nameBalanceMap[nameDocument.normalizedLabel] = balance
@@
-                val balance = nameBalanceMap[nameDocument.normalizedLabel]
-                println("$i.  ${nameDocument.label}: ${Coin.valueOf((balance ?: -1) / 1000).toFriendlyString()}")
+                val balance = nameBalanceMap[nameDocument.normalizedLabel]
+                if (balance == null) {
+                    println("$i.  ${nameDocument.label}: <balance unavailable>")
+                } else {
+                    println("$i.  ${nameDocument.label}: ${Coin.valueOf(balance / 1000).toFriendlyString()}")
+                }
```<!-- review_comment_end -->
<!-- file_end -->

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
            allDocuments.forEachIndexed { i, document ->
                val nameDocument = DomainDocument(document)
                val balance = try {
                    platform.client.getIdentityBalance(nameDocument.dashUniqueIdentityId!!)
                } catch (e: Exception) {
                    println("Failed to fetch balance for ${nameDocument.label}: ${e.message}")
                    null
                } ?: return@forEachIndexed
                nameBalanceMap[nameDocument.normalizedLabel] = balance
            }
            println("username count: ${allDocuments.size}")
            allDocuments.forEachIndexed { i, document ->
                val nameDocument = DomainDocument(document)
                val balance = nameBalanceMap[nameDocument.normalizedLabel]
                if (balance == null) {
                    println("$i.  ${nameDocument.label}: <balance unavailable>")
                } else {
                    println("$i.  ${nameDocument.label}: ${Coin.valueOf(balance / 1000).toFriendlyString()}")
                }
            }
🧰 Tools
🪛 detekt (1.23.8)

[warning] 74-74: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🤖 Prompt for AI Agents
In examples/src/main/kotlin/dashj/org/platform/RegisteredNamesWithBalance.kt
around lines 72–82 the code swallows all Exceptions when fetching balances and
writes -2 into the map (which later prints as 0 DASH), hiding real errors;
change the balance handling to catch the exception, log the error with context
(e.g., identity id and exception message/stack) and store a nullable or explicit
"unavailable" marker in nameBalanceMap (or skip insertion) instead of -2, then
update the printing loop to detect null/unavailable and print "unavailable" (or
skip) rather than converting a sentinel into currency; ensure you use the
project logger (or println if none) and avoid dividing/formatting when balance
is unavailable.

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.

2 participants