> ## 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.

# Presales

> Read public presale state and your own participation with the Blank API v2.

# Presales

Presales are read-only in the public API. You can list every presale, read one presale's public state, and read the participation record for the wallet your API key is pinned to. There are no presale write operations in v2 — deposits, claims, and refunds happen in the Blank app.

For the product rules behind caps, allocations, and settlement, see [Pre-Raise](/docs/launching-tokens/pre-raise).

## Operations

| Operation                 | HTTP                               | SDK                                       | Auth      | Scope           | Caching                                                |
| ------------------------- | ---------------------------------- | ----------------------------------------- | --------- | --------------- | ------------------------------------------------------ |
| `listPresales`            | `GET /presales`                    | `blank.presales.list(query)`              | Anonymous | —               | `public, max-age=10, stale-while-revalidate=30` + ETag |
| `getPresale`              | `GET /presales/{id}`               | `blank.presales.get(presaleId)`           | Anonymous | —               | `public, max-age=5, stale-while-revalidate=20` + ETag  |
| `getPresaleParticipation` | `GET /presales/{id}/participation` | `blank.presales.participation(presaleId)` | API key   | `presales:read` | `private, no-store`                                    |

Base URL is `https://api.blank.build/api/v2`. See [Scopes](/docs/for-developers/scopes) for how to grant `presales:read`, and [Conventions](/docs/for-developers/conventions) for the pagination, caching, rate-limit, and retry contract.

## Client setup

API keys are server-side only. Never ship one to a browser.

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

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

Every SDK call resolves to `{ data, metadata, response }`. `data` is the parsed payload, `metadata` carries the request ID and rate-limit state, and `response` is the raw `Response`.

## List presales

`listPresales` is newest-first and cursor paginated. Pass `cursor` and `limit` (1–100, default 50).

```ts theme={null}
const first = await blank.presales.list({ limit: 25 });

for (const presale of first.data.data) {
  console.log(presale.id, presale.status, presale.totalDepositedLamports);
}

if (first.data.page.hasMore) {
  const next = await blank.presales.list({
    limit: 25,
    cursor: first.data.page.nextCursor ?? undefined,
  });
  console.log(next.data.data.length);
}
```

The raw response is a standard cursor collection:

```json theme={null}
{
  "data": [
    {
      "id": "3f6b4d10-9a52-4f2b-8f22-6c8a1f0e5d31",
      "mintAddress": "So11111111111111111111111111111111111111112",
      "status": "active",
      "minCapLamports": "50000000000",
      "maxCapLamports": "250000000000",
      "perWalletCapLamports": "5000000000",
      "creatorCommitmentLamports": "10000000000",
      "totalDepositedLamports": "118420000000",
      "participantCount": 412,
      "publicStartAt": "2026-08-09T12:00:00Z",
      "deadline": "2026-08-12T12:00:00Z",
      "allocationsBps": { "sale": 6000, "pool": 3000, "burn": 1000 },
      "createdAt": "2026-08-08T09:14:02Z",
      "updatedAt": "2026-08-10T07:31:44Z"
    }
  ],
  "page": {
    "nextCursor": "eyJ2IjoxLCJzb3J0IjpbIjIwMjYtMDgtMDlUMDA6MDA6MDBaIl19",
    "hasMore": true
  }
}
```

Anonymous reads work over plain HTTP with no credentials:

```bash theme={null}
curl -s "https://api.blank.build/api/v2/presales?limit=25"
```

Both list and detail responses carry an ETag. Send it back as `If-None-Match` to get a `304` instead of a body.

## Presale fields

| Field                       | Type                    | Notes                                                                            |
| --------------------------- | ----------------------- | -------------------------------------------------------------------------------- |
| `id`                        | UUID                    | Presale identifier used in the detail and participation routes.                  |
| `mintAddress`               | Base58 address          | The token being raised for.                                                      |
| `status`                    | Enum                    | `draft`, `initializing`, `active`, `settling`, `settled`, `refunding`, `failed`. |
| `minCapLamports`            | Unsigned integer string | Soft cap the raise must clear.                                                   |
| `maxCapLamports`            | Unsigned integer string | Hard cap.                                                                        |
| `perWalletCapLamports`      | Unsigned integer string | Maximum a single wallet may deposit.                                             |
| `creatorCommitmentLamports` | Unsigned integer string | The creator's own committed amount.                                              |
| `totalDepositedLamports`    | Unsigned integer string | Sum of all deposits so far.                                                      |
| `participantCount`          | Integer                 | Distinct depositing wallets.                                                     |
| `publicStartAt`             | RFC 3339                | When the public phase opens.                                                     |
| `deadline`                  | RFC 3339                | When the raise closes.                                                           |
| `allocationsBps`            | Object                  | Integer basis points (0–10000) under `sale`, `pool`, and `burn`.                 |
| `createdAt` / `updatedAt`   | RFC 3339                | Record timestamps.                                                               |

