Skip to main content

General

Testnet

  • Most of the endpoints can be used in the testnet platform.
  • The REST baseurl for testnet is https://api.base-sepolia.jojo.exchange/<api_version>
  • The Websocket baseurl for testnet is wss://stream.base-sepolia.jojo.exchange/<api_version>

Mainnets

BASE Mainnet

Api Version

The current official version is v1

General API Information

  • All endpoints return either a JSON object or array.
  • Data is returned in ascending order. Oldest first, newest last.
  • All time-related and timestamp-related fields are in milliseconds.

HTTP Return Codes

  • HTTP 4XX return codes are used for malformed requests; the issue is on the sender's side.
  • HTTP 403 return code is used when the WAF Limit (Web Application Firewall) has been violated.
  • HTTP 429 return code is used when breaking a request rate limit.
  • HTTP 418 return code is used when an IP has been auto-banned for continuing to send requests after receiving 429 codes.
  • HTTP 5XX return codes are used for internal errors; the issue is on JOJO's side.

Error Codes and Messages

  • Any endpoint can return an ERROR
{
"code": 1012,
"message": "Order Signature is invalid",
"codeText": "Invalid signature"
}
  • Specific error codes and messages are defined in Error Codes.

General Information on Endpoints

  • For GET, DELETE  endpoints, parameters must be sent as a query string.
  • For POSTPUT endpoints, the parameters may be sent as a query string or in the request body with content-type application/x-www-form-urlencoded. You may mix parameters between both the query string and request body if you wish to do so.
  • In POST and PUT requests, if a parameter is sent in both the query string and request body, the request body parameter will be used.

Limits

Rate limiters

  • The /api/v1/exchangeInfo rateLimits array contains objects related to the exchange's COMMON, and ORDER rate limits.
  • 429 will be returned when either rate limit is violated.
note

💡 JOJO has the right to further tighten the rate limits on users with intent to attack.

Common Rate Limits

  • The common rate limit is counted against the IP address
  • Except for the interfaces related to placing and canceling orders
  • Every request will contain X-Rate-Limit-Common-Remaining-{limit}-{interval} in the response headers which has the currently used weight for the IP for a request rate limiter defined.
  • Each route has a weight that determines the number of requests each endpoint counts for. Heavier endpoints and endpoints that do operations on multiple markets will have a heavier weight.
  • When a 429 is received, it's your obligation as an API user to back off and not spam the API.
  • Repeatedly violating rate limits and/or failing to back off after receiving 429s will result in an automated IP ban (HTTP status 418).
  • IP bans are tracked and scale in duration for repeat offenders, from 2 minutes to 3 days.

It is strongly recommended to use the WebSocket stream for getting data as much as possible, which can not only ensure the timeliness of the message but also reduce the access restriction pressure caused by the request.

Order Rate Limits

  • The order rate limit is counted against the account address.
  • Placing and canceling orders interfaces are applicable.
  • Every order response will contain a X-Rate-Limit-Order-Remaining-{limit}-{interval} header which has the current order count for the account for all order rate limiters defined.

Endpoint Security Type

  • Public rest API endpoints can be accessed freely.
  • Private rest API endpoints require sending a valid signature.
  • Market streams can be subscribed to freely.
  • Account stream requires connecting with a valid signature.

Private endpoint authentication

  • Beside the default parameters, Private reset API endpoints and Account stream require an additional parameter, signature, to be sent in the query string or request body.
  • ECDSA private key is the private key of the blockchain account in our general understanding.
  • Sorted urlencode params are a string constructed by the sorted parameters pairs of the request, excluding the signature parameter.
    • Pairs are sorted by parameter name.
    • A pair with an empty value should be omitted.
    • Key and value in a pair are connected by an = character, then pairs are connected by & characters.
    • See the example below.
  • Hashed sorted urlencode params is the Keccak-256 hash of the sorted urlencode params string with \x19Ethereum Signed Message:\n + length(message) Prefix.
  • The signature is generated by signing the Hashed sorted urlencode params with the ECDSA private key.
  • The signature is not case-sensitive.

Timing Security

  • A Signature protected endpoint also requires a parameter, timestamp, to be sent which should be the millisecond timestamp of when the request was created and sent. It's also a part of the sorted urlencode params.
  • An additional parameter, recvWindow, may be sent to specify the number of milliseconds after timestamp the request is valid for. If recvWindow is not sent, it defaults to 5000.
  • The logic is as follows:
  if (timestamp < (serverTime + 1000) && (serverTime - timestamp) <= recvWindow) {
// process request
} else {
// reject request
}
note

Serious trading is about timing. Networks can be unstable and unreliable, which can lead to requests taking varying amounts of time to reach the servers. With recvWindow, you can specify that the request must be processed within a certain number of milliseconds or be rejected by the server.

Example

Test Case

// With this test private key
ECDSA private key: "0000000000000000000000000000000000000000000000000000000000000001"

// And this sorted urlencode params
Sorted urlencode params: "account=0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf&argument2=bar&param1=foo&timestamp=1656059987512"

// The following hash is the hex encoded `Hashed sorted urlencode params`
Hashed sorted urlencode params: "0x067dced39c2ba229af48cbcdb675602b18b8fa8dbedf75f1651df994626d3f97"

// The following signature is the hex encoded `signature`
Valid siganture: "0x0620b244b8c02bd9882c50b9c5a8a7e0c244756c6a82ea0c79fac5ba38b43d2a279548c48e91c96aaa09c461f3c1e9a29151db4f90954990b8cb329bb857736d1b"

Golang Example

import(
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)

...

sortedParamsString := "account=0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf&argument2=bar&param1=foo&timestamp=1656059987512"
privateKey, _ := crypto.HexToECDSA("0000000000000000000000000000000000000000000000000000000000000001")
prefixedSortedParamsString := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(sortedParamsString), sortedParamsString)
hashedMessage := crypto.Keccak256([]byte(prefixedSortedParamsString))
signature, _ := crypto.Sign(hashedMessage, privateKey)
if signature[64] == 1 {
signature[64] = 28
} else if signature[64] == 0 {
signature[64] = 27
}

JOJO doesn't take EIP-155 into consideration. We only support the BASE chain. The V part in the signature (last two hex digits) can be in the form of 00, 01, or it can be 1b or 1C.

Nodejs Example

const { ecsign, hashPersonalMessage, toBuffer, toRpcSig } = require("ethereumjs-util");

const paramsString = "account=0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf&argument2=bar&param1=foo&timestamp=1656059987512"
const privateKey = "0x0000000000000000000000000000000000000000000000000000000000000001";
const hash = hashPersonalMessage(Buffer.from(paramsString, "utf-8"));
const ecdsaSignature = ecsign(hash, toBuffer(privateKey));
const signature = toRpcSig(ecdsaSignature.v,ecdsaSignature.r,ecdsaSignature.s);
console.log(signature)

Python Example

from web3.auto import w3
import json
import urllib
import binascii

def sign_message(message, private_key):
message_hash = w3.keccak(text=message)
signed_message = w3.eth.account.signHash(message_hash, private_key=private_key)
return signed_message.signature

payload = {}
payload['timestamp'] = "1656059987512"
payload['account'] = "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"
payload['argument2'] = "bar"
payload['param1'] = "foo"

sorted_params_str = json.dumps(payload, sort_keys=True)
sorted_params = json.loads(sorted_params_str)
url_params = urllib.parse.urlencode(sorted_params)

message = '\x19Ethereum Signed Message:\n{}{}'.format(len(url_params), url_params)
print("message: ", message)


signature = sign_message(message, "0000000000000000000000000000000000000000000000000000000000000001")
if signature[64] == 1:
signature[64] = 28
elif signature[64] == 0:
signature[64] = 27

print("signature:", "0x" + binascii.hexlify(signature).decode())

Operator endpoint authentication

Users can authorize the operator to a professional trading team so that professional traders can help users operate accounts to achieve the effect of high returns. And the way of operator endpoint authentication is the same as Private endpoint authentication, the only difference is that it uses the private key of the operator to sign parameters, but the account parameter should be the subaccount address.

Public Endpoints Info

Terminology

  • base asset refers to the asset that is the amount of a symbol.
  • quote asset refers to the asset that is the price of a symbol.

ENUM definitions

Order status (status):

  • CREATED (Initial status of the order)
  • NEW (No amount filled, but order has entered the matching engine)
  • FILLED (All amount filled)
  • FAILED (All amount failed)
  • CANCELED (All amount canceled)
  • EXPIRED (All amount expired)
  • PARTIAL_FILLED (Partial amount filled)

Order types (orderTypes, type):

  • LIMIT
  • MARKET

Order side (side):

  • BUY
  • SELL

Position side (positionSide):

  • LONG
  • SHORT

Trade types:

  • PENDING (confirming on the blockchain)
  • FAILED (Confirmed with a failed result)
  • SETTLED (Confirmed with a successful result)

Time in force (timeInForce):

  • GTC - Good Till Cancel
  • IOC - Immediate or Cancel
  • FOK - Fill or Kill
  • POST_ONLY - post only

Kline/Candlestick chart intervals:

M -> minutes; H -> hours; D -> days; W -> weeks; MO -> months

  • 1M
  • 5M
  • 15M
  • 30M
  • 1H
  • 2H
  • 4H
  • 6H
  • 1D
  • 1W
  • 1MO.

Income Types

  • DEPOSIT
  • WITHDRAWAL
  • LIQUIDATED (be liquidated)
  • LIQUIDATE (liquidate other's position)
  • TAKER_FEE
  • MAKER_FEE
  • BUY (closing position)
  • SELL (closing position)
  • FUNDING_FEE

Filters

Filters define trading rules on a symbol or an exchange.

PRICE_FILTER

{
"filterType": "PRICE_FILTER",
"minPrice": "0.00000100",
"maxPrice": "100000.00000000",
"tickSize": "0.00000100"
}

The PRICE_FILTER defines the price rules for a symbol. There are 3 parts:

  • minPrice defines the minimum price allowed; disabled on minPrice == 0.
  • maxPrice defines the maximum price allowed; disabled on maxPrice == 0.
  • tickSize defines the intervals of a price that can be increased/decreased by; disabled on tickSize == 0.

Any of the above variables can be set to 0, which disables that rule in the PRICE_FILTER. In order to pass the PRICE_FILTER, the following must be true for price of the enabled rules:

  • price >= minPrice
  • price <= maxPrice
  • (price - minPrice) % tickSize == 0

AMOUNT_FILTER

{
"filterType": "LOT_SIZE",
"minAmount": "0.00100000",
"maxAmount": "100000.00000000",
"stepSize": "0.00100000"
}

The AMOUNT_FILTER filter defines the amount rules for a symbol. There are 3 parts:

  • minAmount defines the minimum amount allowed.
  • maxAmount defines the maximum amount allowed.
  • stepSize defines the intervals of an amount that can be increased/decreased.

In order to pass the AMOUNT_FILTER, the following must be true for amount:

  • amount >= minAmount
  • amount <= maxAmount
  • (amount - minAmount) % stepSize == 0

MARKET_AMOUNT_FILTER

{
"filterType": "MARKET_AMOUNT_FILTER",
"minAmount": "0.00100000",
"maxAmount": "100000.00000000",
"stepSize": "0.00100000"
}

The MARKET_AMOUNT_FILTER filter defines the amount rules for MARKET orders on a market. There are 3 parts:

  • minAmount defines the minimum amount allowed.
  • maxAmount defines the maximum amount allowed.
  • stepSize defines the intervals of an amount that can be increased/decreased.

In order to pass the MARKET_AMOUNT_FILTER, the following must be true for amount:

  • amount >= minAmount
  • amount <= maxAmount
  • (amount - minAmount) % stepSize == 0

MAX_NUM_ORDERS

{
"filterType": "MAX_OPEN_ORDERS_FILTER",
"limit": 200
}

The MAX_OPEN_ORDERS_FILTER filter defines the maximum number of orders an account is allowed to have open on a market.