Skip to content

API Reference

Comprehensive API documentation for CloudBank Prediction Market, covering REST APIs, smart contract interfaces, GraphQL queries, and WebSocket channels.


Authentication

User Authentication (EIP-191 Signature)

1. POST /api/v1/auth/nonce        → get challenge nonce
2. sign message with wallet                → EIP-191 personal_sign
3. POST /api/v1/auth/login         → submit signature and obtain JWT
4. Authorization: Bearer {token}   → include in subsequent requests

JWT is signed with HS256. Claims include Subject (wallet address), LoginProvider, and ExpiresAt.

Admin Authentication

  • JWT Bearer: POST /api/v1/admin/auth/login get access + refresh token
  • Basic Auth: used during bootstrap phase Authorization: Basic base64(user:pass)
  • RBAC: superadmin (full permissions) / admin (limited permissions)

REST API

Base URL: https://{host}/api/v1

Quick cURL Examples (Auth / Markets / Trades / Wallet)

bash
export HOST="https://docs-test.cloudbank.to"
export API="$HOST/api/v1"
export ADDRESS="0xYourWalletAddress"

1) Auth: nonce + login

bash
curl -sS -X POST "$API/auth/nonce" \
  -H "Content-Type: application/json" \
  -d "{\"address\":\"$ADDRESS\"}"
json
{
  "nonce": "7fa3f1d9",
  "message": "CloudBank login nonce: 7fa3f1d9",
  "expiresAt": "2026-03-06T00:00:00Z"
}

After signing, submit login ($SIGNATURE and $MESSAGE must be replaced with actual wallet signature data):

bash
export MESSAGE="CloudBank login nonce: 7fa3f1d9"
export SIGNATURE="0xYourEIP191Signature"

curl -sS -X POST "$API/auth/login" \
  -H "Content-Type: application/json" \
  -d "{\"address\":\"$ADDRESS\",\"message\":\"$MESSAGE\",\"signature\":\"$SIGNATURE\"}"
json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "custodialWalletAddress": "0x4D7E...A9f2",
  "expiresAt": "2026-03-06T12:00:00Z"
}
bash
export TOKEN="eyJhbGciOi..."

2) Markets: query market list

bash
curl -sS "$API/markets?state=TRADING&limit=20&offset=0" \
  -H "Authorization: Bearer $TOKEN"
json
{
  "items": [
    {
      "id": "0x2E79B7190c463CD11793520d23F63A3d035A94c2",
      "question": "Will BTC close above $80k on Friday?",
      "state": "TRADING",
      "closeTime": "2026-03-08T12:00:00Z"
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0
}

3) Trades: submit order

bash
curl -sS -X POST "$API/orderbook/orders" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "marketId":"0x2E79B7190c463CD11793520d23F63A3d035A94c2",
    "outcome":"YES",
    "side":"BUY",
    "orderType":"LIMIT",
    "price":"0.62",
    "amount":"100"
  }'
json
{
  "orderHash": "0x8f7b...c31d",
  "status": "open",
  "filledAmount": "0",
  "createdAt": "2026-03-05T10:00:00Z"
}

4) Wallet: get info + withdraw

bash
curl -sS "$API/wallet/info" \
  -H "Authorization: Bearer $TOKEN"
json
{
  "walletAddress": "0x4D7E...A9f2",
  "balances": {
    "BNB": "0.132",
    "USDC": "1250.50"
  },
  "chainId": 97
}
bash
curl -sS -X POST "$API/wallet/withdraw" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "asset":"USDC",
    "toAddress":"0xReceiverAddress",
    "amount":"25.0"
  }'
json
{
  "txHash": "0x4b17...9f25",
  "asset": "USDC",
  "amount": "25.0",
  "status": "pending"
}

Auth

MethodPathAuthRate LimitDescription
POST/auth/nonce-100/min/IPGenerate EIP-191 challenge (address -> nonce + message)
POST/auth/login-5/min/IPVerify signature and return JWT + custodialWalletAddress

POST /auth/nonce

