Skip to content

Comments

feat(rs-sdk): expose transition hash from state transition methods#3092

Open
thepastaclaw wants to merge 3 commits intodashpay:v3.1-devfrom
thepastaclaw:feat/sdk-transition-hash
Open

feat(rs-sdk): expose transition hash from state transition methods#3092
thepastaclaw wants to merge 3 commits intodashpay:v3.1-devfrom
thepastaclaw:feat/sdk-transition-hash

Conversation

@thepastaclaw
Copy link
Contributor

@thepastaclaw thepastaclaw commented Feb 17, 2026

Issue being fixed or feature implemented

State transition methods in the Rust SDK (broadcast_and_wait, put_to_platform_and_wait_for_response, etc.) discard the transition hash (transaction ID) after broadcast. Downstream consumers (WASM SDK, evo-sdk) cannot access it without re-computing or restructuring their calls.

This is the Rust SDK foundation for the work started in #2953, restructured per review feedback to implement the logic in the Rust SDK first (rather than only in the WASM SDK).

What was done?

Added StateTransitionResult<T> — a thin wrapper that bundles a proof result T with the 32-byte transition hash (transaction ID).

Core changes:

  • New type: StateTransitionResult<T> in packages/rs-sdk/src/platform/transition/state_transition_result.rs
    • transition_hash() -> [u8; 32] — get the hash
    • into_parts() -> (T, [u8; 32]) — destructure
    • into_inner() -> T — unwrap (backward compat)
    • Deref<Target=T> — transparent access to inner result
  • Modified: BroadcastStateTransition::broadcast_and_wait returns StateTransitionResult<T> instead of T
  • Updated all 28 downstream methods across documents, tokens, identity, and transfer operations

Race condition avoidance

The transition hash is computed deterministically from the StateTransition struct before broadcast via StateTransition::transaction_id() (SHA-256 of serialized transition). It does not depend on blockchain state — no race condition possible. This addresses the concern raised in #2953.

Files modified (30 files)

  • broadcast.rs — core trait change
  • state_transition_result.rs — new wrapper type
  • 6 document transitions (create, delete, purchase, replace, set_price, transfer)
  • 11 token transitions (burn, claim, config_update, destroy_frozen_funds, direct_purchase, emergency_action, freeze, mint, set_price_for_direct_purchase, transfer, unfreeze)
  • 9 identity/transfer operations

How Has This Been Tested?

  • cargo check -p dash-sdk passes
  • All changes are type-level — existing code using into_inner() or Deref works unchanged

Breaking Changes

broadcast_and_wait now returns StateTransitionResult<T> instead of T. Callers that need just T can use .into_inner() or rely on Deref.

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

Summary by CodeRabbit

  • New Features

    • State transition results now include deterministic transition hashes for tracking and validation.
  • Refactor

    • Updated broadcast operations to wrap results with transition metadata; all document and token transitions aligned with new result handling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A new StateTransitionResult<T> wrapper type is introduced to bundle state transition results with deterministic transition hashes. The broadcast_and_wait API is updated to return wrapped results, and 20+ call sites across document, token, and general transitions are modified to unwrap the inner value via into_inner().

Changes

Cohort / File(s) Summary
Core State Transition Infrastructure
packages/rs-sdk/src/platform/transition.rs, packages/rs-sdk/src/platform/transition/state_transition_result.rs
Introduces new public StateTransitionResult<T> generic wrapper struct with methods new(), transition_hash(), into_parts(), into_inner(), map(), and Deref impl. Adds public module export in transition.rs.
Broadcast API Change
packages/rs-sdk/src/platform/transition/broadcast.rs
Updates broadcast_and_wait return type from Result<T, Error> to Result<StateTransitionResult<T>, Error>. Adds transition_hash computation via transaction_id() and wraps final result via StateTransitionResult::new().
Document Transitions Call Site Updates
packages/rs-sdk/src/platform/documents/transitions/create.rs, delete.rs, purchase.rs, replace.rs, set_price.rs, transfer.rs
Added .into_inner() calls after awaiting broadcast_and_wait to extract wrapped inner values before pattern matching.
Token Transitions Call Site Updates
packages/rs-sdk/src/platform/tokens/transitions/burn.rs, claim.rs, config_update.rs, destroy_frozen_funds.rs, direct_purchase.rs, emergency_action.rs, freeze.rs, mint.rs, set_price_for_direct_purchase.rs, transfer.rs, unfreeze.rs
Added .into_inner() calls after awaiting broadcast_and_wait to extract wrapped inner values before pattern matching.
General Transition Call Site Updates
packages/rs-sdk/src/platform/transition/address_credit_withdrawal.rs, put_identity.rs, top_up_address.rs, top_up_identity.rs, top_up_identity_from_addresses.rs, transfer.rs, transfer_address_funds.rs, transfer_to_addresses.rs, withdraw_from_identity.rs
Added .into_inner() calls after awaiting broadcast_and_wait to extract wrapped inner values before pattern matching. Some call sites explicitly specify generic type parameters (e.g., :<PartialIdentity>).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A transition wrapped with care,
Hash and value form a pair,
Unwrapped with .into_inner() call,
Consistent changes throughout it all!
Deterministic hops, SDK takes flight,

