This readme overviews how to configure the oracle side-car as well as how to hook it up to your application. To see an example of a properly configured oracle-side car, visit the local config files -
oracle.json
andmarket.json
. To see an example of a properly configured application, please visit the test application's app.toml generation code. Otherwise, read on to learn how to configure the oracle side-car and application.Validators running on a network that support's Slinky must run the oracle side-car and configure it into their application. Non-validators can configure their oracle config's to be disabled, and the oracle side-car will not be run.
Type Oracle/Market Config Oracle Metrics App Metrics Validator Required Recommended Recommended Non-Validator Optional Optional Optional
All oracle configurations are broken down into three files:
- Oracle side-car configuration (
oracle.json
): This contains the data provider's that are utilized, how often they should be polled, and a variety of other configurations for API and web socket providers. - Market side-car configuration (
market.json
): This contains the desired markets that the side-car will fetch prices for. NOTE: It is recommended that this file is NOT modified nor created by validators. This file is typically provided by the chain that the side-car supports. - Oracle configuration in the application (
app.toml
): A few additional lines of code that must be added to the application'sapp.toml
file to configure the oracle sidecar into the application.
The focus of this readme is the oracle side-car configuration and the application configuration. The market side-car configuration is typically provided by the chain that the oracle supports.
The app.toml
file is the configuration file that is consumed by the application. This file contains over-arching configurations for your entire Cosmos SDK application, as well as a few new configurations for the oracle. You must use this template to add the oracle configurations to your app.toml
file:
# Other configurations
# ...
###############################################################################
### Oracle ###
###############################################################################
[oracle]
# Enabled indicates whether the oracle is enabled.
enabled = "{{ .Oracle.Enabled }}"
# Oracle Address is the URL of the out of process oracle sidecar. This is used to
# connect to the oracle sidecar when the application boots up. Note that the address
# can be modified at any point, but will only take effect after the application is
# restarted. This can be the address of an oracle container running on the same
# machine or a remote machine.
oracle_address = "{{ .Oracle.OracleAddress }}"
# Client Timeout is the time that the client is willing to wait for responses from
# the side-car before timing out.
client_timeout = "{{ .Oracle.ClientTimeout }}"
# MetricsEnabled determines whether oracle metrics are enabled. Specifically
# this enables instrumentation of the side-car client and the interaction between
# the side-car and the app.
metrics_enabled = "{{ .Oracle.MetricsEnabled }}"
# ...
# More configurations
In your app.toml
, you should see / write something that looks like this.
Note: This is only required if you are running a validator node. If you are running a non-validator node, you can skip this section.
# ...
###############################################################################
### Oracle ###
###############################################################################
[oracle]
# Enabled indicates whether the oracle is enabled.
enabled = "true"
# Oracle Address is the URL of the out of process oracle sidecar. This is used to
# connect to the oracle sidecar when the application boots up. Note that the address
# can be modified at any point, but will only take effect after the application is
# restarted. This can be the address of an oracle container running on the same
# machine or a remote machine.
oracle_address = "0.0.0.0:8080"
# Client Timeout is the time that the client is willing to wait for responses from
# the oracle before timing out.
client_timeout = "1s"
# MetricsEnabled determines whether oracle metrics are enabled. Specifically
# this enables instrumentation of the oracle client and the interaction between
# the oracle and the app.
metrics_enabled = "true"
# PrometheusServerAddress is the address of the prometheus server that metrics will be
# exposed to.
prometheus_server_address = "0.0.0.0:8001"
# ...
The oracle.json
file is the configuration file that is consumed by the oracle sidecar. Note that in most cases, this should NOT be custom-made by validators - unless specified otherwise. A predefined oracle sidecar configuration should be provided by the chain that the oracle supports. This file contains:
- The desired data providers to be utilized i.e. Coinbase, Binance, etc.
- Metrics instrumentation.
- API & WebSocket configurations for each provider.
In some cases, validators must configure the market map provider into their oracle.json
. The market map provider is a special provider that provides the desired markets that the oracle should fetch prices for. This is particularly useful for chains that have a large number of markets that are constantly changing. The market map provider allows the side-car to be updated with new markets without needing to restart the side-car. Please check the relevant chain's documentation & channels to determine if you need to configure the market map provider.
The main oracle configuration object is located in oracle.go. This is utilized to set up the oracle and to configure the providers that the oracle will use. The object is defined as follows:
type OracleConfig struct {
UpdateInterval time.Duration `json:"updateInterval"`
MaxPriceAge time.Duration `json:"maxPriceAge"`
Providers []ProviderConfig `json:"providers"`
Production bool `json:"production"`
Metrics MetricsConfig `json:"metrics"`
Host string `json:"host"`
Port string `json:"port"`
}
This field is utilized to set the interval at which the side-car will aggregate price feeds from price providers.
This field is utilized to set the maximum age of a price that the oracle will consider when aggregating prices. If a price is older than this value, the side-car will not consider it when aggregating prices.
This field is utilized to set the list of providers that the oracle will fetch prices from. A given provider's configuration is composed of:
- An API configuration that defines the various API configurations that the oracle will use to fetch prices from the provider.
- A WebSocket configuration that defines the various WebSocket configurations that the oracle will use to fetch prices from the provider.
- A type that defines the type of provider - specifically this is currently either a price or market map provider. Price providers supply price feeds for a given set of markets, while a market map provider supplies the desired markets that need to be fetched. Market map providers allow the side-car to be updated with new markets without needing to restart the side-car.
Note: Typically only one of either the API or WebSocket config is required. However, some providers may require both. Read the provider's documentation to learn more about how to configure the provider. Each provider provides sensible defaults for the API and WebSocket configurations that should be used for most cases. This should be modified with caution.
type ProviderConfig struct {
Name string `json:"name"`
API APIConfig `json:"api"`
WebSocket WebSocketConfig `json:"webSocket"`
Type string `json:"type"`
}
This field is utilized to set the name of the provider. This name is used to identify the provider in the oracle's logs as well as in the oracle's metrics.
This field is utilized to set the various API configurations that are specific to the provider.
type APIConfig struct {
Enabled bool `json:"enabled"`
Timeout time.Duration `json:"timeout"`
Interval time.Duration `json:"interval"`
ReconnectTimeout time.Duration `json:"reconnectTimeout"`
MaxQueries int `json:"maxQueries"`
Atomic bool `json:"atomic"`
URL string `json:"url"`
Name string `json:"name"`
}
This field is utilized to set whether the provider is API based. If the provider is not API based, this field should be set to false
.
This field is utilized to set the amount of time the provider should wait for a response from its API before timing out.
This field is utilized to set the interval at which the provider should update the prices. Note that provider's may rate limit based on this interval, so it is recommended to tune this value as necessary.
This field is utilized to set the maximum number of queries that the provider will make within the interval.
This field is utilized to set whether the provider can fulfill its queries in a single request. If the provider can fulfill its queries in a single request, this field should be set to true
. Otherwise, this field should be set to false
. In the case where all requests can be fulfilled atomically, the oracle will make a single request to the provider to fetch prices for all currency pairs once every interval.
This field is utilized to set the URL that is used to fetch data from the API.
This field is utilized to set the name of the provider. Mostly used as a sanity check to ensure the API configurations correctly correspond to the provider.
This field is utilized to set the various WebSocket configurations that are specific to the provider.
type WebSocketConfig struct {
Enabled bool `json:"enabled"`
MaxBufferSize int `json:"maxBufferSize"`
ReconnectionTimeout time.Duration `json:"reconnectionTimeout"`
WSS string `json:"wss"`
Name string `json:"name"`
ReadBufferSize int `json:"readBufferSize"`
WriteBufferSize int `json:"writeBufferSize"`
HandshakeTimeout time.Duration `json:"handshakeTimeout"`
EnableCompression bool `json:"enableCompression"`
ReadTimeout time.Duration `json:"readTimeout"`
WriteTimeout time.Duration `json:"writeTimeout"`
PingInterval time.Duration `json:"pingInterval"`
MaxReadErrorCount int `json:"maxReadErrorCount"`
MaxSubscriptionsPerConnection int `json:"maxSubscriptionsPerConnection"`
}
This field is utilized to set whether the provider is WebSocket based. If the provider is not WebSocket based, this field should be set to false
.
This field is utilized to set the maximum number of messages that the provider will buffer at any given time. If the provider receives more messages than this, it will block receiving messages until the buffer is cleared.
This field is utilized to set the timeout for the provider to attempt to reconnect to the websocket endpoint. In the case when the connection is corrupted, the provider will wait the ReconnectionTimeout
before attempting to reconnect.
This field is utilized to set the websocket endpoint for the provider.
This field is utilized to set the name of the provider. Mostly used as a sanity check to ensure the WebSocket configurations correctly correspond to the provider.
This field is utilized to set the I/O read buffer size. If a buffer size of 0 is specified, then a default buffer size is used.
This field is utilized to set the I/O write buffer size. If a buffer size of 0 is specified, then a default buffer size is used.
This field is utilized to set the duration for the handshake to complete.
This field is utilized to set whether the client should attempt to negotiate per message compression (RFC 7692). Setting this value to true does not guarantee that compression will be supported. Note that enabling compression may increase latency.
This field is utilized to set the read deadline on the underlying network connection. After a read has timed out, the websocket connection state is corrupt and all future reads will return an error. A zero value for t means reads will not time out.
This field is utilized to set the write deadline on the underlying network connection. After a write has timed out, the websocket state is corrupt and all future writes will return an error. A zero value for t means writes will not time out.
This field is utilized to set the interval to ping the server. Note that a ping interval of 0 disables pings. This is utilized to send heartbeat messages to the server to ensure that the connection is still alive.
This field is utilized to set the maximum number of read errors that the provider will tolerate before closing the connection and attempting to reconnect.
This field is utilized to set the maximum number of subscriptions that the provider will allow per connection. By default, this value is set to 0, which means that there is no limit to the number of subscriptions that can be made per connection.
This field is utilized to set whether the oracle is running in production mode. This is used to determine whether the oracle should be run in debug mode or not. This particularly helpful for logging purposes.
This field is utilized to set the metrics configurations for the oracle. To read more about the various metrics that are collected and corresponding queries, please read the Readme.
type MetricsConfig struct {
PrometheusServerAddress string `json:"prometheusServerAddress"`
Enabled bool `json:"enabled"`
}
This field is utilized to set the address of the prometheus server that the oracle will expose metrics to.
This field is utilized to set whether metrics should be enabled.
Sample configuration:
{
"updateInterval": 500000000,
"maxPriceAge": 120000000000,
"providers": [
{
"name": "binance_api",
"api": {
"enabled": true,
"timeout": 1000000000,
"interval": 400000000,
"reconnectTimeout": 2000000000,
"maxQueries": 1,
"atomic": true,
"url": "https://api.binance.com/api/v3/ticker/price?symbols=%s%s%s",
"name": "binance_api"
},
"webSocket": {
"enabled": false,
"maxBufferSize": 0,
"reconnectionTimeout": 0,
"wss": "",
"name": "",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 0,
"enableCompression": false,
"readTimeout": 0,
"writeTimeout": 0,
"pingInterval": 0,
"maxReadErrorCount": 0,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "coinbase_api",
"api": {
"enabled": true,
"timeout": 500000000,
"interval": 100000000,
"reconnectTimeout": 2000000000,
"maxQueries": 5,
"atomic": false,
"url": "https://api.coinbase.com/v2/prices/%s/spot",
"name": "coinbase_api"
},
"webSocket": {
"enabled": false,
"maxBufferSize": 0,
"reconnectionTimeout": 0,
"wss": "",
"name": "",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 0,
"enableCompression": false,
"readTimeout": 0,
"writeTimeout": 0,
"pingInterval": 0,
"maxReadErrorCount": 0,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "kraken_api",
"api": {
"enabled": true,
"timeout": 500000000,
"interval": 400000000,
"reconnectTimeout": 2000000000,
"maxQueries": 1,
"atomic": true,
"url": "https://api.kraken.com/0/public/Ticker?pair=%s",
"name": "kraken_api"
},
"webSocket": {
"enabled": false,
"maxBufferSize": 0,
"reconnectionTimeout": 0,
"wss": "",
"name": "",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 0,
"enableCompression": false,
"readTimeout": 0,
"writeTimeout": 0,
"pingInterval": 0,
"maxReadErrorCount": 0,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "bitfinex_ws",
"api": {
"enabled": false,
"timeout": 0,
"interval": 0,
"reconnectTimeout": 0,
"maxQueries": 0,
"atomic": false,
"url": "",
"name": ""
},
"webSocket": {
"enabled": true,
"maxBufferSize": 1000,
"reconnectionTimeout": 10000000000,
"wss": "wss://api-pub.bitfinex.com/ws/2",
"name": "bitfinex_ws",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 45000000000,
"enableCompression": false,
"readTimeout": 45000000000,
"writeTimeout": 45000000000,
"pingInterval": 0,
"maxReadErrorCount": 100,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "bitstamp_ws",
"api": {
"enabled": false,
"timeout": 0,
"interval": 0,
"reconnectTimeout": 0,
"maxQueries": 0,
"atomic": false,
"url": "",
"name": ""
},
"webSocket": {
"enabled": true,
"maxBufferSize": 1024,
"reconnectionTimeout": 10000000000,
"wss": "wss://ws.bitstamp.net",
"name": "bitstamp_ws",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 45000000000,
"enableCompression": false,
"readTimeout": 45000000000,
"writeTimeout": 45000000000,
"pingInterval": 10000000000,
"maxReadErrorCount": 100,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "bybit_ws",
"api": {
"enabled": false,
"timeout": 0,
"interval": 0,
"reconnectTimeout": 0,
"maxQueries": 0,
"atomic": false,
"url": "",
"name": ""
},
"webSocket": {
"enabled": true,
"maxBufferSize": 1000,
"reconnectionTimeout": 10000000000,
"wss": "wss://stream.bybit.com/v5/public/spot",
"name": "bybit_ws",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 45000000000,
"enableCompression": false,
"readTimeout": 45000000000,
"writeTimeout": 45000000000,
"pingInterval": 15000000000,
"maxReadErrorCount": 100,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "coinbase_ws",
"api": {
"enabled": false,
"timeout": 0,
"interval": 0,
"reconnectTimeout": 0,
"maxQueries": 0,
"atomic": false,
"url": "",
"name": ""
},
"webSocket": {
"enabled": true,
"maxBufferSize": 1024,
"reconnectionTimeout": 10000000000,
"wss": "wss://ws-feed.exchange.coinbase.com",
"name": "coinbase_ws",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 45000000000,
"enableCompression": false,
"readTimeout": 45000000000,
"writeTimeout": 5000000000,
"pingInterval": 0,
"maxReadErrorCount": 100,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "crypto_dot_com_ws",
"api": {
"enabled": false,
"timeout": 0,
"interval": 0,
"reconnectTimeout": 0,
"maxQueries": 0,
"atomic": false,
"url": "",
"name": ""
},
"webSocket": {
"enabled": true,
"maxBufferSize": 1024,
"reconnectionTimeout": 10000000000,
"wss": "wss://stream.crypto.com/exchange/v1/market",
"name": "crypto_dot_com_ws",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 45000000000,
"enableCompression": false,
"readTimeout": 45000000000,
"writeTimeout": 45000000000,
"pingInterval": 0,
"maxReadErrorCount": 100,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "gate_ws",
"api": {
"enabled": false,
"timeout": 0,
"interval": 0,
"reconnectTimeout": 0,
"maxQueries": 0,
"atomic": false,
"url": "",
"name": ""
},
"webSocket": {
"enabled": true,
"maxBufferSize": 1000,
"reconnectionTimeout": 10000000000,
"wss": "wss://api.gateio.ws/ws/v4/",
"name": "gate_ws",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 45000000000,
"enableCompression": false,
"readTimeout": 45000000000,
"writeTimeout": 45000000000,
"pingInterval": 0,
"maxReadErrorCount": 100,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "huobi_ws",
"api": {
"enabled": false,
"timeout": 0,
"interval": 0,
"reconnectTimeout": 0,
"maxQueries": 0,
"atomic": false,
"url": "",
"name": ""
},
"webSocket": {
"enabled": true,
"maxBufferSize": 1000,
"reconnectionTimeout": 10000000000,
"wss": "wss://api.huobi.pro/ws",
"name": "huobi_ws",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 45000000000,
"enableCompression": false,
"readTimeout": 45000000000,
"writeTimeout": 45000000000,
"pingInterval": 0,
"maxReadErrorCount": 100,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "kucoin_ws",
"api": {
"enabled": false,
"timeout": 5000000000,
"interval": 60000000000,
"reconnectTimeout": 0,
"maxQueries": 1,
"atomic": false,
"url": "https://api.kucoin.com",
"name": "kucoin_ws"
},
"webSocket": {
"enabled": true,
"maxBufferSize": 1024,
"reconnectionTimeout": 10000000000,
"wss": "wss://ws-api-spot.kucoin.com/",
"name": "kucoin_ws",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 45000000000,
"enableCompression": false,
"readTimeout": 45000000000,
"writeTimeout": 45000000000,
"pingInterval": 10000000000,
"maxReadErrorCount": 100,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "mexc_ws",
"api": {
"enabled": false,
"timeout": 0,
"interval": 0,
"reconnectTimeout": 0,
"maxQueries": 0,
"atomic": false,
"url": "",
"name": ""
},
"webSocket": {
"enabled": true,
"maxBufferSize": 1000,
"reconnectionTimeout": 10000000000,
"wss": "wss://wbs.mexc.com/ws",
"name": "mexc_ws",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 45000000000,
"enableCompression": false,
"readTimeout": 45000000000,
"writeTimeout": 45000000000,
"pingInterval": 20000000000,
"maxReadErrorCount": 100,
"maxSubscriptionsPerConnection": 20
},
"type": "price_provider"
},
{
"name": "okx_ws",
"api": {
"enabled": false,
"timeout": 0,
"interval": 0,
"reconnectTimeout": 0,
"maxQueries": 0,
"atomic": false,
"url": "",
"name": ""
},
"webSocket": {
"enabled": true,
"maxBufferSize": 1000,
"reconnectionTimeout": 10000000000,
"wss": "wss://ws.okx.com:8443/ws/v5/public",
"name": "okx_ws",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 45000000000,
"enableCompression": false,
"readTimeout": 45000000000,
"writeTimeout": 45000000000,
"pingInterval": 0,
"maxReadErrorCount": 100,
"maxSubscriptionsPerConnection": 0
},
"type": "price_provider"
},
{
"name": "dydx_api",
"api": {
"enabled": true,
"timeout": 20000000000,
"interval": 10000000000,
"reconnectTimeout": 2000000000,
"maxQueries": 1,
"atomic": true,
"url": "localhost:1317",
"name": "dydx_api"
},
"webSocket": {
"enabled": false,
"maxBufferSize": 0,
"reconnectionTimeout": 0,
"wss": "",
"name": "",
"readBufferSize": 0,
"writeBufferSize": 0,
"handshakeTimeout": 0,
"enableCompression": false,
"readTimeout": 0,
"writeTimeout": 0,
"pingInterval": 0,
"maxReadErrorCount": 0,
"maxSubscriptionsPerConnection": 0
},
"type": "market_map_provider"
}
],
"production": true,
"metrics": {
"prometheusServerAddress": "0.0.0.0:8002",
"enabled": true
},
"host": "0.0.0.0",
"port": "8080"
}
This readme has provided an overview of how to configure the oracle side-car and application. It has also provided a brief overview of the oracle side-car configuration and the application configuration. To see an example of a properly configured oracle sidecar, please visit the local config files - oracle.json
and market.json
.
In general, it is best to consult the chain's documentation and channels to determine the correct configurations for the oracle side-car. If you have any questions, please feel free to reach out to the Skip team on the Skip Discord.