json
// Request
{ "address": "0x..." }
// Response
{ "nonce": "abc123", "message": "Sign this message...", "expiresAt": "2026-01-01T00:00:00Z" }

POST /auth/login

json
// Request
{ "address": "0x...", "signature": "0x...", "message": "Sign this message..." }
// Response
{ "token": "eyJ...", "custodialWalletAddress": "0x...", "expiresAt": "2026-01-01T00:00:00Z" }

Wallet

MethodPathAuthRate LimitDescription
GET/wallet/infoJWT100/minGet custodial wallet info
DELETE/wallet/meJWT100/minDelete custodial wallet (requires non-functional state)
POST/wallet/signJWT20/minSign transaction on behalf (whitelisted contracts)
POST/wallet/withdrawJWT3/minWithdraw BNB/USDC (daily limit)

POST /wallet/sign

json
// Request
{ "to": "0x...", "data": "0x...", "value": "0", "product": "predict" }
// Response
{ "txHash": "0x...", "gasMode": "paymaster", "status": "pending", "createdAt": "..." }

POST /wallet/withdraw

json
// Request
{ "asset": "USDC", "toAddress": "0x...", "amount": "100.5" }
// Response
{ "txHash": "0x...", "asset": "USDC", "amount": "100.5", "fee": "0", "gasMode": "user", "status": "pending" }

Orderbook

MethodPathAuthRate LimitDescription
GET/orderbook/book-120/min/IPGet orderbook depth
POST/orderbook/ordersJWT30/minSubmit order
POST/orderbook/routeJWT30/minHybrid route (AMM + Orderbook)
DELETE/orderbook/orders/:orderHashJWT30/minCancel order
GET/orderbook/heartbeat/statusJWT30/minGet heartbeat status
GET/orderbook/heartbeat/wsJWT-WebSocket heartbeat channel

GET /orderbook/book

?marketId={id}&outcomeTokenId={tokenId}&depth=20
json
// Response
{ "market": "0x...", "bids": [{"price": "0.65", "size": "100"}], "asks": [...] }

Invite Relations

MethodPathAuthRate LimitDescription
GET/invite-relations/by-inviter-120/min/IPQuery relations by inviter
POST/invite-relations/bindJWT60/minBind invite relation
GET/invite-relations/meJWT60/minGet own invite relations

Admin — Auth

MethodPathAuthRate LimitDescription
POST/admin/auth/login-30/min/IPAdmin login
POST/admin/auth/refresh-30/min/IPRefresh token

Admin — Users (superadmin)

MethodPathAuthDescription
GET/admin/usersAdmin+SAList all admins
GET/admin/users/:idAdmin+SAGet admin details
POST/admin/usersAdmin+SACreate admin
PATCH/admin/users/:idAdmin+SAUpdate admin
POST/admin/users/:id/reset-passwordAdmin+SAReset password
POST/admin/users/:id/bind-operation-walletAdmin+SABind operation wallet
POST/admin/users/:id/unbind-operation-walletAdmin+SAUnbind operation wallet

Admin — Custodial Users

MethodPathAuthDescription
GET/admin/custodial-usersAdminPaginated query for custodial users
GET/admin/custodial-users/:idAdminGet user details + wallet
DELETE/admin/custodial-users/:id/walletSAPermanently delete user wallet

Admin — Operation Wallets (superadmin)

MethodPathAuthDescription
GET/admin/operation-walletsSAList operation wallets
GET/admin/operation-wallets/:idSAGet wallet details
POST/admin/operation-walletsSACreate operation wallet
PATCH/admin/operation-wallets/:idSAUpdate operation wallet
POST/admin/wallet/signAdminOperation wallet sign-on-behalf transaction

Admin — Whitelist

MethodPathAuthDescription
POST/admin/whitelistAdminAdd contract whitelist
GET/admin/whitelistAdminQuery whitelist (filterable by product)
PATCH/admin/whitelist/:idAdminUpdate whitelist entry
DELETE/admin/whitelist/:idAdminDelete whitelist entry

Admin — Invite Relations (superadmin)