🚥 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 'feat(rs-sdk): expose transition hash from state transition methods' directly and accurately describes the main objective of the PR: introducing a wrapper type to expose the 32-byte transition hash from state transition methods.
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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@thepastaclaw
Copy link
Contributor Author

@shumkov This implements the transition hash exposure in the Rust SDK first (as you suggested on #2953), so the WASM SDK stays thin. Would appreciate your review when you have a moment.

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: 1

🧹 Nitpick comments (2)
packages/rs-sdk/src/platform/transition/state_transition_result.rs (1)

11-15: Consider deriving PartialEq and Eq for testability.

Adding PartialEq and Eq (bounded on T: PartialEq/T: Eq) would make it straightforward to assert equality in tests without having to decompose into parts.

Suggested change
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-sdk/src/platform/transition/state_transition_result.rs` around
lines 11 - 15, The StateTransitionResult struct should derive PartialEq and Eq
for easier testing: add PartialEq and Eq to the derive list on
StateTransitionResult and constrain implementations so these derives require T:
PartialEq (and T: Eq for Eq). Update the derive macro on the struct
(StateTransitionResult) to include PartialEq and Eq so tests can directly
compare instances without manual decomposition.
packages/rs-sdk/src/platform/documents/transitions/create.rs (1)

174-179: Consider propagating the transition hash through the result enums.

All document transition methods (create, delete, replace, purchase, set_price) currently discard the transition hash via into_inner(). To fully surface the hash to SDK consumers — which is the goal of this PR — these per-operation result enums (e.g., DocumentCreateResult) could carry the [u8; 32] alongside the payload, or the methods could return StateTransitionResult<DocumentCreateResult> directly. This can be a follow-up.

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

In `@packages/rs-sdk/src/platform/documents/transitions/create.rs` around lines
174 - 179, The DocumentCreateResult enum currently drops the transition hash
(via into_inner()), so update the API to propagate the transition hash: either
add a hash field to the enum (e.g., change
DocumentCreateResult::Document(Document) to carry (Document, [u8; 32]) or
similar) and adjust all callers that construct/consume DocumentCreateResult, or
instead change the create method(s) to return
StateTransitionResult<DocumentCreateResult> directly so the hash is preserved;
apply the same pattern for the other per-operation enums/methods (delete,
replace, purchase, set_price) and ensure functions that call into_inner() are
updated to handle the preserved hash accordingly.
🤖 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/rs-sdk/src/platform/tokens/transitions/unfreeze.rs`:
- Around line 69-70: The broadcast call hardcodes None for settings causing
caller-provided settings to be ignored; change the unfreeze transition to
extract settings from the builder (use
unfreeze_tokens_transition_builder.settings or the builder’s with_settings()
value) and pass that into
state_transition.broadcast_and_wait::<StateTransitionProofResult>(self,
put_settings) instead of None; apply the same fix pattern used in
destroy_frozen_funds (and replicate for mint.rs, freeze.rs,
set_price_for_direct_purchase.rs) so user_fee_increase and retry configuration
are honored.

---

Nitpick comments:
In `@packages/rs-sdk/src/platform/documents/transitions/create.rs`:
- Around line 174-179: The DocumentCreateResult enum currently drops the
transition hash (via into_inner()), so update the API to propagate the
transition hash: either add a hash field to the enum (e.g., change
DocumentCreateResult::Document(Document) to carry (Document, [u8; 32]) or
similar) and adjust all callers that construct/consume DocumentCreateResult, or
instead change the create method(s) to return
StateTransitionResult<DocumentCreateResult> directly so the hash is preserved;
apply the same pattern for the other per-operation enums/methods (delete,
replace, purchase, set_price) and ensure functions that call into_inner() are
updated to handle the preserved hash accordingly.

In `@packages/rs-sdk/src/platform/transition/state_transition_result.rs`:
- Around line 11-15: The StateTransitionResult struct should derive PartialEq
and Eq for easier testing: add PartialEq and Eq to the derive list on
StateTransitionResult and constrain implementations so these derives require T:
PartialEq (and T: Eq for Eq). Update the derive macro on the struct
(StateTransitionResult) to include PartialEq and Eq so tests can directly
compare instances without manual decomposition.

@thepastaclaw
Copy link
Contributor Author

Addressed the CodeRabbit review in 22fc89c:

Fixed:

  • Settings bugunfreeze, mint, freeze, and set_price_for_direct_purchase now extract settings from the builder before sign() consumes it, passing them to broadcast_and_wait instead of hardcoded None. Matches the pattern in destroy_frozen_funds.
  • PartialEq/Eq — Added derives to StateTransitionResult for testability.

Deferred:

  • The into_inner() hash propagation suggestion — agreed this is better as a follow-up since it changes the public API of all document transition result enums.

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 (1)
packages/rs-sdk/src/platform/transition/state_transition_result.rs (1)

27-29: Nit: consider returning &[u8; 32] from transition_hash().

Currently returns by value (copy). Since [u8; 32] is Copy this is fine and arguably simpler, but returning a reference would be more conventional for accessor methods on fixed-size data and avoids a 32-byte copy on non-optimized builds. Totally optional.

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

In `@packages/rs-sdk/src/platform/transition/state_transition_result.rs` around
lines 27 - 29, The accessor transition_hash() currently returns [u8; 32] by
value which copies the array; change its signature to return a reference ->
&[u8; 32] and return &self.transition_hash to avoid the copy (update the method
in the impl for the relevant struct). After changing the signature, update any
call sites that rely on ownership (e.g., places that expect a copied array
should explicitly clone/copy if needed) and run tests to ensure no API
breakages.
🤖 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/rs-sdk/src/platform/transition/state_transition_result.rs`:
- Around line 27-29: The accessor transition_hash() currently returns [u8; 32]
by value which copies the array; change its signature to return a reference ->
&[u8; 32] and return &self.transition_hash to avoid the copy (update the method
in the impl for the relevant struct). After changing the signature, update any
call sites that rely on ownership (e.g., places that expect a copied array
should explicitly clone/copy if needed) and run tests to ensure no API
breakages.

Copy link
Collaborator

@shumkov shumkov left a comment

Choose a reason for hiding this comment

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

It doesn't make sense to me. You have already state transition around so you can store hash into varibable and reuse it. @PastaPastaPasta please review your friend's PRs first.

@thepastaclaw
Copy link
Contributor Author

Thanks for the review @shumkov. You make a fair point — if the caller already has the state transition, they can compute the hash themselves via state_transition.transaction_id().expect("...").

The motivation was convenience for the WASM SDK layer (where thephez reported needing the txid from put_to_platform calls), but I agree this can be done at the call site without modifying the SDK return types.

Happy to close this if the consensus is that it's unnecessary overhead. @PastaPastaPasta thoughts?

@PastaPastaPasta
Copy link
Member

Shumkov and claw most times you use the SDK you don't actually have the transition? It gets built internally I think.

I see no reason to not return txid.

@thepastaclaw
Copy link
Contributor Author

@shumkov — I want to respectfully push back on this.

You're right that inside the SDK, the state transition exists as a local variable. But that's exactly the point — it's local to the SDK internals. The caller never sees it.

Look at create.rs:204-250:

pub async fn document_create(...) -> Result<DocumentCreateResult, Error> {
    let state_transition = builder.sign(...).await?;  // built internally
    let proof_result = state_transition.broadcast_and_wait(self, settings).await?;
    match proof_result { ... }  // consumed internally — only proof result returned
}

The StateTransition object is constructed, broadcast, and consumed entirely within this function. The caller gets back a DocumentCreateResult, MintResult, Identity, etc. — never the StateTransition.

So "you already have the state transition around" is true for someone modifying the SDK, but false for SDK consumers (WASM SDK, js-evo-sdk, application code). Without this PR (or similar), there's no way for WASM SDK callers to get the transaction ID after a put_to_platform call.

The StateTransitionResult<T> wrapper is a zero-cost way to surface this — Deref and into_inner() mean existing code doesn't break, and callers who don't need the hash simply ignore it.

As Pasta noted: "most times you use the SDK you don't actually have the transition. It gets built internally." This PR fixes that gap.

Comment on lines 66 to 75
let put_settings = freeze_tokens_transition_builder.settings;

let state_transition = freeze_tokens_transition_builder
.sign(self, signing_key, signer, platform_version)
.await?;

let proof_result = state_transition
.broadcast_and_wait::<StateTransitionProofResult>(self, None)
.await?;
.broadcast_and_wait::<StateTransitionProofResult>(self, put_settings)
.await?
.into_inner();
Copy link
Member

@PastaPastaPasta PastaPastaPasta Feb 19, 2026

Choose a reason for hiding this comment

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

claw is this let put_settings = freeze_tokens_transition_builder.settings a seperate bug fix that should live in it's own PR? or an unintended change here maybe?

same question for other adding of let put_settings = mint_tokens_transition_builder.settings;

Comment on lines 3 to 12
/// Wrapper that bundles a state transition proof result with the transition hash.
///
/// The transition hash (also known as the transaction ID) is computed deterministically
/// from the serialized `StateTransition` before broadcast, so it does not depend on
/// blockchain state and there is no race condition.
///
/// `StateTransitionResult<T>` implements `Deref<Target = T>`, so existing code that
/// only needs the inner result can use it transparently.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StateTransitionResult<T> {
Copy link
Member

Choose a reason for hiding this comment

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

claw; would it make more sense for the T to just include the transition hash? is that possible?

@thepastaclaw
Copy link
Contributor Author

Re: put_settings question:

Good catch — the put_settings additions are a separate bug fix. Those transition builders were not propagating the caller's PutSettings (retry config, timeout, etc.) to the broadcast_and_wait_for_state_transition_result call, so any custom settings would get silently ignored. I'll split it into its own PR.

Re: T including transition hash:

That's a good idea. The challenge is that T is currently the inner result type (e.g., Document, Identity) which comes from the platform response, while the transition hash is computed locally before broadcast. We could add a trait bound like WithTransitionHash to T or wrap them together in a new struct. I think the cleanest approach would be a TransitionResult<T> struct that pairs T with its hash — which is essentially what this PR does. Want me to rename for clarity?

@thepastaclaw
Copy link
Contributor Author

Re: @PastaPastaPasta's inline comments:

On put_settings in freeze/mint tokens: Good catch — those are indeed a separate bug fix (settings not being passed through to the broadcast call). Will split them into their own PR to keep it clean. 1 PR = 1 thing.

On including transition hash in T: That's a good idea — including the hash directly in the generic StateTransitionProofResult<T> type would be cleaner than a separate accessor. Let me look at whether StateTransitionProofResult can carry a transaction_id field alongside the existing data. Will prototype it.

@thepastaclaw
Copy link
Contributor Author

Re: put_settings question:

Done — split into its own PR: #3132

Removed the put_settings changes from this PR.

Re: T including transition hash:

Looked into this. StateTransitionProofResult is an enum with 20+ variants — adding a transaction_id to each would be very invasive. The StateTransitionResult<T> wrapper in this PR is essentially the same thing: it pairs any result T with its transition hash, with Deref and into_inner() so existing code doesn't break. This is the cleanest approach without touching every variant.

@thepastaclaw
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 (1)
packages/rs-sdk/src/platform/transition/transfer_to_addresses.rs (1)

23-27: ⚠️ Potential issue | 🟡 Minor

Stale duplicate bullet in the Returns doc-comment.

The return type is a 2-tuple (AddressInfos, Credits) but the doc lists three bullets, repeating "Proof-backed address infos for provided recipients" as both the first and third items.

📝 Proposed fix
-    /// Returns tuple of:
-    /// * Proof-backed address infos for provided recipients
-    /// * Updated identity balance
-    /// * Proof-backed address infos for provided recipients
+    /// Returns tuple of:
+    /// * Proof-backed address infos for provided recipients
+    /// * Updated identity balance after the transfer
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-sdk/src/platform/transition/transfer_to_addresses.rs` around
lines 23 - 27, The Returns doc-comment in transfer_to_addresses.rs currently
lists three bullets and repeats "Proof-backed address infos for provided
recipients" although the function returns a 2-tuple (AddressInfos, Credits);
update the doc-comment for the function (e.g., the transfer_to_addresses
function) to list exactly two return bullets matching the actual return types:
1) Proof-backed address infos for provided recipients (AddressInfos) and 2)
Updated identity balance (Credits), removing the duplicated third bullet and
keeping wording consistent with the return type names.
🧹 Nitpick comments (1)
packages/rs-sdk/src/platform/transition/top_up_identity_from_addresses.rs (1)

