Canonical backend service for storing, validating, indexing, and distributing Ownables on Base-focused EVM networks.
The Hub is not just a file host. It accepts uploaded Ownable archives, validates them against anchored and public event history, stores package and chain data separately, derives recipient availability, exposes that availability to wallets for discovery and import, and returns indexed event data so clients can verify Ownables independently.
For lockable NFT-backed Ownables, the Hub also acts as unlock-signing authority, but only when it has a validated copy of the corresponding Ownable.
- Accepts uploaded Ownable archives and validates them before making them available
- Stores package artifacts and canonical Ownable chain data separately
- Indexes relevant Anchor-contract EVM events for server-side and wallet-side verification
- Tracks and exposes public events needed to replay and verify Ownable state
- Derives which owner account an Ownable is currently available to
- Exposes Hub-backed recipient discovery for wallet import flows
- Serves package downloads, chain downloads, and indexed event history
- Maps NFT-backed Ownables for unlock-proof flows where applicable
- Signs unlock challenges for lockable NFT-backed Ownables when the Hub holds a validated copy
The current Hub model is built around validation and discovery rather than messaging.
- Sender-side transfer appends the private transfer event locally, then uploads the resulting archive to Hub
- Hub validates and replays the uploaded Ownable instead of writing private events itself
- Hub derives recipient availability from the resulting Ownable state
- Recipient wallets discover available Ownables directly from the Hub
- Wallets can fetch indexed anchor/public events from the Hub to perform their own verification
The Hub is intentionally not:
- a generic mailbox between wallets
- a notification product
- a pure file server
- a service that writes private Ownable history on behalf of wallets
The Hub is a verification boundary as much as a storage boundary.
It persistently indexes the relevant EVM events emitted through the Anchor contract. Those indexed events are used internally to validate uploaded Ownables against anchored history, and are also exposed so SDK wallets can independently verify that an Ownable's chain is consistent with the same anchor history.
In addition to anchor events, the Hub tracks and exposes public events. Together, the Ownable's chain, indexed anchor events, and public events provide the replay inputs needed to determine effective Ownable state, detect stale uploads, derive the current owner, and support import flows.
The Hub keeps package identity and Ownable identity distinct.
- Package CID identifies a specific package artifact
- Ownable identity identifies the Ownable instance
- Recipient availability and transfer semantics are keyed by Ownable identity, not only by package CID
This distinction is important for discovery, replay, and import, especially once multiple artifacts or versions are involved.
yarn installBuilt with NestJS.
Protected hub routes use SIWE-authenticated request context.
POST /api/v1/auth/noncereturns noncePOST /api/v1/auth/verifyverifies SIWE payload and returns bearer token- Send
Authorization: Bearer <base64-json-token>for protected endpoints
Config:
SIWE_DOMAIN=<expected-domain>sets accepted SIWE domain
Operational routes:
GET /infobasic service metadataGET /healthreadiness and dependency health
Auth routes:
POST /api/v1/auth/noncegenerate SIWE noncePOST /api/v1/auth/verifyverify SIWE payload and return bearer token
Ownables routes (base path /ownables):
POST /ownables/uploadupload Ownable packagePOST /ownables/bridgecompatibility alias for uploadGET /ownables/:cid/downloaddownload stored Ownable packageGET /ownables/:id/chaindownload canonical Ownable chain JSONGET /ownables/:cid/eventsfetch indexed event history exposed for verificationGET /ownables/available?owner=<caip10-account>list local/dev-only Hub-available ownables for one owner/account when explicitly enabledGET /ownables/proof?cid=...get unlock proof for mapped NFTGET /ownables/isUnlockProofValid?...verify unlock proofGET /ownables/claim?...compatibility alias for package downloadGET /ownables/bridgedinspect uploaded Ownables visible to the authenticated signerGET /ownables/cid?...lookup CID by NFTGET /ownables/chainslist available NFT chain setupGET /ownables/serverinfoinspect wallet balances and hub info
Swagger: http://localhost:3000/api-docs
Runtime requires DATABASE_URL and does not fall back to libpq defaults.
Runtime storage uses OWNABLES_STORAGE if set, and defaults to file://storage when unset.
Use the local dev startup flow:
yarn db:startyarn db:start is rerunnable and will:
- create or start a local Docker Postgres container
- keep data in a persistent Docker volume
- run
yarn db:migrate:upagainstDATABASE_URL - print the exact local
DATABASE_URLused for migration
Default local settings (override with env vars):
HUB_DB_CONTAINER=ownables-hub-postgresHUB_DB_VOLUME=ownables-hub-postgres-dataHUB_DB_PORT=54329HUB_DB_NAME=ownables_hubHUB_DB_USER=ownablesHUB_DB_PASSWORD=ownablesHUB_LOCAL_DATABASE_URLcan override the derived local DSN used bydb:start- derived default
DATABASE_URL=postgres://ownables:[email protected]:54329/ownables_hub HUB_DB_READY_TIMEOUT_SECONDSreadiness timeout for existing containers (default60)HUB_DB_COLD_START_TIMEOUT_SECONDSreadiness timeout for new containers (default240)
Run migrations directly (for existing Postgres targets):
DATABASE_URL=postgres://user:[email protected]:5432/db yarn db:migrate:up
DATABASE_URL=postgres://user:[email protected]:5432/db yarn db:migrate:downyarn db:start only boots Postgres and runs migrations. Start the app with runtime env vars:
DATABASE_URL=postgres://ownables:[email protected]:54329/ownables_hub \
OWNABLES_STORAGE=file://storage \
CORS_ORIGINS=http://127.0.0.1:5173,http://localhost:5173 \
yarn start:devThe runtime targets Node 24 and ESM. For local browser integration, point the SDK at the Hub origin through VITE_HUB and optionally enable recipient discovery with LOCAL_DEV_RECIPIENT_DISCOVERY_ENABLED=true.
Set these env vars for local runtime:
DATABASE_URLOWNABLES_STORAGE(optional; defaults tofile://storage, set to override)CORS_ORIGINS(optional; comma-separated browser origins allowed to call Hub. Outside production the default allowlist ishttp://127.0.0.1:5173,http://localhost:5173for local SDK smoke; production defaults to no allowed origins until explicitly configured.)
Additional runtime configuration (feature-dependent):
SIGNER_MNEMONICHUB_NETWORK_PROFILE(testnetdefault, ormainnet)TESTNET_*_RPC_URL/MAINNET_*_RPC_URLoverridesSIWE_DOMAINPUBLIC_BASE_URLfor absolute package and chain URLs in Hub responsesLOCAL_DEV_RECIPIENT_DISCOVERY_ENABLED=trueto exposeGET /ownables/availableoutside production for localhost receive/import testing
yarn test
yarn test:covUseful verification commands:
yarn typecheck
yarn lint:check
yarn build
yarn verify:core-compat
yarn verify:hub-replay-boundaryReleases are created by GitHub Actions using semantic-release.
- See RELEASING.md for the release flow and commit conventions.
