---
title: "NFT Exhibition Marketplace"
description: "Dual-lane Solana Exhibition — verified KEYS gates and member-created Metaplex Core collections, settled in RING"
locale: "en"
---
# NFT Exhibition Marketplace

> **Success**
> The shipped marketplace is **Solana-first** with **two lanes**: verified Ringdom **KEYS** gate NFTs, and an open **member creator** lane for on-platform Metaplex Core collections. Both settle in **RING** (ledger-dev PoC when `gateMarketProgramId` is empty).

The NFT Exhibition Marketplace lets members list, browse, buy, and cancel eligible assets. For **KEYS** gates, a purchase transfers ownership, but **ownership alone does not unlock product access** — buyers must stake through GateEscrow before `GateResolver.hasFeature` grants the related feature. For **member** mints, purchase is ownership + RING only (no stake CTA, no feature unlock).

| Surface | Verified truth |
|---------|----------------|
| Marketplace | `/nft/market`, `/nft/market/[listingId]`, `/nft/market/sell` |
| Member create | `/nft/create`, `/nft/create/[collectionId]` |
| Collections | `/nft/collections`, `/nft/collections/[slug]` |
| Search | Search redirects land on `/nft/market?q=...` |
| Feed | `useCursorFeed` with `moduleId="nft-market"`; optional `lane=keys\|member` |
| Payment | RING only |
| KEYS symbol | `KEYS` from gate collection metadata |
| Member collections | `nft_member_collections` + listing `lane: 'member'` |
| Listing storage | `nft_listings` |
| Sale ledger | `nft_market_sales` |
| Collection cache | `nft_market_collections` |
| Migrations | `031_nft_exhibition_market_schema.sql`, `032_nft_member_collections_schema.sql` |
| Media field | Generative Gallery — Upload \| Generate on `/nft/create` (see below) |

## Generative Gallery (media)

Member collection cover and mint media use the shared **Generative Gallery** field (`features/generative-media/`): Upload or Generate tabs, Ghost-write prompt enrichment, ImageConductor `n=4` variations, WebP strip derivatives, and a hidden `genmedia:` tool chat (`Conversation.type: 'product'` + `metadata.kind: 'generative_gallery'` — not a separate channel; see [Messaging](/docs/features/messaging.md)). On mint, `buildMetaplexMetadataJson` + `uploadNftMetadataJson` produce off-chain Metaplex JSON (`image`, optional `animation_url`, `properties.files`); ownership and listing details can show showcase animation + gallery thumbs.

Full operator and integrator reference: [Generative Gallery](/docs/features/generative-media.md).

### For founders

## Why this matters for your clone

The marketplace turns tradeable vendor access keys into a visible economy **and** lets privileged members create their own on-platform collections without opening an external NFT import path.

  
- **[KEYS resale liquidity](/docs/features/nft-market.md)** — Vendor-grade gates can move from one member to another without making membership transferable.

  
- **[Member creator lane](/docs/features/nft-market.md)** — Members create Metaplex Core collections, mint, and list for RING — ownership transfer only, no GateEscrow features.

  
- **[Keep KEYS access gated](/docs/features/nft-gates.md)** — A KEYS buyer must stake the gate before features activate. Listing and staking are mutually exclusive.

  
- **[Use RING settlement](/docs/features/wallet.md)** — Buyers pay in the clone native token through the custodial Solana wallet path.

### Operator Checklist

- Enable NFT gates and marketplace config (`nft.marketplaceEnabled`).
- Enable member collections PoC with `nft.memberCollectionsEnabled` and caps (`maxCollectionsPerMember`, `maxMintsPerCollection`).
- Mint or import eligible vendor gates from the KEYS collection for lane K.
- Confirm KEYS sellers understand that staked gates must be unstaked before listing.
- Confirm KEYS buyers understand that a bought gate must be staked before benefits activate.
- Confirm member mints never promise GateEscrow feature unlock.
- Keep mainnet GateMarket disabled until the Anchor programs pass audit and Squads authority setup.

> **Warning**
> Membership NFTs are soulbound and never listable. KEYS lane remains only for `vendor-store-deed`, `vendor-dagi-key`, `vendor-annual-store-license`, and `vendor-quarterly-store-license`. Member lane assets use `source: member_mint` and are never GateEscrow feature keys.

### User Journey

```mermaid
flowchart TD
  subgraph keys [Lane K KEYS]
    SellerK[Seller owns tradeable KEYS gate] --> Unstaked{Gate is unstaked?}
    Unstaked -- no --> Stop[Unstake before listing]
    Unstaked -- yes --> ListK[List on /nft/market/sell]
    ListK --> ActiveK[Active listing]
    ActiveK --> BuyerK[Buyer pays RING]
    BuyerK --> OwnK[Ownership transfers]
    OwnK --> Stake[Buyer stakes gate]
    Stake --> Feature[Feature access activates]
  end
  subgraph member [Lane M Member]
    Member[hasMemberPrivileges] --> Create[/nft/create collection]
    Create --> Mint[Mint asset]
    Mint --> ListM[List for RING]
    ListM --> BuyerM[Buyer pays RING]
    BuyerM --> OwnM[Ownership only]
  end
```