84-116: Transition hash is silently discarded — high-level helpers won't propagate it to WASM/JS callers.

All five high-level SDK methods updated in this PR (top_up_from_addresses_with_nonce, token_freeze, document_transfer, transfer_credits_to_addresses, put_with_address_funding) call .into_inner() immediately and throw away the 32-byte transition hash. The hash is only observable from callers who invoke broadcast_and_wait directly — but the PR's stated motivation is specifically that "WASM SDK, js-evo-sdk, application code do not have access to the internal StateTransition". Callers using these high-level SDK methods, including the WASM SDK thin layer built on top of them, will still receive no hash.

Consider either:

  • Returning (ReturnType, [u8; 32]) from each high-level method, or
  • Defining a higher-level counterpart of StateTransitionResult<T> that wraps the domain-specific result types (FreezeResult, DocumentTransferResult, etc.)

If propagation is intentionally deferred, tracking the gap in a follow-up issue would help ensure it's not forgotten.

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

In `@packages/rs-sdk/src/platform/transition/top_up_identity_from_addresses.rs`
around lines 84 - 116, The code discards the 32-byte transition hash by calling
.into_inner() on broadcast_and_wait results; update
top_up_from_addresses_with_nonce (and the other high-level methods) to capture
the transition hash before into_inner() and propagate it to callers—either by
returning a tuple like (address_infos, balance, transition_hash) from the
handler that matches
StateTransitionProofResult::VerifiedIdentityWithAddressInfos (use the existing
collect_address_infos_from_proof and identity.balance logic) or by introducing a
small wrapper type (e.g., StateTransitionResult<T> containing result and
[u8;32]) and returning that instead so the transition hash from
broadcast_and_wait is not lost.
🤖 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/rs-sdk/src/platform/transition/transfer_to_addresses.rs`:
- Around line 23-27: The Returns doc-comment in transfer_to_addresses.rs
currently lists three bullets and repeats "Proof-backed address infos for
provided recipients" although the function returns a 2-tuple (AddressInfos,
Credits); update the doc-comment for the function (e.g., the
transfer_to_addresses function) to list exactly two return bullets matching the
actual return types: 1) Proof-backed address infos for provided recipients
(AddressInfos) and 2) Updated identity balance (Credits), removing the
duplicated third bullet and keeping wording consistent with the return type
names.

---

Nitpick comments:
In `@packages/rs-sdk/src/platform/transition/top_up_identity_from_addresses.rs`:
- Around line 84-116: The code discards the 32-byte transition hash by calling
.into_inner() on broadcast_and_wait results; update
top_up_from_addresses_with_nonce (and the other high-level methods) to capture
the transition hash before into_inner() and propagate it to callers—either by
returning a tuple like (address_infos, balance, transition_hash) from the
handler that matches
StateTransitionProofResult::VerifiedIdentityWithAddressInfos (use the existing
collect_address_infos_from_proof and identity.balance logic) or by introducing a
small wrapper type (e.g., StateTransitionResult<T> containing result and
[u8;32]) and returning that instead so the transition hash from
broadcast_and_wait is not lost.

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

94-100: ⚠️ Potential issue | 🟠 Major

transition_hash is silently discarded — consider surfacing it to JS callers.

.into_inner() correctly satisfies the convert_proof_result type, but it throws away the transition_hash that StateTransitionResult<T> carries. The PR discussion explicitly identifies WASM SDK consumers (js-evo-sdk, browser apps) as the primary beneficiaries of having the transaction hash, yet those callers receive no way to observe it from broadcastAndWait.

A minimal approach would be to return a small two-field JS object that contains both the proof result and the hex-encoded hash, e.g.:

💡 Sketch of a hash-exposing return value
+use wasm_bindgen::JsValue;
+use js_sys::Object;

 pub async fn broadcast_and_wait(
     &self,
     #[wasm_bindgen(js_name = "stateTransition")] state_transition: &StateTransitionWasm,
     settings: Option<PutSettingsJs>,
-) -> Result<StateTransitionProofResultTypeJs, WasmSdkError> {
+) -> Result<JsValue, WasmSdkError> {
     let st: StateTransition = state_transition.into();
     let put_settings = parse_put_settings(settings)?;

     let result = st
         .broadcast_and_wait::<StateTransitionProofResult>(self.as_ref(), put_settings)
         .await
         .map_err(|e| WasmSdkError::generic(format!("Failed to broadcast: {}", e)))?;

+    let (inner, hash) = result.into_parts();
+    let proof = convert_proof_result(inner).map_err(WasmSdkError::from)?;
+    let hash_hex = hex::encode(hash);
+    let obj = Object::new();
+    js_sys::Reflect::set(&obj, &"result".into(), &proof.into())?;
+    js_sys::Reflect::set(&obj, &"transitionHash".into(), &hash_hex.into())?;
+    Ok(obj.into())
-    convert_proof_result(result.into_inner()).map_err(WasmSdkError::from)
 }

Alternatively, exposing a broadcastAndWaitWithHash sibling method would avoid a breaking change to broadcastAndWait's return type for existing callers.

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

In `@packages/wasm-sdk/src/state_transitions/broadcast.rs` around lines 94 - 100,
The code currently discards the transition_hash by calling result.into_inner()
before convert_proof_result; update the broadcast path (the method invoking
st.broadcast_and_wait::<StateTransitionProofResult> — the wasm-facing
broadcastAndWait wrapper) to extract both the inner proof result and the
transition_hash from the returned StateTransitionResult<T> (do not call
into_inner() alone), then return both to JS either by changing the return to a
small two-field object { proofResult, transitionHashHex } or by adding a sibling
method broadcastAndWaitWithHash that returns that object; ensure you hex-encode
the transition_hash, keep existing error handling (WasmSdkError) and still call
convert_proof_result on the proof payload before packaging the result.
🤖 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/wasm-sdk/src/state_transitions/broadcast.rs`:
- Around line 94-100: The code currently discards the transition_hash by calling
result.into_inner() before convert_proof_result; update the broadcast path (the
method invoking st.broadcast_and_wait::<StateTransitionProofResult> — the
wasm-facing broadcastAndWait wrapper) to extract both the inner proof result and
the transition_hash from the returned StateTransitionResult<T> (do not call
into_inner() alone), then return both to JS either by changing the return to a
small two-field object { proofResult, transitionHashHex } or by adding a sibling
method broadcastAndWaitWithHash that returns that object; ensure you hex-encode
the transition_hash, keep existing error handling (WasmSdkError) and still call
convert_proof_result on the proof payload before packaging the result.

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: 1

