eosfinex Websocket adapter for Node and the browser.
We designed the eosfinex Websocket API with both speed and compatibility in mind. It is a subset of the existing Bitfinex API v2, with modifications for on-chain trading. Existing API v2 users should be able to start with only a small changeset to their existing clients.
Sunbeam is the current reference implementation of the Websocket API client. It interacts with the contract ABI. For more details, see the signing section.
This readme covers both the Sunbeam API and explains how it interacts behind the scenes with the server.
The main difference between the Bitfinex WS API and eosfinex's API is the signing of messages. For eosfinex, we have to sign actions with our eosjs private key. A signed transaction is also used for subscriptions to trade, order and wallet snapshots and updates.
const Sunbeam = require('sunbeam')
// Browser usage: import into browsers
import Sunbeam from 'sunbeam/dist'
On paper trading tokens are generally prefixed by P
. So BTC
becomes
PBTC
. The only exception is the resource token EOX
. For a list of
available trading pairs, see trade-pairs.
Example of the Websocket client usage: example-ws.js
Run with:
node example-ws.js
You can see all API calls in example-ws.js or example-ual.js. Sunbeam support private key or UAL for authentication.
client <Object>
rpc
official eosjs rpc class instanceapi
official eosjs Api class instance
opts <Object>
urls <Object>
Websocket transportspub <String>
Public transportpriv <String>
Private transportaux <String>
Aux transport
eos <Object>
options passed to Eos client for signing transactionsexpireInSeconds <Number>
Expiration time for signed tx. 7 days by defaulthttpEndpoint <String|null>
an Eos node HTTP endpoint, used to get the contract abi, if abi not passed via options.exchangeContract <String|null>
name of the used exchange contract, defaults toefinexchange
auth
Auth optionskeys
use default signingkeyProvider <String>
your key, used to sign transactionsaccount <String>
accountname to use for the keypermission <String>
permission level to use for the account
ual <Object>
UAL options if UAL is used for signinguser <String>
Authenticated UAL user
state <Object>
Options passed to state componentstransform <Object>
transformation options (keyed objects or array format)orderbook <Object>
keyed <Boolean>
Manage state as keyed Objects instead of an Array
wallet <Object>
orders <Object>
keyed <Boolean>
Manage state as keyed Objects instead of an ArraymarkDeleted <Boolean>
cancelled orders are flagged as deleted, but not removed from the state
// prepare eosjs lib for signing of websocket messages
const { Api, JsonRpc } = require('eosjs')
const { JsSignatureProvider } = require('eosjs/dist/eosjs-jssig')
const fetch = require('node-fetch')
const { TextDecoder, TextEncoder } = require('util')
const keys = ['SECRET']
const signatureProvider = new JsSignatureProvider(keys)
const httpEndpoint = 'https://api.eosfinex.com'
const rpc = new JsonRpc(httpEndpoint, { fetch })
const api = new Api({
rpc,
signatureProvider,
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder()
})
const client = {
rpc,
api
}
// setup sunbeam
const opts = {
urls: {
pub: 'wss://api.bitfinex.com/ws/2',
priv: 'wss://api.eosfinex.com/ws'
},
eos: {
expireInSeconds: 7 * 24 * 60 * 60, // 7 days
httpEndpoint, // Used to get metadata for signing transactions
exchangeContract: 'eosfinexeos1', // Name of the exchange contract
auth: {
ual: {
user: '', // UAL user object
},
},
},
state: {
transform: {
orderbook: { keyed: true },
wallet: {},
orders: {}
}
}
}
const ws = new Sunbeam(client, opts)
In order to start trading it is required to sign up for the platform at eosfinex.com and accept terms of services.
For an example how to use Scatter for auth, see example-scatter.js.
Emitted when the socket connection is established.
Example:
const ws = new Sunbeam(opts)
ws.on('open', () => {
// ready to trade!
})
ws.open()
Emitted for every message that the Websocket client receives. Useful for debugging and custom extensions.
Example:
ws.on('message', (m) => {
console.log(m)
})
Emitted in case of an error.
Example:
ws.on('error', (m) => {
console.error(m)
})
You can see all API calls in example-ws.js. Before you can run it, make sure to configure your environment:
cp config/example-ws.config.json.example config/example-ws.config.json
vim config/example-ws.config.json
or see example-scatter.js on the in-code configuration example.
Opens a Websocket.
Example:
ws.open()
Closes the connection to the server.
Example:
ws.close()
Subscribes you to wallet
, trade
and order
updates for the specified account.
Takes the account name you have defined when creating a Sunbeam instance with
opts.eos.auth
or receives the account name from Scatter. Your private key
stays on your machine.
You can authenticate on the eosfinex Websocket API like with the Bitfinex API.
The API will then send you your wallet, order and trades snapshots and updates.
For this a special action is available in the contract ABI called validate
.
The Websocket plugin uses it to verify that the transaction belongs to the
proper account.
Sunbeam signs a verification transaction that is send to the Websocket endpoint for validation. This transaction is just used for verifying the signature.
If you configured auth via scatter, it will connect to scatter. Remember to remove any global references to ScatterJS and any global references to Sunbeam:
window.ScatterJS = null;
Sent payload:
{
event: 'auth',
account: <username>,
meta: <signed>
}
Where <signed>
is a signed transaction for the validate
action.
Important: to be able to trade, you have to sign up for the platform and accept the terms of service at eosfinex.com.
user <Object>
optional user object. If not defined, the data provided in the constructor will be used or retrieved from Scatter
Signs a transaction that can be used for login on the WS server for custom auth flows.
Example:
const { Serialize } = require('eosjs')
const user = {
authorization: {
authorization: "testuser1114@active"
},
account: "testuser1114",
permission: "active"
}
const signed = await ws.getSignedTx(user)
const serializedTransaction = api.serializeTransaction(signed)
const authTxHex = Serialize.arrayToHex(serializedTransaction)
const authTxPayload = {
t: authTxHex,
s: signed.signatures[0]
}
const history = await fetch(moonbeamUrl + '/v1/history', {
method: 'POST',
body: JSON.stringify({
auth: authTxPayload,
data: { limit: 100 }
}),
headers: { 'Content-Type': 'application/json' }
}).then(res => res.json())
console.log(JSON.stringify(history, null, 2))
Forgets scatter identity (if scatter is used for auth).
order <Object>
symbol <String>
Token pair to trade foramount <String>
Amount to buy/selltype <String>
Order type,EXCHANGE MARKET
,EXCHANGE LIMIT
,EXCHANGE STOP
,EXCHANGE FOK
,EXCHANGE IOC
price <String>
Price for orders except market orderscid <Number>
Every order must have a unique id assigned from the client, defaults to current timestampgid <Number>
Group id for the order, optionalflags <Number>
See flags listtif <Datetime string> or <Number>
Time-In-Force: datetime for automatic order cancellation (ie.2020-01-01 10:45:23
). Applicable toEXCHANGE LIMIT
orders only.tif
may be an integer value representing the number of milliseconds since January 1, 1970, or a string value representing a date (see more here). NOTE: if no timezone is passed, client timezone will be implied.
Creates an order compatible with the contract ABI, pre-signs it and sends it to the Websocket endpoint.
To be able to identify the order, a client MUST send a custom client id with it that must be unique to the clients orders.
The following Websocket message is sent on authentication and contains the maximum possible
price deviation from the best price when placing an order{ event: 'pl', val: 0.1 }
.
For example, if the best price is 10 and deviation is 0.1, you won't be able to place a buy order for the price
less than 10 - 10 * 0.1 = 9.
If the price in the order
exceeds the deviation, an error event will be generated.
Some flags are abstracted by Sunbeam. Here is a full list of available flags:
Flag Type Value Description
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Hidden int 64 The hidden order option ensures an order does not appear in the order book; thus does not influence other market participants.
Close int 512 Close position if position present.
Reduce Only int 1024 Ensures that the executed order does not flip the opened position.
Post Only int 4096 The post-only limit order option ensures the limit order will be added to the order book and not match with a pre-existing order.
curl --request POST \
--url https://api.eosfinex.com/v1/chain/get_abi \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{"account_name":"eosfinexeos1"}'
After signing, the transaction is sent to the Websocket server:
const signed = {
"expiration": "2020-05-10T18:17:13.000",
"ref_block_num": 27684,
"ref_block_prefix": 959416581,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [
{
"account": "efinexchange",
"name": "place",
"authorization": [
{
"actor": "testuser1114",
"permission": "active"
}
],
"data": "40420857619DB1CAF1B4BFA26A01000000800139610D3A5548DA2D0000000000942A00000000000000"
}
],
"transaction_extensions": [],
"signatures": [
"SIG_K1_Ke541LARCg24Zg66cacNLEfrJXJzfdgeZKNmyKEsxyGUVFxxEPxqJqYQJgv8gc2CXsG9a5HYvDrMePUGyfH8P6uihDoiSp"
]
}
// payload sent to the server:
[0, 'on', null, {
cid,
type,
symbol,
price,
amount,
flags,
meta: <transaction data>
}]
Example:
const order = {
symbol: pair,
price: '8100',
amount: '-0.01',
type: 'EXCHANGE LIMIT',
cid: '1332'
}
ws.place(order)
The request will be signed locally using the eosjs
module.
Sent payload:
[0, 'on', null, {
cid,
type,
symbol,
price,
amount,
flags,
meta: <transaction data>
}]
<transaction data>
is a signed transaction payload in array notation for the place
action of the exchange contract.
data
id <String>
The id returned from the contract on placement
Cancels an order.
Example:
ws.cancel({ id: '18446744073709551612' })
Sent payload:
[0, 'oc', null, { id }]
data
- an Object or Array of Objects with the following propertiescurrency <String>
The currency to withdraw, e.g.BTC
amount <String>
The amount to withdrawmemo <String> (optional)
Additional note
Withdraws tokens to the account used for authentication.
A transaction is created, signed using eosjs client
that was passed at Sunbeam instantiation, and pushed to the chain.
A single transaction may hold several withdrawals.
Transaction data and push result are returned.
Example:
const { txResult, txData } = await ws.withdraw({
currency: 'EUR',
amount: '0.678'
})
// txData - array of objects: { currency, amount }
// txResult - data returned from `eosjs.api.transact(..)`
The request will be signed locally using the eosjs
module.
data
- an Object or Array of Objects with the following propertiescurrency <String>
The currency to deposit, e.g.BTC
amount <String>
The amount to depositto <String> (optional)
The receiver. Defaults toexchangeContract
from the configuration objectmemo <String> (optional)
Additional note
Deposits the desired amount to the exchange. Takes the user account used for auth.
A transaction is created, signed using eosjs client
passed at Sunbeam instantiation, and pushed to the chain.
A single transaction may hold several deposits.
Transaction data and push result are returned.
Example:
const { txResult, txData } = await ws.deposit({
currency: 'EUR',
amount: '2'
})
// txData - array of objects: { currency, amount, to }
// txResult - data returned from `eosjs.api.transact(..)`
The request will be signed locally using the eosjs
module.
transport <String>
The Websocket transport to use (priv
,pub
,aux
)channel <String>
The channel name to subscribe toopts <Object>
Additional data to send
Subscribes to a Websocket channel.
Available channels for priv
:
reports trade updates
wallets wallet snapshots / updates
The channels reports
and wallets
are automatically subscribed on authentication
via Websocket.
Example:
ws.subscribe('priv', 'wallets', { account: 'testuser1431' })
Sent Payload:
{
event: 'subscribe',
channel: 'wallets',
account: 'testuser1431'
}
Channel book
is available for pub
and represents orderbook information.
See bitfinex docs for more details.
transport <String>
The Websocket transport to use (priv
,pub
,aux
)channel <String>
The channel name or channel id to unsubscribe fromopts <Object>
Additional data to send
Unsubscribes from a channel.
The channels reports
and wallets
are automatically subscribed on authentication
via Websocket.
Example:
ws.unsubscribe('priv', 'wallets', { account: 'testuser1431' })
Sent Payload:
{
event: 'unsubscribe',
channel: 'wallets',
account: 'testuser1431'
}
There are a few additional RPC calls that can help when writing your own client.
Returns metadata used for signing transactions via Websocket.
Format is:
[0, 'ci', [
$headBlockTime,
$lastIrreversibleBlockNumber,
$chainId,
$refBlockPrefix
]]
Example:
ws.on('message', (m) => {
console.log(m)
})
ws.send('priv', { event: 'chain' })
or
const meta = await ws.requestChainMeta('priv')
Example response:
[ '0',
'ci',
[ '1557850696',
9296555,
'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f',
'372320450' ] ]
Example:
ws.on('message', (m) => {
console.log(m)
})
ws.send('priv', { event: 'symbols' })
Example response:
[
0,
'si',
[
{
pair: 'tEOSUST',
price_precision: 5,
initial_margin: '0.0',
minimum_margin: '0.0',
maximum_order_size: '100000.0',
minimum_order_size: '1',
expiration: 'NA',
margin: 0
},
{
pair: 'tBTCUSD',
price_precision: 5,
initial_margin: '0.0',
minimum_margin: '0.0',
maximum_order_size: '100000.0',
minimum_order_size: '1',
expiration: 'NA',
margin: 0
},
{
pair: 'tBTCUST',
price_precision: 5,
initial_margin: '0.0',
minimum_margin: '0.0',
maximum_order_size: '100000.0',
minimum_order_size: '1',
expiration: 'NA',
margin: 0
}
]
]
Usually the Bitfinex trading protocol will send a snapshot, and later just updates, for performance reasons.
When you register a managed
orderbook handler, the managed state component
will take care of parsing the snapshots update the state when partial updates arrive.
For every update, the full updated data is emitted.
Orderbook updates come for the orders of authenticated user. Public orderbook data is available via Bitfinex Websocket connection.
opts <Object>
symbol <String>
The symbol to emit the orderbook update for, i.e.tBTCUST
handler <Function>
Called every time the state is updated
The input format is R0
: see bitfinex docs for more details
If you want to manage state on your own, our just need a stream of updates, use
the onOrderbook
handler.
Example:
const pair = 'tBTCUST'
ws.onManagedOrderbook({ symbol: pair }, (ob) => {
console.log(`ws.onManagedOrderbook({ symbol: ${pair} })`)
console.log(ob)
ws.unsubscribeOrderbook(pair)
})
Registered for messages from the corresponding book channel (received on subscribe).
opts <Object>
handler <Function>
Called every time the state is updated
Example:
ws.onManagedWallet({}, (mw) => {
console.log('ws.onManagedWallet')
console.log(mw)
})
ws.auth()
Registered for ws
, wu
messages via channel 0
.
Channel is automatically subscribed by the API when doing an auth.
opts <Object>
handler <Function>
Called every time the state is updated
Example:
ws.onManagedOrders({}, (orders) => {
console.log(orders)
})
ws.auth()
Registered for os
, on
, ou
, oc
messages via channel 0
.
If you want to manage state on your own, or have a special use case, you can use unmanaged handlers.
opts <Object>
handler <Function>
Called every time the state is updated
Example:
ws.onWallet({}, (wu) => {
console.log('ws.onWallet')
console.log(wu)
})
ws.auth()
Registered for ws
, wu
messages via channel 0
.
Channel is automatically subscribed by the API when doing an auth.
opts <Object>
?symbol <String>
optional: filter by pairhandler <Function>
The callback called for every update
Example:
const pair = 'tBTCUST'
ws.onOrders({}, (data) => {
console.log('ws.onOrders({})')
console.log(data)
})
// filter enabled
ws.onOrders({ symbol: pair }, (data) => {
console.log(`ws.onOrders({ symbol: ${pair} })`)
console.log(data)
})
ws.auth()
Registered for os
, on
, ou
, oc
messages via channel 0
.
opts <Object>
handler <Function>
The callback called for every update
Called when an own, submitted order matches.
Example:
ws.onPrivateTrades({}, (data) => {
console.log('ws.onPrivateTrades({})')
console.log('private trade', data) // emits [ 'ETH.USD', 'te', [ '3', 1537196302500, -0.9, 1 ] ]
})
ws.auth()
Registered for tu
, te
messages via channel 0
.
opts <Object>
?symbol <String>
optional: the symbol to emit the public trade updates for, i.e.tBTCUST
handler <Function>
The callback called for every update
Example:
const pair = 'tBTCUST'
ws.onPublicTrades({}, (data) => {
console.log(`ws.onPublicTrades({})`)
console.log('public trade', data)
})
ws.onPublicTrades({ symbol: pair }, (data) => {
console.log(`ws.onPublicTrades({ symbol: ${pair} })`)
console.log('public trade', data)
})
ws.subscribePublicTrades('tBTCUST')
Registered for tu
, te
messages via the corresponding channel for the symbol.
opts <Object>
symbol <String>
The symbol to emit the orderbook update for, i.e.tBTCUST
handler <Function>
The callback called for every update
Just emits order updates and order snapshots without keeping or managing state.
Example:
const pair = 'tBTCUST'
ws.onOrderbook({ symbol: pair }, (ob) => {
console.log(`ws.onOrderbook({ symbol: ${pair} })`)
console.log(ob)
})
ws.subscribeOrderbook(pair)
Registered for messages from the corresponding book channel (received on subscribe).
Sunbeam can take care of managing state snapshots for you, and keeps them up to date when the API sends updates.
Sometimes you may want to interact with Sunbeam's managed state. They are exposed through this.state
ws.state
Start nodeos:
nodeos --access-control-allow-origin "*" --verbose-http-error --http-validate-host=false --enable-stale-production --producer-name eosio --plugin eosio::chain_api_plugin --plugin eosio::net_api_plugin
--contracts-console
will output the logging from custom contracts
Enable CORS for your EOS node, by enabling it via config:
cd ~/eosdata/
echo "access-control-allow-origin = *" >> config.ini