npm package: @streamflow/stream
Description: JavaScript SDK to interact with Streamflow protocol.
Latest version: 11.3.1

## JS SDK to interact with the Streamflow protocol.

This package allows you to `create`, `createMultiple`, `withdraw`, `cancel`, `topup`, `transfer`, `update` a token stream.

You can also `getOne` stream and `get` multiple streams.

## Table of Contents

- [Installation](#installation)
- [Documentation](#documentation)
- [Import SDK](#import-sdk)
- [Create StreamClient instance](#create-streamclient-instance)
  - [Chain-specific clients](#chain-specific-clients)
  - [Generic Stream Client](#generic-stream-client)
- [Chain-specific Parameters](#chain-specific-parameters)
- [Create a Vesting Stream](#create-a-vesting-stream)
  - [Streamflow App Vesting criteria](#streamflow-app-vesting-criteria)
  - [Example for creating a Vesting Stream](#example-for-creating-a-vesting-stream)
- [Create a Token Lock](#create-a-token-lock)
  - [Streamflow app token lock criteria](#streamflow-app-token-lock-criteria)
  - [Example for creating a token lock](#example-for-creating-a-token-lock)
- [Create a Price-based Stream (Aligned)](#create-a-price-based-stream-aligned)
  - [Example for creating a Price-based Vesting Stream](#example-for-creating-a-price-based-vesting-stream)
  - [Create a Price-based Lock](#create-a-price-based-lock)
  - [Fetching Aligned Stream Data](#fetching-aligned-stream-data)
- [General Stream creation](#general-stream-creation)
- [Create multiple streams at once](#create-multiple-streams-at-once)
- [Identifying created Streams](#identifying-created-streams)
- [Withdraw from stream](#withdraw-from-stream)
- [Automatic withdrawal](#automatic-withdrawal)
- [Topup stream](#topup-stream)
- [Transfer stream to another recipient](#transfer-stream-to-another-recipient)
- [Cancel stream](#cancel-stream)
- [Update a stream](#update-a-stream)
- [Get one stream by its ID](#get-one-stream-by-its-id)
- [Fetching unlocked amount](#fetching-unlocked-amount)
- [Reading withdrawn amount and remaining funds](#reading-withdrawn-amount-and-remaining-funds)
- [Get multiple streams for a specific wallet address](#get-multiple-streams-for-a-specific-wallet-address)
- [Search Solana Streams](#search-solana-streams)
- [Handling errors](#handling-errors)
- [Additional notes](#additional-notes)

---

## Installation

`npm i -s @streamflow/stream`

or

`yarn add @streamflow/stream`

## Documentation
API Documentation available here: [docs site →](https://streamflow-finance.github.io/js-sdk/)

## Import SDK

Most common imports:

```javascript
import { BN } from "bn.js";
import { getBN, getNumberFromBN } from "@streamflow/stream";
```

_Check the SDK for other types and utility functions._

## Create StreamClient instance

Before creating and manipulating streams, a Solana client instance must be created. All stream functions are methods on this instance.

### Solana client

```javascript
// Solana
import { StreamflowSolana } from "@streamflow/stream";
const solanaClient = new StreamflowSolana.SolanaStreamClient(
  "https://api.mainnet-beta.solana.com"
);
```

## Universal parameters

Each method requires some shared parameters. Here are the common patterns:
These are defined in:
- [Solana Types](./solana/types.ts) 

```javascript
// Solana parameters for Create 
const solanaCreateParams = {
  sender: wallet, // SignerWalletAdapter or Keypair of Sender account, can be only PublicKey if you are not using the client to initiate the signing and execution of the transaction
  isNative: false // [optional] Whether Stream should be paid with Solana native token (wSOL)
};

// Solana parameters for Withdraw, TopUp, Update, Cancel etc.
const solanaInteractParams = {
  invoker: wallet, // SignerWalletAdapter or Keypair signing the transaction, can be only PublicKey if you are not using the client to initiate the signing and execution of the transaction
  isNative: false // [optional] Whether Stream should be paid with Solana native token (wSOL)
};
```


> NOTE: All timestamp parameters are in seconds.

## Create a Vesting Stream

Vesting Streams are a type of Stream used to linearly unlock a given amount of tokens over a period of time. You can specify the amount, cliff, start time and releasePeriod along with other configuration that controls who can transfer or cancel the contract. For a Stream to be considered Vesting in the Streamflow App it has to match the criteria from the `isVesting` helper function (or `buildStreamType` returns `StreamType.Vesting`). The cliff amount must not be too close to the total amount and it must not be a Dynamic token lock.

For more details creating a Stream with arbitrary configuration see [General Stream creation](#general-stream-creation).
For creating multiple Streams at once see [Create Multiple Streams](#create-multiple-streams-at-once).

### Streamflow App Vesting criteria

The categorization criteria and other helper functions are defined in [contractUtils.ts](./common/contractUtils.ts)

```javascript
  return (
    !streamData.canTopup &&
    !isCliffCloseToDepositedAmount(streamData) &&
    !isDynamicLock(streamData.minPrice, streamData.maxPrice, streamData.minPercentage, streamData.maxPercentage)
  );
```

### Example for creating a Vesting Stream
The following code will create a Vesting Stream that unlocks 20% of the locked tokens at the start with the remaining 80% unlocked daily over a period of 2 weeks.

```javascript
const tokenDecimals = 9; 
const totalAmount = getBN(1000, tokenDecimals);
const cliffAmount = getBN(200, tokenDecimals); // optional

const day = 60 * 60 * 24; // in seconds
const twoWeeks = day * 14;

const remainingAmount = totalAmount.sub(cliffAmount);
const amountPerPeriod = remainingAmount.divn(twoWeeks); 

const createVestingParams: Types.ICreateStreamData = {
  recipient: "4ih00075bKjVg000000tLdk4w42NyG3Mv0000dc0M00", // Recipient address.
  tokenId: "DNw99999M7e24g99999999WJirKeZ5fQc6KY999999gK", // Token mint address.
  start: 1643363040, // In this case start time will be the time of unlock
  amount: totalAmount,
  period: day, // 1 day for daily unlocks
  cliff: 1643363040, // Cliff should match start
  cliffAmount: cliffAmount, // Amount unlocked at the "cliff" timestamp.
  amountPerPeriod: amountPerPeriod,
  name: "Transfer to Jane Doe.", // The stream name or subject.
  canTopup: false,  
};

// Using the client to trigger the transaction signing and execution
try {
  const { ixs, tx, metadata } = await client.create(createVestingParams, solanaCreateParams);
} catch (exception) {
  // handle exception
}

// Creating the transaction yourself
// note: in this case you do not have to pass a Signer or Keypair in solanaCreateParams, you can pass only the PublicKey
const { ixs } = await client.prepareCreateStreamInstructions(createVestingParams, solanaCreateParams)
```

## Create a Token Lock

A token lock is a Stream that has specific configuration making the unlock happen (almost) instantenously at a certain point in time. For a Stream to be considered a Token Lock in the Streamflow App it must match the criteria defined in the `isTokenLock` function (or for the `buildStreamType` type function to return `StreamType.Lock`). The Stream must not allow top-ups or auto-withdrawal and it can not be transferable by the sender or cancelable by either the sneder or the recipient. The `cliffAmount` must be equal to or greater than `depositedAmount.subn(1)`. Dynamic (price-based) locks conform to a different criteria.

### Streamflow app token lock criteria

The categorization criteria and other helper functions are defined in [contractUtils.ts](./common/contractUtils.ts)

```javascript
return (
  !streamData.canTopup &&
  !streamData.automaticWithdrawal &&
  !streamData.cancelableBySender &&
  !streamData.cancelableByRecipient &&
  !streamData.transferableBySender &&
  (isCliffCloseToDepositedAmount(streamData) ||
    isDynamicLock(streamData.minPrice, streamData.maxPrice, streamData.minPercentage, streamData.maxPercentage))
);

const isCliffCloseToDepositedAmount = (streamData: { depositedAmount: BN; cliffAmount: BN }): boolean => {
return streamData.cliffAmount.gte(streamData.depositedAmount.sub(new BN(1)));
```

### Example for creating a token lock
```javascript
const tokenDecimals = 9; 
const totalAmount = getBN(1000, tokenDecimals);
const cliffAmount = totalAmount.subn(1);

const createTokenLockParams: Types.ICreateStreamData = {
  recipient: "4ih00075bKjVg000000tLdk4w42NyG3Mv0000dc0M00", // Recipient address.
  tokenId: "DNw99999M7e24g99999999WJirKeZ5fQc6KY999999gK", // Token mint address.
  start: 1643363040, // In this case start time will be the time of unlock
  amount: totalAmount,
  period: 1, // 1 second
  cliff: 1643363040, // Cliff should match start
  cliffAmount: cliffAmount, // Amount unlocked at the "cliff" timestamp.
  amountPerPeriod: new BN(1), // Remaining "1" which is smallest token denominator will be unlocked in 1 second
  name: "Transfer to Jane Doe.", // The stream name or subject.
  // The settings below are only necessary if you wish the Stream to be labelled as a Token Lock on the Streamflow App (or if you use the smae criteria for categorization of Streams)
  canTopup: false, 
  cancelableBySender: false, 
  cancelableByRecipient: false, 
  transferableBySender: false, 
  transferableByRecipient: false, 
};

// Using the client to trigger the transaction signing and execution
try {
  const { ixs, tx, metadata } = await client.create(createTokenLockParams, solanaCreateParams);
} catch (exception) {
  // handle exception
}

// Creating the transaction yourself
// note: in this case you do not have to pass a Signer or Keypair in solanaCreateParams, you can pass only the PublicKey
const { ixs } = await client.prepareCreateStreamInstructions(createTokenLockParams, solanaCreateParams)
```

## Create a Price-based Stream (Aligned)

Price-based Streams (also known as Aligned or Dynamic Streams) are a type of Stream where the unlock rate dynamically adjusts based on the price of a token as reported by a price oracle. The unlock percentage scales between `minPercentage` and `maxPercentage` based on whether the current price is at or below `minPrice` or at or above `maxPrice`. This allows for vesting schedules that accelerate or decelerate based on token price performance.

Price-based Streams require all the standard stream parameters plus additional price-based configuration parameters. The unlock rate is recalculated periodically based on the oracle price, making the vesting schedule responsive to market conditions.

Streamflow's custom oracles can be fetched through the Oracle API 
- Endpoint -> `https://oracle-api-public.streamflow.finance/oracle/{mint}`
- [API Docs](https://oracle-api-public.streamflow.finance/docs)

For more details creating a Stream with arbitrary configuration see [General Stream creation](#general-stream-creation).

### Example for creating a Price-based Vesting Stream

The following code will create a Price-based Stream that adjusts its unlock rate based on token price. When the price is at or below `minPrice`, it unlocks at `minPercentage` rate. When the price is at or above `maxPrice`, it unlocks at `maxPercentage` rate. For prices between these bounds, the unlock rate scales proportionally.

```javascript
const tokenDecimals = 9; 
const totalAmount = getBN(1000, tokenDecimals);
const cliffAmount = getBN(100, tokenDecimals);

const day = 60 * 60 * 24; // in seconds
const twoWeeks = day * 14;

const mint = "DNw99999M7e24g99999999WJirKeZ5fQc6KY999999gK"
const remainingAmount = totalAmount.sub(cliffAmount);
const baseAmountPerPeriod = remainingAmount.divn(twoWeeks);

const priceOracleAddress = await fetch(`https://oracle-api.streamflow.finance/oracle/${mint}`).then(res => res.json()).then(data => data.address);

const createPriceBasedParams: Types.ICreateAlignedStreamData = {
  recipient: "4ih00075bKjVg000000tLdk4w42NyG3Mv0000dc0M00", // Recipient address.
  tokenId: mint, // Token mint address.
  start: 1643363040, // Timestamp (in seconds) when the stream starts.
  amount: totalAmount,
  period: day, // 1 day for daily unlock updates
  cliff: 1643363040, // Cliff timestamp in seconds.
  cliffAmount: cliffAmount, // Amount unlocked at the "cliff" timestamp.
  amountPerPeriod: baseAmountPerPeriod, // Base release rate (will be adjusted based on price).
  name: "Price-based Vesting for Jane Doe.", // The stream name or subject.
  transferableBySender: false, // not possible for aligned streams
  automaticWithdrawal: false, // not possible for aligned streams
  // Price-based configuration parameters - NOTE: all of these must be present in the creation order for the created Stream to be price-based
  minPrice: 0.05, // Minimum price threshold. If current price <= minPrice, minPercentage is used.
  maxPrice: 1.0, // Maximum price threshold. If current price >= maxPrice, maxPercentage is used.
  minPercentage: 1, // Minimum unlock percentage (in basis points, e.g., 1 = 0.01%). Used when price <= minPrice.
  maxPercentage: 1000, // Maximum unlock percentage (in basis points, e.g., 1000 = 10%). Used when price >= maxPrice.
  // If either of these are omitted or oracleType is set to "none" the resulting Stream will behave like a regular Stream
  oracleType: "test", //  "test" is passed if you are using Streamflow's price oracles, you can also pass "pyth" if you are passing a Pyth price oracle to priceOracle
  priceOracle: priceOracleAddress, // Address of the price oracle, either a Streamflow oracle address, or a Pyth oracle address.
  skipInitial: false, //  [optional] Whether to skip initial unlock amount update when the stream starts. Defaults to false.
  tickSize: 1, // [optional] Tick size for percentage calculations when price is between minPrice and maxPrice. Defaults to 1.
};

try {
  const { ixs, tx, metadata } = await client.create(createPriceBasedParams, solanaCreateParams);
} catch (exception) {
}

const { ixs } = await client.prepareCreateStreamInstructions(createPriceBasedParams, solanaCreateParams)
```
## Create a Price-based Lock

You can also create a Stream that behaves similar to a token Lock and unlocks at a specific price. There are some technical limitations that cause a small portion of the tokens to be unlocked over time that depends on the contract amount and number of decimals, but for most cases the time it would take for the whole contract to be unlocked would be measured in decades or even hundreds of years. You can achieve this with the following settings:

```javascript
const targetPrice = 100; // Price at which you want tokens to unlock
const minPrice = targetPrice - 1;
const maxPrice = targetPrice;
const minPercentage = 0;
const maxPercentage = 100;
const totalAmount = new BN(1000000);
const amountPerPeriod = totalAmount;
const cliffAmount = new BN(0); // No cliff for price-based lock

const createPriceBasedParams: Types.ICreateAlignedStreamData = {
  recipient: "4ih00075bKjVg000000tLdk4w42NyG3Mv0000dc0M00", // Recipient address.
  tokenId: mint, // Token mint address.
  start: 1643363040, // Timestamp (in seconds) when the stream starts.
  amount: totalAmount, 
  period: 30, // 30 seconds so in case the target price is reached, the oracle has 30 seconds to fetch the price and update the contract release rate
  cliff: 1643363040, // Cliff timestamp in seconds (should match start for price-based lock).
  cliffAmount: cliffAmount, // Amount unlocked at the "cliff" timestamp.
  amountPerPeriod: amountPerPeriod, // Will be updated to 1 as long as price below targetPrice,
  // meaning that tokens are unlocked at rate of 1 smallest denominator of the token per each period (if token has 9 decimals that is 0.000000001 token every 30 seconds)
  name: "Price-based Lock for Jane Doe.",
  transferableBySender: false, // not possible for aligned streams
  automaticWithdrawal: false, // not possible for aligned streams
  // Price-based configuration parameters - NOTE: all of these must be present in the creation order for the created Stream to be price-based
  minPrice, // Minimum price threshold. If current price <= minPrice, minPercentage is used.
  maxPrice, // Maximum price threshold. If current price >= maxPrice, maxPercentage is used.
  minPercentage, // Minimum unlock percentage (in basis points, e.g., 1 = 0.01%). Used when price <= minPrice.
  maxPercentage, // Maximum unlock percentage (in basis points, e.g., 1000 = 10%). Used when price >= maxPrice.
  // If either of these are omitted or oracleType is set to "none" the resulting Stream will behave like a regular Stream
  oracleType: "test", //  "test" is passed if you are using Streamflow's price oracles, you can also pass "pyth" if you are passing a Pyth price oracle to priceOracle
  priceOracle: priceOracleAddress, // Address of the price oracle, either a Streamflow oracle address, or a Pyth oracle address.
  skipInitial: false, // In this case it should not be set to true 
  // These parameters make sense for a price-based token lock
  floorPrice: 20, // lower bound targetPrice - at or below this price tokens will be unlocked
  expiryTime: 1643364640, // If the targetPrice has not been reached by this point in time, the tokens will be unlocked
  expiryPercentage: maxPercentage, // The percentage unlock rate of tokens after expiryTime, it should be maxPercentage or 100

};
```

### Fetching Aligned Stream Data

When you fetch a price-based stream using `getOne()`, `get()` or `searchStreams()`, it automatically returns an `AlignedContract` instance if the stream is price-based. You can check if a stream is aligned and access its price-based properties:

```javascript
import { isAligned, getNumberFromBN } from "@streamflow/stream";

const tokenDecimals = 9; // Token decimals for the stream's token

const stream = await client.getOne({
  id: "AAAAyotqTZZMAAAAmsD1JAgksT8NVAAAASfrGB5RAAAA",
});

// Check if the stream is price-based (aligned)
if (isAligned(stream)) {
  console.log("Min Price:", stream.minPrice);
  console.log("Max Price:", stream.maxPrice);
  console.log("Min Percentage:", stream.minPercentage);
  console.log("Max Percentage:", stream.maxPercentage); 
  console.log("Tick Size:", stream.tickSize);
  console.log("Oracle Type:", stream.oracleType); // "test", "pyth", or "none"
  console.log("Price Oracle Address:", stream.priceOracle);
  console.log("Initial Price:", stream.initialPrice);
  console.log("Last Price:", stream.lastPrice);
  console.log("Last Amount Update Time:", stream.lastAmountUpdateTime);
  console.log("Initial Amount Per Period:", getNumberFromBN(stream.initialAmountPerPeriod, tokenDecimals));
  console.log("Expiry Time:", stream.expiryTime);
  console.log("Expiry Percentage:", stream.expiryPercentage);
  console.log("Floor Price:", stream.floorPrice);
  console.log("Proxy Address:", stream.proxyAddress);
}
```

All standard stream properties (like `amount`, `withdrawnAmount`, `unlocked()`, etc.) are also available on aligned streams, as `AlignedContract` extends the base `Contract` class.

> **Note:** Price-based Streams use a separate proxy program (Aligned Unlocks) to manage the dynamic unlock logic. The unlock rate is recalculated periodically based on the oracle price. Automatic withdrawal and rate updates are not supported for price-based streams.

## General Stream creation

This covers arbitrary configuration for creating a Stream - depending on the configuration it may fall into different categories in the Streamflow App if the Streamflow protocol is used. For more details on automatic withdrawal see [Automatic Withdrawal](#automatic-withdrawal).

```javascript
const createStreamParams: Types.ICreateStreamData = {
  recipient: "4ih00075bKjVg000000tLdk4w42NyG3Mv0000dc0M00", // Recipient address.
  tokenId: "DNw99999M7e24g99999999WJirKeZ5fQc6KY999999gK", // Token mint address.
  start: 1643363040, // Timestamp (in seconds) when the stream/token vesting starts.
  amount: getBN(100, 9), // depositing 100 tokens with 9 decimals mint.
  period: 1, // Time step (period) in seconds per which the unlocking occurs.
  cliff: 1643363160, // Vesting contract "cliff" timestamp in seconds.
  cliffAmount: new BN(10), // Amount unlocked at the "cliff" timestamp.
  amountPerPeriod: getBN(5, 9), // Release rate: how many tokens are unlocked per each period.
  name: "Transfer to Jane Doe.", // The stream name or subject.
  canTopup: false, // Whether additional tokens can be deposited after creation, setting to FALSE will effectively create a vesting contract.
  canUpdateRate: false, // settings to TRUE allows sender to update amountPerPeriod
  cancelableBySender: true, // Whether or not sender can cancel the stream.
  cancelableByRecipient: false, // Whether or not recipient can cancel the stream.
  transferableBySender: true, // Whether or not sender can transfer the stream.
  transferableByRecipient: false, // Whether or not recipient can transfer the stream.
  automaticWithdrawal: true, // Whether or not a 3rd party (e.g. cron job, "cranker") can initiate a token withdraw/transfer.
  withdrawalFrequency: 10, // Relevant when automatic withdrawal is enabled. If greater than 0 our withdrawor will take care of withdrawals. If equal to 0 our withdrawor will skip, but everyone else can initiate withdrawals.
  partner: undefined, //  (optional) Partner's wallet address (string | undefined).
};

try {
  const { ixs, tx, metadata } = await client.create(createStreamParams, solanaCreateParams);
} catch (exception) {
  // handle exception
}
```

## Create multiple streams at once

```javascript
const recipients = [
  {
    recipient: "4ih00075bKjVg000000tLdk4w42NyG3Mv0000dc0M00", // Recipient address.
    amount: getBN(100, 9), // depositing 100 tokens with 9 decimals mint.
    name: "January Payroll", // The stream name/subject.
    cliffAmount: getBN(10, 9), // amount released on cliff for this recipient
    amountPerPeriod: getBN(1, 9), //amount released every specified period epoch
  },
];
const createMultiStreamsParams: ICreateMultipleStreamData = {
  recipients: recipients, // Array of recipient objects.
  tokenId: "DNw99999M7e24g99999999WJirKeZ5fQc6KY999999gK", // Token mint address.
  start: 1643363040, // Timestamp (in seconds) when the stream/token vesting starts.
  period: 1, // Time step (period) in seconds per which the unlocking occurs.
  cliff: 1643363160, // Vesting contract "cliff" timestamp in seconds.
  canTopup: false, // setting to FALSE will effectively create a vesting contract.
  cancelableBySender: true, // Whether or not sender can cancel the stream.
  cancelableByRecipient: false, // Whether or not recipient can cancel the stream.
  transferableBySender: true, // Whether or not sender can transfer the stream.
  transferableByRecipient: false, // Whether or not recipient can transfer the stream.
  automaticWithdrawal: true, // Whether or not a 3rd party (e.g. cron job, "cranker") can initiate a token withdraw/transfer.
  withdrawalFrequency: 10, // Relevant when automatic withdrawal is enabled. If greater than 0 our withdrawor will take care of withdrawals. If equal to 0 our withdrawor will skip, but everyone else can initiate withdrawals.
  partner: undefined, //  (optional) Partner's wallet address (string | undefined).
};

try {
  const { txs } = await client.createMultiple(createMultiStreamsParams, solanaCreateParams);
} catch (exception) {
  // handle exception
}
```

Please note that transaction fees for the scheduled transfers are paid upfront by the stream creator (sender).

## Identifying created Streams

All Stream Clients return `ICreateResult` object (`createMultiple` returns an Array) that has the following structure

```javascript
interface ICreateResult {
  ixs: (TransactionInstruction | TransactionPayload)[];
  txId: string;
  metadataId: MetadataId;
}
```

`metadataId` is the id of the created stream.

## Withdraw from stream

To fetch the unlocked amount from a Stream see [Fetching unlocked amount](#fetching-unlocked-amount)

```javascript
const withdrawStreamParams: Types.IWithdrawData = {
  id: "AAAAyotqTZZMAAAAmsD1JAgksT8NVAAAASfrGB5RAAAA", // Identifier (address) of a stream to be withdrawn from.
  amount: getBN(100, 9), // Requested amount to withdraw. If stream is completed, the whole amount will be withdrawn.
};

try {
  const { ixs, tx } = await client.withdraw(withdrawStreamParams, solanaInteractParams);
} catch (exception) {
  // handle exception
}
```

## Automatic withdrawal

This configuration controls whether automatic withdrawal are enabled for a Stream. `automaticWithdrawal` enables or disables this functionality. If set to `true` it will be possible for 3rd parties to withdraw the Stream on the recipient's behalf. This is also controlled by `withdrawalFrequency`. Setting `withdrawalFrequency` to `>0` will cause our own withdrawor to automatically withdraw funds to the recipient's wallet with the specifed frequency. Passing `0` will make our withdrawor ignore the Stream for auto-withdrawing, but it will be possible for others to initiate the witdrawal process.

## Topup stream

```javascript
const topupStreamParams: ITopUpData = {
  id: "AAAAyotqTZZMAAAAmsD1JAgksT8NVAAAASfrGB5RAAAA", // Identifier (address) of a stream to be topped up.
  amount: getBN(100, 9), // Specified amount to topup (increases deposited amount).
};

// Note: You can add isNative: true for wSOL streams to the solanaInteractParams
try {
  const { ixs, tx } = await client.topup(topupStreamParams, solanaInteractParams);
} catch (exception) {
  // handle exception
}
```

## Transfer stream to another recipient

```javascript
const data: ITransferData = {
  id: "AAAAyotqTZZMAAAAmsD1JAgksT8NVAAAASfrGB5RAAAA", // Identifier (address) of the stream to be transferred
  newRecipient: "99h00075bKjVg000000tLdk4w42NyG3Mv0000dc0M99", // Identifier (address) of a stream to be transferred.
};

try {
  const { tx } = await client.transfer(data, solanaInteractParams);
} catch (exception) {
  // handle exception
}
```

## Cancel stream

```javascript
const cancelStreamParams: ICancelData = {
  id: "AAAAyotqTZZMAAAAmsD1JAgksT8NVAAAASfrGB5RAAAA", // Identifier of a stream to be canceled.
};

try {
  const { ixs, tx } = await client.cancel(cancelStreamParams, solanaInteractParams);
} catch (exception) {
  // handle exception
}
```

## Update a stream

```javascript
const updateStreamParams: IUpdateData = {
  id: "AAAAyotqTZZMAAAAmsD1JAgksT8NVAAAASfrGB5RAAAA", // Identifier of a stream to update.
  enableAutomaticWithdrawal: true,  // [optional], allows to enable AW if it wasn't, disable is not possible
  withdrawFrequency: 60,  // [optional], allows to update withdrawal frequency, may result in additional AW fees
  amountPerPeriod: getBN(10, 9),  // [optional], allows to update release amount effective on next unlock
}

try {
  const { ixs, tx } = await client.update(updateStreamParams, solanaInteractParams);
} catch (exception) {
  // handle exception
}
```

## Get one stream by its ID

```javascript
const data: IGetOneData = {
  id: "AAAAyotqTZZMAAAAmsD1JAgksT8NVAAAASfrGB5RAAAA", // Identifier of a stream
};

try {
  const stream = await client.getOne(data);
} catch (exception) {
  // handle exception
}
```

## Fetching unlocked amount

```javascript
const stream = await client.getOne({
  id: "AAAAyotqTZZMAAAAmsD1JAgksT8NVAAAASfrGB5RAAAA",
});

const unlocked = stream.unlocked(tsInSeconds); // BN amount unlocked at the tsInSeconds
console.log(getNumberFromBN(unlocked, 9)); 
```

- Note: unlocked amount is determined based on configuration set on creation, no dynamic data is involved.

## Reading withdrawn amount and remaining funds

```javascript
const stream = await client.getOne({
  id: "AAAAyotqTZZMAAAAmsD1JAgksT8NVAAAASfrGB5RAAAA",
});
const withdrawn = stream.withdrawnAmount; // bn amount withdrawn already
console.log(getNumberFromBN(withdrawn, 9));
const remaining = stream.remaining(9); // amount of remaining funds
console.log(remaining);
```

## Get multiple streams for a specific wallet address

```javascript
const data: IGetAllData = {
  address: "99h00075bKjVg000000tLdk4w42NyG3Mv0000dc0M99",
  type: StreamType.All, // StreamType.Vesting, StreamType.Lock
  direction: StreamDirection.All, // StreamDirection.Outgoing, StreamDirection.Incoming
};

try {
  const streams = client.get(data);
} catch (exception) {
  // handle exception
}
```

## Search Solana Streams

Solana RPC is pretty rich in what data it can allow to filter by, so we expose a separate `searchStreams` method on `SolanaStreamClient`:

```javascript
// All parameters are optional, so in theory you can just fetch all Streams
const params = {
  mint: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
  sender: "AKp8CxGbhsrsEsCFUtx7e3MWyW7SWi1uuSqv6N4BEohJ",
  recipient: "9mqcpDjCHCPmttJp2t477oJ71NdAvJeSus8BcCrrvwy5",
}
// nativeStreamClient is exposed on a GenericStreamClient, you can also use SolanaStreamClient directly
// Return an Array of objects {publicKey: PublicKey, account: Stream}
const streams = await client.nativeStreamClient.searchStreams(params);
```

## Handling errors

`GenericStreamClient` wraps all errors when making on-chain calls with `ContractError` error class:
- this class inherits original traceback
- error may optionally contain `contractErrorCode` property that can be further mapped to a specific **Contract** error
- for `createMultiple` method errors are wrapped individually for every recipient address
- please check documentation for common enums `ContractErrorCode` and `SolanaContractErrorCode` to see short description for each error

A public map of protocol errors is available [here](https://streamflow.notion.site/Streamflow-protocol-docs-0accad86d5c44e5db84fd4fb49b8ff54).

## Additional notes

Streamflow protocol program IDs

| Solana  |                                              |
| ------- | -------------------------------------------- |
| Devet   | HqDGZjaVRXJ9MGRQEw7qDc2rAr6iH1n1kAQdCZaCMfMZ |
| Mainnet | strmRqUCoQUgGUan5YhzUZa6KqdzwX5L6FpUxfmKg5m  |

**All BN amounts are denominated in their smallest units.**

E.g, if the amount is 1 SOL than this amount in lamports is `1000 \* 10^9 = 1_000_000_000.`

And `new BN(1_000_000_000)` is used.

Use `getBN` and `getNumberFromBN` utility functions for conversions between `BN` and `Number` types.

`getBN(1, 9)` is equal to `new BN(1_000_000_000)` 

`getNumberFromBN(new BN(1_000_000_000), 9)` will return 1