Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features

* [FEATURE] Distinct `ApplyTransactionAfterSubmitFailed` error variant with one apply retry, so callers know a submitted tx landed on-chain and should not be re-submitted ([#2059](https://github.com/0xMiden/miden-client/pull/2059)).
* [FEATURE][web] Stable `errorCode` string property on thrown JS errors for variant dispatch without message-string parsing ([#2060](https://github.com/0xMiden/miden-client/pull/2060)).
* [FEATURE][web] Added `"custom"` operation to `preview()` so users can dry-run any pre-built `TransactionRequest`, not just send/mint/consume/swap ([#2052](https://github.com/0xMiden/miden-client/pull/2052)).

## 0.14.3 (2026-04-16)
Expand Down
38 changes: 38 additions & 0 deletions crates/web-client/src/lib.rs

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.

It would be awesome to have a test for this

Original file line number Diff line number Diff line change
Expand Up @@ -349,12 +349,21 @@ where
}

let help = hint_from_error(&err);
let code = error_code_from_error(&err);
let js_error: JsValue = JsError::new(&error_string).into();

if let Some(help) = help {
let _ = Reflect::set(&js_error, &JsValue::from_str("help"), &JsValue::from_str(&help));
}

// Stable, machine-readable code for consumers that need to react
// differently based on the specific ClientError variant. The string
// text is load-bearing — see `error_code_from_client_error` for the
// list of codes and their contract.
if let Some(code) = code {
let _ = Reflect::set(&js_error, &JsValue::from_str("errorCode"), &JsValue::from_str(code));
}

js_error
}

Expand All @@ -365,3 +374,32 @@ fn hint_from_error(err: &(dyn Error + 'static)) -> Option<String> {

err.source().and_then(hint_from_error)
}

/// Walks the error chain looking for a [`ClientError`] and returns a
/// stable, machine-readable code for it. Used by [`js_error_with_context`]
/// to attach an `errorCode` string to the JS Error — consumers can
/// pattern-match on it to trigger failure-mode-specific handling
/// (e.g. treat a submitted-but-not-applied tx as Completed rather than
/// Failed). Codes are the variant names; the contract is that they
/// don't change across releases.
fn error_code_from_error(err: &(dyn Error + 'static)) -> Option<&'static str> {
if let Some(client_error) = err.downcast_ref::<ClientError>() {
return error_code_from_client_error(client_error);
}
err.source().and_then(error_code_from_error)
}

fn error_code_from_client_error(err: &ClientError) -> Option<&'static str> {
// Only include variants consumers are known to dispatch on. Others
// can be added as new callers need them — the stability contract is
// "code string never changes once added."
match err {
ClientError::ApplyTransactionAfterSubmitFailed { .. } => {
Some("ApplyTransactionAfterSubmitFailed")
},
ClientError::AccountLocked(_) => Some("AccountLocked"),
ClientError::NoteNotFoundOnChain(_) => Some("NoteNotFoundOnChain"),
ClientError::RpcError(_) => Some("RpcError"),
_ => None,
}
}
Comment on lines +392 to +405

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.

When someone adds a new ClientError variant that JS should dispatch on, the compiler won't remind them. We might want to use an enum containing the variants that we want to expose as JS errors, that way we could enforce the conversion at compile time.

Loading