MethodPathAuthDescription
GET/admin/invite-relationsSAPaginated query (CSV export supported)
POST/admin/invite-relations/importSABulk import (<=500 rows per batch)

System

MethodPathAuthDescription
GET/health-Health check (SQL/Redis/BSC status)
GET/metrics-Prometheus metrics

Error Codes

All error responses follow this format:

json
{ "code": "error_code", "message": "Human-readable message", "details": {} }
CodeHTTP StatusDescription
invalid_request400Invalid request parameters
invalid_signature401EIP-191 signature verification failed
invalid_nonce401Nonce is invalid or expired
unauthorized401Not authenticated or token expired
forbidden403Forbidden
contract_not_whitelisted403Contract is not whitelisted
insufficient_bnb400Insufficient BNB balance
insufficient_balance400Insufficient balance
daily_limit_exceeded429Exceeded daily withdrawal limit
not_found404Resource not found
rate_limited429Rate limit exceeded
invite_binding_conflict409Invite relation conflict
invite_request_replayed409Duplicate bind request
invite_self_not_allowed400Self-invite is not allowed
internal_error500Internal server error
service_unavailable503Service unavailable

WebSocket

Orderbook Heartbeat

URL: wss://{host}/api/v1/orderbook/heartbeat/wsAuth: Bearer JWT (query param or header)

Server → Client

json
{ "type": "heartbeat_connected", "status": { ... } }
{ "type": "heartbeat_ack",       "status": { ... } }
{ "type": "error",               "message": "Error description" }

Client → Server

  • Any message can trigger heartbeat keep-alive
  • Supports standard WebSocket ping/pong

Smart Contract Interfaces

OptimisticController

Binary prediction market controller that manages lifecycle states: TRADING -> PROPOSED -> DISPUTED -> RESOLVED / CANCELLED.

solidity
// Read methods
function getQuestion() external view returns (string memory);
function getMetadata() external view returns (string memory);
function getState() external view returns (MarketState);
function getConditionId() external view returns (bytes32);
function getPositionIds() external view returns (uint256[] memory);
function isActive() external view returns (bool);
function canSettle() external view returns (bool);
function getFeatureFlags() external pure returns (uint256);

// Market management (onlyOwner)
function setMetadata(string calldata metadata) external;
function setOracleAskAfter(uint256 newAskAfter) external;
function depositStake() external;
function proposeOutcome(uint8 outcome, bytes calldata data) external;
function settle(bytes calldata data) external;
function cancelAsInvalid() external;

// Open operations
function disputeOutcome(bytes calldata data) external;
function requestOracleQuestion(bytes calldata data) external payable;
function refundOracleQuestion() external;
function proposeOutcomeFromOracle(bytes calldata data) external;

Events:

  • OutcomeProposed(uint8 indexed outcome, address indexed proposer, uint256 timestamp, bytes32 dataHash)
  • OutcomeDisputed(address indexed disputer, uint256 timestamp, bytes32 dataHash)
  • OracleQuestionRequested(bytes32 indexed oracleQuestionId, uint256 deadline, address indexed requester, uint256 value, bytes32 dataHash)
  • StakeProcessed(address indexed creator, address indexed token, uint256 amount, bool refunded)
  • StateTransition(MarketState indexed from, MarketState indexed to)
  • MarketResolved(uint8 indexed outcome, uint256 timestamp)

BinaryCPMM

Constant-product market maker (x*y=k), 0% fee, based on Gnosis CTF ERC-1155 position tokens.

solidity
// Read methods
function getReserves() external view returns (uint256 yesReserves, uint256 noReserves);
function getPrice(uint256 tokenId) external view returns (uint256 price1e18);
function quote(uint256 tokenIn, uint256 amountIn) public view returns (uint256 amountOut);

// Liquidity
function addLiquidity(uint256 collateralAmount) external returns (uint256 lpOut);
function removeLiquidity(uint256 lpIn) external returns (uint256 yesOut, uint256 noOut);

