> For the complete documentation index, see [llms.txt](https://docs.trover.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trover.tech/engineering/opensea-marketplace.md).

# OpenSea-native Trover NFT marketplace

> Historical (2026-09-08): the Trover Planets collection was dropped with the move to token economics (`token-economics.md`). The SeaDrop and Planets sections below describe the old plan and are kept for context only; the OpenSea read/execution boundary still applies to every other collection.

## Current state (2026-09-08)

This document was written when OpenSea was the marketplace's data source. It is kept for the collection deployment, primary-drop probe, execution boundary and rollout order, which still hold. What has changed since:

* **The chain is primary; OpenSea is enrichment and fallback.** Mints, sales and collection discovery come from the on-chain scanners and the `nft-realtime` websocket lanes (`robinhood_wss:chainstack`, `robinhood_wss:tenderly`). OpenSea supplies the orderbook, listings, stats, images, drop schedules and the trending / top / new lists, and never overrides a chain-verified event. The scanners, the ownership walk and the lifecycle recompute are not gated on `OPENSEA_ENABLED`.
* **Budget.** One key, 120 calls a minute, shared by the API and the worker through Redis. Each process paces 250 ms between calls, waits out a block of up to 3 s inline and retries a burst 429 once; worker clients reserve the last 20 calls of the minute for the API. The in-process cache is bounded to 300 entries.
* **Verification.** A fill decoded from a chain log is stored verified; the redundant RPC re-check answers `true`, `false` or `null`, and only `false` (a node answered and the transaction reverted or missed the contract, or two healthy nodes report it missing) rejects. `null` is unverifiable and accepted. The `nft-rejected-sale-repair` job re-accepts rows rejected under the old rule.
* **Discovery** is warmed by the worker every minute and served by the API from cache (90 s, stale past 15 s while one request rebuilds it); the provider list is never called on the request path.
* **Presentation.** `minted_out` is shown for 15 minutes after the last mint, then as `secondary`.
* `OPENSEA_STREAM_ENABLED` is on in production for listings; the browser canary (`docker-compose.opensea-canary.yml`) is stopped and the worker drops its jobs.

The full pipeline is in `architecture.md` under "NFT market pipeline".

Trover Planets settles on Robinhood Chain and uses one SeaDrop-compatible collection. The collection contract owns the only 5,555 supply counter. The legacy `TroverPlanetMinter` source and deployment path are removed; it cannot be deployed or granted mint authority.

## Runtime configuration

Create a temporary development key with OpenSea's instant-key endpoint, then replace it with a full Developer key before production:

```bash
curl -X POST https://api.opensea.io/api/v2/auth/keys
```

Store the key only on the API and worker. Never add it to a `NEXT_PUBLIC_*` variable.

```dotenv
OPENSEA_ENABLED=true
OPENSEA_API_KEY=
OPENSEA_API_KEY_EXPIRES_AT=
OPENSEA_API_BASE_URL=https://api.opensea.io/api/v2
OPENSEA_CHAIN=robinhood
OPENSEA_COLLECTION_SLUG=
OPENSEA_CONTRACT_ADDRESS=
OPENSEA_PRIMARY_DROP_ENABLED=false
OPENSEA_STREAM_ENABLED=false
OPENSEA_CREATOR_FEE_BPS=500
OPENSEA_REVENUE_ROUTER_ADDRESS=
OPENSEA_PUBLIC_RPC_URLS_JSON={"1":["https://ethereum-rpc.publicnode.com"],"8453":["https://base-rpc.publicnode.com"],"42161":["https://arbitrum-one-rpc.publicnode.com"]}
```

Instant keys are temporary and OpenSea may change their validity window. Store the expiry returned when the key is issued instead of assuming a duration. `/v1/admin/opensea/health` reports the configured expiry, observed rate-limit state, Robinhood chain discovery, collection indexing configuration, cache state, and primary-drop readiness.

## Collection deployment

Production deployment is Safe-controlled:

1. Obtain the current OpenSea SeaDrop address for Robinhood Chain.
2. Read its deployed bytecode and pin the expected bytecode hash in the deployment manifest. Never deploy using an unverified address supplied only through configuration.
3. Deploy `TroverPlanets` with the Safe as admin, the onchain renderer, `TroverRevenueRouter` as ERC-2981 receiver, the verified SeaDrop address, a frozen collection seed, and `https://trover.tech/api/nft/contract-metadata` as `contractURI`.
4. From the Safe, configure one public drop with a 0.002 ETH price, 5,555 maximum total supply, an on-token maximum of 20 per transaction, and the provider-documented 1,000 bps primary-drop fee. The 0.002 ETH amount remains the displayed mint price; Trover reviews the provider-returned buyer total, platform fee, net proceeds, and gas before signing.
5. Configure the creator payout and ERC-2981 receiver to `TroverRevenueRouter`. The royalty is 500 bps. Only creator earnings actually received are routed through the 40% holder / 60% treasury ledger.
6. Grant `BURNER_ROLE` only to `TroverAgentProgress`, connect the renderer to that progression contract, and verify all deployed contracts.

The testnet deployment script accepts `TROVER_TESTNET_SEADROP_ADDRESS` only when `TROVER_TESTNET_SEADROP_BYTECODE_HASH` is also pinned and matches the deployed runtime. When no testnet SeaDrop is available it deploys `MockSeaDrop` for contract-mechanics testing and records `hostedPrimaryDrop: false`; this is never a production fallback.

## Primary-drop probe

Before enabling mainnet minting, the admin health response must show:

* OpenSea API key valid and unexpired;
* `robinhoodSupported: true`;
* contract and collection slug configured;
* revenue router configured;
* a successful low-value mint-action build for the deployed collection.

Run the protected `POST /v1/admin/opensea/probe-primary-drop` endpoint after OpenSea has indexed the collection, with a body such as `{"minterAddress":"0x..."}`. Minting must stay disabled unless it returns `available: true`, the provider-indexed contract matches the exact configured collection address, and OpenSea returns a concrete one-item mint action. The probe never signs or submits that action.

Only after the probe succeeds should an operator set `OPENSEA_PRIMARY_DROP_ENABLED=true` and restart the API and worker. The API, agent, and worker all reject mint actions with `opensea_primary_drop_unavailable` while the latch is off.

If OpenSea does not offer hosted primary-drop actions for Robinhood at rollout, leave mint actions disabled with `opensea_primary_drop_unavailable`. Keep the same SeaDrop-compatible collection and Trover mint UI; secondary OpenSea reads and trading can remain enabled.

## Execution boundary

OpenSea responses are provider data, not transaction authority. Every action is bound to the authenticated `TradingProfile`, exact active wallet revision, chain, collection, token IDs, recipient, order hashes, price ceiling, and quote expiry. Returned transactions are normalized, simulated, and fingerprinted before Privy or an external wallet can submit them.

The browser never receives the API key. Delegated wallets may execute ordered steps when confirmation is disabled. External wallets use an action-specific signing handoff. No separate Trover fee transfer is added to NFT purchases.

## Rollout order

1. Add the development key and enable read-only collection/search views.
2. Obtain the production key and configure key expiry monitoring.
3. Deploy and verify the Safe-controlled collection and SeaDrop configuration.
4. Index the collection and confirm floor, listing, offer, holder, and activity reconciliation.
5. Enable minting, then buying/listing/offers.
6. Enable cross-chain fulfillment only after a controlled low-value canary.

References: [OpenSea supported chains](https://api.opensea.io/api/v2/chains), [SeaDrop integration](https://docs.opensea.io/docs/seadrop), and [OpenSea API keys](https://docs.opensea.io/reference/api-keys).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.trover.tech/engineering/opensea-marketplace.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
