Docs pages

API and SDK

Everything the app does goes through a public HTTP API, and you can use it from your own code. The TypeScript SDK, @hourglass/contract, wraps every operation with its types and includes the exact settlement, auction and collateral arithmetic as pure functions.

Endpoint

The API is served at https://hourglasscompute.com/v1. Responses share one envelope:

{ ok: true, data: … , meta: { requestId } }
{ ok: false, error: { code, message, retryable, fieldErrors, retryAfterSeconds }, meta: { requestId } }

USDC amounts and prices are strings of base units (6 decimals: "2200000" is 2.20). Hours are exact decimal strings.

Install the SDK

npm install https://hourglasscompute.com/sdk/hourglass-contract.tgz @solana/web3.js tweetnacl
import { createClient, previewSettlement } from '@hourglass/contract'

const api = createClient({ baseUrl: 'https://hourglasscompute.com' })
const series = await api.call('series.list', { region: 'NA' })

The previews run locally, without a request: previewAuction clears an example book, previewSettlement splits a ticket's escrow for given certified hours, previewCollateral prices an ask's collateral.

Sign in from a script

Reads of public data need no session. Account reads and actions need a wallet session: request a challenge, sign it with the wallet's key, and verify. In Node the client keeps the session cookie for you.

import { Keypair, VersionedTransaction } from '@solana/web3.js'
import nacl from 'tweetnacl'

const wallet = Keypair.fromSecretKey(/* your 64-byte key */)
const challenge = await api.call('auth.nonce', { wallet: wallet.publicKey.toBase58(), origin: 'https://hourglasscompute.com' })
if (!challenge.ok) throw new Error(challenge.error.message)
const signature = nacl.sign.detached(new TextEncoder().encode(challenge.data.message), wallet.secretKey)
const session = await api.call('auth.verify', {
  challengeId: challenge.data.challengeId,
  wallet: wallet.publicKey.toBase58(),
  signatureBase64: Buffer.from(signature).toString('base64'),
})
if (!session.ok) throw new Error(session.error.message)
const write = { csrfToken: session.data.csrfToken }

Send csrfToken with every write. A session lasts an hour.

Run an action

Every change on chain runs the same pipeline: check, prepare, sign, submit, wait.

import { randomUUID } from 'node:crypto'

const action = { kind: 'place_order', seriesId, side: 'bid', pricePerGpuHour: '2400000', nodeWeeks: 1 } as const

const check = await api.call('actions.preflight', { action }, write)
if (!check.ok || !check.data.allowed) throw new Error('not allowed now')

const prepared = await api.call('actions.prepare', { action }, { ...write, idempotencyKey: randomUUID() })
if (!prepared.ok || prepared.data.signing.kind !== 'solana_v0') throw new Error('not signable')

const tx = VersionedTransaction.deserialize(Buffer.from(prepared.data.signing.transactionBase64, 'base64'))
tx.sign([wallet])
const receipt = await api.call(
  'actions.submit',
  { preparedActionId: prepared.data.id, mode: 'signed', signedTransactionBase64: Buffer.from(tx.serialize()).toString('base64') },
  { ...write, idempotencyKey: randomUUID() },
)
// then poll actions.status with receipt.data.id until it is confirmed and indexed

The service signs nothing on your behalf: the transaction you get back is the exact one the programs will run, and the submit step rejects any change to it other than compute-budget and wallet guard instructions a wallet adds. Retry a submit with the same idempotency key and the same signed bytes.

Operations

Group Operations Access
Market data spec.get, series.list, series.get, markets.book, statistics, buyback.get public
Previews preview.settlement, preview.collateral, preview.calendar public
Sign-in auth.nonce, auth.verify, auth.session, auth.logout public, session
Your account portfolio.get, tickets.get, participant.apply, participant.status session
Roles seller.get, nodes.submit, nodes.list, lender.get, inspector.get that role
Actions actions.preflight, actions.prepare, actions.submit, actions.status session
Delivery access.handoff, access.get, access.confirm, evidence.context, evidence.submit session, agent signature

Writes are rate-limited per address and per wallet; a RATE_LIMITED error carries retryAfterSeconds.