Research
Security News
Malicious PyPI Package ‘pycord-self’ Targets Discord Developers with Token Theft and Backdoor Exploit
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
mango-bowl
Advanced tools
familiar experience for centralized exchanges APIs users
WebSocket API with Pub/Sub flow - subscribe to selected channels and markets and receive real-time data as easy to parse JSON messages that can be consumed from any language supporting WebSocket protocol
incremental L2 order book updates - instead of decoding Mango market asks
and bids
accounts for each account change in order to detect order book updates, receive initial L2 snapshot and incremental updates as JSON messages real-time over WebSocket connection
tick-by-tick trades - instead of decoding eventQueue
account data which is quite large and in practice it's hard to consume real-time directly from Solana RPC node due to it's size, receive individual trade
messages real-time over WebSocket connection
real-time L3 data - receive the most granular updates on individual order level: open
, change
, fill
and done
messages for every order that Mango DEX processes
decreased load and bandwidth consumption for Solana RPC nodes hosts - by providing real-time market data API via mango-bowl server instead of RPC node directly, hosts can decrease substantially both CPU load and bandwidth requirements as only mango-bowl will be direct consumer of RPC API when it comes to market data accounts changes and will efficiently normalize and broadcast small JSON messages to all connected clients
mango-bowl provides real-time market data only and does not include endpoints for placing/canceling or tracking own orders as that requires handling private keys which is currently out of scope of this project.
Both microwavedcola1/mango-v3-service and @blockworks-foundation/mango-client provide such functionality and are recommended alternatives.
Run the code snippet below in the browser Dev Tools directly or in Node.js (requires installation of ws
lib, see).
// connect to hosted demo server
const ws = new WebSocket('wss://api.mango-bowl.com/v1/ws')
// if connecting to mango-bowl server running locally
// const ws = new WebSocket('ws://localhost:8010/v1/ws')
ws.onmessage = (message) => {
console.log(JSON.parse(message.data))
}
ws.onopen = () => {
// subscribe both to trades and level2 real-time channels
const subscribeTrades = {
op: 'subscribe',
channel: 'trades',
markets: ['MNGO-PERP', 'SOL-PERP']
}
const subscribeL2 = {
op: 'subscribe',
channel: 'level2',
markets: ['MNGO-PERP', 'SOL-PERP']
}
ws.send(JSON.stringify(subscribeTrades))
ws.send(JSON.stringify(subscribeL2))
}
Mango-bowl public hosted WebSocket server (backed by Project Serum RPC node) is available at:
wss://api.mango-bowl.com/v1/ws
For the best mango-bowl data reliability it's advised to set up a dedicated Solana RPC node and connect mango-bowl
to it instead of default https://solana-api.projectserum.com
which may rate limit or frequently restart Websocket RPC connections since it's a public node used by many.
Installs and starts mango-bowl server running on port 8000
.
npx mango-bowl
If you'd like to switch to different Solana RPC node endpoint like for example local one, change port or run with debug logs enabled, just add one of the available CLI options.
npx mango-bowl --endpoint http://localhost:8090 --ws-endpoint-port 8899 --log-level debug --port 8900
Alternatively you can install mango-bowl globally.
npm install -g mango-bowl
mango-bowl
name | default | description |
---|---|---|
port | 8010 | Port to bind server on |
endpoint | https://solana-api.projectserum.com | Solana RPC node endpoint that mango-bowl uses as a data source |
ws-endpoint-port | - | Optional Solana RPC WS node endpoint port that mango-bowl uses as a data source (if different than REST endpoint port) source |
log-level | info | Log level, available options: debug, info, warn and error |
minions-count | 1 | Minions worker threads count that are responsible for broadcasting normalized WS messages to connected clients |
commitment | confirmed | Solana commitment level to use when communicating with RPC node, available options: confirmed and processed |
group-name | mainnet.1 | Config group name to load Mango perp markets from |
Run npx mango-bowl --help
to see all available startup options.
Pulls and runs latest version of tardisdev/mango-bowl
Docker Image on port 8010
.
docker run -p 8010:8010 -d tardisdev/mango-bowl:latest
If you'd like to switch to different Solana RPC node endpoint, change port or run with debug logs enabled, just specify those via one of the available env variables.
docker run -p 8010:8010 -e "MB_LOG_LEVEL=debug" -d tardisdev/mango-bowl:latest
name | default | description |
---|---|---|
MB_PORT | 8010 | Port to bind server on |
MB_ENDPOINT | https://solana-api.projectserum.com | Solana RPC node endpoint that mango-bowl uses as a data source |
MB_WS_ENDPOINT_PORT | - | Optional Solana RPC WS node endpoint port that mango-bowl uses as a data source (if different than REST endpoint port) source |
MB_LOG_LEVEL | info | Log level, available options: debug, info, warn and error |
MB_MINIONS_COUNT | 1 | Minions worker threads count that are responsible for broadcasting normalized WS messages to connected clients |
MB_COMMITMENT | confirmed | Solana commitment level to use when communicating with RPC node, available options: confirmed and processed |
MB_GROUP_NAME | mainnet.1 | Config group name to load Mango perp markets from |
Mango-bowl supports SSL/TLS but it's not enabled by default. In order to enable it you need to set CERT_FILE_NAME
env var pointing to the certificate file and KEY_FILE_NAME
pointing to private key of that certificate.
WebSocket API provides real-time market data feeds of Mango Markets DEX and uses a bidirectional protocol which encodes all messages as JSON objects.
ws://localhost:8010/v1/ws - assuming mango-bowl runs locally on default port without SSL enabled
wss://api.mango-bowl.com/v1/ws - demo mango-bowl server endpoint
To begin receiving real-time market data feed messages, you must first send a subscribe message to the server indicating channels and markets for which you want the data for.
If you want to unsubscribe from channel and markets, send an unsubscribe message. The structure is equivalent to subscribe messages except op
field which should be set to "op": "unsubscribe"
.
const ws = new WebSocket('ws://localhost:8010/v1/ws')
ws.onopen = () => {
const subscribeL2 = {
op: 'subscribe',
channel: 'trades',
markets: ['SOL-PERP', 'MNGO-PERP']
}
ws.send(JSON.stringify(subscribeL2))
}
{
"op": "subscribe" | "unsubscribe",
"channel": "level3" | "level2" | "level1" | "trades",
"markets": string[]
}
subscribe
message{
"op": "subscribe",
"channel": "level2",
"markets": ["SOL-PERP", "MNGO-PERP"]
}
Once a subscription (or unsubscription) request is processed by the server, it will push subscribed
(or unsubscribed
) confirmation message or error
if received request message was invalid.
{
"type": "subscribed" | "unsubscribed",
"channel": "level3" | "level2" | "level1" | "trades",
"markets": string[],
"timestamp": string
}
subscribed
confirmation message{
"type": "subscribed",
"channel": "level2",
"markets": ["SOL-PERP"],
"timestamp": "2021-12-14T11:06:30.010Z"
}
Error message is pushed for invalid subscribe/unsubscribe messages - non existing market, invalid channel name etc.
{
"type": "error",
"message": "string,
"timestamp": "string
}
error
message{
"type": "error",
"message": "Invalid channel provided: 'levels1'.",
"timestamp": "2021-12-14T07:12:11.110Z"
}
When subscribed to the channel, server will push the data messages as specified below.
trades
level1
level2
level3
Markets supported by mango-bowl server can be queried via GET /markets
HTTP endpoint ([].name
field).
type
is determining message's data type so it can be handled appropriately
timestamp
when message has been received from node RPC API by mango-bowl server in ISO 8601 format with milliseconds, for example: "2021-12-14T07:03:03.994Z"
slot
is a Solana's slot number for which message has produced
version
of Mango DEX program layout
price
and size
are provided as strings to preserve precision
eventTimestamp
is a timestamp of event provided by DEX (with seconds precision) in ISO 8601, for example: "2021-12-14T07:03:03.000Z"
recent_trades
Up to 100 recent trades pushed immediately after successful subscription confirmation.
trades
array has the same format as trade
message{
"type": "recent_trades",
"market": string,
"trades": Trade[],
"timestamp": string
}
recent_trades
message{
"type": "recent_trades",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:33:42.437Z",
"trades": [
{
"type": "trade",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:33:02.437Z",
"slot": 111490354,
"version": 1,
"id": "293690612397529782803247|296512964240807321150678",
"side": "sell",
"price": "160.73",
"size": "4.53",
"eventTimestamp": "2021-12-14T12:32:59.000Z",
"takerAccount": "AAddgLu9reZCUWW1bNQFaXrCMAtwQpMRvmeusgk4pCM6",
"makerAccount": "EpAdzaqV13Es3x4dukfjFoCrKVXnZ7y9Y76whgMHo5qx",
"takerFeeCost": 0.04854859999988685,
"makerFeeCost": -0.02912915999993211
}
]
}
trade
Pushed real-time for each trade as it happens on a DEX (decoded from the eventQueue
account).
side
describes a liquidity taker side
id
field is an unique id constructed by joining fill taker and fill maker order id
eventTimestamp
is a timestamp of trade provided by DEX (with seconds precision) in ISO 8601
taker*
and maker*
fields provide info regarding maker and taker orders that constitute the trade
{
"type": "trade",
"market": string,
"timestamp": string,
"slot": number,
"version": number,
"id": string,
"side": "buy" | "sell",
"price": string,
"size": string,
"eventTimestamp": string,
"takerAccount": string,
"makerAccount": string,
"takerOrderId": string,
"makerOrderId": string,
"takerClientId": string,
"makerClientId": string,
"takerFeeCost": number,
"makerFeeCost": number
}
trade
message{
"type": "trade",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:35:34.309Z",
"slot": 111490613,
"version": 1,
"id": "296310050055996539132982|296346943544143935185875",
"side": "sell",
"price": "160.64",
"size": "0.50",
"eventTimestamp": "2021-12-14T12:35:29.000Z",
"takerAccount": "27YAdJWpEdMB4Pxk8EmkQ5PgDEiwwdxjQibcJy6jBNGS",
"makerAccount": "GR8wiP7NfHR8nihLjiADRoRM7V7aUvtTfUnkBy8Zkd5T",
"takerOrderId": "169580918269611952651370",
"makerOrderId": "173897456382859898438567",
"takerClientId": "864345997654345666",
"makerClientId": "7067091222090985486",
"takerFeeCost": 0.04854859999988685,
"makerFeeCost": -0.02912915999993211
}
quote
Pushed real-time for any change in best bid/ask price or size for a given market (decoded from the bids
and asks
accounts).
bestAsk
and bestBid
are tuples where first item is a price and second is a size of the best bid/ask level{
"type": "quote",
"market": string,
"timestamp": string,
"slot": number,
"version": number,
"bestAsk": [price: string, size: string] | undefined,
"bestBid": [price: string, size: string] | undefined
}
quote
message{
"type": "quote",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:40:18.809Z",
"slot": 111491086,
"version": 1,
"bestAsk": ["160.63", "0.50"],
"bestBid": ["160.53", "0.50"]
}
l2snapshot
Entire up-to-date order book snapshot with orders aggregated by price level pushed immediately after successful subscription confirmation.
asks
and bids
arrays contain tuples where first item of a tuple is a price level and second one is a size of the resting orders at that price level
it can be pushed for an active connection as well when underlying server connection to the RPC node has been restarted, in such scenario locally maintained order book should be re-initialized with a new snapshot
together with l2update
messages it can be used to maintain local up-to-date full order book state
{
"type": "l2snapshot",
"market": string,
"timestamp": string,
"slot": number,
"version": number,
"asks": [price: string, size: string][],
"bids": [price: string, size: string][]
}
l2snapshot
message{
"type": "l2snapshot",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:41:38.369Z",
"slot": 111491223,
"version": 1,
"asks": [
["160.63", "0.50"],
["160.68", "101.27"]
],
"bids": [
["160.53", "0.50"],
["160.47", "2.00"],
["160.45", "1828.47"]
]
}
l2update
Pushed real-time for any change to the order book for a given market with updated price levels and sizes since the previous update (decoded from the bids
and asks
accounts).
together with l2snapshot
, l2update
messages can be used to maintain local up-to-date full order book state
asks
and bids
arrays contain updates which are provided as a tuples where first item is an updated price level and second one is an updated size of the resting orders at that price level (absolute value, not delta)
if size is set to 0
it means that such price level does not exist anymore and shall be removed from locally maintained order book
{
"type": "l2update",
"market": string,
"timestamp": string,
"slot": number,
"version": number,
"asks": [price: string, size: string][],
"bids": [price: string, size: string][]
}
l2update
message{
"type": "l2update",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:43:03.993Z",
"slot": 111491367,
"version": 1,
"asks": [["160.63", "0.50"]],
"bids": []
}
l3snapshot
Entire up-to-date order book snapshot with all individual orders pushed immediately after successful subscription confirmation.
clientId
is an client provided order id for an order
account
is an open orders account address
accountSlot
is a an open orders account slot number
eventTimestamp
is a timestamp when order was added to the order book, provided by DEX (with seconds precision) in ISO 8601
together with open
, change
, fill
and done
messages it can be used to maintain local up to date Level 3 order book state
it can be pushed for an active connection as well when underlying server connection to the RPC node has been restarted, in such scenario locally maintained L3 order book should be re-initialized with a new snapshot
{
"type": "l3snapshot",
"market": string,
"timestamp": string,
"slot": number,
"version": number,
"asks": {
"price": string,
"size": string,
"side": "sell",
"orderId": string,
"clientId": string,
"account": string,
"accountSlot": number,
"eventTimestamp": string
}[],
"bids": {
"price": string,
"size": string,
"side": "buy",
"orderId": string,
"clientId": string,
"account": string,
"accountSlot": number,
"eventTimestamp": string
}[]
}
l3snapshot
message{
"type": "l3snapshot",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:46:40.670Z",
"slot": 111491726,
"version": 1,
"asks": [
{
"orderId": "295645967269342995275767",
"clientId": "1639485991672",
"side": "sell",
"price": "160.27",
"size": "101.53",
"account": "7xfGLkYwMBFbQ6iLTVMqmDrsU4CkaLHWuNLmdLTPmAtv",
"accountSlot": 3,
"eventTimestamp": "2021-12-14T12:46:34.000Z"
},
{
"orderId": "295664414013416704827395",
"clientId": "1639485996363",
"side": "sell",
"price": "160.28",
"size": "0.09",
"account": "B8CcUApFnKCWC49zy8u6YjCSFBKaATxQytegNSyQnoAn",
"accountSlot": 11,
"eventTimestamp": "2021-12-14T12:46:38.000Z"
}
],
"bids": [
{
"orderId": "295277032387868781191181",
"clientId": "3411370557685501304",
"side": "buy",
"price": "160.06",
"size": "8.06",
"account": "DAcKCh6VB2faJ8HRXXygr6fsCNRLB6JqzjmVmCJnGkx5",
"accountSlot": 0,
"eventTimestamp": "2021-12-14T12:46:29.000Z"
}
]
}
open
Pushed real-time for every new order opened on the limit order book (decoded from the bids
and asks
accounts).
eventTimestamp
is a timestamp when order was added to the order book, provided by DEX (with seconds precision) in ISO 8601
no open
messages are pushed for order that are filled or canceled immediately, for example - ImmediateOrCancel
orders
{
"type": "open",
"market": string,
"timestamp": string,
"slot": number,
"version": number,
"orderId": string,
"clientId": string,
"side": "buy" | "sell",
"price": string,
"size": string,
"account": string,
"accountSlot": number,
"eventTimestamp": string
}
open
message{
"type": "open",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:48:55.039Z",
"slot": 111491934,
"version": 1,
"orderId": "295147905179352837382395",
"clientId": "1639486128126",
"side": "sell",
"price": "160.00",
"size": "401.00",
"account": "8o2itcoSF7AfmhotNo7KjHaMSfUqHitJAr3wRnh9DkpF",
"accountSlot": 0,
"eventTimestamp": "2021-12-14T12:48:49.000Z"
}
change
Pushed real-time anytime order size changes as a result of self-trade prevention (decoded from the bids
and asks
accounts).
size
field contains updated order size{
"type": "change",
"market": string,
"timestamp": string,
"slot": number,
"version": number,
"orderId": string,
"clientId": string,
"side": "buy" | "sell",
"price": string,
"size": string,
"account": string,
"accountSlot": number
}
change
message{
"type": "change",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:49:00.359Z",
"slot": 111491944,
"version": 1,
"orderId": "293487698212718954684144",
"clientId": "1639486135724",
"side": "buy",
"price": "159.09",
"size": "102.06",
"account": "7xfGLkYwMBFbQ6iLTVMqmDrsU4CkaLHWuNLmdLTPmAtv",
"accountSlot": 0
}
fill
Pushed real-time anytime trade happens (decoded from the eventQueue
accounts).
there are always two fill
messages for a trade, one for a maker and one for a taker order
eventTimestamp
is a timestamp of fill provided by DEX (with seconds precision) in ISO 8601
feeCost
is provided in a quote currency
{
"type": "fill",
"market": string,
"timestamp": string,
"slot": number,
"version": number,
"orderId": string,
"clientId": string,
"side": "buy" | "sell",
"price": string,
"size": string,
"maker" boolean,
"feeCost" number,
"account": string,
"accountSlot": number,
"eventTimestamp": string
}
fill
message{
"type": "fill",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:49:05.442Z",
"slot": 111491953,
"version": 1,
"orderId": "294594502857141527781101",
"clientId": "1639486138455",
"side": "buy",
"price": "159.69",
"size": "0.50",
"maker": true,
"feeCost": -0.03193799999992557,
"account": "FowB2BtTty6MrBbY5ecAFVqiyJ6iLWVPsB58VrLjqW1Z",
"accountSlot": 0,
"eventTimestamp": "2021-12-14T12:49:02.000Z"
}
done
Pushed real-time when the order is no longer on the order book (decoded from the eventQueue
accounts).
this message can result from an order being canceled or filled (reason
field)
there will be no more messages for this orderId
after a done
message
it can be pushed for orders that were never open
in the order book in the first place (ImmediateOrCancel
orders for example)
sizeRemaining
field is available only for canceled orders (reason="canceled"
)
{
"type": "done",
"market": string,
"timestamp": string,
"slot": number,
"version": number,
"orderId": string,
"clientId": string,
"side": "buy" | "sell",
"reason" : "canceled" | "filled",
"sizeRemaining": string | undefined
"account": string,
"accountSlot": number
}
done
message{
"type": "done",
"market": "SOL-PERP",
"timestamp": "2021-12-14T12:49:09.928Z",
"slot": 111491960,
"version": 1,
"orderId": "294760523553804936798483",
"clientId": "1639486138456",
"side": "sell",
"reason": "canceled",
"account": "FowB2BtTty6MrBbY5ecAFVqiyJ6iLWVPsB58VrLjqW1Z",
"accountSlot": 1,
"sizeRemaining": "0.50"
}
/markets
Returns Mango DEX markets list supported by mango-bowl instance.
http://localhost:8010/v1/markets - assuming mango-bowl runs locally on default port without SSL enabled
https://api.mango-bowl.com/v1/markets - demo mango-bowl server endpoint
{
"name": string,
"baseCurrency": string,
"quoteCurrency": string,
"version": number,
"address": string,
"programId": string,
"tickSize": number,
"minOrderSize": number,
"takerFee": string,
"makerFee": string,
"liquidationFee": string
}[]
[
{
"name": "SOL-PERP",
"baseCurrency": "SOL",
"quoteCurrency": "USDC",
"version": 1,
"address": "2TgaaVoHgnSeEtXvWTx13zQeTf4hYWAMEiMQdcG6EwHi",
"programId": "mv3ekLzLbnVPNxjSKvqBpU3ZeZXPQdEC3bp5MDEBG68",
"tickSize": 0.01,
"minOrderSize": 0.01,
"takerFee": "0.00050",
"makerFee": "-0.00040",
"liquidationFee": "0.02500"
}
]
FAQs
Real-time WebSocket market data API server for Mango DEX
The npm package mango-bowl receives a total of 0 weekly downloads. As such, mango-bowl popularity was classified as not popular.
We found that mango-bowl demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.
Security News
Snyk's use of malicious npm packages for research raises ethical concerns, highlighting risks in public deployment, data exfiltration, and unauthorized testing.