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
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, 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))

## 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