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.