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):
- Sign and broadcast via Privy or BYOW.
- The DEPLOY Chainhook fires on confirmation.
- The handler sets
contractAddresson theSmartProductrow and transitions status toPUBLISHED.
See Contract Templates for what generation does, and How State Syncs for what happens after you broadcast.