NAV
Shell TypeScript

Introduction

Welcome to Injective Protocol's documentation!

Here you can find a comprehensive overview of our protocol, as well as tutorials, guides and general resources for developers.

If you would like to ask any questions or be a part of our community, please join our Telegram Group.

Architecture

Injective Protocol is comprised of five principal components:

  1. Injective Chain
  2. Injective Exchange Client
  3. Injective API Provider
  4. Injective EVM RPC provider
  5. Injective Bridge Contracts on Ethereum

Injective Chain

The Injective Chain is the core backbone for Injective's layer-2 derivatives platform and hosts a fully decentralized orderbook, trade execution coordinator, EVM execution environment, and bi-directional token bridge to Ethereum.

Layer-2 EVM Execution Environment

The Injective Chain supports generalized smart contract execution through a modular implementation of the Ethereum Virtual Machine (EVM) on top of the Cosmos-SDK (based on Ethermint). By implementing the EVM on top of Tendermint, users enjoy a scalable and interoperable implementation of Ethereum built on Proof-of-Stake with 1-block finality.

Developers can enjoy an identical experience for creating dApps on the Injective EVM, with additional benefits including native support for transaction fee delegation and an increased contract bytecode size limit of 100KB.

Upon genesis, the following contracts are deployed on the Injective EVM:

Injective DEX Contracts

The Injective DEX Protocol is a decentralized exchange protocol supporting peer-to-peer spot and derivatives trading. The DEX protocol is implemented through smart contracts (written in Solidity) and is deployed on the Injective Layer-2 EVM execution environment. The 0x V3 Exchange Protocol is used for spot markets and the bespoke Injective Derivatives Protocol is used for derivatives markets.

Injective Derivatives Contracts

The Injective Derivatives Protocol enables traders to create, enter into, and execute decentralized perpetual swap contracts and CFDs on any arbitrary market. Comprehensive details can be found here.

0x V3 Exchange Contracts

We leverage the 0x V3 Exchange Contracts for peer-to-peer spot exchange.

Injective Coordinator Contract

The Injective Coordinator Contract follows the 0x Coordinator specification for both spot 0x transactions. The principal purpose of the coordinator is to serve as a liquidity solution enabling more competitive pricing by preventing front-running and allowing for much lower latency trading. However, unlike traditional implementations which only require one signature from a centralized coordinator, Injective's decentralized coordinator enforces transactions to have a minimum threshold of coordinator signatures in order for a transaction to be approved. These required signatures are provided through the application logic built into the consensus of the Injective Chain. Each coordinator is bonded through INJ stake and can be slashed for improperly approving transactions.

Staking Contract
The Injective Staking Contract maintains a compressed representation of the Injective Chain's validator set and is used to govern the Injective Derivatives Protocol and to process cross-chain ERC-20 token deposits/withdrawals.

Injective EVM Bridge Contracts
The Injective Bridge Contracts encompass a suite of smart contracts managing the two-way peg between Ethereum and the Injective Chain. More details can be found here.

Decentralized Orderbook

Injective's Decentralized Orderbook is a fully decentralized 0x-based orderbook enabling sidechain order relay with on-chain settlement - a decentralized implementation of the traditionally centralized off-chain order relay used by nearly all central limit order book decentralized exchanges.

Nodes of the Injective Chain host a decentralized, censorship-resistant orderbook which stores and relays orders for both spot and derivatives trading.

Trade Execution Coordinator

The Injective Trade Execution Coordinator (TEC) is a decentralized coordinator implementation based off the 0x Coordinator specification. The Injective TEC safeguards trades from front-running using Verifiable Delay Functions and enables lower-latency trading through soft-cancellations.

Injective Exchange Client

Injective provides a powerful, full-fledged decentralized exchange open-source front-end implementation allowing anyone to easily participate in our decentralized exchange protocol in a fully permissionless manner.

The Injective Client is a comprehensive yet friendly graphical user interface catered towards the general public as well as more advanced users. Relayers can host the client on a server to allow users to interact with the protocol. Individuals can also run the client from their locally to directly interact with the protocol. The exchange client interface will also be deployed on IPFS.

Injective API Provider

Injective’s model rewards relayers in the Injective network for sourcing liquidity. By doing so, exchange providers are incentivized to better serve users, competing amongst each other to provide better user experience, thus broadening access to DeFi for users all around the world.

Injective API nodes have two purposes: 1) providing transaction relay services and 2) serving as a data layer for the protocol.

Transaction Relay Service
Although users can directly interact with the Injective Chain by broadcasting a compatible Tendermint transaction encoding a compatible message type, doing so would be cumbersome for most users. To this end, API nodes provide users a simple HTTP, gRPC and Websocket API to interact with the protocol. The API nodes then formulate the appropriate transactions and relay them to the Injective Chain.

Data Layer
Injective Exchange API nodes also serve as a data layer for external clients. Injective provides a data and analytics API which is out-of-the-box compatible with Injective's sample frontend interface.

The Injective API supports the Injective Derivatives and Spot Exchange APIs for the Injective Client, the 0x Standard Coordinator API, the Injective Derivatives Protocol Graph Node GraphQL API and other API services required by the Injective Exchange Client. A partial specification for this API can be found at api.injective.dev.

Injective EVM RPC provider

Nodes also provide the full Ethereum JSON-RPC API which connects to the Injective EVM.

Injective ⮂ Ethereum Bridge

Users can transfer ERC-20 tokens from Ethereum through the bi-directional Injective Token Bridge, which serves as a two-way Ethereum peg-zone for ERC-20 tokens to be transferred to the Injective Chain EVM. The peg-zone is based off Peggy and is secured by the Proof-of-Stake security of the Injective Chain. ERC-20 tokens can be transferred to and from Ethereum to the Injective Chain through the Injective Bridge . The process to do so is inspired by the standard flow as defined by Peggy.

Ethereum → Injective Chain

The following is the underlying process involved in transferring ERC-20 tokens from Ethereum to the Injective Chain. Validators witness the locking of ERC20 assets and sign a data package containing information about the lock, which is then relayed to the Injective chain and witnessed by the EthBridge module. Once a quorum of 2/3 of the validators by signing power have confirmed that the transaction's information is valid, the funds are released by the Oracle module and are transferred to the intended recipient's Cosmos address if a Cosmos address was specified in the lock event. The user can also choose to transfer the ERC-20 token to the corresponding child ERC-20 token on the Injective EVM chain.

This process is abstracted away from the end user, who simply needs to transfer their ERC-20 to the Injective Peg Zone contract and specify whether they desire to have their funds sent to their Cosmos address (represented in the Cosmos bank module) or on the child ERC-20 contract on the Injective EVM.

images/inj-peg-c9229a81.png

On a high level, the transfer flow for transferring a token to the is as follows:

  1. User sends the ERC-20 to the Injective Bridge Contract, emitting a LogLock event.
  2. An Injective relayer listening to the event creates and signs a Tendermint transaction encoding this information which is then broadcasted to the Injective Chain.
  3. The nodes of the Injective Chain verify the validity of the transaction.
  4. New tokens representing the ERC-20 are minted in the bank module.

Thereafter, the ERC-20 can be used on Injective Chain's EVM as well as in the Cosmos-SDK based application logic of the Injective Chain. In the future, the Injective chain will support cross-chain transfers using Cosmos IBC.

Injective Chain → Ethereum

The following is the underlying process involved in transferring ETH/ERC-20 tokens from the Injective Chain to Ethereum.

Validators witness transactions on the Injective Chain and sign a data package containing the information. The user's ETH/ERC-20 on the Injective Chain is burned, resulting in unlocking the ERC-20 on Ethereum. The data package containing the validator's signature is then relayed to the Injective Bridge contracts deployed on the Ethereum blockchain. Once enough other validators have confirmed that the transaction's information is valid, the funds are released/minted to the intended recipient's Ethereum address.

Token Economics

Injective transforms exchange into a fully decentralized public utility that’s owned and controlled by the community of INJ holders, as opposed to a single centralized entity. This community-driven philosophy underpins our entire protocol, where value is exclusively captured by the users and Injective token holders.

Injective Protocol's native token (INJ) is used for the following purposes:

1. Proof of Stake Security

To ensure the security of our sidechain, we inflate the supply of our token to incentivize nodes to stake INJ and participate in the Injective network.

The tentative initial supply of INJ will be set to 100,000,000 tokens and shall increase for a finite amount of time through block rewards.

The target INJ inflation will tentatively be 7% at genesis and decrease over time to 2%. Over time, the total supply of INJ may be lower than the initial supply due to our deflationary mechanism detailed in the Exchange Fee Value Accrual section above.

2. Governance

The INJ token can be used to govern various components of our sidechain including the futures protocol, exchange parameters and protocol upgrades.

The governance process for the Injective Chain core node is divided in a few steps that are outlined below:

For any governance decision, INJ holders can initiate a referendum by submitting a signed on-chain proposal. Once at least 1% of the total supply of INJ token holders support the proposal, a 14-day referendum period will commence. During this time, INJ holders do not need to lock their tokens and can simply submit their vote on-chain. Their voting power, which is proportional to their token balance, will be calculated at the end of the 14-day period. After the voting window elapses, the proposal will only be accepted if a majority of voting power approve the proposal and if more than a predetermined percentage of the total token supply has participated in the election.

3. Market Maker Incentives

Make orders will receive a net positive fee rebate to incentivize liquidity. Distribution will happen periodically based on snapshots.

Our decentralized exchange will initially implement a global minimum exchange fee of $$r_m = 0.1 \%$$ for makers and $$r_t = 0.2\%$$ for takers.

As one mechanism of bootstrapping liquidity in the two-sided market of our decentralized exchange, we incentivize market makers to provide liquidity through exchange fee rebates in our INJ token. Traders who place make orders that are filled are proportionally rewarded (by $$\alpha_{filled}$$) with a filled make order rebate reward equal to:

$$\textbf{Filled Make Order Rebate} = \alpha_{filled} (\delta \cdot r_m)$$

Where $$\delta$$ is the ratio between the market value of the reward and the exchange fee. The distribution of the INJ token will be done off-chain and a minimum threshold of the aggregate make order notional value for each address will be in place to qualify for the market maker incentive. At genesis, $$\delta$$ will be greater than 1 and slowly decrease to 0.5 in linear time over 4 years.

4. Relayer Incentives

Nodes and validators of the Injective sidechain also have the capability to act as relayers who can cater to traders in their desired ways (e.g. a relayer can provide an improved interface/API catering to a specialized group of traders). As an incentive mechanism for relayers to provide the best experience for traders, we reward relayers who originate orders into the shared orderbook. The node that first discovers a make order (by relaying to the shared orderbook) will receive a ratio of the exchange fee of each make order discovered by them equal to the following:

$$\textbf{Make Order Relayer Reward} = \beta_{make} (\delta \cdot r_m)$$

Similarly, the node that first relays a take order will receive a ratio of the exchange fee of each make order discovered by them equal to the following:

$$\textbf{Take Order Relayer Reward} = \beta_{take} (\delta \cdot r_t)$$

