Skip to content

Comments

feat: adding Transfer events on the Token#269

Open
wei3erHase wants to merge 20 commits intodevfrom
feat/transfer-events
Open

feat: adding Transfer events on the Token#269
wei3erHase wants to merge 20 commits intodevfrom
feat/transfer-events

Conversation

@wei3erHase
Copy link
Member

@wei3erHase wei3erHase commented Feb 9, 2026

🤖 Linear

Closes AZT-224

Description

Add Transfer events to the Token contract

Introduces a Transfer { from, to, amount } event emitted on every balance-changing operation in the Token (and Tokenized Vault), including mints, burns, public transfers, and cross-domain (private <-> public) moves.

A sentinel PRIVATE_ADDRESS_MAGIC_VALUE is used as the from or to field whenever the counterpart of a balance change is a private note (since the actual address cannot be revealed). address(0) is used for mints (from = 0) and burns (to = 0), following the ERC-20 convention.

Invariant: for every Transfer event, the sum of all amounts where from == address(0) minus the sum of all amounts where to == address(0) must equal the current total_supply. This ensures full accountability of supply changes through public event data, even when individual balances are shielded.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Transfer event logging system to track token balance changes across public and private boundaries.
    • Transfer events are now emitted during mint, burn, and transfer operations to provide visibility into balance movements.
  • Tests

    • Enhanced token and vault tests with Transfer event assertions.
    • Added test utilities to validate Transfer event emissions across various token operations and vault interactions.

@linear
Copy link

linear bot commented Feb 9, 2026

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

Benchmark Comparison

CPU Cores RAM Arch
AMD EPYC 7763 64-Core Processor 16 63 GiB x64

Contract: token

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
🔴 burn_private 481,551 481,551 12,800 15,872 +3,072 (+24.0%) 146,878 147,583 +705 (+0.5%) 6,472 6,453 -19 (-0.3%)
🔴 burn_public 348,087 348,087 3,072 6,144 +3,072 (+100.0%) 136,102 136,810 +708 (+0.5%) 5,415 5,439 +24 (+0.4%)
initialize_transfer_commitment 426,173 426,173 11,264 11,264 512 512 6,076 6,024 -52 (-0.9%)
🔴 mint_to_private 461,103 461,103 12,288 15,360 +3,072 (+25.0%) 115,835 116,540 +705 (+0.6%) 6,361 6,341 -20 (-0.3%)
🔴 mint_to_public 348,087 348,087 3,072 6,144 +3,072 (+100.0%) 144,298 145,006 +708 (+0.5%) 5,575 5,435 -140 (-2.5%)
transfer_private_to_commitment 446,287 446,287 13,824 13,824 512 512 6,155 6,133 -22 (-0.4%)
transfer_private_to_private 444,042 444,042 22,016 22,016 512 512 6,145 6,100 -45 (-0.7%)
🔴 transfer_private_to_public 481,627 481,627 12,800 15,872 +3,072 (+24.0%) 149,749 150,457 +708 (+0.5%) 6,539 6,469 -70 (-1.1%)
🔴 transfer_private_to_public_with_commitment 510,841 510,841 23,040 26,112 +3,072 (+13.3%) 180,549 181,257 +708 (+0.4%) 6,655 6,688 +33 (+0.5%)
🔴 transfer_public_to_commitment 348,087 348,087 4,608 7,680 +3,072 (+66.7%) 132,362 133,070 +708 (+0.5%) 5,417 5,448 +31 (+0.6%)
🔴 transfer_public_to_private 457,757 457,757 12,288 15,360 +3,072 (+25.0%) 119,030 119,738 +708 (+0.6%) 6,339 6,317 -22 (-0.3%)
🔴 transfer_public_to_public 348,087 348,087 3,072 6,144 +3,072 (+100.0%) 138,790 139,501 +711 (+0.5%) 5,465 5,425 -40 (-0.7%)

@wei3erHase
Copy link
Member Author

Transfer Event Coverage

Full coverage table for all Transfer event assertions across Token and Tokenized Vault TypeScript tests.

Legend:

  • 0x0 = AztecAddress.ZERO (mint/burn sentinel)
  • PRIV = PRIVATE_ADDRESS sentinel (private balance side)
  • "Asset" = events on the underlying asset contract
  • "Vault" = events on the vault (shares) contract

Token Tests (token.test.ts)

Test Transaction Contract Expected Events Line
mint in public, prepare partial note and finalize it mint_to_public(alice, AMOUNT) Token Transfer(0x0, alice, AMOUNT) 119
transfer_public_to_commitment(alice, commitment, AMOUNT) Token Transfer(alice, PRIV, AMOUNT) 145
public transfer with authwitness mint_to_public(alice, AMOUNT) Token Transfer(0x0, alice, AMOUNT) 163
transfer_public_to_public(alice, bob, AMOUNT) Token Transfer(alice, bob, AMOUNT) 192
private transfer with authwitness mint_to_public(alice, AMOUNT) Token Transfer(0x0, alice, AMOUNT) 207
transfer_public_to_private(alice, alice, AMOUNT) Token Transfer(alice, PRIV, AMOUNT) 217
transfer_private_to_private(alice, bob, AMOUNT) Token (none — purely private) 243

Tokenized Vault Tests — No Authwits (tokenized_vault.test.ts)

Public Assets, Public Shares