🧹 Nitpick comments (1)
packages/rs-sdk/src/platform/transition.rs (1)

19-19: Optional: state_transition_result module declaration is out of alphabetical order.

It is placed after transfer_to_addresses (line 18) rather than between put_settings and top_up_address, breaking the existing alphabetical grouping. Consider moving it to maintain consistency.

♻️ Suggested placement
 pub mod put_settings;
+pub mod state_transition_result;
 pub mod top_up_address;
 ...
-pub mod state_transition_result;  // remove from line 19
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-sdk/src/platform/transition.rs` at line 19, The module
declaration for state_transition_result is out of alphabetical order relative to
the other pub mod lines; move the pub mod state_transition_result; declaration
so it appears between pub mod put_settings; and pub mod top_up_address; (i.e.,
place state_transition_result between those two) to restore the alphabetical
grouping and consistent ordering with transfer_to_addresses, put_settings, and
top_up_address.
🤖 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/rs-sdk/src/platform/transition/state_transition_result.rs`:
- Line 11: Add PartialEq and Eq to the derive list for the StateTransitionResult
struct so tests can use assert_eq!; update the existing #[derive(Debug, Clone)]
line to #[derive(Debug, Clone, PartialEq, Eq)] (i.e., extend the derive on the
struct defined in state_transition_result.rs) and run cargo test to verify
equality-based assertions now work.

