|
| 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 |
0 commit comments