Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .well-known/beacon.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@
}
],
"capabilities": ["getHoldings", "getNftsForOwner", "getNftMetadata", "getProfilePicture"],
"runtime": "hyper-on-bun",
"transport": {
"http": ["GET /holdings/{address}", "GET /nfts/{contract}/owner/{address}", "GET /nfts/{contract}/{tokenId}"],
"openapi": "/openapi.json (3.1) + /docs (Swagger UI)",
"mcp": ["GET /.well-known/mcp.json (manifest)", "POST /mcp (JSON-RPC 2.0)"]
},
"cycle_state": {
"status": "candidate",
"since": "2026-05-23",
"next_review": "2026-08-21"
},
"_comment": "Library stage. Live counts + ACVP envelope wired to belt-gateway; per-token ownership (getNftsForOwner/getProfilePicture rich path) + MCP/HTTP deployment pending the sonar owner-token index (docs/sonar-ownership-gap.md). composes_with omitted pending port-schema sealing (ADR-007 App A.2 / ADR-008 §D-11) — consumes sonar-api + storage-api per README."
"_comment": "Hyper (hyperjs.ai) service on Bun — consumed over HTTP + MCP, NOT an npm package (DEP-2). One route declaration per endpoint (src/routes.ts) generates runtime + OpenAPI 3.1 + MCP; route handlers call the domain core (src/inventory.ts). Hyper is source-distributed under src/hyper/ (hyper.lock.json). Live counts + ACVP envelope wired to belt-gateway; per-token ownership (Token + CandiesHolderBalance) activated against sonar cycle/sonar-belt-factory — covered hermetically, pending live deploy/reindex verification (docs/sonar-ownership-gap.md). composes_with omitted pending port-schema sealing (ADR-007 App A.2 / ADR-008 §D-11) — consumes sonar-api + storage-api per README."
}
54 changes: 40 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,38 @@
- Does **NOT** proxy chain RPC — it consumes `freeside-sonar` for indexed reads.
- Does **NOT** own metadata — that's the codex (today) / `freeside-storage` (sovereign target).

## API
## Service (HTTP + MCP)

```ts
import { getHoldings, getNftsForOwner, getNftMetadata } from "@freeside/inventory";
inventory-api is a **Hyper** (hyperjs.ai) service running on **Bun**, consumed
over the wire — **not an npm package** (honeyroad reads it over HTTP + MCP).
One route declaration per endpoint (`src/routes.ts`) generates the runtime,
the OpenAPI 3.1 document, and the MCP tool surface from a single source. The
route handlers are thin — they call the domain functions in `src/inventory.ts`
(the sonar ⨝ codex join + ACVP envelope), which remain the core.

```bash
bun run dev # hot-reload dev server (PORT, default 8787)
bun run start # production server
bun run openapi:emit # writes openapi.json (the consumer's drift-CI anchor)
bun run mcp:emit # writes mcp.json (MCP tool manifest)
bun run typecheck # tsc --noEmit (Bun tsconfig)
npm test # vitest — runs fully offline
```

| Method | Returns | Source |
|--------|---------|--------|
| `getHoldings(address)` | holdings + completeness envelope | sonar (counts) |
| `getNftsForOwner(address, contract)` | paginated NFTs w/ metadata | sonar ⨝ codex |
| `getNftMetadata(contract, tokenId)` | single MetadataDocument | codex |
| Route | Returns | Source | MCP tool |
|-------|---------|--------|----------|
| `GET /holdings/:address` | holdings (counts + tokenIds) + completeness envelope | sonar | `getHoldings` |
| `GET /nfts/:contract/owner/:address` | paginated NFTs w/ metadata | sonar ⨝ codex | `getNftsForOwner` |
| `GET /nfts/:contract/:tokenId` | single MetadataDocument | codex | `getNftMetadata` |

Discovery: `GET /openapi.json` (OpenAPI 3.1), `GET /docs` (Swagger UI),
`GET /.well-known/mcp.json` (MCP manifest), `POST /mcp` (MCP JSON-RPC 2.0),
`GET /health`.

The Hyper framework is **source-distributed** under `src/hyper/` (vendored via
`bun create hyper` + `hyper add openapi openapi-zod mcp` — yours to read/edit;
tracked in `hyper.lock.json`). The domain functions stay importable internally
via `index.ts` for the route handlers + tests.

## Modes

Expand All @@ -36,15 +57,20 @@ import { getHoldings, getNftsForOwner, getNftMetadata } from "@freeside/inventor
holder counts + a real ACVP envelope from the live belt. Fail-soft: unreachable → fixture + `degraded`.

```bash
SONAR_GRAPHQL_ENDPOINT=https://<belt-gateway-host>/v1/graphql npm test -- live-smoke
SONAR_GRAPHQL_ENDPOINT=https://<belt-gateway-host>/v1/graphql npx vitest run live-smoke
```

## Known gap
## Ownership activation (DEP-2)

Per-token current ownership (`owner → tokenIds`) is not yet published by the sonar belt
(`Token` entity empty for Mibera). Per ADR-008's belt model that index is **sonar's to publish**,
not inventory's to derive. Until it lands, live `getHoldings` returns real `tokenCount` with
`tokenIds: []`, and `getNftsForOwner` stays on fixtures. See [`docs/sonar-ownership-gap.md`](docs/sonar-ownership-gap.md).
Per-token current ownership (`owner → tokenIds`) is now wired to the sonar belt's
`Token` index (ERC-721) + `CandiesHolderBalance` (ERC-1155 Candies), merged to sonar's
`cycle/sonar-belt-factory` branch. Live `getHoldings` populates real `tokenIds` and
`getNftsForOwner` joins live ownership with codex metadata; both fail-soft to fixtures
when the index is unreachable. **Not yet verified against a live endpoint** — the belt
branch is merged but not yet deployed/reindexed, so the activation is covered hermetically
(`tests/live-ownership.test.ts` stubs the known belt schema shapes). Per ADR-008's belt
model that index is **sonar's to publish**, not inventory's to derive. See
[`docs/sonar-ownership-gap.md`](docs/sonar-ownership-gap.md).

## Provenance

Expand Down
Loading
Loading