---

Nitpick comments:
In `@packages/rs-sdk/src/platform/transition.rs`:
- Line 19: The module declaration for state_transition_result is out of
alphabetical order relative to the other pub mod lines; move the pub mod
state_transition_result; declaration so it appears between pub mod put_settings;
and pub mod top_up_address; (i.e., place state_transition_result between those
two) to restore the alphabetical grouping and consistent ordering with
transfer_to_addresses, put_settings, and top_up_address.

///
/// `StateTransitionResult<T>` implements `Deref<Target = T>`, so existing code that
/// only needs the inner result can use it transparently.
#[derive(Debug, Clone)]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "state_transition_result.rs" | head -5

Repository: dashpay/platform

Length of output: 128


🏁 Script executed:

cat -n packages/rs-sdk/src/platform/transition/state_transition_result.rs | head -50

Repository: dashpay/platform

Length of output: 2044


🏁 Script executed:

git log --oneline --all | grep -i "22fc89c1c\|state.transition.result" | head -10

Repository: dashpay/platform

Length of output: 144


🏁 Script executed:

git show --stat 22fc89c1c 2>/dev/null | head -30

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

git log --all --oneline --decorate | grep -i "state.transition" | head -20

Repository: dashpay/platform

Length of output: 1741


🏁 Script executed:

