Skip to content

Commit 6b68a57

Browse files
critesjoshclaude
andcommitted
Add streaming payments contract example
A private streaming payments contract for Aztec that enables salary streaming, token vesting, and subscription payments with full privacy. Features: - Linear vesting with optional cliff period - Private streams using notes (amounts, schedules, identities hidden) - Recipient withdrawal of vested tokens - Sender cancellation with unvested token recovery - Full integration with defi-wonderland/aztec-standards Token contract Includes: - 11 unit tests for vesting calculations - 4 integration tests with Token contract (TXE) - 7 e2e TypeScript tests with TestWallet - CI workflow for automated testing Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 42c7773 commit 6b68a57

File tree

14 files changed

+7775
-0
lines changed

14 files changed

+7775
-0
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Streaming Payments Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
paths:
11+
- "streaming-payments/**"
12+
- ".github/workflows/streaming-payments-tests.yml"
13+
workflow_dispatch:
14+
15+
jobs:
16+
streaming-payments-tests:
17+
name: Streaming Payments Tests
18+
runs-on: ubuntu-latest
19+
env:
20+
AZTEC_ENV: local-network
21+
AZTEC_VERSION: 3.0.0-devnet.20251212
22+
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@v5
26+
27+
- name: Set up Node.js
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: "22"
31+
32+
- name: Set up Docker
33+
uses: docker/setup-buildx-action@v3
34+
35+
- name: Install Aztec CLI
36+
run: |
37+
curl -s https://install.aztec.network > tmp.sh
38+
NON_INTERACTIVE=1 bash tmp.sh
39+
rm tmp.sh
40+
41+
- name: Update path
42+
run: echo "$HOME/.aztec/bin" >> $GITHUB_PATH
43+
44+
- name: Set Aztec version
45+
run: aztec-up ${{ env.AZTEC_VERSION }}
46+
47+
- name: Run setup script (download Token contract and compile)
48+
working-directory: streaming-payments
49+
run: |
50+
chmod +x scripts/setup-token.sh
51+
./scripts/setup-token.sh
52+
timeout-minutes: 15
53+
54+
- name: Run Noir unit and integration tests (TXE)
55+
working-directory: streaming-payments
56+
run: aztec test --package streaming_payments_contract
57+
timeout-minutes: 10
58+
59+
- name: Upload test results if failed
60+
if: failure()
61+
uses: actions/upload-artifact@v4
62+
with:
63+
name: streaming-payments-test-logs
64+
path: |
65+
streaming-payments/target/**
66+
streaming-payments/.deps/**
67+
retention-days: 7
68+
69+
- name: Cleanup
70+
if: always()
71+
run: |
72+
echo "Cleaning up..."
73+
pkill -f "aztec" || true
74+
docker stop $(docker ps -q) || true
75+
docker rm $(docker ps -a -q) || true

streaming-payments/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
target/
2+
artifacts/
3+
node_modules/
4+
dist/
5+
.deps/
6+
pxe-test-data/
7+
*.log

streaming-payments/Nargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[workspace]
2+
members = [
3+
"contracts/streaming_payments",
4+
".deps/token_contract",
5+
]

streaming-payments/README.md

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# Streaming Payments Contract
2+
3+
A private streaming payments contract for Aztec that enables:
4+
- Salary streaming
5+
- Token vesting
6+
- Subscription payments
7+
8+
All with full privacy - amounts, schedules, and participant identities remain hidden.
9+
10+
## Features
11+
12+
- **Linear Vesting**: Tokens unlock linearly from start to end time
13+
- **Cliff Period**: Optional cliff before which no tokens can be withdrawn
14+
- **Private Streams**: Stream details stored in private notes
15+
- **Cancellation**: Sender can cancel and reclaim unvested tokens
16+
- **Partial Withdrawals**: Recipient can withdraw any unlocked amount
17+
- **Full Token Integration**: Uses the defi-wonderland/aztec-standards Token contract
18+
19+
## Architecture
20+
21+
### Storage
22+
23+
```
24+
Storage:
25+
├── token: PublicImmutable<AztecAddress> # Token contract address
26+
└── streams: Map<Field, Map<AztecAddress, Owned<PrivateMutable<StreamNote>>>>
27+
```
28+
29+
### StreamNote
30+
31+
Each stream is represented as a private note containing:
32+
33+
| Field | Type | Description |
34+
|-------|------|-------------|
35+
| stream_id | Field | Unique identifier |
36+
| sender | AztecAddress | Stream creator |
37+
| total_amount | u128 | Total tokens to stream |
38+
| start_time | u64 | When streaming begins |
39+
| end_time | u64 | When fully vested |
40+
| cliff_time | u64 | No withdrawals before this |
41+
| claimed_amount | u128 | Already withdrawn |
42+
| owner | AztecAddress | Recipient (note owner) |
43+
44+
### Key Functions
45+
46+
| Function | Visibility | Description |
47+
|----------|------------|-------------|
48+
| `constructor(token)` | public | Initialize with token address |
49+
| `create_stream(...)` | private | Create a new stream (requires authwit) |
50+
| `withdraw(stream_id, amount)` | private | Withdraw unlocked tokens |
51+
| `cancel_stream(stream_id, recipient, unvested)` | private | Cancel and reclaim unvested |
52+
| `get_stream_info(...)` | utility | View stream details |
53+
| `get_withdrawable(...)` | utility | Calculate withdrawable amount |
54+
| `get_unvested(...)` | utility | Calculate unvested amount |
55+
56+
## Privacy Properties
57+
58+
**Private** (hidden from observers):
59+
- Total stream amounts
60+
- Vesting schedules
61+
- Sender and recipient identities
62+
- Withdrawal amounts and timing
63+
64+
**Public** (visible on-chain):
65+
- Stream existence (via nullifiers)
66+
- Token contract address
67+
68+
## Usage
69+
70+
### Prerequisites
71+
72+
```bash
73+
# Install Aztec tools
74+
bash -i <(curl -s https://install.aztec.network)
75+
aztec-up 3.0.0-devnet.20251212
76+
```
77+
78+
### Setup
79+
80+
The project uses a workspace structure with the Token contract as a dependency. Run the setup script to download and compile everything:
81+
82+
```bash
83+
./scripts/setup-token.sh
84+
```
85+
86+
This will:
87+
1. Clone the Token contract from [defi-wonderland/aztec-standards](https://github.com/defi-wonderland/aztec-standards)
88+
2. Compile both contracts in the workspace
89+
3. Generate TypeScript bindings in `artifacts/`
90+
91+
### Build (Manual)
92+
93+
If you've already run setup, you can rebuild with:
94+
95+
```bash
96+
aztec compile --workspace
97+
aztec codegen target -o artifacts
98+
```
99+
100+
### Test
101+
102+
#### Noir Tests (TXE)
103+
104+
Run all Noir tests including integration tests with the Token contract:
105+
106+
```bash
107+
aztec test --package streaming_payments_contract
108+
```
109+
110+
This runs 15 tests:
111+
- 11 unit tests for vesting calculations
112+
- 4 integration tests with full token transfers
113+
114+
#### TypeScript Tests (requires running node)
115+
116+
```bash
117+
# Start Aztec local network first
118+
aztec start --local-network
119+
120+
# In another terminal, run tests
121+
npm install
122+
npm test
123+
```
124+
125+
### Example Flow
126+
127+
1. **Deploy Token**: Deploy a Token contract with minting capability
128+
129+
2. **Deploy StreamingPayments**: Deploy with the token address
130+
```
131+
StreamingPayments.constructor(token_address)
132+
```
133+
134+
3. **Mint Tokens**: Mint tokens to the sender's private balance
135+
136+
4. **Create Stream**: Sender creates a stream (requires authwit for token transfer)
137+
```
138+
// Create authwit for transfer_private_to_public
139+
create_stream(recipient, 1000, start, end, cliff, nonce) -> stream_id
140+
```
141+
142+
5. **Time Passes**: After vesting period, tokens become withdrawable
143+
144+
6. **Withdraw**: Recipient withdraws vested tokens to their private balance
145+
```
146+
withdraw(stream_id, amount)
147+
```
148+
149+
7. **Cancel** (optional): Sender cancels and reclaims unvested tokens
150+
```
151+
cancel_stream(stream_id, recipient, unvested_amount)
152+
```
153+
154+
## Linear Vesting Formula
155+
156+
```
157+
unlocked = total_amount * (current_time - start_time) / (end_time - start_time)
158+
```
159+
160+
Where:
161+
- Before `cliff_time`: `unlocked = 0`
162+
- After `end_time`: `unlocked = total_amount`
163+
164+
## Token Integration
165+
166+
This contract integrates with the [aztec-standards Token contract](https://github.com/defi-wonderland/aztec-standards):
167+
168+
- **create_stream**: Transfers tokens from sender's private balance to contract's public balance using `transfer_private_to_public` with authwit
169+
- **withdraw**: Transfers tokens from contract's public balance to recipient's private balance using `transfer_public_to_private`
170+
- **cancel_stream**: Returns unvested tokens to sender's private balance
171+
172+
### Authwit (Authorization Witness)
173+
174+
Creating a stream requires the sender to authorize the contract to transfer tokens on their behalf:
175+
176+
```noir
177+
// 1. Create the transfer call
178+
let transfer_call = Token::at(token).transfer_private_to_public(
179+
sender, streaming_contract, amount, nonce
180+
);
181+
182+
// 2. Add authwit
183+
add_private_authwit_from_call(env, sender, streaming_contract, transfer_call);
184+
185+
// 3. Create stream (uses the same nonce)
186+
StreamingPayments::at(streaming_contract).create_stream(
187+
recipient, amount, start, end, cliff, nonce
188+
);
189+
```
190+
191+
## File Structure
192+
193+
```
194+
streaming-payments/
195+
├── Nargo.toml # Workspace configuration
196+
├── README.md # This file
197+
├── package.json # NPM dependencies for tests
198+
├── contracts/
199+
│ └── streaming_payments/
200+
│ ├── Nargo.toml # Contract dependencies
201+
│ └── src/
202+
│ ├── main.nr # Main contract
203+
│ ├── stream_note.nr # StreamNote type
204+
│ └── lib.nr # Pure functions + tests
205+
├── scripts/
206+
│ └── setup-token.sh # Setup script
207+
├── tests/
208+
│ └── streaming_payments.test.ts # TypeScript integration tests
209+
├── .deps/ # Downloaded dependencies (gitignored)
210+
│ └── token_contract/ # Token contract from aztec-standards
211+
├── target/ # Compiled artifacts (gitignored)
212+
└── artifacts/ # TypeScript bindings (gitignored)
213+
```
214+
215+
## Dependencies
216+
217+
- Aztec v3.0.0-devnet.20251212
218+
- Token contract from [defi-wonderland/aztec-standards](https://github.com/defi-wonderland/aztec-standards) (dev branch)
219+
220+
## Related Patterns
221+
222+
- **Token Contract**: For actual token transfers
223+
- **Crowdfunding**: Similar time-based private payments
224+
- **Private Voting**: Uses similar note replacement patterns
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "streaming_payments_contract"
3+
authors = [""]
4+
compiler_version = ">=1.0.0"
5+
type = "contract"
6+
7+
[dependencies]
8+
aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v3.0.0-devnet.20251212", directory = "noir-projects/aztec-nr/aztec" }
9+
token = { path = "../../.deps/token_contract" }

0 commit comments

Comments
 (0)