At genesis, $$\delta$$ will be set at 40% and subject to change by governance.

5. Exchange Fee Value Accrual

After the relayer reward distribution, the rest of the exchange fee will undergo an on-chain buy-back-and-burn event to accrue value for INJ. Since it's not necessary for users to utilize INJ for the exchange fee, exchange fees collected from all trading pairs are aggregated over a set period of time and sold in batch to market makers who bid with INJ tokens. To achieve this, we utilize an absolute auction mechanism that repeats every month. A derivatives protocol contract will continuously aggregate all exchange fees collected during the monthly period into a pool and then conduct a week-long blind auction at the end of the period. The smart contract will simply verify and select the highest bid to conduct the exchange. All proceeds from the auction will be burnt.

6. Collateral Backing for Derivatives

INJ will be utilized as an alternative to stablecoins as margin and collateral for Injective's derivatives markets. In some futures markets, INJ can also be used as collateral backing or insurance pool staking where stakers can earn interest on their locked tokens.

7. Exchange participation incentives

We plan to distribute a fixed number of INJ tokens daily over a predetermined period of time. Each day, a snapshot of all account profit-and-loss in selected markets will be taken. An aggregate profit-and-loss for the address will be calculated and used as the weight for token distribution. In practice, an avid Injective participant with high notional profit will receive more INJ than another participant with lower notional profit, even if he or she has a higher profit-and-loss percentage.

Derivatives Specification

Overview

This protocol enables traders to create, enter into, and execute decentralized perpetual swap contracts on any arbitrary market.

Our design adopts an account balance model where all positions are publicly recorded with their respective accounts. Architecturally, the core logic is implemented on on-chain smart contracts while the order matching is done through on the Injective Chain (in an off-chain relay on-chain settlement fashion).

In our system, users have subaccounts which manage their positions in one or more futures markets. The first subaccount is treated as default account. Each futures market specifies the bilateral futures contract parameters and terms, including most notably the margin ratio and oracle. Buyers and sellers of contracts in a futures market coordinate off-chain and then enter into contracts through an on-chain transaction. At all times, the payout (NPV) of long positions are balanced with corresponding short positions. The positions held by an account are subject to liquidation when the NAV of the account becomes negative.

While a market is live, market-wide actions may affect all positions in the market such as dynamic funding rate adjustment (to balance market long/shorts) as well as clawbacks in rare scenarios.

Key Terms

Perpetual Swap

A derivative providing exposure to the value of an underlying asset which mimics a margin-based spot market and has no expiry/settlement.

CFD

Contract for difference which is a derivative stipulating that the sellers will pay the buyers the difference between the current value of an asset and its value at the contract's closing time.

Address

A secp256k1 based address. It's important to differentiate an address from an account in the futures protocol.

Subaccount

An address can own one or more subaccounts which hold positions. Isolated margin positions are each held by their own subaccount while cross margined positions are held by the same account.

Deposits

The value of an account's aggregate deposits denominated in a specified base currency (e.g. USDT).

Market

A market is defined by its supported base currency, oracle, and minimum margin ratio / margin requirement. The market may also support a unique funding rates calculation, which is a distinctive feature of a perpetual swap futures market.

Direction

Long or Short.

Index Price

The reference spot index price (indexPrice) of the underlying asset that the derivative contract derives from. This value is obtained from an oracle.

Contract Price

The value of the futures contract the position entered/created (contractPrice). This is purely market driven and is set by individuals, independent of the oracle price.

Quantity

The quantity of contracts (quantity). Must be a whole number.

Position

Executing a contract creates two opposing positions (one long and one short) which reflects the ownership conditions of some quantity of contracts.

A cross margined subaccount can hold multiple positions while an isolated margined subaccount holds one position.

Margin

Margin refers to the amount of base currency that a trader posts as collateral for a given position.

Contract

A contract refers to the single unit of subaccount for a position in a given direction in a given market. There are two sides of a contract (long or short) that one can enter into.

Order

A cryptographically signed message expressing an unilateral agreement to enter into a position under certain specified terms (i.e. make order or take order).

Funding

Funding refers to the periodic payments exchanged between the traders that are long or short of a perpetual contract at the end of every funding epoch (e.g. every 8 hours). When the funding rate is positive, longs pay shorts. When negative, shorts pay longs.

Funding Rate

The funding rate value determines the funding fee that positions pay and is based on the difference between the price of the contract in the perpetual swap markets and spot markets.

Funding Fee

The funding fee f_i refers to the funding payment that is made for a single contract in the market for a given epoch i. When a position is created, the position records the current epoch which is noted as the entry epoch.

Cumulative funding F refers to the cumulative funding for the contract since position entry. F_entry = f_entry + ... + f_current

NPV

Net Present Value of a single contract. For long and short perpetual contracts, the NPV calculation is:

The Net Asset Value for an account. Equals the sum of the net position value over all of the account's positions.

NAV = sum(quantity * NPV + margin - indexPrice * maintenanceMarginRatio)

Liquidation

When an account's NAV becomes negative, all of its positions are subject to liquidation, i.e. the forced closure of the position due to a maintenance margin requirement being breached.

Vaporization

A subaccount is subject to vaporization when the subaccount is no longer possible to be liquidated with a net nonnegative payout which occurs when NPV + margin < 0.

Maintenance Margin Requirement

The maintenance margin requirement refers to the minimum amount of margin that a position must maintain after being established. If this requirement is breached, the position is subject to liquidation.

Throughout the lifetime of a position, each contract must satisfy the following margin requirement:

margin / quantity >= indexPrice * maintenanceMarginRatio - NPV

Initial Margin Requirement

When a position is first created, the amount of collateral supplied as margin must satisfy the initial margin requirement. This margin requirement is stricter than the maintenance margin requirement and exists in order to reduce the risk of immediate liquidation.

initialMarginRatio = maintenanceMarginRatio + initialMarginRatioFactor

Upon position creation, each contract must satisfy the following margin requirement:

margin / quantity >= max(contractPrice * initialMarginRatio, indexPrice * initialMarginRatio - NPV

Liquidation Price

The liquidation price is the price at which a position can be liquidated and can be derived from the maintenance margin requirement formula.

For longs:

contractPrice = indexPrice - indexPrice * maintenanceMarginRatio + margin / quantity - F_entry

For shorts:

contractPrice = indexPrice + indexPrice * maintenanceMarginRatio - margin / quantity - F_entry

Clawback

A clawback event occurs when a threshold number of accounts cannot be liquidated with a net-zero final payout. In practice, this happens when a chain reaction of unidirectional liquidation cannot be liquidated with the position margin due to volatility or lack of liquidity.

Leverage

Although leverage is not explicitly defined in our protocol, the amount of margin a trader uses to collateralize a position is a function of leverage according to the following formula:

margin = quantity * max(contractPrice / leverage, indexPrice / leverage - NPV)

For new positions, the funding fee component of the NPV formula can be removed since the position has not been created yet, resulting in the following NPV calculations:

Make Orders

In the Injective Perpetuals Protocol, there are two main types of orders: maker orders and taker orders. Maker orders are stored on Injective's decentralized orderbook on the Injective Chain while Take orders are immediately executed against make orders on the Injective Perpetuals Contract.

Once a maker order is executed to create a position, the maker can also create stop loss and take profit orders.

Order Message Format

How to create order within SDK

const limitOrder = await sdkClient.futures.buildLimitOrder({
  orderType: DerivativeOrderType.Long,
  marketId: market.id,
  makerAddress: fromAddress,
  contractPrice: new BigNumber(fromWei("1.7")),
  leverage: 20,
  quantity: 10,
  // subaccountNonce: 0 (0 = default)
});

const stopLimitProfitDirectionOrder = await sdkClient.futures.buildStopLimitOrder({
  orderType: DerivativeOrderType.Long,
  isProfitDirection: true,
  marketId: market.id,
  makerAddress: fromAddress,
  contractPrice: new BigNumber(fromWei("1.9")),
  leverage: 20,
  quantity: 10,
  triggerPrice: 1.8,
});

const stopLimitLossDirectionOrder = await sdkClient.futures.buildStopLimitOrder({
  orderType: DerivativeOrderType.Long,
  isProfitDirection: false,
  marketId: market.id,
  makerAddress: fromAddress,
  contractPrice: new BigNumber(fromWei("1.5")),
  leverage: 20,
  quantity: 10,
  triggerPrice: 1.6,
});

const stopLossOrder = await sdkClient.futures.buildStopLossOrder({
  orderType: DerivativeOrderType.Long,
  marketId: market.id,
  makerAddress: fromAddress,
  leverage: 20,
  quantity: 10,
  triggerPrice: 1.4,
});

const takeProfitOrder = await sdkClient.futures.buildStopLossOrder({
  orderType: DerivativeOrderType.Long,
  marketId: market.id,
  makerAddress: fromAddress,
  leverage: 20,
  quantity: 10,
  triggerPrice: 2.1,
});
Examples of different orders

Limit Order Long
- makerAssetAmount (contractPrice): 1.7
- takerAssetAmount (quantity): 10
- makerFee (margin): 50
- takerFee (subaccount nonce): 0
- makerAssetData : 0xasd12f... (market id)
- takerAssetData: 0x00000...
- makerFeeAssetData (orderType): 0
- takerFeeAssetData (triggerPrice): 0

Stop Limit Order Long Profit Direction
- makerAssetAmount (contractPrice): 1.9
- takerAssetAmount (quantity): 10
- makerFee (margin): 50
- takerFee (subaccount nonce): 0
- makerAssetData : 0xasd12f... (market id)
- takerAssetData: 0x00000...
- makerFeeAssetData (orderType): 1
- takerFeeAssetData (triggerPrice): 1.8

Stop Limit Order Long Loss Direction
- makerAssetAmount (contractPrice): 1.5
- takerAssetAmount (quantity): 10
- makerFee (margin): 50
- takerFee (subaccount nonce): 0
- makerAssetData : 0xasd12f... (market id)
- takerAssetData: 0x00000...
- makerFeeAssetData (orderType): 2
- takerFeeAssetData (triggerPrice): 1.6

Stop Loss Order Long
- makerAssetAmount (contractPrice): 0
- takerAssetAmount (quantity): 10
- makerFee (margin): 0
- takerFee (subaccount nonce): 0
- makerAssetData : 0xasd12f... (market id)
- takerAssetData: 0x00000...
- makerFeeAssetData (orderType): 3
- takerFeeAssetData (triggerPrice): 1.4

Take Profit Order Long
- makerAssetAmount (contractPrice): 0
- takerAssetAmount (quantity): 10
- makerFee (margin): 0
- takerFee (subaccount nonce): 0
- makerAssetData : 0xasd12f... (market id)
- takerAssetData: 0x00000...
- makerFeeAssetData (orderType): 4
- takerFeeAssetData (triggerPrice): 2.1

The Injective Perpetuals Protocol leverages the 0x Order Message format for the external interface to represent a make order for a derivative position, i.e. a cryptographically signed message expressing an agreement to enter into a derivative position under specified parameters.

A make order message consists of the following parameters:

0x Parameter Name Type Description
makerAddress makerAddress address Address that created the order.
takerAddress - address Empty.
feeRecipientAddress feeRecipientAddress address Address that will receive fees when order is filled.
senderAddress - address Empty.
makerAssetAmount contractPrice uint256 The contract price, i.e. the price of one contract denominated in base currency. Set to 0 for Stop Loss and Take Profit orders.
takerAssetAmount quantity uint256 The quantity of contracts the maker seeks to obtain.
makerFee margin uint256 The amount of margin denoted in base currency the maker would like to post/risk for the order. Set to 0 for Stop Loss and Take Profit orders.
takerFee subaccountNonce uint256 The desired account nonce to use for cross-margining. If set to 0, the default subaccount is used.
expirationTimeSeconds expirationTimeSeconds uint256 Timestamp in seconds at which order expires.
salt salt uint256 Arbitrary number to facilitate uniqueness of the order's hash.
makerAssetData marketIdLong bytes The first 32 bytes contain the marketID of the market for the position if the order is LONG, empty otherwise. Right padded with 0's to be 36 bytes
takerAssetData marketIdShort bytes The first 32 bytes contain the marketID of the market for the position if the order is SHORT, empty otherwise. Right padded with 0's to be 36 bytes
makerFeeAssetData orderType bytes The bytes-encoded order type, see below for available order types.
takerFeeAssetData triggerPrice bytes The bytes-encoded trigger price for stop limit orders, stop loss orders and take profit orders. Empty for regular limit orders.

In a given perpetual market specified by marketID, an order encodes the willingness to purchase quantity contracts in a given direction (long or short) at a specified contract price contractPrice using a specified amount of margin of base currency as collateral.

Order Types

There are 5 different types of orders noted in the makerFeeAssetData field.

  1. Limit Order: Regular Order to create a position with given quantity and contractPrice. (makerFeeAssetData = 0)
  2. Stop Limit Order Profit Direction: Limit order with given quantity and contractPrice that becomes only valid after the indexPrice has moved towards profit and is now at least the trigger price, i.e., for a Long indexPrice ≥ triggerPrice and for a Short indexPrice ≤ triggerPrice. (makerFeeAssetData = 1)
  3. Stop Limit Order Loss Direction: Limit order with given quantity and contractPrice that becomes only valid after the indexPrice has moved towards loss and is now at least the trigger price, i.e., for a Long indexPrice ≤ triggerPrice and for a Short indexPrice ≥ triggerPrice. (makerFeeAssetData = 2)
  4. Stop Loss Order: Order to close an existing position once the trigger price is reached towards the loss direction. (makerFeeAssetData = 3)
  5. Take Profit Order: Order to close an existing position once the trigger price is reached towards the profit direction. (makerFeeAssetData = 4)

Isolated and Cross Margin

In the derivatives space, margin refers to the amount needed to enter into a leveraged position. Initial and Maintenance Margin refer to the minimum initial amount needed to enter a position and the minimum amount needed to keep that position from getting liquidated. As various users have varying trading strategies, Injecive has employed two different methods of margining:

The Injective Perpetuals Protocol currently only supports cross-margining for positions in the same market by position netting.

To specify a cross-margined order, the order maker should specify the account he desires to use for cross-margining with the account nonce in the takerFee parameter which uniquely determines his subAccountID.

Stop Limit Order

A Stop Limit Order is an order that cannot be executed until the market's index price reaches a certain Trigger Price as specified by the takerFeeAssetData.

Traders use this type of order for two main strategies:

  1. As a risk-management tool to limit losses on existing positions (a Stop Loss Limit Order), and
  2. As an automatic tool to enter the market at a desired entry point without manually waiting for the market to place the order.

For a long stop limit profit direction order, the order will only be able to be filled if the index price is greater than or equal to the trigger price.

For a short stop limit profit direction order, the order will only be able to be filled if the index price is less than or equal to the trigger price.

Stop Limit Order Example

Quantity = 50 contracts
Contract Price = 9
Trigger Price = 10
Direction = Long
IsProfitDirection = True

In this example, the trader has selected a Stop Limit Long Order with a contract price of 9 and a trigger price of 10. This order will only be fillable when the index price exceeds 10. If the trader wants to increase the chances of his order being executed, he should set the his contract price higher (e.g. to 10.5).

Stop Loss Limit Order

If the quantity of the stop loss order is greater than the quantity of contracts in the position (e.g. after partial position closure), the maximum fillable quantity of the stop loss limit order will be the total number of contracts of the position.

Note: Traders must have an active position to create a stop loss limit order. However, an active position is not needed for a pure stop limit order.

Take Profit Limit Order

To be a valid take profit order, the position referenced by positionID must be owned by the maker, have the same marketID, and have the opposite direction as the position.

Note: the takerFeeAssetData must be empty.

Transaction Fees

The business model for traditional derivatives exchanges (e.g. BitMEX, Binance, FTX, etc.) is to take a transaction fee from the each trade. However, because Injective Protocol is designed as a fully decentralized network, we do not follow this approach.

Instead, transaction fees paid in Injective Protocol are used for two purposes.

  1. As compensation for relayers for driving liquidity to the protocol
  2. As auction collateral for a "buyback and burn" process. In this token economic model, transaction fees collected over a fixed period of time (e.g. 2 weeks) are aggregated and then auctioned off to the public for the INJ token. The INJ received from this auction is then permanently burned.

There are two types of transaction fees that should be introduced: maker fees (for limit or "maker" orders) and taker fees (for market or "taker" orders). For both maker and taker fees, transaction fees are to be extracted from the notional value of the trade.

Maker Fees

Maker fees are fees that makers of limit orders pay. These are the fees paid by the order makers (found in the makerAddress parameter of each order). Limit orders (i.e. the orders from which maker fees apply) are leftOrders and rightOrder in the multiMatchOrders function, orders in the marketOrders function, order in the fillOrder function, order in the closePosition function, and orders in the closePositionWithOrders function.

Notional Value of Maker Orders

Recall that within a given perpetual market, an order encodes the willingness to purchase up to quantity contracts in a given direction (long or short) at a specified contractPrice using a specified amount of margin of base currency as collateral.

The notional value of a single contract is simply the contractPrice (or $P_{contract}$). Hence, the notional value of $n$ contracts is simply n * contractPrice. Note that orders can be partially filled so n must be less than or equal to quantity.

This the calculation for notional value that you will need to use forleftOrders and rightOrder* in multiMatchOrders, orders in the marketOrders function, order in the fillOrder function, order in the closePosition function, and orders in the closePositionWithOrders function.

Taker Fees

Taker fees are the fees paid by the msg.sender (the taker) in the fillOrder and marketOrders calls.

Notional Value for Taker Orders

The notional value calculation in fillOrder for the taker is simply notional = quantity * contractPrice.

The notional value calculation in marketOrders is the sum of the notional values for the quantity of contracts taken in each of the individual orders i.e. notional = quantity_1 * contractPrice_1 + quantity_2 * contractPrice_2 + ... + quantity_n * contractPrice_n. Note that $\sum \limits_{i=1}^{n} quantity_i$ must equal quantity.

The notional value calculation in closePosition and closePositionWithOrders for the taker (i.e. the closer/msg.sender) is notional = quantity * avgContractPrice (avgContractPrice is already defined in the contract).

Maker Orders Transaction Fees

Currently, whenever n contracts of a maker order are executed, the proportional amount of margin (where margin = order.makerFee * n / order.takerAssetAmount) is transferred from the user's base currency ERC-20 balance to the perpetuals contract.

With the introduction of the maker order fee, executing n contracts of the order should result in the trader to post margin + txFee where txFee = notional * MAKER_FEE_PERCENT / 10000 where notional = n * order.makerAssetAmountand where MAKER_FEE_PERCENT refers to the digits of the maker fee percentage scaled by 10000 (i.e a 0.15% maker fee would be 15).

Taker Orders Transaction Fees

Similarly, with the introduction of the taker order fee, executing n contracts of the order should result in the trader posting margin + txFee where txFee = notional * TAKER_FEE_PERCENT / 10000 where TAKER_FEE_PERCENT refers to the digits of the taker fee percentage scaled by 10000 (i.e a 0.25% taker fee would be 25) and where notional is described in the Notional Value for Taker Orders section.

Transaction Fee Distribution

For a given order with a transaction fee of txFee, if the feeRecipientAddress is defined, txFee * RELAYER_FEE_PROPORTION / 100 should be distributed to the feeRecipientAddress's balance. and the remainder should be distributed to the auction contract's balance. Note: "distributed to balance" in this case does not mean an ERC-20 transfer but rather incrementing the recipient's internal balance in the perpetuals contract from which the recipient can withdraw from in the future (this withdrawal functionality already exists).

Order Validation

getOrderRelevantState

getOrderRelevantState can be used to validate an order before use with a given index price.

/// @dev Fetches all order-relevant information needed to validate if the supplied order is fillable.
/// @param order The order structure
/// @param signature Signature provided by maker that proves the order's authenticity.
/// @param indexPrice The index price to use as a reference. If 0, use the market's existing index price.
/// @return The orderInfo (hash, status, and `takerAssetAmount` already filled for the given order),
/// fillableTakerAssetAmount (amount of the order's `takerAssetAmount` that is fillable given all on-chain state),
/// and isValidSignature (validity of the provided signature).
function getOrderRelevantState(
    LibOrder.Order memory order,
    bytes memory signature,
    uint256 indexPrice
)
    public
    view
    returns (
        LibOrder.OrderInfo memory orderInfo,
        uint256 fillableTakerAssetAmount,
        bool isValidSignature
    )
{

Logic

Calling getOrderRelevantState will perform the following steps:

  1. Set the hash of the order in orderInfo.orderHash
  2. Set the quantity of contracts of the order that have been filled in orderInfo.orderTakerAssetFilledAmount
  3. Set the order status of the order in orderInfo.orderStatus according to the following conditions:
    1. If the order has been cancelled, the orderStatus will be CANCELLED.
    2. If the order has been fully filled (i.e. orderInfo.orderTakerAssetFilledAmount equals order.takerAssetAmount, the orderStatus will be FULLY_FILLED.
    3. If the order has expired, the orderStatus will be EXPIRED.
    4. If the order's margin (order.makerFee) does not satisfy the initial margin requirement, the orderStatus will be INVALID_MAKER_ASSET_AMOUNT. Note: the index price used in this calculation is the inputted indexPrice.
    5. Otherwise if the order is fillable, the orderStatus will be FILLABLE and the fillableTakerAssetAmount to equal the quantity of contracts that remain fillable (i.e. order.takerAssetAmount - orderInfo.orderTakerAssetFilledAmount).
  4. Check whether or not the signature is valid and set isValidSignature to true if valid. Otherwise, set to false and set the orderStatus to INVALID.
  5. If the orderStatus from the previous steps is FILLABLE, check that the order maker has sufficient balance of baseCurrency in his subaccount deposits (his availableMargin) to fill fillableTakerAssetAmount contracts of the order.
    • If the maker does not have at least order.makerFee * fillableTakerAssetAmount / order.takerAssetAmount + fillableTakerAssetAmount * order.makerAssetAmount * makerTxFee amount of balance to the fill the order, fillableTakerAssetAmount is set to the maximum quantity of contracts fillable which equals availableMargin * order.TakerAssetAmount / order.MakerFee + order.TakerAssetAmount * makerTxFee. If this value is zero, the orderStatus will be INVALID_TAKER_ASSET_AMOUNT.

getOrderRelevantStates

getOrderRelevantStates can be used to validate multiple orders before use.

/// @dev Fetches all order-relevant information needed to validate if the supplied orders are fillable.
/// @param orders Array of order structures
/// @param signatures Array of signatures provided by makers that prove the authenticity of the orders.
/// @return The ordersInfo (array of the hash, status, and `takerAssetAmount` already filled for each order),
/// fillableTakerAssetAmounts (array of amounts for each order's `takerAssetAmount` that is fillable given all on-chain state),
/// and isValidSignature (array containing the validity of each provided signature).
/// NOTE: Expects each of the orders to be of the same marketID, otherwise may potentially return relevant states for orders of differing marketID's using a stale price
function getOrderRelevantStates(LibOrder.Order[] memory orders, bytes[] memory signatures)
  public
  view
  returns (
    LibOrder.OrderInfo[] memory ordersInfo,
    uint256[] memory fillableTakerAssetAmounts,
    bool[] memory isValidSignature
    );

Logic

Calling getOrderRelevantStates will result in sequentially calling getOrderRelevantState with the current index price obtained from the oracle.

getMakerOrderRelevantStates

getMakerOrderRelevantStates can be used to validate multiple orders from a single maker before use.

/// @dev Fetches all order-relevant information needed to validate if the supplied orders are fillable.
/// @param orders Array of order structures
/// @param signatures Array of signatures provided by makers that prove the authenticity of the orders.
/// @param makerAddress Address of maker to check.
/// @return The ordersInfo (array of the hash, status, and `takerAssetAmount` already filled for each order),
/// fillableTakerAssetAmounts (array of amounts for each order's `takerAssetAmount` that is fillable given all on-chain state),
/// isValidSignature (array containing the validity of each provided signature), and availableMargin (amount of available
/// base currency usable as margin after margin needs of the `orders` are satisfied).
/// NOTE: Expects each of the orders to be of the same marketID, otherwise may potentially return relevant states for orders of differing marketID's using a stale price
function getMakerOrderRelevantStates(
    LibOrder.Order[] memory orders,
    bytes[] memory signatures,
    address makerAddress
)
    public
    view
    returns (
        LibOrder.OrderInfo[] memory ordersInfo,
        uint256[] memory fillableTakerAssetAmounts,
        bool[] memory isValidSignature,
        uint256 availableMargin
    )

OrderInfo

struct OrderInfo {
    uint8 orderStatus;                    // Status that describes order's validity and fillability.
    bytes32 orderHash;                    // EIP712 hash of the order (see LibOrder.getOrderHash).
    uint256 orderTakerAssetFilledAmount;  // Amount of order that has already been filled.
}

Order Status

enum OrderStatus {
    INVALID,
    INVALID_MAKER_ASSET_AMOUNT,
    INVALID_TAKER_ASSET_AMOUNT,
    FILLABLE,
    EXPIRED,
    FULLY_FILLED,
    CANCELLED
}

cancelOrder

/// @dev Cancels the input order
/// @param order the order to cancel
function cancelOrder(LibOrder.Order calldata order) external;

Take Orders

Orders can be filled by calling the following methods on the InjectiveFutures contract

fillOrder

This is the most basic way to fill an order. All of the other methods call fillOrder under the hood with additional logic. This function will attempt to execute quantity contracts of the order specified by the caller. However, if the remaining fillable amount is less than the quantity specified, the remaining amount will be filled. Partial fills are allowed when filling orders.

/// @dev Fills the input order.
/// @param order The make order to be executed.
/// @param quantity Desired quantity of contracts to execute.
/// @param margin Desired amount of margin (denoted in baseCurrency) to use to fill the order.
/// @param subAccountID The subAccountID of the account for the taker to cross-margin with.
/// @param signature The signature of the order signed by maker.
/// @return fillResults
function fillOrder(
    LibOrder.Order memory order,
    uint256 quantity,
    uint256 margin,
    bytes32 subAccountID,
    bytes memory signature
) external returns (FillResults memory);

Logic

Calling fillOrder will perform the following steps:

  1. Query the oracle to obtain the most recent price and funding fee.
  2. Query the state and status of the order with getOrderRelevantState.
  3. Revert if the orderStatus is not FILLABLE.
  4. Create the Maker's Position.
    1. If the order has been used previously, execute funding payments on the existing position and then update the existing position state. Otherwise, create a new account with a corresponding new position for the maker and log a FuturesPosition event.
    2. Transfer fillResults.makerMarginUsed + fillResults.feePaid of base currency from the maker to the contract to create (or add to) the maker's position.
    3. Allocate fillResults.makerMarginUsed for the new position margin and allocate relayerFeePercentage of the fillResults.makerFeePaid to the fee recipient (if specified) and the remaining fillResults.feePaid to the insurance pool.
  5. Create the Taker's position.
    1. Create a new account with a corresponding new position for the taker and log a FuturesPosition event.
    2. Transfer margin + fillResults.takerFeePaid of base currency from the taker to the contract to create the taker's position.
    3. Allocate margin for the new position margin and allocate relayerFeePercentage of the fillResults.takerFeePaid to the fee recipient (if specified) and the remaining fillResults.takerFeePaid to the insurance pool.

fillOrKillOrder

fillOrKillOrder can be used to fill an order while guaranteeing that the specified amount will either be filled or the call will revert.

/// @dev Fills the input order. Reverts if exact quantity not filled
/// @param order The make order to be executed.
/// @param quantity Desired quantity of contracts to execute.
/// @param margin Desired amount of margin (denoted in baseCurrency) to use to fill the order.
/// @param subAccountID The subAccountID of the account for the taker to cross-margin with.
/// @param signature The signature of the order signed by maker.
/// return results The fillResults
function fillOrKillOrder(
  LibOrder.Order memory order,
  uint256 quantity,
  uint256 margin,
  bytes memory signature
) external returns (FillResults memory fillResults);

Logic

Calling fillOrKillOrder will perform the following steps:

  1. Call _fillOrder with the passed in inputs
  2. Revert if fillResults.quantityFilled does not equal the passed in quantity

batchFillOrders

batchFillOrders can be used to fill multiple orders in a single transaction.

/// @dev Executes multiple calls of fillOrder.
/// @param orders The make order to be executed.
/// @param quantities Desired quantity of contracts to execute.
/// @param margins Desired amount of margin (denoted in baseCurrency) to use to fill the order.
/// @param subAccountIDs The subAccountIDs of the accounts for the taker to cross-margin with.
/// @param signatures The signature of the order signed by maker.
/// return results The fillResults
function batchFillOrders(
  LibOrder.Order[] memory orders,
  uint256[] memory quantities,
  uint256[] memory margins,
  bytes32[] memory subAccountIDs,
  bytes[] memory signatures
) external returns (FillResults[] memory results);

Logic

Calling batchFillOrders will perform the following steps:

  1. Sequentially call fillOrder for each element of orders, passing in the order, fill amount, and signature at the same index.

batchFillOrKillOrders

batchFillOrKillOrderscan be used to fill multiple orders in a single transaction while guaranteeing that the specified amounts will either be filled or the call will revert.

/// @dev Executes multiple calls of fillOrKill orders.
/// @param orders The make order to be executed.
/// @param quantities Desired quantity of contracts to execute.
/// @param margins Desired amount of margin (denoted in baseCurrency) to use to fill the order.
/// @param subAccountIDs The subAccountIDs of the accounts for the taker to cross-margin with.
/// @param signatures The signature of the order signed by maker.
/// return results The fillResults
function batchFillOrKillOrders(
  LibOrder.Order[] memory orders,
  uint256[] memory quantities,
  uint256[] memory margins,
  bytes32[] memory subAccountIDs,
  bytes[] memory signatures
) external returns (FillResults[] memory results)

Logic

Calling batchFillOrKillOrders will perform the following steps:

  1. Sequentially call fillOrder for each element of orders, passing in the order, fill amount, and signature at the same index.
  2. Revert if any of the fillOrder calls do not fill the entire quantity passed.

batchFillOrdersSinglePosition

batchFillOrdersSinglePosition can be used to fill multiple orders in a single transaction while creating just one opposing position for the taker.

/// @dev Executes multiple calls of fillOrder but creates only one position for the taker.
/// @param orders The make order to be executed.
/// @param quantities Desired quantity of contracts to execute.
/// @param margins Desired amount of margin (denoted in baseCurrency) to use to fill the order.
/// @param subAccountID The subAccountID of the account for the taker to cross-margin with.
/// @param signatures The signature of the order signed by maker.
/// return results The fillResults
function batchFillOrdersSinglePosition(
  LibOrder.Order[] memory orders,
  uint256[] memory quantities,
  uint256[] memory margins,
  bytes32 subAccountID,
  bytes[] memory signatures
) external returns (FillResults[] memory results)

Logic

Calling batchFillOrdersSinglePosition will perform the same steps as batchFillOrders but will reuse the same taker position to create the opposing position for each order.

batchFillOrKillOrdersSinglePosition

batchFillOrKillOrdersSinglePosition can be used to

/// @dev Executes batchFillOrKillOrders but creates only one position for the taker.
/// @param orders The make order to be executed.
/// @param quantities Desired quantity of contracts to execute.
/// @param margins Desired amount of margin (denoted in baseCurrency) to use to fill the order.
/// @param subAccountID The subAccountID of the account for the taker to cross-margin with.
/// @param signatures The signature of the order signed by maker.
/// return results The fillResults
function batchFillOrKillOrdersSinglePosition(
  LibOrder.Order[] memory orders,
  uint256[] memory quantities,
  uint256[] memory margins,
  bytes32 subAccountID,
  bytes[] memory signatures
) external returns (FillResults[] memory results)

Logic

Calling batchFillOrKillOrdersSinglePosition will perform the same steps as batchFillOrdersSinglePosition but will revert if each of the quantities specified is not filled.

marketOrders

marketOrders can be used to can be used to purchase a specified quantity of contracts of a derivative by filling multiple orders while guaranteeing that no individual fill throws an error. Note that this function does not enforce that the entire quantity is filled. The input orders should be sorted from best to worst price.

/// @dev marketOrders executes the orders sequentially using `fillOrder` until the desired `quantity` is reached or until all of the margin provided is used.
/// @param orders Array of order specifications.
/// @param quantity Desired quantity of contracts to execute.
/// @param margin Desired amount of margin (denoted in baseCurrency) to use to fill the orders.
/// @param subAccountID The subAccountID of the account for the taker to cross-margin with.
/// @param signatures Proofs that orders have been signed by makers.
function marketOrders(
    LibOrder.Order[] memory orders,
    uint256 quantity,
    uint256 margin,
    bytes32 subAccountID,
    bytes[] memory signatures
) public returns (FillResults[] memory results)

Logic

Calling marketOrders will perform the following steps:

  1. Sequentially call fillOrder while decrementing the quantity and margin executed after each fill until the quantity is fully filled, the margin is exhausted, or all of the orders have been executed.

marketOrdersOrKill

marketOrders can be used to can be used to purchase a specified quantity of contracts of a derivative by filling multiple orders while guaranteeing that no individual fill throws an error. Note that this function enforces that the entire quantity is filled. The input orders should be sorted from best to worst price.

/// @dev marketOrdersOrKill performs the same steps as `marketOrders` but reverts if the inputted `quantity` of contracts are not filled
/// @param orders Array of order specifications.
/// @param quantity Desired quantity of contracts to execute.
/// @param margin Desired amount of margin (denoted in baseCurrency) to use to fill the orders.
/// @param subAccountID The subAccountID of the account for the taker to cross-margin with.
/// @param signatures Proofs that orders have been signed by makers.
function marketOrdersOrKill(
  LibOrder.Order[] memory orders,
  uint256 quantity,
  uint256 margin,
  bytes32 subAccountID,
  bytes[] memory signatures
) public returns (FillResults[] memory results);

Logic

Calling marketOrdersOrKill will perform the same steps as marketOrders but will revert if the entire quantity is not filled.

Matching Orders

Two orders of opposing directions can directly be matched if they have a negative spread.

matchOrders

matchOrders can be used to atomically fill 2 orders without requiring the taker to hold any capital. This function is optimized for creator of the rightOrder.

/// @dev Matches the input orders.
/// @param leftOrder The order to be settled.
/// @param rightOrder The order to be settled.
/// @param leftSignature The signature of the order signed by maker.
/// @param rightSignature The signature of the order signed by maker.
function matchOrders(
  LibOrder.Order memory leftOrder,
  LibOrder.Order memory rightOrder,
  bytes memory leftSignature,
  bytes memory rightSignature
) external;

Logic

Calling matchOrders will perform the following steps: TODO

multiMatchOrders

multiMatchOrders can be used to match a set of orders with another opposing order with negative spread, resulting in the creation of just one position for the creator of the rightOrder. This function is optimized for creator of the rightOrder.

/// @dev Matches the input orders and only creates one position for the `rightOrder` maker.
/// @param leftOrders The orders to be settled.
/// @param rightOrder The order to be settled.
/// @param leftSignatures The signatures of the order signed by maker.
/// @param rightSignature The signature of the order signed by maker.
function multiMatchOrders(
  LibOrder.Order[] memory leftOrders,
  LibOrder.Order memory rightOrder,
  bytes[] memory leftSignatures,
  bytes memory rightSignature
) external;

Logic

Calling multiMatchOrders will sequentially call matchOrders.

batchMatchOrders

batchMatchOrders can be used to match 2 sets of an arbitrary number of orders with each other using the same matching strategy as matchOrders.

/// @dev Matches the input orders.
/// @param leftOrders The orders to be settled.
/// @param rightOrder The order to be settled.
/// @param leftSignatures The signatures of the order signed by maker.
/// @param rightSignature The signature of the order signed by maker.
function batchMatchOrders(
  LibOrder.Order[] memory leftOrders,
  LibOrder.Order[] memory rightOrders,
  bytes[] memory leftSignatures,
  bytes[] memory rightSignatures
) external;

Logic

Calling batchMatchOrders will sequentially call matchOrders.

Positions

closePosition

/// @dev Closes the input position.
/// @param positionID The positionID of the position being closed
/// @param orders The orders to use to settle the position
/// @param quantity The quantity of contracts being used in the order
/// @param signatures The signatures of the orders signed by makers.
function closePosition(
    uint256 positionID,
    LibOrder.Order[] memory orders,
    uint256 quantity,
    bytes[] memory signatures
) external (PositionResults[] memory pResults, CloseResults memory cResults);

Logic

Calling closePosition will perform the following steps:

  1. Query the oracle to obtain the most recent price and funding fee.
  2. Execute funding payments on the existing position and then update the existing position state.
  3. Check that the existing position (referenced by positionID) is valid and can be closed.
  4. Create the Makers' Positions.
    1. For each order i:
    2. If the order has been used previously, execute funding payments on the existing position and then update the existing position state. Otherwise, create a new account with a corresponding new position with the pResults[i].quantity contracts for the maker and log a FuturesPosition event.
    3. Transfer pResults[i].marginUsed + pResults[i].fee of base currency from the maker to the contract to create (or add to) the maker's position.
    4. Allocate pResults.marginUsed for the new position margin and allocate relayerFeePercentage of the pResults.fee to the fee recipient (if specified) and the remaining pResults.fee to the insurance pool.
  5. Close the cResults.quantity quantity conracts of the existing position.
    1. Calculate the PNL per contract (contractPNL) which equals averageClosingPrice - position.contractPrice for longs and position.contractPrice - averageClosingPrice for shorts, where:
      • averageClosingPrice = (orders[i].contractPrice * results[0].quantity + orders[n-1].contractPrice * results[n-1].quantity)/(cResults.quantity)
      • cResults.quantity = results[0].quantity + ... + results[n-1].quantity . Note that cResults.quantity <= quantity.
    2. Transfer cResults.payout to the owner of the position and update the position state.
      • cResults.payout = cResults.quantity * (position.margin / position.quantity + contractPNL)
    3. Log a FuturesClose event.

closePositionOrKill

/// @dev Closes the input position and revert if the entire `quantity` of contracts cannot be closed.
/// @param positionID The positionID of the position being closed
/// @param orders The orders to use to settle the position
/// @param quantity The quantity of contracts being used in the order
/// @param signatures The signatures of the orders signed by makers.
function closePositionOrKill(
  uint256 positionID,
  LibOrder.Order[] memory orders,
  uint256 quantity,
  bytes[] memory signatures
) external returns (PositionResults[] memory pResults, CloseResults memory cResults);

Logic

Calling closePositionOrKill will perform the same steps as closePosition but will revert if the entire quantity of contracts inputted cannot be closed.

Liquidation

liquidatePositionWithOrders

/// @dev Liquidates the input position.
/// @param positionID The ID of the position to liquidate.
/// @param quantity The quantity of contracts of the position to liquidate.
/// @param orders The orders to use to liquidate the position.
/// @param signatures The signatures of the orders signed by makers.
function liquidatePositionWithOrders(
  uint256 positionID,
  uint256 quantity,
  LibOrder.Order[] memory orders,
  bytes[] memory signatures
) external returns (PositionResults[] memory pResults, LiquidateResults memory lResults){

Logic

Calling liquidatePositionWithOrders will perform the following steps:

  1. Query the oracle to obtain the most recent price and funding fee.
  2. Execute funding payments on the existing position and then update the existing position state.
  3. Check that the existing position (referenced by positionID) is valid and can be liquidated (i.e. that the maintenance margin requirement is breached.
  4. Create the Makers' Positions.

    1. For each order i:
      1. If the order has been used previously, execute funding payments on the existing position and then update the existing position state. Otherwise, create a new account with a corresponding new position with the pResults[i].quantity contracts for the maker and log a FuturesPosition event.`
      2. Transfer pResults[i].marginUsed + pResults[i].fee of base currency from the maker to the contract to create (or add to) the maker's position.
      3. Allocate pResults.marginUsed for the new position margin and allocate relayerFeePercentage of the pResults.fee to the fee recipient (if specified) and the remaining pResults.fee to the insurance pool.
    2. lResults.quantity equals the total quantity of contracts created across the orders from the previous step, i.e. lResults.quantity = pResults[0].quantity + ... + pResults[n-1].quantity . Note that lResults.quantity <= quantity.
    3. lResults.liquidationPrice equals the weighted average price, i.e. lResults.liquidationPrice = (orders[i].contractPrice * pResults[0].quantity + orders[n-1].contractPrice * pResults[n-1].quantity)/(lResults.quantity)
      • To liquidate a long position, lResults.liquidationPrice must be greater than or equal to the index price.
      • To liquidate a short position, lResults.liquidationPrice must be less than or equal to the index price.
  5. Close the lResults.quantity quantity contracts of the existing position.

    1. Calculate the trader's loss (position.margin / position.quantity * quantity) and update the state of the trader's position.
    2. Calculate the PNL per contract (contractPNL) which equals lResults.liquidationPrice - position.contractPrice for longs and position.contractPrice - averageClosingPrice for shorts.
    3. Calculate the total payout from the positioncResults.payout = cResults.quantity * (position.margin / position.quantity + contractPNL)
      1. Allocate half of the payout to the liquidator and half to the insurance fund
    4. Decrement the position's remaining margin by position.margin / position.quantity * (position.quantity - quantity)
    5. Emit a FuturesLiquidation event.

Vaporization

vaporizePosition

/// @dev Vaporizes the position.
/// @param positionID The ID of the position to vaporize.
/// @param quantity The quantity of contracts of the position to vaporize.
/// @param orders The orders to use to vaporize the position.
/// @param signatures The signatures of the orders signed by makers.
function vaporizePosition(
  uint256 positionID,
  LibOrder.Order[] memory orders,
  uint256 quantity,
  bytes[] memory signatures
) external returns (PositionResults[] memory pResults, CloseResults memory cResults)

Oracle

General concept

The oracle serves one function:

  1. Update the index price of an asset

1. Update the index price

The index price is used to calculate the NPV of positions. It will be periodically updated by the oracle.

Funding Fee

The funding fee is a critical piece to ensure convergence of market prices to the real underlying asset price. It consists of two components:

  1. Index Price: The price provided by the oracle.
  2. Futures VWAP: The VWAP (Volume Weighted Average Price), which emerges from the trades in injective futures.

VWAP

The VWAP consists of two components:

  1. Price: The contract price of the trade.
  2. Quantity: The quantity of contracts that got settled in the trade.

The VWAP calculation formula is the following:

VWAP = ∑(price * quantity) / ∑quantity

In this formula, we can set ∑(price * quantity) as volume and ∑quantity as totalQuantity, so we have:

VWAP = volume / totalQuantity

Futures VWAP

To calculate futures VWAP, there are three cases, where volume and totalQuantity should be updated:

  1. On order filling.
  2. On order matching.
  3. On position closing.

In those 3 cases, _addValuesForVWAP function is called.

function _addValuesForVWAP(
    bytes32 marketID,
    uint256 quantity,
    uint256 contractPrice
) internal {
    mostRecentEpochVolume[marketID] = mostRecentEpochVolume[marketID].add(quantity.mul(contractPrice));
    mostRecentEpochQuantity[marketID] = mostRecentEpochQuantity[marketID].add(quantity);
}

Calculation

The funding fee formula is the following:

fundingFee = (futuresVWAP - indexPrice) / (24 / fundingInterval)

, where fundingInterval may differ between markets and it represents how often the funding fee is applied.

Testnet setup

For the testnet we are setting up a centralized oracle service. In later versions this will be replaced by a decentralized mechanism.

Testnet pairs

-XAU/USDT -ETH/USDT -UNI/USDT -YFI/USDT -DOT/USDT -BTC/USDT -EGLD/USDT -LINK/USDT -BNB/USDT

For all market pairs the fundingInterval is 1 hour.

That means that funding fees are applied every hour between all positions of a market.

Oracle services

Prices in all markets are updated by oracle service every 10 seconds, except XAU/USDT pair where the price is updated every 5 minutes.

If any asynchronous requests fail, we deploy an exponential backoff strategy.

For XAU/USDT price is taken from https://metals-api.com/.

For all other pairs prices are taken from binance API. Example for ETH/USDT pair: [https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT].

Events

InjectiveFutures Contract Events

FuturesPosition

event FuturesPosition(
  address indexed makerAddress, // Address that created the order.
  bytes32 subAccountID, // subaccount id that created the order.
  bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getTypedDataHash).
  bytes32 indexed marketID, // Market ID
  uint256 contractPrice, // Price of the contract
  uint256 quantityFilled, // quantity of contracts added
  uint256 totalQuantity, // total quantity of contracts in position
  uint256 initialMargin, // initial margin
  int256 cumulativeFundingEntry, // cum. funding at position start
  uint256 positionID, // positionID
  bool isLong // true if long, false if short
);

FuturesCancel

event FuturesCancel(
  address indexed makerAddress, // Address that created the order.
  bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getTypedDataHash).
  bytes32 indexed marketID, // Market ID
  uint256 contractPrice, // Price of the contract
  uint256 quantityFilled // quantity of contracts filled
);

FuturesMatch

event FuturesMatch(
  bytes32 indexed leftOrderHash, // ID of the position
  bytes32 indexed rightOrderHash, // ID of the position
  bytes32 indexed marketID, //  Market ID
  uint256 quantity // quantity of contracts being matched.
);

FuturesLiquidation

event FuturesLiquidation(
  uint256 indexed positionID, // ID of the position
  bytes32 indexed marketID, //  Market ID
  bytes32 indexed subAccountID, // subaccount id
  uint256 quantity, // quantity of contracts being closed.
  int256 contractPNL // PNL for one contract
);

FuturesClose

event FuturesClose(
  uint256 indexed positionID, // ID of the position
  bytes32 indexed marketID, //  Market ID
  bytes32 indexed subAccountID, // subaccount id
  uint256 quantity, // quantity of contracts being closed.
  int256 contractPNL // PNL for one contract
);

RegisterMarket

event RegisterMarket(
  bytes32 indexed marketID, // Market ID
  uint256 fundingInterval, // Funding interval
  uint256 initialPrice, // Initial price of the market
  uint256 timestamp // When the market was registered
);

MarketCreation

event MarketCreation(
  bytes32 indexed marketID, // the unique identifier of market created
  string indexed ticker,   // the human-readable ticker for the market
  address indexed oracle,   // the oracle address for the market
  PermyriadMath.Permyriad maintenanceMarginRatio // the maintenance margin ratio for the market
);

AccountCreation

event AccountCreation(
  address indexed creator, // account creator
  bytes32 subAccountID, // subaccount id
  uint256 accountNonce // account nonce
);

IncrementSubaccountDeposits

event IncrementSubaccountDeposits(
  bytes32 indexed subsubAccountID, // subsubAccountID to add deposits
  uint256 amount // amount to add
);

DecrementSubaccountDeposits

event DecrementSubaccountDeposits(
  bytes32 indexed subsubAccountID, // subsubAccountID to remove deposits
  uint256 amount // amount to remove
);

Oracle Contract Events

SetFunding

event SetFunding(
  bytes32 indexed marketID, // Market ID
  int256 fundingRate, // funding rate
  int256 fundingFee, // funding fee
  uint256 epoch // current epoch
);

SetPrice

event SetPrice(
  bytes32 indexed marketID, // Market ID
  uint256 price, // price
  uint256 timestamp, // current timestamp
  uint256 epoch // current epoch
);

Types

Order

struct Order {
  address makerAddress; // Address that created the order.
  address takerAddress; // Empty.
  address feeRecipientAddress; // Address that will receive fees when order is filled.
  address senderAddress; // Empty.
  uint256 makerAssetAmount; // The contract price i.e. the price of 1 contract denominated in base currency.
  uint256 takerAssetAmount; // The quantity of contracts the maker seeks to obtain.
  uint256 makerFee; // The amount of margin denoted in base currency the maker would like to post/risk for the order. If set to 0, the order is a Stop Loss of Take Profit order.
  uint256 takerFee; // The desired account nonce to use for cross-margining. If set the 0, the order is an isolated margin order.
  uint256 expirationTimeSeconds; // Timestamp in seconds at which order expires.
  uint256 salt; // Arbitrary number to facilitate uniqueness of the order's hash.
  bytes makerAssetData; // The first 32 bytes contain the marketID of the market for the position if the order is LONG, empty otherwise. Right padded with 0's to be 36 bytes.
  bytes takerAssetData; // The first 32 bytes contain the marketID of the market for the position if the order is SHORT, empty otherwise. Right padded with 0's to be 36 bytes.
  bytes makerFeeAssetData; // The bytes-encoded positionID of the position to use for stop loss and take profit orders. Empty for vanilla make orders.
  bytes takerFeeAssetData; // The bytes-encoded trigger price for stop limit orders. Empty for vanilla make orders.
    }

OrderInfo

struct OrderInfo {
  OrderStatus orderStatus; // Status that describes order's validity and fillability.
  bytes32 orderHash; // EIP712 typed data hash of the order (see LibOrder.getTypedDataHash).
  uint256 orderTakerAssetFilledAmount; // Amount of order that has already been filled.
    }

Account

 struct Account {
    bytes32 subAccountID;
    uint256 accountNonce;
 }

Market

struct Market {
  bytes32 marketID;
  string ticker;
  address oracle;
  PermyriadMath.Permyriad initialMarginRatioFactor;
  PermyriadMath.Permyriad maintenanceMarginRatio;
  uint256 indexPrice;
  uint256 nextFundingTimestamp; // the current funding timestamp
  uint256 fundingInterval; // defines the interval in seconds by which the nextFundingTimestamp increments
  int256 cumulativeFunding; //Stored based on one contract. /10^6
  PermyriadMath.Permyriad makerTxFee; // transaction maker fee
  PermyriadMath.Permyriad takerTxFee; // transaction taker fee
  PermyriadMath.Permyriad relayerFeePercentage; // transaction relayer fee percentage
  }

Position

struct Position {
  // owner of the position
  bytes32 subAccountID;
  // marketID of the position
  bytes32 marketID;
  // direction of the position
  Direction direction;
  // quantity of the position
  uint256 quantity;
  // contractPrice of the position
  uint256 contractPrice;
  // the margin the trader has posted for the position
  uint256 margin;
  // The cumulative funding value. Just for perpetuals.
  int256 cumulativeFundingEntry;
  // order hash used to establish the position, if any (existence implies position was created by a make order maker)
  bytes32 orderHash;
}

CloseResults

struct CloseResults {
  // Payout to the owner resulting from closing. Negative if vaporized.
  int256 payout;
  // quantity of contracts closed
  uint256 quantityClosed;
}

FillResults

struct FillResults {
  uint256 makerPositionID; // maker positionID
  uint256 takerPositionID; // taker positionID
  uint256 makerMarginUsed; // Total amount of margin used to create the position for the maker.
  uint256 takerMarginUsed; // Total amount of margin used to create the position for the taker.
  uint256 quantityFilled; // Total quantity of contracts filled.
  uint256 makerFeePaid; // Total amount of fee paid by maker.
  uint256 takerFeePaid; // Total amount of fee paid by taker.
}

PositionResults

struct PositionResults {
  uint256 positionID;
  uint256 marginUsed;
  uint256 quantity;
  uint256 fee;
}

Testnet

Our testnet is a Tendermint based sidechain but abstracts away the necessity for users to send Tendermint style transactions. To perform actions on our sidechain (e.g. creating orders, placing trades, canceling orders, etc.) users can simply send HTTP requests to any relayer which runs our relayer-api REST server which is compliant with the 0x v3 Standard Relayer API specification.

The endpoints served by a Relayer API server are divided into the following subroutes:

The full API specification can be found here. OpenAPI YAML spec is located here.

See some examples on how to interact with the API endpoints.

Testnet Sidechain Deployment

We run a small set of consensus validator nodes that are built using Cosmos SDK and operate using Tendermint protocol. While RPC of those nodes is not directly exposed, we run a covenient API wrapper - relayer-api that implements caching, data validation and adapts the responses to be compliant with SRA, TradingView, etc.

A few sentry or read-only nodes are replicating the state of the sidechain to different regions, and we set up an API endpoint for each supported region, so the frontentd and CLI tools can switch to the closest region with lower latency.

Joining the Testnet

It is very easy for anyone to join the testnet and run own relayerd node in read-only mode. While it won't participate in consensus as a validator, it will be a full-node receiving all updates and keeping the state history. Such self-hosted node should be trusted 100% and you may run own relayer-api instance for your frontend of choice.

Further instructions for setting up a testnet sentry node are here.


Relayer API Examples

Public Testnet Endpoints

Example Request

curl -X POST "https://testnet-api.injective.dev/api/sra/v3/order" \
    -H "accept: application/json" \
    -H "Content-Type: application/json" -d'
{
  "chainId": 1337,
  "exchangeAddress": "0x12459c951127e0c374ff9105dda097662a027093",
  "expirationTimeSeconds": "1532560590",
  "feeRecipientAddress": "0xb046140686d052fff581f63f8136cce132e857da",
  "makerAddress": "0x9e56625509c2f60af937f23b7b532600390e8c8b",
  "makerAssetAmount": "10000000000000000",
  "makerAssetData": "0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498",
  "makerFee": "100000000000000",
  "makerFeeAssetData": "0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498",
  "salt": "1532559225",
  "senderAddress": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32",
  "signature": "0x012761a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33",
  "takerAddress": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32",
  "takerAssetAmount": "20000000000000000",
  "takerAssetData": "0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498",
  "takerFee": "200000000000000",
  "takerFeeAssetData": "0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498"
}'

201: Created – Order successfully posted.

{
  "rLimitLimit": 60,
  "rLimitRemaining": 56,
  "rLimitReset": 1372700873
}

417: Expectation Failed

{
  "code": 101,
  "reason": "Validation failed",
  "validationErrors": [
    {
      "code": 1001,
      "field": "maker",
      "reason": "Incorrect format"
    }
  ]
}

500: Internal Server Error

{
    "name": "internal",
    "id": "EMlNh2zD",
    "message": "post order failed: rpc error: code = Internal desc = unknown request: invalid signature",
    "temporary": false,
    "timeout": false,
    "fault": false
}

POST Post Order (Spot Markets)

https://testnet-api.injective.dev/api/sra/v3/order

This is a SRA v3 compatible endpoint from 0x spec. Allows user to submit a signed make order to the relayer to be added into spot market's orderbook.

Body Parameters    
chainIdRequired integer Specify Chain ID of the transaction.
exchangeAddressRequired string Exchange v3 contract address.
expirationTimeSecondsRequired string Timestamp in seconds at which order expires.
feeRecipientAddressRequired string Address that will receive fees when order is filled.
makerAddressRequired string Address that created the order.
makerAssetAmountRequired string Amount of makerAsset being offered by maker. Must be greater than 0.
makerAssetDataRequired string ABIv2 encoded data that can be decoded by a specified proxy contract when transferring makerAsset.
makerFeeRequired string Amount of FeeAsset paid to feeRecipient by maker when order is filled. If set to 0, no transfer of FeeAsset from maker to feeRecipient will be attempted.
makerFeeAssetDataRequired string ABIv2 encoded data that can be decoded by a specified proxy contract when transferring makerFee.
saltRequired string Arbitrary number to facilitate uniqueness of the order's hash.
senderAddressRequired string Address that is allowed to call Exchange contract methods that affect this order. If set to 0, any address is allowed to call these methods.
signatureRequired string Order EIP712 signature.
takerAddressRequired string Address that is allowed to fill the order. If set to 0, any address is allowed to fill the order.
takerAssetAmountRequired string Amount of takerAsset being bid on by maker. Must be greater than 0.
takerAssetDataRequired string ABIv2 encoded data that can be decoded by a specified proxy contract when transferring takerAsset.
takerFeeRequired string Amount of FeeAsset paid to feeRecipient by taker when order is filled. If set to 0, no transfer of FeeAsset from taker to feeRecipient will be attempted.
takerFeeAssetDataRequired string ABIv2 encoded data that can be decoded by a specified proxy contract when transferring takerFee.

Example Request

curl -X GET "https://testnet-api.injective.dev/api/chronos/v1/history?symbol=INJ%2FWETH&resolution=1D&from=1596011845&to=1596019845" -H "accept: application/json"

200: OK

{
  "c": [
    3662.25,
    3663.13,
    3664.01
  ],
  "h": [
    3667.24,
    3664.47,
    3664.3
  ],
  "l": [
    3661.55,
    3661.9,
    3662.43
  ],
  "nb": 1484871000,
  "o": [
    3667,
    3662.25,
    3664.29
  ],
  "t": [
    1547942400,
    1547942460,
    1547942520
  ],
  "v": [
    34.7336,
    2.4413,
    11.7075
  ]
}

GET History (for TradingView)

https://testnet-api.injective.dev/api/chronos/v1/history

Request for history bars for TradingView. Corresponds to UDF methods from this TradingView spec - https://www.tradingview.com/rest-api-spec/#operation/getHistory.

Body Parameters    
symbolRequired string Symbol name or ticker.
resolutionRequired string Symbol resolution. Possible resolutions are daily (D or 1D, 2D ... ), weekly (1W, 2W ...), monthly (1M, 2M...) and an intra-day resolution – minutes(1, 2 ...).
from integer Unix timestamp (UTC) of the leftmost required bar, including from.
toRequired integer Unix timestamp (UTC) of the rightmost required bar, including to. It can be in the future. In this case, the rightmost required bar is the latest available bar.
countback integer Number of bars (higher priority than from) starting with to. If countback is set, from should be ignored.

Example Message

{
    "type": "subscribe",
    "channel": "orders",
    "requestId": "123e4567-e89b-12d3-a456-426655440000"
}

200: OK

{
    "type": "update",
    "channel": "orders",
    "requestId": "123e4567-e89b-12d3-a456-426655440000",
    "payload":  [
        {
          "order": {
              "exchangeAddress": "0x12459c951127e0c374ff9105dda097662a027093",
              "domainHash" "0x78772b297e1b0b31089589a6608930cceba855af9d3ccf7b92cf47fa881e21f7",
              "makerAddress": "0x9e56625509c2f60af937f23b7b532600390e8c8b",
              "takerAddress": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32",
              "feeRecipientAddress": "0xb046140686d052fff581f63f8136cce132e857da",
              "senderAddress": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32",
              "makerAssetAmount": "10000000000000000",
              "takerAssetAmount": "20000000000000000",
              "makerFee": "100000000000000",
              "takerFee": "200000000000000",
              "expirationTimeSeconds": "1532560590",
              "salt": "1532559225",
              "makerAssetData": "0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498",
              "takerAssetData": "0x02571792000000000000000000000000371b13d97f4bf77d724e78c16b7dc74099f40e840000000000000000000000000000000000000000000000000000000000000063",
              "exchangeAddress": "0x12459c951127e0c374ff9105dda097662a027093",
              "makerFeeAssetData": "0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498",
              "takerFeeAssetData": "0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498",
              "signature": "0x012761a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33"
            },
            "metaData": {
              "remainingTakerAssetAmount": "500000000"
            }
        },
        ...
    ]
}

POST 0x SRAv3 WebSocket Subscription

https://testnet-api.injective.dev/api/sra/v3/ws

Implements a WebSocket endpoint conforming the 0x WebSocket API Specification v3. https://github.com/0xProject/standard-relayer-api/blob/master/ws/v3.md
Clients must subscribe by sending a message of the following contents, the socket will send the updates for existing orders, so the orderbook can be updated accordingly.

Body Parameters    
typeRequired string Type of request, e.g. `subscribe`.
channelRequired string Should be `orders`.
requestIdRequired string A string UUID that will be sent back by the server in response messages so the client can appropriately respond when multiple subscriptions are made.

Learn from Dexterm

Dexterm is our barebones trading terminal for CLI (Command Line Interface). It's written in Go programming language and is already open-sourced. You can learn how to make Relayer API calls from the code in order to make trades. Also, if you're a Gopher, there is a handful of auto-generated API bindings with client-side validation and such.


Joining the Testnet

1. Get docker image

The most straightforward way to get the latest version of relayerd and relayer-api distribution is to pull a Docker image. We use the exact same image for our deployments, so you will be up to date with the rest of the network. Learn here on how to install Docker itself.

$ docker pull docker.injective.dev/core:latest

$ docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
docker.injective.dev/core   latest              99e2d2457df0        44 hours ago        145MB
docker.injective.dev/core   <none>              8c951f6c8274        4 days ago          145MB

If you are not a big fan of managing Docker images, stacks and want to avoid extra parameters in the commands, you can fetch a pre-built binaries for your OS here: https://github.com/InjectiveLabs/injective-core-releases/releases/

2. Familiarize yourself with Relayerd options

$ docker run -it --rm docker.injective.dev/core:latest relayerd --help

Relayer Daemon (a Tendermint node)

Usage:
  relayerd [command]

Available Commands:
  init                Initialize private validator, p2p, genesis, and application configuration files
  collect-gentxs      Collect genesis txs and output a genesis.json file
  migrate             Migrate genesis to a specified target version
  gentx               Generate a genesis tx carrying a self delegation
  validate-genesis    validates the genesis file at the default location or at the location passed as an arg
  add-genesis-account Add a genesis account to genesis.json

  keys                Add or view local private keys
  debug               Tool for helping with debugging your application
  info                Print version info
  start               Run the full node
  unsafe-reset-all    Resets the blockchain database, removes address book files, and resets priv_validator.json to the genesis state

  tendermint          Tendermint subcommands
  export              Export state to JSON

  version             Print the app version
  help                Help about any command

Flags:
  -b, --broadcast-mode string        Transaction broadcasting mode (sync|async|block) (default "sync")
      --chain-id string              Specify Chain ID for sending Tx (default "testnet")
      --eth-coordinator string       Ethereum address of Injective 0x Coordinator contract. (Ex: 0xb125995F...)
      --eth-from string              Ethereum wallet address to send from. (Ex: 0xf91fb157...)
      --eth-from-passphrase string   Passphrase for wallet private key specified with 'from' (default "12345678")
      --eth-from-pk string           Ethereum wallet private key (for testing only) (Ex: 5D862464FE95...)
      --eth-futures string           Ethereum address of Injective Futures contract. (Ex: 0xb125995F...)
      --eth-node-http string         HTTP endpoint for an Ethereum node. (default "http://localhost:8545")
      --eth-node-ws string           WebSocket endpoint for an Ethereum node. (default "ws://localhost:8545")
      --fees string                  Fees to pay along with transaction; eg: 10uatom
      --from string                  Name or address of private key with which to sign
      --from-passphrase string       Passphrase for private key specified with 'from' (default "12345678")
      --gas string                   gas limit to set per-transaction; set to "auto" to calculate required gas automatically (default 200000) (default "200000")
      --gas-adjustment float         adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored  (default 1)
      --gas-prices string            Gas prices to determine the transaction fee (e.g. 10uatom)
      --grpc-listen-addr string      gRPC server listening address (default "localhost:9900")
  -h, --help                         help for relayerd
      --home string                  directory for config and data (default "/root/.relayerd")
      --keyring-backend string       Select keyring's backend (default "file")
      --log_level string             Log level (default "main:info,state:info,*:error")
      --node string                  <host>:<port> to tendermint rpc interface for this chain (default "tcp://localhost:26657")
      --statsd-address string        UDP address of a StatsD compatible metrics aggregator. (default "localhost:8125")
      --statsd-enabled               Enabled StatsD reporting.
      --statsd-prefix string         Specify StatsD compatible metrics prefix. (default "relayerd")
      --statsd-stuck-func string     Sets a duration to consider a function to be stuck (e.g. in deadlock). (default "5m")
      --trace                        print out full stack trace on errors
      --trust-node                   Trust connected full node (don't verify proofs for responses) (default true)
      --zeroex-devutils string       Ethereum address of 0x DevUtils contract. (Ex: 0xb125995F...)
      --zeroex-exchange string       Ethereum address of 0x Exchange contract. (Ex: 0xb125995F...)

Use "relayerd [command] --help" for more information about a command.

Yes, there are many, but don't be afraid because most of them are static for all Testnet participants.

By default relayerd keeps all state in the --home directory which is /root/.relayerd in the container. So you need to specify a volume mount from the host directory into container. That way the state will survive during restarts of the Docker container.

$ mkdir ~/relayerd
$ alias relayerd='docker run -it -v ~/relayerd:/root/.relayerd --rm docker.injective.dev/core relayerd'
# ^ put that alias into .bashrc or something

3. Init Relayerd state

Before syncing up with the rest of the testnet, you need to init a full node state. Right after init, the genesis config needs to be replaced with our pre-baked version. So your node will catch up with others on the first start.

$ relayerd init [your_custom_moniker] --chain-id testnet

You can edit this node moniker later, in the $HOME/relayerd/config/config.toml file. When running without Docker, the default location would be $HOME/.relayerd, and with Docker the state dir would be owned by root — so use sudo to edit this.

# A custom human readable name for this node
moniker = "<your_custom_moniker>"

You must also set the list of persistent peers so your node can sync the state from these bootstrap nodes.

# Comma separated list of nodes to keep persistent connections to
persistent_peers = "ff48adee4733a3edf9d449fce76cddf919569920@testnet-rpc.injective.dev:26656,07d0159602fca092c4702f514e994879ab78064e@testnet-rpc-us.injective.dev:26656,3816d76550646ca74d57b2ed16ff1acc2b7925f0@testnet-rpc-ap.injective.dev:26656"

Optionally enable Prometheus metrics for the node.

# When true, Prometheus metrics are served under /metrics on
# PrometheusListenAddr.
# Check out the documentation for the list of available metrics.
prometheus = true

# Address to listen for Prometheus collector(s) connections
prometheus_listen_addr = ":26660"

Save and close the config.toml file.

Download the latest genesis.json for the Testnet network:

$ wget https://testnet-genesis.injective.dev/genesis.json -O $HOME/relayerd/config/genesis.json

4. Prepare launch script

To launch relayerd for syncing you'll need to set some parameters to their defaults for Testnet. Just copy this launch script template and place it into $HOME/relayerd-testnet.sh:

relayerd-testnet.sh

#!/bin/sh

# NOTE: chain-id in relayerd arguments is Cosmos chain.
# Both Ethereum Network ID and Chain ID are fetched from the node.

docker run -it --rm \
    -v ~/relayerd:/root/.relayerd \
    -p 26657:26657 -p 9900:9900 \
    docker.injective.dev/core relayerd \
    --chain-id=testnet \
    --eth-coordinator="0x3b46eF40b11888b7353C764Fca86A83fF89dC90C" \
    --eth-futures="0x8f399baf9009a1466d9a3d8372703427c9f0c8cc" \
  --zeroex-devutils="0xDf12200825b1D37F92a6A959813cd2B2bfA2c488" \
  --zeroex-exchange="0xe525672f353e1b155f4495010D7814c5dd64a3C6" \
    --eth-node-ws="wss://evm-eu.injective.dev/ws" \
    --eth-node-http="https://evm-eu.injective.dev" \
    --log_level="*:info" \
    --rpc.laddr="tcp://0.0.0.0:26657" \
    --grpc-listen-addr "0.0.0.0:9900" \
    start

EVM Endpoints available

Just pick one that is closer geographically to your sentry node and fill into the script template.

One last step is to make the script executable:

$ chmod +x relayerd-testnet.sh

5. Sync the node

Run the script to start syncing the node with the rest of Testnet. You should not close terminal, as this script is not daemonized — if you replace -it --rm with -d in the command to run a detached container. There is a lot of ways to daemonize this alternatively — using systemd, docker-compose or even docker stack. Daemonization of the relayerd process is out of scope of this tutorial.

$ ./relayerd-testnet.sh

...
I[2020-07-29|18:32:07.878] No 'from' account provided, loopback client will be in read-only mode module=main
I[2020-07-29|18:32:07.891] RelayerDaemon init done                      module=main.
...

Note that the node without --from argument will run in read-only mode and won't be able to submit sidechain transactions, e.g. when posting the order. Please contact with Injective Team to get some Testnet coins to pay for transaction gas. A simple restart would be enough to activate transaction sending then.

The next step after the node is fully synced would be to launch own Relayer API gateway.

Hosting Relayer API

1. Get docker image

The most straightforward way to get the latest version of relayer-api server distribution is to pull a Docker image. We use the exact same image for our deployments, so you will be up to date with the rest of the network. Learn here on how to install Docker itself.

$ docker pull docker.injective.dev/core:latest

$ docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
docker.injective.dev/core   latest              99e2d2457df0        44 hours ago        145MB
docker.injective.dev/core   <none>              8c951f6c8274        4 days ago          145MB

If you are not a big fan of managing Docker images, stacks and want to avoid extra parameters in the commands, you can fetch a pre-built binaries for your OS here: https://github.com/InjectiveLabs/injective-core-releases/releases/

This guide relies on Docker features, but is straightforward for binary distribution users as well.

2. Familiarize yourself with relayer-api options

$ docker run -it --rm docker.injective.dev/core:latest relayer-api --help

Usage: relayer-api [OPTIONS] COMMAND [arg...]

Web server exposing injective-core API services.

Options:
      --env                   Application environment (env $APP_ENV) (default "local")
  -l, --log-level             Available levels: error, warn, info, debug. (env $APP_LOG_LEVEL) (default "info")
      --svc-wait-timeout      Standard wait timeout for all service dependencies (e.g. relayerd). (env $SERVICE_WAIT_TIMEOUT) (default "1m")
      --eth-node-ws           Specify WS endpoint for an Ethereum node. (env $ETHEREUM_RPC_WS) (default "ws://localhost:8545")
      --eth-node-http         Specify HTTP endpoint for an Ethereum node. (env $ETHEREUM_RPC_HTTP) (default "http://localhost:8545")
      --evm-node              Specify URI for an EVM sidechain node. (env $EVM_RPC_HTTP) (default "wss://evm-eu.injective.dev/ws")
      --zeroex-devutils       Ethereum address of 0x DevUtils contract. (Ex: 0xb125995F...) (env $ZEROEX_DEVUTILS_ADDR)
      --zeroex-exchange       Ethereum address of 0x Exchange contract. (Ex: 0xb125995F...) (env $ZEROEX_EXCHANGE_ADDR)
      --injective-futures     EVM address of Injective Futures contract. (Ex: 0xb125995F...) (env $INJECTIVE_FUTURES_ADDR)
  -B, --chronos-block         Specify block offset to watch fill events from. (env $CHRONOS_BLOCK_OFFSET) (default 0)
  -D, --chronos-data          Path to state DB of chronos server. (env $CHRONOS_DATA_PATH) (default "var/data")
      --relayerd-rpc-addr     Specify GRPC address of the relayerd service. (env $RELAYERD_RPC_ADDR) (default "localhost:9900")
      --tendermint-rpc-addr   Specify RPC address of a tendermint node. (env $TENDERMINT_RPC_ADDR) (default "tcp://localhost:26657")
      --http-listen-addr      HTTP server listening address (env $HTTP_LISTEN_ADDR) (default "localhost:4444")
      --ws-listen-addr        WebSocket server listening address (env $WS_LISTEN_ADDR) (default "localhost:4445")
      --statsd-prefix         Specify StatsD compatible metrics prefix. (env $STATSD_PREFIX) (default "relayer_api")
      --statsd-addr           UDP address of a StatsD compatible metrics aggregator. (env $STATSD_ADDR) (default "localhost:8125")
      --statsd-stuck-func     Sets a duration to consider a function to be stuck (e.g. in deadlock). (env $STATSD_STUCK_DUR) (default "5m")
      --statsd-mocking        If enabled replaces statsd client with a mock one that simply logs values. (env $STATSD_MOCKING) (default "false")
      --statsd-disabled       Force disabling statsd reporting completely. (env $STATSD_DISABLED) (default "false")

Commands:
  export                      Export Chronos DB as JSON

Run 'relayer-api COMMAND --help' for more information on a command.

Yes, there are many, but don't be afraid because most of them are static for all Testnet participants.

By default relayer-api keeps Chronos DB state in the --chronos-data directory which is /apps/data/var/data in the container. This can be overridden by CLI flag or ENV variables.

3. Prepare Docker Swarm config

Our example of deploying Relayer API server involves creating a Docker Swarm config docker-compose.yml that provides a very simple way to manage the service.

docker-compose.yml

version: "3.7"
services:
  relayer-api:
    image: docker.injective.dev/core:latest
    volumes:
       - chronos-data:/data/chronos
    deploy:
      replicas: 1
      endpoint_mode: vip
      restart_policy:
        condition: on-failure
    environment:
      - ETHEREUM_RPC_WS=wss://evm-eu.injective.dev/ws
      - ETHEREUM_RPC_HTTP=https://evm-eu.injective.dev
      - ZEROEX_DEVUTILS_ADDR=0xDf12200825b1D37F92a6A959813cd2B2bfA2c488
      - ZEROEX_EXCHANGE_ADDR=0xe525672f353e1b155f4495010D7814c5dd64a3C6
      - INJECTIVE_FUTURES_ADDR=0x8f399baf9009a1466d9a3d8372703427c9f0c8cc
      - CHRONOS_DATA_PATH=/data/chronos
      - RELAYERD_RPC_ADDR=172.17.0.1:9900
      - TENDERMINT_RPC_ADDR=tcp://172.17.0.1:26657
      - HTTP_LISTEN_ADDR=0.0.0.0:4444
      - WS_LISTEN_ADDR=0.0.0.0:4445
    command: relayer-api
    ports:
      - "8084:4444"
      - "8085:4445"
    networks:
      - relayer

networks:
  relayer:

volumes:
  chronos-data:

create.sh

#!/bin/sh

# docker pull docker.injective.dev/core:latest
docker stack deploy --resolve-image=always -c docker-compose.yml relayer
docker stack ls

update.sh

#!/bin/sh

docker pull docker.injective.dev/core:latest
docker service update --force relayer_relayer-api
docker service ls

EVM Endpoints available

Just pick one that is closer geographically to your sentry node and fill into the YAML template.

Docker Swarm Init

One prerequisite is that Docker swarm must be active, either by joining to existing pool or by initialization on the current machine:

$ docker swarm init

Swarm initialized: current node (mniwbffn9913gq9mwe0r88qb5) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-2tql4rttlmczysn8tavqrsr2u3f9n59rpn2qucj356de225udo-298f4i4zrjypqqv7ke6i7eus0 XXX.XXX.XXX.XXX:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

Preparing scripts

Create create.sh and update.sh scripts as suggested above to start relayer-api service. When creating the stack for the first time, run create.sh. The update script will download newer image and force-update the containers.

$ chmod +x create.sh
$ chmod +x update.sh

4. Run API Server

Before starting Relayer API container, make sure that your Relayerd sentry node is fully synced and started its gRPC server.

Running the create script launches a new stack relayer:

$ ./create.sh

Creating network relayer_relayer
Creating service relayer_relayer-api
NAME                SERVICES            ORCHESTRATOR
relayer             1                   Swarm

To shutdown stack completely:

$ docker stack rm relayer

Note that the Chronos DB volume survives the stack deletion. The volumes in replicated deployments are created on each swarm machine locally.

$ docker volume list

DRIVER              VOLUME NAME
local               relayer_chronos-data

Listing running services in stack:

$ docker service ls

ID                  NAME                  MODE                REPLICAS            IMAGE                              PORTS
n818lmaihmpx        relayer_relayer-api   replicated          1/1                 docker.injective.dev/core:latest   *:8084-8085->4444-4445/tcp

Reading Relayer API logs:

$ docker service logs -f relayer_relayer-api

5. Expose Relayer API

If you are running the server using a stack config above, then API server exposes ports 8084 and 8085 for HTTP API and WS API accordingly. You might expose those using a reverse proxy such as Nginx or Caddy. We use the following config for Caddy v2:

https://testnet-api.injective.dev {
        reverse_proxy /api/sra/v3/ws 172.18.0.1:8085
        reverse_proxy 172.18.0.1:8084
}

The IP 172.17.0.1 in stack config corresponds to docker0 allowing to access host ports from within a containter running in stack. And IP 172.18.0.1 there corresponds to docker_gwbridge allowing to access service ports from the host machine. Your environment and OS configuration might vary, so adjust accordingly.