git log --all --grep="PartialEq\|Eq" --oneline | head -10

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

rg "StateTransitionResult" packages/rs-sdk/src/platform/transition/ -A 5 -B 2

Repository: dashpay/platform

Length of output: 10366


🏁 Script executed:

git log --all --oneline packages/rs-sdk/src/platform/transition/state_transition_result.rs | head -10

Repository: dashpay/platform

Length of output: 130


🏁 Script executed:

git show d6f4eb9ac --stat | head -40

Repository: dashpay/platform

Length of output: 2283


🏁 Script executed:

git log --all --oneline --grep="22fc89c1c" | head -5

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

git log --all --oneline | grep -E "22fc89c1c|3077" | head -5

Repository: dashpay/platform

Length of output: 135


🏁 Script executed:

git show d6f4eb9ac:packages/rs-sdk/src/platform/transition/state_transition_result.rs 2>/dev/null | head -20

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

git show d6f4eb9ac --format=fuller | head -100

Repository: dashpay/platform

Length of output: 4611


🏁 Script executed:

git log --all --oneline -S "PartialEq" -- packages/rs-sdk/src/platform/transition/state_transition_result.rs | head -10

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

git log --all --oneline -S "Eq" -- packages/rs-sdk/src/platform/transition/state_transition_result.rs | head -10

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