Transaction Contract Expected Events Line
mint_to_public(alice, 100) Asset Transfer(0x0, alice, 100) 89
mint_to_public(bob, 100) Asset Transfer(0x0, bob, 100) 93
deposit_public_to_public(alice, alice, 9) Asset Transfer(alice, vault, 9) 104
Vault Transfer(0x0, alice, 9) 107
mint_to_public(vault, 5) (yield) Asset Transfer(0x0, vault, 5) 113
issue_public_to_public(bob, bob, 10, 15) Vault Transfer(0x0, bob, 10) 124
withdraw_public_to_public(alice, alice, maxWithdraw) Vault Transfer(alice, 0x0, 9) 144
Asset Transfer(vault, alice, maxWithdraw) 147
redeem_public_to_public(bob, bob, 10) Vault Transfer(bob, 0x0, 10) 154
Asset Transfer(vault, bob, 15) 157

Private Assets, Public Shares

Transaction Contract Expected Events Line
mint_to_private(alice, 100) Asset Transfer(0x0, PRIV, 100) 179
mint_to_private(bob, 100) Asset Transfer(0x0, PRIV, 100) 183
deposit_private_to_public(alice, alice, 9) Asset Transfer(PRIV, vault, 9) 194
Vault Transfer(0x0, alice, 9) 197
issue_private_to_public_exact(bob, bob, 10, 15) Vault Transfer(0x0, bob, 10) 212
withdraw_public_to_private(alice, alice, maxWithdraw) Vault Transfer(alice, 0x0, 9) 231
Asset Transfer(vault, PRIV, maxWithdraw) 234
redeem_public_to_private_exact(bob, bob, 10, 15) Vault Transfer(bob, 0x0, 10) 245
Asset Transfer(vault, PRIV, 15) 248

Public Assets, Private Shares

Transaction Contract Expected Events Line
deposit_public_to_private(alice, alice, 9, 9) Asset Transfer(alice, vault, 9) 278
Vault Transfer(0x0, PRIV, 9) 281
issue_public_to_private(bob, bob, 10, 15) Vault Transfer(0x0, PRIV, 10) 296
withdraw_private_to_public_exact(alice, alice, maxWithdraw, 9) Vault Transfer(PRIV, 0x0, 9) 317
Asset Transfer(vault, alice, maxWithdraw) 320
redeem_private_to_public(bob, bob, 10) Vault Transfer(PRIV, 0x0, 10) 327
Asset Transfer(vault, bob, 15) 330

Private Assets, Private Shares

Transaction Contract Expected Events Line
mint_to_private(alice, 100) Asset Transfer(0x0, PRIV, 100) 351
mint_to_private(bob, 100) Asset Transfer(0x0, PRIV, 100) 355
deposit_private_to_private(alice, alice, 9, 9) Asset Transfer(PRIV, vault, 9) 366
Vault Transfer(0x0, PRIV, 9) 369
issue_private_to_private_exact(bob, bob, 10, 15) Vault Transfer(0x0, PRIV, 10) 384
withdraw_private_to_private(alice, alice, maxWithdraw, 9) Vault Transfer(PRIV, 0x0, 9) 404
Asset Transfer(vault, PRIV, maxWithdraw) 407
redeem_private_to_private_exact(bob, bob, 10, 15) Vault Transfer(PRIV, 0x0, 10) 417

Exact Methods, Mixed Assets, Private Shares

Transaction Contract Expected Events Line
deposit_private_to_private_exact(alice, alice, 9, 9) Asset Transfer(PRIV, vault, 9) 447
Vault Transfer(0x0, PRIV, 9) 451
deposit_public_to_private_exact(bob, bob, 15, 10) Asset Transfer(bob, vault, 15) 465
withdraw_private_to_private_exact(alice, alice, maxWithdraw, 9) Vault Transfer(PRIV, 0x0, 9) 488
Asset Transfer(vault, PRIV, maxWithdraw) 491
withdraw_private_to_public_exact(bob, bob, 15, 10) Vault Transfer(PRIV, 0x0, 10) 501
Asset Transfer(vault, bob, 15) 504

Tokenized Vault Tests — With Authwits (tokenized_vault.test.ts)

The authwit test suite mirrors the no-authwit suite above but uses Carl as the transaction submitter with authorization witnesses. The same Transfer event patterns apply — authwits do not change which events are emitted, only who is authorized to submit the transaction.

Public Assets, Public Shares (authwit)

Transaction Contract Expected Events Line
deposit_public_to_public (carl submits) Asset Transfer(alice, vault, 9) 535
Vault Transfer(0x0, alice, 9) 538
issue_public_to_public (carl submits) Vault Transfer(0x0, bob, 10) 549
withdraw_public_to_public (carl submits) Vault Transfer(alice, 0x0, 9) 569
Asset Transfer(vault, alice, maxWithdraw) 572
redeem_public_to_public (carl submits) Vault Transfer(bob, 0x0, 10) 580
Asset Transfer(vault, bob, 15) 583

Private Assets, Public Shares (authwit)

Transaction Contract Expected Events Line
deposit_private_to_public (carl submits) Asset Transfer(PRIV, vault, 9) 617
Vault Transfer(0x0, alice, 9) 620
issue_private_to_public_exact (carl submits) Vault Transfer(0x0, bob, 10) 636
withdraw_public_to_private Vault Transfer(alice, 0x0, 9) 656
Asset Transfer(vault, PRIV, maxWithdraw) 659
redeem_public_to_private_exact Vault Transfer(bob, 0x0, 10) 668
Asset Transfer(vault, PRIV, 15) 671

