Rising Tide Developers
SDKCore

rtClient.contracts

SDK methods for Clarity contract generation - the per-domain process methods and the smartProductId override path.

The contracts service generates Clarity source for a smart product deploy. All four process methods return a Clarity source string ready to hand to a wallet for signing.

The same backend endpoint (POST /api/contracts/generate) backs all four - the productType field in the body routes to the correct template pipeline.

Per-domain process methods

rtClient.contracts.processWritingContract(request)

Generate a PAGE-domain contract (haiku, tanka, basicpage, openletter).

const clarityCode = await rtClient.contracts.processWritingContract({
  name: "morning rain",
  description: "a haiku",
  version: "1",
  contractName: "haiku-1701234567",
  contractDeployer: user.stxAddress,
  thumbnail: thumbnailUri,
  webHost: WEB_HOST,
  apiHost: API_HOST,
  launchUri: `${API_HOST}/api/contracts/page/${user.stxAddress}/haiku-1701234567/tokens/`,
  fees: { stx: 1000000, sbtc: 100 },
  creators: [{ name: user.displayName, address: user.stxAddress, percentageShare: 100 }],
  isTransferable: true,
  pageTags: ["haiku"],
  pageTypeId: PAGE_TYPE_ID,
  styleSetId: 7,
  sourceAppId: APP_ID,
  productType: "haiku",
  smartProductId: productId,  // optional - see "Two write paths" below
});

rtClient.contracts.processEventContract(request)

Generate an EVENT-domain contract. Request type: EventContractProcessRequest.


rtClient.contracts.processBracketContract(request)

Generate a PREDICTION-domain (bracket) contract. Request type: BracketContractProcessRequest. Includes bracket-specific fields (bracketSize, templateArtist, poolPercentage, maxBracketsPerWallet, games).


rtClient.contracts.processContract(request)

Generic escape hatch. Takes any body with a productType field. Use the per-domain methods above instead - this exists for tooling and tests.

Two write paths for creators[]

The endpoint accepts two shapes for the creators[] array. Which one runs depends on whether the request body includes smartProductId: number.

Substrate-derived (canonical, as of 2026-05-30)

When smartProductId is set, the route ignores any client-supplied creators[] and rewrites it from that product's frozen contributions. This is the path to use - the on-chain payout order must match what the protocol reads back by position.

It pairs with the createDraft-first flow in useDeployProduct.generateContractCode: the draft SmartProduct is created first (server materializes contributions from the picker payload), then contract code is generated from the frozen substrate rows. (useDeployProduct is the deploy-orchestration hook from @rising-tide/ui - it sequences createDraft -> generate -> sign -> patch; the headless core SDK has no hooks.)

const deployer = useDeployProduct(rtClient);

await deployer.deploy(
  { contractName, ownerAddress: user.stxAddress, network, getPrivyToken },
  {
    createDraft: async () => {
      const res = await rtClient.smartProducts.createPage({
        // ... contribution picker payload feeds the substrate
        contributions: { styleSetId, collaborators, fundingGroups, stickers },
        // ... other create fields
      });
      return res.data!.id;
    },
    generateContractCode: async (smartProductId) => {
      return rtClient.contracts.processWritingContract({
        // placeholder creators[] - server overrides via smartProductId
        creators: [{ name: user.displayName, address: user.stxAddress, percentageShare: 100 }],
        smartProductId,
        // ... other request fields
      });
    },
    patchDeployTxId: async (productId, deployTxId) => {
      await rtClient.smartProducts.updatePage(productId, { deployTxId });
    },
    deleteDraft: async (productId) => {
      await rtClient.smartProducts.deletePage(productId);
    },
  },
);

Legacy client-supplied

When smartProductId is absent, the route trusts the creators[] array verbatim. Kept for tooling that builds contracts without going through the SmartProduct draft surface (one-shot scripts, integration tests). Bypasses substrate, so contracts deployed this way do not have matching ProductContribution rows.

For new write surfaces, always pass smartProductId.

Resolve an NFT asset class

rtClient.contracts.getNftAssetClass(contractAddress, contractName, network?)

Resolve a deployed contract's NFT asset-class name - the define-non-fungible-token symbol its editions live under (page-edition / ticket-nft / bracket-edition). A transfer or burn post-condition must name this asset class, not the contract name. Server-cached, so repeated dispatches don't re-read the chain interface. Returns null when the contract declares no NFT or the interface read fails.

const assetClass = await rtClient.contracts.getNftAssetClass(
  contractAddress,
  contractName,
  "testnet",
);

Deploy lifecycle

Once the client has Clarity source from a process*Contract() call, the deploy goes through useDeployProduct (or the equivalent BYOW flow):

  1. Sign and broadcast via Privy or BYOW.
  2. The DEPLOY Chainhook fires on confirmation.
  3. The handler sets contractAddress on the SmartProduct row and transitions status to PUBLISHED.

See Contract Templates for what generation does, and How State Syncs for what happens after you broadcast.