@streamflow/staking 7.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +216 -0
  3. package/dist/cjs/index.js +39 -0
  4. package/dist/cjs/solana/client.js +315 -0
  5. package/dist/cjs/solana/constants.js +61 -0
  6. package/dist/cjs/solana/descriptor/fee_manager.js +2 -0
  7. package/dist/cjs/solana/descriptor/idl/fee_manager.json +284 -0
  8. package/dist/cjs/solana/descriptor/idl/reward_pool.json +905 -0
  9. package/dist/cjs/solana/descriptor/idl/stake_pool.json +734 -0
  10. package/dist/cjs/solana/descriptor/reward_pool.js +2 -0
  11. package/dist/cjs/solana/descriptor/stake_pool.js +2 -0
  12. package/dist/cjs/solana/lib/derive-accounts.js +46 -0
  13. package/dist/cjs/solana/lib/rewards.js +134 -0
  14. package/dist/cjs/solana/types.js +2 -0
  15. package/dist/cjs/solana/utils.js +61 -0
  16. package/dist/esm/index.d.ts +6 -0
  17. package/dist/esm/index.js +6 -0
  18. package/dist/esm/solana/client.d.ts +77 -0
  19. package/dist/esm/solana/client.js +313 -0
  20. package/dist/esm/solana/constants.d.ts +50 -0
  21. package/dist/esm/solana/constants.js +58 -0
  22. package/dist/esm/solana/descriptor/fee_manager.d.ts +290 -0
  23. package/dist/esm/solana/descriptor/fee_manager.js +1 -0
  24. package/dist/esm/solana/descriptor/idl/fee_manager.json +284 -0
  25. package/dist/esm/solana/descriptor/idl/reward_pool.json +905 -0
  26. package/dist/esm/solana/descriptor/idl/stake_pool.json +734 -0
  27. package/dist/esm/solana/descriptor/reward_pool.d.ts +939 -0
  28. package/dist/esm/solana/descriptor/reward_pool.js +1 -0
  29. package/dist/esm/solana/descriptor/stake_pool.d.ts +538 -0
  30. package/dist/esm/solana/descriptor/stake_pool.js +1 -0
  31. package/dist/esm/solana/lib/derive-accounts.d.ts +10 -0
  32. package/dist/esm/solana/lib/derive-accounts.js +31 -0
  33. package/dist/esm/solana/lib/rewards.d.ts +24 -0
  34. package/dist/esm/solana/lib/rewards.js +136 -0
  35. package/dist/esm/solana/types.d.ts +66 -0
  36. package/dist/esm/solana/types.js +1 -0
  37. package/dist/esm/solana/utils.d.ts +11 -0
  38. package/dist/esm/solana/utils.js +50 -0
  39. package/package.json +48 -0
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.deriveFeeValuePDA = exports.deriveConfigPDA = exports.deriveRewardEntryPDA = exports.deriveRewardVaultPDA = exports.deriveRewardPoolPDA = exports.deriveStakeEntryPDA = exports.deriveStakeMintPDA = exports.deriveStakeVaultPDA = exports.deriveStakePoolPDA = void 0;
7
+ const web3_js_1 = require("@solana/web3.js");
8
+ // eslint-disable-next-line no-restricted-imports
9
+ const bn_js_1 = __importDefault(require("bn.js"));
10
+ const constants_js_1 = require("../constants.js");
11
+ const deriveStakePoolPDA = (programId, mint, authority, nonce) => {
12
+ return web3_js_1.PublicKey.findProgramAddressSync([constants_js_1.STAKE_POOL_PREFIX, mint.toBuffer(), authority.toBuffer(), new bn_js_1.default(nonce).toArrayLike(Buffer, "le", 1)], programId)[0];
13
+ };
14
+ exports.deriveStakePoolPDA = deriveStakePoolPDA;
15
+ const deriveStakeVaultPDA = (programId, stakePool) => {
16
+ return web3_js_1.PublicKey.findProgramAddressSync([constants_js_1.STAKE_VAULT_PREFIX, stakePool.toBuffer()], programId)[0];
17
+ };
18
+ exports.deriveStakeVaultPDA = deriveStakeVaultPDA;
19
+ const deriveStakeMintPDA = (programId, stakePool) => {
20
+ return web3_js_1.PublicKey.findProgramAddressSync([constants_js_1.STAKE_MINT_PREFIX, stakePool.toBuffer()], programId)[0];
21
+ };
22
+ exports.deriveStakeMintPDA = deriveStakeMintPDA;
23
+ const deriveStakeEntryPDA = (programId, stakePool, authority, nonce) => {
24
+ return web3_js_1.PublicKey.findProgramAddressSync([constants_js_1.STAKE_ENTRY_PREFIX, stakePool.toBuffer(), authority.toBuffer(), new bn_js_1.default(nonce).toArrayLike(Buffer, "le", 4)], programId)[0];
25
+ };
26
+ exports.deriveStakeEntryPDA = deriveStakeEntryPDA;
27
+ const deriveRewardPoolPDA = (programId, stakePool, mint, nonce) => {
28
+ return web3_js_1.PublicKey.findProgramAddressSync([constants_js_1.REWARD_POOL_PREFIX, stakePool.toBuffer(), mint.toBuffer(), new bn_js_1.default(nonce).toArrayLike(Buffer, "le", 1)], programId)[0];
29
+ };
30
+ exports.deriveRewardPoolPDA = deriveRewardPoolPDA;
31
+ const deriveRewardVaultPDA = (programId, rewardPool) => {
32
+ return web3_js_1.PublicKey.findProgramAddressSync([constants_js_1.REWARD_VAULT_PREFIX, rewardPool.toBuffer()], programId)[0];
33
+ };
34
+ exports.deriveRewardVaultPDA = deriveRewardVaultPDA;
35
+ const deriveRewardEntryPDA = (programId, rewardPool, stakeEntry) => {
36
+ return web3_js_1.PublicKey.findProgramAddressSync([constants_js_1.REWARD_ENTRY_PREFIX, rewardPool.toBuffer(), stakeEntry.toBuffer()], programId)[0];
37
+ };
38
+ exports.deriveRewardEntryPDA = deriveRewardEntryPDA;
39
+ const deriveConfigPDA = (programId) => {
40
+ return web3_js_1.PublicKey.findProgramAddressSync([constants_js_1.CONFIG_PREFIX], programId)[0];
41
+ };
42
+ exports.deriveConfigPDA = deriveConfigPDA;
43
+ const deriveFeeValuePDA = (programId, target) => {
44
+ return web3_js_1.PublicKey.findProgramAddressSync([constants_js_1.FEE_VALUE_PREFIX, target.toBuffer()], programId)[0];
45
+ };
46
+ exports.deriveFeeValuePDA = deriveFeeValuePDA;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.calcRewards = exports.RewardEntryAccumulator = exports.REWARD_AMOUNT_PRECISION_FACTOR = void 0;
7
+ const web3_js_1 = require("@solana/web3.js");
8
+ // eslint-disable-next-line no-restricted-imports
9
+ const bn_js_1 = __importDefault(require("bn.js"));
10
+ const constants_js_1 = require("../constants.js");
11
+ exports.REWARD_AMOUNT_PRECISION_FACTOR = new bn_js_1.default("1000000000");
12
+ class RewardEntryAccumulator {
13
+ constructor(delegate) {
14
+ this.delegate = delegate;
15
+ this.lastAccountedTs = delegate.lastAccountedTs;
16
+ this.claimedAmount = delegate.claimedAmount;
17
+ this.accountedAmount = delegate.accountedAmount;
18
+ this.rewardPool = delegate.rewardPool;
19
+ this.stakeEntry = delegate.stakeEntry;
20
+ this.createdTs = delegate.createdTs;
21
+ this.buffer = delegate.buffer;
22
+ this.lastRewardAmount = delegate.lastRewardAmount;
23
+ this.lastRewardPeriod = delegate.lastRewardPeriod;
24
+ }
25
+ // Calculate accountable amount by calculating how many seconds have passed since last claim/stake time
26
+ getAccountableAmount(stakedTs, accountableTs, effectiveStakedAmount, rewardAmount, rewardPeriod) {
27
+ const lastAccountedTs = this.lastAccountedTs.gt(new bn_js_1.default(0)) ? this.lastAccountedTs : stakedTs;
28
+ const secondsPassed = accountableTs.sub(lastAccountedTs);
29
+ if (secondsPassed.lt(rewardPeriod)) {
30
+ return new bn_js_1.default(0);
31
+ }
32
+ const periodsPassed = secondsPassed.div(rewardPeriod);
33
+ const claimablePerEffectiveStake = periodsPassed.mul(rewardAmount);
34
+ const accountableAmount = claimablePerEffectiveStake.mul(effectiveStakedAmount).div(constants_js_1.SCALE_PRECISION_FACTOR_BN);
35
+ return accountableAmount;
36
+ }
37
+ // Calculates claimable amount from accountable amount.
38
+ getClaimableAmount() {
39
+ const claimedAmount = this.claimedAmount.mul(exports.REWARD_AMOUNT_PRECISION_FACTOR);
40
+ const nonClaimedAmount = this.accountedAmount.sub(claimedAmount);
41
+ const claimableAmount = nonClaimedAmount.div(exports.REWARD_AMOUNT_PRECISION_FACTOR);
42
+ return claimableAmount;
43
+ }
44
+ // Get the time of the last unlock
45
+ getLastAccountedTs(stakedTs, claimableTs, rewardPeriod) {
46
+ const lastAccountedTs = this.lastAccountedTs.gtn(0) ? this.lastAccountedTs : stakedTs;
47
+ const totalSecondsPassed = claimableTs.sub(lastAccountedTs);
48
+ const periodsPassed = totalSecondsPassed.div(rewardPeriod);
49
+ const periodsToSeconds = periodsPassed.mul(rewardPeriod);
50
+ const currClaimTs = lastAccountedTs.add(periodsToSeconds);
51
+ return currClaimTs;
52
+ }
53
+ // Adds accounted amount
54
+ addAccountedAmount(accountedAmount) {
55
+ this.accountedAmount = this.accountedAmount.add(accountedAmount);
56
+ }
57
+ // Adds claimed amount
58
+ addClaimedAmount(claimedAmount) {
59
+ this.claimedAmount = this.claimedAmount.add(claimedAmount);
60
+ }
61
+ }
62
+ exports.RewardEntryAccumulator = RewardEntryAccumulator;
63
+ const createDefaultRewardEntry = (stakeEntry, rewardPool) => {
64
+ return {
65
+ stakeEntry: new web3_js_1.PublicKey(stakeEntry.publicKey),
66
+ rewardPool: new web3_js_1.PublicKey(rewardPool.publicKey),
67
+ createdTs: stakeEntry.account.createdTs,
68
+ lastAccountedTs: new bn_js_1.default(0),
69
+ lastRewardAmount: new bn_js_1.default(0),
70
+ lastRewardPeriod: new bn_js_1.default(0),
71
+ accountedAmount: new bn_js_1.default(0),
72
+ claimedAmount: new bn_js_1.default(0),
73
+ buffer: [],
74
+ };
75
+ };
76
+ const calcRewards = (rewardEntryAccount, stakeEntryAccount, rewardPoolAccount) => {
77
+ const rewardEntry = rewardEntryAccount?.account ?? createDefaultRewardEntry(stakeEntryAccount, rewardPoolAccount);
78
+ const stakeEntry = stakeEntryAccount.account;
79
+ const rewardPool = rewardPoolAccount.account;
80
+ const rewardEntryAccumulator = new RewardEntryAccumulator(rewardEntry);
81
+ if (rewardEntryAccumulator.createdTs.lt(stakeEntry.createdTs)) {
82
+ throw new Error("InvalidRewardEntry");
83
+ }
84
+ const currTs = Math.floor(Date.now() / 1000);
85
+ const stakedTs = rewardPool.createdTs ? bn_js_1.default.max(stakeEntry.createdTs, rewardPool.createdTs) : stakeEntry.createdTs;
86
+ const claimableTs = stakeEntry.closedTs.gtn(0) ? stakeEntry.closedTs : new bn_js_1.default(currTs);
87
+ const amountUpdated = !rewardPool.rewardAmount.eq(rewardPool.lastRewardAmount) &&
88
+ rewardPool.lastAmountUpdateTs.gt(stakeEntry.createdTs) &&
89
+ rewardPool.lastAmountUpdateTs.gt(stakeEntry.closedTs);
90
+ const periodUpdated = !rewardPool.rewardPeriod.eq(rewardPool.lastRewardPeriod) &&
91
+ rewardPool.lastPeriodUpdateTs.gt(stakeEntry.createdTs) &&
92
+ rewardPool.lastPeriodUpdateTs.gt(stakeEntry.closedTs);
93
+ if (amountUpdated || periodUpdated) {
94
+ let firstUpdateTs, secondUpdateTs, rewardAmount, rewardPeriod;
95
+ if (amountUpdated && periodUpdated) {
96
+ if (rewardPool.lastAmountUpdateTs.lt(rewardPool.lastPeriodUpdateTs)) {
97
+ firstUpdateTs = rewardPool.lastAmountUpdateTs;
98
+ secondUpdateTs = rewardPool.lastPeriodUpdateTs;
99
+ rewardAmount = rewardPool.rewardAmount;
100
+ rewardPeriod = rewardEntryAccumulator.lastRewardPeriod;
101
+ }
102
+ else {
103
+ firstUpdateTs = rewardPool.lastPeriodUpdateTs;
104
+ secondUpdateTs = rewardPool.lastAmountUpdateTs;
105
+ rewardAmount = rewardEntryAccumulator.lastRewardAmount;
106
+ rewardPeriod = rewardPool.rewardPeriod;
107
+ }
108
+ }
109
+ else if (amountUpdated) {
110
+ firstUpdateTs = new bn_js_1.default(0);
111
+ secondUpdateTs = rewardPool.lastAmountUpdateTs;
112
+ rewardAmount = rewardEntryAccumulator.lastRewardAmount;
113
+ rewardPeriod = rewardEntryAccumulator.lastRewardPeriod;
114
+ }
115
+ else {
116
+ firstUpdateTs = new bn_js_1.default(0);
117
+ secondUpdateTs = rewardPool.lastPeriodUpdateTs;
118
+ rewardAmount = rewardEntryAccumulator.lastRewardAmount;
119
+ rewardPeriod = rewardEntryAccumulator.lastRewardPeriod;
120
+ }
121
+ if (firstUpdateTs.gtn(0)) {
122
+ const firstAccountableAmount = rewardEntryAccumulator.getAccountableAmount(stakedTs, firstUpdateTs, stakeEntry.effectiveAmount, rewardEntryAccumulator.lastRewardAmount, rewardEntryAccumulator.lastRewardPeriod);
123
+ rewardEntryAccumulator.addAccountedAmount(firstAccountableAmount);
124
+ rewardEntryAccumulator.lastAccountedTs = rewardEntryAccumulator.getLastAccountedTs(stakedTs, firstUpdateTs, rewardPool.lastRewardPeriod);
125
+ }
126
+ const secondAccountableAmount = rewardEntryAccumulator.getAccountableAmount(stakedTs, secondUpdateTs, stakeEntry.effectiveAmount, rewardAmount, rewardPeriod);
127
+ rewardEntryAccumulator.addAccountedAmount(secondAccountableAmount);
128
+ rewardEntryAccumulator.lastAccountedTs = rewardEntryAccumulator.getLastAccountedTs(stakedTs, secondUpdateTs, rewardPeriod);
129
+ }
130
+ const accountableAmount = rewardEntryAccumulator.getAccountableAmount(stakedTs, claimableTs, stakeEntry.effectiveAmount, rewardPool.rewardAmount, rewardPool.rewardPeriod);
131
+ rewardEntryAccumulator.addAccountedAmount(accountableAmount);
132
+ return rewardEntryAccumulator.getClaimableAmount();
133
+ };
134
+ exports.calcRewards = calcRewards;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.calculateAmountWithTransferFees = exports.divCeilN = exports.calculateDecimalsShift = exports.calculateFeeAmount = exports.calculateStakeWeight = void 0;
7
+ // eslint-disable-next-line no-restricted-imports
8
+ const bn_js_1 = __importDefault(require("bn.js"));
9
+ const constants_js_1 = require("./constants.js");
10
+ const calculateStakeWeight = (minDuration, maxDuration, maxWeight, duration) => {
11
+ const durationSpan = maxDuration.sub(minDuration);
12
+ if (durationSpan.eq(new bn_js_1.default(0))) {
13
+ return constants_js_1.SCALE_PRECISION_FACTOR_BN;
14
+ }
15
+ const durationExceedingMin = duration.sub(minDuration);
16
+ const normalizedWeight = durationExceedingMin.mul(constants_js_1.SCALE_PRECISION_FACTOR_BN).div(durationSpan);
17
+ const weightDiff = maxWeight.sub(constants_js_1.SCALE_PRECISION_FACTOR_BN);
18
+ return bn_js_1.default.max(constants_js_1.SCALE_PRECISION_FACTOR_BN.add(normalizedWeight.mul(weightDiff).div(constants_js_1.SCALE_PRECISION_FACTOR_BN)), constants_js_1.SCALE_PRECISION_FACTOR_BN);
19
+ };
20
+ exports.calculateStakeWeight = calculateStakeWeight;
21
+ const calculateFeeAmount = (amount, fee = constants_js_1.DEFAULT_FEE_BN) => {
22
+ if (fee.eq(constants_js_1.FEE_PRECISION_FACTOR_BN)) {
23
+ return amount;
24
+ }
25
+ return amount.mul(fee).div(constants_js_1.FEE_PRECISION_FACTOR_BN);
26
+ };
27
+ exports.calculateFeeAmount = calculateFeeAmount;
28
+ const calculateDecimalsShift = (maxWeight, maxShift = 999) => {
29
+ if (maxShift == 0) {
30
+ return 0;
31
+ }
32
+ let decimalsShift = 0;
33
+ while ((maxWeight * constants_js_1.U64_MAX) / BigInt(constants_js_1.SCALE_PRECISION_FACTOR) / BigInt(10 ** decimalsShift) > constants_js_1.U64_MAX) {
34
+ decimalsShift += 1;
35
+ if (decimalsShift == maxShift) {
36
+ return maxShift;
37
+ }
38
+ }
39
+ return decimalsShift;
40
+ };
41
+ exports.calculateDecimalsShift = calculateDecimalsShift;
42
+ const divCeilN = (n, d) => n / d + (n % d ? BigInt(1) : BigInt(0));
43
+ exports.divCeilN = divCeilN;
44
+ async function calculateAmountWithTransferFees(connection, transferFeeConfig, transferAmount) {
45
+ const epoch = await connection.getEpochInfo();
46
+ const transferFee = epoch.epoch >= transferFeeConfig.newerTransferFee.epoch
47
+ ? transferFeeConfig.newerTransferFee
48
+ : transferFeeConfig.olderTransferFee;
49
+ const transferFeeBasisPoints = BigInt(transferFee.transferFeeBasisPoints);
50
+ let feeCharged = BigInt(0);
51
+ if (transferFeeBasisPoints !== BigInt(0)) {
52
+ const numerator = transferAmount * 10000n;
53
+ const denominator = 10000n - transferFeeBasisPoints;
54
+ const rawPreFeeAmount = (0, exports.divCeilN)(numerator, denominator);
55
+ const fee = rawPreFeeAmount - transferAmount;
56
+ transferAmount = rawPreFeeAmount;
57
+ feeCharged = fee > transferFee.maximumFee ? transferFee.maximumFee : fee;
58
+ }
59
+ return { transferAmount, feeCharged };
60
+ }
61
+ exports.calculateAmountWithTransferFees = calculateAmountWithTransferFees;
@@ -0,0 +1,6 @@
1
+ export { default as SolanaStakingClient } from "./solana/client.js";
2
+ export * from "./solana/utils.js";
3
+ export * from "./solana/types.js";
4
+ export * from "./solana/lib/derive-accounts.js";
5
+ export * from "./solana/lib/rewards.js";
6
+ export * as constants from "./solana/constants.js";
@@ -0,0 +1,6 @@
1
+ export { default as SolanaStakingClient } from "./solana/client.js";
2
+ export * from "./solana/utils.js";
3
+ export * from "./solana/types.js";
4
+ export * from "./solana/lib/derive-accounts.js";
5
+ export * from "./solana/lib/rewards.js";
6
+ export * as constants from "./solana/constants.js";
@@ -0,0 +1,77 @@
1
+ import { Program, ProgramAccount } from "@coral-xyz/anchor";
2
+ import { Commitment, Connection, ConnectionConfig, PublicKey, TransactionInstruction } from "@solana/web3.js";
3
+ import { ICluster, ITransactionResult } from "@streamflow/common";
4
+ import PQueue from "p-queue";
5
+ import { REWARD_ENTRY_BYTE_OFFSETS, STAKE_ENTRY_BYTE_OFFSETS, STAKE_POOL_BYTE_OFFSETS } from "./constants.js";
6
+ import { FeeManager as FeeManagerProgramType } from "./descriptor/fee_manager.js";
7
+ import { RewardPool as RewardPoolProgramType } from "./descriptor/reward_pool.js";
8
+ import { StakePool as StakePoolProgramType } from "./descriptor/stake_pool.js";
9
+ import { ClaimRewardPoolArgs, CreateRewardEntryArgs, CreateRewardPoolArgs, CreateStakePoolArgs, FeeValue, FundPoolArgs, IInteractSolanaExt, RewardEntry, RewardPool, StakeArgs, StakeEntry, StakePool, UnstakeArgs } from "./types.js";
10
+ interface Programs {
11
+ stakePoolProgram: Program<StakePoolProgramType>;
12
+ rewardPoolProgram: Program<RewardPoolProgramType>;
13
+ feeManagerProgram: Program<FeeManagerProgramType>;
14
+ }
15
+ type CreationResult = ITransactionResult & {
16
+ metadataId: PublicKey;
17
+ };
18
+ interface IInitOptions {
19
+ clusterUrl: string;
20
+ cluster?: ICluster;
21
+ commitment?: Commitment | ConnectionConfig;
22
+ programIds?: {
23
+ stakePool?: string;
24
+ rewardPool?: string;
25
+ feeManager?: string;
26
+ };
27
+ sendRate?: number;
28
+ sendThrottler?: PQueue;
29
+ }
30
+ export default class SolanaStakingClient {
31
+ connection: Connection;
32
+ private commitment;
33
+ private sendThrottler;
34
+ private programs;
35
+ constructor({ clusterUrl, cluster, commitment, programIds, sendRate, sendThrottler, }: IInitOptions);
36
+ getCurrentProgramId(programKey: keyof Programs): PublicKey;
37
+ getCommitment(): Commitment | undefined;
38
+ getStakePool(id: string | PublicKey): Promise<StakePool>;
39
+ searchStakePools(criteria?: Partial<Pick<StakePool, keyof typeof STAKE_POOL_BYTE_OFFSETS>>): Promise<ProgramAccount<StakePool>[]>;
40
+ getStakeEntry(id: string | PublicKey): Promise<StakeEntry | null>;
41
+ searchStakeEntries(criteria?: Partial<Pick<StakeEntry, keyof typeof STAKE_ENTRY_BYTE_OFFSETS>>): Promise<ProgramAccount<StakeEntry>[]>;
42
+ searchRewardPools(criteria?: Partial<Pick<RewardPool, "stakePool" | "mint">>): Promise<ProgramAccount<RewardPool>[]>;
43
+ searchRewardEntries(criteria: Partial<Pick<RewardEntry, keyof typeof REWARD_ENTRY_BYTE_OFFSETS>>): Promise<ProgramAccount<RewardEntry>[]>;
44
+ getFeeValueIfExists(target: string | PublicKey): Promise<FeeValue | null>;
45
+ createStakePool(data: CreateStakePoolArgs, extParams: IInteractSolanaExt): Promise<CreationResult>;
46
+ prepareCreateStakePoolInstructions({ maxWeight, maxDuration, minDuration, mint, permissionless, nonce, tokenProgramId, }: CreateStakePoolArgs, extParams: IInteractSolanaExt): Promise<{
47
+ ixs: TransactionInstruction[];
48
+ publicKey: PublicKey;
49
+ }>;
50
+ stake(data: StakeArgs, extParams: IInteractSolanaExt): Promise<ITransactionResult>;
51
+ prepareStakeInstructions({ nonce, amount, duration, stakePool, stakePoolMint, tokenProgramId }: StakeArgs, extParams: IInteractSolanaExt): Promise<{
52
+ ixs: TransactionInstruction[];
53
+ }>;
54
+ unstake(data: UnstakeArgs, extParams: IInteractSolanaExt): Promise<ITransactionResult>;
55
+ prepareUnstakeInstructions({ stakePool, stakePoolMint, nonce, tokenProgramId }: UnstakeArgs, extParams: IInteractSolanaExt): Promise<{
56
+ ixs: TransactionInstruction[];
57
+ }>;
58
+ createRewardPool(data: CreateRewardPoolArgs, extParams: IInteractSolanaExt): Promise<CreationResult>;
59
+ prepareCreateRewardPoolInstructions({ nonce, rewardAmount, rewardPeriod, rewardMint, permissionless, stakePool, tokenProgramId, }: CreateRewardPoolArgs, extParams: IInteractSolanaExt): Promise<{
60
+ publicKey: PublicKey;
61
+ ixs: TransactionInstruction[];
62
+ }>;
63
+ claimRewards(data: ClaimRewardPoolArgs, extParams: IInteractSolanaExt): Promise<ITransactionResult>;
64
+ prepareClaimRewardsInstructions({ rewardPoolNonce, depositNonce, stakePool, tokenProgramId, rewardMint }: ClaimRewardPoolArgs, extParams: IInteractSolanaExt): Promise<{
65
+ ixs: TransactionInstruction[];
66
+ }>;
67
+ fundPool(data: FundPoolArgs, extParams: IInteractSolanaExt): Promise<ITransactionResult>;
68
+ prepareFundPoolInstructions({ amount, tokenProgramId, rewardMint, stakePool, feeValue, nonce }: FundPoolArgs, extParams: IInteractSolanaExt): Promise<{
69
+ ixs: TransactionInstruction[];
70
+ }>;
71
+ createRewardEntry(data: CreateRewardEntryArgs, extParams: IInteractSolanaExt): Promise<ITransactionResult>;
72
+ prepareCreateRewardEntryInstructions({ stakePoolMint, stakePool, rewardPoolNonce, depositNonce }: CreateRewardEntryArgs, extParams: IInteractSolanaExt): Promise<{
73
+ ixs: TransactionInstruction[];
74
+ }>;
75
+ private execute;
76
+ }
77
+ export {};
@@ -0,0 +1,313 @@
1
+ import { Program, parseIdlErrors, translateError, } from "@coral-xyz/anchor";
2
+ import { TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from "@solana/spl-token";
3
+ import { Connection, PublicKey, } from "@solana/web3.js";
4
+ import { ContractError, ICluster } from "@streamflow/common";
5
+ import { buildSendThrottler, checkOrCreateAtaBatch, prepareTransaction, signAndExecuteTransaction, } from "@streamflow/common/solana";
6
+ import { REWARD_ENTRY_BYTE_OFFSETS, REWARD_POOL_BYTE_OFFSETS, REWARD_POOL_PROGRAM_ID, STAKE_ENTRY_BYTE_OFFSETS, STAKE_POOL_BYTE_OFFSETS, STAKE_POOL_PROGRAM_ID, STREAMFLOW_TREASURY_PUBLIC_KEY, } from "./constants.js";
7
+ import FeeManagerIDL from "./descriptor/idl/fee_manager.json";
8
+ import RewardPoolIDL from "./descriptor/idl/reward_pool.json";
9
+ import StakePoolIDL from "./descriptor/idl/stake_pool.json";
10
+ import { deriveFeeValuePDA, deriveRewardPoolPDA, deriveRewardVaultPDA, deriveStakeEntryPDA, deriveStakeMintPDA, deriveStakePoolPDA, } from "./lib/derive-accounts.js";
11
+ export default class SolanaStakingClient {
12
+ connection;
13
+ commitment;
14
+ sendThrottler;
15
+ programs;
16
+ constructor({ clusterUrl, cluster = ICluster.Mainnet, commitment = "confirmed", programIds, sendRate = 1, sendThrottler, }) {
17
+ this.commitment = commitment;
18
+ this.connection = new Connection(clusterUrl, this.commitment);
19
+ this.sendThrottler = sendThrottler ?? buildSendThrottler(sendRate);
20
+ const stakePoolIdl = {
21
+ ...StakePoolIDL,
22
+ address: programIds?.stakePool ?? STAKE_POOL_PROGRAM_ID[cluster] ?? StakePoolIDL.address,
23
+ };
24
+ const rewardPoolIdl = {
25
+ ...RewardPoolIDL,
26
+ address: programIds?.rewardPool ?? REWARD_POOL_PROGRAM_ID[cluster] ?? RewardPoolIDL.address,
27
+ };
28
+ const feeManagerIdl = {
29
+ ...FeeManagerIDL,
30
+ address: programIds?.feeManager ?? FeeManagerIDL.address,
31
+ };
32
+ this.programs = {
33
+ stakePoolProgram: new Program(stakePoolIdl, {
34
+ connection: this.connection,
35
+ }),
36
+ rewardPoolProgram: new Program(rewardPoolIdl, {
37
+ connection: this.connection,
38
+ }),
39
+ feeManagerProgram: new Program(feeManagerIdl, {
40
+ connection: this.connection,
41
+ }),
42
+ };
43
+ }
44
+ getCurrentProgramId(programKey) {
45
+ const program = this.programs[programKey];
46
+ invariant(program, `Program ${programKey} is not found`);
47
+ return program.programId;
48
+ }
49
+ getCommitment() {
50
+ return typeof this.commitment == "string" ? this.commitment : this.commitment.commitment;
51
+ }
52
+ async getStakePool(id) {
53
+ const { stakePoolProgram } = this.programs;
54
+ return stakePoolProgram.account.stakePool.fetch(id);
55
+ }
56
+ async searchStakePools(criteria = {}) {
57
+ const { stakePoolProgram } = this.programs;
58
+ return stakePoolProgram.account.stakePool.all(getFilters(criteria, STAKE_POOL_BYTE_OFFSETS));
59
+ }
60
+ async getStakeEntry(id) {
61
+ const { stakePoolProgram } = this.programs;
62
+ return stakePoolProgram.account.stakeEntry.fetch(id);
63
+ }
64
+ async searchStakeEntries(criteria = {}) {
65
+ const { stakePoolProgram } = this.programs;
66
+ return stakePoolProgram.account.stakeEntry.all(getFilters(criteria, STAKE_ENTRY_BYTE_OFFSETS));
67
+ }
68
+ async searchRewardPools(criteria = {}) {
69
+ const { rewardPoolProgram } = this.programs;
70
+ return rewardPoolProgram.account.rewardPool.all(getFilters(criteria, REWARD_POOL_BYTE_OFFSETS));
71
+ }
72
+ async searchRewardEntries(criteria) {
73
+ const { rewardPoolProgram } = this.programs;
74
+ return rewardPoolProgram.account.rewardEntry.all(getFilters(criteria, REWARD_ENTRY_BYTE_OFFSETS));
75
+ }
76
+ getFeeValueIfExists(target) {
77
+ const { feeManagerProgram } = this.programs;
78
+ const feeValueKey = deriveFeeValuePDA(feeManagerProgram.programId, new PublicKey(target));
79
+ return feeManagerProgram.account.feeValue.fetchNullable(feeValueKey);
80
+ }
81
+ async createStakePool(data, extParams) {
82
+ const { ixs, publicKey } = await this.prepareCreateStakePoolInstructions(data, extParams);
83
+ const { signature } = await this.execute(ixs, extParams);
84
+ return {
85
+ ixs,
86
+ txId: signature,
87
+ metadataId: publicKey,
88
+ };
89
+ }
90
+ async prepareCreateStakePoolInstructions({ maxWeight, maxDuration, minDuration, mint, permissionless = false, nonce, tokenProgramId = TOKEN_PROGRAM_ID, }, extParams) {
91
+ const { stakePoolProgram } = this.programs;
92
+ const creator = extParams.invoker.publicKey;
93
+ invariant(creator, "Undefined invoker publicKey");
94
+ const createInstruction = await stakePoolProgram.methods
95
+ .createPool(nonce, maxWeight, minDuration, maxDuration, permissionless)
96
+ .accounts({
97
+ creator,
98
+ mint,
99
+ tokenProgram: tokenProgramId,
100
+ })
101
+ .instruction();
102
+ const stakePoolPDA = deriveStakePoolPDA(stakePoolProgram.programId, pk(mint), creator, nonce);
103
+ return { ixs: [createInstruction], publicKey: stakePoolPDA };
104
+ }
105
+ async stake(data, extParams) {
106
+ const { ixs } = await this.prepareStakeInstructions(data, extParams);
107
+ const { signature } = await this.execute(ixs, extParams);
108
+ return {
109
+ ixs,
110
+ txId: signature,
111
+ };
112
+ }
113
+ async prepareStakeInstructions({ nonce, amount, duration, stakePool, stakePoolMint, tokenProgramId = TOKEN_PROGRAM_ID }, extParams) {
114
+ const { stakePoolProgram } = this.programs;
115
+ const staker = extParams.invoker.publicKey;
116
+ invariant(staker, "Undefined invoker publicKey");
117
+ const mint = deriveStakeMintPDA(stakePoolProgram.programId, pk(stakePool));
118
+ const stakeMintAccountKey = getAssociatedTokenAddressSync(mint, staker, false, pk(tokenProgramId));
119
+ const poolMintAccountKey = getAssociatedTokenAddressSync(pk(stakePoolMint), staker, false, pk(tokenProgramId));
120
+ const instruction = await stakePoolProgram.methods
121
+ .stake(nonce, amount, duration)
122
+ .accounts({
123
+ stakePool: stakePool,
124
+ tokenProgram: tokenProgramId,
125
+ from: poolMintAccountKey,
126
+ to: stakeMintAccountKey,
127
+ authority: staker,
128
+ payer: staker,
129
+ })
130
+ .instruction();
131
+ return { ixs: [instruction] };
132
+ }
133
+ async unstake(data, extParams) {
134
+ const { ixs } = await this.prepareUnstakeInstructions(data, extParams);
135
+ const { signature } = await this.execute(ixs, extParams);
136
+ return {
137
+ ixs,
138
+ txId: signature,
139
+ };
140
+ }
141
+ async prepareUnstakeInstructions({ stakePool, stakePoolMint, nonce, tokenProgramId = TOKEN_PROGRAM_ID }, extParams) {
142
+ const { stakePoolProgram } = this.programs;
143
+ const staker = extParams.invoker.publicKey;
144
+ invariant(staker, "Undefined invoker publicKey");
145
+ const stakeMintKey = deriveStakeMintPDA(stakePoolProgram.programId, pk(stakePool));
146
+ const stakeEntryKey = deriveStakeEntryPDA(stakePoolProgram.programId, pk(stakePool), staker, nonce);
147
+ const poolMintAccountKey = getAssociatedTokenAddressSync(pk(stakePoolMint), staker, false, pk(tokenProgramId));
148
+ const stakeMintAccountKey = getAssociatedTokenAddressSync(stakeMintKey, staker, false, pk(tokenProgramId));
149
+ const instruction = await stakePoolProgram.methods
150
+ .unstake()
151
+ .accounts({
152
+ stakeEntry: stakeEntryKey,
153
+ to: poolMintAccountKey,
154
+ from: stakeMintAccountKey,
155
+ authority: staker,
156
+ tokenProgram: tokenProgramId,
157
+ })
158
+ .instruction();
159
+ return { ixs: [instruction] };
160
+ }
161
+ async createRewardPool(data, extParams) {
162
+ const { ixs, publicKey } = await this.prepareCreateRewardPoolInstructions(data, extParams);
163
+ const { signature } = await this.execute(ixs, extParams);
164
+ return {
165
+ ixs,
166
+ txId: signature,
167
+ metadataId: publicKey,
168
+ };
169
+ }
170
+ async prepareCreateRewardPoolInstructions({ nonce, rewardAmount, rewardPeriod, rewardMint, permissionless = false, stakePool, tokenProgramId = TOKEN_PROGRAM_ID, }, extParams) {
171
+ const { rewardPoolProgram } = this.programs;
172
+ const creator = extParams.invoker.publicKey;
173
+ invariant(creator, "Undefined invoker publicKey");
174
+ const instruction = await rewardPoolProgram.methods
175
+ .createPool(nonce, rewardAmount, rewardPeriod, permissionless)
176
+ .accounts({
177
+ creator,
178
+ stakePool,
179
+ mint: rewardMint,
180
+ tokenProgram: tokenProgramId,
181
+ })
182
+ .instruction();
183
+ const rewardPoolKey = deriveRewardPoolPDA(rewardPoolProgram.programId, pk(stakePool), pk(rewardMint), nonce);
184
+ return { publicKey: rewardPoolKey, ixs: [instruction] };
185
+ }
186
+ async claimRewards(data, extParams) {
187
+ const { ixs } = await this.prepareClaimRewardsInstructions(data, extParams);
188
+ const { signature } = await this.execute(ixs, extParams);
189
+ return {
190
+ ixs,
191
+ txId: signature,
192
+ };
193
+ }
194
+ async prepareClaimRewardsInstructions({ rewardPoolNonce, depositNonce, stakePool, tokenProgramId = TOKEN_PROGRAM_ID, rewardMint }, extParams) {
195
+ const { stakePoolProgram, rewardPoolProgram } = this.programs;
196
+ const staker = extParams.invoker.publicKey;
197
+ invariant(staker, "Undefined invoker publicKey");
198
+ const instruction = await rewardPoolProgram.methods
199
+ .claimRewards()
200
+ .accounts({
201
+ stakeEntry: deriveStakeEntryPDA(stakePoolProgram.programId, pk(stakePool), staker, depositNonce),
202
+ rewardPool: deriveRewardPoolPDA(rewardPoolProgram.programId, pk(stakePool), pk(rewardMint), rewardPoolNonce),
203
+ claimant: staker,
204
+ tokenProgram: tokenProgramId,
205
+ to: getAssociatedTokenAddressSync(pk(rewardMint), staker, false, pk(tokenProgramId)),
206
+ })
207
+ .instruction();
208
+ return { ixs: [instruction] };
209
+ }
210
+ async fundPool(data, extParams) {
211
+ const { ixs } = await this.prepareFundPoolInstructions(data, extParams);
212
+ const { signature } = await this.execute(ixs, extParams);
213
+ return {
214
+ ixs,
215
+ txId: signature,
216
+ };
217
+ }
218
+ async prepareFundPoolInstructions({ amount, tokenProgramId = TOKEN_PROGRAM_ID, rewardMint, stakePool, feeValue, nonce }, extParams) {
219
+ const { rewardPoolProgram } = this.programs;
220
+ const staker = extParams.invoker.publicKey;
221
+ invariant(staker, "Undefined invoker publicKey");
222
+ const existingFee = await this.getFeeValueIfExists(staker);
223
+ const rewardMintPk = pk(rewardMint);
224
+ const tokenProgramPk = pk(tokenProgramId);
225
+ const treasuryATA = !existingFee || existingFee.streamflowFee.gtn(0)
226
+ ? await checkOrCreateAtaBatch(this.connection, [STREAMFLOW_TREASURY_PUBLIC_KEY], rewardMintPk, extParams.invoker, tokenProgramPk)
227
+ : null;
228
+ const rewardPoolPda = deriveRewardPoolPDA(rewardPoolProgram.programId, pk(stakePool), rewardMintPk, nonce);
229
+ const instruction = await rewardPoolProgram.methods
230
+ .fundPool(amount)
231
+ .accountsPartial({
232
+ funder: staker,
233
+ rewardPool: rewardPoolPda,
234
+ from: getAssociatedTokenAddressSync(rewardMintPk, staker, false, tokenProgramPk),
235
+ tokenProgram: tokenProgramId,
236
+ vault: deriveRewardVaultPDA(rewardPoolProgram.programId, rewardPoolPda),
237
+ mint: rewardMint,
238
+ feeValue,
239
+ })
240
+ .instruction();
241
+ return { ixs: treasuryATA ? treasuryATA.concat([instruction]) : [instruction] };
242
+ }
243
+ async createRewardEntry(data, extParams) {
244
+ const { ixs } = await this.prepareCreateRewardEntryInstructions(data, extParams);
245
+ const { signature } = await this.execute(ixs, extParams);
246
+ return {
247
+ ixs,
248
+ txId: signature,
249
+ };
250
+ }
251
+ async prepareCreateRewardEntryInstructions({ stakePoolMint, stakePool, rewardPoolNonce, depositNonce }, extParams) {
252
+ const { stakePoolProgram, rewardPoolProgram } = this.programs;
253
+ const staker = extParams.invoker.publicKey;
254
+ invariant(staker, "Undefined invoker publicKey");
255
+ const instruction = await rewardPoolProgram.methods
256
+ .createEntry()
257
+ .accounts({
258
+ payer: staker,
259
+ authority: staker,
260
+ stakeEntry: deriveStakeEntryPDA(stakePoolProgram.programId, pk(stakePool), staker, depositNonce),
261
+ rewardPool: deriveRewardPoolPDA(rewardPoolProgram.programId, pk(stakePool), pk(stakePoolMint), rewardPoolNonce),
262
+ })
263
+ .instruction();
264
+ return { ixs: [instruction] };
265
+ }
266
+ async execute(ixs, extParams) {
267
+ const { tx, hash, context } = await prepareTransaction(this.connection, ixs, extParams.invoker.publicKey);
268
+ try {
269
+ const signature = await signAndExecuteTransaction(this.connection, extParams.invoker, tx, {
270
+ hash,
271
+ context,
272
+ commitment: this.getCommitment(),
273
+ }, { sendThrottler: this.sendThrottler });
274
+ return { signature };
275
+ }
276
+ catch (err) {
277
+ if (err instanceof Error) {
278
+ const parsed = translateError(err, parseIdlErrors(this.programs.stakePoolProgram.idl));
279
+ if (parsed) {
280
+ throw new ContractError(err, parsed.name, parsed.message);
281
+ }
282
+ }
283
+ throw err;
284
+ }
285
+ }
286
+ }
287
+ function pk(address) {
288
+ return typeof address === "string" ? new PublicKey(address) : address;
289
+ }
290
+ const prefix = "Assertion failed";
291
+ function invariant(condition, message) {
292
+ if (condition) {
293
+ return;
294
+ }
295
+ const provided = typeof message === "function" ? message() : message;
296
+ const value = provided ? `${prefix}: ${provided}` : prefix;
297
+ throw new Error(value);
298
+ }
299
+ function getFilters(criteria, byteOffsets) {
300
+ return Object.entries(criteria).reduce((acc, [key, value]) => {
301
+ const criteriaKey = key;
302
+ const effectiveByteOffset = byteOffsets[criteriaKey];
303
+ if (criteria[criteriaKey] && effectiveByteOffset) {
304
+ acc.push({
305
+ memcmp: {
306
+ offset: effectiveByteOffset,
307
+ bytes: value.toString(),
308
+ },
309
+ });
310
+ }
311
+ return acc;
312
+ }, []);
313
+ }