// Trading
function swap(uint256 tokenIn, uint256 amountIn, uint256 minOut) external returns (uint256 amountOut);
function swapCollateralForOutcome(uint256 outcomeTokenId, uint256 collateralAmount, uint256 minOutcomeOut) external returns (uint256 outcomeOut);
function sellOutcomeForCollateral(uint256 outcomeTokenId, uint256 outcomeIn, uint256 minCollateralOut) external returns (uint256 collateralOut);
function sellAllOutcomeForCollateral(uint256 outcomeTokenId, uint256 minCollateralOut) external returns (uint256 collateralOut);

Events:

  • LiquidityAdded(address indexed provider, uint256 collateralIn, uint256 lpOut)
  • LiquidityRemoved(address indexed provider, uint256 lpIn, uint256 yesOut, uint256 noOut)
  • Swapped(address indexed trader, uint256 indexed tokenIn, uint256 amountIn, uint256 indexed tokenOut, uint256 amountOut)
  • CollateralSwappedForOutcome(address indexed trader, uint256 collateralIn, uint256 indexed outcomeTokenId, uint256 outcomeOut)
  • OutcomeSoldForCollateral(address indexed trader, uint256 indexed outcomeTokenId, uint256 outcomeIn, uint256 collateralOut)

DCPPFactory

Market factory that deploys market + AMM via EIP-1167 minimal proxies.

solidity
// Create market
function createOptimisticMarket(string calldata question, uint256 livenessPeriod, address, uint256, uint8 outcomeSlots) external returns (address market);
function createOptimisticMarket_V2(string calldata question, uint256 livenessPeriod, address, uint256, uint8 outcomeSlots) external returns (address market);
function createOptimisticMarketWithInitialLiquidity(...) external returns (address market, address amm);
function createOptimisticMarketWithInitialLiquidity_V2(...) external returns (address market, address amm);
function createOptimisticMarket_V3(string calldata question, string calldata metadata, ...) public returns (address market, address amm);
function createOptimisticMarket_V4(..., bytes32 key) external returns (address market, address amm);

// Read methods
function getMarketCount() external view returns (uint256);
function getMarketByIndex(uint256 idx) external view returns (address);

// Management (onlyOwner)
function setOracleAdapter(IYesNoOracleAdapter newAdapter) external;
function setMultiOutcomeFactory(address newFactory) external;

MultiOutcomeMarket

Multi-option market wrapper that composes N independent binary markets (one-vs-rest mode).

solidity
function optionCount() external view returns (uint256);
function getOption(uint256 index) external view returns (string label, address market, address amm, uint256 yesTokenId, uint256 noTokenId);
function proposeWinner(uint8 winnerIndex, bytes calldata data) external;  // onlyOwner
function settleWinner(uint8 winnerIndex) external;  // onlyOwner

// Oracle flow
function requestWinnerFromOracle(uint256 deadline, bytes calldata data) external payable returns (bytes32 qid);
function proposeWinnerFromOracle(bytes calldata data) external;
function settleWinnerFromOracle() external;

SoraOracle

Minimal Yes/No oracle, answered by the designated provider.

solidity
function askYesNoQuestion(string calldata question, uint256 deadline) external payable returns (uint256 questionId);
function provideAnswer(uint256 questionId, bool boolAnswer, uint8 confidenceScore, string calldata dataSource) external;  // onlyOracleProvider
function getQuestionWithAnswer(uint256 questionId) external view returns (string, uint256, string, bool, AnswerStatus, uint256);
function refundUnansweredQuestion(uint256 questionId) external;

CloudBankVerifyingPaymaster

ERC-4337 AA Paymaster with signature validation, daily quota, and sender whitelist.

solidity
function validatePaymasterUserOp(UserOperation calldata userOp, bytes32, uint256 maxCost) external returns (bytes memory context, uint256 validationData);
function postOp(PostOpMode, bytes calldata context, uint256 actualGasCost) external;
function getHash(UserOperation calldata userOp, uint48 validUntil, uint48 validAfter) public view returns (bytes32);