Public Assets, Private Shares (authwit)

Transaction Contract Expected Events Line
deposit_public_to_private (carl submits) Asset Transfer(alice, vault, 9) 705
Vault Transfer(0x0, PRIV, 9) 708
issue_public_to_private (carl submits) Vault Transfer(0x0, PRIV, 10) 724
withdraw_private_to_public_exact (carl submits) Vault Transfer(PRIV, 0x0, 9) 753
Asset Transfer(vault, alice, maxWithdraw) 756
redeem_private_to_public (carl submits) Vault Transfer(PRIV, 0x0, 10) 764
Asset Transfer(vault, bob, 15) 767

Private Assets, Private Shares (authwit)

Transaction Contract Expected Events Line
deposit_private_to_private (carl submits) Asset Transfer(PRIV, vault, 9) 801
Vault Transfer(0x0, PRIV, 9) 804
issue_private_to_private_exact (carl submits) Vault Transfer(0x0, PRIV, 10) 820
withdraw_private_to_private (carl submits) Vault Transfer(PRIV, 0x0, 9) 843
Asset Transfer(vault, PRIV, maxWithdraw) 846
redeem_private_to_private_exact (carl submits) Vault Transfer(PRIV, 0x0, 10) 854

Exact Methods, Mixed Assets, Private Shares (authwit)

Transaction Contract Expected Events Line
deposit_private_to_private_exact (carl submits) Asset Transfer(PRIV, vault, 9) 888
Vault Transfer(0x0, PRIV, 9) 891
deposit_public_to_private_exact (carl submits) Asset Transfer(bob, vault, 15) 907
withdraw_private_to_private_exact (carl submits) Vault Transfer(PRIV, 0x0, 9) 930
Asset Transfer(vault, PRIV, maxWithdraw) 933
withdraw_private_to_public_exact (carl submits) Vault Transfer(PRIV, 0x0, 10) 943
Asset Transfer(vault, bob, 15) 946

Coverage Summary

Category Flows Covered Event Assertions
Token (mint, public transfer, commitment, public-to-private, private-to-private) 7 transactions 7 assertions (incl. 1 empty)
Vault — No Authwits (5 asset/share combos x deposit+issue+withdraw+redeem) ~40 transactions ~40 assertions
Vault — With Authwits (5 asset/share combos x deposit+issue+withdraw+redeem) ~40 transactions ~40 assertions
Total ~87 transactions ~87 event assertions

Event Patterns Verified

  • Mint to public: Transfer(0x0, recipient, amount)
  • Mint to private: Transfer(0x0, PRIV, amount)
  • Public-to-public transfer: Transfer(from, to, amount)
  • Public-to-private transfer: Transfer(from, PRIV, amount)
  • Private-to-public transfer: Transfer(PRIV, to, amount)
  • Private-to-private transfer: (no public events)
  • Burn from public: Transfer(from, 0x0, amount)
  • Burn from private: Transfer(PRIV, 0x0, amount)
  • Vault deposit (dual-contract): Asset Transfer(from, vault, assets) + Vault Transfer(0x0, to, shares)
  • Vault withdraw (dual-contract): Vault Transfer(from, 0x0, shares) + Asset Transfer(vault, to, assets)
  • Vault issue (dual-contract): Asset Transfer(from, vault, max_assets) + Vault Transfer(0x0, to, shares)
  • Vault redeem (dual-contract): Vault Transfer(from, 0x0, shares) + Asset Transfer(vault, to, assets)

@wei3erHase
Copy link
Member Author

Private vs Public Event Testing

Private Events

  • Defined with EventInterface and Serialize traits, emitted via emit_event_in_private().
  • Can be asserted in Noir unit tests using the TestEnvironment utility.
  • Can also be retrieved in TypeScript via wallet.getPrivateEvents().

Public Events

  • Defined with the #[event] attribute, emitted via self.emit().
  • Cannot be asserted in Noir unit tests — there is no equivalent of getPrivateEvents() for public logs in the Noir test environment.
  • Can be retrieved in TypeScript via the AztecNode.getPublicLogs(filter) API, which returns ExtendedPublicLog[] objects.

This asymmetry means that public event assertions must be done in TypeScript integration tests, not in Noir.

Public Log Field Layout

When a public event is emitted and retrieved via node.getPublicLogs(), the returned PublicLog object has:

  • contractAddress: AztecAddress — the emitting contract (stored separately).
  • fields: Fr[] — the serialized event data.

The fields array layout for a Transfer event is:

Index Content Description
fields[0] from Source address
fields[1] to Destination address
fields[2] amount Transfer amount (u128 as Field)
fields[3] Event selector 0x70a1894e (constant for Transfer)

Key finding: The event selector is appended at the end of the fields array, not at the beginning. The PublicLog class has a getEmittedFieldsWithoutTag() method that strips fields[0], but this removes the from address — not the event selector. For decoding Transfer events, read directly from fields[0..2].

Sentinel Addresses

The contract uses two sentinel addresses in Transfer events to represent non-standard balance sources/destinations:

Sentinel Value Usage
AztecAddress.ZERO 0x0000...0000 Mint origin (from) / Burn destination (to)
PRIVATE_ADDRESS sha224("PRIVATE_ADDRESS") = 0x1ea7e...3264 Represents the "private side" of a balance change — used when tokens move between public and private balances

Why PRIVATE_ADDRESS?

When a token is transferred from a public balance to a private balance (or vice versa), the actual private recipient/sender address cannot be revealed in a public event. Instead, the contract uses this sentinel value to indicate that the counterpart is a private balance operation.

Examples:

  • Transfer(alice, PRIVATE_ADDRESS, amount) — Alice's public balance decreased, tokens moved to private.
  • Transfer(PRIVATE_ADDRESS, vault, amount) — Tokens moved from a private balance into the vault's public balance.
  • Transfer(0x0, PRIVATE_ADDRESS, amount) — Mint to private (total supply increased, tokens went to private balance).

Utility Functions

The test utilities in src/ts/test/utils.ts provide:

  • getTransferEvents(txHash, contractAddress) — Queries node.getPublicLogs() filtered by transaction and contract, returns decoded TransferEvent[].
  • expectTransferEvents(txHash, contractAddress, expected) — Asserts that the emitted events match the expected list exactly (count, order, and content).

Caveats

  1. No Noir-level public event testing — Public events can only be asserted via TypeScript integration tests using the AztecNode API.
  2. Field layout is not obvious — The event selector sits at the end of the fields array. The getEmittedFieldsWithoutTag() method strips the first field (the actual from address), which is misleading for event decoding. Always read from fields[0] directly.
  3. Fr type mismatch across packages — When constructing AztecAddress values in TypeScript tests, use AztecAddress.fromBigInt() instead of AztecAddress.fromField(new Fr(...)) to avoid BaseField ctor errors caused by different Fr class instances across hoisted dependencies.
  4. Private-to-private transfers emit no public events — Operations like transfer_private_to_private are purely private and produce zero public logs. This is expected and must be explicitly tested with an empty expected array.

// Sentinel address used in Transfer events to represent the private side of a balance change.
// sha224sum 'PRIVATE_ADDRESS'
global PRIVATE_ADDRESS_MAGIC_VALUE: AztecAddress =
AztecAddress::from_field(0x1ea7e01501975545617c2e694d931cb576b691a4a867fed81ebd3264);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: We should be able compute these in comptime

Copy link
Member Author

Choose a reason for hiding this comment

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

i looked at packages on how they declare *_MAGIC_VALUES and they just comment it on top and declare the hex or number, are we ok with letting it this way?

Choose a reason for hiding this comment

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

That's what I did

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

Benchmark Comparison

CPU Cores RAM Arch
AMD EPYC 7763 64-Core Processor 16 63 GiB x64

Contract: token

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
🔴 burn_private 481,551 481,551 12,800 15,872 +3,072 (+24.0%) 146,878 147,583 +705 (+0.5%) 6,566 6,506 -60 (-0.9%)
🔴 burn_public 348,087 348,087 3,072 6,144 +3,072 (+100.0%) 136,102 136,810 +708 (+0.5%) 5,504 5,438 -66 (-1.2%)
initialize_transfer_commitment 426,173 426,173 11,264 11,264 512 512 6,129 6,073 -56 (-0.9%)
🔴 mint_to_private 461,103 461,103 12,288 15,360 +3,072 (+25.0%) 115,835 116,540 +705 (+0.6%) 6,430 6,394 -36 (-0.6%)
🔴 mint_to_public 348,087 348,087 3,072 6,144 +3,072 (+100.0%) 144,298 145,006 +708 (+0.5%) 5,457 5,463 +6 (+0.1%)
transfer_private_to_commitment 446,287 446,287 13,824 13,824 512 512 6,228 6,183 -45 (-0.7%)
transfer_private_to_private 444,042 444,042 22,016 22,016 512 512 6,192 6,179 -13 (-0.2%)
🔴 transfer_private_to_public 481,627 481,627 12,800 15,872 +3,072 (+24.0%) 149,749 150,457 +708 (+0.5%) 6,723 6,470 -253 (-3.8%)
🔴 transfer_private_to_public_with_commitment 510,841 510,841 23,040 26,112 +3,072 (+13.3%) 180,549 181,257 +708 (+0.4%) 6,762 6,687 -75 (-1.1%)
🔴 transfer_public_to_commitment 348,087 348,087 4,608 7,680 +3,072 (+66.7%) 132,362 133,070 +708 (+0.5%) 5,472 5,421 -51 (-0.9%)
🔴 transfer_public_to_private 457,757 457,757 12,288 15,360 +3,072 (+25.0%) 119,030 119,738 +708 (+0.6%) 6,396 6,368 -28 (-0.4%)
🔴 transfer_public_to_public 348,087 348,087 3,072 6,144 +3,072 (+100.0%) 138,790 139,501 +711 (+0.5%) 5,477 5,466 -11 (-0.2%)

@github-actions
Copy link

Benchmark Comparison

CPU Cores RAM Arch
AMD EPYC 7763 64-Core Processor 16 63 GiB x64

