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
- Rest: https://api.base-mainnet.jojo.exchange/<api_version>
- Websocket: wss://stream.base-mainnet.jojo.exchange/<api_version>
JOJO API SDK
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
4XXreturn codes are used for malformed requests; the issue is on the sender's side. - HTTP
403return code is used when the WAF Limit (Web Application Firewall) has been violated. - HTTP
429return code is used when breaking a request rate limit. - HTTP
418return code is used when an IP has been auto-banned for continuing to send requests after receiving429codes. - HTTP
5XXreturn 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,DELETEendpoints, parameters must be sent as aquery string. - For
POST,PUTendpoints, the parameters may be sent as aquery stringor in therequest bodywith content-typeapplication/x-www-form-urlencoded. You may mix parameters between both thequery stringandrequest bodyif you wish to do so. - In
POSTandPUTrequests, if a parameter is sent in both thequery stringandrequest body, therequest bodyparameter will be used.
Limits
Rate limiters
- The
/api/v1/exchangeInforateLimitsarray contains objects related to the exchange'sCOMMON, andORDERrate limits. - A
429will be returned when either rate limit is violated.
💡 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
weightthat determines the number of requests each endpoint counts for. Heavier endpoints and endpoints that do operations on multiple markets will have a heavierweight. - 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 APIendpoints andAccount streamrequire an additional parameter,signature, to be sent in thequery stringorrequest body. ECDSA private keyis the private key of the blockchain account in our general understanding.Sorted urlencode paramsare a string constructed by the sorted parameters pairs of the request, excluding thesignatureparameter.- 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 paramsis theKeccak-256hash of thesorted urlencode paramsstring with\x19Ethereum Signed Message:\n + length(message)Prefix.- The
signatureis generated by signing theHashed sorted urlencode paramswith theECDSA private key. - The
signatureis 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 thesorted urlencode params. - An additional parameter,
recvWindow, may be sent to specify the number of milliseconds aftertimestampthe request is valid for. IfrecvWindowis not sent, it defaults to 5000. - The logic is as follows:
if (timestamp < (serverTime + 1000) && (serverTime - timestamp) <= recvWindow) {
// process request
} else {
// reject request
}
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¶m1=foo×tamp=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¶m1=foo×tamp=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¶m1=foo×tamp=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 assetrefers to the asset that is theamountof a symbol.quote assetrefers to the asset that is thepriceof 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
1M5M15M30M1H2H4H6H1D1W1MO.
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:
minPricedefines the minimumpriceallowed; disabled onminPrice== 0.maxPricedefines the maximumpriceallowed; disabled onmaxPrice== 0.tickSizedefines the intervals of apricethat can be increased/decreased by; disabled ontickSize== 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>=minPriceprice<=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:
minAmountdefines the minimumamountallowed.maxAmountdefines the maximumamountallowed.stepSizedefines the intervals of anamountthat can be increased/decreased.
In order to pass the AMOUNT_FILTER, the following must be true for amount:
amount>=minAmountamount<=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:
minAmountdefines the minimumamountallowed.maxAmountdefines the maximumamountallowed.stepSizedefines the intervals of anamountthat can be increased/decreased.
In order to pass the MARKET_AMOUNT_FILTER, the following must be true for amount:
amount>=minAmountamount<=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.