# @meteora-ag/referral

TypeScript SDK for the Meteora Referral Staking Program on Solana.

**Program ID:** `refnrNncADccJPZEB5hHuiFVb3trY5LH39ykwMD59Ht`

## Installation

```bash
bun add @meteora-ag/referral
```

## Quick Start

### Initialize a Staking Pool

```ts
import { Connection, Keypair } from "@solana/web3.js";
import BN from "bn.js";
import { Referral } from "@meteora-ag/referral";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const payer = Keypair.generate();
const staking = Keypair.generate();

const transaction = await Referral.initializeStaking(connection, {
  owner: payer.publicKey,
  tokenMint,
  stakingPenaltyFeeReceiver: feeReceiver.publicKey,
  staking,
  payer: payer.publicKey,
  unstakeCooldownSeconds: new BN(86401),
  minStakingAmount: new BN(1_000_000),
  immediateUnstakePenaltyBps: 500, // 5%
});
```

### Interact with an Existing Pool

```ts
const referral = await Referral.create(connection, stakingAddress);

// Initialize escrow for a user
const escrowTx = await referral.initializeEscrow({
  owner: user.publicKey,
  payer: payer.publicKey,
});

// Stake tokens (signer must be the escrow owner)
const stakeTx = await referral.stake({
  escrowAddress,
  owner: user.publicKey,
  amount: new BN(10_000_000),
});

// Quote what the user receives at the current on-chain penalty.
const wrapper = referral.getStakingWrapper();
const { userReceiveAmount, penaltyFeeAmount } = wrapper.quoteImmediateUnstake(
  new BN(10_000_000),
);

// Immediate unstake. `maxImmediateUnstakePenaltyBps` is the ceiling you'll
// accept on the on-chain penalty bps; the tx reverts if the pool's current
// penalty exceeds this. To allow N bps of slippage above the cached value,
// pass `wrapper.getImmediateUnstakePenaltyBps() + N`.
const currentBps = wrapper.getImmediateUnstakePenaltyBps();
const immUnstakeTx = await referral.immediateUnstake({
  escrowAddress,
  owner: user.publicKey,
  maxAmount: new BN(10_000_000),
  maxImmediateUnstakePenaltyBps: currentBps + 100, // accept up to +1% slippage
  receiver: user.publicKey,
});

// Or unstake with cooldown
const { transaction: unstakeTx, unstakeKeypair } = await referral.unstake({
  escrowAddress,
  owner: user.publicKey,
  payer: user.publicKey,
  maxAmount: new BN(10_000_000),
});

// Withdraw after cooldown
const withdrawTx = await referral.withdraw({
  unstakeAddress: unstakeKeypair.publicKey,
  escrowAddress,
  owner: user.publicKey,
  rentReceiver: user.publicKey,
  receiver: user.publicKey,
});
```

## Staking Flow

```
User                                    Program
 |                                         |
 |-- initialize_escrow ------------------>|  Create escrow account
 |                                         |
 |-- stake(amount) ---------------------->|  Transfer tokens -> vault
 |                                         |
 |-- unstake(max_amount) ---------------->|  Start cooldown timer
 |         |                               |
 |         |-- withdraw ----------------->|  After cooldown: tokens -> user
 |         |                               |
 |         +-- cancel_unstake ----------->|  Return to staked state
 |                                         |
 +-- immediate_unstake(max_amount) ------>|  Deduct penalty, tokens -> user
```

## Instructions

### Permissionless

| Method              | Description                                                            |
| ------------------- | ---------------------------------------------------------------------- |
| `initializeStaking` | Create a staking pool with cooldown, minimum stake, and penalty config |
| `initializeEscrow`  | Create a per-user escrow account linked to a staking pool              |
| `stake`             | Deposit tokens into the vault                                          |
| `unstake`           | Begin a time-locked unstake with cooldown                              |
| `cancelUnstake`     | Cancel a pending unstake, returning tokens to staked state             |
| `withdraw`          | Claim tokens after the unstake cooldown has elapsed                    |
| `immediateUnstake`  | Instantly withdraw staked tokens, paying a penalty fee                 |
| `closeEscrow`       | Close an empty escrow account and reclaim rent                         |
| `claimPenaltyFee`   | Withdraw accumulated penalty fees to the configured fee receiver       |

### Permissioned (Owner)

| Method                     | Description                                                       |
| -------------------------- | ----------------------------------------------------------------- |
| `updatePenaltyFeeReceiver` | Change the account that receives penalty fees                     |
| `updateConfig`             | Modify staking pool parameters (cooldown, min stake, penalty bps) |
| `transferOwner`            | Transfer staking pool ownership to a new account                  |

### Configuration constraints

`unstakeCooldownSeconds` (passed to `initializeStaking` and `updateConfig`) must fall within the bounds exported from [`src/constant.ts`](./src/constant.ts); values outside the range are rejected on-chain with `ReferralError::InvalidInputParams`:

| Constant                           | Value                   |
| ---------------------------------- | ----------------------- |
| `MINIMUM_UNSTAKE_COOLDOWN_SECONDS` | `86_400` (1 day)        |
| `MAXIMUM_UNSTAKE_COOLDOWN_SECONDS` | `31_536_000` (365 days) |

## Account Wrappers

The SDK provides read-only wrappers for convenient state access:

- **`StakingWrapper`** — Pool state, config, and metrics
- **`EscrowWrapper`** — User escrow balances with `isEmpty()` / `canClose()` helpers
- **`UnstakeWrapper`** — Unstake record with `canWithdraw()` / `getTimeUntilWithdrawable()` cooldown helpers

## Development

```bash
bun install
bun run build
bun test
```