Contract: token

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
🔴 burn_private 481,551 481,551 12,800 15,872 +3,072 (+24.0%) 146,878 147,583 +705 (+0.5%) 6,566 6,572 +6 (+0.1%)
🔴 burn_public 348,087 348,087 3,072 6,144 +3,072 (+100.0%) 136,102 136,810 +708 (+0.5%) 5,504 5,459 -45 (-0.8%)
initialize_transfer_commitment 426,173 426,173 11,264 11,264 512 512 6,129 6,078 -51 (-0.8%)
🔴 mint_to_private 461,103 461,103 12,288 15,360 +3,072 (+25.0%) 115,835 116,540 +705 (+0.6%) 6,430 6,405 -25 (-0.4%)
🔴 mint_to_public 348,087 348,087 3,072 6,144 +3,072 (+100.0%) 144,298 145,006 +708 (+0.5%) 5,457 5,456 -1 (-0.0%)
transfer_private_to_commitment 446,287 446,287 13,824 13,824 512 512 6,228 6,133 -95 (-1.5%)
transfer_private_to_private 444,042 444,042 22,016 22,016 512 512 6,192 6,200 +8 (+0.1%)
🔴 transfer_private_to_public 481,627 481,627 12,800 15,872 +3,072 (+24.0%) 149,749 150,457 +708 (+0.5%) 6,723 6,558 -165 (-2.5%)
🔴 transfer_private_to_public_with_commitment 510,841 510,841 23,040 26,112 +3,072 (+13.3%) 180,549 181,257 +708 (+0.4%) 6,762 6,706 -56 (-0.8%)
🔴 transfer_public_to_commitment 348,087 348,087 4,608 7,680 +3,072 (+66.7%) 132,362 133,070 +708 (+0.5%) 5,472 5,477 +5 (+0.1%)
🔴 transfer_public_to_private 457,757 457,757 12,288 15,360 +3,072 (+25.0%) 119,030 119,738 +708 (+0.6%) 6,396 6,361 -35 (-0.5%)
🔴 transfer_public_to_public 348,087 348,087 3,072 6,144 +3,072 (+100.0%) 138,790 139,501 +711 (+0.5%) 5,477 5,483 +6 (+0.1%)

wei3erHase and others added 4 commits February 13, 2026 16:51
# 🤖 Linear

Closes AZT-XXX

## Description
# 🤖 Linear

Closes AZT-XXX

## Description 

still WIP

---------

Co-authored-by: Weißer Hase <84595958+wei3erHase@users.noreply.github.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Warning

Rate limit exceeded

@wei3erHase has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 14 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Adds Transfer event logging to token contract for indexer support. Introduces PRIVATE_ADDRESS_MAGIC_VALUE sentinel constant to represent private balance operations, emits Transfer events at mint, burn, and transfer points, and extends test suite with event assertion utilities to verify transfers across public/private boundaries.

Changes

Cohort / File(s) Summary
Smart Contract
src/token_contract/src/main.nr
Adds PRIVATE_ADDRESS_MAGIC_VALUE constant and Transfer event struct; emits Transfer events at token mint, burn, and transfer operations to track balance changes between public/private boundaries and zero/private addresses.
Test Utilities
src/ts/test/utils.ts
Introduces PRIVATE_ADDRESS constant, TransferEvent type, getTransferEvents() and expectTransferEvents() functions to decode and assert Transfer events from node logs. Note: utilities are duplicated across two insertion points.
Token Tests
src/ts/test/token.test.ts
Adds Transfer event assertions across mint, transfer, and settlement operations; verifies events from zero address for mints and to/from PRIVATE_ADDRESS for boundary transfers.
Vault Tests
src/ts/test/tokenized_vault.test.ts
Refactors callVaultWithPublicAuthWit and callVaultWithPrivateAuthWit to return TxHash; adds extensive Transfer event assertions across vault mint, deposit, issue, redeem, and withdraw operations across public/private asset flows.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • xorsal
  • 0xShaito
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately captures the main feature addition: implementing Transfer events on the Token contract.
Description check ✅ Passed Description follows template structure with Linear issue reference and provides comprehensive context on Transfer event implementation, sentinel values, and supply accountability invariant.
Linked Issues check ✅ Passed PR fully addresses AZT-224 requirements: Transfer events enable indexer discovery of token movements across public/private boundaries while preserving privacy via sentinel representation.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing Transfer events for indexing, adding event emissions, test utilities, and comprehensive event assertion coverage across Token and Vault contracts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/transfer-events

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

Benchmark Comparison

CPU Cores RAM Arch
AMD EPYC 7763 64-Core Processor 16 63 GiB x64

Contract: escrow

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
(partial) withdraw 551,361 551,361 22,016 22,016 512 512 6,377 7,610 +1,233 (+19.3%)
withdraw 551,361 551,361 11,776 11,776 512 512 6,366 7,571 +1,205 (+18.9%)
withdraw_nft 521,153 521,153 11,776 11,776 512 512 6,193 7,375 +1,182 (+19.1%)

Contract: logic

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
get_escrow 413,263 413,263 1,024 1,024 512 512 5,386 6,427 +1,041 (+19.3%)
secret_keys_to_public_keys 408,340 408,340 1,024 1,024 512 512 5,341 6,349 +1,008 (+18.9%)
share_escrow 405,898 405,898 11,264 11,264 512 512 5,320 6,410 +1,090 (+20.5%)

