Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
### Enhancements

* [FEATURE][web,react] AggLayer bridge-out (B2AGG) note support. `client.transactions.bridge({ account, bridgeAccount, token, amount, destinationNetwork, destinationAddress })` bridges a fungible asset out to another network — emitting a single public B2AGG (Bridge-to-AggLayer) note that the bridge account consumes, burning the asset so it can be claimed at the destination Ethereum address on the AggLayer-assigned `destinationNetwork`. The lower-level builders are also exposed: `Note.createB2AggNote(sender, bridgeAccount, assets, destinationNetwork, destinationAddress)` and `client.newB2AggTransactionRequest(...)`. A new `EthAddress` class carries the 20-byte destination address (`EthAddress.fromHex("0x…")` / `EthAddress.fromBytes(bytes)`, with `toHex()` / `toBytes()`). The `@miden-sdk/react` `useBridge()` hook wraps the build-and-submit flow: `bridge({ from, bridgeAccount, assetId, amount, destinationNetwork, destinationAddress })`. Builds on the `miden-agglayer` re-export already present in the bundled `miden-client` — no new dependency. (closes [#173](https://github.com/0xMiden/web-sdk/issues/173))
* [FEATURE][web] Added `client.transactions.batch({ account, operations })` to `MidenClient` for atomic multi-tx batches against a single account. Operations are discriminated by `kind` (`"send" | "mint" | "consume" | "swap" | "execute" | "custom"`) and reuse the same options shape as their singular counterparts. Returns `{ blockNumber }`. Companion `submitBatch(account, requests, options?)` is the lower-level escape hatch for pre-built `TransactionRequest`s. Wraps the underlying WASM `submitNewTransactionBatch` so consumers don't have to call `.serialize()` themselves. ([web-sdk#31](https://github.com/0xMiden/web-sdk/pull/31), client [#2109](https://github.com/0xMiden/miden-client/pull/2109))
* [FEATURE][web] Added `client.transactions.batch({ operations })` to `MidenClient` for atomic multi-tx batches across one or more local accounts. Each operation specifies its executing `account`; a batch may mix operations across any combination of tracked accounts, and a later transaction may consume a note produced by an earlier one (cross-account in-batch note flow supported). Operations are discriminated by `kind` (`"send" | "mint" | "consume" | "swap" | "execute" | "custom"`) and reuse the same options shape as their singular counterparts. Returns `{ blockNumber }`. Companion `submitBatch(items, options?)` takes an array of `{ account, request }` pairs and is the lower-level escape hatch for pre-built `TransactionRequest`s. Wraps the underlying WASM `submitNewTransactionBatch(items: BatchItem[])`, where each `BatchItem` is a `(AccountId, TransactionRequest)` pair built via `new BatchItem(accountId, request)`. ([web-sdk#31](https://github.com/0xMiden/web-sdk/pull/31), client [#2109](https://github.com/0xMiden/miden-client/pull/2109), [#2177](https://github.com/0xMiden/miden-client/pull/2177))

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.

nit: Seems this also contains changes for the React SDK

Suggested change
* [FEATURE][web] Added `client.transactions.batch({ operations })` to `MidenClient` for atomic multi-tx batches across one or more local accounts. Each operation specifies its executing `account`; a batch may mix operations across any combination of tracked accounts, and a later transaction may consume a note produced by an earlier one (cross-account in-batch note flow supported). Operations are discriminated by `kind` (`"send" | "mint" | "consume" | "swap" | "execute" | "custom"`) and reuse the same options shape as their singular counterparts. Returns `{ blockNumber }`. Companion `submitBatch(items, options?)` takes an array of `{ account, request }` pairs and is the lower-level escape hatch for pre-built `TransactionRequest`s. Wraps the underlying WASM `submitNewTransactionBatch(items: BatchItem[])`, where each `BatchItem` is a `(AccountId, TransactionRequest)` pair built via `new BatchItem(accountId, request)`. ([web-sdk#31](https://github.com/0xMiden/web-sdk/pull/31), client [#2109](https://github.com/0xMiden/miden-client/pull/2109), [#2177](https://github.com/0xMiden/miden-client/pull/2177))
* [FEATURE][web, react] Added `client.transactions.batch({ operations })` to `MidenClient` for atomic multi-tx batches across one or more local accounts. Each operation specifies its executing `account`; a batch may mix operations across any combination of tracked accounts, and a later transaction may consume a note produced by an earlier one (cross-account in-batch note flow supported). Operations are discriminated by `kind` (`"send" | "mint" | "consume" | "swap" | "execute" | "custom"`) and reuse the same options shape as their singular counterparts. Returns `{ blockNumber }`. Companion `submitBatch(items, options?)` takes an array of `{ account, request }` pairs and is the lower-level escape hatch for pre-built `TransactionRequest`s. Wraps the underlying WASM `submitNewTransactionBatch(items: BatchItem[])`, where each `BatchItem` is a `(AccountId, TransactionRequest)` pair built via `new BatchItem(accountId, request)`. ([web-sdk#31](https://github.com/0xMiden/web-sdk/pull/31), client [#2109](https://github.com/0xMiden/miden-client/pull/2109), [#2177](https://github.com/0xMiden/miden-client/pull/2177))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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


## 0.15.3 (2026-06-25)

Expand Down
5 changes: 2 additions & 3 deletions crates/idxdb-store/src/account/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use alloc::collections::BTreeMap;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::string::{String, ToString};
use alloc::vec::Vec;

Expand Down Expand Up @@ -343,8 +343,7 @@ impl IdxdbStore {
}
},
AccountStorageFilter::SlotNames(names) => {
let wanted: alloc::collections::BTreeSet<&str> =
names.iter().map(StorageSlotName::as_str).collect();
let wanted: BTreeSet<&str> = names.iter().map(StorageSlotName::as_str).collect();
account_storage_idxdb
.into_iter()
.filter(|s| wanted.contains(s.slot_name.as_str()))
Expand Down
29 changes: 15 additions & 14 deletions crates/idxdb-store/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,9 @@ impl IdxdbStore {

// Simulates read-writes across batch transactions, since nothing is persisted to
// IndexedDB until the single Dexie transaction at the end.
let mut vault_overlay: BTreeMap<AssetVaultKey, Option<Asset>> = BTreeMap::new();
let mut map_roots_overlay: BTreeMap<StorageSlotName, Word> = BTreeMap::new();
let mut vault_overlay: BTreeMap<(AccountId, AssetVaultKey), Option<Asset>> =
BTreeMap::new();
let mut map_roots_overlay: BTreeMap<(AccountId, StorageSlotName), Word> = BTreeMap::new();

for update in &tx_updates {
let (payload, account_id) = self
Expand Down Expand Up @@ -377,8 +378,8 @@ impl IdxdbStore {
async fn prepare_update_for_batch(
&self,
update: &TransactionStoreUpdate,
vault_overlay: &mut BTreeMap<AssetVaultKey, Option<Asset>>,
map_roots_overlay: &mut BTreeMap<StorageSlotName, Word>,
vault_overlay: &mut BTreeMap<(AccountId, AssetVaultKey), Option<Asset>>,
map_roots_overlay: &mut BTreeMap<(AccountId, StorageSlotName), Word>,
) -> Result<(BatchUpdatePayload, AccountId), StoreError> {
let executed_tx = update.executed_transaction();
let delta = executed_tx.account_delta();
Expand Down Expand Up @@ -422,14 +423,14 @@ impl IdxdbStore {

// Seed overlays with the new full account state so subsequent non-full-state
// preparations in the same batch see post-this-tx values.
vault_overlay.clear();
vault_overlay.retain(|(acc, _), _| *acc != account_id);
for asset in account.vault().assets() {
vault_overlay.insert(asset.vault_key(), Some(asset));
vault_overlay.insert((account_id, asset.vault_key()), Some(asset));
}
map_roots_overlay.clear();
map_roots_overlay.retain(|(acc, _), _| *acc != account_id);
for slot in account.storage().slots() {
if let StorageSlotContent::Map(map) = slot.content() {
map_roots_overlay.insert(slot.name().clone(), map.root());
map_roots_overlay.insert((account_id, slot.name().clone()), map.root());
}
}

Expand Down Expand Up @@ -474,7 +475,7 @@ impl IdxdbStore {
let mut old_vault_assets: Vec<Asset> = Vec::new();
let mut vault_keys_to_fetch: Vec<String> = Vec::new();
for (vault_key, _) in delta.vault().fungible().iter() {
match vault_overlay.get(vault_key) {
match vault_overlay.get(&(account_id, *vault_key)) {
Some(Some(asset)) => old_vault_assets.push(*asset),
Some(None) => { /* key was removed earlier in the batch; treat as empty */ },
None => vault_keys_to_fetch.push(vault_key.to_string()),
Expand All @@ -488,7 +489,7 @@ impl IdxdbStore {
let mut old_map_roots: BTreeMap<StorageSlotName, Word> = BTreeMap::new();
let mut map_slot_names_to_fetch: Vec<String> = Vec::new();
for (slot_name, _) in delta.storage().maps() {
if let Some(root) = map_roots_overlay.get(slot_name) {
if let Some(root) = map_roots_overlay.get(&(account_id, slot_name.clone())) {
old_map_roots.insert(slot_name.clone(), *root);
} else {
map_slot_names_to_fetch.push(slot_name.to_string());
Expand Down Expand Up @@ -547,16 +548,16 @@ impl IdxdbStore {
smt_forest.stage_roots(account_id, final_roots);

// Propagate this tx's post-state into the overlays so subsequent preparations
// in the same batch see these updates.
// against this same account in the batch see these updates.
for asset in &updated_assets {
vault_overlay.insert(asset.vault_key(), Some(*asset));
vault_overlay.insert((account_id, asset.vault_key()), Some(*asset));
}
for vault_key in &removed_vault_keys {
vault_overlay.insert(*vault_key, None);
vault_overlay.insert((account_id, *vault_key), None);
}
for (slot_name, (new_root, slot_type)) in &updated_storage_slots {
if *slot_type == StorageSlotType::Map {
map_roots_overlay.insert(slot_name.clone(), *new_root);
map_roots_overlay.insert((account_id, slot_name.clone()), *new_root);
}
}

Expand Down
23 changes: 10 additions & 13 deletions crates/web-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,35 +454,32 @@ console.log(`Balance: ${balance}`);

### Batch Operations

Submit multiple operations against a single account as one atomic batch — every transaction in the batch lands together or none does. Each operation builds its own `TransactionRequest` internally; you don't have to assemble or serialize them yourself.
Submit multiple operations across one or more local accounts as one atomic batch — every transaction in the batch lands together or none does. Each operation builds its own `TransactionRequest` internally; you don't have to assemble or serialize them yourself.

```typescript
const { blockNumber } = await client.transactions.batch({
account: wallet,
operations: [
{ kind: "send", to: alice, token: dagToken, amount: 50n, type: "public" },
{ kind: "send", to: bob, token: dagToken, amount: 30n, type: "public" },
{ kind: "consume", notes: pendingNotes },
{ kind: "send", account: alice, to: bob, token: dagToken, amount: 50n, type: "public" },
{ kind: "send", account: alice, to: carol, token: dagToken, amount: 30n, type: "public" },
{ kind: "consume", account: bob, notes: pendingNotes },
],
waitForConfirmation: true,
});
console.log(`Batch landed in block ${blockNumber}`);
```

Operations are discriminated by `kind`: `"send"`, `"mint"`, `"consume"`, `"swap"`, `"execute"`, and `"custom"` (escape hatch for a pre-built `TransactionRequest`). The shape of each operation mirrors the singular options object (`SendOptions`, `MintOptions`, …) minus the `account` field, which is set once at the batch level.

V1 supports only same-account batches — every operation must execute against the `account` passed at the top level. Mixing accounts in one batch is not supported.
Operations are discriminated by `kind`: `"send"`, `"mint"`, `"consume"`, `"swap"`, `"execute"`, and `"custom"` (escape hatch for a pre-built `TransactionRequest`). Each operation specifies its executing `account`; a batch may mix any combination of tracked local accounts, and a later transaction may consume a note produced by an earlier one in the same batch.

For callers that already hold pre-built `TransactionRequest`s, `submitBatch` skips the high-level builders:
For callers that already hold pre-built `TransactionRequest`s, `submitBatch` skips the high-level builders. Pass an array of `{ account, request }` pairs:

```typescript
const { blockNumber } = await client.transactions.submitBatch(wallet, [
request1,
request2,
const { blockNumber } = await client.transactions.submitBatch([
{ account: alice, request: request1 },
{ account: bob, request: request2 },
]);
```

The V1 batch primitive returns only the block number — there are no per-tx ids in the result. `waitForConfirmation` polls local sync height until it reaches `blockNumber` (rather than per-tx polling like singular `send` / `consume`).
The batch primitive returns only the block number — there are no per-tx ids in the result. `waitForConfirmation` polls local sync height until it reaches `blockNumber` (rather than per-tx polling like singular `send` / `consume`).

### Partial-Swap (PSWAP) Orders

Expand Down
Loading
Loading