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
| Field | Value |
|---|---|
name | predict.fun CTF Exchange |
version | 1 |
chainId | 56 on BNB Mainnet, 97 on BNB Testnet |
verifyingContract | The 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.
isNegRisk | isYieldBearing | BNB Mainnet | BNB Testnet |
|---|---|---|---|
false | false | 0x8BC070BEdAB741406F4B1Eb65A72bee27894B689 | 0x2A6413639BD3d73a20ed8C95F634Ce198ABbd2d7 |
true | false | 0x365fb81bd4A24D6303cd2F19c349dE6894D8d58A | 0xd690b2bd441bE36431F6F6639D7Ad351e7B29680 |
false | true | 0x6bEb5a40C032AFc305961162d8204CDA16DECFa5 | 0x8a6B4Fa700A1e310b106E7a48bAFa29111f66e89 |
true | true | 0x8A289d458f5a134bA40015085A8F50Ffb681B41d | 0x95D5113bc50eD201e319101bbca3e0E250662fCC |
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)| Field | Value |
|---|---|
salt | A random integer below 2^63, so that two orders with the same terms get different hashes. The SDKs pick one below 2^31. |
maker | The wallet's address. For a Predict account, the account's address. |
signer | The same address as maker. |
taker | 0x0000000000000000000000000000000000000000, so anyone can fill the order. |
tokenId | The outcome's onChainId, from the market. |
makerAmount | What you give, in wei: USDT for a buy, shares for a sell. See Amounts. |
takerAmount | What you get, in wei: shares for a buy, USDT for a sell. |
expiration | Unix 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. |
nonce | 0, unless the wallet has raised its nonce on the exchange. |
feeRateBps | The market's feeRateBps. See Fees. |
side | 0 to buy, 1 to sell. |
signatureType | 0, 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:
- Pick
pricePerSharein wei. It's above 0 and below10^18, with at most the market'sdecimalPrecisiondecimals: a multiple of10^16whendecimalPrecisionis 2, and of10^15when it's 3. - For a buy,
takerAmountis the number of shares in wei, andmakerAmountispricePerShare × takerAmount / 10^18, rounded down. - For a sell,
makerAmountis the number of shares in wei, andtakerAmountispricePerShare × 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
}| Value | Hash |
|---|---|
| Type hash | 0xa852566c4e14d00869b6db0220888a9090a13eccdaea03713ff0a3d27bf9767c |
| Domain separator | 0x61e5a410200861954c73b7ec87a9054507b7b09d95035434808a719bf33bac8b |
| Order hash | 0xe6a0edf257e9904e5fd4a1b8fdc3dd092e8789c4fa596ff6275097515ee4fd34 |
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:
- Wrap it for Kernel:
wrapped = keccak256(abi.encode(keccak256("Kernel(bytes32 hash)"), H)). - Hash it with the account's EIP-712 domain:
digest = keccak256(0x1901 ‖ domainSeparator ‖ wrapped). The domain isnameKernel,version0.3.1, thechainId, and the account's address asverifyingContract. - Sign
digestwith the Privy wallet as a personal message (EIP-191), over its 32 bytes. - Prefix the 65-byte signature with the byte
0x01and 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.