Contract: nft

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
burn_private 460,583 460,583 2,560 2,560 132,265 132,265 5,698 6,875 +1,177 (+20.7%)
burn_public 346,521 346,521 3,072 3,072 139,708 139,708 4,854 5,843 +989 (+20.4%)
mint_to_private 458,872 458,872 12,288 12,288 120,770 120,770 5,806 6,864 +1,058 (+18.2%)
mint_to_public 346,521 346,521 3,072 3,072 143,932 143,932 4,915 5,878 +963 (+19.6%)
transfer_private_to_private 411,588 411,588 11,776 11,776 512 512 5,365 6,435 +1,070 (+19.9%)
transfer_private_to_public 460,586 460,586 2,560 2,560 127,336 127,336 5,769 6,863 +1,094 (+19.0%)
transfer_public_to_private 455,437 455,437 12,288 12,288 121,079 121,079 5,669 6,755 +1,086 (+19.2%)
transfer_public_to_public 346,521 346,521 2,048 2,048 102,023 102,023 4,876 5,883 +1,007 (+20.7%)

Contract: token

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
🔴 burn_private 479,299 479,299 12,800 15,872 +3,072 (+24.0%) 146,878 147,583 +705 (+0.5%) 5,850 6,919 +1,069 (+18.3%)
🔴 burn_public 346,521 346,521 3,072 6,144 +3,072 (+100.0%) 136,102 136,810 +708 (+0.5%) 4,897 5,837 +940 (+19.2%)
initialize_transfer_commitment 424,338 424,338 11,264 11,264 512 512 5,425 6,534 +1,109 (+20.4%)
🔴 mint_to_private 458,815 458,815 12,288 15,360 +3,072 (+25.0%) 115,835 116,540 +705 (+0.6%) 5,732 6,783 +1,051 (+18.3%)
🔴 mint_to_public 346,521 346,521 3,072 6,144 +3,072 (+100.0%) 144,298 145,006 +708 (+0.5%) 4,879 5,871 +992 (+20.3%)
transfer_private_to_commitment 444,035 444,035 13,824 13,824 512 512 5,547 6,629 +1,082 (+19.5%)
transfer_private_to_private 441,790 441,790 22,016 22,016 512 512 5,530 6,664 +1,134 (+20.5%)
🔴 transfer_private_to_public 479,375 479,375 12,800 15,872 +3,072 (+24.0%) 149,749 150,457 +708 (+0.5%) 5,890 6,986 +1,096 (+18.6%)
🔴 transfer_private_to_public_with_commitment 509,042 509,042 23,040 26,112 +3,072 (+13.3%) 180,549 181,257 +708 (+0.4%) 6,045 7,113 +1,068 (+17.7%)
🔴 transfer_public_to_commitment 346,521 346,521 4,608 7,680 +3,072 (+66.7%) 132,362 133,070 +708 (+0.5%) 4,920 5,830 +910 (+18.5%)
🔴 transfer_public_to_private 455,469 455,469 12,288 15,360 +3,072 (+25.0%) 119,030 119,738 +708 (+0.6%) 5,717 6,848 +1,131 (+19.8%)
🔴 transfer_public_to_public 346,521 346,521 3,072 6,144 +3,072 (+100.0%) 138,790 139,501 +711 (+0.5%) 4,958 5,876 +918 (+18.5%)

Contract: tokenized_vault

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
🔴 deposit_private_to_private 712,559 712,559 14,336 20,480 +6,144 (+42.9%) 295,387 296,596 +1,209 (+0.4%) 7,772 9,252 +1,480 (+19.0%)
🔴 deposit_private_to_private_exact 742,301 742,301 24,576 30,720 +6,144 (+25.0%) 326,556 328,065 +1,509 (+0.5%) 7,945 9,466 +1,521 (+19.1%)
🔴 deposit_private_to_public 712,296 712,296 5,120 11,264 +6,144 (+120.0%) 314,505 316,017 +1,512 (+0.5%) 7,754 9,140 +1,386 (+17.9%)
🔴 deposit_public_to_private 455,479 455,479 15,360 21,504 +6,144 (+40.0%) 362,177 363,653 +1,476 (+0.4%) 5,833 6,981 +1,148 (+19.7%)
🔴 deposit_public_to_private_exact 485,251 485,251 25,600 31,744 +6,144 (+24.0%) 393,508 395,020 +1,512 (+0.4%) 5,946 7,076 +1,130 (+19.0%)
🔴 deposit_public_to_public 346,521 346,521 6,144 12,288 +6,144 (+100.0%) 379,762 381,277 +1,515 (+0.4%) 4,990 5,909 +919 (+18.4%)
🔴 issue_private_to_private_exact 899,814 899,814 24,576 30,720 +6,144 (+25.0%) 326,832 328,305 +1,473 (+0.5%) 9,084 10,711 +1,627 (+17.9%)
🔴 issue_private_to_public_exact 899,574 899,574 15,360 21,504 +6,144 (+40.0%) 346,277 347,753 +1,476 (+0.4%) 9,196 10,874 +1,678 (+18.2%)
🔴 issue_public_to_private 455,470 455,470 15,360 21,504 +6,144 (+40.0%) 363,098 364,574 +1,476 (+0.4%) 5,805 6,921 +1,116 (+19.2%)
🔴 issue_public_to_public 346,521 346,521 6,144 12,288 +6,144 (+100.0%) 380,167 381,646 +1,479 (+0.4%) 4,949 5,975 +1,026 (+20.7%)
🔴 redeem_private_to_private_exact 776,472 776,472 24,064 30,208 +6,144 (+25.5%) 296,101 297,574 +1,473 (+0.5%) 8,026 9,530 +1,504 (+18.7%)
🔴 redeem_private_to_public 479,375 479,375 4,608 10,752 +6,144 (+133.3%) 285,086 286,562 +1,476 (+0.5%) 5,810 6,883 +1,073 (+18.5%)
🔴 redeem_public_to_private_exact 752,144 752,144 24,576 30,720 +6,144 (+25.0%) 304,183 305,659 +1,476 (+0.5%) 7,915 9,400 +1,485 (+18.8%)
🔴 redeem_public_to_public 346,521 346,521 5,120 11,264 +6,144 (+120.0%) 291,800 293,279 +1,479 (+0.5%) 4,893 5,802 +909 (+18.6%)
🔴 withdraw_private_to_private 595,855 595,855 13,824 19,968 +6,144 (+44.4%) 264,251 265,460 +1,209 (+0.5%) 6,639 7,895 +1,256 (+18.9%)
🔴 withdraw_private_to_private_exact 615,681 615,681 24,064 30,208 +6,144 (+25.5%) 295,663 297,172 +1,509 (+0.5%) 6,794 8,044 +1,250 (+18.4%)
🔴 withdraw_private_to_public_exact 499,220 499,220 14,848 20,992 +6,144 (+41.4%) 316,546 318,058 +1,512 (+0.5%) 5,951 7,013 +1,062 (+17.8%)
🔴 withdraw_public_to_private 571,676 571,676 14,336 20,480 +6,144 (+42.9%) 271,925 273,437 +1,512 (+0.6%) 6,561 7,802 +1,241 (+18.9%)
🔴 withdraw_public_to_public 346,521 346,521 5,120 11,264 +6,144 (+120.0%) 291,683 293,198 +1,515 (+0.5%) 4,841 5,771 +930 (+19.2%)

