> ## Documentation Index
> Fetch the complete documentation index at: https://aven-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Stream Contract: Escrow, Verification, and Payment Release

> The Aven Stream Contract manages payment stream lifecycle, escrow balances, work verification, approval, and payment release on Stellar Soroban.

The Stream Contract is the heart of Aven. It is the only contract that holds funds. Every employer deposit, every work-session verification, every approval decision, and every payment release passes through it. The contract independently enforces the payment formula — neither the dashboard nor the verifier service can bypass the on-chain checks.

## Purpose

When an employer creates a stream, the contract immediately transfers the full deposit into its own escrow. From that point, funds can only leave the contract in two ways:

* **Payment to the recipient** — after a verified withdrawal is approved (or auto-released after the deadline).
* **Refund to the sender** — when a stream is cancelled and no funds are reserved for outstanding withdrawals.

The contract recomputes every payment amount itself using `rate_per_second × active_duration_seconds`. Workers never submit their own payment figure; the contract enforces it.

***

## Data Structures

> **StreamRecord**
>
> The primary record stored for every stream. Read it with `get_stream(stream_id)`.
>
> | Field | Type | Description |
> | - | - | - |
> | `id` | `u64` | Auto-incrementing stream identifier |
> | `sender` | `Address` | Employer who created the stream and funded escrow |
> | `recipient` | `Address` | Worker who will receive payments |
> | `rate_per_ledger` | `i128` | Derived from `rate_per_second × 5` (5 ledgers per second). Used internally for payment math |
> | `asset` | `Address` | Stellar asset contract address (XLM SAC or testnet USDC) |
> | `total_deposited` | `i128` | Total amount locked in escrow at creation (7-decimal fixed-point) |
> | `total_withdrawn` | `i128` | Cumulative amount paid out to the recipient so far |
> | `start_ledger` | `u32` | Ledger number when the stream was created |
> | `duration_ledgers` | `u32` | Total scheduled duration in ledgers |
> | `status` | `StreamStatus` | Current state: `Active`, `Paused`, `Completed`, or `Cancelled` |
> | `category` | `Category` | Work category: `Freelance`, `Salary`, `Bounty`, `Grant`, `AgentTask`, or `Subscription` |
> | `title` | `String` | Human-readable stream title (max 80 UTF-8 bytes) |
> | `paused_at_ledger` | `u32` | Ledger when the stream was paused (0 if not paused) |
> | `paused_duration_ledgers` | `u32` | Accumulated paused time, used to adjust computations |
> | `checkpoint_count` | `u32` | Legacy field retained for client compatibility; always `0` in new streams |
> | `checkpoint_span_ledgers` | `u32` | Legacy field; always `0` in new streams |
> | `withdrawable_cap_percent` | `u32` | Legacy field; always `0` in new streams |
> | `approval_timeout_ledgers` | `u32` | Ledgers after verification before auto-release is permitted |

> **WithdrawalRecord**
>
> Created by `verify_work` (or `request_withdrawal` in legacy mode). Each record represents one work session payment.
>
> | Field | Type | Description |
> | - | - | - |
> | `stream_id` | `u64` | Parent stream |
> | `request_id` | `String` | Unique session identifier (max 64 bytes), generated by the verifier |
> | `amount` | `i128` | Contract-enforced payment amount in 7-decimal fixed-point |
> | `requested_at_ledger` | `u32` | Ledger when the withdrawal record was created |
> | `deadline_ledger` | `u32` | Ledger after which the worker may auto-release without employer approval |
> | `status` | `WithdrawalStatus` | Current state (see below) |
> | `evidence_hash` | `Option<BytesN<32>>` | SHA-256 of the verifier's work report; `None` for legacy paths |
> | `work_start_ledger` | `u32` | Ledger when the tracked work session began |
> | `active_duration_seconds` | `u64` | Verified active seconds recorded by the CLI |

> **WithdrawalStatus enum**
>
> The lifecycle of a single withdrawal request.
>
> | Variant | Meaning |
> | - | - |
> | `Pending` | Verified and reserved; awaiting employer action |
> | `Approved` | Employer explicitly approved; ready for `withdraw_approved` |
> | `Disputed` | Employer disputed; reservation released back to unreserved escrow |
> | `Withdrawn` | Payment has been transferred to the recipient; terminal state |

> **StreamStatus enum**
>
> The lifecycle state of a stream.
>
> | Variant | Meaning |
> | - | - |
> | `Active` | Stream is running; new work sessions can be verified |
> | `Paused` | Stream is paused by the sender; new verifications are blocked |
> | `Completed` | All deposited funds have been paid out; terminal state |
> | `Cancelled` | Sender cancelled the stream; remaining unreserved escrow was refunded |

