Skip to content

Commit 820eabb

Browse files
critesjoshclaude
andcommitted
Refactor streaming payments to use public stream registry
Fix cancellation issue where sender couldn't nullify recipient's private note. Now stream metadata is stored publicly while token balances remain private. Changes: - Add StreamData struct for public storage in lib.nr - Replace private note storage with PublicMutable<StreamData> - Sender can now cancel streams since data is publicly accessible - Update cancel_stream to take (stream_id, unvested_amount) instead of (stream_id, recipient, unvested_amount) - Add public helper functions for state updates via enqueue Privacy tradeoffs: - Stream existence, parties, and schedule are now public - Token balances and actual withdrawal amounts remain private Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 68eb605 commit 820eabb

2 files changed

Lines changed: 238 additions & 108 deletions

File tree

streaming-payments/contracts/streaming_payments/src/lib.nr

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,76 @@
1-
/// Pure computation functions for streaming payments
1+
/// Pure computation functions and types for streaming payments
22
///
33
/// These functions can be tested without the aztec context.
44

5+
use aztec::protocol_types::{address::AztecAddress, traits::{Deserialize, Packable, Serialize}};
6+
7+
/// Public stream data stored in contract state
8+
/// Contains all stream parameters and current status
9+
#[derive(Eq, Serialize, Deserialize, Packable)]
10+
pub struct StreamData {
11+
/// Address that created the stream (can cancel)
12+
pub sender: AztecAddress,
13+
/// Address that receives the streamed tokens
14+
pub recipient: AztecAddress,
15+
/// Total tokens to be streamed
16+
pub total_amount: u128,
17+
/// Unix timestamp when streaming starts
18+
pub start_time: u64,
19+
/// Unix timestamp when streaming ends (fully vested)
20+
pub end_time: u64,
21+
/// Unix timestamp before which no tokens can be withdrawn
22+
pub cliff_time: u64,
23+
/// Amount already claimed by recipient
24+
pub claimed_amount: u128,
25+
/// Whether the stream has been cancelled by sender
26+
pub cancelled: bool,
27+
}
28+
29+
impl StreamData {
30+
pub fn new(
31+
sender: AztecAddress,
32+
recipient: AztecAddress,
33+
total_amount: u128,
34+
start_time: u64,
35+
end_time: u64,
36+
cliff_time: u64,
37+
) -> Self {
38+
StreamData {
39+
sender,
40+
recipient,
41+
total_amount,
42+
start_time,
43+
end_time,
44+
cliff_time,
45+
claimed_amount: 0,
46+
cancelled: false,
47+
}
48+
}
49+
50+
/// Calculate how many tokens are unlocked at the given timestamp
51+
pub fn compute_unlocked(self, current_time: u64) -> u128 {
52+
compute_unlocked_amount(
53+
self.total_amount,
54+
self.start_time,
55+
self.end_time,
56+
self.cliff_time,
57+
current_time,
58+
)
59+
}
60+
61+
/// Calculate how many tokens can be withdrawn (unlocked minus already claimed)
62+
pub fn compute_withdrawable(self, current_time: u64) -> u128 {
63+
let unlocked = self.compute_unlocked(current_time);
64+
compute_withdrawable_amount(unlocked, self.claimed_amount)
65+
}
66+
67+
/// Calculate unvested amount (for cancellation)
68+
pub fn compute_unvested(self, current_time: u64) -> u128 {
69+
let vested = self.compute_unlocked(current_time);
70+
self.total_amount - vested
71+
}
72+
}
73+
574
/// Calculate how many tokens are unlocked at a given timestamp
675
///
776
/// # Arguments
@@ -264,11 +333,7 @@ fn test_linear_vesting_precision() {
264333
use crate::StreamingPayments;
265334
use dep::aztec::{
266335
oracle::random::random,
267-
protocol_types::address::AztecAddress,
268-
test::helpers::{
269-
authwit::add_private_authwit_from_call,
270-
test_environment::TestEnvironment,
271-
},
336+
test::helpers::{authwit::add_private_authwit_from_call, test_environment::TestEnvironment},
272337
};
273338
use dep::token::Token;
274339

@@ -439,11 +504,8 @@ unconstrained fn test_cancel_stream() {
439504
// Stream is already fully vested - cancel with unvested_amount = 0
440505
// This tests that cancellation works for a fully vested stream
441506
let unvested_amount: u128 = 0;
442-
let cancel_call = StreamingPayments::at(streaming_address).cancel_stream(
443-
stream_id,
444-
recipient,
445-
unvested_amount,
446-
);
507+
let cancel_call =
508+
StreamingPayments::at(streaming_address).cancel_stream(stream_id, unvested_amount);
447509
env.call_private(sender, cancel_call);
448510

449511
// Sender should receive nothing back (all vested)

0 commit comments

Comments
 (0)