GuidesResources

Order signing

The EIP-712 domain, the order struct, the amount rules and the Predict account signature format, for signing orders without an SDK.

The TypeScript and Python SDKs build and sign orders for you. This page describes what they do, so you can sign orders from a language without an SDK, such as Rust or Go. If an SDK fits your stack, use it instead.

An order is EIP-712 typed data. You fill in the order's fields, hash them with the domain of the market's exchange, sign the hash, and send the signed order to POST /v1/orders as shown in Place orders.

The domain

FieldValue
namepredict.fun CTF Exchange
version1
chainId56 on BNB Mainnet, 97 on BNB Testnet
verifyingContractThe exchange for the market's type, from the table below

The market's isNegRisk and isYieldBearing fields decide the exchange. An order signed for the wrong exchange is rejected with create_order_contract_invalid_signature.

isNegRiskisYieldBearingBNB MainnetBNB Testnet
falsefalse0x8BC070BEdAB741406F4B1Eb65A72bee27894B6890x2A6413639BD3d73a20ed8C95F634Ce198ABbd2d7
truefalse0x365fb81bd4A24D6303cd2F19c349dE6894D8d58A0xd690b2bd441bE36431F6F6639D7Ad351e7B29680
falsetrue0x6bEb5a40C032AFc305961162d8204CDA16DECFa50x8a6B4Fa700A1e310b106E7a48bAFa29111f66e89
truetrue0x8A289d458f5a134bA40015085A8F50Ffb681B41d0x95D5113bc50eD201e319101bbca3e0E250662fCC

These are the CTFExchange and NegRiskCtfExchange contracts on Deployed contracts.

The order

The order type is:

Order(uint256 salt,address maker,address signer,address taker,uint256 tokenId,uint256 makerAmount,uint256 takerAmount,uint256 expiration,uint256 nonce,uint256 feeRateBps,uint8 side,uint8 signatureType)
FieldValue
saltA random integer below 2^63, so that two orders with the same terms get different hashes. The SDKs pick one below 2^31.
makerThe wallet's address. For a Predict account, the account's address.
signerThe same address as maker.
taker0x0000000000000000000000000000000000000000, so anyone can fill the order.
tokenIdThe outcome's onChainId, from the market.
makerAmountWhat you give, in wei: USDT for a buy, shares for a sell. See Amounts.
takerAmountWhat you get, in wei: shares for a buy, USDT for a sell.
expirationUnix time in seconds. At least 2 minutes ahead, and no later than 4102444800 (January 1, 2100). For a limit order that shouldn't expire, the SDKs use 4102444800. For a market order, they use 5 minutes from now.
nonce0, unless the wallet has raised its nonce on the exchange.
feeRateBpsThe market's feeRateBps. See Fees.
side0 to buy, 1 to sell.
signatureType0, for a regular wallet and for a Predict account alike.

In the request body, send the uint256 fields as decimal strings and side and signatureType as numbers, as in the request example. The hash you send is the order's EIP-712 hash, the one you sign.

Amounts

A limit order's amounts must match its price exactly, or the order is rejected with create_order_limit_amounts_mismatch. Compute them like this, in integers:

  1. Pick pricePerShare in wei. It's above 0 and below 10^18, with at most the market's decimalPrecision decimals: a multiple of 10^16 when decimalPrecision is 2, and of 10^15 when it's 3.
  2. For a buy, takerAmount is the number of shares in wei, and makerAmount is pricePerShare × takerAmount / 10^18, rounded down.
  3. For a sell, makerAmount is the number of shares in wei, and takerAmount is pricePerShare × makerAmount / 10^18, rounded down.

The amount that sets the price, makerAmount for a buy and takerAmount for a sell, must also be a multiple of 10^10, or the order is rejected with create_order_price_field_precision_invalid. The SDKs keep the number of shares to 5 significant digits, which makes it one for any order of at least 1 share.

For example, buying 10 shares at 0.40 gives a takerAmount of 10000000000000000000 and a makerAmount of 4000000000000000000.

A market order's amounts come from walking the order book, with your slippage. The API rejects one whose signed average price is more than 1% better for you than its pricePerShare, with create_order_market_amounts_mismatch. The SDKs' getMarketOrderAmounts is the reference for that calculation.

Sign with a regular wallet

Sign the typed data with any EIP-712 implementation, the same way eth_signTypedData_v4 does. The signature is the usual 65 bytes, r, s and v.

To check your encoding, hash this order. The domain is BNB Mainnet's exchange for markets that are neither negative-risk nor yield-bearing.

{
  "salt": "123456789",
  "maker": "0x1111111111111111111111111111111111111111",
  "signer": "0x1111111111111111111111111111111111111111",
  "taker": "0x0000000000000000000000000000000000000000",
  "tokenId": "1000000000000000000",
  "makerAmount": "4000000000000000000",
  "takerAmount": "10000000000000000000",
  "expiration": "4102444800",
  "nonce": "0",
  "feeRateBps": "200",
  "side": 0,
  "signatureType": 0
}
ValueHash
Type hash0xa852566c4e14d00869b6db0220888a9090a13eccdaea03713ff0a3d27bf9767c
Domain separator0x61e5a410200861954c73b7ec87a9054507b7b09d95035434808a719bf33bac8b
Order hash0xe6a0edf257e9904e5fd4a1b8fdc3dd092e8789c4fa596ff6275097515ee4fd34

Sign with a Predict account

A Predict account is a Kernel smart account (version 0.3.1). The exchange checks its signatures through the account, with ERC-1271. Your Privy wallet signs, and maker and signer are the account's address.

To sign a hash H for the account:

  1. Wrap it for Kernel: wrapped = keccak256(abi.encode(keccak256("Kernel(bytes32 hash)"), H)).
  2. Hash it with the account's EIP-712 domain: digest = keccak256(0x1901 ‖ domainSeparator ‖ wrapped). The domain is name Kernel, version 0.3.1, the chainId, and the account's address as verifyingContract.
  3. Sign digest with the Privy wallet as a personal message (EIP-191), over its 32 bytes.
  4. Prefix the 65-byte signature with the byte 0x01 and the address of the ECDSA validator, 0x845ADb2C711129d4f3966735eD98a9F09fC4cE57. The result is 86 bytes.

For an order, H is the order's EIP-712 hash. To log in, H is the EIP-191 hash of the message from GET /v1/auth/message, and the signer you send is the account's address.

The ECDSA validator has the same address on BNB Mainnet and BNB Testnet.

On this page