> For the complete documentation index, see [llms.txt](https://docs.zebec.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zebec.io/developer-docs/sdks/streaming-sdk/aleo-streaming-sdk.md).

# Aleo

Aleo Streaming SDK - TypeScript SDK for Zebec payment streams on Aleo (public and private).

A TypeScript SDK for building payroll and streaming apps on Aleo. It wraps the Zebec stream program (`test_zebec_stream_v3.aleo` on testnet, `zebec_stream_v1.aleo` on mainnet) so your app can create, fund, pause, withdraw, auto-withdraw, and cancel IARC-22 stablecoin streams without assembling Leo plaintext by hand.

Package: [`@zebec-network/aleo-stream-sdk`](https://www.npmjs.com/package/@zebec-network/aleo-stream-sdk)

## Overview

Use this SDK when you are shipping a streaming product on Aleo: a payroll dashboard, a vesting app, or a backend worker that auto-withdraws vested tokens.

Typical use cases include:

* **Payroll** — Stream salaries to employees over a duration (minute / hour / day frequencies).
* **Vesting** — Deposit tokens once and let the receiver withdraw as they vest.
* **Private payroll** — Keep sender/receiver tickets as private records while still publishing a public accounting anchor.
* **Auto-pay** — A designated withdrawer pulls vested amounts on a fixed interval.

The SDK does **not** ship a wallet. You implement a thin `AleoWallet` adapter (Puzzle, a Provable `Account` + delegated proving service, or your own signer). The service only calls `executeTransaction`, `decrypt`, and `requestRecords`.

{% hint style="info" %}
Load **one** Aleo network per app process. `ZebecStreamService` dynamically imports either `@provablehq/sdk/testnet.js` or `mainnet.js`. Statically importing both downloads two WASM blobs and will stall a browser app.
{% endhint %}

## Installation

```bash
npm install @zebec-network/aleo-stream-sdk
```

```bash
yarn add @zebec-network/aleo-stream-sdk
```

### Runtime dependencies

Installed with the package:

* [`@provablehq/sdk`](https://www.npmjs.com/package/@provablehq/sdk) `^0.11.9` — Aleo WASM, addresses, hashing, network client
* [`bignumber.js`](https://www.npmjs.com/package/bignumber.js) — amount scaling

### Requirements

* Node.js 20+ (the package is ESM: `"type": "module"`)
* TypeScript 5+ recommended
* An Aleo account with credits for transaction fees
* For **public** streams: public token balance and an `approve_public` allowance to the stream program
* For **private** streams: unspent token records and a record scanner
* Optional: [Provable delegated proving](https://api.provable.com) if you do not prove locally

## Implement a streaming app

Build the product in this order. Each step maps to a screen or backend job.

```
1. Wallet adapter  →  2. ZebecStreamService
        ↓
3. Admin: config + whitelist  (once per tenant)
        ↓
4. Backend signs StreamTokenFee
        ↓
5. Sender creates public or private stream
        ↓
6. Dashboard lists streams (reads)
        ↓
7. Receiver withdraws / sender pauses, topups, cancels
        ↓
8. Optional: withdrawer auto-withdraw worker
```

### 1. Wallet adapter

Map your wallet to `AleoWallet`. This is the only integration surface for signing and records.

```ts
import type { AleoWallet, TransactionOptions } from "@zebec-network/aleo-stream-sdk";

const wallet: AleoWallet = {
  address: connectedAddress,
  decrypt: (cipherText) => puzzle.decrypt(cipherText),
  requestRecords: (program, includePlaintext) =>
    puzzle.requestRecords(program, includePlaintext),
  executeTransaction: async (options: TransactionOptions) => {
    // options.fee is microcredits (service default 100_000).
    // If your proving API expects credits, divide by 1_000_000 here.
    const { transactionId } = await puzzle.execute(options);
    return { transactionId };
  },
};
```

| Method               | Used for                                                    |
| -------------------- | ----------------------------------------------------------- |
| `executeTransaction` | Every write (create, topup, withdraw, admin, token approve) |
| `decrypt`            | Private tickets and records                                 |
| `requestRecords`     | Finding tickets, token records, credits records             |

Pass `options.imports` through unchanged. The service fills IARC-22 nested imports for token `call.dynamic`.

{% hint style="warning" %}
A proving service returning `accepted` is **not** the same as explorer confirmation. Mappings such as `stream_anchors` can lag. Wait until `getStreamAnchor` reflects the new state before the next write, or follow-on proves will be rejected.
{% endhint %}

### 2. Construct the service

Create **one** service per role (admin, sender, receiver, withdrawer), each with that role's wallet.

```ts
import {
  Network,
  ZebecStreamService,
} from "@zebec-network/aleo-stream-sdk";

const network = Network.TESTNET; // or Network.MAINNET

const sender = await new ZebecStreamService(wallet, {
  network,
  // host: "https://api.provable.com/v2", // default
}).ready();
```

`ready()` waits for that network's WASM. Async methods wait automatically; await `ready()` before using `sender.networkClient`.

| Network   | Program                     | Explorer                                                               |
| --------- | --------------------------- | ---------------------------------------------------------------------- |
| `testnet` | `test_zebec_stream_v3.aleo` | [testnet.explorer.provable.com](https://testnet.explorer.provable.com) |
| `mainnet` | `zebec_stream_v1.aleo`      | [explorer.provable.com](https://explorer.provable.com)                 |

Transaction URL: `https://testnet.explorer.provable.com/transaction/<id>`.

### 3. Bootstrap a tenant (admin, once)

Every stream belongs to a **config**. The on-chain key is a `field`. Hash a human-readable name with `configNameToField`.

```ts
import { configNameToField } from "@zebec-network/aleo-stream-sdk";

const configName = await configNameToField("Acme_Payroll", network);

const config = {
  configName,
  admin: adminWallet.address,
  feeVault: adminWallet.address,
  withdrawer: withdrawerWallet.address, // auto-withdraw signer
  baseFee: 0,      // credits
  platformFee: 0,  // credits
};

await admin.initializeConfig(config, { priorityFee: 100_000 });

// Token id without `.aleo`. Testnet programs are prefixed `test_`.
await admin.setTokenWhitelisted(configName, "test_usdcx_stablecoin", true, {
  priorityFee: 100_000,
});
```

`initializeConfig` always sets `admin` to the executing wallet. Only that admin may later `updateConfig` or `setTokenWhitelisted`.

Supported stream tokens: `usdcx_stablecoin` and `usad_stablecoin`. On testnet the service prefixes `test_` on create.

### 4. Sign the stream fee (backend)

Create-stream verifies a Schnorr signature from the config admin over a `StreamTokenFee`. Keep the admin private key on the server; the frontend only receives `feeSignature`.

```ts
import { signStreamTokenFee, randomField } from "@zebec-network/aleo-stream-sdk";

const now = Math.floor(Date.now() / 1000);

const tokenFee = {
  config: configName,
  streamToken: "test_usdcx_stablecoin",
  streamFeeAmount: 0,          // in stream-token units
  expiry: now + 3600,
  nonce: `${await randomField(network)}field`,
};

const feeSignature = await signStreamTokenFee(
  adminPrivateKey, // server-side only
  tokenFee,
  network,
  6, // token decimals
);
```

Verify with `verifyStreamTokenFeeSignature(adminAddress, tokenFee, feeSignature, network)` before returning it to the client.

### 5. Create a stream (sender)

Pick public or private based on product requirements.

|             | Public                                 | Private                                             |
| ----------- | -------------------------------------- | --------------------------------------------------- |
| Funding     | Public token balance                   | Private token record + freeze-list Merkle proofs    |
| Visibility  | `Stream` and `StreamAnchor` are public | Tickets are private; `StreamAnchor` is still public |
| Listing     | `listPublicStreams(config)`            | `listPrivateStreams()`                              |
| Extra steps | `approveTokenPublic` first             | Records + compliance proofs (SDK fetches proofs)    |

Do **not** stream to the sender's own address.

```ts
import { randomField } from "@zebec-network/aleo-stream-sdk";

const decimals = 6;
const streamId = `${await randomField(network)}field`;
const now = Math.floor(Date.now() / 1000);

const params = {
  receiver: receiverAddress,
  streamId,
  amount: "10",
  startTime: now,
  duration: 86_400,
  isCancelable: true,
  isPausable: true,
  autoWithdrawable: false,
  withdrawFrequency: 60, // must be an allowed frequency (see below)
  startNow: true,        // on-chain start_time = create block time
  canTopup: true,
  // can_topup requires buffer > 0. Deposit is this amount, not `amount`.
  initialBufferAmount: "0.000001",
};
```

**Public create**

```ts
const payroll = await sender.programAddress();

await sender.approveTokenPublic(
  "test_usdcx_stablecoin",
  payroll,
  "100", // allowance covering fee + deposit + later topups
  decimals,
  { priorityFee: 100_000 },
);

const txId = await sender.createStreamPublic(
  params,
  "usdcx_stablecoin",
  decimals,
  config,
  tokenFee,
  feeSignature,
  { priorityFee: 100_000 },
);
```

**Private create**

```ts
const txId = await sender.createStreamPrivate(
  params,
  "usdcx_stablecoin",
  decimals,
  config,
  tokenFee,
  feeSignature,
  { priorityFee: 100_000 },
);
```

After create, poll `getStreamAnchor(streamId)` until it exists before any pause / topup / withdraw.

Allowed `withdrawFrequency` values (seconds): `60`, `120`, `3600`, `43200`, `86400`, `604800`, `1209600`, `2592000`, `7776000`, `15552000`, `31536000`.

### 6. Build dashboards (reads)

Wire list screens to these getters. They do not submit transactions.

**Employer / employee public list**

```ts
const streams = await sender.listPublicStreams(configName);
// each entry: streamId, direction ("outgoing" | "incoming"),
// stream (amounts in token units), anchor (deposited, withdrawn, paused, canceled)
```

**Private tickets for the connected wallet**

```ts
const privateStreams = await sender.listPrivateStreams();
```

**Single stream status** (progress bars, pause state)

```ts
const stream = await service.getStream(streamId);
const anchor = await service.getStreamAnchor(streamId);

const vestedRate = Number(stream.fullAmount) / Number(anchor.duration);
const elapsed =
  Math.floor(Date.now() / 1000) -
  Number(anchor.startTime) -
  Number(anchor.pausedInterval);
```

Other reads you will need:

| Method                                                     | Use in the app                          |
| ---------------------------------------------------------- | --------------------------------------- |
| `getStreamConfig(configName)`                              | Show admin, fees, withdrawer            |
| `isTokenWhitelisted(configName, token)`                    | Gate the create form                    |
| `getPublicTokenBalance(tokenProgramId, decimals)`          | Show spendable public balance           |
| `getPrivateTokenBalance(tokenProgramId, decimals)`         | Show spendable private balance          |
| `programAddress()`                                         | Spender to display for approve          |
| `getComplianceProofs("usdcx" \| "usad", address, network)` | Private create (also called internally) |

### 7. Lifecycle actions

Pass a **fresh** unix `timestamp` on every write. Finalize requires it near chain time; for `start_now` streams it must not be older than on-chain `start_time`.

```ts
const op = {
  streamId,
  timestamp: Math.floor(Date.now() / 1000),
};
```

| User action    | Public                                                | Private                                 | Signer                  |
| -------------- | ----------------------------------------------------- | --------------------------------------- | ----------------------- |
| Add funds      | `topupStreamPublic({ ...op, amount, tokenDecimals })` | `topupStreamPrivate(...)`               | Sender (`canTopup`)     |
| Pause / resume | `pauseResumeStreamPublic(op)`                         | `pauseResumeStreamPrivate(op)`          | Sender (`isPausable`)   |
| Claim vested   | `withdrawStreamPublic(op)`                            | `withdrawStreamPrivate(op)`             | Receiver                |
| Auto-claim     | `withdrawStreamAutoPublic(op, config)`                | `withdrawStreamAutoPrivate(op, config)` | Config `withdrawer`     |
| Stop stream    | `cancelStreamPublic(op)`                              | `cancelStreamPrivate(op)`               | Sender (`isCancelable`) |

Private methods accept an optional `ticket` plaintext. If omitted, the service scans records for ticket type `0` (sender), `1` (receiver), or `2` (withdrawer).

{% hint style="warning" %}
`cancel_stream_public` requires withdrawable ≤ deposited. After a 1-micro create buffer that usually means a successful topup first. Wait for `getStreamAnchor` to show the new `depositedAmount` / `withdrawnAmount` before cancel.
{% endhint %}

**Example: receiver withdraw (public)**

```ts
const before = await receiver.getStreamAnchor(streamId);

await receiver.withdrawStreamPublic(
  { streamId, timestamp: Math.floor(Date.now() / 1000) },
  { priorityFee: 100_000 },
);

// Poll until mapping catches up, then refresh the UI.
```

### 8. Auto-withdraw worker (optional)

If you set `autoWithdrawable: true` at create:

1. Wait at least one `withdrawFrequency` after `anchor.startTime` (minus `pausedInterval`).
2. Call auto-withdraw with the **config withdrawer** wallet, not the receiver.
3. Auto-withdraw fee in microcredits is `platformFee + (duration * baseFee) / frequency` (`computeAutoWithdrawalFee`).

```ts
await withdrawer.withdrawStreamAutoPublic(
  { streamId, timestamp: Math.floor(Date.now() / 1000) },
  config,
  { priorityFee: 100_000 },
);
```

## Token helpers

Public create and topup pull from the sender's public IARC-22 balance:

```ts
await sender.approveTokenPublic(token, spender, amount, decimals, options);
await sender.transferTokenPublic(token, decimals, amount, recipient, options);
await sender.transferTokenPrivateToPublic(token, decimals, amount, recipient, options);
```

`createStreamPublic` spends `streamFeeAmount + deposit` from the allowance (fee to `feeVault`, deposit into the program).

## Execute options

Every write accepts:

| Field         | Type        | Description                                                     |
| ------------- | ----------- | --------------------------------------------------------------- |
| `priorityFee` | `number?`   | Microcredits. Service default `100000`.                         |
| `privateFee`  | `boolean?`  | Pay the Aleo fee from a private credits record.                 |
| `feeRecord`   | `string?`   | Credits record plaintext when `privateFee` is true.             |
| `imports`     | `string[]?` | Extra program imports; omit unless you override token dispatch. |

## Data types (app-facing)

### `CreateStreamParams`

| Field                 | Type               | Description                                            |
| --------------------- | ------------------ | ------------------------------------------------------ |
| `receiver`            | `string`           | Aleo address. Cannot be the sender.                    |
| `streamId`            | `string \| bigint` | Random `field` (use `randomField`).                    |
| `amount`              | `string \| number` | Total stream amount in token units.                    |
| `startTime`           | `number`           | Unix seconds (ignored when `startNow` is true).        |
| `duration`            | `number`           | Duration in seconds.                                   |
| `isCancelable`        | `boolean`          | Sender may cancel.                                     |
| `isPausable`          | `boolean`          | Sender may pause/resume.                               |
| `autoWithdrawable`    | `boolean`          | Config withdrawer may auto-withdraw.                   |
| `withdrawFrequency`   | `number`           | Must be an allowed frequency.                          |
| `startNow`            | `boolean`          | Stamp `start_time` from the create block.              |
| `canTopup`            | `boolean`          | Sender may top up. Requires `initialBufferAmount > 0`. |
| `initialBufferAmount` | `string \| number` | Deposit at create when `canTopup` is true.             |

### `Config`

| Field         | Type               | Description                                    |
| ------------- | ------------------ | ---------------------------------------------- |
| `configName`  | `string \| bigint` | Field key in `stream_configs`.                 |
| `admin`       | `string`           | Admin address.                                 |
| `feeVault`    | `string`           | Receives the signed stream fee (stream token). |
| `withdrawer`  | `string`           | Auto-withdraw signer.                          |
| `baseFee`     | `string \| number` | Credits, used in auto-withdraw fee math.       |
| `platformFee` | `string \| number` | Credits, used in auto-withdraw fee math.       |

### `StreamTokenFee`

| Field             | Type               | Description                                              |
| ----------------- | ------------------ | -------------------------------------------------------- |
| `config`          | `string \| bigint` | Binds the signature to one config.                       |
| `streamToken`     | `string`           | Token program identifier (e.g. `test_usdcx_stablecoin`). |
| `streamFeeAmount` | `string \| number` | Fee in stream-token units.                               |
| `expiry`          | `number`           | Unix seconds after which the signature is invalid.       |
| `nonce`           | `string \| bigint` | Replay protection (`field`).                             |

### `StreamAnchor` (public accounting)

Use this for UI: `paused`, `canceled`, `depositedAmount`, `withdrawnAmount`, `startTime`, `duration`, `pausedInterval`, `isPublic`.

## Error handling

Handle these cases in the product:

| Situation                                       | What to show                                                           |
| ----------------------------------------------- | ---------------------------------------------------------------------- |
| `cannot create a stream to yourself`            | Pick a different receiver.                                             |
| `invalid receiver address`                      | Address checksum / network mismatch.                                   |
| `stream config not found`                       | Tenant was never initialized, or wrong hashed name.                    |
| Token not whitelisted                           | Admin must `setTokenWhitelisted`.                                      |
| `no unspent token record` / `No records found`  | Shield or fund private balance.                                        |
| Explorer status `rejected` after DPS `accepted` | Stale anchor — wait and retry with a fresh `getStreamAnchor`.          |
| Cancel rejected                                 | Withdrawable still greater than deposited; top up or wait for mapping. |

```ts
try {
  await sender.createStreamPublic(params, "usdcx_stablecoin", 6, config, tokenFee, feeSignature);
} catch (error) {
  const message = error instanceof Error ? error.message : String(error);
  if (message.includes("cannot create a stream to yourself")) {
    // form validation
  } else {
    console.error("Create failed:", message);
  }
}
```

## Network support

| Network | Status    | Program                     |
| ------- | --------- | --------------------------- |
| Testnet | Supported | `test_zebec_stream_v3.aleo` |
| Mainnet | Supported | `zebec_stream_v1.aleo`      |

REST default: `https://api.provable.com/v2`. Override with `host` on `ZebecStreamService`.

## Related

* [Streaming SDK overview](/developer-docs/sdks/streaming-sdk.md)
* [Solana Streaming SDK](/developer-docs/sdks/streaming-sdk/solana-streaming-sdk.md)
* [Stellar Streaming SDK](/developer-docs/sdks/streaming-sdk/stellar-streaming-sdk.md)
* [Enterprise Payroll streaming guide](/zebec-application-suite/enterprise-payroll/streaming-payroll.md)
* [Partner API overview](/developer-docs/partner-api.md)
