Summary
The PXE's private-event store internally tracks txIndexInBlock and eventIndexInTx and uses them to sort within a single getPrivateEvents query, but those two fields are stripped before the result reaches Wallet.getPrivateEvents callers. SDKs that merge events across multiple (contractAddress, eventSelector) queries — wallet-history features, indexers, portfolio tools — can't reconstruct the protocol-level emission ordering for events that share a (blockNumber, txHash) but came from different queries. The data exists; it's just discarded on the way out.
Current state (verified at master HEAD, 2026-05-04)
yarn-project/pxe/src/storage/private_event_store/private_event_store.ts stores both fields:
type PrivateEventMetadata = InTx & {
contractAddress: AztecAddress;
scope: AztecAddress;
/** The index of the tx within the block */
txIndexInBlock: number;
/** The index of the event within the tx (based on nullifier position) */
eventIndexInTx: number;
};
And uses them to sort the per-query result:
events.sort((a, b) => {
if (a.l2BlockNumber !== b.l2BlockNumber) return a.l2BlockNumber - b.l2BlockNumber;
if (a.txIndexInBlock !== b.txIndexInBlock) return a.txIndexInBlock - b.txIndexInBlock;
return a.eventIndexInTx - b.eventIndexInTx;
});
But PackedPrivateEvent (yarn-project/pxe/src/pxe.ts) drops them:
export type PackedPrivateEvent = InTx & {
packedEvent: Fr[];
eventSelector: EventSelector;
};
And Wallet.getPrivateEvents (yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts) trims further:
return {
event: decodeFromAbi([eventDef.abiType], pxeEvent.packedEvent) as T,
metadata: {
l2BlockNumber: pxeEvent.l2BlockNumber,
l2BlockHash: pxeEvent.l2BlockHash,
txHash: pxeEvent.txHash,
},
};
Why this is a problem
A privacy-preserving DEX's wallet-history feature renders the user's swap and liquidity history merged across:
- pair-direct private events (one query per
(pair, eventSelector))
- router-mediated private events (one query per
(router, eventSelector))
For users who interact with the SDK directly, each tx emits at most one event per (contractAddress, eventSelector) bucket, so a (blockNumber, txHash) sort is sufficient. But for users who integrate via a batch / wrapper contract (a common pattern for gas-aggregating multi-hop or multi-trade UX), one atomic tx can emit several events of different selectors from one contract, OR the same selector from multiple contracts. Without eventIndexInTx, those events can only be ordered lexically by selector, which doesn't match emission order. Cost-basis tools (FIFO/LIFO accounting), running-balance reconstructions, and any "chronological feed" UX get non-deterministic answers.
Proposed change
Add the two indices to PackedPrivateEvent:
export type PackedPrivateEvent = InTx & {
packedEvent: Fr[];
eventSelector: EventSelector;
txIndexInBlock: number;
eventIndexInTx: number;
};
And surface them through Wallet.getPrivateEvents:
metadata: {
l2BlockNumber: pxeEvent.l2BlockNumber,
l2BlockHash: pxeEvent.l2BlockHash,
txHash: pxeEvent.txHash,
txIndexInBlock: pxeEvent.txIndexInBlock,
eventIndexInTx: pxeEvent.eventIndexInTx,
},
Summary
The PXE's private-event store internally tracks
txIndexInBlockandeventIndexInTxand uses them to sort within a singlegetPrivateEventsquery, but those two fields are stripped before the result reachesWallet.getPrivateEventscallers. SDKs that merge events across multiple(contractAddress, eventSelector)queries — wallet-history features, indexers, portfolio tools — can't reconstruct the protocol-level emission ordering for events that share a(blockNumber, txHash)but came from different queries. The data exists; it's just discarded on the way out.Current state (verified at master HEAD, 2026-05-04)
yarn-project/pxe/src/storage/private_event_store/private_event_store.tsstores both fields:And uses them to sort the per-query result:
But
PackedPrivateEvent(yarn-project/pxe/src/pxe.ts) drops them:And
Wallet.getPrivateEvents(yarn-project/wallet-sdk/src/base-wallet/base_wallet.ts) trims further:Why this is a problem
A privacy-preserving DEX's wallet-history feature renders the user's swap and liquidity history merged across:
(pair, eventSelector))(router, eventSelector))For users who interact with the SDK directly, each tx emits at most one event per
(contractAddress, eventSelector)bucket, so a(blockNumber, txHash)sort is sufficient. But for users who integrate via a batch / wrapper contract (a common pattern for gas-aggregating multi-hop or multi-trade UX), one atomic tx can emit several events of different selectors from one contract, OR the same selector from multiple contracts. WithouteventIndexInTx, those events can only be ordered lexically by selector, which doesn't match emission order. Cost-basis tools (FIFO/LIFO accounting), running-balance reconstructions, and any "chronological feed" UX get non-deterministic answers.Proposed change
Add the two indices to
PackedPrivateEvent:And surface them through
Wallet.getPrivateEvents: