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 slugconst 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;
}
}| Code | Status | Retryable | Meaning |
|---|---|---|---|
invalid_request | 400 | No | A field is missing or malformed. details.field names it. |
unauthorized | 401 | No | Missing, malformed or revoked API key. |
forbidden_scope | 403 | No | The key is valid but has no write scope, or the grant is not live. |
not_found | 404 | No | Unknown slug, id or address. |
ticker_taken | 409 | No | The ticker is already deployed by this account. |
cap_exceeded | 422 | No | Per-launch or rolling 24-hour cap would be breached. |
insufficient_funds | 422 | No | The derived wallet cannot cover bootstrap plus the 1% deployment fee. |
simulation_failed | 422 | No | The dry run reverted. Nothing was spent. details.revertReason is set. |
rate_limited | 429 | Yes | Back off for details.retryAfterMs. |
upstream_unavailable | 503 | Yes | Chain or indexer unavailable. Safe to retry reads. |