<Note>
  All lamport fields are unsigned integer strings. 1 SOL = 1,000,000,000
  lamports. Parse them with `BigInt`, never `Number` — a large raise overflows
  IEEE-754 precision and silently rounds.
</Note>

```ts theme={null}
const presale = await blank.presales.get(
  "3f6b4d10-9a52-4f2b-8f22-6c8a1f0e5d31"
);

const raisedSol =
  Number(BigInt(presale.data.totalDepositedLamports) / 1_000_000n) / 1_000;
console.log(
  `${raisedSol} SOL raised from ${presale.data.participantCount} wallets`
);
```

`getPresale` returns public state only. Custody internals are deliberately excluded from the API surface.

## Your participation

`getPresaleParticipation` requires an API key with the `presales:read` scope and is never cached (`private, no-store`).

```ts theme={null}
const participation = await blank.presales.participation(
  "3f6b4d10-9a52-4f2b-8f22-6c8a1f0e5d31"
);

console.log(participation.data.status, participation.data.depositedLamports);
```

<Warning>
  This route returns participation **only for the wallet pinned to your API
  key**. A v2 API key is bound to one Blank account, and that account has one
  verified wallet. There is no parameter for reading someone else's
  participation, and no key can do it.
</Warning>

| Field                        | Type                              | Notes                                                            |
| ---------------------------- | --------------------------------- | ---------------------------------------------------------------- |
| `presaleId`                  | UUID                              | The presale this record belongs to.                              |
| `walletAddress`              | Base58 address                    | Always your key's pinned wallet.                                 |
| `depositedLamports`          | Unsigned integer string           | Total deposited by that wallet.                                  |
| `tokenAllocationRaw`         | Unsigned integer string, nullable | Raw token amount allocated; `null` until allocation is computed. |
| `status`                     | Enum                              | `active`, `claimable`, `claimed`, `refundable`, `refunded`.      |
| `claimTransactionSignature`  | String, nullable                  | Set once the claim confirms on chain.                            |
| `refundTransactionSignature` | String, nullable                  | Set once the refund confirms on chain.                           |
| `updatedAt`                  | RFC 3339                          | Last change to this record.                                      |

`tokenAllocationRaw` is a raw on-chain amount in the token's base units — it is not scaled by decimals. Keep it as a `BigInt` and apply the mint's decimals yourself when displaying it.

A useful polling shape: watch `status` move from `active` to `claimable` or `refundable`, then to `claimed` or `refunded` once the corresponding signature appears.

```ts theme={null}
const { data } = await blank.presales.participation(presaleId);

if (data.status === "claimable" && data.tokenAllocationRaw !== null) {
  console.log(`Allocation ready: ${data.tokenAllocationRaw} base units`);
}
```

## Errors

| Status | Code                              | What it means                                                                                                   |
| ------ | --------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| 400    | `cursor_invalid`                  | The cursor is malformed, tampered with, or does not match the current query. Restart the walk without a cursor. |
| 401    | `invalid_api_key`                 | Missing, malformed, or revoked key. See [Authentication](/docs/for-developers/authentication).                       |
| 403    | `insufficient_scope`              | The key lacks `presales:read`. See [Scopes](/docs/for-developers/scopes).                                            |
| 404    | `presale_not_found`               | No presale with that ID.                                                                                        |
| 404    | `presale_participation_not_found` | Your pinned wallet has no participation record in that presale.                                                 |

Errors use the shared problem-details body documented in [Error Model](/docs/reference/errors). If a call behaves unexpectedly, [Troubleshooting](/docs/reference/troubleshooting) covers the common causes.
