Rising Tide Developers
Concepts

Smart Products (developer)

Technical reference for the SmartProduct entity - schema, service layer, and API surface.

SmartProduct is the central protocol entity. It represents a deployable Clarity contract with associated off-chain metadata. It is a discriminated union: every row carries a productTypeId that determines the domain, plus an optional domain detail (pageDetails, eventDetails, bracketDetails, gameLobbyDetails) embedded by ?include=. A GAME lobby's seats and fee legs come from GET /api/games/{id}.

Schema (SmartProductBase)

interface SmartProductBase {
  id: number;
  productTypeId: number;
  contractAddress: string;        // empty string until deploy confirms
  contractName: string;
  ownerAddress: string;
  title: string;
  subtitle: string | null;
  thumbnailUri: string | null;
  slug: string | null;
  status: ProductStatus;          // DRAFT | PUBLISHED | LOCKED | SETTLED | FAILED | ARCHIVED
  publishedAt: string | null;
  lockedAt: string | null;
  settledAt: string | null;
  archivedAt: string | null;
  minFeeStx: string;              // BigInt-as-string per SDK convention
  minFeeSbtc: string;
  maxInstances: number | null;    // null = unlimited
  currentInstances: number;
  isTransferable: boolean;
  allowOffers: boolean;
  sourceAppId: number;            // App that created this product
  deployTxId: string | null;
  deployTxStatus: "PENDING" | "CONFIRMED" | "FAILED";
  styleSetId: number | null;
  manifestStorageId: string | null;
  productConfig: { version: number } & Record<string, unknown>;
  createdAt: string;
  updatedAt: string;
  // Optional embeds (`?include=...`):
  productTags?: ProductTag[];
  productStickers?: ProductSticker[];
}

Four concrete variants extend this, each with a domain discriminator:

type SmartProductPage       = SmartProductBase & { domain: "PAGE";       pageDetails?: PageDetails }
type SmartProductEvent      = SmartProductBase & { domain: "EVENT";      eventDetails?: EventDetails }
type SmartProductPrediction = SmartProductBase & { domain: "PREDICTION"; bracketDetails?: BracketDetails }
type SmartProductGame       = SmartProductBase & { domain: "GAME";       gameLobbyDetails?: GameLobbyDetails }

type SmartProduct = SmartProductPage | SmartProductEvent | SmartProductPrediction | SmartProductGame

ProductStatus transitions

FromToCondition
DRAFTPUBLISHEDContract deployed successfully
DRAFTFAILEDContract deployment failed
PUBLISHEDLOCKEDPREDICTION domain only
LOCKEDSETTLEDPREDICTION domain only
DRAFT, PUBLISHED, LOCKED, FAILED or SETTLEDARCHIVEDManual action (LOCKED and SETTLED only in PREDICTION)

Every domain except PREDICTION follows: DRAFT -> PUBLISHED -> ARCHIVED. See Status Machines for the full table.

API endpoints

MethodPathDescription
GET/api/smart-productsList (paginated, filterable by domain, status, productTypeId, productTypeSlug, ownerAddress, sourceAppId, collectionId, search)
GET/api/smart-products/:idGet by numeric ID. Use ?include=instances,contributions,productType,appListings
POST/api/smart-productsCreate a PAGE-domain product (status: DRAFT)
PATCH/api/smart-products/:idUpdate metadata, deploy state or status, subject to the status machine
DELETE/api/smart-products/:idDelete a draft
POST/api/smart-products/:id/publishManual DRAFT -> PUBLISHED, the operator escape hatch for a missed deploy event (the owner, or MANAGE_TOURNAMENTS)
GET/api/smart-products/:id/editionsThe pending-aware edition read

Contract source comes from POST /api/contracts/generate, not from this resource. Events and brackets are created through /api/events and /api/brackets.

SDK methods

rtClient.smartProducts.list(params?: SmartProductListParams)
rtClient.smartProducts.getById(id: number, include?: SmartProductDetailInclude[])
rtClient.smartProducts.createPage(input)  // also createEvent, createPrediction
rtClient.smartProducts.updatePage(id, patch)  // also updateEvent, updatePrediction
rtClient.smartProducts.publishPrediction(id)  // manual publish; a lobby uses rtClient.games

See rtClient.smartProducts for the full surface.

Domain enforcement

Only PREDICTION products can be locked or settled, and they use the full six-state lifecycle; for any other domain, attempting it is refused with INVALID_STATUS_TRANSITION (409). A GAME lobby's matches have a lifecycle of their own - see Status Machines.