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>
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 receiving429
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 aquery string
. - For
POST
,PUT
endpoints, the parameters may be sent as aquery string
or in therequest body
with content-typeapplication/x-www-form-urlencoded
. You may mix parameters between both thequery string
andrequest body
if you wish to do so. - In
POST
andPUT
requests, if a parameter is sent in both thequery string
andrequest body
, therequest body
parameter will be used.
Limits
Rate limiters
- The
/api/v1/exchangeInfo
rateLimits
array contains objects related to the exchange'sCOMMON
, andORDER
rate limits. - A
429
will 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
weight
that 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 API
endpoints andAccount stream
require an additional parameter,signature
, to be sent in thequery string
orrequest 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 thesignature
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 theKeccak-256
hash of thesorted urlencode params
string with\x19Ethereum Signed Message:\n + length(message)
Prefix.- The
signature
is generated by signing theHashed sorted urlencode params
with theECDSA 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 thesorted urlencode params
. - An additional parameter,
recvWindow
, may be sent to specify the number of milliseconds aftertimestamp
the request is valid for. IfrecvWindow
is 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 asset
refers to the asset that is theamount
of a symbol.quote asset
refers to the asset that is theprice
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 minimumprice
allowed; disabled onminPrice
== 0.maxPrice
defines the maximumprice
allowed; disabled onmaxPrice
== 0.tickSize
defines the intervals of aprice
that 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
>=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 minimumamount
allowed.maxAmount
defines the maximumamount
allowed.stepSize
defines the intervals of anamount
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 minimumamount
allowed.maxAmount
defines the maximumamount
allowed.stepSize
defines the intervals of anamount
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.