### For developers

## Implementation Map

| Path | Role |
|------|------|
| `app/[locale]/nft/market/page.tsx` | Server page, parses `q`, `collection`, `slug`, `lane`, `sort` |
| `app/[locale]/nft/create/page.tsx` | Member create/mint/list wizard (requires `hasMemberPrivileges`) |
| `app/[locale]/nft/market/sell/page.tsx` | KEYS seller wizard (excludes `member_mint`) |
| `app/_actions/nft-member.ts` | `createMemberCollectionAction`, `mintMemberAssetAction`, `listMemberAssetAction` |
| `features/generative-media/*` | Gallery SSOT; mint metadata helpers; see [Generative Gallery](/docs/features/generative-media.md) |
| `features/nft-market/member/*` | Collection/mint services + `assertMemberAssetCanBeListed` |
| `features/nft-market/listing-policy.ts` | KEYS tradeable SKU / stake / collection checks (lane K only) |
| `features/nft-market/services/listing-service.ts` | Dual-lane draft/activate; member vs KEYS policy branch |
| `features/nft-market/services/solana-market-client.ts` | Ledger-dev list/cancel/buy; Anchor branch blocked until program IDs |
| `features/wallet/conductor/wallet-conductor.ts` | `purchaseNftListing` for both lanes |

### Listing and Purchase Sequence

```mermaid
sequenceDiagram
  participant Seller
  participant Action as app/_actions/nft-market or nft-member
  participant Policy as listing-policy or member-listing-policy
  participant Service as listing-service
  participant Market as SolanaMarketClient
  participant Wallet as WalletConductor
  participant Escrow as GateEscrow

  Seller->>Action: list action
  Action->>Service: createListingDraft(lane)
  Service->>Policy: assertGateCanBeListed or assertMemberAssetCanBeListed
  Policy-->>Service: ok
  Service->>Market: listGate()
  Market-->>Service: ledger-dev listingPda + signature
  Service-->>Action: active listing
  Buyer->>Action: purchaseGateListingAction(listingId, idempotencyKey)
  Action->>Wallet: purchaseNftListing()
  Wallet->>Market: buyGate()
  Market-->>Wallet: RING settlement signature
  Wallet->>Service: markSold()
  Note over Escrow: KEYS only: stakeGateAction after buy
```

### Listing Policy

`isNftMarketplaceEnabled()` must pass. Member create/mint also requires `isMemberCollectionsEnabled()`.

**Lane K:** slug must be one of the four tradeable SKUs. Template/ownership must not be soulbound; asset verifies against KEYS; no active stake.

**Lane M:** `hasMemberPrivileges`, ownership `source: member_mint`, collection active, not soulbound, no active listing. No GateEscrow stake check for listing.

An active listing blocks duplicate listings for the same asset. For KEYS: a staked gate blocks listing; an active listing means feature access is not active through staking.

{`export const TRADEABLE_GATE_SLUGS = [
  'vendor-store-deed',
  'vendor-dagi-key',
  'vendor-annual-store-license',
  'vendor-quarterly-store-license',
] as const`}

### Configuration Keys

| Key | Effect |
|-----|--------|
| `nft.marketplaceEnabled` | Master secondary-market switch |
| `nft.memberCollectionsEnabled` | Enables member create/mint/list PoC |
| `nft.maxCollectionsPerMember` | Cap (default 3) |
| `nft.maxMintsPerCollection` | Cap (default 50) |
| `nft.gateMarketProgramId` | Empty = ledger-dev; non-empty Anchor path throws in this build |
| `nft.marketplaceFeeRecipient` | Optional RING fee recipient |
| `nft.marketplaceFeeBps` | Fee split basis points, clamped 0–10000 |

### Persistence

- `031_nft_exhibition_market_schema.sql` — listings, sales, collection cache
- `032_nft_member_collections_schema.sql` — `nft_member_collections` + listing `lane` / `collectionId` indexes + KEYS lane backfill

> **Development**
> `SolanaMarketClient.buyGate` settles through ledger-dev RING transfers when `gateMarketProgramId` is empty. Member lane reuses the same purchase path. The Anchor GateMarket CPI path is intentionally not implemented in this build.

The future program scaffold lives under `solana/programs/{gate-escrow,gate-market}`. `solana/SECURITY.md` blocks production or mainnet deployment until Smart Contract Security Auditor review, Squads authorities, pinned deployment records, and invariant tests are complete.

## Future On-Chain Work

## Related Documentation

  
- **[Generative Gallery](/docs/features/generative-media.md)** — Upload \| Generate field, credits billing, Metaplex metadata, product form reuse.

  
- **[Solana NFT Gates](/docs/features/nft-gates.md)** — Primary sale, KEYS collection metadata, GateEscrow staking, and template activation.

  
- **[Wallet](/docs/features/wallet.md)** — Native RING balance and custodial Solana wallet flows used for marketplace settlement.

  
- **[Subscriptions](/docs/features/subscriptions.md)** — How `nft_gate` fits beside other access providers.

  
- **[Security](/docs/features/security.md)** — Production guardrails before audited on-chain marketplace deployment.