// Management (onlyOwner)
function setVerifyingSigner(address newSigner) external;
function setEnforceSenderAllowlist(bool enabled) external;
function setDailySponsorLimit(uint256 newLimit) external;
function setSenderAllowlist(address sender, bool allowed) external;
function batchSetSenderAllowlist(address[] calldata senders, bool allowed) external;
function deposit() external payable;

FactoryRegistry

Factory registry that provides a stable pointer to the current factory address.

solidity
function factory() external view returns (address);
function setFactory(address newFactory) external;  // onlyOwner

Key Interfaces

solidity
// IMarket
interface IMarket {
    function getQuestion() external view returns (string memory);
    function getState() external view returns (MarketState);
    function getConditionId() external view returns (bytes32);
    function isActive() external view returns (bool);
    function canSettle() external view returns (bool);
    function settle(bytes calldata data) external;
}

// IYesNoOracleAdapter
interface IYesNoOracleAdapter {
    function ask(string calldata question, uint256 deadline, bytes calldata data) external payable returns (bytes32);
    function getAnswer(bytes32 oracleQuestionId) external view returns (bool finalized, uint8 outcome);
    function refund(bytes32 oracleQuestionId) external returns (uint256 refunded);
}

// IConditionalTokens (Gnosis CTF)
interface IConditionalTokens {
    function prepareCondition(address oracle, bytes32 questionId, uint256 outcomeSlotCount) external;
    function reportPayouts(bytes32 questionId, uint256[] calldata payouts) external;
    function splitPosition(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] calldata partition, uint256 amount) external;
    function mergePositions(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] calldata partition, uint256 amount) external;
    function redeemPositions(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] calldata indexSets) external;
}

GraphQL (The Graph / Goldsky)

Endpoint: https://api.goldsky.com/api/public/project_{id}/subgraphs/cloudbank-bsc-testnet/{version}/gn

Schema Entities

Market

graphql
type Market @entity {
  id: ID!                        # Market contract address
  question: String!              # Market question
  conditionId: Bytes!            # CTF condition ID
  state: Int!                    # 0=TRADING, 1=PROPOSED, 2=RESOLVED
  amm: Bytes                     # AMM contract address
  yesTokenId: BigInt             # YES token ID (ERC-1155)
  noTokenId: BigInt              # NO token ID (ERC-1155)
  creator: Bytes!                # Creator address
  createdAtTimestamp: BigInt!
  totalVolume: BigDecimal!       # Total volume (USDC)
  metadataUri: String            # IPFS metadata URI
  category: String               # Market category (parsed from IPFS)
  isMultiOutcome: Boolean!       # Is multi-outcome market
  multiOutcomeMarket: MultiOutcomeMarket
  optionIndex: Int               # Multi-outcome option index
  optionLabel: String            # Multi-outcome option label
  trades: [Trade!]! @derivedFrom(field: "market")
}

Trade

graphql
type Trade @entity {
  id: ID!                        # txHash-logIndex
  market: Market!
  amm: Bytes!
  trader: Bytes!
  user: User!
  type: String!                  # "buy" | "sell"
  side: String!                  # "yes" | "no"
  amountIn: BigInt!
  amountOut: BigInt!
  price: BigDecimal!             # amountIn / amountOut
  outcomeTokenId: BigInt!
  timestamp: BigInt!
  blockNumber: BigInt!
  txHash: Bytes!
}

User

graphql
type User @entity {
  id: ID!                        # Address (lowercase)
  totalVolume: BigDecimal!
  realizedPnL: BigDecimal!
  totalTrades: Int!
  totalBuys: Int!
  totalSells: Int!
  totalProfitableSells: Int!
  firstTradeTimestamp: BigInt!
  lastTradeTimestamp: BigInt!
  trades: [Trade!]! @derivedFrom(field: "user")
  positions: [UserPosition!]! @derivedFrom(field: "user")
}

UserPosition