***

## Public Entrypoints

### `create_stream`

Creates a new payment stream and transfers the full deposit into escrow.

```rust theme={null}
create_stream(
    sender, recipient, rate_per_second, asset,
    total_deposited, duration_ledgers, checkpoint_count,
    withdrawable_cap_percent, approval_timeout_ledgers,
    category, title
) → u64  // returns the new stream_id
```

**sender** `Address` *required*

The employer's Stellar address. Must authorize this transaction. Must differ from `recipient`.

**recipient** `Address` *required*

The worker's Stellar address who will receive payments.

**rate\_per\_second** `i128` *required*

Payment rate in Stellar 7-decimal fixed-point per second. For example, `1_0000000` = 1 XLM/s. Must be greater than 0.

**asset** `Address` *required*

Stellar asset contract address. Use the XLM SAC address or the testnet USDC contract.

**total\_deposited** `i128` *required*

Total escrow amount in 7-decimal fixed-point. Must be at least `rate_per_second × 5 × duration_ledgers`.

**duration\_ledgers** `u32` *required*

Scheduled stream length in ledgers (approximately 5 seconds per ledger). Must be greater than 0.

**checkpoint\_count** `u32`

Legacy parameter. Pass `0` — the contract ignores this value in work-session mode.

**withdrawable\_cap\_percent** `u32`

Legacy parameter. Pass `0` — the contract ignores this value in work-session mode.

**approval\_timeout\_ledgers** `u32` *required*

Number of ledgers the employer has to approve or dispute after `verify_work` is called. After this deadline the worker can call `withdraw_approved` without explicit employer action. Must be greater than 0.

**category** `Category` *required*

One of `Freelance`, `Salary`, `Bounty`, `Grant`, `AgentTask`, or `Subscription`. Affects reputation scoring weights.

**title** `String` *required*

Human-readable stream name. Maximum 80 UTF-8 bytes.

***

### `verify_work`

Called exclusively by the Aven verifier service. Reserves funds for one completed work session and creates a `WithdrawalRecord` in `Pending` status.

```rust theme={null}
verify_work(
    stream_id, request_id, amount,
    evidence_hash, active_duration_seconds, work_start_ledger
) → ()
```

> [!NOTE]
>
> You do not call `verify_work` directly. It is signed by the Aven verifier service after the CLI submits a work-session report. The contract independently recalculates `rate_per_second × active_duration_seconds` and rejects the call if the reported `amount` does not match.

***

### `request_withdrawal`

Legacy direct-request path. Disabled whenever the verifier is configured.

```rust theme={null}
request_withdrawal(stream_id, recipient, request_id, amount) → ()
```

> [!WARNING]
>
> If a verifier is set on the stream contract, calling `request_withdrawal` returns **Error 32: VerificationRequired**. All active Aven deployments use the verifier. This entrypoint exists for compatibility with older integrations.

***

### `approve_withdrawal`

Called by the employer to approve a `Pending` withdrawal.

```rust theme={null}
approve_withdrawal(stream_id, sender, request_id) → ()
```

The `sender` must be the stream's original employer address and must authorize the transaction. Moves the `WithdrawalRecord` status from `Pending` → `Approved`.

***

### `dispute_withdrawal`

Called by the employer to dispute a `Pending` withdrawal.

```rust theme={null}
dispute_withdrawal(stream_id, sender, request_id) → ()
```

Moves the status to `Disputed` and releases the reserved amount back to unreserved escrow so future sessions can use it. Cannot be called on an already-approved or withdrawn record.

***

### `withdraw_approved`

Called by the recipient to transfer an approved (or auto-released) payment.

```rust theme={null}
withdraw_approved(stream_id, recipient, request_id) → i128
```

Returns the amount transferred. Also triggers `mint_attestation` on the Attestation Contract. If this payment completes the stream (`total_withdrawn == total_deposited`), a second `StreamCompletion` attestation is minted for reputation scoring.

Auto-release applies when the current ledger is at or past `deadline_ledger` and the record is still in `Pending` status — the employer did not act within the timeout window.

***

### Stream Lifecycle Management

```rust theme={null}
pause_stream(stream_id, caller)   → ()
resume_stream(stream_id, caller)  → ()
cancel_stream(stream_id, caller)  → ()
```

All three require the `caller` to be the stream's `sender` and to authorize the transaction.

* **`pause_stream`** — moves an `Active` stream to `Paused`. Records the current ledger so paused time can be tracked. New work sessions cannot be verified on a paused stream.
* **`resume_stream`** — moves a `Paused` stream back to `Active`. Accumulates the paused duration.
* **`cancel_stream`** — cancels an `Active` or `Paused` stream. Returns all unreserved escrow to the sender. **Fails** with **Error 36: OutstandingWithdrawals** if any funds are currently reserved for pending withdrawals — resolve those first.

