ALMERU Launch on X Connect
Docs · Build

SDK reference

A typed client over the same surface the agent uses. Reads are open; anything that spends requires a key and an authorized wallet.

Install

npm i @almeru/sdk        # or: pnpm add @almeru/sdk

The package ships ESM and CJS builds with bundled type declarations and no runtime dependencies beyond the platform fetch. Node 18+ or any modern runtime with global fetch works unmodified.

Constructing a client

import { Almeru } from '@almeru/sdk';

const almeru = new Almeru({
  apiKey: process.env.ALMERU_API_KEY,   // required for writes, optional for reads
  baseUrl: 'https://api.almeru.app/v1', // default
  chainId: 4663,                        // Robinhood Chain, default
  timeoutMs: 15000,                     // per request, default 15000
  maxRetries: 2,                        // GET only; never retries POST /launches
});

Retries apply to idempotent reads only. Writes are never replayed automatically — pass an idempotencyKey and retry them yourself if you need at-most-once semantics across process restarts.

Shared types

type ChainId = 4663;
type ProtocolKind = 'treasury' | 'miner';
type RoundPhase = 'open' | 'lock' | 'draw' | 'settle';
type Decimal = string;                  // fixed-point, never a JS number

interface Page<T> {
  data: T[];
  hasMore: boolean;
  nextCursor: string | null;
}

Every monetary value crosses the wire as a decimal string. The client never parses them into number, because a token balance with eighteen decimals does not survive a float.

protocols

protocols.list(params?: {
  kind?: ProtocolKind;
  status?: 'live' | 'settling' | 'retired';
  limit?: number;                       // 1–100, default 25
  cursor?: string | null;
}): Promise<Page<ProtocolSummary>>

protocols.get(slug: string): Promise<Protocol>
const page = await almeru.protocols.list({ kind: 'treasury', limit: 2 });

// {
//   data: [
//     {
//       slug: 'harbor-reserve',
//       name: 'Harbor Reserve',
//       ticker: 'HRBR',
//       kind: 'treasury',
//       status: 'live',
//       chainId: 4663,
//       address: '0x7b41c0a2e5d93f6a8c1b0e47d2f95a3c6b8e1d40',
//       createdAt: '2026-05-02T09:14:31Z',
//       treasuryValue: '184320.55',
//       backingPerToken: '1.0742'
//     },
//     { slug: 'tin-line', name: 'Tin Line', ticker: 'TINL', /* … */ }
//   ],
//   hasMore: true,
//   nextCursor: 'c3Rhcl9oYXJib3ItcmVzZXJ2ZQ'
// }

protocols.get() returns the summary above plus description, imageUrl, deployer, the full bootstrapSplit (emissionsBps: 8000, stakingBps: 1000, reserveBps: 1000) and the routing block. It throws not_found for an unknown slug rather than returning null.

miners

miners.list(params?: {
  phase?: RoundPhase;
  limit?: number;                       // 1–100, default 25
  cursor?: string | null;
}): Promise<Page<MinerSummary>>

miners.get(id: string): Promise<Miner>   // accepts an id or a slug
const miner = await almeru.miners.get('mnr_8fq2k1x');

// {
//   id: 'mnr_8fq2k1x',
//   slug: 'copper-field',
//   name: 'Copper Field',
//   ticker: 'CPFD',
//   chainId: 4663,
//   tiles: 25,
//   round: {
//     index: 41207,
//     phase: 'open',
//     epoch: 27714055,              // 60-second global epoch
//     locksAt: '2026-07-24T11:04:00Z',
//     winnerReward: '12.480000',
//     tokenPot: '1.248000',         // fixed 10:1 against the winner reward
//     entries: 63,
//     entryValue: '418.75'
//   },
//   odds: { tile: '1/25', tokenPotRelease: '1/333' },
//   schedule: { fundedRoundsTotal: 525600, fundedRoundsRemaining: 484393 },
//   routing: { operationsBps: 100, losingSideRecycledBps: 1000, deploymentFeeBps: 100 }
// }

The epoch field is the value a locked round commits to. Re-derive a settled draw yourself by applying the project and round separators to it and reducing modulo tiles; the arithmetic is described in Miner protocol.

launches

launches.create(input: {
  kind: ProtocolKind;
  name: string;                         // 1–32 chars
  ticker: string;                       // 2–8 chars
  bootstrap: { asset: string; amount: Decimal };
  xUserId: string;                      // owner of the derived wallet
  imageUrl?: string;
  description?: string;
  simulateOnly?: boolean;               // stops after the dry run
  idempotencyKey?: string;
}): Promise<LaunchReceipt>

launches.status(id: string): Promise<LaunchStatus>
const receipt = await almeru.launches.create({
  kind: 'miner',
  name: 'Copper Field',
  ticker: 'CPFD',
  bootstrap: { asset: 'USDC', amount: '1200.00' },
  xUserId: '1596180932847104000',
  idempotencyKey: 'copper-field-2026-07-24-01',
});

// { id: 'lnc_3v7pq0m', state: 'queued', quote: {
//     bootstrap: '1200.00', deploymentFee: '12.00', total: '1212.00',
//     capRemaining24h: '12300.00' } }

const status = await almeru.launches.status(receipt.id);

// {
//   id: 'lnc_3v7pq0m',
//   state: 'settled',                  // queued | simulating | executing | settled | failed
//   kind: 'miner',
//   steps: [
//     { name: 'parse',    status: 'ok' },
//     { name: 'validate', status: 'ok' },
//     { name: 'quote',    status: 'ok' },
//     { name: 'simulate', status: 'ok', block: 19442806 },
//     { name: 'execute',  status: 'ok', block: 19442807,
//       tx: '0x4c1d9a70be25f3c8d1a6470b9e83f215cc47ab30' }
//   ],
//   protocol: { slug: 'copper-field', address: '0x5d38…9c02' }
// }

Simulation always runs exactly one block before execution, so a settled launch has consecutive block numbers in its step list. A failed simulation short-circuits with state: 'failed' and never spends.

wallet

wallet.derive(xUserId: string): Promise<DerivedWallet>
wallet.authorize(input: {
  xUserId: string;
  siweMessage: string;                  // EIP-4361
  siweSignature: string;
  policySignature: string;              // EIP-712 typed data
  caps: { perLaunch: Decimal; rolling24h: Decimal; asset: string };
}): Promise<AuthorizationGrant>
await almeru.wallet.derive('1596180932847104000');

// {
//   xUserId: '1596180932847104000',
//   address: '0x2ea9c4b7f18d3056a9c7e21b40fd7c85ba36d0f1',
//   chainId: 4663,
//   deployed: false,                   // derivable before it exists
//   rootOwner: null,
//   agentSigner: { granted: false, method: 'executeLaunch' },
//   balance: '0.00'
// }

wallet.derive() is a pure read and needs no key. It is safe to call for any account id and returns the same address forever, deployed or not. wallet.authorize() submits the two signatures gathered in the browser; it never sees a private key and returns the live grant, including the caps the contract will enforce.

treasury

treasury.read(address: string): Promise<TreasurySnapshot>

// {
//   address: '0x7b41c0a2e5d93f6a8c1b0e47d2f95a3c6b8e1d40',
//   chainId: 4663,
//   asset: 'USDC',
//   treasuryValue: '184320.55',
//   supply: '171589.03',
//   backingPerToken: '1.0742',
//   reserve: { timelocked: '18432.05', unlocksAt: '2026-08-01T00:00:00Z' },
//   staking: { streamsOpen: 214, vestingDays: 7, returnThresholdBps: 1000 },
//   updatedAt: '2026-07-24T11:02:07Z'
// }

returnThresholdBps: 1000 is the 10% floor below which unvested stake can no longer be returned mid-stream.

Pagination

Every list method uses opaque forward cursors. Never construct one; pass back exactly what you received. A cursor stays valid across pages of the same query and is not portable between different filters.

// manual
let cursor = null;
do {
  const page = await almeru.miners.list({ phase: 'open', limit: 100, cursor });
  for (const m of page.data) console.log(m.slug, m.round.entryValue);
  cursor = page.nextCursor;
} while (cursor);

// or let the client walk it
for await (const m of almeru.miners.paginate({ phase: 'open' })) {
  console.log(m.slug);
}

Errors

Every non-2xx response becomes an AlmeruError. The class carries a stable machine code, the HTTP status, the request id to quote in a support thread, and whether retrying could plausibly help.

import { Almeru, AlmeruError } from '@almeru/sdk';

try {
  await almeru.launches.create({ /* … */ });
} catch (err) {
  if (err instanceof AlmeruError) {
    console.error(err.code, err.status, err.requestId, err.retryable);
    if (err.code === 'cap_exceeded') console.error(err.details.capRemaining24h);
  } else {
    throw err;
  }
}
CodeStatusRetryableMeaning
invalid_request400NoA field is missing or malformed. details.field names it.
unauthorized401NoMissing, malformed or revoked API key.
forbidden_scope403NoThe key is valid but has no write scope, or the grant is not live.
not_found404NoUnknown slug, id or address.
ticker_taken409NoThe ticker is already deployed by this account.
cap_exceeded422NoPer-launch or rolling 24-hour cap would be breached.
insufficient_funds422NoThe derived wallet cannot cover bootstrap plus the 1% deployment fee.
simulation_failed422NoThe dry run reverted. Nothing was spent. details.revertReason is set.
rate_limited429YesBack off for details.retryAfterMs.
upstream_unavailable503YesChain or indexer unavailable. Safe to retry reads.
The SDK is a thin, typed wrapper — every method maps to one documented endpoint. If you would rather call the surface directly, the shapes above are identical in the REST API (snake_case on the wire, camelCase in the client).

Keep reading