Base automatically changed from chore/v4 to dev February 19, 2026 15:59
@github-actions
Copy link

Benchmark Comparison

CPU Cores RAM Arch
AMD EPYC 7763 64-Core Processor 16 63 GiB x64

Contract: escrow

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
(partial) withdraw 551,361 551,361 22,016 22,016 512 512 7,056 7,043 -13 (-0.2%)
withdraw 551,361 551,361 11,776 11,776 512 512 7,070 7,055 -15 (-0.2%)
withdraw_nft 521,153 521,153 11,776 11,776 512 512 6,902 6,891 -11 (-0.2%)

Contract: logic

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
get_escrow 413,263 413,263 1,024 1,024 512 512 6,018 6,054 +36 (+0.6%)
secret_keys_to_public_keys 408,340 408,340 1,024 1,024 512 512 5,940 6,009 +69 (+1.2%)
share_escrow 405,898 405,898 11,264 11,264 512 512 5,932 5,953 +21 (+0.4%)

Contract: nft

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
burn_private 460,583 460,583 2,560 2,560 132,265 132,265 6,346 6,335 -11 (-0.2%)
burn_public 346,521 346,521 3,072 3,072 139,708 139,708 5,408 5,394 -14 (-0.3%)
mint_to_private 458,872 458,872 12,288 12,288 120,770 120,770 6,448 6,371 -77 (-1.2%)
mint_to_public 346,521 346,521 3,072 3,072 143,932 143,932 5,427 5,483 +56 (+1.0%)
transfer_private_to_private 411,588 411,588 11,776 11,776 512 512 6,030 5,968 -62 (-1.0%)
transfer_private_to_public 460,586 460,586 2,560 2,560 127,336 127,336 6,356 6,365 +9 (+0.1%)
transfer_public_to_private 455,437 455,437 12,288 12,288 121,079 121,079 6,368 6,300 -68 (-1.1%)
transfer_public_to_public 346,521 346,521 2,048 2,048 102,023 102,023 5,422 5,438 +16 (+0.3%)

Contract: token

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
🔴 burn_private 479,299 479,299 12,800 15,872 +3,072 (+24.0%) 146,878 147,583 +705 (+0.5%) 6,532 6,529 -3 (-0.0%)
🔴 burn_public 346,521 346,521 3,072 6,144 +3,072 (+100.0%) 136,102 136,810 +708 (+0.5%) 5,480 5,452 -28 (-0.5%)
initialize_transfer_commitment 424,338 424,338 11,264 11,264 512 512 6,066 6,065 -1 (-0.0%)
🔴 mint_to_private 458,815 458,815 12,288 15,360 +3,072 (+25.0%) 115,835 116,540 +705 (+0.6%) 6,360 6,381 +21 (+0.3%)
🔴 mint_to_public 346,521 346,521 3,072 6,144 +3,072 (+100.0%) 144,298 145,006 +708 (+0.5%) 5,431 5,430 -1 (-0.0%)
transfer_private_to_commitment 444,035 444,035 13,824 13,824 512 512 6,183 6,206 +23 (+0.4%)
transfer_private_to_private 441,790 441,790 22,016 22,016 512 512 6,148 6,178 +30 (+0.5%)
🔴 transfer_private_to_public 479,375 479,375 12,800 15,872 +3,072 (+24.0%) 149,749 150,457 +708 (+0.5%) 6,515 6,463 -52 (-0.8%)
🔴 transfer_private_to_public_with_commitment 509,042 509,042 23,040 26,112 +3,072 (+13.3%) 180,549 181,257 +708 (+0.4%) 6,699 6,656 -43 (-0.6%)
🔴 transfer_public_to_commitment 346,521 346,521 4,608 7,680 +3,072 (+66.7%) 132,362 133,070 +708 (+0.5%) 5,433 5,443 +10 (+0.2%)
🔴 transfer_public_to_private 455,469 455,469 12,288 15,360 +3,072 (+25.0%) 119,030 119,738 +708 (+0.6%) 6,363 6,359 -4 (-0.1%)
🔴 transfer_public_to_public 346,521 346,521 3,072 6,144 +3,072 (+100.0%) 138,790 139,501 +711 (+0.5%) 5,491 5,515 +24 (+0.4%)

