Lucidblock Positions API

v0.1 · Private alpha: partial coverage

Read-only Solana DeFi positions with explicit coverage and sourced USD values. Give it a wallet address; it returns the wallet's positions in the protocols it supports (staking, liquid staking, perpetuals, lending and Raydium liquidity so far), every leg with an exact amount from chain state, a USD value only where one can be sourced (null otherwise), and the coverage behind the answer.

What it is not. Totals cover the supported protocols only: they are not wallet net worth. It is not a price oracle and its prices are not executable quotes. It never asks for a wallet connection or a signature, and it never sends a transaction.

Quickstart

During the private alpha every /v1/* route except /v1/health needs an access token (ask at <CONTACT_EMAIL>). The first request for a wallet usually returns 202 with a job to poll; once the scan finishes (typically a few seconds), the same request returns 200 with the snapshot.

BASE_URL="https://api.lucidblock.xyz"
W="G4VmLamEV6h15gndx7nHn8TTcu7fJkBBNVN3wTugMNtH"
AUTH="Authorization: Bearer $LUCIDBLOCK_TOKEN"

curl -s -H "$AUTH" "$BASE_URL/v1/solana/wallets/$W/defi-positions"            # 202: {job_id, poll_url, retry_after_seconds}
curl -s -H "$AUTH" "$BASE_URL/v1/solana/wallets/$W/defi-positions/jobs/<job_id>"  # poll until succeeded or partial
curl -s -H "$AUTH" "$BASE_URL/v1/solana/wallets/$W/defi-positions"            # 200: the snapshot

Honour Retry-After and retry_after_seconds. Poll the job (cost 1), not the positions (cost 5).

Endpoints

Method and path Purpose Cost
GET /v1/health Service health: ok, degraded or down. No token needed. 1
GET /v1/solana/wallets/{wallet}/defi-positions Wallet snapshot (200) or a queued scan (202). Query: protocols, include, include_rewards_in_total, freshness_seconds, limit, cursor. 5
GET /v1/solana/wallets/{wallet}/defi-positions/jobs/{jobId} Scan job status: queued, running, succeeded, partial, failed, expired. 1
GET /v1/solana/protocols Protocol adapters and their status (verified, awaiting review, quarantined, disabled). 1
GET /v1/solana/protocols/candidates Programs seen but not supported (experimental; no wallet data). 1
GET /v1/solana/protocols/{programId}/discovery Discovery detail for one program (experimental; no wallet data). 1
GET /v1/solana/coverage Service-wide coverage: categories covered and the release status. 1
GET /openapi.json The OpenAPI 3.1 contract. No token, no rate-limit cost. 0
GET /assets/v1/{file} First-party token and protocol logo files referenced by metadata. No token, no rate-limit cost. 0
GET / This page. No token, no rate-limit cost. 0

Reading a snapshot

  1. Coverage first. coverage.status is complete_for_supported_protocols, partial or stale. Each protocol in coverage.protocols[] is complete, partial, error, not_applicable or skipped (with an error_code). The absence of a position means something only for protocols listed complete.
  2. Then totals. totals.total_value_usd is non-null only when coverage is complete and every counted leg is priced; otherwise it is null and totals.total_null_reasons says why. totals.known_value_usd is the sum of the legs we could price (priced plus indicative): it is not a total, and not a lower bound when a debt is unpriced.
  3. Then positions and legs. Each position has assets, debts (positive amounts) and rewards legs. Each leg carries amount_raw (atomic integer) and amount (exact decimal), value_usd, pricing_source, valuation_status, in_totals and exclusion_reason.

Null, not zero. Unknown is null, never "0". Amounts and USD values are decimal strings: parse them with a decimal library, not floats.

Status vocabulary

Leg valuation_status

Coverage statuses

owner_role

Errors and limits

Every error has the same envelope, and every response carries X-Request-Id:

{ "error": { "code": "INVALID_ADDRESS", "message": "…", "details": { } }, "request_id": "…" }
HTTP Codes
400 INVALID_PARAMETER, INVALID_ADDRESS, UNSUPPORTED_PROTOCOL, CURSOR_EXPIRED
401 UNAUTHORIZED (missing or wrong access token)
404 / 405 NOT_FOUND, JOB_NOT_FOUND, METHOD_NOT_ALLOWED
429 RATE_LIMITED (details.reason: per_ip_rate or cold_scan_limit)
500 INTERNAL
503 DB_UNAVAILABLE, QUEUE_UNAVAILABLE, SNAPSHOT_UNAVAILABLE, SCAN_FAILED, RPC_BUDGET_EXHAUSTED, CONFIG_INVALID

Rate limits: 60 units per minute per IP. A wallet read costs 5 units; everything else under /v1 costs 1. At most 20 new wallet scans per IP per hour (cold_scan_limit). Responses carry RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; a 429 carries Retry-After. Retry only 429 and 503, after Retry-After.