> ## Documentation Index
> Fetch the complete documentation index at: https://blank.build/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Staking

> Read a token's staking pool, its leaderboard, and the staking positions owned by your API key's wallet.

# Staking

Three read operations. Two are anonymous, one requires an API key.

| Operation                   | HTTP                                     | SDK                                    | Auth                    |
| --------------------------- | ---------------------------------------- | -------------------------------------- | ----------------------- |
| `getStakingPool`            | `GET /tokens/{mint}/staking`             | `blank.staking.pool(mint)`             | Anonymous               |
| `listStakingLeaderboard`    | `GET /tokens/{mint}/staking/leaderboard` | `blank.staking.leaderboard(mint)`      | Anonymous               |
| `listOwnedStakingPositions` | `GET /tokens/{mint}/staking/positions`   | `blank.staking.positions(mint, query)` | API key, `staking:read` |

## Setup

The two anonymous operations need no key:

```ts theme={null}
import { BlankClient } from "@blankdotbuild/sdk";

const blank = new BlankClient();
```

For positions, pass a server-side key. The SDK refuses to accept one in a browser runtime.

```ts theme={null}
const blank = new BlankClient({ apiKey: process.env.BLANK_API_KEY });
```

## Amounts are raw integer strings

Fields ending in `Raw`, plus `accumulatedRewardsLamports`, are **unsigned integer strings** in the token's base units. `aprPercent` is a decimal string. Parse raw amounts with `BigInt` and decimals with a decimal library — never `Number`.

## Pool state

```ts theme={null}
const { data: pool } = await blank.staking.pool(mint);
```

| Field                        | Type                    | Notes                                                    |
| ---------------------------- | ----------------------- | -------------------------------------------------------- |
| `mintAddress`                | string                  |                                                          |
| `poolAddress`                | string                  | The on-chain staking pool.                               |
| `enabled`                    | boolean                 | Whether staking is on for this token.                    |
| `allocationBps`              | integer                 | 0–10,000. Share of fees routed to stakers. 100 bps = 1%. |
| `totalStakedRaw`             | unsigned integer string |                                                          |
| `totalWeightedStakeRaw`      | unsigned integer string | Stake after lock-period weighting.                       |
| `accumulatedRewardsLamports` | unsigned integer string |                                                          |
| `activeStakers`              | integer                 |                                                          |
| `aprPercent`                 | decimal string \| null  | Null when there is not enough data to compute it.        |
| `asOf`                       | RFC 3339                | When the pool state was computed.                        |
| `lastSyncedSlot`             | numeric string \| null  |                                                          |

Cached `public, max-age=10, stale-while-revalidate=30` with an `ETag`. Rate policy `public-read-cheap`.

## Leaderboard

The top 100 wallets by weighted stake.

```ts theme={null}
const { data } = await blank.staking.leaderboard(mint);

for (const entry of data.data) {
  console.log(entry.rank, entry.walletAddress, entry.weightedAmountRaw);
}
```

The response is `{ data: [...] }`. It is **not** cursor paginated — the top 100 is the whole result, and there is no way to page past it.

| Field               | Type                    |
| ------------------- | ----------------------- |
| `rank`              | integer                 |
| `walletAddress`     | string                  |
| `amountRaw`         | unsigned integer string |
| `weightedAmountRaw` | unsigned integer string |
| `positionCount`     | integer                 |

Ranking is by weighted stake, so a smaller position on a longer lock can outrank a larger one on a shorter lock. Cached `public, max-age=30, stale-while-revalidate=120` with an `ETag`. Rate policy `public-read-expensive`.

## Your positions

```ts theme={null}
const { data } = await blank.staking.positions(mint, { limit: 50 });

for (const position of data.data) {
  console.log(position.lockPeriod, position.amountRaw, position.lockEnd);
}
```

<Warning>
  `listOwnedStakingPositions` returns **only** positions owned by the wallet
  pinned to your API key. A v2 API key is bound to exactly one Blank account and
  that account's single verified wallet. It can never read another wallet's
  positions, and there is no parameter that changes this.
</Warning>

| Field                  | Type                                          | Notes                     |
| ---------------------- | --------------------------------------------- | ------------------------- |
| `id`                   | uuid                                          |                           |
| `mintAddress`          | string                                        |                           |
| `ownerWallet`          | string                                        | Always your key's wallet. |
| `funderWallet`         | string                                        | Who funded the position.  |
| `positionIndex`        | unsigned integer string                       |                           |
| `amountRaw`            | unsigned integer string                       |                           |
| `weightedAmountRaw`    | unsigned integer string                       |                           |
| `lockPeriod`           | `week` \| `month` \| `quarter` \| `half_year` |                           |
| `lockStart`            | RFC 3339                                      |                           |
| `lockEnd`              | RFC 3339                                      | When the lock expires.    |
| `transactionSignature` | string \| null                                |                           |
| `updatedAt`            | RFC 3339                                      |                           |

Cursor paginated with `cursor` and `limit`, returning `{ data: [...], page: { nextCursor, hasMore } }`. Never cached — the response is `private, no-store` on the `api-key-read` rate policy. See [Conventions](/docs/for-developers/conventions).

### Raw HTTP

From a server shell, never a browser:

```bash theme={null}
curl -sS \
  -H "Authorization: Bearer $BLANK_API_KEY" \
  "https://api.blank.build/api/v2/tokens/$MINT/staking/positions?limit=50"
```

## Reads only

The v2 API exposes staking **reads**. There is no public API to enable staking, change the allocation, stake, unstake, or top up rewards. Those are creator-dashboard and app flows.

See [Staking](/docs/launching-tokens/staking) for the product rules: lock periods, weighting, and how rewards accrue.

## Errors

| Code                     | Status | Meaning                                                       |
| ------------------------ | ------ | ------------------------------------------------------------- |
| `cursor_invalid`         | 400    | The cursor is malformed or no longer valid. Restart the walk. |
| `invalid_api_key`        | 401    | The key is missing, unknown, revoked, or expired.             |
| `insufficient_scope`     | 403    | The key lacks `staking:read`.                                 |
| `staking_pool_not_found` | 404    | No staking pool for that mint.                                |

Full problem-details shape lives in [Errors](/docs/reference/errors).

## Next

* [Fees](/docs/for-developers/fee-status) — the fee state that feeds `allocationBps`.
* [Tokens and Market Data](/docs/for-developers/market-data) — anonymous public reads.
* [Scopes](/docs/for-developers/scopes) — what each scope unlocks.