git show d6f4eb9ac -- packages/rs-sdk/src/platform/transition/state_transition_result.rs | head -50

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

git show d6f4eb9ac:packages/rs-sdk/src/platform/transition/state_transition_result.rs 2>&1 | cat

Repository: dashpay/platform

Length of output: 179


🏁 Script executed:

git log --all --pretty=format:"%H %s" | grep "3077"

Repository: dashpay/platform

Length of output: 408


🏁 Script executed:

git show --name-status d6f4eb9ac | grep "state_transition_result"

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

git diff d6f4eb9ac~1 d6f4eb9ac -- packages/rs-sdk/src/platform/transition/state_transition_result.rs

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

git log --all --oneline -S "StateTransitionResult" -- "**.rs" | head -20

Repository: dashpay/platform

Length of output: 146


🏁 Script executed:

git log --oneline --all packages/rs-sdk/src/platform/transition/ | head -20

Repository: dashpay/platform

Length of output: 1669


🏁 Script executed:

git show HEAD:packages/rs-sdk/src/platform/transition/state_transition_result.rs 2>&1 | head -30

Repository: dashpay/platform

Length of output: 1067


🏁 Script executed:

rg "PartialEq.*Eq" packages/rs-sdk/src/platform/transition/ -B 2

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

git show 07b2e5cba --format=fuller | head -50

