Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staking SDK #199

Merged
merged 10 commits into from
Sep 20, 2024
Merged
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
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

. "$(dirname -- "$0")/_/husky.sh"

npm run lint
pnpm lint --parallel
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"printWidth": 110,
"useTabs": false
}
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
}
],
"cSpell.words": [
"Streamflow"
"permissionless",
"Solana",
"Streamflow",
"Unstake"
],
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.preferences.importModuleSpecifierEnding": "js",
Expand Down
5 changes: 5 additions & 0 deletions packages/staking/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": [
"@streamflow/eslint-config"
]
}
4 changes: 4 additions & 0 deletions packages/staking/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*
!dist/**/*
!package.json
!README.md
2 changes: 2 additions & 0 deletions packages/staking/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
coverage
216 changes: 216 additions & 0 deletions packages/staking/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# Streamflow Staking

## JS SDK to interact with Streamflow Staking.

This package allows you to
- `create staking pools and rewards pools`;
- `claim rewards`;
- `stake`;
- `unstake`;
- `fund rewards pools`;

with the Streamflow Staking protocol.

This protocol is the complex of several programs which ensures flexibility and accountability for stakers and respective incentives for them.

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


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

The Stake Pool Program entities:
1. Stake Pool
2. Stake Entry (PDA which stores info about current stakes, durations and rewards structure)

The Reward Pool Program entities:
1. Reward Pool (must has a back-reference to a stake pool)
2. Reward Entry (PDA stores claims information and time-bound params)

1 Stake Pool can have N Reward Pools.

> [!NOTE]
> There is a limitation of 255 Reward Pools per a single token mint for a stake pool.


## Installation

Install the sdk with npm

```npm
npm install @streamflow/staking
```
```yarn
yarn install @streamflow/staking
```
```pnpm
pnpm add @streamflow/staking
```

## Usage/Examples

#### Create a client
```typescript
const client = new SolanaStakingClient({
clusterUrl: "https://api.mainnet-beta.solana.com",
cluster: ICluster.Mainnet
});
```

> [!WARNING]
> All operations expect ATAs to be created at the moment of execution and don't add these instructions.
> - Stake - staker's ATAs for stake mint and stake mint (see deriveStakeMintPDA fn)
> - Withdraw/Unstake - staker's ATAs for stake mint and stake mint (see deriveStakeMintPDA fn)
> - Claim rewards - staker's ATAs for reward mint
> - Fund Reward Pool - signer creates Streamflow Treasury's ATA for holding fee if defined

#### Read operations
```typescript

await client.searchStakePools({ mint, creator }) // returns results of lookup, `mint` and `creator` both optional. Omit the argument to get all pools

await client.searchStakeEntries({ payer, stakePool }) // returns all stake entries. Omit the argument to get all.

await client.searchRewardPools({ stakePool, mint })

await client.searchRewardEntries({ stakeEntry, rewardPool })

```

#### Create a staking pool

```typescript
const client = new SolanaStakingClient({
clusterUrl: "https://api.mainnet-beta.solana.com",
cluster: ICluster.Mainnet
});
/*
Rewards Multiplier powered by 10^9.
Example: if multiplier is 2_000_000_000 than stakes for maxDuration will have 2x more rewards than stakes for minDuration
*/
const multiplier = new BN(1_000_000_000);
/*
30 days - Unix time in seconds
*/
const maxDuration = new BN(2592000);
/*
1 day - Unix time in seconds
*/
const maxDuration = new BN(86400);
/*
Limits signers that can create/assign reward pools to this stake pool. True - anyone can
*/
const permissionless = false;
/*
[0;256) derive stake pool PDA account address.
If stake pool with the same mint already exists, it is required to pick a vacant nonce
*/
const nonce = 0;

const { metadataId: stakePoolPda } = await client.createStakePool({
maxWeight: multiplier,
maxDuration,
minDuration,
mint: MINT_ADDRESS,
permissionless,
nonce:
})

```

#### Create a rewardPool pool
```typescript
/*
[0;256) derive reward pool PDA account address.
If reward pool with the same mint already exists, it is required to pick a vacant nonce
*/
const nonce = 0;
/*
Amount of rewarding tokens stakers get in return for staking exactly 1 token to the staking pool
*/
const rewardAmount = new BN(100);
/*
1 day - Unix time in seconds. Period for rewarding stakers. Period starts counting from the moment of staking
*/
const rewardPeriod = new BN(86400);
const rewardMint = REWARD_MINT_ADDRESS; // rewarding token
/*
Whether to allow anyone to fund this reward pool. If true anyone can fund, otherwise only the creator can
*/
const permissionless = true;

client.createRewardPool({
nonce,
rewardAmount,
rewardPeriod,
rewardMint,
permissionless = false,
stakePool: stakePoolPda,
stakePoolMint: MINT_ADDRESS,
})
```