***

### Read-Only Queries

```rust theme={null}
get_stream(stream_id)                 → StreamRecord
get_sender_streams(sender)            → Vec<u64>
get_recipient_streams(recipient)      → Vec<u64>
get_withdrawal(stream_id, request_id) → WithdrawalRecord
compute_available(stream_id)          → i128
compute_earned(stream_id)             → i128
```

| Function | Returns |
| - | - |
| `get_stream` | Full `StreamRecord` for the given ID |
| `get_sender_streams` | List of stream IDs where the address is the sender |
| `get_recipient_streams` | List of stream IDs where the address is the recipient |
| `get_withdrawal` | A single `WithdrawalRecord` by stream and request ID |
| `compute_available` | Unreserved escrow remaining — funds not yet committed to any pending withdrawal |
| `compute_earned` | `total_withdrawn + currently_reserved` — total value measured by submitted sessions |

***

## Contract Events

The Stream Contract emits the following events. You can subscribe to them via the Stellar Horizon events API or the `stellar-sdk` event stream.

| Event | Topics | Data Fields |
| - | - | - |
| `stream_created` | `stream_id`, `sender`, `recipient` | `asset`, `total_deposited` |
| `work_verified` | `stream_id`, `recipient` | `request_id`, `amount`, `active_duration_seconds`, `evidence_hash`, `deadline_ledger` |
| `withdrawal_requested` | `stream_id`, `recipient` | `request_id`, `amount`, `deadline_ledger` |
| `withdrawal_approved` | `stream_id`, `sender` | `request_id` |
| `withdrawal_disputed` | `stream_id`, `sender` | `request_id` |
| `stream_withdrawn` | `stream_id`, `recipient` | `amount` |
| `stream_paused` | `stream_id` | `paused_at_ledger` |
| `stream_resumed` | `stream_id` | `resumed_at_ledger` |
| `stream_cancelled` | `stream_id` | `paid_to_recipient`, `refunded_to_sender` |

***

## Error Codes

The following errors are returned as Soroban contract errors. The numeric code is embedded in the transaction result.

| Code | Name | When it occurs |
| - | - | - |
| 1 | `AlreadyInitialized` | `init` called more than once |
| 2 | `NotInitialized` | Contract used before `init` |
| 3 | `InvalidRate` | `rate_per_second` ≤ 0 |
| 4 | `InvalidDeposit` | `total_deposited` ≤ 0 |
| 5 | `InvalidDuration` | `duration_ledgers` = 0 |
| 6 | `TitleTooLong` | Title exceeds 80 bytes |
| 7 | `InsufficientDeposit` | Deposit is less than `rate × duration` |
| **8** | **`StreamNotFound`** | No stream exists for the given `stream_id` |
| **9** | **`NotSender`** | Caller is not the stream's `sender` |
| **10** | **`NotRecipient`** | Caller is not the stream's `recipient` |
| **11** | **`WrongStatus`** | Action not permitted in the current `StreamStatus` (e.g. pausing an already-paused stream) |
| 12 | `NothingToWithdraw` | `WithdrawalRecord` is already `Withdrawn` |
| 13 | `Overflow` | Arithmetic overflow in payment math |
| 14 | `HistoryFull` | Address has reached the 1,000 stream limit |
| 22 | `InvalidRequestId` | `request_id` is empty or exceeds 64 bytes |
| 23 | `InvalidAmount` | `amount` ≤ 0 |
| **24** | **`AmountExceedsWithdrawable`** | Requested amount exceeds unreserved escrow |
| 25 | `WithdrawalNotFound` | No withdrawal record for the given `stream_id` + `request_id` |
| 26 | `WithdrawalAlreadyExists` | A withdrawal with this `request_id` already exists |
| 27 | `WithdrawalNotApproved` | `withdraw_approved` called before employer approved and before deadline |
| 28 | `WithdrawalDisputed` | Operation not permitted on a disputed record |
| 30 | `NotAdmin` | Admin-only function called by a non-admin |
| 31 | `VerifierNotConfigured` | `verify_work` called before a verifier was set |
| **32** | **`VerificationRequired`** | `request_withdrawal` called when a verifier is configured |
| 33 | `SenderMatchesRecipient` | `sender` and `recipient` are the same address |
| 34 | `InvalidActiveDuration` | `active_duration_seconds` = 0 in `verify_work` |
| **35** | **`PaymentMismatch`** | Submitted `amount` does not match `rate × active_duration_seconds` |
| **36** | **`OutstandingWithdrawals`** | `cancel_stream` called while reserved funds are non-zero |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.