You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/docs-developers/docs/aztec-nr/framework-description/note_delivery.md
+43-50Lines changed: 43 additions & 50 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,7 +15,7 @@ In Aztec, creating a note involves two steps:
15
15
16
16
Without delivery, the recipient won't know the note exists or be able to access its contents, even though the note hash is onchain.
17
17
18
-
## The `.deliver()`Method
18
+
## The `.deliver()`method
19
19
20
20
When you create a note using state variables like `PrivateMutable`, `PrivateSet`, `BalanceSet`, or `SinglePrivateMutable`, the creation methods return a `NoteMessage` or `MaybeNoteMessage` object. A message contains arbitrary information emitted from a contract - currently this includes notes and private events, though developers may define other message types in the future. You must call `.deliver()` on this object to send the message (containing the note) to the recipient.
21
21
@@ -43,21 +43,23 @@ Aztec provides three delivery modes that offer different tradeoffs between cost,
43
43
44
44
This delivery method encrypts messages without constraints and emits them via an oracle call as offchain effects, rather than through the protocol's log stream (which would post data to Ethereum blobs). With offchain delivery, you must manually handle both message transmission and processing.
1.**Message emission**: The contract encrypts the message (without constraints) and emits it via an oracle call. This creates an "offchain effect" that is included in the transaction but not posted to L1.
51
51
52
-
2.**Manual extraction**: When the transaction is sent, you must extract the offchain message from the transaction's offchain effects (available via `provenTx.offchainEffects` in aztec.js).
52
+
2.**Extraction**: When the transaction is sent, you read the emitted messages from the send result (`offchainMessages` in aztec.js), or extract them from a proven transaction's offchain effects.
53
53
54
-
3.**Manual delivery**: You deliver the message through your own channel - Signal, cloud storage, QR codes, peer-to-peer networks, etc.
54
+
3.**Manual delivery**: You deliver the message through your own channel - a claim link, QR code, messaging service, cloud storage, etc.
55
55
56
-
4.**Manual processing**: The recipient calls `process_message`on the target contract (as an unconstrained function), passing the ciphertext and message context. This decrypts the message and processes it (e.g., adding notes to the PXE database).
56
+
4.**Receipt and processing**: The recipient calls the `offchain_receive` utility function on the contract that emitted the message (this function is generated automatically by the `#[aztec]` macro). The message is stored in a local inbox and processed during private state sync once the transaction that emitted it is found onchain.
57
57
58
-
The PXE cannot automatically discover offchain messages during private state sync because they are not in the log stream that nodes load from Ethereum blobs. **You are responsible for implementing both the delivery mechanism and ensuring the recipient processes the message.**
58
+
The PXE cannot automatically discover offchain messages during private state sync because they are not in the log stream that nodes load from Ethereum blobs. **You are responsible for implementing the delivery mechanism and ensuring the recipient receives the message.**
59
59
60
-
#### When to Use
60
+
See [Offchain message delivery](./offchain_message_delivery.md) for the complete workflow, including sender and recipient code, message processing details, and how to test it from Noir.
61
+
62
+
#### When to use
61
63
62
64
-**Use when:** The sender is incentivized to deliver correctly (e.g., sending to yourself, payment for goods/services where recipient must receive the note to complete the transaction)
63
65
-**Costs:** Zero delivery fees (no blob space), zero proving time overhead
@@ -66,7 +68,7 @@ The PXE cannot automatically discover offchain messages during private state syn
66
68
67
69
This is expected to be the most common delivery method when you don't need constrained delivery guarantees, as it completely eliminates blob space costs.
68
70
69
-
#### Example Use Cases
71
+
#### Example use cases
70
72
71
73
- Change notes when transferring tokens (you're sending to yourself)
72
74
- Payments where the recipient won't provide goods/services without the note
This section will be updated with a complete TypeScript example showing how to extract offchain messages from transaction effects and manually deliver them once the API in Aztec.js is finalized. The full workflow example will make the offchain delivery pattern clearer.
84
-
:::
85
-
86
-
#### JavaScript Implementation
84
+
#### JavaScript implementation
87
85
88
-
When using offchain delivery, extract and manually deliver messages in your application:
86
+
When using offchain delivery, extract the emitted messages from the send result and deliver them to the recipient in your application. The recipient hands each message to their wallet via the auto-generated `offchain_receive` utility function:
Unconstrained delivery exposes `via_non_interactive_handshake()`, `via_interactive_handshake()` and `via_address_derived_secret()`. Constrained delivery exposes only `via_non_interactive_handshake()` and `via_interactive_handshake()`, since an address-derived secret cannot back constrained delivery.
206
199
207
-
## Note Discovery and the Sender
200
+
## Note discovery and the sender
208
201
209
202
When a note is delivered, recipients need to discover it among all the encrypted logs on the network. Aztec.nr uses a **tagging system** that requires computing a shared secret between the sender and recipient.
210
203
211
-
### Who is the "Sender"?
204
+
### Who is the "sender"?
212
205
213
206
The "sender" for note discovery is **not the contract calling `.deliver()`**. Instead, it's the **account contract** that initiated the transaction.
214
207
215
208
When your wallet submits a transaction, it tells PXE which address to use as the sender for tags (typically the originating account). Recipients compute the tag to find their notes from a secret shared between the sender and recipient, and there is [more than one way to establish that secret](#tagging-secret-strategy), chosen by the wallet. Contracts can override the sender at message delivery via the `with_sender` builder method, which works for both constrained and unconstrained delivery, e.g. `MessageDelivery::onchain_constrained().with_sender(address)`. They can similarly override how the tag secret is derived via the builder's `via_*` methods; see [overriding the strategy from the contract](#overriding-the-strategy-from-the-contract).
216
209
217
210
**Example:** If Alice uses her account contract to call a token contract that mints tokens to Bob, the "sender for tags" is Alice's account contract address, not the token contract address.
218
211
219
-
### Discovering Notes from Unknown Senders
212
+
### Discovering notes from unknown senders
220
213
221
214
When the tag is derived from an address-based shared secret, you cannot compute it for a sender you haven't registered in advance, so you cannot receive those notes from an unknown sender. Handshake protocols let the two parties agree on the secret another way and lift this restriction.
222
215
223
216
See [You cannot receive address-derived tagged notes from an unknown sender](../../foundational-topics/advanced/storage/note_discovery.md#you-cannot-receive-address-derived-tagged-notes-from-an-unknown-sender) in the note discovery documentation for the approaches and workarounds.
224
217
225
-
## Delivering to Someone Other Than the Note Owner
218
+
## Delivering to someone other than the note owner
226
219
227
220
You can deliver a note to an address other than the note's owner using `.deliver_to()`:
description: Deliver notes and events to recipients through your own channel with offchain message delivery, avoiding data availability costs entirely.
5
+
sidebar_position: 4
6
+
---
7
+
8
+
Offchain message delivery lets you send notes and events to recipients without posting any data onchain. The encrypted message is returned to the sender's application, which transports it to the recipient through its own channel: a link, a QR code, a direct message, or any other medium. This eliminates data availability (DA) costs and adds zero proving time, at the cost of the application handling transport itself.
9
+
10
+
This page walks through the complete workflow. For guidance on when offchain delivery is the right choice compared to the onchain modes, see [Note delivery](./note_delivery.md).
11
+
12
+
## Overview
13
+
14
+
An offchain-delivered message goes through five stages:
15
+
16
+
1.**Emission**: The contract delivers a note or event with `MessageDelivery::offchain()`. The message is encrypted to the recipient and emitted as an offchain effect, which is part of the transaction's execution output but never posted onchain.
17
+
2.**Extraction**: After sending the transaction, the sender's application reads the emitted messages from the send result (`offchainMessages`).
18
+
3.**Transport**: The application delivers each message to its recipient through a channel of its choosing. This step is entirely the application's responsibility.
19
+
4.**Receipt**: The recipient hands the message to their wallet by calling the `offchain_receive` utility function on the contract that emitted it. This function is generated automatically by the `#[aztec]` macro, so every Aztec.nr contract has it.
20
+
5.**Processing**: The message sits in a local inbox until the transaction that emitted it is found onchain. During private state sync, the wallet decrypts the message, validates the resulting notes and events against onchain data, and adds them to its database.
21
+
22
+
Because processing validates the message against the onchain transaction, a malformed or dishonest message cannot trick the recipient: a message that cannot be decrypted or validated is simply ignored.
23
+
24
+
## Emitting offchain messages from a contract
25
+
26
+
Choose offchain delivery by passing `MessageDelivery::offchain()` to `.deliver()` (for notes) or `.deliver_to()` (for events). The token contract's `transfer_in_private_with_offchain_delivery` function delivers both resulting balance notes and a `Transfer` event offchain:
Nothing else changes in the contract: the same notes and events are created, only their delivery mechanism differs.
31
+
32
+
Under the hood, `MessageDelivery::offchain()` encrypts the message without constraints and emits it via the [`deliver_offchain_message`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/offchain_messages/fn.deliver_offchain_message) function. See the [`MessageDelivery`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/message_delivery/struct.MessageDelivery) API reference for details on guarantees, costs, and privacy.
33
+
34
+
## Extracting messages on the sender side
35
+
36
+
In `aztec.js`, the result of sending a transaction includes the offchain messages emitted during execution. Each message carries the encrypted `payload`, the `recipient` address, the emitting `contractAddress`, and the `anchorBlockTimestamp` of the transaction:
The recipient needs four pieces of information to receive a message:
61
+
62
+
- the message payload (ciphertext)
63
+
- the recipient address
64
+
- the hash of the transaction that emitted the message
65
+
- the anchor block timestamp
66
+
67
+
How you get these to the recipient is up to your application. Common patterns include encoding them into a claim link or QR code that the recipient opens, sending them through a messaging service, or storing them where the recipient can fetch them.
68
+
69
+
:::warning Back up your messages
70
+
Offchain messages are not stored onchain, so they cannot be recovered from the network. Until the recipient has processed a message, losing it means the recipient cannot decrypt the corresponding note contents (the note itself still exists onchain).
71
+
:::
72
+
73
+
## Receiving messages
74
+
75
+
The recipient calls the auto-generated `offchain_receive` utility function on the contract that emitted the message. Utility functions run locally and do not create a transaction, so this call is free:
`offchain_receive` accepts up to 16 messages per call. It stores them in a persistent inbox scoped to each message's recipient; processing happens later, during sync.
91
+
92
+
## How messages are processed
93
+
94
+
Once received, messages are managed by an inbox that the wallet syncs against (see [`receive`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/processing/offchain/fn.receive) in the API reference):
95
+
96
+
-**Transaction resolution**: A message is only processed once the transaction identified by its `tx_hash` is found onchain. This provides the context needed to validate the notes and events the message contains. Until then, the message waits in the inbox.
97
+
-**Reorg safety**: Messages remain in the inbox even after processing. If a reorg reverts the transaction that emitted a message, its effects are rolled back; when the transaction is mined again, the message is reprocessed automatically without needing to be delivered again.
98
+
-**Expiry**: Messages are evicted from the inbox once they expire, which happens 26 hours after their anchor block timestamp (the maximum transaction lifetime of 24 hours plus a 2 hour tolerance). A transaction can no longer be included after that point, so an unresolved message can never become processable.
99
+
100
+
## Current limitations
101
+
102
+
-**Senders must self-deliver their own messages.** Messages addressed to the sender (such as the change note in a token transfer) are not processed automatically yet. Call `offchain_receive` for them just like you would on the recipient side.
103
+
-**Messages must be bound to a transaction.** Messages without an associated transaction hash are not processed in the current version; they sit in the inbox until they expire.
104
+
-**Batch size**: A single `offchain_receive` call accepts at most 16 messages. Split larger sets into multiple calls.
105
+
106
+
## Testing offchain delivery from Noir
107
+
108
+
The TXE test environment buffers offchain messages emitted during calls and exposes them via `env.offchain_messages()`. Feed them back through `offchain_receive` to complete the delivery loop inside a test:
`env.offchain_messages()` returns up to 64 messages, so drain it into batches of at most 16 when a test emits more messages than one `offchain_receive` call accepts.
113
+
114
+
## Next steps
115
+
116
+
- Compare delivery modes and their guarantees in [Note delivery](./note_delivery.md)
117
+
- Learn how onchain-delivered notes are found in [Note discovery](../../foundational-topics/advanced/storage/note_discovery.md)
118
+
- Browse the [`MessageDelivery`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/message_delivery/struct.MessageDelivery) API reference
0 commit comments