Contract: tokenized_vault

Function Gates DA Gas L2 Gas Proving Time (ms)
Status Base PR Diff Base PR Diff Base PR Diff Base PR Diff
🔴 deposit_private_to_private 712,559 712,559 14,336 20,480 +6,144 (+42.9%) 295,387 296,596 +1,209 (+0.4%) 8,651 8,640 -11 (-0.1%)
🔴 deposit_private_to_private_exact 742,301 742,301 24,576 30,720 +6,144 (+25.0%) 326,556 328,065 +1,509 (+0.5%) 8,888 8,895 +7 (+0.1%)
🔴 deposit_private_to_public 712,296 712,296 5,120 11,264 +6,144 (+120.0%) 314,505 316,017 +1,512 (+0.5%) 8,676 8,617 -59 (-0.7%)
🔴 deposit_public_to_private 455,479 455,479 15,360 21,504 +6,144 (+40.0%) 362,177 363,653 +1,476 (+0.4%) 6,490 6,447 -43 (-0.7%)
🔴 deposit_public_to_private_exact 485,251 485,251 25,600 31,744 +6,144 (+24.0%) 393,508 395,020 +1,512 (+0.4%) 6,694 6,629 -65 (-1.0%)
🔴 deposit_public_to_public 346,521 346,521 6,144 12,288 +6,144 (+100.0%) 379,762 381,277 +1,515 (+0.4%) 5,562 5,537 -25 (-0.4%)
🔴 issue_private_to_private_exact 899,814 899,814 24,576 30,720 +6,144 (+25.0%) 326,832 328,305 +1,473 (+0.5%) 10,010 10,031 +21 (+0.2%)
🔴 issue_private_to_public_exact 899,574 899,574 15,360 21,504 +6,144 (+40.0%) 346,277 347,753 +1,476 (+0.4%) 10,290 10,189 -101 (-1.0%)
🔴 issue_public_to_private 455,470 455,470 15,360 21,504 +6,144 (+40.0%) 363,098 364,574 +1,476 (+0.4%) 6,527 6,441 -86 (-1.3%)
🔴 issue_public_to_public 346,521 346,521 6,144 12,288 +6,144 (+100.0%) 380,167 381,646 +1,479 (+0.4%) 5,548 5,542 -6 (-0.1%)
🔴 redeem_private_to_private_exact 776,472 776,472 24,064 30,208 +6,144 (+25.5%) 296,101 297,574 +1,473 (+0.5%) 8,922 8,971 +49 (+0.5%)
🔴 redeem_private_to_public 479,375 479,375 4,608 10,752 +6,144 (+133.3%) 285,086 286,562 +1,476 (+0.5%) 6,421 6,492 +71 (+1.1%)
🔴 redeem_public_to_private_exact 752,144 752,144 24,576 30,720 +6,144 (+25.0%) 304,183 305,659 +1,476 (+0.5%) 8,785 8,773 -12 (-0.1%)
🔴 redeem_public_to_public 346,521 346,521 5,120 11,264 +6,144 (+120.0%) 291,800 293,279 +1,479 (+0.5%) 5,398 5,428 +30 (+0.6%)
🔴 withdraw_private_to_private 595,855 595,855 13,824 19,968 +6,144 (+44.4%) 264,251 265,460 +1,209 (+0.5%) 7,409 7,388 -21 (-0.3%)
🔴 withdraw_private_to_private_exact 615,681 615,681 24,064 30,208 +6,144 (+25.5%) 295,663 297,172 +1,509 (+0.5%) 7,527 7,553 +26 (+0.3%)
🔴 withdraw_private_to_public_exact 499,220 499,220 14,848 20,992 +6,144 (+41.4%) 316,546 318,058 +1,512 (+0.5%) 6,584 6,599 +15 (+0.2%)
🔴 withdraw_public_to_private 571,676 571,676 14,336 20,480 +6,144 (+42.9%) 271,925 273,437 +1,512 (+0.6%) 7,283 7,332 +49 (+0.7%)
🔴 withdraw_public_to_public 346,521 346,521 5,120 11,264 +6,144 (+120.0%) 291,683 293,198 +1,515 (+0.5%) 5,424 5,414 -10 (-0.2%)

Copy link
Contributor

@zkfrov zkfrov left a comment

Choose a reason for hiding this comment

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

Niceee, lgtm! I would add some reference of how events are implemented in the docs (specially considering the magic value), update the forum post and/or update natspec of functions that emit events.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants