@unstake-it/sol
TypeScript icon, indicating that this package has built-in type declarations

2.0.0 • Public • Published

@unstake.it/sol

Typescript SDK for the unstake.it solana program that allows users to instantly unstake their stake accounts for a fee.

Built on anchor 0.24.2.

Contents:

Installation

npm

$ npm install @unstake-it/sol

yarn

$ yarn add @unstake-it/sol

Examples

Initialization

Initialize the anchor program and define address constants.

import { PublicKey } from "@solana/web3.js";
import { Program } from "@project-serum/anchor";
import { IDL_JSON as UNSTAKE_IDL_JSON, Unstake } from "@unstake-it/sol";

// our mainnet program address
const PROG_ID = new PublicKey("unpXTU2Ndrc7WWNyEhQWe4udTzSibLPi25SXv2xbCHQ");

// construct new Program with provider set to anchor's default getProvider()
const UNSTAKE_PROGRAM: Program<Unstake> = new Program(
  UNSTAKE_IDL_JSON as Unstake,
  PROG_ID
);

// our mainnet unstake liquidity pool
const UNSTAKE_POOL_ADDRESS = new PublicKey(
  "FypPtwbY3FUfzJUtXHSyVRokVKG2jKtH29FmK4ebxRSd"
);

Fetch Pool Data

const FETCHED_POOL_DATA = await UNSTAKE_PROGRAM.account.pool.fetch(
  UNSTAKE_POOL_ADDRESS
);
const { feeAuthority, lpMint, incomingStake } = FETCHED_POOL_DATA;

Fetch Available Liquidity

The liquidity currently available for unstaking and liquidity removal is simply the SOL balance of the pool's reserves account.

import { findPoolSolReserves } from "@unstake-it/sol";

// this address is 3rBnnH9TTgd3xwu48rnzGsaQkSr1hR64nY71DrDt6VrQ on mainnet
const POOL_RESERVES_ADDRESS = await findPoolSolReserves(
  UNSTAKE_PROGRAM,
  UNSTAKE_POOL_ADDRESS
);
const availableLiquidityLamports =
  await UNSTAKE_PROGRAM.provider.connection.getBalance(POOL_RESERVES_ADDRESS);

Fetch Fee Params

The fee params for the unstake pool is stored in a separate PDA.

import { findPoolFeeAccount } from "@unstake-it/sol";

// this address is 5Pcu8WeQa3VbBz2vdBT49Rj4gbS4hsnfzuL1LmuRaKFY on mainnet
const POOL_FEE_ADDRESS = await findPoolFeeAccount(
  UNSTAKE_PROGRAM,
  UNSTAKE_POOL_ADDRESS
);
const FETCHED_FEE_DATA = await UNSTAKE_PROGRAM.account.fee.fetch(
  POOL_FEE_ADDRESS
);

// `fee` is an enum containing different fields depending on the fee schedule variant.
// Recommend using `applyFee` to estimate fees (see example in #estimate-fees below)
const { fee } = FETCHED_FEE_DATA;

Fetch Protocol Fee Params

The global protocol fee params for the unstake pool is stored in a singleton PDA.

import { findProtocolFeeAccount } from "@unstake-it/sol";

// this address is 2hN9UhvRFVfPYKL6rZJ5YiLEPCLTpN755pgwDJHWgFbU on mainnet
const PROTOCOL_FEE_ADDRESS = await findProtocolFeeAccount(UNSTAKE_PROGRAM);
const FETCHED_PROTOCOL_FEE_DATA =
  await UNSTAKE_PROGRAM.account.protocolFee.fetch(PROTOCOL_FEE_ADDRESS);

const { destination, authority, feeRatio, referrerFeeRatio } =
  FETCHED_PROTOCOL_FEE_DATA;

// this account data is required for unstakeTx(), unstakeWsolTx() and previewUnstake()
const PROTOCOL_FEE = {
  publicKey: PROTOCOL_FEE_ADDRESS,
  account: FETCHED_PROTOCOL_FEE_DATA,
};

Previewing an Unstake

This function simply makes a simulateTransaction RPC call and reports the change in lamports to the destination account the transaction results in.

import { previewUnstake } from "@unstake-it/sol";

// stakeAccountPubkey defined elsewhere

const changeInLamports = await previewUnstake(UNSTAKE_PROGRAM, {
  poolAccount: UNSTAKE_POOL_ADDRESS,
  stakeAccount: stakeAccountPubkey,
  unstaker: UNSTAKE_PROGRAM.provider.wallet.publicKey,
  protocolFee: PROTOCOL_FEE,
});

Estimate Fees

Estimate fees for an unstake amount manually using the fetched fee params. This is useful in cases where previewUnstake can't be used, such as when the stake account to be unstaked hasn't been created yet.

import BN from "bn.js";
import {
  applyFee,
  applyProtocolFee,
  findPoolSolReserves,
} from "@unstake-it/sol";

const plannedUnstakeAmountLamports = new BN(1_000_000_000);

// this address is 3rBnnH9TTgd3xwu48rnzGsaQkSr1hR64nY71DrDt6VrQ on mainnet
const POOL_RESERVES_ADDRESS = await findPoolSolReserves(
  UNSTAKE_PROGRAM,
  UNSTAKE_POOL_ADDRESS
);
const availableLiquidityLamports =
  await UNSTAKE_PROGRAM.provider.connection.getBalance(POOL_RESERVES_ADDRESS);
const { incomingStake } = await UNSTAKE_PROGRAM.account.pool.fetch(
  UNSTAKE_POOL_ADDRESS
);

const estFeeDeductedLamports = applyFee(FETCHED_FEE_DATA, {
  poolIncomingStake: incomingStake,
  solReservesLamports: new BN(availableLiquidityLamports),
  stakeAccountLamports: plannedUnstakeAmountLamports,
});

// calculate the part of the fees that is levied as protocol and referral fees
const { protocolLamports, referrerLamports } = applyProtocolFee(
  FETCHED_PROTOCOL_FEE_DATA,
  estFeeDeductedLamports
);

Unstake

Create and send the unstake transaction

import BN from "bn.js";
import { deactivateStakeAccountTx, unstakeTx } from "@unstake-it/sol";
// use solana-stake-sdk's util functions for fetching and processing stake accounts
import { getStakeAccount, stakeAccountState } from "@soceanfi/solana-stake-sdk";

// stakeAccountPubkey defined elsewhere

const { data: stakeAccount } = await getStakeAccount(
  UNSTAKE_PROGRAM.provider.connection,
  stakeAccountPubkey
);

const tx = await unstakeTx(UNSTAKE_PROGRAM, {
  stakeAccount: stakeAccountPubkey,
  poolAccount: UNSTAKE_POOL_ADDRESS,
  unstaker: UNSTAKE_PROGRAM.provider.wallet.publicKey,
  protocolFee: PROTOCOL_FEE,

  // You can optionally add a `referrer` pubkey
  // to receive referral bonuses in SOL.
  // You can use `applyProtocolFee()` to calculate how much
  // this amount will be for a given unstake fee amount.
  // (see example in #estimate-fees above)
  referrer: MY_SOL_ACC_PUBKEY,
});

// deactivateStakeAccount is a permissionless crank instruction that allows
// the pool to deactivate the stake accounts it receives so that it can
// reclaim the SOL liquidity on the next epoch.
// You can help our operations by including it in the same transaction
// that unstakes the stake account (:
const {
  epochInfo: { epoch },
} = await UNSTAKE_PROGRAM.provider.connection.getEpochInfo();
const stakeState = stakeAccountState(stakeAccount, new BN(epoch));
if (stakeState === "active" || stakeState === "activating") {
  tx.add(
    await deactivateStakeAccountTx(unstakeProgram, {
      stakeAccount: stakeAccountPubkey,
      poolAccount: UNSTAKE_POOL_ADDRESS,
    })
  );
}

const signature = await UNSTAKE_PROGRAM.provider.sendAndConfirm(tx);

UnstakeWsol

We also provide an instruction for unstaking SOL to wrapped SOL token accounts. Its usage is the same as Unstake; simply replace unstakeTx() with unstakeWsolTx() in the example above.

Reclaim Stake

ReclaimStake is a permissionless crank instruction that returns SOL from previously unstaked stake accounts that have now successfully deactivated to the pool's SOL reserves. This increases the liquidity available for unstaking and gives users a better rate.

import { Transaction } from "@solana/web3.js";
import {
  fetchLiquidityPoolStakeAccounts,
  reclaimStakeAccountTx,
} from "@unstake-it/sol";

const { inactive: stakeAccountsToReclaim } =
  await fetchLiquidityPoolStakeAccounts(UNSTAKE_PROGRAM, UNSTAKE_POOL_ADDRESS);

// You can fit 5 reclaimStakeAccount instructions into one tx before running into packet size limits
const CHUNK_SIZE = 5;
const chunks = [];
for (let i = 0; i < stakeAccountsToReclaim.length; i += CHUNK_SIZE) {
  chunks.push(stakeAccountsToReclaim.slice(i, i + CHUNK_SIZE));
}

const signatures = await Promise.all(
  chunks.map(async (chunk) => {
    const txs = await Promise.all(({ accountId }) =>
      reclaimStakeAccountTx(UNSTAKE_PROGRAM, {
        poolAccount: UNSTAKE_POOL_ADDRESS,
        stakeAccount: accountId,
      })
    );
    const txToSend = txs.reduce(
      (combined, tx) => combined.add(tx),
      new Transaction()
    );
    return UNSTAKE_PROGRAM.provider.sendAndConfirm(txToSend);
  })
);

Take Flash Loan

Create a transaction that takes a flash loan, executes a passed transaction, and repays the flash loan with interest. Imagine if you wanted a transaction that takes a flash loan of 1000 SOL, arbs those 1000 SOL for 1 SOL profit and returns flash loan with interest. To do that, you would create the arb 1000 SOL for 1 SOL profit transaction with the assumption that the 1000 SOL is gonna be in your SOL account and then call takeFlashLoanTx(UNSTAKE_PROGRAM, 1000 SOL (in lamports), arbTransaction, flashLoanAccounts) to mutate the arbTransaction and then send it.

import { Transaction } from "@solana/web3.js";
import { takeFlashLoanTx, findProtocolFeeAccount } from "@unstake-it/sol";
import BN from "bn.js";

const [protocolFeeAddr] = await findProtocolFeeAccount(
  UNSTAKE_PROGRAM.programId
);
const protocolFee = {
  publicKey: protocolFeeAddr,
  account: await UNSTAKE_PROGRAM.account.protocolFee.fetch(protocolFeeAddr),
};

const arbTransaction = new Transaction();
// add your instructions to arbTransaction here
// ...

const tx = await takeFlashLoanTx(
  UNSTAKE_PROGRAM,
  new BN(1_000_000_000_000),
  arbTransaction,
  {
    to: wallet.publicKey,
    poolAccount: UNSTAKE_POOL_ADDRESS,
    protocolFee,
  }
);

return UNSTAKE_PROGRAM.provider.sendAndConfirm(tx);

Readme

Keywords

none

Package Sidebar

Install

npm i @unstake-it/sol

Weekly Downloads

113

Version

2.0.0

License

MIT

Unpacked Size

598 kB

Total Files

182

Last publish

Collaborators

  • socean