Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions lerna.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"packages": ["packages/*"],
"version": "11.0.5",
"packages": [
"packages/*"
],
"version": "11.1.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"command": {
"run": {
Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@streamflow/common",
"version": "11.0.5",
"version": "11.1.0",
"description": "Common utilities and types used by streamflow packages.",
"homepage": "https://github.com/streamflow-finance/js-sdk/",
"type": "module",
Expand Down
5 changes: 4 additions & 1 deletion packages/common/solana/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,11 @@ export class TransactionFailedError extends Error {
}
}

export interface ITransactionResult {
export interface IPrepareResult {
ixs: TransactionInstruction[];
}

export interface ITransactionResult extends IPrepareResult {
txId: string;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/distributor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@streamflow/distributor",
"version": "11.0.5",
"version": "11.1.0",
"description": "JavaScript SDK to interact with Streamflow Airdrop protocol.",
"homepage": "https://github.com/streamflow-finance/js-sdk/",
"main": "./dist/cjs/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@streamflow/eslint-config",
"version": "11.0.5",
"version": "11.1.0",
"description": "ESLint configuration for Streamflow protocol.",
"homepage": "https://github.com/streamflow-finance/js-sdk/",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/launchpad/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@streamflow/launchpad",
"version": "11.0.5",
"version": "11.1.0",
"description": "JavaScript SDK to interact with Streamflow Launchpad protocol.",
"homepage": "https://github.com/streamflow-finance/js-sdk/",
"main": "./dist/cjs/index.cjs",
Expand Down
47 changes: 44 additions & 3 deletions packages/staking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This protocol is the complex of several programs that ensure flexibility and acc

aforementioned programs are:
- Stake Pools Program
- Reward Pools Program
- Reward Pools Program (fixed and dynamic variants)
- Fee Management Program (for streamflow usage, non-required and omitted from further docs)


Expand Down Expand Up @@ -338,6 +338,45 @@ Client also exposes methods to group staking/unstaking with reward pool actions.
> [!Note]
> Transactions can become quite large if you have many rewards pools, that can make it impossible to do all actions in 1 transaction - in this case please stick with `prepare` methods to build custom instructions and execute them with `execute`.

### Auto-Unstake

Stake pools can be configured with `autoUnstake: true` (via `createPoolV2`) to automatically unstake entries once their lock duration expires.
- When auto-unstake is enabled, a worker will unstake on the user's behalf after the lock period ends;
- if the stake entry has an associated reward entry from a fixed reward pool, the worker will also claim rewards during the auto-unstake process.

This feature works well with `maxTotalStakeCumulative` — together they allow stake pool creators to have a predictable amount of tokens needed in the reward pool. Without auto-unstake, rewards continue to accumulate past unlock time, which can drain a reward pool unexpectedly.

```typescript
const { metadataId: stakePoolPda } = await client.createStakePool({
maxWeight: multiplier,
maxDuration,
minDuration,
mint: MINT_ADDRESS,
permissionless: false,
nonce: 0,
autoUnstake: true,
maxTotalStakeCumulative: new BN(1_000_000_000), // cap total stake
}, extParams);
```

### Fund Delegate

Fund Delegate allows configuring automated periodic top-ups of a dynamic reward pool. The delegate account holds tokens that a worker uses to fund the reward pool on a schedule. Users transfer tokens to the delegate's token account, and the worker funds the pool according to the configured schedule (start time, period, and expiry).

```typescript
const rewardPool = /* dynamic reward pool address */;

// Create a fund delegate with a schedule
const { tokenAccount } = await client.createFundDelegate({
rewardPool,
startTs: new BN(Math.floor(Date.now() / 1000)), // start now
period: new BN(86400), // fund every 24 hours
expiryTs: new BN(Math.floor(Date.now() / 1000) + 86400 * 30), // expire in 30 days
}, extParams);
```

After creating the delegate, transfer reward tokens to the `tokenAccount`, which is an ATA for the fund delegate PDA address. The worker will periodically fund the reward pool from these tokens according to the schedule.

### Set Token Metadata

SolanaStakingClient also exposes original IDL of all programs, so you can use some additional instructions, that are not wrapped by the client. Currently there is no method to update Token Metadata of the Staking Mint that stakers get in return for their stake, but you can call the instructions from the original IDL like so:
Expand Down Expand Up @@ -372,10 +411,12 @@ Streamflow Staking protocol program IDs
| Solana | |
| ------- | -------------------------------------------- |
| Staking Pools Mainnet | STAKEvGqQTtzJZH6BWDcbpzXXn2BBerPAgQ3EGLN2GH |
| Reward Pools Mainnet | RWRDdfRbi3339VgKxTAXg4cjyniF7cbhNbMxZWiSKmj |
| Reward Pools (fixed) Mainnet | RWRDdfRbi3339VgKxTAXg4cjyniF7cbhNbMxZWiSKmj |
| Reward Pools (dynamic) Mainnet | RWRDyfZa6Rk9UYi85yjYYfGmoUqffLqjo6vZdFawEez |
| ---- | --- |
| Staking Pools Devnet | STAKEvGqQTtzJZH6BWDcbpzXXn2BBerPAgQ3EGLN2GH |
| Reward Pools Devnet | RWRDdfRbi3339VgKxTAXg4cjyniF7cbhNbMxZWiSKmj |
| Reward Pools (fixed) Devnet | RWRDdfRbi3339VgKxTAXg4cjyniF7cbhNbMxZWiSKmj |
| Reward Pools (dynamic) Devnet | RWRDyfZa6Rk9UYi85yjYYfGmoUqffLqjo6vZdFawEez |

### IDLs
For further details you can consult with IDLs of protocols available at:
Expand Down
2 changes: 1 addition & 1 deletion packages/staking/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@streamflow/staking",
"version": "11.0.5",
"version": "11.1.0",
"description": "JavaScript SDK to interact with Streamflow Staking protocol.",
"homepage": "https://github.com/streamflow-finance/js-sdk/",
"main": "./dist/cjs/index.cjs",
Expand Down
44 changes: 40 additions & 4 deletions packages/staking/solana/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { type StakePool as StakePoolProgramType } from "./descriptor/stake_pool.
import {
deriveConfigPDA,
deriveFeeValuePDA,
deriveFundDelegatePDA,
deriveRewardPoolPDA,
deriveRewardVaultPDA,
deriveStakeEntryPDA,
Expand All @@ -55,6 +56,9 @@ import type {
ClaimRewardPoolArgs,
CloseRewardEntryArgs,
CloseStakeEntryArgs,
CreateFundDelegateArgs,
CreateFundDelegatePrepareResult,
CreateFundDelegateResult,
CreateRewardEntryArgs,
CreateRewardPoolArgs,
CreateStakePoolArgs,
Expand Down Expand Up @@ -315,10 +319,7 @@ export class SolanaStakingClient {
* @param data - enriched stake params with an array of reward pools
* @param extParams - parameter required for transaction execution
*/
async stakeAndCreateEntries(
data: StakeAndCreateEntriesArgs,
extParams: IInteractExt,
): Promise<ITransactionResult> {
async stakeAndCreateEntries(data: StakeAndCreateEntriesArgs, extParams: IInteractExt): Promise<ITransactionResult> {
const { ixs } = await this.prepareStakeAndCreateEntriesInstructions(data, extParams);
const { signature } = await this.execute(ixs, extParams);

Expand Down Expand Up @@ -743,6 +744,41 @@ export class SolanaStakingClient {
return { ixs: [instruction] };
}

async createFundDelegate(data: CreateFundDelegateArgs, extParams: IInteractExt): Promise<CreateFundDelegateResult> {
const { ixs, tokenAccount } = await this.prepareCreateFundDelegateInstructions(data, extParams);
const { signature } = await this.execute(ixs, extParams);

return {
ixs,
txId: signature,
tokenAccount,
};
}

async prepareCreateFundDelegateInstructions(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since it's publicly accessible add an explicit return type

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one more thing, some checks would be nice here since the protocol has these

startTs must not be in the past
period must be >= 1 day and <= expiry duration
expiryTs must be after startTs

Copy link
Collaborator Author

@Yolley Yolley Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But all these checks are done on the protocol side. And we have some error extraction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking to make it more dev friendly since this is public so we don't get unnecessary questions

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And with typing the prepare it would probably require a full refactoring - we never typed prepare... method. 🌚

I've added the interface, only for this method now tho.

{ rewardPool, startTs, period, expiryTs, tokenProgramId = TOKEN_PROGRAM_ID }: CreateFundDelegateArgs,
extParams: IInteractExt,
): Promise<CreateFundDelegatePrepareResult> {
const { rewardPoolDynamicProgram } = this.programs;
const authority = extParams.invoker.publicKey;
invariant(authority, "Undefined invoker publicKey");
const rewardPoolPk = pk(rewardPool);
const rewardPoolData = await rewardPoolDynamicProgram.account.rewardPool.fetch(rewardPoolPk);
const mint = rewardPoolData.mint;
const fundDelegate = deriveFundDelegatePDA(rewardPoolDynamicProgram.programId, rewardPoolPk);
const tokenAccount = getAssociatedTokenAddressSync(mint, fundDelegate, true, pk(tokenProgramId));

const instruction = await rewardPoolDynamicProgram.methods
.createFundDelegate(startTs, period, expiryTs)
.accounts({
rewardPool: rewardPoolPk,
tokenProgram: tokenProgramId,
})
.instruction();

return { ixs: [instruction], tokenAccount };
}

async updateRewardPool(data: UpdateRewardPoolArgs, extParams: IInteractExt) {
const { ixs } = await this.prepareUpdateRewardPoolInstructions(data, extParams);
const { signature } = await this.execute(ixs, extParams);
Expand Down
2 changes: 2 additions & 0 deletions packages/staking/solana/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export const REWARD_VAULT_PREFIX = Buffer.from("reward-vault", "utf-8");
export const REWARD_ENTRY_PREFIX = Buffer.from("reward-entry", "utf-8");
export const CONFIG_PREFIX = Buffer.from("config", "utf-8");
export const FEE_VALUE_PREFIX = Buffer.from("fee-value", "utf-8");
export const FUND_DELEGATE_PREFIX = Buffer.from("fund-delegate", "utf-8");
export const STREAMFLOW_TREASURY_PUBLIC_KEY = new PublicKey("5SEpbdjFK5FxwTvfsGMXVQTD2v4M2c5tyRTxhdsPkgDw");
export const WORKER_PUBLIC_KEY = new PublicKey("wdrwhnCv4pzW8beKsbPa4S2UDZrXenjg16KJdKSpb5u");

export const ANCHOR_DISCRIMINATOR_OFFSET = 8;
export const STAKE_ENTRY_STAKE_POOL_OFFSET = ANCHOR_DISCRIMINATOR_OFFSET + 4;
Expand Down
Loading
Loading