graphql
type UserPosition @entity {
  id: ID!                        # user-market-side
  user: User!
  market: Market!
  side: String!                  # "yes" | "no"
  shares: BigDecimal!            # Current holdings
  totalBought: BigDecimal!
  totalSold: BigDecimal!
  costBasis: BigDecimal!         # Cost basis
  proceeds: BigDecimal!          # Cumulative proceeds
  isOpen: Boolean!
  openedAtTimestamp: BigInt!
  lastTradeTimestamp: BigInt!
}

MultiOutcomeMarket

graphql
type MultiOutcomeMarket @entity {
  id: ID!
  question: String!
  metadataUri: String
  creator: Bytes!
  optionCount: Int!
  options: [Market!]! @derivedFrom(field: "multiOutcomeMarket")
  createdAtTimestamp: BigInt!
}

Common Queries

Get all markets

graphql
query GetMarkets {
  markets(orderBy: createdAtTimestamp, orderDirection: desc) {
    id, question, state, amm, yesTokenId, noTokenId, totalVolume, category
    trades(first: 1, orderBy: timestamp, orderDirection: desc) { price, side }
  }
}

Get market trades

graphql
query GetMarketTrades($marketId: ID!) {
  trades(where: { market: $marketId }, orderBy: timestamp, orderDirection: desc, first: 100) {
    id, trader, type, side, amountIn, amountOut, price, timestamp, txHash
  }
}

Get user positions

graphql
query GetUserPositions($user: ID!) {
  user(id: $user) {
    positions(where: { isOpen: true }) {
      side, shares, costBasis, proceeds
      market { id, question, state }
    }
  }
}

Leaderboard (by volume)

graphql
query GetTopTraders($first: Int!) {
  users(orderBy: totalVolume, orderDirection: desc, first: $first, where: { totalTrades_gt: 0 }) {
    id, totalVolume, realizedPnL, totalTrades, totalProfitableSells
  }
}

Get multi-outcome markets

graphql
query GetMultiOutcomeMarket($id: ID!) {
  multiOutcomeMarket(id: $id) {
    id, question, optionCount
    options(orderBy: optionIndex) {
      id, optionLabel, state, amm, yesTokenId, noTokenId, totalVolume
    }
  }
}

Indexed Events

The Graph subgraph listens to the following on-chain events:

ContractEventHandlerEntity
DCPPFactoryMarketCreatedWithAMMhandleMarketCreatedWithAMMMarket
DCPPFactoryMarketCreatedWithAMMWithTokenshandleMarketCreatedWithAMMWithTokensMarket
MultiOutcomeMarketFactoryMultiOutcomeMarketCreatedhandleMultiOutcomeMarketCreatedMultiOutcomeMarket
MultiOutcomeMarketFactoryMultiOutcomeOptionCreatedhandleMultiOutcomeOptionCreatedMarket
BinaryCPMM (template)CollateralSwappedForOutcomehandleBuyTrade, User, UserPosition
BinaryCPMM (template)SwappedhandleSellTrade, User, UserPosition
BinaryCPMM (template)OutcomeSoldForCollateralhandleOutcomeSoldForCollateralTrade, User, UserPosition

Contract Addresses (BSC Testnet)

ContractAddress
FactoryRegistry0xCC74a39C33a6058a18c4658590cCd3137d8DdC89
DCPPFactory0x615e991d7be7d6f40f4cdd797a94c972bce91917
MultiOutcomeMarketFactory0xdb714af139fce03d68496473cebd9d9d8b72b990
ConditionalTokens (CTF)0x2C9e65D17C01fEC736DcF5857C408483ec0a0D72
Collateral Token (USDC)0x71172871BCbe41dD8624Ede0294a56FA0FB87622

Rate Limiting

Redis-based token bucket strategy with a sliding 1-minute window, segmented by User ID / Admin ID / IP.

Endpoint CategoryLimitIdentifier
Auth nonce100/minIP
Auth login5/minIP
Wallet info100/minUser
Sign tx20/minUser
Withdraw3/minUser
Invite bind60/minUser
Orderbook read120/minIP
Orderbook write30/minUser
Admin auth30/minIP
Admin ops120/minAdmin
Admin sign30/minAdmin

When exceeded, returns 429 Too Many Requests + Retry-After header.