Skip to content
Open
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
65 changes: 58 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ env:
# to in Cargo.lock: that ref's own Cargo.lock pins the node rev the client is
# built against. A commit sha (not the branch name) keeps cache keys stable.
# Re-pin whenever Cargo.lock moves, and to the release tag once one ships.
# A `Client PR:` marker overrides this for the test-node jobs
# (resolve-client-ref); this value stays the fallback for pushes to next.
# v0.17.0-rc.1
MIDEN_CLIENT_REF: 2fb20aca0869034dd26aa35473dbfb14fa017083
# Sequencer 0.17.0-rc.2 will not start without a batch-builder wallet. The
# binary reads this variable; the node repo's local runner uses the same
# placeholder. A node that does not know it ignores the variable.
MIDEN_NODE_BATCH_BUILDER_WALLET_ACCOUNT_ID: "0xcc0000000000dd010000ee000000ff"

jobs:
# Pre-flight: detect whether any non-docs files changed. See build.yml's
Expand Down Expand Up @@ -826,11 +832,45 @@ jobs:
# the node rev pinned in its Cargo.lock, and builds the gen-genesis fixture
# generator from its test-node-genesis crate. This job pre-builds both
# pieces so the consumer jobs only download and start them.
# The published pin above tracks Cargo.lock. A PR that builds against an
# unreleased client via `Client PR:` must start the node from that same
# commit, or the block's protocol-config commitment is not the one the
# client stored.
resolve-client-ref:
name: Resolve test node ref
runs-on: ubuntu-24.04
outputs:
ref: ${{ steps.out.outputs.ref }}
steps:
- id: out
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
FALLBACK: ${{ env.MIDEN_CLIENT_REF }}
run: |
set -euo pipefail
ref="$FALLBACK"
if [ -n "${PR_NUMBER:-}" ]; then
body=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.body // ""')
marker=$(printf '%s\n' "$body" | grep -ioE '^[[:space:]]*Client PR:[[:space:]]*([0-9a-zA-Z._-]+/[0-9a-zA-Z._-]+)?#[0-9]+' | head -1 || true)
if [ -n "$marker" ]; then
repo=$(printf '%s\n' "$marker" | grep -oE '[0-9a-zA-Z._-]+/[0-9a-zA-Z._-]+' | head -1 || true)
repo=${repo:-0xMiden/rust-sdk}
num=$(printf '%s\n' "$marker" | grep -oE '[0-9]+$')
ref=$(gh api "repos/${repo}/pulls/${num}" --jq '.head.sha')
echo "linked client ${repo}#${num} -> ${ref}"
fi
fi
echo "ref=${ref}" >> "$GITHUB_OUTPUT"
echo "test node rust-sdk ref: ${ref}"

