Protocol
Direct onchain interaction
The relayer is a convenience, not a gate. Power users can interact with the ThassaMarkets contract directly — pre-approve the payment token, pay your own gas, and call the book with no signatures relayed and no API key involved. Both paths land on the same order book with the same matching rules.
Contract surface
ThassaMarkets — direct-path external surface
enum Side { YES, NO } // 0 = YES, 1 = NO
function createMarketDirect(string calldata question, string calldata settlementQuery,
uint8 side, uint8 price, uint80 shares) external returns (uint256 marketId); // transferFrom path
function placeOrder(uint256 marketId, uint8 side, uint8 price, uint80 shares,
uint256 affiliatePostId) external returns (uint256 orderId); // direct path
function cancelOrder(uint256 marketId, uint256 orderId) external;
function settleMarket(uint256 marketId) external; // pulls $0.05 via transferFrom
function redeem(uint256 marketId) external; // winner claims, minus withdrawal fee
function withdraw(uint256 amount) external; // free balance out, minus withdrawal fee
function claimCreatorFees(uint256 marketId) external;
function claimAffiliateFees(uint256 postId) external;
function getMarket(uint256 marketId) external view returns (Market memory);
function bestPrices(uint256 marketId) external view returns (uint8 bestYes, uint8 bestNo);
function nonces(address maker) external view returns (uint256);Two paths, one book
The relayer path (
createMarket, placeOrdersBatch) funds orders with EIP-3009 authorizations; the direct path funds via transferFrom after a standard ERC-20 approval. Orders from both paths cross against each other.Placing and managing orders
placeOrder(marketId, side, price, shares, affiliatePostId)— escrowsprice × shares(YES) or the complement (NO) viatransferFrom, matches whatever crosses at the best levels (taker fee applies to matched shares), and rests the remainder. Callable by anyone onchain.cancelOrder(marketId, orderId)— maker only (or via relayer with a signed cancel); refunds the unfilled remainder to your free balance.settleMarket(marketId)— anyone; pulls the $0.05 settlement fee viatransferFromand places the hub bid. Re-triggerable if the bid is cancelled or expires.redeem(marketId)— afterSETTLED, pays $1 per winning share, minus the flat withdrawal fee.withdraw(amount)— moves free (unescrowed) balance out, minus the flat withdrawal fee.
Example (viem)
direct-path trading
import { createWalletClient, http, parseUnits } from "viem";
// 1. One-time: approve the payment token for the markets contract.
await wallet.writeContract({
address: PAYMENT_TOKEN,
abi: erc20Abi,
functionName: "approve",
args: [MARKETS_CONTRACT, parseUnits("1000", 6)], // token uses 6 decimals here
});
// 2. Buy 100 YES shares at a 62¢ limit. Escrow pulled via transferFrom:
// 100 × $0.62 = $62 (+ taker fee on any crossing fills).
const orderId = await wallet.writeContract({
address: MARKETS_CONTRACT,
abi: thassaMarketsAbi,
functionName: "placeOrder",
args: [
42n, // marketId
0, // side: 0 = YES
62, // price, cents
100n, // shares
0n, // affiliatePostId (0 = none)
],
});
// 3. Later: cancel the unfilled remainder, or redeem after settlement.
await wallet.writeContract({
address: MARKETS_CONTRACT,
abi: thassaMarketsAbi,
functionName: "cancelOrder",
args: [42n, orderId],
});
await wallet.writeContract({
address: MARKETS_CONTRACT,
abi: thassaMarketsAbi,
functionName: "redeem",
args: [42n], // $1/share to the winning side, minus the flat withdrawal fee
});Views worth knowing
| View | Returns |
|---|---|
getMarket(marketId) | The Market struct: creator, status, settlement outcome (settled, direction), accrued creator fees, matched volume. |
bestPrices(marketId) | Best resting YES and NO prices (cents). |
nonces(maker) | The maker’s next sequential order nonce (relayer path). |
orderDigest(order) | EIP-712 digest of a SignedOrder — cross-check your client-side hashing (see Gasless orders). |
Events
All indexed for offchain consumption:
events
event MarketCreated(uint256 indexed marketId, address indexed creator, string question, string settlementQuery);
event OrderPlaced(uint256 indexed marketId, uint256 indexed orderId, address indexed maker, uint8 side, uint8 price, uint80 shares);
event OrderMatched(uint256 indexed marketId, uint256 takerOrderId, uint256 makerOrderId, uint8 price, uint80 shares, uint256 fee);
event OrderCancelled(uint256 indexed marketId, uint256 indexed orderId);
event MarketMatched(uint256 indexed marketId); // first fill vs creator's opening order
event SettlementRequested(uint256 indexed marketId, uint256 bidId, address indexed caller);
event MarketSettled(uint256 indexed marketId, bool direction);Plus OrderCancelled, Redeemed, Withdrawn, CreatorFeesClaimed, and AffiliateFeesClaimed for accounting flows. If you run your own indexer, key event processing by (tx_hash, log_index) and upsert — re-scans are then harmless.