#### Deposit/Stake to a stake pool
```typescript
/*
[0;256) derive stake entry PDA account address.
If stake entry with the same nonce already exists, it is required to pick a vacant one
*/
const nonce = 0;
const amount = new BN(1000); // tokens to stake
const duration = new BN(86400 * 2) // 2 days, must be in the range of stakePool's min and max durations
await client.stake({ nonce, amount, duration, stakePool, stakePoolMint });
```

#### Unstake/Withdraw to a stake pool
```typescript
/*
Usually to achieve this the app already loaded available stakeEntries.
Stake Entry holds used `nonce`, so `nonce` below could be taken from the stake entry
*/

/*
[0;256) derived stake entry PDA account address.
If stake entry with the same nonce already exists, it is required to pick a vacant one
*/
const nonce = 0; //
await client.unstake({ stakePool, stakePoolMint, nonce });
```

#### Claim a reward
Since each stake entry can produce multiple rewards (single claim per each reward pool linked to the staking pool) this operation
can be triggered for every reward pool separately.

```typescript
await client.claimRewards({
rewardPoolNonce,
depositNonce,
stakePool,
rewardMint,
});
```

> [!Note]
> All operations have accompanying APIs for manual transaction building. Consult with API docs to find respective calls.
> For instance, prepareClaimRewardsInstructions.
> These APIs allow to aggregate multiple operations in a single transaction according to the app needs.

## Appendix

Streamflow Staking protocol program IDs

| Solana | |
| ------- | -------------------------------------------- |
| Staking Pools Mainnet | STAKEvGqQTtzJZH6BWDcbpzXXn2BBerPAgQ3EGLN2GH |
| Reward Pools Mainnet | RWRDdfRbi3339VgKxTAXg4cjyniF7cbhNbMxZWiSKmj |
| ---- | --- |
| Staking Pools Devnet | STAKEvGqQTtzJZH6BWDcbpzXXn2BBerPAgQ3EGLN2GH |
| Reward Pools Devnet | RWRDdfRbi3339VgKxTAXg4cjyniF7cbhNbMxZWiSKmj |

### IDLs
For further details you can consult with IDLs of protocols available at:
`@streamflow/staking/dist/esm/solana/descriptor`
6 changes: 6 additions & 0 deletions packages/staking/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { default as SolanaStakingClient } from "./solana/client.js";
export * from "./solana/utils.js";
export * from "./solana/types.js";
export * from "./solana/lib/derive-accounts.js";
export * from "./solana/lib/rewards.js";
export * as constants from "./solana/constants.js";
49 changes: 49 additions & 0 deletions packages/staking/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@streamflow/staking",
"version": "7.0.0-alpha.1",
"description": "JavaScript SDK to interact with Streamflow Staking protocol.",
"homepage": "https://github.com/streamflow-finance/js-sdk/",
"main": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"type": "module",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"types": "./dist/esm/index.d.ts"
},
"./solana": {
"import": "./dist/esm/solana/index.js",
"require": "./dist/cjs/solana/index.js",
"types": "./dist/esm/solana/index.d.ts"
}
},
"scripts": {
"build:cjs": "rm -rf dist/cjs; tsc -p tsconfig.cjs.json",
"build:esm": "rm -rf dist/esm; tsc -p tsconfig.esm.json",
"build": "rm -rf dist; pnpm run build:cjs && pnpm run build:esm",
"pack": "pnpm build && pnpm pack",
"lint": "eslint --fix .",
"prepublishOnly": "npm run lint && npm run build"
},
"gitHead": "a37306eba0e762af096db642fa22f07194014cfd",
"devDependencies": {
"@streamflow/eslint-config": "workspace:*",
"@types/bn.js": "5.1.1",
"date-fns": "2.28.0",
"typescript": "^4.9.5"
},
"dependencies": {
"@coral-xyz/borsh": "^0.29.0",
"@coral-xyz/anchor": "^0.30.0",
"@solana/buffer-layout": "4.0.1 ",
"@solana/spl-token": "0.3.6",
"@solana/wallet-adapter-base": "0.9.19",
"@solana/web3.js": "1.70.1",
"@streamflow/common": "workspace:*",
"bn.js": "5.2.1",
"borsh": "^2.0.0",
"bs58": "5.0.0",
"p-queue": "^8.0.1"
}
}
Loading
Loading