build-test-node:
name: Build test node
runs-on: warp-ubuntu-latest-x64-8x
needs: [changes]
needs: [changes, resolve-client-ref]
if: needs.changes.outputs.non_docs == 'true'
env:
MIDEN_CLIENT_REF: ${{ needs.resolve-client-ref.outputs.ref }}
steps:
- name: Checkout miden-client for test infra
uses: actions/checkout@v6
Expand Down Expand Up @@ -901,9 +941,10 @@ jobs:
# before the split.
name: Web client tests (Node.js)
runs-on: warp-ubuntu-latest-x64-8x
needs: [build-test-node]
needs: [build-test-node, resolve-client-ref]
env:
SCCACHE_GHA_ENABLED: "true"
MIDEN_CLIENT_REF: ${{ needs.resolve-client-ref.outputs.ref }}
steps:
- uses: actions/checkout@v6
# Auto-patch miden-client dep against any "Client PR: #N" marker in
Expand Down Expand Up @@ -990,7 +1031,9 @@ jobs:
# with the native fee asset, so run the node fee-free. Remove once the
# tests draw fee balances from the genesis funder wallets.
MIDEN_VERIFICATION_BASE_FEE: "0"
run: ./scripts/start-test-node.sh --background
run: |
../scripts/patch-linked-test-node.sh ./scripts/start-test-node.sh
./scripts/start-test-node.sh --background
# Since 0.17 the fee asset lives in the protocol configuration rather than
# the block header, so a client is told which faucet issues it instead of
# reading it off a block. Each genesis mints a fresh one, so it is read
Expand Down Expand Up @@ -1040,7 +1083,9 @@ jobs:
# for the rationale and the per-shard file lists.
name: Integration tests (${{ matrix.project }})
runs-on: warp-ubuntu-latest-x64-8x
needs: [changes, build-web-client-dist-folder, build-test-node]
needs: [changes, build-web-client-dist-folder, build-test-node, resolve-client-ref]
env:
MIDEN_CLIENT_REF: ${{ needs.resolve-client-ref.outputs.ref }}
if: needs.changes.outputs.non_docs == 'true'
strategy:
fail-fast: false
Expand Down Expand Up @@ -1103,7 +1148,9 @@ jobs:
# with the native fee asset, so run the node fee-free. Remove once the
# tests draw fee balances from the genesis funder wallets.
MIDEN_VERIFICATION_BASE_FEE: "0"
run: ./scripts/start-test-node.sh --background
run: |
../scripts/patch-linked-test-node.sh ./scripts/start-test-node.sh
./scripts/start-test-node.sh --background
# Since 0.17 the fee asset lives in the protocol configuration rather than
# the block header, so a client is told which faucet issues it instead of
# reading it off a block. Each genesis mints a fresh one, so it is read
Expand Down Expand Up @@ -1154,7 +1201,9 @@ jobs:
integration-tests-remote-prover-web-client:
name: Integration tests for remote prover
runs-on: warp-ubuntu-latest-x64-8x
needs: [changes, build-web-client-dist-folder, build-test-node]
needs: [changes, build-web-client-dist-folder, build-test-node, resolve-client-ref]
env:
MIDEN_CLIENT_REF: ${{ needs.resolve-client-ref.outputs.ref }}
# Remote prover tests are slow (~14 min) and rarely affected by typical PR
# changes. Skip on PRs unless explicitly opted in via the
# 'run-remote-prover' label; always run on push to main/next.
Expand Down Expand Up @@ -1217,7 +1266,9 @@ jobs:
# with the native fee asset, so run the node fee-free. Remove once the
# tests draw fee balances from the genesis funder wallets.
MIDEN_VERIFICATION_BASE_FEE: "0"
run: ./scripts/start-test-node.sh --background
run: |
../scripts/patch-linked-test-node.sh ./scripts/start-test-node.sh
./scripts/start-test-node.sh --background
# Since 0.17 the fee asset lives in the protocol configuration rather than
# the block header, so a client is told which faucet issues it instead of
# reading it off a block. Each genesis mints a fresh one, so it is read
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* [FEATURE][web] Exported `isConsumableNow(record, accountIdHex?)`, the rule `notes.listAvailable` and `transactions.consumeAll` apply, for code that reads the low-level client directly. ([#170](https://github.com/0xMiden/web-sdk/pull/170))

### Changes
* [BREAKING][web] `AccountFile` and `NoteFile` bytes are the protocol 0.17.0-rc.6 protobuf file format. `serialize()` and `deserialize()` keep the same shape, but bytes written by 0.17.0-rc.1 do not decode, and the other way around. The types moved onto `miden-objects` in [rust-sdk#2594](https://github.com/0xMiden/rust-sdk/pull/2594), which dropped the old `Serializable` codec. ([#414](https://github.com/0xMiden/web-sdk/pull/414))
* [BREAKING][behavior][web] `notes.listAvailable({ account })` and `transactions.consumeAll({ account })` no longer return or consume block-locked notes. Both keep only notes the client's note screener reports as consumable by `account` at the last synced block, so `consumeAll` no longer fails a whole transaction on one time-locked note, and its `consumed`/`remaining` counts follow. Use `notes.listConsumable()` to also see block-locked notes. ([#170](https://github.com/0xMiden/web-sdk/pull/170))
* [BREAKING][behavior][react] `useNotes().consumableNotes` (and `consumableNoteSummaries`), `useWaitForNotes().waitForConsumableNotes` and `useSessionAccount`'s funding poll now apply the same rule: block-locked notes are not reported as consumable, are not waited on as if they were, and are no longer put into a consume transaction that the whole account's funding step would fail on. ([#170](https://github.com/0xMiden/web-sdk/pull/170))
## 0.17.0-rc.1 (2026-09-21)
Expand Down
7 changes: 3 additions & 4 deletions crates/web-client/src/import.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use js_export_macro::js_export;
use miden_client::account::{AccountFile as NativeAccountFile, AccountId as NativeAccountId};
use miden_client::keystore::Keystore;
use miden_client::notes::NoteFile as NativeNoteFile;
use miden_client::note::NoteFile as NativeNoteFile;
#[cfg(feature = "browser")]
use wasm_bindgen::prelude::*;

Expand All @@ -23,9 +23,8 @@ impl WebClient {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
let account_data: NativeAccountFile = account_file.into();
let account_id = account_data.account.id().to_string();

let NativeAccountFile { account, auth_secret_keys } = account_data;
let account_id = account_data.account().id().to_string();
let (account, auth_secret_keys) = account_data.into_parts();

client
.add_account(&account.clone(), false)
Expand Down
23 changes: 14 additions & 9 deletions crates/web-client/src/models/account_file.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use js_export_macro::js_export;
use miden_client::account::AccountFile as NativeAccountFile;

use crate::js_error_with_context;
use crate::models::account::Account;
use crate::models::account_id::AccountId;
use crate::platform::{JsBytes, JsErr};
use crate::utils::{deserialize_from_bytes, serialize_to_bytes};
use crate::platform::{JsBytes, JsErr, bytes_to_js, js_to_bytes};

#[derive(Debug, Clone)]
#[js_export]
Expand All @@ -15,28 +15,33 @@ impl AccountFile {
/// Returns the account ID.
#[js_export(js_name = "accountId")]
pub fn account_id(&self) -> AccountId {
self.0.account.id().into()
self.0.account().id().into()
}

/// Returns the account data.
pub fn account(&self) -> Account {
self.0.account.clone().into()
self.0.account().clone().into()
}

/// Returns the number of auth secret keys included.
#[js_export(js_name = "authSecretKeyCount")]
pub fn auth_secret_key_count(&self) -> usize {
self.0.auth_secret_keys.len()
self.0.auth_secret_keys().len()
}

/// Serializes the `AccountFile` into a byte array
/// Encodes this file as protobuf account-file bytes.
///
/// Bytes written by web-sdk 0.17.0-rc.1 used the old `Serializable` codec and do not decode.
pub fn serialize(&self) -> JsBytes {
serialize_to_bytes(&self.0)
bytes_to_js(&self.0.to_bytes())
}

/// Deserializes a byte array into an `AccountFile`
/// Decodes protobuf account-file bytes.
///
/// Rejects bytes produced by web-sdk 0.17.0-rc.1.
pub fn deserialize(bytes: JsBytes) -> Result<AccountFile, JsErr> {
let native_account_file: NativeAccountFile = deserialize_from_bytes(&bytes)?;
let native_account_file = NativeAccountFile::try_from_bytes(&js_to_bytes(&bytes))
.map_err(|err| js_error_with_context(err, "account file deserialization failed"))?;
Ok(Self(native_account_file))
}
}
Expand Down
18 changes: 9 additions & 9 deletions crates/web-client/src/models/note_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ use js_export_macro::js_export;
use miden_client::block::BlockNumber as NativeBlockNumber;
use miden_client::note::{
NoteDetails as NativeNoteDetails,
NoteFile as NativeNoteFile,
NoteId as NativeNoteId,
NoteSyncHint as NativeNoteSyncHint,
NoteTag as NativeNoteTag,
};
use miden_client::notes::NoteFile as NativeNoteFile;
use miden_client::{Deserializable, Serializable};
#[cfg(feature = "nodejs")]
use napi_derive::napi;
#[cfg(feature = "browser")]
Expand Down Expand Up @@ -118,19 +117,20 @@ impl NoteFile {
}
}

/// Turn a notefile into its byte representation.
/// Encodes this file as protobuf note-file bytes.
///
/// Bytes written by web-sdk 0.17.0-rc.1 used the old `Serializable` codec and do not decode.
#[js_export(js_name = serialize)]
pub fn serialize(&self) -> Vec<u8> {
let mut buffer = vec![];
self.inner.write_into(&mut buffer);
buffer
self.inner.to_bytes()
}

/// Given a valid byte representation of a `NoteFile`,
/// return it as a struct.
/// Decodes protobuf note-file bytes.
///
/// Rejects bytes produced by web-sdk 0.17.0-rc.1.
#[js_export(js_name = deserialize)]
pub fn deserialize(bytes: &[u8]) -> Result<Self, JsErr> {
let deserialized = NativeNoteFile::read_from_bytes(bytes)
let deserialized = NativeNoteFile::try_from_bytes(bytes)
.map_err(|err| js_error_with_context(err, "notefile deserialization failed"))?;
Ok(Self { inner: deserialized })
}
Expand Down
38 changes: 38 additions & 0 deletions scripts/patch-linked-test-node.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# The rust-sdk start script checked out for a linked client PR can predate the
# node it installs. Node 0.17.0-rc.2 will not start a sequencer until
# fee-collector.mac exists in the node data directory and that account has been
# deployed. Insert those two commands when the script does not already have them.
set -euo pipefail

target="${1:?path to start-test-node.sh}"

if grep -q 'fee-collector create' "$target"; then
exit 0
fi

python3 - "$target" <<'PY'
import pathlib
import sys

path = pathlib.Path(sys.argv[1])
text = path.read_text()
needle = (
"# Let the validator bind before the sequencer starts producing blocks against it.\n"
"sleep 2\n"
"start sequencer"
)
insert = (
"# Let the validator bind before the sequencer starts producing blocks against it.\n"
"sleep 2\n"
"if [ ! -f \"$DATA/node/fee-collector.mac\" ]; then\n"
" \"$BIN/miden-node\" fee-collector create --data-directory \"$DATA/node\"\n"
"fi\n"
"\"$BIN/miden-node\" fee-collector deploy "
"--data-directory \"$DATA/node\" --validator.url \"http://$VALIDATOR\"\n"
"start sequencer"
)
if needle not in text:
raise SystemExit(f"could not find the sequencer startup in {path}")
path.write_text(text.replace(needle, insert, 1))
PY
Loading