Repository: dashpay/platform

Length of output: 2109


🏁 Script executed:

git show 07b2e5cba -- packages/rs-sdk/src/platform/transition/state_transition_result.rs | head -80

Repository: dashpay/platform

Length of output: 2982


🏁 Script executed:

git log --all -p -- packages/rs-sdk/src/platform/transition/state_transition_result.rs | grep -A 3 -B 3 "PartialEq\|Eq" | head -40

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

git show 07b2e5cba --stat | grep "state_transition_result"

Repository: dashpay/platform

Length of output: 245


🏁 Script executed:

git log --all --oneline --grep="post-review\|22fc89c1c" | head -10

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

rg "StateTransitionResult" packages/rs-sdk/src/platform/transition/broadcast.rs -B 2 -A 2 | head -50

Repository: dashpay/platform

Length of output: 1363


Add PartialEq and Eq derives for test assertions.

The struct only derives Debug and Clone, but PartialEq and Eq would enable assert_eq! comparisons in tests. Both should be unconditionally derived.

🛠️ Proposed fix
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct StateTransitionResult<T> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-sdk/src/platform/transition/state_transition_result.rs` at line
11, Add PartialEq and Eq to the derive list for the StateTransitionResult struct
so tests can use assert_eq!; update the existing #[derive(Debug, Clone)] line to
#[derive(Debug, Clone, PartialEq, Eq)] (i.e., extend the derive on the struct
defined in state_transition_result.rs) and run cargo test to verify
equality-based assertions now work.

@thepastaclaw thepastaclaw force-pushed the feat/sdk-transition-hash branch from 07b2e5c to 4e49bd2 Compare February 21, 2026 18:02
Add StateTransitionResult<T> wrapper type that bundles the proof result
with the transition hash (transaction ID). The hash is computed
deterministically from the serialized StateTransition BEFORE broadcast,
avoiding any race condition with blockchain state.

broadcast_and_wait now returns StateTransitionResult<T> instead of T.
The wrapper implements Deref<Target=T> for ergonomic access and provides
into_inner() for callers that only need the result. All existing callers
updated to use .into_inner() to maintain current behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@thepastaclaw thepastaclaw force-pushed the feat/sdk-transition-hash branch from 4e49bd2 to b2f3b42 Compare February 21, 2026 19:25
…esult

- Move state_transition_result module to alphabetical position in transition.rs
- Update wasm-sdk broadcast.rs to unwrap StateTransitionResult with into_inner()
thepastaclaw added a commit to thepastaclaw/platform that referenced this pull request Feb 21, 2026
…3092)

The into_inner() calls on broadcast results require the StateTransitionResult
type from PR dashpay#3092. Since this PR targets v3.1-dev which doesn't have dashpay#3092
merged yet, these calls break compilation. Reverting until dashpay#3092 lands.
wait_for_response returns T directly, not StateTransitionResult<T>,
so into_inner() is not needed there.
@@ -0,0 +1,56 @@
use std::ops::Deref;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question. Yes, it would be cleaner to include the transition hash directly in the result types (DocumentCreateResult, MintResult, etc.) rather than using a generic wrapper.

The wrapper approach (StateTransitionResult<T>) was chosen because:

  1. It avoids modifying 20+ result enums individually
  2. The hash is a property of the broadcast operation, not the result type itself

But Pasta's right that embedding it in T would be simpler for consumers — no .into_inner() calls, no wrapper to deal with. The downside is every result type needs a transition_hash field added.

I'd lean toward keeping the wrapper for now since it's non-invasive, but happy to refactor if you prefer the embedded approach. What's your preference?

@thepastaclaw
Copy link
Contributor Author

Addressing the review comments:

  1. PutSettings bug — Yes, that was a separate bug. Split it out to PR #3132. This PR no longer includes those changes.

  2. Transition hash in T vs wrapper — Replied inline. The StateTransitionResult<T> wrapper was chosen to avoid modifying 20+ result types, but happy to refactor to embedded fields if you prefer that approach.

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