Change Log
2026-02-19
- Updated all messages to reflect the changes included in the chain version v1.18.0, and the Indexer for that chain version
- Added documentation for the new websocket for exchange module events available on the chain nodes
- Python SDK v1.13.0
- Go SDK v1.60.0
2025-11-10
- Updated all messages to reflect the changes included in the chain version 1.17.0, and the Indexer for that chain version
- Python SDK v1.12.0
- Go SDK v1.59.0
2025-09-24
- Updated all messages to reflect the changes included in the chain version 1.16.4, and the Indexer for that chain version
- Python SDK v1.11.2
- Go SDK v1.58.3
2025-07-29
- Updated all messages to reflect the changes included in the chain version 1.16, and the Indexer for that chain version
- Added documentation for
erc20modules endpoints - Added documentation for
evmmodules endpoints - Updated all
exchangemodule examples to query the exchange v2 endpoints and send exchange v2 messages - New GTB orders functionality (Good-Til-Block orders)
- Python SDK v1.11.0
- Go SDK v1.58.0
2025-04-21
- Added documentation for
TXFeesmodules endpoints - Updated all messages to reflect the changes included in the chain version 1.15, and the Indexer for that chain version
- Python SDK v1.10.0
- Go SDK v1.57.0
2025-02-17
- Changed the
Permissionsmodule documentation to reflect the new module version (new queries and messages) - Updated all messages to reflect the changes included in the chain version 1.14, and the Indexer for that chain version
- Python SDK v1.9.0
- Go SDK v1.56.0
2024-08-06
- Added support for the following messages in the chain "exchange" module:
- MsgDecreasePositionMargin
- MsgUpdateSpotMarket
- MsgUpdateDerivativeMarket
- MsgAuthorizeStakeGrants
- MsgActivateStakeGrantmodule
- Python SDK v1.6.1: add min_notional to all market classes
- Go SDK v1.51.1: add min_notional to all market structs
2024-07-30
- Updated requests and responses messages with parameters added in chain upgrade to v1.13
- Updated the API documentation to include all queries and messages for the
tendermintmodule - Updated the API documentation to include all queries and messages for the
IBC transfermodule - Updated the API documentation to include all queries and messages for the
IBC core channelmodule - Updated the API documentation to include all queries and messages for the
IBC core clientmodule - Updated the API documentation to include all queries and messages for the
IBC core connectionmodule - Updated the API documentation to include all queries and messages for the
permissionsmodule - Python SDK v1.6.0
- Added support for all queries from the
tendermintmodule - Added support for all queries from the
IBC transfermodule - Added support for all queries from the
IBC core channelmodule - Added support for all queries from the
IBC core clientmodule - Added support for all queries from the
IBC core connectionmodule - Added support for all queries from the
permissionsmodule
- Added support for all queries from the
2024-03-08
- Updated the API documentation to include all queries and messages for the
distributionandchain exchangemodules - Python SDK v1.4.0
- Added support for all queries and messages from the
distributionmodule - Added support for all queries and messages from the
chain exchangemodule
- Added support for all queries and messages from the
2024-01-25
- Python SDK v1.2.0
- Improved message based gas estimator to consider that Post Only orders creation require more gas than normal orders
2024-01-02
- Python SDK v1.0 and Go SDK v1.49
- Added logic to support use of Client Order ID (CID) new identifier in OrderInfo
- New chain stream support
- Remove support for
sentrynodes in mainnet network. The only supported node option in mainnet islb - Migrated all proto objects dependency to support chain version 1.12
- Added logic to cover all bank module queries
- Added logic in Python SDK to support the initialization of tokens with all the tokens from the chain (DenomsMetadata)
- Added logic in Go SDK to allow the initialization of markets and tokens from the chain (without using the local .ini files). Also included functionality to initialize the tokens wilh all the tokens from the chain (DenomsMetadata)
- Added support for wasm, tokenfactory and wasmx modules, including example script for all their endpoints
2023-09-06
- Python SDK v0.8 release
- Network class was moved from
pyinjective.constanttopyinjective.core.network - Configuration to use secure or insecure channels has been moved into the Network class
- The Composer can be created now by the AsyncClient, taking markets and tokens from the chain information instead of using the local configuration files
- Changed the cookies management logic. All cookies management is done now by Network
- Network class was moved from
2023-08-28
- Added IP rate limits documentation
Introduction
Welcome to Injective's documentation!
Here you can find a comprehensive overview of our protocol, as well as tutorials, guides and general resources for developers and API traders.
If you would like to ask any questions or be a part of our community, please join our Discord Group or Telegram Group. We have a dedicated channel in our Discord group for questions related to the API.
Clients
Python Client
Dependencies
Ubuntu
sudo apt install python3.X-dev autoconf automake build-essential libffi-dev libtool pkg-config
Fedora
sudo dnf install python3-devel autoconf automake gcc gcc-c++ libffi-devel libtool make pkgconfig
macOS
brew install autoconf automake libtool
Installation
Install injective-py from PyPI using pip.
pip install injective-py
Reference
Choose Exchange V1 or Exchange V2 queries
The Injective Python SDK provides two different clients for interacting with the exchange:
Example - Exchange V1 Client
from injective.async_client import AsyncClient
from injective.network import Network
async def main():
# Initialize client with mainnet
client = AsyncClient(network=Network.mainnet())
# Or use testnet
# client = AsyncClient(network=Network.testnet())
# Use V1 exchange queries here
- Exchange V1 Client (
async_clientmodule):- Use this client if you need to interact with the original Injective Exchange API
- Import using:
from injective.async_client import AsyncClient - Suitable for applications that need to maintain compatibility with the original exchange interface
Example - Exchange V2 Client
from injective.async_client_v2 import AsyncClient
from injective.network import Network
async def main():
# Initialize client with mainnet
client = AsyncClient(network=Network.mainnet())
# Or use testnet
# client = AsyncClient(network=Network.testnet())
# Use V2 exchange queries here
- Exchange V2 Client (
async_client_v2module):- Use this client for the latest exchange features and improvements
- Import using:
from injective.async_client_v2 import AsyncClient - Recommended for new applications and when you need access to the latest exchange features
Both clients provide similar interfaces but with different underlying implementations. Choose V2 for new projects unless you have specific requirements for V1 compatibility.
Market Format Differences:
- V1 AsyncClient: Markets are initialized with values in chain format (raw blockchain values)
- V2 AsyncClient: Markets are initialized with values in human-readable format (converted to standard decimal numbers)
Exchange Endpoint Format Differences:
- V1 Exchange endpoints: All values (amounts, prices, margins, notionals) are returned in chain format
- V2 Exchange endpoints:
- Human-readable format for: amounts, prices, margins, and notionals
- Chain format for: deposit-related information (to maintain consistency with the Bank module)
Golang Client
1. Create your own client repo and go.mod file
go mod init foo
2. Import SDK into go.mod
require (
github.com/InjectiveLabs/sdk-go v1.58.0
)
Consult the sdk-go repository to find the latest release and replace the version in your go.mod file. Version v1.39.4 is only an example and must be replaced with the newest release
3. Download the package
Download the package using go mod download
go mod download github.com/InjectiveLabs/sdk-go
Choose Exchange V1 or Exchange V2 queries
The SDK provides two different clients for interacting with the Injective Exchange:
Example - ChainClient
// For Exchange V1
client := chainclient.NewChainClient(...)
// For Exchange V2
clientV2 := chainclient.NewChainClientV2(...)
ChainClient: Use this client if you need to interact with Exchange V1. This client maintains compatibility with the original exchange implementation and is suitable for existing applications that haven't migrated to V2 yet. Note that this client will not include any new endpoints added to the Exchange module - for access to new features, you should migrate to V2.ChainClientV2: Use this client for all new applications or when you need to interact with Exchange V2 features. This client provides access to the latest exchange functionality and improvements, including all new endpoints added to the Exchange module.
Markets Assistant
Example - Markets Assistant
// For Exchange V1 markets
marketsAssistant, err := chain.NewMarketsAssistant(ctx, client) // ChainClient instance
if err != nil {
// Handle error
}
// For Exchange V2 markets
marketsAssistantV2, err := chain.NewHumanReadableMarketsAssistant(ctx, clientV2) // ChainClientV2 instance
if err != nil {
// Handle error
}
The SDK provides a Markets Assistant to help you interact with markets in both V1 and V2. Here's how to create instances for each version
The Markets Assistant provides helper methods to:
- Fetch market information
- Get market prices
- Query orderbooks
- Access market statistics
Make sure to use the correct version of the Markets Assistant that matches your ChainClient version to ensure compatibility. The V1 assistant (NewMarketsAssistant) will only work with V1 markets, while the V2 assistant (NewHumanReadableMarketsAssistant) provides access to V2 markets and their features.
Format Differences
There are important format differences between V1 and V2 endpoints:
- Exchange V1: All values (amounts, prices, margins, notionals) are returned in chain format (raw numbers)
- Exchange V2: Most values are returned in human-readable format for better usability:
- Amounts, prices, margins, and notionals are in human-readable format
- Deposit-related information remains in chain format to maintain consistency with the Bank module
This format difference is one of the key improvements in V2, making it easier to work with market data without manual conversion.
Markets and Tokens information
Since version 1.49 the SDK is able also to get the markets and tokens information directly from the chain data (through the Indexer process). The benefit of this approach is that it is not necessary to update the SDK version when a new market is created in the chain or a new token is added.
Example - Get markets and tokens from Indexer (ExchangeClient)
package main
import (
"context"
"github.com/InjectiveLabs/sdk-go/client"
"github.com/InjectiveLabs/sdk-go/client/core"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
"os"
"github.com/InjectiveLabs/sdk-go/client/common"
chainclient "github.com/InjectiveLabs/sdk-go/client/chain"
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
tmClient, err := rpchttp.New(network.TmEndpoint, "/websocket")
if err != nil {
panic(err)
}
senderAddress, cosmosKeyring, err := chainclient.InitCosmosKeyring(
os.Getenv("HOME")+"/.injectived",
"injectived",
"file",
"inj-user",
"12345678",
"5d386fbdbf11f1141010f81a46b40f94887367562bd33b452bbaa6ce1cd1381e", // keyring will be used if pk not provided
false,
)
if err != nil {
panic(err)
}
// initialize grpc client
clientCtx, err := chainclient.NewClientContext(
network.ChainId,
senderAddress.String(),
cosmosKeyring,
)
if err != nil {
panic(err)
}
clientCtx = clientCtx.WithNodeURI(network.TmEndpoint).WithClient(tmClient)
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketsAssistant, err := core.NewMarketsAssistantUsingExchangeClient(ctx, exchangeClient)
if err != nil {
panic(err)
}
}
- To get the markets and tokens information directly from the chain, create an instance of MarketsAssistant using the
NewMarketsAssistantUsingExchangeClientfunction and passing the ExchangeClient as parameter
Example - MarketsAssistant with all tokens
package main
import (
"context"
"github.com/InjectiveLabs/sdk-go/client"
"github.com/InjectiveLabs/sdk-go/client/core"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
"os"
"github.com/InjectiveLabs/sdk-go/client/common"
chainclient "github.com/InjectiveLabs/sdk-go/client/chain"
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
tmClient, err := rpchttp.New(network.TmEndpoint, "/websocket")
if err != nil {
panic(err)
}
senderAddress, cosmosKeyring, err := chainclient.InitCosmosKeyring(
os.Getenv("HOME")+"/.injectived",
"injectived",
"file",
"inj-user",
"12345678",
"5d386fbdbf11f1141010f81a46b40f94887367562bd33b452bbaa6ce1cd1381e", // keyring will be used if pk not provided
false,
)
if err != nil {
panic(err)
}
// initialize grpc client
clientCtx, err := chainclient.NewClientContext(
network.ChainId,
senderAddress.String(),
cosmosKeyring,
)
if err != nil {
panic(err)
}
clientCtx = clientCtx.WithNodeURI(network.TmEndpoint).WithClient(tmClient)
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
chainClient, err := chainclient.NewChainClient(
clientCtx,
network,
common.OptionGasPrices(client.DefaultGasPriceWithDenom),
)
if err != nil {
panic(err)
}
ctx := context.Background()
marketsAssistant, err := core.NewMarketsAssistantWithAllTokens(ctx, exchangeClient, chainClient)
if err != nil {
panic(err)
}
}
By default the MarketsAssistant will only initialize the tokens that are part of an active market. In order to let it use any of the tokens available in the chain, the user has to create the MarketsAssistant instance using the function NewMarketsAssistantWithAllTokens.
The MarketsAssistant instance can be used with the following ChainClient functions:
CreateSpotOrderCreateDerivativeOrder
Reference
Typescript Client
Installation
Install the @injectivelabs/sdk-ts npm package using yarn
yarn add @injectivelabs/sdk-ts
Reference
To see Typescript examples please check the Typescript SDK documentation page listed above
For other languages
Currently Injective provides SDKs only for Go, Python and TypeScript. To interact with the nodes using a different language please connect directly using the gRPC proto objects. The compiled proto files for C++, C# and Rust can be found in InjectiveLabs/injective-proto
Overview
Injective is a DeFi focused layer-1 blockchain built for the next generation of decentralized derivatives exchanges. The Injective Chain is a Tendermint-based IBC-compatible blockchain which supports a decentralized orderbook-based DEX protocol and a trustless ERC-20 token bridge to the Ethereum blockchain.
It is the first decentralized exchange focused layer-1 blockchain for perpetual swaps, futures, and spot trading that unlocks the full potential of decentralized derivatives and borderless DeFi. Every component of the protocol has been built to be fully trustless, censorship-resistant, publicly verifiable, and front-running resistant.
By providing the unrestricted and unprecedented ability to express diverse views in decentralized financial markets, we strive to empower individuals with the ability to more efficiently allocate capital in our society.
Architecture Overview
Injective enables traders to create and trade on arbitrary spot and derivative markets. The entire process includes on-chain limit orderbook management, on-chain trade execution, on-chain order matching, on-chain transaction settlement, and on-chain trading incentive distribution through the logic codified by the Injective Chain's exchange module.
Architecturally there are two main services that traders should concern themselves with:
- The Injective Chain node (the Chain API)
- The Injective Exchange API
The trading lifecycle is as follows:
- First, traders cryptographically sign a transaction containing one or more order messages (e.g.
MsgBatchCreateDerivativeLimitOrders,MsgCreateSpotMarketOrder,MsgCancelDerivativeLimitOrder, etc. ). - Then the transaction is broadcasted to an Injective Chain node.
- The transaction is then added to the mempool and becomes included in a block. More details on this process can be found here.
- The handler for each respective message is run. During handler execution, order cancel and liquidation messages are processed immediately, whereas order creation messages are added to a queue.
- At the end of the block, the batch auction process for order matching begins.
- First, the queued market orders are executed against the resting orderbook (which does NOT include the new orders from the current block) and are cleared at a uniform clearing price.
- Second, the queued limit orders are matched against each other and the resting orderbook to result in an uncrossed orderbook. Limit orders created in that block are cleared at a uniform clearing price while resting limit orders created in previous blocks are cleared at an equal or better price than their limit order price.
- The funds are settled accordingly, with positions being created for derivative trades and assets being swapped for spot trades.
- Events containing the trade and settlement information are emitted by the Chain.
- The Injective Exchange API backend indexes the events and pushes updates to all subscribed traders.
Key Differences To CEX
- All information is public which includes things like untriggered Stop/Take orders or pending orders in the mempool.
- The data stored on-chain is minimal for performance reasons and reflects only the current state; exchange dApps provide additional historical data as well as a user interface for traders through the Injective Exchange API backend.
- Usually a DEX has front-running issues, but those are mitigated at Injective through fast block times and FBA (Frequent Batch Auction).
- The order of execution is different. Any new exchange action is a new transaction and is not executed immediately. Instead, it is added to a queue (mempool) and executed once the block is committed. At the time of the block commit, all included transactions happen more or less instantly. Firstly, code that is inside the handler is executed in the transaction sequence which is decided by the miner. This is not a problem since the sequence does not affect matching prices due to FBA and thus fairness is guaranteed.
To summarize the sequence of state changes on the Injective Chain:
- Mempool: A queue of pending transactions.
- BeginBlocker: Code that is executed at the beginning of every block. We use it for certain maintenance tasks (details can be found in the exchange module documentation).
- Handler: Code that is executed when a transaction is included in a block.
- EndBlocker: Code that is executed at the end of every block. We use it to match orders, calculate changes in funds, and update positions.
Comparison to CEX
| Centralized Exchange (CEX) | Decentralized Exchange (DEX) |
|---|---|
| Exchange Gateway | Injective Chain Handler |
| Exchange Matching Engine | Injective Chain EndBlocker |
| Exchange Trade Report | Injective Chain EndBlocker |
| Co-location | Injective Node (Decentralized Validators) |
Frequent Batch Auction (FBA)
The goal is to further prevent any Front-Running in a decentralized setting. Most DEX's suffer from this as all information is public and traders can collude with miners or pay high gas fees enabling them to front-run any trades. We mitigate this by combining fast block times with a Frequent Batch Auction:
In any given block:
- Calculate one uniform clearing price for all market orders and execute them. For an example for the market order matching in FBA fashion, look here.
- Limit orders are combined with the resting orderbook and orders are matched as long as there is still negative spread. The limit orders are all matched at one uniform clearing price. For an example for the limit order matching in FBA fashion, look here.
Trading Fees and Gas
If you are a trader on existing centralized exchanges, you will be familiar with the concept of trading fees. Traders are charged a fee for each successful trade. However, for a DEX, there are additional gas costs that must be paid to the network. And luckily, the gas fee from trading on Injective is very minimal.
- If you are a trader using a DEX UI, you don't need to worry about gas costs because the exchange dApp will pay them for you. However, you will pay trading fees in full.
- If you are using the API, then you will need to pay the gas costs.
- The gas costs are currently minimal, 20K transactions will cost about 1 INJ.
- You can set the fee_recipient to your own wallet address to save 40% of all trading fees.
Mark Price Margin Requirement
Quantity = 2 BTC, InitialMarginRatio = 0.05
MarkPrice = $45,000, EntryPrice = $43,000
Margin ≥ 2 * 0.05 * $45,000 = $4,500
MarginLong ≥ max(2 * (0.05 * $45,000 - ($45,000 - $43,000)), $4,500)
MarginLong ≥ max($500, $4,500) = $4,500
MarginShort ≥ max(2 * (0.05 * $45,000 - ($43,000 - $45,000)), $4,500)
MarginShort ≥ max($8,500, $4,500) = $8,500
So in this case if the trader wanted to create a short position with
an entry price which essentially starts at a loss of $2,000 as
unrealized PNL, he would need to post at a minimum $8,500 as margin,
rather than the usual required $4,500.
You might be familiar with margin requirements on Centralized Exchanges. When creating a new position, it must fulfill the following requirement:
Margin >= InitialMarginRatio * Quantity * EntryPrice
For example in a market with maximally 20x leverage, your initial margin must be at least 0.05 of the order's notional (entryPrice * quantity). On Injective additionally the margin must also fulfill the following mark price requirement:
Margin >= Quantity * (InitialMarginRatio * MarkPrice - PNL)
where PNL is the expected profit and loss of the position if it was closed at the MarkPrice.
Liquidations
Long Position:
Quantity = 1 BTC, MaintenanceMarginRatio = 0.05
EntryPrice = $50,000, Margin = $5,000
Now the MarkPrice drops down to $47,300, which is below the liquidation price of $47,368.42 (when margin = $2,368.42, maintenance ratio ≈ .04999998).
The position is auto-closed via reduce-only order:
Sell order:
Quantity = 1 BTC, Price = $0, Margin = $0
Assuming it gets matched with a clearing price of 47,100:
Liquidation Payout = Position Margin + PNL = $5,000 - $2,900 = $2,100
Liquidator Profit = $2,100 * 0.5 = $1,050
Insurance Fund Profit = $2,100 * 0.5 = $1,050
When your position falls below the maintenance margin ratio, the position can and likely will be liquidated by anyone running the liquidator bot. You will loose your entire position and all funds remaining in the position. On-chain, a reduce-only market order of the same size as the position is automatically created. The market order will have a worst price defined as Infinity or 0, implying it will be matched at whatever prices are available in the order book.
One key difference is that the payout from executing the reduce-only market order will not go towards the position owner. Instead, half of the remaining funds are transferred to the liquidator bot and the other half is transferred to the insurance fund.
If the payout in the position was negative, i.e., the position's negative PNL was greater than its margin, then the insurance fund will cover the missing funds.
Note: liquidations are executed immediately in a block before any other order matching occurs.
Fee Discounts
Fee discounts are enabled by looking at the past trailing 30 day window. As long as you meet both conditions for a tier (volume traded AND staked amount), you will receive the respective discounts.
- Note that there is a caching mechanism in place which can take up to one day before being updated with a new tier.
- Negative maker fee markets are not eligible for discounts.
Funding Rate
The hourly funding rate on perpetual markets determines the percentage that traders on one side have to pay to the other side each hour. If the rate is positive, longs pay shorts. If the rate is negative, shorts pay longs. The further trade prices deviate from the mark price within the hour, the higher the funding rate will be up to a maximum of 0.0625% (1.5% per day).
Closing a Position
Suppose you have an open position:
- Direction = Long
- Margin = $5,000
- EntryPrice = $50,000
- Quantity = 0.5 BTC
You create a new vanilla order for
- Direction = Sell
- Margin = $10,000
- Price = $35,000
- Quantity = 0.75 BTC
which is fully matched. First, the position is fully closed:
- OrderMarginUsedForClosing = OrderMargin * CloseQuantity / OrderQuantity
- OrderMarginUsedForClosing = $10,000 * 0.5 / 0.75 = $6,667
The proportional closing order margin is then used for the payout:
- Payout = PNL + PositionMargin + OrderMarginUsedForClosing
- Payout = ($35,000-$50,000) * 0.5 + $5,000 + $6,667 = $4,167
And a new position is opened in the opposite direction:
- Direction = Short
- Margin = $3,333
- Price = $35,000
- Quantity = 0.25 BTC
There are two ways to close a position:
Closing via Reduce-Only Order
When you close a position via a reduce-only order, no additional margin is used from the order. All reduce-only orders have a margin of zero. In addition, reduce-only orders are only used to close positions, not to open new ones.
Closing via Vanilla Order
You can also close a position via vanilla orders. When a sell vanilla order is getting matched while you have an open Long position, the position will be closed at the price of the sell order. Depending on the size of the order and position, the position may be either
- partially closed
- fully closed
- or fully closed with subsequent opening of a new position in the opposite direction.
Note that how the margin inside the order is used depends on which of the three scenarios you are in. If you close a position via vanilla order, the margin is only used to cover PNL payouts, not to go into the position. If the order subsequently opens a new position in the opposite direction (scenario 3), the remaining proportional margin will go towards the new position.
Trading Rewards
Assume you have a trading rewards campaign with 100 INJ as rewards:
Reward Tokens: 100 INJ
Trader Reward Points = 100
Total Reward Points = 1,000
Trader Rewards = Trader Reward Points / Total Reward Points * Reward Tokens
Trader Rewards = 100 / 1,000 * 100 INJ = 10 INJ
During a given campaign, the exchange will record each trader's cumulative trading reward points obtained from trading fees (with boosts applied, if applicable) from all eligible markets. At the end of each campaign each trader will receive a pro-rata percentage of the trading rewards pool based off their trading rewards points from that campaign epoch. Those rewards will be automatically deposited into the trader's respective wallets, it's not necessary to manually withdraw them.
Reduce-Only Order Precedence
Imagine a trader has the following position:
- LONG:
1 BTCwith EntryPrice of $59,000
And the following SELL orders:
| Buy Price | Quantity | Order Type |
|---|---|---|
| $66,500 | 0.2 BTC | Vanilla |
| $65,500 | 0.1 BTC | Reduce-only |
| $65,400 | 0.1 BTC | Vanilla |
| $64,500 | 0.3 BTC | Vanilla |
| $63,500 | 0.1 BTC | Reduce-only |
This has some implications when placing new orders.
Upon placing a reduce-only order:
We check if any reduce-only orders would be invalid after executing all of the trader's other limit sell orders that have better prices in the same direction.
In our example, consider a new reduce-only order of 0.4 BTC at $64,600.
| Sell Price | Quantity | Order Type |
|---|---|---|
| $66,500 | 0.2 BTC | Vanilla |
| $65,500 | 0.1 BTC | Reduce-only |
| $65,400 | 0.1 BTC | Vanilla |
| $64,600 | 0.4 BTC | Reduce-only |
| $64,500 | 0.3 BTC | Vanilla |
| $63,500 | 0.1 BTC | Reduce-only |
This is perfectly valid and no further action is required. If the buy price hit $65,500 and all limit sell orders less than or equal to that price were filled, then the long position would be closed. If the price hit $66,500 and the vanilla sell order was filled, then the trader would open a 0.2 BTC short position. But what if the reduce-only order was for 0.5 BTC instead?
| Sell Price | Quantity | Order Type |
|---|---|---|
| $66,500 | 0.2 BTC | Vanilla |
| $65,500 | 0.1 BTC | Reduce-only |
| $65,400 | 0.1 BTC | Vanilla |
| $64,600 | 0.5 BTC | Reduce-only |
| $64,500 | 0.3 BTC | Vanilla |
| $63,500 | 0.1 BTC | Reduce-only |
If the orders are getting matched, once the last vanilla order of 0.1 BTC at $65,400 is filled, the position will have been reduced to 1 BTC - 0.1 BTC - 0.3 BTC - 0.5 BTC - 0.1 BTC = 0 BTC. The next reduce-only order of 0.1 BTC at $65,500 will thus be invalid.
To prevent that, we automatically cancel all reduce-only orders at a price where the cumulative sum of orders up to and including the reduce-only order would add up to more than the trader’s current long amount. Another way to think about it: we find the reduce-only order with the highest price such that all orders (vanilla and reduce-only) including and below that price add up in quantity to less than the long quantity. All reduce-only orders above that price will be canceled so that no reduce-only orders exist when the position is closed or short. The same concept applies to reduce-only orders on short positions, but we look for the lowest price instead of the highest on buy orders so that no reduce-only orders exist when the position is closed or long.
Upon placing a vanilla limit order:
We check if any reduce-only limit orders would be invalidated if all the orders up to and including the new vanilla limit order were filled.
In our example, consider a new vanilla order of 0.4 BTC at $64,600.
| Sell Price | Quantity | Order Type |
|---|---|---|
| $66,500 | 0.2 BTC | Vanilla |
| $65,500 | 0.1 BTC | Reduce-only |
| $65,400 | 0.1 BTC | Vanilla |
| $64,600 | 0.4 BTC | Vanilla |
| $64,500 | 0.3 BTC | Vanilla |
| $63,500 | 0.1 BTC | Reduce-only |
Again this perfectly valid and no further action is required because all order quantities up to the highest priced reduce-only order add up to ≤ the long position quantity. But what if the order was for 0.5 BTC instead?
| Sell Price | Quantity | Order Type |
|---|---|---|
| $66,500 | 0.2 BTC | Vanilla |
| $65,500 | 0.1 BTC | Reduce-only |
| $65,400 | 0.1 BTC | Vanilla |
| $64,600 | 0.5 BTC | Vanilla |
| $64,500 | 0.3 BTC | Vanilla |
| $63,500 | 0.1 BTC | Reduce-only |
If the orders are getting matched, once the last reduce-only order of $65,500 is reached, the position will have been reduced to 1 BTC - 0.1 BTC - 0.3 BTC - 0.5 BTC - 0.1 BTC = 0 BTC. A reduce-only order of 0.1 BTC after that will thus be invalid.
To prevent this, we automatically cancel the existing 0.1 BTC reduce-only order. In other words, new vanilla limit orders can invalidate and auto-cancel existing reduce-only limit orders if the reduce-only order becomes invalid at its price.
Rate Limits
The public mainnet and testnet nodes have a request rate limit associated to the requester IP address. The limits are:
- 20 requests/second for the "chain" group
- 50 requests/second for the "indexer" group
Each endpoint's section in this document clarifies which group the endpoint belongs to. When the limit is reached the server will respond sending an error response with code 429.
Order types
- BUY (1): A standard buy order to purchase an asset at either the current market price or a set limit price. Market orders in Injective also have a price to stablish a limit to the market price the order will be executed with.
- SELL (2): A standard sell order to sell an asset at either the current market price or a set limit price. Market orders in Injective also have a price to stablish a limit to the market price the order will be executed with.
- STOP_BUY (3): A stop-loss buy order converts into a regular buy order once the oracle price reaches or surpasses a specified trigger price.
- STOP_SELL (4): A stop-loss sell order becomes a regular sell order once the oracle price drops to or below a specified trigger price.
- TAKE_BUY (5): A take-profit buy order converts into a regular buy order once the oracle price reaches or surpasses a specified trigger price.
- TAKE_SELL (6): A take-profit sell order becomes a regular sell order once the oracle price drops to or below a specified trigger price.
- BUY_PO (7): Post-Only Buy. This order type ensures that the order will only be added to the order book and not match with a pre-existing order. It guarantees that you will be the market "maker" and not the "taker".
- SELL_PO (8): Post-Only Sell. Similar to BUY_PO, this ensures that your sell order will only add liquidity to the order book and not match with a pre-existing order.
- BUY_ATOMIC (9): An atomic buy order is a market order that gets executed instantly, bypassing the Frequent Batch Auctions (FBA). It's intended for smart contracts that need to execute a trade instantly. A higher fee is paid defined in the global exchange parameters (currently it is two times the normal trading fee).
- SELL_ATOMIC (10): An atomic sell order is similar to a BUY_ATOMIC, and it gets executed instantly at the current market price, bypassing the FBA.
Gas estimation
Interactions with the Injective Chain that involve processing will incur gas consumption. The gas required for any action is related to the interactions with the chain store, and thus, the amount of gas is not deterministic. Users sending messages to the chain in transactions must estimate the gas required. There are two primary methods for determining the gas required for a transaction:
- Transaction simulation: The chain allows users to submit transactions in simulation mode. In this mode, the transaction is executed as if it were part of the next chain block, and a response is returned to the user. This response includes the gas required for the simulation. It is advisable to slightly increase the simulated gas when broadcasting the actual transaction to minimize the risk of encountering an out-of-gas error.
- Gas estimation: Users can estimate the gas required for a transaction based on the messages included and the historical gas consumption of similar messages processed on-chain. For users utilizing the Injective Python SDK, the message broadcaster can assist in this estimation.
The InjectiveLabs team is developing a new functionality to make the gas requirement for certain Exchange module messages fixed, thereby making gas calculation deterministic. The fixed gas values, which will be implemented, can already serve as a reliable approximation for manually calculating the gas requirement for a transaction.
Fixed gas requirement
The following table lists message types with a fixed gas requirement. Any message not mentioned will continue to have a non-deterministic gas requirement based on chain store interactions.
| Message type | Gas units |
|---|---|
MsgCreateDerivativeLimitOrderGas |
120000 |
MsgCreateDerivativeLimitPostOnlyOrderGas |
140000 |
MsgCreateDerivativeMarketOrderGas |
105000 |
MsgCancelDerivativeOrderGas |
70000 |
MsgCreateSpotLimitOrderGas |
100000 |
MsgCreateSpotLimitPostOnlyOrderGas |
120000 |
MsgCreateSpotMarketOrderGas |
50000 |
MsgCancelSpotOrderGas |
65000 |
MsgCreateBinaryOptionsLimitOrderGas |
120000 |
MsgCreateBinaryOptionsLimitPostOnlyOrderGas |
140000 |
MsgCreateBinaryOptionsMarketOrderGas |
105000 |
MsgCancelBinaryOptionsOrderGas |
70000 |
MsgDepositGas |
70000 |
MsgWithdrawGas |
70000 |
MsgSubaccountTransferGas |
70000 |
MsgExternalTransferGas |
70000 |
MsgIncreasePositionMarginGas |
70000 |
MsgDecreasePositionMarginGas |
70000 |
For the MsgBatchUpdateOrders the gas requirement will also be fixed. The gas requirement for each order action will match the individual order message actions as specified in the table. This applies similarly to the following batch messages:
MsgBatchCreateSpotLimitOrdersMsgBatchCancelSpotOrdersMsgBatchCreateDerivativeLimitOrdersMsgBatchCancelDerivativeOrdersMsgBatchCancelBinaryOptionsOrders
Market and Limit Order Examples
Adding a Spot Market Buy Order
Maker Fee = -0.01%Taker Fee = 0.1%Market Buy Order:
Quantity = 1,000 INJWorst Price = 5 USDT
→ The account's available balance is decremented by 5,000 USDT + Taker Fee = 5,005 USDT.
Upon matching with a resting sell order with price of 4 USDT the new account balances are calculated as:
Trading Fee = 1,000 * 4 * 0.001 = 4 USDTCredit Amount = 1,000 INJDebit Amount = 1,000 * 4 + 4 = 4,004 USDTClearing Refund = 5,005 - 4,004 = 1,001 USDT
Adding a Spot Market Sell Order
Maker Fee = -0.01%Taker Fee = 0.1%Market Sell Order:
Quantity = 1,000 INJWorst Price = 3 USDT
→ The account's available balance is decremented by 1,000 INJ.
Upon matching with a resting sell order with price of 4 USDT the new account balances are calculated as:
Trading Fee = 1,000 * 4 * 0.001 = 4 USDTDebit Amount = 1,000 INJCredit Amount = 1,000 * 4 - 4 = 3,996 USDTClearing Refund = 0
Adding a Spot Limit Buy Order
Maker Fee = -0.01%Taker Fee = 0.1%We initially assume taker fees for the limit order in case the limit order is matched immediately. With price and quantity of:
Quantity = 1,000 INJPrice = 5 USDT
→ The account's available balance is decremented by 5,000 USDT + Taker Fee = 5,005 USDT.
After the order is submitted:
- If Matched Immediately: the limit order is filled and no fees are refunded since taker fees were previously deducted from the account's available balance. However, if Post-Only was selected, then the order will not be matched and will be rejected, with the trading fees being refunded. Assuming a clearing price of 4 USDT and a non Post-Only order:
Trading Fee = 1,000 * 4 * 0.001 = 4 USDTCredit Amount = 5,000 - 4,000 = 1,000 INJDebit Amount = 1,000 * 4 + 4 = 4,004 USDTClearing Refund = 5,005 - 4,004 = 1,001 USDTUnmatched Fee Refund = 0 USDT
- If Entirely Unmatched, the order becomes a resting limit order and we refund the taker fee:
Fee Refund = 1,000 * 5 * (0.001) = 5 USDT
- If Filled Later by Market Order, a maker fee will be charged, or a rebate will be credited. Since the order is filled by Market Order, the clearing price will be the price set in the limit order:
Trading Fee Rebate = 1,000 * 5 * -0.0001 = 0.5 USDTCredit Amount = 1,000 INJ + 0.5 USDTDebit Amount (in quote asset) = 1,000 * 5 = 5,000 USDT
If Partially Matched Immediately: the portion of the limit order that is filled immediately is charged taker fees with the rest being charged/credited maker fees/rebates. Assuming half the order is matched at a clearing price of 4 USDT and half the order is filled by Market Order at 5 USDT:
- Portion Immediately Filled:
Taker Trading Fee = 500 * 4 * 0.001 = 2 USDTCredit Amount = 500 INJDebit Amount (Including Fees) = 500 * 4 + 2 = 2,002 USDTClearing Refund = (quantity of order filled * limit price) - (quantity of order filled * clearing price) + (proportional difference between limit fees and clearing fees)= (500 * 5) - (500 * 4) + ((500 * 5) * 0.001 - (500 * 4) * 0.001) = 500.5 USDTUnmatched Fee Refund = quantity of order unfilled * limit price * taker fee rate = 500 * 5 * 0.001 = 2.5 USDT
- Rest of Order Filled Later by Market Order:
Maker Trading Fee Rebate = 500 * 5 * -0.0001 = 0.25 USDTCredit Amount = 500 INJDebit Amount = 500 * 5 = 2,500 USDTClearing Refund = 0 USDT
- In Total:
Net Trading Fee = 2 - 0.25 = 1.75 USDTCredit Amount (Including Maker Fee Rebates) = 1000 INJ + 0.25 USDTDebit Amount (Including Taker Fees) = 4,502 USDT
- Portion Immediately Filled:
Adding a Spot Limit Sell Order
Maker Fee = -0.01%Taker Fee = 0.1%We initially assume taker fees for the limit order in case the limit order is matched immediately. With price and quantity of:
Quantity = 1,000 INJPrice = 3 USDT
→ The account's available balance is decremented by 1,000 INJ.
After the order is submitted:
- If Matched Immediately: the limit order is filled and taker fees are deducted, unless Post-Only is selected, in which case the order will not be matched and will be rejected with no trading fees being charged. Assuming a clearing price of 4 USDT:
Trading Fee = 1,000 * 4 * 0.001 = 4 USDTCredit Amount = 1,000 * 4 - 4 = 3,996 USDTDebit Amount = 1,000 INJClearing Refund = 0 ETHFee Refund/Rebate = 0 USDT
- If Filled Later by Market Order, a maker fee rebate will be credited. Since the order is filled by Market Order, the clearing price will be the price set in the limit order:
Maker Trading Rebate = 1,000 * 3 * 0.0001 = 0.3 USDTCredit Amount (in quote asset) = 1,000 * 3 + 0.3 = 3,000.3 USDTDebit Amount (in base asset) = 1,000 INJ
- If Partially Matched Immediately: the portion of the limit order that is filled immediately is charged taker fees with the rest being charged maker fees. Similar logic to a spot limit buy order applies.
Derivative Market Order Payouts
The payouts for derivative market orders work the same way as for derivative limit orders, with the one difference being they are cancelled if not immediately matched. See spot market and derivative limit orders as reference.
Adding a Derivative Limit Buy Order
Quantity = 1,000 INJ,Price = 5 USDT,Margin = 1,000 USDTTakerFeeRate = 0.001MakerFeeRate = -0.0001
→ The account's available balance is decremented by Margin + Taker Fee = 1000 + 5000 * 0.001 = 1005 USDT.
After creation:
If Unmatched, the order becomes a resting limit order (maker) and we refund the taker fee on vanilla orders (reduce-only orders don't pay upfront fees):
Fee Refund = 5 USDT
If Matched:
Assuming:
- a clearing price of 4 USDT
an existing
SHORTposition:Position Quantity = 600 INJPosition Entry Price = 4.5 USDTPosition Margin = 400 USDT
Would result in:
1. Closing existing position with proportional order margin for closing:
CloseExecutionMargin = ExecutionMargin * CloseQuantity / OrderQuantity = 1000 * 600 / 1000 = 600 USDT- Where
CloseExecutionMargin = Portion of margin used to close position - And
ExecutionMargin = Margin supplied in new order
- Where
ClosingPayout = PNL + PositionMargin * CloseQuantity / PositionQuantity + CloseExecutionMarginShort PNL = CloseQuantity * (EntryPrice - FillPrice) = 600 * (4.5 - 4) = 300 USDTClosingPayout = 300 + 400 * 600 / 600 + 600 = 1300 USDT
2. Opening new position in opposite direction:
a new
LONGposition:Position Quantity = 400 INJPosition Entry Price = 4 USDTNewPositionMargin = ExecutionMargin - CloseExecutionMargin = 1000 - 600 = 400 USDT
3. Refunding margin difference from order price vs. clearing price:
- Since the order was placed with 5x leverage at a price of 5 USDT, some margin is refunded with the new clearing price to maintain the 5x leverage.
Margin Refund = NewPositionMargin - NewPositionMarginRequired = 400 - 400 * 4 / 5 = 80 USDT
4. Refunding fee difference from order price vs. clearing price:
PriceDelta = Price - ClearingPrice = 5 - 4 = 1 USDTClearingFeeRefund = FillQuantity * PriceDelta * TakerFeeRate = 1000 * 1 * 0.001 = 1 USDT- In the case of matching a sell order, this would have been a charge, not a refund
Market Order Matching
Existing Orderbook
| Sells | Buys | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
New Orders
- 1x market buy order for 0.2 BTC with worst price 64,360
- 1x market buy order for 0.4 BTC with worst price 66,000
- 1x market sell order for 0.1 BTC with worst price 60,000
- 1x market sell order for 0.2 BTC with worst price 61,000
- 1x market sell order for 0.3 BTC with worst price 69,000
Resulting Orderbook
| Sells | Buys | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
Market Buys: Matching the highest priced market buy order first for 0.4 BTC. Now for the second market buy order only 0.1 BTC is left at matchable price, meaning the other 0.1 BTC in the order will be cancelled. Both orders will be matched with the single resting limit order at a price of 64,360 for a total quantity of 0.5 BTC.
Market Sells: Matching the first two market sell orders for at a matching price of (64,210*0.1 + 64,205*0.2) / 0.3 = 64,206.67 for a total quantity of 0.3 BTC. The resting limit orders are both matched at their specified price points of 64,210 and 64,205. Since the last market sell order of 69,000 cannot be fulfilled, it is cancelled.
Limit Order Matching
Existing Orderbook
| Sells | Buys | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
New Orders
| Sells | Buys | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
Matching Orders
All new orders are incorporated into the existing orderbook. In our case this results in a negative spread:
| Sells | Buys | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
As long as negative spread exists, orders are matched against each other. The first buy order is fully matched:
| Sells | Buys | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
Now the second buy order can still be fully matched:
| Sells | Buys | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
This is the end of the matching, since no more negative spread exists (64,220 > 62,210).
All orders will be matched with a uniform clearing price within the range of the last sell order price and the last buy order price.
- Last sell order price: 64,220
- Last buy order price: 64,360
- 64,220 >= Clearing price >= 64,360
Step 1: Check if clearing price range is out of bounds regarding the resting orderbook mid price.
- Resting orderbook mid price: (64,250+64,210)/2 = 64,230
- Is within range of clearing price ✅ (if not, a clearing price of either last buy or last sell price would be used)
Step 2: Check if clearing price range is out of bounds regarding the mark price.
- Let's assume mark price is 64,300
- Is within range of clearing price ✅ (if not, a clearing price of either last buy or last sell price would be used)
Step 3: Set clearing price = mid price or mark price for spot or perpetual markets, respectively, or in the case where these prices are out of bounds, use last buy or last sell price.
Resources
Here you can find a comprehensive overview of the exchange ecosystem on Injective, guides and general resources for developers and API traders.
Coin denoms and market IDs for testnet and mainnet can be found on the Injective testnet explorer and mainnet explorer under the Markets tab and Assets tab.
Explorer
A Web interface that allows you to search for information on the Injective Chain
Faucet
A web-based service that provides free tokens to users on testnet and allows them to experiment on the Injective Chain.
Status
Monitor the uptime of all public services.
Message Broadcaster
In the examples included in this documentation you will see all the steps required to interact with the chain, from deriving a public key from a private key, creating messages to query the chain or creating orders, to creating and broadcasting transactions to the chain. Before going to the examples of all the possible actions it is important to state that you can avoid implementing yourself all the steps to create and configure correctly a transaction. If you are not interested in defining all the low level aspects you can use the component called MsgBroadcasterWithPk. To use the broadcaster you just need to create an instance of MsgBroadcasterWithPk, and once all the messages to be included in the transaction have been created, use the broadcast method, passing the messages as a parameter. The broadcaster will take care of: - Calculate the gas fee to pay for the transaction - Create the transaction and configure it - Sign the transaction - Broadcast it to the chain
Broadcaster for standard account
Calculate gas fee simulating the transaction
Example - Calculate gas fee simulating the transaction:
import asyncio
import json
import os
import uuid
from decimal import Decimal
import dotenv
from pyinjective.async_client_v2 import AsyncClient
from pyinjective.core.broadcaster import MsgBroadcasterWithPk
from pyinjective.core.network import Network
from pyinjective.wallet import PrivateKey
async def main() -> None:
dotenv.load_dotenv()
private_key_in_hexa = os.getenv("INJECTIVE_PRIVATE_KEY")
# select network: local, testnet, mainnet
network = Network.testnet()
client = AsyncClient(network)
composer = await client.composer()
gas_price = await client.current_chain_gas_price()
# adjust gas price to make it valid even if it changes between the time it is requested and the TX is broadcasted
gas_price = int(gas_price * 1.1)
message_broadcaster = MsgBroadcasterWithPk.new_using_simulation(
network=network,
private_key=private_key_in_hexa,
gas_price=gas_price,
client=client,
composer=composer,
)
priv_key = PrivateKey.from_hex(private_key_in_hexa)
pub_key = priv_key.to_public_key()
address = pub_key.to_address()
subaccount_id = address.get_subaccount_id(index=0)
# prepare trade info
fee_recipient = "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r"
spot_market_id_create = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
spot_orders_to_create = [
composer.spot_order(
market_id=spot_market_id_create,
subaccount_id=subaccount_id,
fee_recipient=fee_recipient,
price=Decimal("3"),
quantity=Decimal("55"),
order_type="BUY",
cid=(str(uuid.uuid4())),
),
composer.spot_order(
market_id=spot_market_id_create,
subaccount_id=subaccount_id,
fee_recipient=fee_recipient,
price=Decimal("300"),
quantity=Decimal("55"),
order_type="SELL",
cid=str(uuid.uuid4()),
),
]
# prepare tx msg
msg = composer.msg_batch_update_orders(
sender=address.to_acc_bech32(),
spot_orders_to_create=spot_orders_to_create,
)
# broadcast the transaction
result = await message_broadcaster.broadcast([msg])
print("---Transaction Response---")
print(json.dumps(result, indent=2))
gas_price = await client.current_chain_gas_price()
# adjust gas price to make it valid even if it changes between the time it is requested and the TX is broadcasted
gas_price = int(gas_price * 1.1)
message_broadcaster.update_gas_price(gas_price=gas_price)
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
For the broadcaster to calculate the gas fee running the simulation, create an instance of MsgBroadcasterWithPk with the message new_using_simulation.
This is the most common broadcaster configuration. Unless you are using grantee accounts (delegated accounts with authz) you should use this one.
Calculate gas fee without simulation
Example - Calculate gas fee without simulation:
import asyncio
import json
import os
import uuid
from decimal import Decimal
import dotenv
from pyinjective.async_client_v2 import AsyncClient
from pyinjective.core.broadcaster import MsgBroadcasterWithPk
from pyinjective.core.network import Network
from pyinjective.wallet import PrivateKey
async def main() -> None:
dotenv.load_dotenv()
private_key_in_hexa = os.getenv("INJECTIVE_PRIVATE_KEY")
# select network: local, testnet, mainnet
network = Network.testnet()
client = AsyncClient(network)
composer = await client.composer()
await client.sync_timeout_height()
gas_price = await client.current_chain_gas_price()
# adjust gas price to make it valid even if it changes between the time it is requested and the TX is broadcasted
gas_price = int(gas_price * 1.1)
message_broadcaster = MsgBroadcasterWithPk.new_using_gas_heuristics(
network=network,
private_key=private_key_in_hexa,
gas_price=gas_price,
client=client,
composer=composer,
)
priv_key = PrivateKey.from_hex(private_key_in_hexa)
pub_key = priv_key.to_public_key()
address = pub_key.to_address()
subaccount_id = address.get_subaccount_id(index=0)
# prepare trade info
fee_recipient = "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r"
spot_market_id_create = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
spot_orders_to_create = [
composer.spot_order(
market_id=spot_market_id_create,
subaccount_id=subaccount_id,
fee_recipient=fee_recipient,
price=Decimal("3"),
quantity=Decimal("55"),
order_type="BUY",
cid=str(uuid.uuid4()),
),
composer.spot_order(
market_id=spot_market_id_create,
subaccount_id=subaccount_id,
fee_recipient=fee_recipient,
price=Decimal("300"),
quantity=Decimal("55"),
order_type="SELL",
cid=str(uuid.uuid4()),
),
]
# prepare tx msg
msg = composer.msg_batch_update_orders(
sender=address.to_acc_bech32(),
spot_orders_to_create=spot_orders_to_create,
)
# broadcast the transaction
result = await message_broadcaster.broadcast([msg])
print("---Transaction Response---")
print(json.dumps(result, indent=2))
gas_price = await client.current_chain_gas_price()
# adjust gas price to make it valid even if it changes between the time it is requested and the TX is broadcasted
gas_price = int(gas_price * 1.1)
message_broadcaster.update_gas_price(gas_price=gas_price)
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
For the broadcaster to calculate the gas fee based on the messages included without running the simulation, create an instance of MsgBroadcasterWithPk with the message new_without_simulation.
Broadcaster for grantee account
This is the required broadcaster configuration when operating with grantee accounts. The broadcaster will take care of creating the MsgExec message, so that the user keeps passing the same messages to the broadcast method that are passed when using the standard broadcaster with non-grantee accounts.
Calculate gas fee simulating the transaction
Example - Calculate gas fee simulating the transaction:
import asyncio
import json
import os
import uuid
from decimal import Decimal
import dotenv
from pyinjective.async_client_v2 import AsyncClient
from pyinjective.core.broadcaster import MsgBroadcasterWithPk
from pyinjective.core.network import Network
from pyinjective.wallet import Address, PrivateKey
async def main() -> None:
dotenv.load_dotenv()
private_key_in_hexa = os.getenv("INJECTIVE_GRANTEE_PRIVATE_KEY")
granter_inj_address = os.getenv("INJECTIVE_GRANTER_PUBLIC_ADDRESS")
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
composer = await client.composer()
await client.sync_timeout_height()
# load account
priv_key = PrivateKey.from_hex(private_key_in_hexa)
pub_key = priv_key.to_public_key()
address = pub_key.to_address()
gas_price = await client.current_chain_gas_price()
# adjust gas price to make it valid even if it changes between the time it is requested and the TX is broadcasted
gas_price = int(gas_price * 1.1)
message_broadcaster = MsgBroadcasterWithPk.new_for_grantee_account_using_simulation(
network=network,
grantee_private_key=private_key_in_hexa,
gas_price=gas_price,
client=client,
composer=composer,
)
# prepare tx msg
market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
granter_address = Address.from_acc_bech32(granter_inj_address)
granter_subaccount_id = granter_address.get_subaccount_id(index=0)
msg = composer.msg_create_spot_limit_order(
market_id=market_id,
sender=granter_inj_address,
subaccount_id=granter_subaccount_id,
fee_recipient=address.to_acc_bech32(),
price=Decimal("7.523"),
quantity=Decimal("0.01"),
order_type="BUY",
cid=str(uuid.uuid4()),
)
# broadcast the transaction
result = await message_broadcaster.broadcast([msg])
print("---Transaction Response---")
print(json.dumps(result, indent=2))
gas_price = await client.current_chain_gas_price()
# adjust gas price to make it valid even if it changes between the time it is requested and the TX is broadcasted
gas_price = int(gas_price * 1.1)
message_broadcaster.update_gas_price(gas_price=gas_price)
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
For the broadcaster to calculate the gas fee running the simulation, create an instance of MsgBroadcasterWithPk with the message new_for_grantee_account_using_simulation.
Calculate gas fee without simulation
Example - Calculate gas fee without simulation:
import asyncio
import json
import os
import uuid
from decimal import Decimal
import dotenv
from pyinjective.async_client_v2 import AsyncClient
from pyinjective.core.broadcaster import MsgBroadcasterWithPk
from pyinjective.core.network import Network
from pyinjective.wallet import Address, PrivateKey
async def main() -> None:
dotenv.load_dotenv()
private_key_in_hexa = os.getenv("INJECTIVE_GRANTEE_PRIVATE_KEY")
granter_inj_address = os.getenv("INJECTIVE_GRANTER_PUBLIC_ADDRESS")
# select network: local, testnet, mainnet
network = Network.testnet()
# initialize grpc client
client = AsyncClient(network)
composer = await client.composer()
# load account
priv_key = PrivateKey.from_hex(private_key_in_hexa)
pub_key = priv_key.to_public_key()
address = pub_key.to_address()
gas_price = await client.current_chain_gas_price()
# adjust gas price to make it valid even if it changes between the time it is requested and the TX is broadcasted
gas_price = int(gas_price * 1.1)
message_broadcaster = MsgBroadcasterWithPk.new_for_grantee_account_without_simulation(
network=network,
grantee_private_key=private_key_in_hexa,
gas_price=gas_price,
client=client,
composer=composer,
)
# prepare tx msg
market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
granter_address = Address.from_acc_bech32(granter_inj_address)
granter_subaccount_id = granter_address.get_subaccount_id(index=0)
msg = composer.msg_create_spot_limit_order(
market_id=market_id,
sender=granter_inj_address,
subaccount_id=granter_subaccount_id,
fee_recipient=address.to_acc_bech32(),
price=Decimal("7.523"),
quantity=Decimal("0.01"),
order_type="BUY",
cid=str(uuid.uuid4()),
)
# broadcast the transaction
result = await message_broadcaster.broadcast([msg])
print("---Transaction Response---")
print(json.dumps(result, indent=2))
gas_price = await client.current_chain_gas_price()
# adjust gas price to make it valid even if it changes between the time it is requested and the TX is broadcasted
gas_price = int(gas_price * 1.1)
message_broadcaster.update_gas_price(gas_price=gas_price)
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
For the broadcaster to calculate the gas fee based on the messages included without running the simulation, create an instance of MsgBroadcasterWithPk with the message new_for_grantee_account_without_simulation.
Fine-tunning message based gas fee estimation
As mentioned before the gas estimation without using simulation is implemented by using fixed values as gas cost for certain messages and actions. Since the real gas cost can differ at some point from the estimator calculations, the Python SDK allows the developer to fine-tune certain gas cost values in order to improve the gas cost estimation. In the next tables you can find the global values used for gas estimation calculations, that can be modified in your application:
Gas limit estimation based on gas heuristics
This is the estimator implemented with the class GasHeuristicsGasLimitEstimator.
| Module | Global Variable | Description |
|---|---|---|
pyinjective.core.gas_heuristics_gas_limit_estimator |
SPOT_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one spot limit order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
SPOT_MARKET_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one spot market order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
POST_ONLY_SPOT_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one post only spot limit order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
DERIVATIVE_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one derivative order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
DERIVATIVE_MARKET_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one derivative market order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
POST_ONLY_DERIVATIVE_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one post only derivative limit order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
BINARY_OPTIONS_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one binary options order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
BINARY_OPTIONS_MARKET_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one binary options market order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
POST_ONLY_BINARY_OPTIONS_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one post only binary options limit order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
SPOT_ORDER_CANCELATION_GAS_LIMIT |
The gas cost associated to the cancellation of one spot order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
DERIVATIVE_ORDER_CANCELATION_GAS_LIMIT |
The gas cost associated to the cancellation of one derivative order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
BINARY_OPTIONS_ORDER_CANCELATION_GAS_LIMIT |
The gas cost associated to the cancellation of one binary options order |
pyinjective.core.gas_heuristics_gas_limit_estimator |
DEPOSIT_GAS_LIMIT |
The gas cost associated to a deposit into a subaccount |
pyinjective.core.gas_heuristics_gas_limit_estimator |
WITHDRAW_GAS_LIMIT |
The gas cost associated to a withdrawal from a subaccount |
pyinjective.core.gas_heuristics_gas_limit_estimator |
SUBACCOUNT_TRANSFER_GAS_LIMIT |
The gas cost associated to a funds transfer between subaccounts of the same address |
pyinjective.core.gas_heuristics_gas_limit_estimator |
EXTERNAL_TRANSFER_GAS_LIMIT |
The gas cost associated to a funds transfer to a subaccount from a different address |
pyinjective.core.gas_heuristics_gas_limit_estimator |
INCREASE_POSITION_MARGIN_TRANSFER_GAS_LIMIT |
The gas cost associated to increasing a position's margin |
pyinjective.core.gas_heuristics_gas_limit_estimator |
DECREASE_POSITION_MARGIN_TRANSFER_GAS_LIMIT |
The gas cost associated to decreasing a position's margin |
| Module | Class | Global Variable | Description |
|---|---|---|---|
pyinjective.core.broadcaster.py |
MessageBasedTransactionFeeCalculator |
TRANSACTION_GAS_LIMIT |
The gas cost associated to the TX processing |
pyinjective.core.gas_heuristics_gas_limit_estimator |
GasHeuristicsGasLimitEstimator |
GENERAL_MESSAGE_GAS_LIMIT |
Generic base gas cost for any message |
pyinjective.core.gas_heuristics_gas_limit_estimator |
BatchUpdateOrdersGasLimitEstimator |
AVERAGE_CANCEL_ALL_AFFECTED_ORDERS |
This global represents the expected number of orders to be cancelled when executing a "cancel all orders for a market" action |
pyinjective.core.gas_heuristics_gas_limit_estimator |
ExecGasLimitEstimator |
DEFAULT_GAS_LIMIT |
Estimation of the general gas amount required for a MsgExec (for the general message processing) |
Gas limit estimation based on chain statistics
This is the estimator implemented with the class GasLimitEstimator, and it is the original implementation for gas estimation without simulations. It has been replaced now by the GasHeuristicsGasLimitEstimator, but the user can still use it.
| Module | Global Variable | Description |
|---|---|---|
pyinjective.core.gas_limit_estimator |
SPOT_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one spot order |
pyinjective.core.gas_limit_estimator |
DERIVATIVE_ORDER_CREATION_GAS_LIMIT |
The gas cost associated to the creation of one derivative order |
pyinjective.core.gas_limit_estimator |
SPOT_ORDER_CANCELATION_GAS_LIMIT |
The gas cost associated to the cancellation of one spot order |
pyinjective.core.gas_limit_estimator |
DERIVATIVE_ORDER_CANCELATION_GAS_LIMIT |
The gas cost associated to the cancellation of one derivative order |
pyinjective.core.gas_limit_estimator |
SPOT_POST_ONLY_ORDER_MULTIPLIER |
Multiplier to increase the gas cost for post only spot orders (in addition to the normal spot order cost) |
pyinjective.core.gas_limit_estimator |
DERIVATIVE_POST_ONLY_ORDER_MULTIPLIER |
Multiplier to increase the gas cost for post only derivative orders (in addition to the normal derivative order cost) |
| Class | Global Variable | Description |
|---|---|---|
MessageBasedTransactionFeeCalculator |
TRANSACTION_GAS_LIMIT |
The gas cost associated to the TX processing |
GasLimitEstimator |
GENERAL_MESSAGE_GAS_LIMIT |
Generic base gas cost for any message |
GasLimitEstimator |
BASIC_REFERENCE_GAS_LIMIT |
Base gas cost for messages not related to orders. Each type of message will calculate its cost multiplying this reference cost by a multiplier |
DefaultGasLimitEstimator |
DEFAULT_GAS_LIMIT |
The gas cost for all messages for which there is no especial estimator implemented |
BatchUpdateOrdersGasLimitEstimator |
CANCEL_ALL_SPOT_MARKET_GAS_LIMIT |
This is an estimation of the gas cost per spot order cancel when cancelling all orders for a spot market |
BatchUpdateOrdersGasLimitEstimator |
CANCEL_ALL_DERIVATIVE_MARKET_GAS_LIMIT |
This is an estimation of the gas cost per derivative order cancel when cancelling all orders for a derivative market |
BatchUpdateOrdersGasLimitEstimator |
MESSAGE_GAS_LIMIT |
Estimation of the general gas amount required for a MsgBatchUpdateOrders (not for particular actions, but for the general message processing) |
BatchUpdateOrdersGasLimitEstimator |
AVERAGE_CANCEL_ALL_AFFECTED_ORDERS |
This global represents the expected number of orders to be cancelled when executing a "cancel all orders for a market" action |
ExecGasLimitEstimator |
DEFAULT_GAS_LIMIT |
Estimation of the general gas amount required for a MsgExec (for the general message processing) |
GenericExchangeGasLimitEstimator |
BASIC_REFERENCE_GAS_LIMIT |
Base gas cost for messages not related to orders. Each type of message will calculate its cost multiplying this reference cost by a multiplier |
Indexer API
The Indexer API is read-only whereas the Chain API is write and also includes a limited set of API requests to read data. The Chain API reads query the blockchain state from the node directly as opposed to the Indexer API which reconstructs state from events emitted by chain.
On a high-level the end-user trading applications and Injective Products use the Indexer API to read data and the Chain API to write data to the blockchain. Even though it’s possible to develop trading applications using the Chain API only, the Indexer API includes more methods, streaming support, gRPC, and also allows you to fetch historical data (the Chain API queries the blockchain state which doesn’t include historical records).
- InjectiveAccountsRPC
InjectiveAccountsRPC defines the gRPC API of the Exchange Accounts provider.
SubaccountsList
Get a list of subaccounts for a specific address.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
account_address = "inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt"
subacc_list = await client.fetch_subaccounts_list(account_address)
print(json.dumps(subacc_list, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
accountAddress := "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku"
res, err := exchangeClient.GetSubaccountsList(ctx, accountAddress)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| account_address | string | Account address, the subaccounts owner | Yes |
Response Parameters
Response Example:
{
"subaccounts":[
"0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001",
"0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000002",
"0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000000"
]
}
{
"subaccounts": [
"0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001",
"0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000002"
]
}
| Parameter | Type | Description |
|---|---|---|
| subaccounts | string array |
SubaccountHistory
Get the subaccount's transfer history.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.client.model.pagination import PaginationOption
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
subaccount = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
denom = "inj"
transfer_types = ["withdraw", "deposit"]
skip = 1
limit = 15
end_time = 1665118340224
pagination = PaginationOption(skip=skip, limit=limit, end_time=end_time)
subacc_history = await client.fetch_subaccount_history(
subaccount_id=subaccount,
denom=denom,
transfer_types=transfer_types,
pagination=pagination,
)
print(json.dumps(subacc_history, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
accountPB "github.com/InjectiveLabs/sdk-go/exchange/accounts_rpc/pb"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
denom := "inj"
subaccountId := "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
transferTypes := []string{"deposit"}
skip := uint64(0)
limit := int32(10)
req := accountPB.SubaccountHistoryRequest{
Denom: denom,
SubaccountId: subaccountId,
TransferTypes: transferTypes,
Skip: skip,
Limit: limit,
}
res, err := exchangeClient.GetSubaccountHistory(ctx, &req)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| subaccount_id | string | SubaccountId of the trader we want to get the history from | Yes |
| denom | string | Filter history by denom | Yes |
| transfer_types | string array | Filter history by transfer type | Yes |
| skip | uint64 | Skip will skip the first n item from the result | Yes |
| limit | int32 | Limit is used to specify the maximum number of items to be returned | Yes |
| end_time | int64 | Upper bound of account transfer history's executedAt | Yes |
Response Parameters
Response Example:
{
"transfers":[
{
"transferType":"deposit",
"srcAccountAddress":"inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"dstSubaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"amount":{
"denom":"inj",
"amount":"2000000000000000000"
},
"executedAt":"1665117493543",
"srcSubaccountId":"",
"dstAccountAddress":""
},
{
"transferType":"deposit",
"srcAccountAddress":"inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"dstSubaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"amount":{
"denom":"inj",
"amount":"15000000000000000000"
},
"executedAt":"1660313668990",
"srcSubaccountId":"",
"dstAccountAddress":""
}
],
"paging":{
"total":"3",
"from":0,
"to":0,
"countBySubaccount":"0",
"next":[
]
}
}
{
"transfers": [
{
"transfer_type": "deposit",
"src_account_address": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"dst_subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"amount": {
"denom": "inj",
"amount": "50000000000000000000"
},
"executed_at": 1651492257605
},
{
"transfer_type": "deposit",
"src_account_address": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"dst_subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"amount": {
"denom": "inj",
"amount": "1000000000000000000"
},
"executed_at": 1652453978939
}
],
"paging": [
{
"total": 3
}
]
}
| Parameter | Type | Description |
|---|---|---|
| transfers | SubaccountBalanceTransfer array | List of subaccount transfers |
| paging | Paging |
SubaccountBalanceTransfer
| Parameter | Type | Description |
|---|---|---|
| transfer_type | string | Type of the subaccount balance transfer |
| src_subaccount_id | string | Subaccount ID of the sending side |
| src_account_address | string | Account address of the sending side |
| dst_subaccount_id | string | Subaccount ID of the receiving side |
| dst_account_address | string | Account address of the receiving side |
| amount | CosmosCoin | Coin amount of the transfer |
| executed_at | int64 | Timestamp of the transfer in UNIX millis |
CosmosCoin
| Parameter | Type | Description |
|---|---|---|
| denom | string | Coin denominator |
| amount | string | Coin amount (big int) |
Paging
| Parameter | Type | Description |
|---|---|---|
| total | int64 | total number of txs saved in database |
| from | int32 | can be either block height or index num |
| to | int32 | can be either block height or index num |
| count_by_subaccount | int64 | count entries by subaccount, serving some places on helix |
| next | string array | array of tokens to navigate to the next pages |
SubaccountBalance
Get the balance of a subaccount for a specific denom.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
subaccount_id = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
denom = "inj"
balance = await client.fetch_subaccount_balance(subaccount_id=subaccount_id, denom=denom)
print(json.dumps(balance, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
)
func main() {
network := common.LoadNetwork("mainnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
subaccountId := "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
denom := "inj"
res, err := exchangeClient.GetSubaccountBalance(ctx, subaccountId, denom)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| subaccount_id | string | SubaccountId of the trader we want to get the trades from | Yes |
| denom | string | Specify denom to get balance | Yes |
Response Parameters
Response Example:
{
"balance":{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"accountAddress":"inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom":"inj",
"deposit":{
"totalBalance":"0",
"availableBalance":"0"
}
}
}
{
"balance": {
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"account_address": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom": "inj",
"deposit": {
"total_balance": "1492235700000000000000",
"available_balance": "1492235700000000000000"
}
}
}
| Parameter | Type | Description |
|---|---|---|
| balance | SubaccountBalance | Subaccount balance |
SubaccountBalance
| Parameter | Type | Description |
|---|---|---|
| subaccount_id | string | Related subaccount ID |
| account_address | string | Account address, owner of this subaccount |
| denom | string | Coin denom on the chain. |
| deposit | SubaccountDeposit |
SubaccountDeposit
| Parameter | Type | Description |
|---|---|---|
| total_balance | string | |
| available_balance | string | |
| total_balance_usd | string | |
| available_balance_usd | string |
SubaccountBalancesList
List the subaccount's balances for all denoms.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
subaccount = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
denoms = ["inj", "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"]
subacc_balances_list = await client.fetch_subaccount_balances_list(subaccount_id=subaccount, denoms=denoms)
print(json.dumps(subacc_balances_list, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
subaccountId := "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
res, err := exchangeClient.GetSubaccountBalancesList(ctx, subaccountId)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| subaccount_id | string | SubaccountId of the trader we want to get the trades from | Yes |
| denoms | string array | Filter balances by denoms. If not set, the balances of all the denoms for the subaccount are provided. | Yes |
Response Parameters
Response Example:
{
"balances":[
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"accountAddress":"inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom":"peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5",
"deposit":{
"totalBalance":"131721505.337958346262317217",
"availableBalance":"0.337958346262317217"
}
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"accountAddress":"inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom":"inj",
"deposit":{
"totalBalance":"0",
"availableBalance":"0"
}
}
]
}
{
"balances": [
{
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"account_address": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"deposit": {
"total_balance": "200501904612800.13082016560359584",
"available_balance": "200358014975479.130820165603595295"
}
},
{
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"account_address": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom": "inj",
"deposit": {
"total_balance": "53790000010000000003",
"available_balance": "52790000010000000003"
}
},
{
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"account_address": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom": "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9",
"deposit": {
"total_balance": "1000000",
"available_balance": "1000000"
}
}
]
}
| Parameter | Type | Description |
|---|---|---|
| balances | SubaccountBalance array | List of subaccount balances |
SubaccountBalance
| Parameter | Type | Description |
|---|---|---|
| subaccount_id | string | Related subaccount ID |
| account_address | string | Account address, owner of this subaccount |
| denom | string | Coin denom on the chain. |
| deposit | SubaccountDeposit |
SubaccountDeposit
| Parameter | Type | Description |
|---|---|---|
| total_balance | string | |
| available_balance | string | |
| total_balance_usd | string | |
| available_balance_usd | string |
SubaccountOrderSummary
Get a summary of the subaccount's active/unfilled orders.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
subaccount = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
order_direction = "buy"
market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"
subacc_order_summary = await client.fetch_subaccount_order_summary(
subaccount_id=subaccount, order_direction=order_direction, market_id=market_id
)
print(json.dumps(subacc_order_summary, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
accountPB "github.com/InjectiveLabs/sdk-go/exchange/accounts_rpc/pb"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketId := "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"
subaccountId := "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
orderDirection := "buy"
req := accountPB.SubaccountOrderSummaryRequest{
MarketId: marketId,
SubaccountId: subaccountId,
OrderDirection: orderDirection,
}
res, err := exchangeClient.GetSubaccountOrderSummary(ctx, &req)
if err != nil {
fmt.Println(err)
}
fmt.Println("spot orders:", res.SpotOrdersTotal)
fmt.Println("derivative orders:", res.DerivativeOrdersTotal)
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| subaccount_id | string | SubaccountId of the trader we want to get the summary from | Yes |
| market_id | string | MarketId is limiting order summary to specific market only | Yes |
| order_direction | string | Filter by direction of the orders | Yes |
Response Parameters
Response Example:
{
"derivativeOrdersTotal":"1",
"spotOrdersTotal":"0"
}
spot orders: 1
derivative orders: 7
| Parameter | Type | Description |
|---|---|---|
| spot_orders_total | int64 | Total count of subaccount's spot orders in given market and direction |
| derivative_orders_total | int64 | Total count of subaccount's derivative orders in given market and direction |
StreamSubaccountBalance
Stream the subaccount's balance for all denoms.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
from typing import Any, Dict
from grpc import RpcError
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def balance_event_processor(event: Dict[str, Any]):
print(event)
def stream_error_processor(exception: RpcError):
print(f"There was an error listening to balance updates ({exception})")
def stream_closed_processor():
print("The balance updates stream has been closed")
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001"
denoms = ["inj", "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"]
task = asyncio.get_event_loop().create_task(
client.listen_subaccount_balance_updates(
subaccount_id=subaccount_id,
callback=balance_event_processor,
on_end_callback=stream_closed_processor,
on_status_callback=stream_error_processor,
denoms=denoms,
)
)
await asyncio.sleep(delay=60)
task.cancel()
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
)
func main() {
// network := common.LoadNetwork("mainnet", "k8s")
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
subaccountId := "0x1b99514e320ae0087be7f87b1e3057853c43b799000000000000000000000000"
stream, err := exchangeClient.StreamSubaccountBalance(ctx, subaccountId)
if err != nil {
panic(err)
}
for {
select {
case <-ctx.Done():
return
default:
res, err := stream.Recv()
if err != nil {
fmt.Println(err)
return
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
}
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| subaccount_id | string | SubaccountId of the trader we want to get the trades from | Yes |
| denoms | string array | Filter balances by denoms. If not set, the balances of all the denoms for the subaccount are provided. | Yes |
Response Parameters
Streaming Response Example:
{
"balance": {
"subaccountId": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"accountAddress": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"deposit": {
"totalBalance": "200493439765890.695319283887814576",
"availableBalance": "200493414240390.695319283887814031"
}
},
"timestamp": 1654234765000
}
{
"balance": {
"subaccountId": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"accountAddress": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"deposit": {
"totalBalance": "200493847328858.695319283887814576",
"availableBalance": "200493821803358.695319283887814031"
}
},
"timestamp": 1654234804000
}
{
"balance": {
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"account_address": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"deposit": {
"total_balance": "200503979400874.28368413692326264",
"available_balance": "200360046875708.283684136923262095"
}
},
"timestamp": 1653037703000
}{
"balance": {
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"account_address": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"deposit": {
"total_balance": "200503560511302.28368413692326264",
"available_balance": "200359627986136.283684136923262095"
}
},
"timestamp": 1653037744000
}
| Parameter | Type | Description |
|---|---|---|
| balance | SubaccountBalance | Subaccount balance |
| timestamp | int64 | Operation timestamp in UNIX millis. |
SubaccountBalance
| Parameter | Type | Description |
|---|---|---|
| subaccount_id | string | Related subaccount ID |
| account_address | string | Account address, owner of this subaccount |
| denom | string | Coin denom on the chain. |
| deposit | SubaccountDeposit |
SubaccountDeposit
| Parameter | Type | Description |
|---|---|---|
| total_balance | string | |
| available_balance | string | |
| total_balance_usd | string | |
| available_balance_usd | string |
OrderStates
Get orders with an order hash. This request will return market orders and limit orders in all states [booked, partial_filled, filled, canceled]. For filled and canceled orders, there is a TTL of 3 minutes. Should your order be filled or canceled you will still be able to fetch it for 3 minutes.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
spot_order_hashes = [
"0xce0d9b701f77cd6ddfda5dd3a4fe7b2d53ba83e5d6c054fb2e9e886200b7b7bb",
"0x2e2245b5431638d76c6e0cc6268970418a1b1b7df60a8e94b8cf37eae6105542",
]
derivative_order_hashes = [
"0x82113f3998999bdc3892feaab2c4e53ba06c5fe887a2d5f9763397240f24da50",
"0xbb1f036001378cecb5fff1cc69303919985b5bf058c32f37d5aaf9b804c07a06",
]
orders = await client.fetch_order_states(
spot_order_hashes=spot_order_hashes, derivative_order_hashes=derivative_order_hashes
)
print(json.dumps(orders, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
accountPB "github.com/InjectiveLabs/sdk-go/exchange/accounts_rpc/pb"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
spotOrderHashes := []string{"0x0b156df549747187210ca5381f0291f179d76d613d0bae1a3c4fd2e3c0504b7c"}
derivativeOrderHashes := []string{"0x82113f3998999bdc3892feaab2c4e53ba06c5fe887a2d5f9763397240f24da50"}
req := accountPB.OrderStatesRequest{
SpotOrderHashes: spotOrderHashes,
DerivativeOrderHashes: derivativeOrderHashes,
}
res, err := exchangeClient.GetOrderStates(ctx, &req)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| spot_order_hashes | string array | Yes | |
| derivative_order_hashes | string array | Yes |
Response Parameters
Response Example:
{
"spotOrderStates": [
{
"orderHash": "0xb7b556d6eab10c4c185a660be44757a8a6715fb16db39708f2f76d9ce5ae8617",
"subaccountId": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"marketId": "0x0511ddc4e6586f3bfe1acb2dd905f8b8a82c97e1edaef654b12ca7e6031ca0fa",
"orderType": "limit",
"orderSide": "buy",
"state": "booked",
"quantityFilled": "0",
"quantityRemaining": "1000000",
"createdAt": 1654080262300,
"updatedAt": 1654080262300
}
],
"derivativeOrderStates": [
{
"orderHash": "0x4228f9a56a5bb50de4ceadc64df694c77e7752d58b71a7c557a27ec10e1a094e",
"subaccountId": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"marketId": "0x1c79dac019f73e4060494ab1b4fcba734350656d6fc4d474f6a238c13c6f9ced",
"orderType": "limit",
"orderSide": "buy",
"state": "booked",
"quantityFilled": "0",
"quantityRemaining": "1",
"createdAt": 1654235059957,
"updatedAt": 1654235059957
}
]
}
{
"spot_order_states": [
{
"order_hash": "0xb7b556d6eab10c4c185a660be44757a8a6715fb16db39708f2f76d9ce5ae8617",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"market_id": "0x0511ddc4e6586f3bfe1acb2dd905f8b8a82c97e1edaef654b12ca7e6031ca0fa",
"order_type": "limit",
"order_side": "buy",
"state": "booked",
"quantity_filled": "0",
"quantity_remaining": "1000000",
"created_at": 1654080262300,
"updated_at": 1654080262300
}
],
"derivative_order_states": [
{
"order_hash": "0x4228f9a56a5bb50de4ceadc64df694c77e7752d58b71a7c557a27ec10e1a094e",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"market_id": "0x1c79dac019f73e4060494ab1b4fcba734350656d6fc4d474f6a238c13c6f9ced",
"order_type": "limit",
"order_side": "buy",
"state": "booked",
"quantity_filled": "0",
"quantity_remaining": "1",
"created_at": 1654235059957,
"updated_at": 1654235059957
}
]
}
| Parameter | Type | Description |
|---|---|---|
| spot_order_states | OrderStateRecord array | List of the spot order state records |
| derivative_order_states | OrderStateRecord array | List of the derivative order state records |
OrderStateRecord
| Parameter | Type | Description |
|---|---|---|
| order_hash | string | Hash of the order |
| subaccount_id | string | The subaccountId that this order belongs to |
| market_id | string | The Market ID of the order |
| order_type | string | The type of the order |
| order_side | string | The side of the order |
| state | string | The state (status) of the order |
| quantity_filled | string | The filled quantity of the order |
| quantity_remaining | string | The filled quantity of the order |
| created_at | int64 | Order committed timestamp in UNIX millis. |
| updated_at | int64 | Order updated timestamp in UNIX millis. |
| price | string | Order prices |
| margin | string | Margin for derivative order |
Portfolio
Get an overview of your portfolio.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku"
portfolio = await client.fetch_portfolio(account_address=account_address)
print(json.dumps(portfolio, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
accountAddress := "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku"
res, err := exchangeClient.GetPortfolio(ctx, accountAddress)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| account_address | string | Account address | Yes |
Response Parameters
Response Example:
{
"portfolio":{
"portfolioValue":"6229.040631548905238875",
"availableBalance":"92.4500010811984646",
"lockedBalance":"13218.3573583009093604",
"unrealizedPnl":"-7081.766727833202586125",
"subaccounts":[
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000002",
"availableBalance":"0",
"lockedBalance":"0",
"unrealizedPnl":"0"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000006",
"availableBalance":"0",
"lockedBalance":"0",
"unrealizedPnl":"0"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000008",
"availableBalance":"0",
"lockedBalance":"0",
"unrealizedPnl":"0"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000009",
"availableBalance":"0",
"lockedBalance":"0",
"unrealizedPnl":"0"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f971347490200000061746f6d2d75736474",
"availableBalance":"0.00000066622556",
"lockedBalance":"0",
"unrealizedPnl":"0"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"availableBalance":"0.0000003382963046",
"lockedBalance":"13218.3573583009093604",
"unrealizedPnl":"-7081.766727833202586125"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f971347490200000000696e6a2d75736474",
"availableBalance":"0.0000000766766",
"lockedBalance":"0",
"unrealizedPnl":"0"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000001",
"availableBalance":"92.45",
"lockedBalance":"0",
"unrealizedPnl":"0"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000003",
"availableBalance":"0",
"lockedBalance":"0",
"unrealizedPnl":"0"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000007",
"availableBalance":"0",
"lockedBalance":"0",
"unrealizedPnl":"0"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000004",
"availableBalance":"0",
"lockedBalance":"0",
"unrealizedPnl":"0"
},
{
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000005",
"availableBalance":"0",
"lockedBalance":"0",
"unrealizedPnl":"0"
}
]
}
}
{
"portfolio": {
"portfolio_value": "16961.63886335580191347385",
"available_balance": "10127.8309908372442029",
"locked_balance": "8192.6038127728038576",
"unrealized_pnl": "-1358.79594025424614702615",
"subaccounts": [
{
"subaccount_id": "0x792bb0b9001d71a8efcb3c026ba4e34608a68a8c000000000000000000000000",
"available_balance": "10127.8309908372442029",
"locked_balance": "8192.6038127728038576",
"unrealized_pnl": "-1358.79594025424614702615"
}
]
}
}
| Parameter | Type | Description |
|---|---|---|
| portfolio | AccountPortfolio | The portfolio of this account |
AccountPortfolio
| Parameter | Type | Description |
|---|---|---|
| portfolio_value | string | The account's portfolio value in USD. |
| available_balance | string | The account's available balance value in USD. |
| locked_balance | string | The account's locked balance value in USD. |
| unrealized_pnl | string | The account's total unrealized PnL value in USD. |
| subaccounts | SubaccountPortfolio array | List of all subaccounts' portfolio |
SubaccountPortfolio
| Parameter | Type | Description |
|---|---|---|
| subaccount_id | string | The ID of this subaccount |
| available_balance | string | The subaccount's available balance value in USD. |
| locked_balance | string | The subaccount's locked balance value in USD. |
| unrealized_pnl | string | The subaccount's total unrealized PnL value in USD. |
Rewards
Get the rewards for Trade & Earn, the request will fetch all addresses for the latest epoch (-1) by default.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku"
epoch = -1
rewards = await client.fetch_rewards(account_address=account_address, epoch=epoch)
print(json.dumps(rewards, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
accountPB "github.com/InjectiveLabs/sdk-go/exchange/accounts_rpc/pb"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
accountAddress := "inj1rwv4zn3jptsqs7l8lpa3uvzhs57y8duemete9e"
epoch := int64(1)
req := accountPB.RewardsRequest{
Epoch: epoch,
AccountAddress: accountAddress,
}
res, err := exchangeClient.GetRewards(ctx, &req)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| epoch | int64 | The distribution epoch sequence number. -1 for latest. | Yes |
| account_address | string | Account address for the rewards distribution | Yes |
Response Parameters
Response Example:
{
"rewards":[
{
"accountAddress":"inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku",
"rewards":[
{
"denom":"inj",
"amount":"11169382212463849"
}
],
"distributedAt":"1672218001897"
}
]
}
{
"rewards": [
{
"account_address": "inj1rwv4zn3jptsqs7l8lpa3uvzhs57y8duemete9e",
"rewards": [
{
"denom": "inj",
"amount": "755104058929571177652"
}
],
"distributed_at": 1642582800716
}
]
}
| Parameter | Type | Description |
|---|---|---|
| rewards | Reward array | The trading rewards distributed |
Reward
| Parameter | Type | Description |
|---|---|---|
| account_address | string | Account address |
| rewards | Coin array | Reward coins distributed |
| distributed_at | int64 | Rewards distribution timestamp in UNIX millis. |
Coin
| Parameter | Type | Description |
|---|---|---|
| denom | string | Denom of the coin |
| amount | string | |
| usd_value | string |
- InjectiveSpotExchangeRPC
InjectiveSpotExchangeRPC defines the gRPC API of the Spot Exchange provider.
Market
Get details of a single spot market.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
market = await client.fetch_spot_market(market_id=market_id)
print(json.dumps(market, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
)
func main() {
// network := common.LoadNetwork("mainnet", "k8s")
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketId := "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"
res, err := exchangeClient.GetSpotMarket(ctx, marketId)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| market_id | string | MarketId of the market we want to fetch | Yes |
Response Parameters
Response Example:
{
"market":{
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"marketStatus":"active",
"ticker":"INJ/USDT",
"baseDenom":"inj",
"baseTokenMeta":{
"name":"Injective Protocol",
"address":"0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30",
"symbol":"INJ",
"logo":"https://static.alchemyapi.io/images/assets/7226.png",
"decimals":18,
"updatedAt":"1683119359318"
},
"quoteDenom":"peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5",
"quoteTokenMeta":{
"name":"Testnet Tether USDT",
"address":"0x0000000000000000000000000000000000000000",
"symbol":"USDT",
"logo":"https://static.alchemyapi.io/images/assets/825.png",
"decimals":6,
"updatedAt":"1683119359320"
},
"makerFeeRate":"-0.0001",
"takerFeeRate":"0.001",
"serviceProviderFee":"0.4",
"minPriceTickSize":"0.000000000000001",
"minQuantityTickSize":"1000000000000000",
"minNotional": "1000000"
}
}
{
"market": {
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"market_status": "active",
"ticker": "INJ/USDT",
"base_denom": "inj",
"base_token_meta": {
"name": "Injective Protocol",
"address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30",
"symbol": "INJ",
"logo": "https://static.alchemyapi.io/images/assets/7226.png",
"decimals": 18,
"updated_at": 1650978921934
},
"quote_denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"maker_fee_rate": "0.001",
"taker_fee_rate": "0.002",
"service_provider_fee": "0.4",
"min_price_tick_size": "0.000000000000001",
"min_quantity_tick_size": "1000000000000000",
"min_notional": "1000000"
}
}
| Parameter | Type | Description |
|---|---|---|
| market | SpotMarketInfo | Info about particular spot market |
SpotMarketInfo
| Parameter | Type | Description |
|---|---|---|
| market_id | string | SpotMarket ID is keccak265(baseDenom || quoteDenom) |
| market_status | string | The status of the market |
| ticker | string | A name of the pair in format AAA/BBB, where AAA is base asset, BBB is quote asset. |
| base_denom | string | Coin denom used for the base asset. |
| base_token_meta | TokenMeta | Token metadata for base asset |
| quote_denom | string | Coin denom used for the quote asset. |
| quote_token_meta | TokenMeta | Token metadata for quote asset |
| maker_fee_rate | string | Defines the fee percentage makers pay when trading (in quote asset) |
| taker_fee_rate | string | Defines the fee percentage takers pay when trading (in quote asset) |
| service_provider_fee | string | Percentage of the transaction fee shared with the service provider |
| min_price_tick_size | string | Defines the minimum required tick size for the order's price |
| min_quantity_tick_size | string | Defines the minimum required tick size for the order's quantity |
| min_notional | string | Minimum notional value for the market |
TokenMeta
| Parameter | Type | Description |
|---|---|---|
| name | string | Token full name |
| address | string | Token contract address (native or not) |
| symbol | string | Token symbol short name |
| logo | string | URL to the logo image |
| decimals | int32 | Token decimals |
| updated_at | int64 | Token metadata fetched timestamp in UNIX millis. |
Markets
Get a list of spot markets.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
market_status = "active"
base_denom = "inj"
quote_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"
market = await client.fetch_spot_markets(
market_statuses=[market_status], base_denom=base_denom, quote_denom=quote_denom
)
print(json.dumps(market, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
spotExchangePB "github.com/InjectiveLabs/sdk-go/exchange/spot_exchange_rpc/pb"
)
func main() {
// network := common.LoadNetwork("mainnet", "k8s")
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketStatus := "active"
quoteDenom := "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7"
req := spotExchangePB.MarketsRequest{
MarketStatus: marketStatus,
QuoteDenom: quoteDenom,
}
res, err := exchangeClient.GetSpotMarkets(ctx, &req)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| market_status | string | Filter by market status | Yes |
| base_denom | string | Filter by the Coin denomination of the base currency | Yes |
| quote_denom | string | Filter by the Coin denomination of the quote currency | Yes |
| market_statuses | string array | Yes |
Response Parameters
Response Example:
{
"markets":[
{
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"marketStatus":"active",
"ticker":"INJ/USDT",
"baseDenom":"inj",
"baseTokenMeta":{
"name":"Injective Protocol",
"address":"0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30",
"symbol":"INJ",
"logo":"https://static.alchemyapi.io/images/assets/7226.png",
"decimals":18,
"updatedAt":"1683119359318"
},
"quoteDenom":"peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5",
"quoteTokenMeta":{
"name":"Testnet Tether USDT",
"address":"0x0000000000000000000000000000000000000000",
"symbol":"USDT",
"logo":"https://static.alchemyapi.io/images/assets/825.png",
"decimals":6,
"updatedAt":"1683119359320"
},
"makerFeeRate":"-0.0001",
"takerFeeRate":"0.001",
"serviceProviderFee":"0.4",
"minPriceTickSize":"0.000000000000001",
"minQuantityTickSize":"1000000000000000",
"minNotional":"1000000"
}
]
}
{
"markets": [
{
"market_id": "0x01edfab47f124748dc89998eb33144af734484ba07099014594321729a0ca16b",
"market_status": "active",
"ticker": "AAVE/USDT",
"base_denom": "peggy0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9",
"base_token_meta": {
"name": "Aave",
"address": "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9",
"symbol": "AAVE",
"logo": "https://static.alchemyapi.io/images/assets/7278.png",
"decimals": 18,
"updated_at": 1650978921846
},
"quote_denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"maker_fee_rate": "0.001",
"taker_fee_rate": "0.002",
"service_provider_fee": "0.4",
"min_price_tick_size": "0.000000000000001",
"min_quantity_tick_size": "1000000000000000"
},
{
"market_id": "0xe8bf0467208c24209c1cf0fd64833fa43eb6e8035869f9d043dbff815ab76d01",
"market_status": "active",
"ticker": "UNI/USDT",
"base_denom": "peggy0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"base_token_meta": {
"name": "Uniswap",
"address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"symbol": "UNI",
"logo": "https://static.alchemyapi.io/images/assets/7083.png",
"decimals": 18,
"updated_at": 1650978922133
},
"quote_denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"maker_fee_rate": "0.001",
"taker_fee_rate": "0.002",
"service_provider_fee": "0.4",
"min_price_tick_size": "0.000000000000001",
"min_quantity_tick_size": "1000000000000000",
"min_notional": "1000000"
}
]
}
| Parameter | Type | Description |
|---|---|---|
| markets | SpotMarketInfo array | Spot Markets list |
SpotMarketInfo
| Parameter | Type | Description |
|---|---|---|
| market_id | string | SpotMarket ID is keccak265(baseDenom || quoteDenom) |
| market_status | string | The status of the market |
| ticker | string | A name of the pair in format AAA/BBB, where AAA is base asset, BBB is quote asset. |
| base_denom | string | Coin denom used for the base asset. |
| base_token_meta | TokenMeta | Token metadata for base asset |
| quote_denom | string | Coin denom used for the quote asset. |
| quote_token_meta | TokenMeta | Token metadata for quote asset |
| maker_fee_rate | string | Defines the fee percentage makers pay when trading (in quote asset) |
| taker_fee_rate | string | Defines the fee percentage takers pay when trading (in quote asset) |
| service_provider_fee | string | Percentage of the transaction fee shared with the service provider |
| min_price_tick_size | string | Defines the minimum required tick size for the order's price |
| min_quantity_tick_size | string | Defines the minimum required tick size for the order's quantity |
| min_notional | string | Minimum notional value for the market |
TokenMeta
| Parameter | Type | Description |
|---|---|---|
| name | string | Token full name |
| address | string | Token contract address (native or not) |
| symbol | string | Token symbol short name |
| logo | string | URL to the logo image |
| decimals | int32 | Token decimals |
| updated_at | int64 | Token metadata fetched timestamp in UNIX millis. |
StreamMarkets
Stream live updates of spot markets.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
from typing import Any, Dict
from grpc import RpcError
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def market_event_processor(event: Dict[str, Any]):
print(event)
def stream_error_processor(exception: RpcError):
print(f"There was an error listening to spot markets updates ({exception})")
def stream_closed_processor():
print("The spot markets updates stream has been closed")
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.mainnet()
client = IndexerClient(network)
task = asyncio.get_event_loop().create_task(
client.listen_spot_markets_updates(
callback=market_event_processor,
on_end_callback=stream_closed_processor,
on_status_callback=stream_error_processor,
)
)
await asyncio.sleep(delay=60)
task.cancel()
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
)
func main() {
// network := common.LoadNetwork("mainnet", "k8s")
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketIds := []string{"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"}
stream, err := exchangeClient.StreamSpotMarket(ctx, marketIds)
if err != nil {
panic(err)
}
for {
select {
case <-ctx.Done():
return
default:
res, err := stream.Recv()
if err != nil {
fmt.Println(err)
return
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
}
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| market_ids | string array | List of market IDs for updates streaming, empty means 'ALL' spot markets | Yes |
Response Parameters
Streaming Response Example:
{
"market":{
"marketId":"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"marketStatus":"active",
"ticker":"INJ/USDT",
"baseDenom":"inj",
"baseTokenMeta":{
"name":"Injective Protocol",
"address":"0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30",
"symbol":"INJ",
"logo":"https://static.alchemyapi.io/images/assets/7226.png",
"decimals":18,
"updatedAt":1632535055751
},
"quoteDenom":"peggy0x69efCB62D98f4a6ff5a0b0CFaa4AAbB122e85e08",
"quoteTokenMeta":{
"name":"Tether",
"address":"0x69efCB62D98f4a6ff5a0b0CFaa4AAbB122e85e08",
"symbol":"USDT",
"logo":"https://static.alchemyapi.io/images/assets/825.png",
"decimals":6,
"updatedAt":1632535055759
},
"makerFeeRate":"0.001",
"takerFeeRate":"0.002",
"serviceProviderRate":"0.4",
"minPriceTickSize":"0.000000000000001",
"minQuantityTickSize":"1000000000000000",
"minNotional":"0"
},
"operationType":"update",
"timestamp":1632535055790
}
{
"market": {
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"market_status": "active",
"ticker": "INJ/USDT",
"base_denom": "inj",
"base_token_meta": {
"name": "Injective Protocol",
"address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30",
"symbol": "INJ",
"logo": "https://static.alchemyapi.io/images/assets/7226.png",
"decimals": 18,
"updated_at": 1632535055751
},
"quote_denom": "peggy0x69efCB62D98f4a6ff5a0b0CFaa4AAbB122e85e08",
"quote_token_meta": {
"name": "Tether",
"address": "0x69efCB62D98f4a6ff5a0b0CFaa4AAbB122e85e08",
"symbol": "USDT",
"logo": "https://static.alchemyapi.io/images/assets/825.png",
"decimals": 6,
"updated_at": 1632535055759
},
"maker_fee_rate": "0.001",
"taker_fee_rate": "0.002",
"service_provider_fee": "0.4",
"min_price_tick_size": "0.000000000000001",
"min_quantity_tick_size": "1000000000000000",
"min_notional": "0",
},
"operation_type": "update",
"timestamp": 1632535055790
}
| Parameter | Type | Description |
|---|---|---|
| market | SpotMarketInfo | Info about particular spot market |
| operation_type | string | Update type |
| timestamp | int64 | Operation timestamp in UNIX millis. |
SpotMarketInfo
| Parameter | Type | Description |
|---|---|---|
| market_id | string | SpotMarket ID is keccak265(baseDenom || quoteDenom) |
| market_status | string | The status of the market |
| ticker | string | A name of the pair in format AAA/BBB, where AAA is base asset, BBB is quote asset. |
| base_denom | string | Coin denom used for the base asset. |
| base_token_meta | TokenMeta | Token metadata for base asset |
| quote_denom | string | Coin denom used for the quote asset. |
| quote_token_meta | TokenMeta | Token metadata for quote asset |
| maker_fee_rate | string | Defines the fee percentage makers pay when trading (in quote asset) |
| taker_fee_rate | string | Defines the fee percentage takers pay when trading (in quote asset) |
| service_provider_fee | string | Percentage of the transaction fee shared with the service provider |
| min_price_tick_size | string | Defines the minimum required tick size for the order's price |
| min_quantity_tick_size | string | Defines the minimum required tick size for the order's quantity |
| min_notional | string | Minimum notional value for the market |
TokenMeta
| Parameter | Type | Description |
|---|---|---|
| name | string | Token full name |
| address | string | Token contract address (native or not) |
| symbol | string | Token symbol short name |
| logo | string | URL to the logo image |
| decimals | int32 | Token decimals |
| updated_at | int64 | Token metadata fetched timestamp in UNIX millis. |
OrdersHistory
List history of orders (all states) for a spot market.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.client.model.pagination import PaginationOption
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
market_ids = ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"]
subaccount_id = "0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000"
skip = 10
limit = 3
order_types = ["buy_po"]
pagination = PaginationOption(skip=skip, limit=limit)
orders = await client.fetch_spot_orders_history(
subaccount_id=subaccount_id,
market_ids=market_ids,
order_types=order_types,
pagination=pagination,
)
print(json.dumps(orders, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
spotExchangePB "github.com/InjectiveLabs/sdk-go/exchange/spot_exchange_rpc/pb"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketId := "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
subaccountId := "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
skip := uint64(0)
limit := int32(10)
orderTypes := []string{"buy_po"}
req := spotExchangePB.OrdersHistoryRequest{
SubaccountId: subaccountId,
MarketId: marketId,
Skip: skip,
Limit: limit,
OrderTypes: orderTypes,
}
res, err := exchangeClient.GetHistoricalSpotOrders(ctx, &req)
if err != nil {
panic(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| subaccount_id | string | subaccount ID to filter orders for specific subaccount | Yes |
| market_id | string | Market ID to filter orders for specific market | Yes |
| skip | uint64 | Skip will skip the first n item from the result | Yes |
| limit | int32 | Limit is used to specify the maximum number of items to be returned | Yes |
| order_types | string array | filter by order types | Yes |
| direction | string | order side filter | Yes |
| start_time | int64 | Search for orders which createdAt >= startTime, time in millisecond | Yes |
| end_time | int64 | Search for orders which createdAt <= endTime, time in millisecond | Yes |
| state | string | Filter by order state | Yes |
| execution_types | string array | Yes | |
| market_ids | string array | Yes | |
| trade_id | string | TradeId of the order we want to fetch | Yes |
| active_markets_only | bool | Return only orders for active markets | Yes |
| cid | string | Client order ID | Yes |
Response Parameters
Response Example:
{
"orders":[
{
"orderHash":"0x4e6629ce45597a3dc3941c5382cc7bc542d52fbcc6b03c4fd604c94a9bec0cc1",
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccountId":"0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000",
"executionType":"limit",
"orderType":"buy_po",
"price":"0.000000000000001",
"triggerPrice":"0",
"quantity":"1000000000000000",
"filledQuantity":"1000000000000000",
"state":"filled",
"createdAt":"1668264339149",
"updatedAt":"1682667017745",
"direction":"buy",
"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"isActive":false,
"cid":""
},
{
"orderHash":"0x347de654c8484fe36473c3569382ff27d25e95c660fd055163b7193607867a8b",
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccountId":"0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000",
"executionType":"limit",
"orderType":"buy_po",
"price":"0.000000000000001",
"triggerPrice":"0",
"quantity":"1000000000000000",
"filledQuantity":"1000000000000000",
"state":"filled",
"createdAt":"1668264339149",
"updatedAt":"1682667017745",
"direction":"buy",
"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"isActive":false,
"cid":""
},
{
"orderHash":"0x2141d52714f5c9328170cc674de8ecf876463b1999bea4124d1de595152b718f",
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccountId":"0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000",
"executionType":"limit",
"orderType":"buy_po",
"price":"0.000000000000001",
"triggerPrice":"0",
"quantity":"1000000000000000",
"filledQuantity":"1000000000000000",
"state":"filled",
"createdAt":"1668264339149",
"updatedAt":"1682667017745",
"direction":"buy",
"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"isActive":false,
"cid":""
}
],
"paging":{
"total":"1000",
"from":0,
"to":0,
"countBySubaccount":"0",
"next":[
]
}
}
{
"orders": [
{
"order_hash": "0x47a3858df766691a6124255a959ac17c79588fa36e52bed6d8aea2d927bb6a60",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000007789",
"trigger_price": "0",
"quantity": "12000000000000000000",
"filled_quantity": "12000000000000000000",
"state": "filled",
"created_at": 1681812187591,
"updated_at": 1681886620984,
"direction": "buy"
},
{
"order_hash": "0x4a0f7bec21c2861ec390510f461ab94a6e4425453e113ba41d67c5e79a45538b",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000007692",
"trigger_price": "0",
"quantity": "14000000000000000000",
"filled_quantity": "14000000000000000000",
"state": "filled",
"created_at": 1681812187591,
"updated_at": 1681886620984,
"direction": "buy"
},
{
"order_hash": "0x447b593a3c1683b64bd6ac4e60aa6ff22078951312eb3bfacf0b8b163eb015e4",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000005787",
"trigger_price": "0",
"quantity": "18000000000000000000",
"filled_quantity": "18000000000000000000",
"state": "filled",
"created_at": 1681812187591,
"updated_at": 1681886620984,
"direction": "buy"
},
{
"order_hash": "0x77d1c86d0b04b3347ace0f4a7f708adbb160d54701891d0c212a8c28bb10f77f",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000005457",
"trigger_price": "0",
"quantity": "8000000000000000000",
"filled_quantity": "8000000000000000000",
"state": "filled",
"created_at": 1681812187591,
"updated_at": 1681886620984,
"direction": "buy"
},
{
"order_hash": "0x76899c13fa3e591b1e2cbadfc2c84db5a7f4f97e42cee2451a6a90d04b100642",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000007134",
"trigger_price": "0",
"quantity": "4000000000000000000",
"filled_quantity": "4000000000000000000",
"state": "filled",
"created_at": 1681812187591,
"updated_at": 1681886620984,
"direction": "buy"
},
{
"order_hash": "0xf353711353a98ac3aceee62a4d7fed30e0c65cf38adfa898c455be5e5c671445",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000006138",
"trigger_price": "0",
"quantity": "2000000000000000000",
"filled_quantity": "2000000000000000000",
"state": "filled",
"created_at": 1681812187591,
"updated_at": 1681886620984,
"direction": "buy"
},
{
"order_hash": "0xb599db2124630b350e0ca2ea3453ece84e7721334e1009b451fa21d072a6cf8f",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000005667",
"trigger_price": "0",
"quantity": "22000000000000000000",
"filled_quantity": "22000000000000000000",
"state": "filled",
"created_at": 1681812187591,
"updated_at": 1681886620984,
"direction": "buy"
},
{
"order_hash": "0x1c28300cfebfef73c26e32d396162e45089e34a5ba0c627cc8b6e3fb1d9861ad",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000006263",
"trigger_price": "0",
"quantity": "20000000000000000000",
"filled_quantity": "20000000000000000000",
"state": "filled",
"created_at": 1681812187591,
"updated_at": 1681886620984,
"direction": "buy"
},
{
"order_hash": "0x7a2b9753c94c67f5e79e2f9dcd8af8a619d55d2f9ba1a134a22c5ef154b76e7f",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000007683",
"trigger_price": "0",
"quantity": "16000000000000000000",
"filled_quantity": "16000000000000000000",
"state": "filled",
"created_at": 1681812187591,
"updated_at": 1681886620984,
"direction": "buy"
},
{
"order_hash": "0x4984a08abefd29ba6bc914b11182251e18c0235842916955a4ffdc8ff149d188",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000007668",
"trigger_price": "0",
"quantity": "6000000000000000000",
"filled_quantity": "6000000000000000000",
"state": "filled",
"created_at": 1681812187591,
"updated_at": 1681886620984,
"direction": "buy"
}
],
"paging": {
"total": 1000
}
}
| Parameter | Type | Description |
|---|---|---|
| orders | SpotOrderHistory array | List of history spot orders |
| paging | Paging |
SpotOrderHistory
| Parameter | Type | Description |
|---|---|---|
| order_hash | string | Hash of the order |
| market_id | string | Spot Market ID is keccak265(baseDenom + quoteDenom) |
| is_active | bool | active state of the order |
| subaccount_id | string | The subaccountId that this order belongs to |
| execution_type | string | The execution type |
| order_type | string | The side of the order |
| price | string | Price of the order |
| trigger_price | string | Trigger price |
| quantity | string | Quantity of the order |
| filled_quantity | string | Filled amount |
| state | string | Order state |
| created_at | int64 | Order committed timestamp in UNIX millis. |
| updated_at | int64 | Order updated timestamp in UNIX millis. |
| direction | string | Order direction (order side) |
| tx_hash | string | Transaction Hash where order is created. Not all orders have this field |
| cid | string | Custom client order ID |
Paging
| Parameter | Type | Description |
|---|---|---|
| total | int64 | total number of txs saved in database |
| from | int32 | can be either block height or index num |
| to | int32 | can be either block height or index num |
| count_by_subaccount | int64 | count entries by subaccount, serving some places on helix |
| next | string array | array of tokens to navigate to the next pages |
StreamOrdersHistory
Stream order updates for spot markets. If no parameters are given, updates to all subaccounts in all spot markets will be streamed.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
from typing import Any, Dict
from grpc import RpcError
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def order_event_processor(event: Dict[str, Any]):
print(event)
def stream_error_processor(exception: RpcError):
print(f"There was an error listening to spot orders history updates ({exception})")
def stream_closed_processor():
print("The spot orders history updates stream has been closed")
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
order_direction = "buy"
task = asyncio.get_event_loop().create_task(
client.listen_spot_orders_history_updates(
callback=order_event_processor,
on_end_callback=stream_closed_processor,
on_status_callback=stream_error_processor,
market_id=market_id,
direction=order_direction,
)
)
await asyncio.sleep(delay=60)
task.cancel()
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
spotExchangePB "github.com/InjectiveLabs/sdk-go/exchange/spot_exchange_rpc/pb"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketId := "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
subaccountId := "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000"
direction := "buy"
req := spotExchangePB.StreamOrdersHistoryRequest{
MarketId: marketId,
SubaccountId: subaccountId,
Direction: direction,
}
stream, err := exchangeClient.StreamHistoricalSpotOrders(ctx, &req)
if err != nil {
panic(err)
}
for {
select {
case <-ctx.Done():
return
default:
res, err := stream.Recv()
if err != nil {
panic(err)
return
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
}
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| subaccount_id | string | subaccount ID to filter orders for specific subaccount | Yes |
| market_id | string | Market ID to filter orders for specific market | Yes |
| order_types | string array | filter by order types | Yes |
| direction | string | order side filter | Yes |
| state | string | Filter by order state | Yes |
| execution_types | string array | Yes |
Response Parameters
Streaming Response Example:
{
"order":{
"orderHash":"0xff6a1ce6339911bb6f0765e17e70144ae62834e65e551e910018203d62bc6d12",
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccountId":"0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004",
"executionType":"limit",
"orderType":"buy_po",
"price":"0.000000000019028",
"triggerPrice":"0",
"quantity":"67129093000000000000000",
"filledQuantity":"0",
"state":"canceled",
"createdAt":"1702044186286",
"updatedAt":"1702044188683",
"direction":"buy",
"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"isActive":false,
"cid":""
},
"operationType":"update",
"timestamp":"1702044191000"
}
{
"order": {
"order_hash": "0xf8a90ee4cfb4c938035b791d3b3561e8991803793b4b5590164b2ecbfa247f3d",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000007438",
"trigger_price": "0",
"quantity": "76848283000000000000000",
"filled_quantity": "0",
"state": "canceled",
"created_at": 1696621893030,
"updated_at": 1696621895445,
"direction": "buy"
},
"operation_type": "update",
"timestamp": 1696621898000
}{
"order": {
"order_hash": "0xfd6bf489944cb181ee94057b80ffdfc113a17d48d0455c8d10e4deadf341bdfd",
"market_id": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccount_id": "0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004",
"execution_type": "limit",
"order_type": "buy_po",
"price": "0.000000000007478",
"trigger_price": "0",
"quantity": "76437220000000000000000",
"filled_quantity": "0",
"state": "canceled",
"created_at": 1696621893030,
"updated_at": 1696621895445,
"direction": "buy"
},
"operation_type": "update",
"timestamp": 1696621898000
}
| Parameter | Type | Description |
|---|---|---|
| order | SpotOrderHistory | Updated order |
| operation_type | string | Order update type |
| timestamp | int64 | Operation timestamp in UNIX millis. |
SpotOrderHistory
| Parameter | Type | Description |
|---|---|---|
| order_hash | string | Hash of the order |
| market_id | string | Spot Market ID is keccak265(baseDenom + quoteDenom) |
| is_active | bool | active state of the order |
| subaccount_id | string | The subaccountId that this order belongs to |
| execution_type | string | The execution type |
| order_type | string | The side of the order |
| price | string | Price of the order |
| trigger_price | string | Trigger price |
| quantity | string | Quantity of the order |
| filled_quantity | string | Filled amount |
| state | string | Order state |
| created_at | int64 | Order committed timestamp in UNIX millis. |
| updated_at | int64 | Order updated timestamp in UNIX millis. |
| direction | string | Order direction (order side) |
| tx_hash | string | Transaction Hash where order is created. Not all orders have this field |
| cid | string | Custom client order ID |
TradesV2
Get trade history for a spot market. The default request returns all spot trades from all markets.
IP rate limit group: indexer
*Trade execution types
"market"for market orders"limitFill"for a resting limit order getting filled by a market order"limitMatchRestingOrder"for a resting limit order getting matched with another new limit order"limitMatchNewOrder"for a new limit order getting matched immediately
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
market_ids = ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"]
execution_side = "taker"
direction = "buy"
subaccount_ids = ["0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001"]
execution_types = ["limitMatchNewOrder", "market"]
orders = await client.fetch_spot_trades(
market_ids=market_ids,
subaccount_ids=subaccount_ids,
execution_side=execution_side,
direction=direction,
execution_types=execution_types,
)
print(json.dumps(orders, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
spotExchangePB "github.com/InjectiveLabs/sdk-go/exchange/spot_exchange_rpc/pb"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketId := "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"
subaccountId := "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
req := spotExchangePB.TradesV2Request{
MarketId: marketId,
SubaccountId: subaccountId,
}
res, err := exchangeClient.GetSpotTradesV2(ctx, &req)
if err != nil {
panic(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| market_id | string | MarketId of the market's orderbook we want to fetch | Yes |
| execution_side | string | Filter by execution side of the trade | Yes |
| direction | string | Filter by direction the trade | Yes |
| subaccount_id | string | SubaccountId of the trader we want to get the trades from | Yes |
| skip | uint64 | Skip will skip the first n item from the item result | Yes |
| limit | int32 | Limit is used to specify the maximum number of items to be returned. | Yes |
| start_time | int64 | The starting timestamp in UNIX milliseconds that the trades must be equal or older than | Yes |
| end_time | int64 | The ending timestamp in UNIX milliseconds that the trades must be equal or younger than | Yes |
| market_ids | string array | MarketIds of the markets of which we want to get trades | Yes |
| subaccount_ids | string array | Subaccount ids of traders we want to get trades | Yes |
| execution_types | string array | Yes | |
| trade_id | string | Filter by the tradeId of the trade | Yes |
| account_address | string | Account address | Yes |
| cid | string | Client order ID | Yes |
| fee_recipient | string | Fee recipient address | Yes |
Response Parameters
Response Example:
{
"trades":[
{
"orderHash":"0x952bb14a7a377697d724c60d6077ef3dfe894c98f854970fab187247be832b6f",
"subaccountId":"0x101411266c6e2b610b4a0324d2bfb2ef0ca6e1dd000000000000000000000000",
"marketId":"0x01edfab47f124748dc89998eb33144af734484ba07099014594321729a0ca16b",
"tradeExecutionType":"limitMatchRestingOrder",
"tradeDirection":"buy",
"price":{
"price":"0.00000000001",
"quantity":"1000000000000000000",
"timestamp":"1701961116630"
},
"fee":"-600",
"executedAt":"1701961116630",
"feeRecipient":"inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r",
"tradeId":"1321_0",
"executionSide":"maker",
"cid":"96866b8b-02dd-4288-97d3-e5254e4888b3"
},
{
"orderHash":"0x85a824c31f59cf68235b48666c4821334813f2b80db937f02d192f1e3fc74368",
"subaccountId":"0x3db1f84431dfe4df617f9eb2d04edf432beb9826000000000000000000000000",
"marketId":"0x01edfab47f124748dc89998eb33144af734484ba07099014594321729a0ca16b",
"tradeExecutionType":"limitMatchNewOrder",
"tradeDirection":"sell",
"price":{
"price":"0.00000000001",
"quantity":"1000000000000000000",
"timestamp":"1701961116630"
},
"fee":"10000",
"executedAt":"1701961116630",
"feeRecipient":"inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r",
"tradeId":"1321_1",
"executionSide":"taker",
"cid":"spot_AAVE/USDT"
},
{
"orderHash":"0xffabb2d12a745d79eb12c7ef0eb59c729aaa4387a141f858153c8b8f58168b2e",
"subaccountId":"0x101411266c6e2b610b4a0324d2bfb2ef0ca6e1dd000000000000000000000000",
"marketId":"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"tradeExecutionType":"limitMatchRestingOrder",
"tradeDirection":"buy",
"price":{
"price":"0.00000000001",
"quantity":"2000000000000000000",
"timestamp":"1701960607140"
},
"fee":"-2400",
"executedAt":"1701960607140",
"feeRecipient":"inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r",
"tradeId":"646_0",
"executionSide":"maker",
"cid":"ec581735-f801-4bf3-9101-282b301bf5cd"
},
{
"orderHash":"0xa19e24eef9877ec4980b8d259c1d21fa1dafcd50691e6f853e84af74fb23c05c",
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"marketId":"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"tradeExecutionType":"limitMatchNewOrder",
"tradeDirection":"sell",
"price":{
"price":"0.00000000001",
"quantity":"2000000000000000000",
"timestamp":"1701960607140"
},
"fee":"40000",
"executedAt":"1701960607140",
"feeRecipient":"inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8",
"tradeId":"646_1",
"executionSide":"taker",
"cid":""
},
{
"orderHash":"0xffabb2d12a745d79eb12c7ef0eb59c729aaa4387a141f858153c8b8f58168b2e",
"subaccountId":"0x101411266c6e2b610b4a0324d2bfb2ef0ca6e1dd000000000000000000000000",
"marketId":"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"tradeExecutionType":"limitMatchRestingOrder",
"tradeDirection":"buy",
"price":{
"price":"0.00000000001",
"quantity":"8000000000000000000",
"timestamp":"1701960594997"
},
"fee":"-9600",
"executedAt":"1701960594997",
"feeRecipient":"inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r",
"tradeId":"630_0",
"executionSide":"maker",
"cid":"ec581735-f801-4bf3-9101-282b301bf5cd"
},
{
"orderHash":"0x87b786072190a2f38e9057987be7bdcb4e2274a6c16fdb9670e5c2ded765140f",
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"marketId":"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"tradeExecutionType":"limitMatchNewOrder",
"tradeDirection":"sell",
"price":{
"price":"0.00000000001",
"quantity":"8000000000000000000",
"timestamp":"1701960594997"
},
"fee":"160000",
"executedAt":"1701960594997",
"feeRecipient":"inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8",
"tradeId":"630_1",
"executionSide":"taker",
"cid":""
}
],
"paging":{
"total":"6",
"from":1,
"to":6,
"countBySubaccount":"0",
"next":[
]
}
}
{
"trades": [
{
"order_hash": "0xffabb2d12a745d79eb12c7ef0eb59c729aaa4387a141f858153c8b8f58168b2e",
"subaccount_id": "0x101411266c6e2b610b4a0324d2bfb2ef0ca6e1dd000000000000000000000000",
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"trade_execution_type": "limitMatchRestingOrder",
"trade_direction": "buy",
"price": {
"price": "0.00000000001",
"quantity": "2000000000000000000",
"timestamp": 1701960607140
},
"fee": "-2400",
"executed_at": 1701960607140,
"fee_recipient": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r",
"trade_id": "646_0",
"execution_side": "maker",
"cid": "ec581735-f801-4bf3-9101-282b301bf5cd"
},
{
"order_hash": "0xa19e24eef9877ec4980b8d259c1d21fa1dafcd50691e6f853e84af74fb23c05c",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"trade_execution_type": "limitMatchNewOrder",
"trade_direction": "sell",
"price": {
"price": "0.00000000001",
"quantity": "2000000000000000000",
"timestamp": 1701960607140
},
"fee": "40000",
"executed_at": 1701960607140,
"fee_recipient": "inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8",
"trade_id": "646_1",
"execution_side": "taker"
},
{
"order_hash": "0xffabb2d12a745d79eb12c7ef0eb59c729aaa4387a141f858153c8b8f58168b2e",
"subaccount_id": "0x101411266c6e2b610b4a0324d2bfb2ef0ca6e1dd000000000000000000000000",
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"trade_execution_type": "limitMatchRestingOrder",
"trade_direction": "buy",
"price": {
"price": "0.00000000001",
"quantity": "8000000000000000000",
"timestamp": 1701960594997
},
"fee": "-9600",
"executed_at": 1701960594997,
"fee_recipient": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r",
"trade_id": "630_0",
"execution_side": "maker",
"cid": "ec581735-f801-4bf3-9101-282b301bf5cd"
},
{
"order_hash": "0x87b786072190a2f38e9057987be7bdcb4e2274a6c16fdb9670e5c2ded765140f",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"trade_execution_type": "limitMatchNewOrder",
"trade_direction": "sell",
"price": {
"price": "0.00000000001",
"quantity": "8000000000000000000",
"timestamp": 1701960594997
},
"fee": "160000",
"executed_at": 1701960594997,
"fee_recipient": "inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8",
"trade_id": "630_1",
"execution_side": "taker"
}
],
"paging": {
"total": 4,
"from": 1,
"to": 4
}
}
| Parameter | Type | Description |
|---|---|---|
| trades | SpotTrade array | Trades of a Spot Market |
| paging | Paging | Paging indicates pages response is on |
SpotTrade
| Parameter | Type | Description |
|---|---|---|
| order_hash | string | Maker order hash. |
| subaccount_id | string | The subaccountId that executed the trade |
| market_id | string | The ID of the market that this trade is in |
| trade_execution_type | string | The execution type of the trade |
| trade_direction | string | The direction the trade |
| price | PriceLevel | Price level at which trade has been executed |
| fee | string | The fee associated with the trade (quote asset denom) |
| executed_at | int64 | Timestamp of trade execution in UNIX millis |
| fee_recipient | string | Fee recipient address |
| trade_id | string | A unique string that helps differentiate between trades |
| execution_side | string | Trade's execution side, marker/taker |
| cid | string | Custom client order ID |
PriceLevel
| Parameter | Type | Description |
|---|---|---|
| price | string | Price number of the price level. |
| quantity | string | Quantity of the price level. |
| timestamp | int64 | Price level last updated timestamp in UNIX millis. |
Paging
| Parameter | Type | Description |
|---|---|---|
| total | int64 | total number of txs saved in database |
| from | int32 | can be either block height or index num |
| to | int32 | can be either block height or index num |
| count_by_subaccount | int64 | count entries by subaccount, serving some places on helix |
| next | string array | array of tokens to navigate to the next pages |
StreamTradesV2
Stream newly executed trades of spot markets. The default request streams trades from all spot markets.
IP rate limit group: indexer
*Trade execution types
"market"for market orders"limitFill"for a resting limit order getting filled by a market order"limitMatchRestingOrder"for a resting limit order getting matched with another new limit order"limitMatchNewOrder"for a new limit order getting matched immediately
Request Parameters
Request Example:
import asyncio
from typing import Any, Dict
from grpc import RpcError
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def trade_event_processor(event: Dict[str, Any]):
print(event)
def stream_error_processor(exception: RpcError):
print(f"There was an error listening to spot trades updates ({exception})")
def stream_closed_processor():
print("The spot trades updates stream has been closed")
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
market_ids = [
"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"0x7a57e705bb4e09c88aecfc295569481dbf2fe1d5efe364651fbe72385938e9b0",
]
execution_side = "maker"
direction = "sell"
subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001"
execution_types = ["limitMatchRestingOrder"]
task = asyncio.get_event_loop().create_task(
client.listen_spot_trades_updates(
callback=trade_event_processor,
on_end_callback=stream_closed_processor,
on_status_callback=stream_error_processor,
market_ids=market_ids,
subaccount_ids=[subaccount_id],
execution_side=execution_side,
direction=direction,
execution_types=execution_types,
)
)
await asyncio.sleep(delay=60)
task.cancel()
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
spotExchangePB "github.com/InjectiveLabs/sdk-go/exchange/spot_exchange_rpc/pb"
)
func main() {
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketId := "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"
subaccountId := "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000"
req := spotExchangePB.StreamTradesV2Request{
MarketId: marketId,
SubaccountId: subaccountId,
}
stream, err := exchangeClient.StreamSpotTradesV2(ctx, &req)
if err != nil {
panic(err)
}
for {
select {
case <-ctx.Done():
return
default:
res, err := stream.Recv()
if err != nil {
fmt.Println(err)
return
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
}
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| market_id | string | MarketId of the market's orderbook we want to fetch | Yes |
| execution_side | string | Filter by execution side of the trade | Yes |
| direction | string | Filter by direction the trade | Yes |
| subaccount_id | string | SubaccountId of the trader we want to get the trades from | Yes |
| skip | uint64 | Skip will skip the first n item from the item result | Yes |
| limit | int32 | Limit is used to specify the maximum number of items to be returned. | Yes |
| start_time | int64 | The starting timestamp in UNIX milliseconds that the trades must be equal or older than | Yes |
| end_time | int64 | The ending timestamp in UNIX milliseconds that the trades must be equal or younger than | Yes |
| market_ids | string array | MarketIds of the markets of which we want to get trades | Yes |
| subaccount_ids | string array | Subaccount ids of traders we want to get trades | Yes |
| execution_types | string array | Yes | |
| trade_id | string | Filter by the tradeId of the trade | Yes |
| account_address | string | Account address | Yes |
| cid | string | Client order ID | Yes |
| fee_recipient | string | Fee recipient address | Yes |
Response Parameters
Streaming Response Example:
{
"trade":{
"orderHash":"0xa7f4a7d85136d97108d271caadd93bf697ff965790e0e1558617b953cced4adc",
"subaccountId":"0x3db1f84431dfe4df617f9eb2d04edf432beb9826000000000000000000000000",
"marketId":"0x01edfab47f124748dc89998eb33144af734484ba07099014594321729a0ca16b",
"tradeExecutionType":"limitMatchNewOrder",
"tradeDirection":"sell",
"price":{
"price":"0.00000000001",
"quantity":"1000000000000000000",
"timestamp":"1701978102242"
},
"fee":"10000",
"executedAt":"1701978102242",
"feeRecipient":"inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r",
"tradeId":"22868_1",
"executionSide":"taker",
"cid":"96866b8b-02dd-4288-97d3-e5254e4999d4"
},
"operationType":"insert",
"timestamp":"1701978103000"
}
{
"trade":{
"orderHash":"0x952bb14a7a377697d724c60d6077ef3dfe894c98f854970fab187247be832b6f",
"subaccountId":"0x101411266c6e2b610b4a0324d2bfb2ef0ca6e1dd000000000000000000000000",
"marketId":"0x01edfab47f124748dc89998eb33144af734484ba07099014594321729a0ca16b",
"tradeExecutionType":"limitMatchRestingOrder",
"tradeDirection":"buy",
"price":{
"price":"0.00000000001",
"quantity":"1000000000000000000",
"timestamp":"1701978102242"
},
"fee":"-600",
"executedAt":"1701978102242",
"feeRecipient":"inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r",
"tradeId":"22868_0",
"executionSide":"maker",
"cid":"96866b8b-02dd-4288-97d3-e5254e4888b3"
},
"operationType":"insert",
"timestamp":"1701978103000"
}
{
"trade": {
"order_hash": "0x88e34872af0147f57c8c5a093c3a6a8a97358615bccf975b4a06dfb5162daeaf",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"trade_execution_type": "market",
"trade_direction": "sell",
"price": {
"price": "0.000000000001654",
"quantity": "1000000000000000000",
"timestamp": 1653042087046
},
"fee": "3308",
"executed_at": 1653042087046,
"fee_recipient": "inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8"
},
"operation_type": "insert",
"timestamp": 1653042089000
}{
"trade": {
"order_hash": "0xb5d651a01faa90ec53b0fa34f00f3ecdfe169f9fc35be8114ee113eea9257c30",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"trade_execution_type": "market",
"trade_direction": "sell",
"price": {
"price": "0.000000000001654",
"quantity": "2000000000000000000",
"timestamp": 1653042093023
},
"fee": "6616",
"executed_at": 1653042093023,
"fee_recipient": "inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8"
},
"operation_type": "insert",
"timestamp": 1653042098000
}
| Parameter | Type | Description |
|---|---|---|
| trade | SpotTrade | New spot market trade |
| operation_type | string | Executed trades update type |
| timestamp | int64 | Operation timestamp in UNIX millis. |
SpotTrade
| Parameter | Type | Description |
|---|---|---|
| order_hash | string | Maker order hash. |
| subaccount_id | string | The subaccountId that executed the trade |
| market_id | string | The ID of the market that this trade is in |
| trade_execution_type | string | The execution type of the trade |
| trade_direction | string | The direction the trade |
| price | PriceLevel | Price level at which trade has been executed |
| fee | string | The fee associated with the trade (quote asset denom) |
| executed_at | int64 | Timestamp of trade execution in UNIX millis |
| fee_recipient | string | Fee recipient address |
| trade_id | string | A unique string that helps differentiate between trades |
| execution_side | string | Trade's execution side, marker/taker |
| cid | string | Custom client order ID |
PriceLevel
| Parameter | Type | Description |
|---|---|---|
| price | string | Price number of the price level. |
| quantity | string | Quantity of the price level. |
| timestamp | int64 | Price level last updated timestamp in UNIX millis. |
OrderbooksV2
Get an orderbook snapshot for one or more spot markets.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
market_ids = [
"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"0x7a57e705bb4e09c88aecfc295569481dbf2fe1d5efe364651fbe72385938e9b0",
]
depth = 1
orderbooks = await client.fetch_spot_orderbooks_v2(market_ids=market_ids, depth=depth)
print(json.dumps(orderbooks, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
)
func main() {
// network := common.LoadNetwork("mainnet", "k8s")
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketIds := []string{"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"}
depth := int32(10)
res, err := exchangeClient.GetSpotOrderbooksV2(ctx, marketIds, depth)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| market_ids | string array | MarketIds of the markets | Yes |
| depth | int32 | Depth of the orderbook | Yes |
Response Parameters
Response Example:
{
"orderbooks":[
{
"marketId":"0x7a57e705bb4e09c88aecfc295569481dbf2fe1d5efe364651fbe72385938e9b0",
"orderbook":{
"sells":[
{
"price":"0.000000000005",
"quantity":"27767884000000000000000",
"timestamp":"1694702425539"
},
{
"price":"0.0000000000045",
"quantity":"3519999000000000000000000",
"timestamp":"1694424758707"
}
],
"timestamp":"-62135596800000",
"buys":[
],
"sequence":"0"
}
},
{
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"orderbook":{
"buys":[
{
"price":"0.000000000073489",
"quantity":"129000000000000000",
"timestamp":"1702042963690"
},
{
"price":"0.000000000064261",
"quantity":"1292000000000000000",
"timestamp":"1702039612697"
}
],
"sells":[
{
"price":"0.000000000085",
"quantity":"6693248000000000000000",
"timestamp":"1702044317059"
},
{
"price":"0.000000000085768",
"quantity":"581000000000000000",
"timestamp":"1701944786578"
}
],
"sequence":"6916386",
"timestamp":"1702044336800"
}
}
]
}
| Parameter | Type | Description |
|---|---|---|
| orderbooks | SingleSpotLimitOrderbookV2 array |
SingleSpotLimitOrderbookV2
| Parameter | Type | Description |
|---|---|---|
| market_id | string | market's ID |
| orderbook | SpotLimitOrderbookV2 | Orderbook of the market |
SpotLimitOrderbookV2
| Parameter | Type | Description |
|---|---|---|
| buys | PriceLevel array | Array of price levels for buys |
| sells | PriceLevel array | Array of price levels for sells |
| sequence | uint64 | market orderbook sequence |
| timestamp | int64 | Last update timestamp in UNIX millis. |
| height | int64 | Block height at which the orderbook was last updated. |
PriceLevel
| Parameter | Type | Description |
|---|---|---|
| price | string | Price number of the price level. |
| quantity | string | Quantity of the price level. |
| timestamp | int64 | Price level last updated timestamp in UNIX millis. |
StreamOrderbookV2
Stream orderbook snapshot updates for one or more spot markets.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
from typing import Any, Dict
from grpc import RpcError
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def orderbook_event_processor(event: Dict[str, Any]):
print(event)
def stream_error_processor(exception: RpcError):
print(f"There was an error listening to spot orderbook snapshots ({exception})")
def stream_closed_processor():
print("The spot orderbook snapshots stream has been closed")
async def main() -> None:
network = Network.testnet()
client = IndexerClient(network)
market_ids = [
"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"0x7a57e705bb4e09c88aecfc295569481dbf2fe1d5efe364651fbe72385938e9b0",
]
task = asyncio.get_event_loop().create_task(
client.listen_spot_orderbook_snapshots(
market_ids=market_ids,
callback=orderbook_event_processor,
on_end_callback=stream_closed_processor,
on_status_callback=stream_error_processor,
)
)
await asyncio.sleep(delay=60)
task.cancel()
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
)
func main() {
network := common.LoadNetwork("devnet-1", "")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketIds := []string{"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"}
stream, err := exchangeClient.StreamSpotOrderbookV2(ctx, marketIds)
if err != nil {
panic(err)
}
for {
select {
case <-ctx.Done():
return
default:
res, err := stream.Recv()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(res.MarketId, res.Orderbook, len(res.Orderbook.Sells), len(res.Orderbook.Buys))
}
}
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| market_ids | string array | List of market IDs for orderbook streaming, empty means 'ALL' spot markets | Yes |
Response Parameters
Streaming Response Example:
{
"orderbook":{
"buys":[
{
"price":"0.000000000073489",
"quantity":"129000000000000000",
"timestamp":"1702042963690"
},
{
"price":"0.000000000064261",
"quantity":"1292000000000000000",
"timestamp":"1702039612697"
}
],
"sells":[
{
"price":"0.000000000085",
"quantity":"6681507000000000000000",
"timestamp":"1702044411262"
},
{
"price":"0.000000000085768",
"quantity":"581000000000000000",
"timestamp":"1701944786578"
}
],
"sequence":"6916434",
"timestamp":"1702044439698"
},
"operationType":"update",
"timestamp":"1702044441000",
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
}
| Parameter | Type | Description |
|---|---|---|
| orderbook | SpotLimitOrderbookV2 | Orderbook of a Spot Market |
| operation_type | string | Order update type |
| timestamp | int64 | Operation timestamp in UNIX millis. |
| market_id | string | MarketId of the market's orderbook |
SpotLimitOrderbookV2
| Parameter | Type | Description |
|---|---|---|
| buys | PriceLevel array | Array of price levels for buys |
| sells | PriceLevel array | Array of price levels for sells |
| sequence | uint64 | market orderbook sequence |
| timestamp | int64 | Last update timestamp in UNIX millis. |
| height | int64 | Block height at which the orderbook was last updated. |
PriceLevel
| Parameter | Type | Description |
|---|---|---|
| price | string | Price number of the price level. |
| quantity | string | Quantity of the price level. |
| timestamp | int64 | Price level last updated timestamp in UNIX millis. |
StreamOrderbookUpdate
Stream incremental orderbook updates for one or more spot markets. This stream should be started prior to obtaining orderbook snapshots so that no incremental updates are omitted between obtaining a snapshot and starting the update stream.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
from decimal import Decimal
from typing import Any, Dict
from grpc import RpcError
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
def stream_error_processor(exception: RpcError):
print(f"There was an error listening to spot orderbook updates ({exception})")
def stream_closed_processor():
print("The spot orderbook updates stream has been closed")
class PriceLevel:
def __init__(self, price: Decimal, quantity: Decimal, timestamp: int):
self.price = price
self.quantity = quantity
self.timestamp = timestamp
def __str__(self) -> str:
return "price: {} | quantity: {} | timestamp: {}".format(self.price, self.quantity, self.timestamp)
class Orderbook:
def __init__(self, market_id: str):
self.market_id = market_id
self.sequence = -1
self.levels = {"buys": {}, "sells": {}}
async def load_orderbook_snapshot(client: IndexerClient, orderbook: Orderbook):
# load the snapshot
res = await client.fetch_spot_orderbooks_v2(market_ids=[orderbook.market_id], depth=0)
for snapshot in res["orderbooks"]:
if snapshot["marketId"] != orderbook.market_id:
raise Exception("unexpected snapshot")
orderbook.sequence = int(snapshot["orderbook"]["sequence"])
for buy in snapshot["orderbook"]["buys"]:
orderbook.levels["buys"][buy["price"]] = PriceLevel(
price=Decimal(buy["price"]),
quantity=Decimal(buy["quantity"]),
timestamp=int(buy["timestamp"]),
)
for sell in snapshot["orderbook"]["sells"]:
orderbook.levels["sells"][sell["price"]] = PriceLevel(
price=Decimal(sell["price"]),
quantity=Decimal(sell["quantity"]),
timestamp=int(sell["timestamp"]),
)
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
client = IndexerClient(network)
market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
orderbook = Orderbook(market_id=market_id)
updates_queue = asyncio.Queue()
tasks = []
async def queue_event(event: Dict[str, Any]):
await updates_queue.put(event)
# start getting price levels updates
task = asyncio.get_event_loop().create_task(
client.listen_spot_orderbook_updates(
market_ids=[market_id],
callback=queue_event,
on_end_callback=stream_closed_processor,
on_status_callback=stream_error_processor,
)
)
tasks.append(task)
# load the snapshot once we are already receiving updates, so we don't miss any
await load_orderbook_snapshot(client=client, orderbook=orderbook)
task = asyncio.get_event_loop().create_task(
apply_orderbook_update(orderbook=orderbook, updates_queue=updates_queue)
)
tasks.append(task)
await asyncio.sleep(delay=60)
for task in tasks:
task.cancel()
async def apply_orderbook_update(orderbook: Orderbook, updates_queue: asyncio.Queue):
while True:
updates = await updates_queue.get()
update = updates["orderbookLevelUpdates"]
# discard updates older than the snapshot
if int(update["sequence"]) <= orderbook.sequence:
return
print(" * * * * * * * * * * * * * * * * * * *")
# ensure we have not missed any update
if int(update["sequence"]) > (orderbook.sequence + 1):
raise Exception(
"missing orderbook update events from stream, must restart: {} vs {}".format(
update["sequence"], (orderbook.sequence + 1)
)
)
print("updating orderbook with updates at sequence {}".format(update["sequence"]))
# update orderbook
orderbook.sequence = int(update["sequence"])
for direction, levels in {"buys": update["buys"], "sells": update["sells"]}.items():
for level in levels:
if level["isActive"]:
# upsert level
orderbook.levels[direction][level["price"]] = PriceLevel(
price=Decimal(level["price"]), quantity=Decimal(level["quantity"]), timestamp=level["timestamp"]
)
else:
if level["price"] in orderbook.levels[direction]:
del orderbook.levels[direction][level["price"]]
# sort the level numerically
buys = sorted(orderbook.levels["buys"].values(), key=lambda x: x.price, reverse=True)
sells = sorted(orderbook.levels["sells"].values(), key=lambda x: x.price, reverse=True)
# lowest sell price should be higher than the highest buy price
if len(buys) > 0 and len(sells) > 0:
highest_buy = buys[0].price
lowest_sell = sells[-1].price
print("Max buy: {} - Min sell: {}".format(highest_buy, lowest_sell))
if highest_buy >= lowest_sell:
raise Exception("crossed orderbook, must restart")
# for the example, print the list of buys and sells orders.
print("sells")
for k in sells:
print(k)
print("=========")
print("buys")
for k in buys:
print(k)
print("====================================")
if __name__ == "__main__":
asyncio.run(main())
package main
import (
"context"
"fmt"
"io"
"os"
"sort"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
spotExchangePB "github.com/InjectiveLabs/sdk-go/exchange/spot_exchange_rpc/pb"
"github.com/shopspring/decimal"
)
type MapOrderbook struct {
Sequence uint64
Levels map[bool]map[string]*spotExchangePB.PriceLevel
}
func main() {
network := common.LoadNetwork("devnet-1", "")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
fmt.Println(err)
panic(err)
}
ctx := context.Background()
marketIds := []string{"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"}
stream, err := exchangeClient.StreamSpotOrderbookUpdate(ctx, marketIds)
if err != nil {
fmt.Println(err)
panic(err)
}
updatesCh := make(chan *spotExchangePB.OrderbookLevelUpdates, 100000)
receiving := make(chan struct{})
var receivingClosed bool
// stream orderbook price levels
go func() {
for {
select {
case <-ctx.Done():
return
default:
res, err := stream.Recv()
if err == io.EOF {
fmt.Println("change stream needs to be restarted")
os.Exit(0)
}
if err != nil {
panic(err) // rester on errors
}
u := res.OrderbookLevelUpdates
if !receivingClosed {
fmt.Println("receiving updates from stream")
close(receiving)
receivingClosed = true
}
updatesCh <- u
}
}
}()
// ensure we are receiving updates before getting the orderbook snapshot,
// we will skip past updates later.
fmt.Println("waiting for streaming updates")
<-receiving
// prepare orderbooks map
orderbooks := map[string]*MapOrderbook{}
depth := int32(0)
res, err := exchangeClient.GetSpotOrderbooksV2(ctx, marketIds, depth)
if err != nil {
panic(err)
}
for _, ob := range res.Orderbooks {
// init inner maps not ready
_, ok := orderbooks[ob.MarketId]
if !ok {
orderbook := &MapOrderbook{
Sequence: ob.Orderbook.Sequence,
Levels: map[bool]map[string]*spotExchangePB.PriceLevel{},
}
orderbook.Levels[true] = map[string]*spotExchangePB.PriceLevel{}
orderbook.Levels[false] = map[string]*spotExchangePB.PriceLevel{}
orderbooks[ob.MarketId] = orderbook
}
for _, level := range ob.Orderbook.Buys {
orderbooks[ob.MarketId].Levels[true][level.Price] = level
}
for _, level := range ob.Orderbook.Sells {
orderbooks[ob.MarketId].Levels[false][level.Price] = level
}
}
// continuously consume level updates and maintain orderbook
skippedPastEvents := false
for {
updates := <-updatesCh
// validate orderbook
orderbook, ok := orderbooks[updates.MarketId]
if !ok {
fmt.Println("updates channel closed, must restart")
return // closed
}
// skip if update sequence <= orderbook sequence until it's ready to consume
if !skippedPastEvents {
if orderbook.Sequence >= updates.Sequence {
continue
}
skippedPastEvents = true
}
// panic if update sequence > orderbook sequence + 1
if updates.Sequence > orderbook.Sequence+1 {
fmt.Printf("skipping levels: update sequence %d vs orderbook sequence %d\n", updates.Sequence, orderbook.Sequence)
panic("missing orderbook update events from stream, must restart")
}
// update orderbook map
orderbook.Sequence = updates.Sequence
for isBuy, update := range map[bool][]*spotExchangePB.PriceLevelUpdate{
true: updates.Buys,
false: updates.Sells,
} {
for _, level := range update {
if level.IsActive {
// upsert
orderbook.Levels[isBuy][level.Price] = &spotExchangePB.PriceLevel{
Price: level.Price,
Quantity: level.Quantity,
Timestamp: level.Timestamp,
}
} else {
// remove inactive level
delete(orderbook.Levels[isBuy], level.Price)
}
}
}
// construct orderbook arrays
sells, buys := maintainOrderbook(orderbook.Levels)
fmt.Println("after", orderbook.Sequence, len(sells), len(buys))
if len(sells) > 0 && len(buys) > 0 {
// assert orderbook
topBuyPrice := decimal.RequireFromString(buys[0].Price)
lowestSellPrice := decimal.RequireFromString(sells[0].Price)
if topBuyPrice.GreaterThanOrEqual(lowestSellPrice) {
panic(fmt.Errorf("crossed orderbook, must restart: buy %s >= %s sell", topBuyPrice, lowestSellPrice))
}
}
res, _ = exchangeClient.GetSpotOrderbooksV2(ctx, marketIds, depth)
fmt.Println("query", res.Orderbooks[0].Orderbook.Sequence, len(res.Orderbooks[0].Orderbook.Sells), len(res.Orderbooks[0].Orderbook.Buys))
// print orderbook
fmt.Println(" [SELLS] ========")
for _, s := range sells {
fmt.Println(s)
}
fmt.Println(" [BUYS] ========")
for _, b := range buys {
fmt.Println(b)
}
fmt.Println("=======================================================")
}
}
func maintainOrderbook(orderbook map[bool]map[string]*spotExchangePB.PriceLevel) (buys, sells []*spotExchangePB.PriceLevel) {
for _, v := range orderbook[false] {
sells = append(sells, v)
}
for _, v := range orderbook[true] {
buys = append(buys, v)
}
sort.Slice(sells, func(i, j int) bool {
return decimal.RequireFromString(sells[i].Price).LessThan(decimal.RequireFromString(sells[j].Price))
})
sort.Slice(buys, func(i, j int) bool {
return decimal.RequireFromString(buys[i].Price).GreaterThan(decimal.RequireFromString(buys[j].Price))
})
return sells, buys
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| market_ids | string array | List of market IDs for orderbook streaming, empty means 'ALL' spot markets | Yes |
Response Parameters
Streaming Response Example:
* * * * * * * * * * * * * * * * * * *
updating orderbook with updates at sequence 724
Max buy: 7.523E-12 - Min sell: 7.525E-12
sells
price: 8E-12 | quantity: 10000000000000000 | timestamp: 1675904636889
price: 7.526E-12 | quantity: 50000000000000000 | timestamp: 1676089482358
price: 7.525E-12 | quantity: 10000000000000000 | timestamp: 1676015247335
=========
buys
price: 7.523E-12 | quantity: 30000000000000000 | timestamp: 1676616192052
price: 7E-12 | quantity: 1000000000000000000 | timestamp: 1675904400063
price: 1E-12 | quantity: 10000000000000000 | timestamp: 1675882430039
price: 1E-15 | quantity: 17983000000000000000 | timestamp: 1675880932648
====================================
* * * * * * * * * * * * * * * * * * *
updating orderbook with updates at sequence 725
Max buy: 7.523E-12 - Min sell: 7.525E-12
sells
price: 8E-12 | quantity: 10000000000000000 | timestamp: 1675904636889
price: 7.526E-12 | quantity: 50000000000000000 | timestamp: 1676089482358
price: 7.525E-12 | quantity: 10000000000000000 | timestamp: 1676015247335
=========
buys
price: 7.523E-12 | quantity: 40000000000000000 | timestamp: 1676616222476
price: 7E-12 | quantity: 1000000000000000000 | timestamp: 1675904400063
price: 1E-12 | quantity: 10000000000000000 | timestamp: 1675882430039
price: 1E-15 | quantity: 17983000000000000000 | timestamp: 1675880932648
====================================
| Parameter | Type | Description |
|---|---|---|
| orderbook_level_updates | OrderbookLevelUpdates | Orderbook level updates of a Spot Market |
| operation_type | string | Order update type |
| timestamp | int64 | Operation timestamp in UNIX millis. |
| market_id | string | MarketId of the market's orderbook |
OrderbookLevelUpdates
| Parameter | Type | Description |
|---|---|---|
| market_id | string | market's ID |
| sequence | uint64 | orderbook update sequence |
| buys | PriceLevelUpdate array | buy levels |
| sells | PriceLevelUpdate array | sell levels |
| updated_at | int64 | updates timestamp |
PriceLevelUpdate
| Parameter | Type | Description |
|---|---|---|
| price | string | Price number of the price level. |
| quantity | string | Quantity of the price level. |
| is_active | bool | Price level status. |
| timestamp | int64 | Price level last updated timestamp in UNIX millis. |
SubaccountOrdersList
Get orders of a subaccount.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.client.model.pagination import PaginationOption
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
client = IndexerClient(network)
subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001"
market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
skip = 10
limit = 10
pagination = PaginationOption(skip=skip, limit=limit)
orders = await client.fetch_spot_subaccount_orders_list(
subaccount_id=subaccount_id, market_id=market_id, pagination=pagination
)
print(json.dumps(orders, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
spotExchangePB "github.com/InjectiveLabs/sdk-go/exchange/spot_exchange_rpc/pb"
)
func main() {
// network := common.LoadNetwork("mainnet", "k8s")
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketId := "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"
subaccountId := "0x0b46e339708ea4d87bd2fcc61614e109ac374bbe000000000000000000000000"
skip := uint64(0)
limit := int32(10)
req := spotExchangePB.SubaccountOrdersListRequest{
MarketId: marketId,
SubaccountId: subaccountId,
Skip: skip,
Limit: limit,
}
res, err := exchangeClient.GetSubaccountSpotOrdersList(ctx, &req)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| subaccount_id | string | subaccount ID to filter orders for specific subaccount | Yes |
| market_id | string | Market ID to filter orders for specific market | Yes |
| skip | uint64 | Skip will skip the first n item from the result | Yes |
| limit | int32 | Limit is used to specify the maximum number of items to be returned | Yes |
Response Parameters
Response Example:
{
"orders":[
{
"orderHash":"0x3414f6f1a37a864166b19930680eb62a99798b5e406c2d14ed1ee66ab9958503",
"orderSide":"buy",
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccountId":"0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000",
"price":"0.000000000003",
"quantity":"55000000000000000000",
"unfilledQuantity":"55000000000000000000",
"triggerPrice":"0",
"feeRecipient":"inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r",
"state":"booked",
"createdAt":"1701808096494",
"updatedAt":"1701808096494",
"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"cid":"670c52ec-f68f-456c-8aeb-e271071a43ac"
},
{
"orderHash":"0xb7b6d54d1e01e1eb0005e34e08a96b715b557eeee7c5f3a439636f98ddd66b91",
"orderSide":"buy",
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"subaccountId":"0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000",
"price":"0.000000000003",
"quantity":"55000000000000000000",
"unfilledQuantity":"55000000000000000000",
"triggerPrice":"0",
"feeRecipient":"inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r",
"state":"booked",
"createdAt":"1701808058512",
"updatedAt":"1701808058512",
"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"cid":"bba97476-e7f4-4313-874b-7ef115daccb4"
}
],
"paging":{
"total":"53",
"from":1,
"to":2,
"countBySubaccount":"0",
"next":[
]
}
}
{
"orders": [
{
"order_hash": "0x5e970df47eb5a65a5f907e3a2912067dde416eca8609c838e08c0dbebfbefaa5",
"order_side": "sell",
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"price": "0.000000000005",
"quantity": "1000000000000000000",
"unfilled_quantity": "1000000000000000000",
"trigger_price": "0",
"fee_recipient": "inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8",
"state": "booked",
"created_at": 1652809317404,
"updated_at": 1652809317404
},
{
"order_hash": "0x318418b546563a75c11dc656ee0fb41608e2893b0de859cf2b9e2d65996b6f9c",
"order_side": "buy",
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"subaccount_id": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"price": "0.000000000001",
"quantity": "1000000000000000000",
"unfilled_quantity": "1000000000000000000",
"trigger_price": "0",
"fee_recipient": "inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8",
"state": "booked",
"created_at": 1652809253308,
"updated_at": 1652809253308
}
]
}
| Parameter | Type | Description |
|---|---|---|
| orders | SpotLimitOrder array | |
| paging | Paging |
SpotLimitOrder
| Parameter | Type | Description |
|---|---|---|
| order_hash | string | Hash of the order |
| order_side | string | The side of the order |
| market_id | string | Spot Market ID is keccak265(baseDenom + quoteDenom) |
| subaccount_id | string | The subaccountId that this order belongs to |
| price | string | Price of the order |
| quantity | string | Quantity of the order |
| unfilled_quantity | string | The amount of the quantity remaining unfilled |
| trigger_price | string | Trigger price is the trigger price used by stop/take orders. 0 if the trigger price is not set. |
| fee_recipient | string | Fee recipient address |
| state | string | Order state |
| created_at | int64 | Order committed timestamp in UNIX millis. |
| updated_at | int64 | Order updated timestamp in UNIX millis. |
| tx_hash | string | Transaction Hash where order is created. Not all orders have this field |
| cid | string | Custom client order ID |
Paging
| Parameter | Type | Description |
|---|---|---|
| total | int64 | total number of txs saved in database |
| from | int32 | can be either block height or index num |
| to | int32 | can be either block height or index num |
| count_by_subaccount | int64 | count entries by subaccount, serving some places on helix |
| next | string array | array of tokens to navigate to the next pages |
SubaccountTradesList
Get trades of a subaccount.
IP rate limit group: indexer
*Trade execution types
"market"for market orders"limitFill"for a resting limit order getting filled by a market order"limitMatchRestingOrder"for a resting limit order getting matched with another new limit order"limitMatchNewOrder"for a new limit order getting matched immediately
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.client.model.pagination import PaginationOption
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
client = IndexerClient(network)
subaccount_id = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000"
market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
execution_type = "market"
direction = "buy"
skip = 2
limit = 3
pagination = PaginationOption(skip=skip, limit=limit)
trades = await client.fetch_spot_subaccount_trades_list(
subaccount_id=subaccount_id,
market_id=market_id,
execution_type=execution_type,
direction=direction,
pagination=pagination,
)
print(json.dumps(trades, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
spotExchangePB "github.com/InjectiveLabs/sdk-go/exchange/spot_exchange_rpc/pb"
)
func main() {
// network := common.LoadNetwork("mainnet", "k8s")
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketId := "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"
subaccountId := "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000"
skip := uint64(0)
limit := int32(10)
req := spotExchangePB.SubaccountTradesListRequest{
MarketId: marketId,
SubaccountId: subaccountId,
Skip: skip,
Limit: limit,
}
res, err := exchangeClient.GetSubaccountSpotTradesList(ctx, &req)
if err != nil {
fmt.Println(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| subaccount_id | string | SubaccountId of the trader we want to get the trades from | Yes |
| market_id | string | Filter trades by market ID | Yes |
| execution_type | string | Filter by execution type of trades | Yes |
| direction | string | Filter by direction trades | Yes |
| skip | uint64 | Skip will skip the first n item from the result | Yes |
| limit | int32 | Limit is used to specify the maximum number of items to be returned | Yes |
Response Parameters
Response Example:
{
"trades":[
{
"orderHash":"0x6dfd01151a5b3cfb3a61777335f0d8d324a479b9006fd9642f52b402aec53d4f",
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"tradeExecutionType":"market",
"tradeDirection":"buy",
"price":{
"price":"0.000000000015589",
"quantity":"1000000000000000",
"timestamp":"1700675201676"
},
"fee":"10.9123",
"executedAt":"1700675201676",
"feeRecipient":"inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh",
"tradeId":"18740619_240_0",
"executionSide":"taker",
"cid":""
},
{
"orderHash":"0x6a6cd0afb038322b67a88cd827e2800483e3571c5e82663df37a33770fa0a8d1",
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"tradeExecutionType":"market",
"tradeDirection":"buy",
"price":{
"price":"0.000000000015742",
"quantity":"1000000000000000",
"timestamp":"1700232025894"
},
"fee":"11.0194",
"executedAt":"1700232025894",
"feeRecipient":"inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh",
"tradeId":"18529043_240_0",
"executionSide":"taker",
"cid":""
},
{
"orderHash":"0xa3049ebaa97ac3755c09bd53ea30e86b98aef40bf037cb9d91dedfc1fd4b7735",
"subaccountId":"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
"marketId":"0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe",
"tradeExecutionType":"limitMatchNewOrder",
"tradeDirection":"buy",
"price":{
"price":"0.000000000015874",
"quantity":"21000000000000000000",
"timestamp":"1700221121919"
},
"fee":"233347.8",
"executedAt":"1700221121919",
"feeRecipient":"inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8",
"tradeId":"18523837_243_0",
"executionSide":"taker",
"cid":""
}
]
}
{
"trades": [
{
"order_hash": "0xbf5cf18a5e73c61d465a60ca550c5fbe0ed37b9ca0a49f7bd1de012e983fe55e",
"subaccount_id": "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000",
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"trade_execution_type": "limitFill",
"trade_direction": "sell",
"price": {
"price": "0.000000000002305",
"quantity": "1000000000000000000",
"timestamp": 1652809734211
},
"fee": "2305",
"executed_at": 1652809734211,
"fee_recipient": "inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r"
},
{
"order_hash": "0xfd474dc696dc291bca8ca1b371653994fd846a303c08d26ccc17a7b60939d256",
"subaccount_id": "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000",
"market_id": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0",
"trade_execution_type": "limitFill",
"trade_direction": "sell",
"price": {
"price": "0.000000000002318",
"quantity": "4000000000000000000",
"timestamp": 1652773190338
},
"fee": "9272",
"executed_at": 1652773190338,
"fee_recipient": "inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r"
}
]
}
| Parameter | Type | Description |
|---|---|---|
| trades | SpotTrade array | List of spot market trades |
SpotTrade
| Parameter | Type | Description |
|---|---|---|
| order_hash | string | Maker order hash. |
| subaccount_id | string | The subaccountId that executed the trade |
| market_id | string | The ID of the market that this trade is in |
| trade_execution_type | string | The execution type of the trade |
| trade_direction | string | The direction the trade |
| price | PriceLevel | Price level at which trade has been executed |
| fee | string | The fee associated with the trade (quote asset denom) |
| executed_at | int64 | Timestamp of trade execution in UNIX millis |
| fee_recipient | string | Fee recipient address |
| trade_id | string | A unique string that helps differentiate between trades |
| execution_side | string | Trade's execution side, marker/taker |
| cid | string | Custom client order ID |
PriceLevel
| Parameter | Type | Description |
|---|---|---|
| price | string | Price number of the price level. |
| quantity | string | Quantity of the price level. |
| timestamp | int64 | Price level last updated timestamp in UNIX millis. |
- InjectiveDerivativeExchangeRPC
InjectiveDerivativeExchangeRPC defines the gRPC API of the Derivative Exchange provider.
Market
Get details of a single derivative market.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
client = IndexerClient(network)
market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"
market = await client.fetch_derivative_market(market_id=market_id)
print(json.dumps(market, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
)
func main() {
// network := common.LoadNetwork("mainnet", "k8s")
network := common.LoadNetwork("testnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketId := "0x4ca0f92fc28be0c9761326016b5a1a2177dd6375558365116b5bdda9abc229ce"
res, err := exchangeClient.GetDerivativeMarket(ctx, marketId)
if err != nil {
panic(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| market_id | string | MarketId of the market we want to fetch | Yes |
Response Parameters
Response Example:
{
"market":{
"marketId":"0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6",
"marketStatus":"active",
"ticker":"INJ/USDT PERP",
"oracleBase":"0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3",
"oracleQuote":"0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588",
"oracleType":"pyth",
"oracleScaleFactor":6,
"initialMarginRatio":"0.05",
"maintenanceMarginRatio":"0.02",
"quoteDenom":"peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5",
"quoteTokenMeta":{
"name":"Testnet Tether USDT",
"address":"0x0000000000000000000000000000000000000000",
"symbol":"USDT",
"logo":"https://static.alchemyapi.io/images/assets/825.png",
"decimals":6,
"updatedAt":"1698247516758"
},
"makerFeeRate":"-0.0001",
"takerFeeRate":"0.001",
"serviceProviderFee":"0.4",
"isPerpetual":true,
"minPriceTickSize":"100",
"minQuantityTickSize":"0.0001",
"perpetualMarketInfo":{
"hourlyFundingRateCap":"0.000625",
"hourlyInterestRate":"0.00000416666",
"nextFundingTimestamp":"1701990000",
"fundingInterval":"3600"
},
"perpetualMarketFunding":{
"cumulativeFunding":"-156010.283874921534910863",
"cumulativePrice":"566.477789213654772072",
"lastTimestamp":"1701906508"
},
"min_notional":"1000000"
}
}
{
"market": {
"market_id": "0x4ca0f92fc28be0c9761326016b5a1a2177dd6375558365116b5bdda9abc229ce",
"market_status": "active",
"ticker": "BTC/USDT PERP",
"oracle_base": "BTC",
"oracle_quote": "USDT",
"oracle_type": "bandibc",
"oracle_scale_factor": 6,
"initial_margin_ratio": "0.095",
"maintenance_margin_ratio": "0.05",
"quote_denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"quote_token_meta": {
"name": "Tether",
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"symbol": "USDT",
"logo": "https://static.alchemyapi.io/images/assets/825.png",
"decimals": 6,
"updated_at": 1650978923435
},
"maker_fee_rate": "0.0005",
"taker_fee_rate": "0.0012",
"service_provider_fee": "0.4",
"is_perpetual": true,
"min_price_tick_size": "100000",
"min_quantity_tick_size": "0.0001",
"perpetual_market_info": {
"hourly_funding_rate_cap": "0.000625",
"hourly_interest_rate": "0.00000416666",
"next_funding_timestamp": 1652864400,
"funding_interval": 3600
},
"perpetual_market_funding": {
"cumulative_funding": "7246105747.050586213851272386",
"cumulative_price": "31.114148427047982579",
"last_timestamp": 1652793510
},
"min_notional": "1000000"
}
}
| Parameter | Type | Description |
|---|---|---|
| market | DerivativeMarketInfo | Info about particular derivative market |
DerivativeMarketInfo
| Parameter | Type | Description |
|---|---|---|
| market_id | string | DerivativeMarket ID is crypto.Keccak256Hash([]byte((oracleType.String() + ticker + quoteDenom + oracleBase + oracleQuote))) for perpetual markets and crypto.Keccak256Hash([]byte((oracleType.String() + ticker + quoteDenom + oracleBase + oracleQuote + strconv.Itoa(int(expiry))))) for expiry futures markets |
| market_status | string | The status of the market |
| ticker | string | A name of the pair in format AAA/BBB, where AAA is base asset, BBB is quote asset. |
| oracle_base | string | Oracle base currency |
| oracle_quote | string | Oracle quote currency |
| oracle_type | string | Oracle Type |
| oracle_scale_factor | uint32 | OracleScaleFactor |
| initial_margin_ratio | string | Defines the initial margin ratio of a derivative market |
| maintenance_margin_ratio | string | Defines the maintenance margin ratio of a derivative market |
| quote_denom | string | Coin denom used for the quote asset. |
| quote_token_meta | TokenMeta | Token metadata for quote asset |
| maker_fee_rate | string | Defines the fee percentage makers pay when trading (in quote asset) |
| taker_fee_rate | string | Defines the fee percentage takers pay when trading (in quote asset) |
| service_provider_fee | string | Percentage of the transaction fee shared with the service provider |
| is_perpetual | bool | True if the market is a perpetual swap market |
| min_price_tick_size | string | Defines the minimum required tick size for the order's price |
| min_quantity_tick_size | string | Defines the minimum required tick size for the order's quantity |
| perpetual_market_info | PerpetualMarketInfo | |
| perpetual_market_funding | PerpetualMarketFunding | |
| expiry_futures_market_info | ExpiryFuturesMarketInfo | |
| min_notional | string | Minimum notional value for the order |
| reduce_margin_ratio | string | Defines the reduce margin ratio of a derivative market |
| open_notional_cap | OpenNotionalCap | The open notional cap of the market, if any |
TokenMeta
| Parameter | Type | Description |
|---|---|---|
| name | string | Token full name |
| address | string | Token contract address (native or not) |
| symbol | string | Token symbol short name |
| logo | string | URL to the logo image |
| decimals | int32 | Token decimals |
| updated_at | int64 | Token metadata fetched timestamp in UNIX millis. |
PerpetualMarketInfo
| Parameter | Type | Description |
|---|---|---|
| hourly_funding_rate_cap | string | Defines the default maximum absolute value of the hourly funding rate of the perpetual market. |
| hourly_interest_rate | string | Defines the hourly interest rate of the perpetual market. |
| next_funding_timestamp | int64 | Defines the next funding timestamp in seconds of a perpetual market in UNIX seconds. |
| funding_interval | int64 | Defines the funding interval in seconds of a perpetual market in seconds. |
PerpetualMarketFunding
| Parameter | Type | Description |
|---|---|---|
| cumulative_funding | string | Defines the cumulative funding of a perpetual market. |
| cumulative_price | string | Defines defines the cumulative price for the current hour up to the last timestamp. |
| last_timestamp | int64 | Defines the last funding timestamp in seconds of a perpetual market in UNIX seconds. |
| last_funding_rate | string | Defines the last funding rate of a perpetual market. |
ExpiryFuturesMarketInfo
| Parameter | Type | Description |
|---|---|---|
| expiration_timestamp | int64 | Defines the expiration time for a time expiry futures market in UNIX seconds. |
| settlement_price | string | Defines the settlement price for a time expiry futures market. |
Markets
Get a list of one or more derivative markets.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
import json
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
client = IndexerClient(network)
market_statuses = ["active"]
quote_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"
market = await client.fetch_derivative_markets(market_statuses=market_statuses, quote_denom=quote_denom)
print(json.dumps(market, indent=2))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/InjectiveLabs/sdk-go/client/common"
exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange"
derivativeExchangePB "github.com/InjectiveLabs/sdk-go/exchange/derivative_exchange_rpc/pb"
)
func main() {
// network := common.LoadNetwork("mainnet", "k8s")
network := common.LoadNetwork("mainnet", "lb")
exchangeClient, err := exchangeclient.NewExchangeClient(network)
if err != nil {
panic(err)
}
ctx := context.Background()
marketStatus := "active"
quoteDenom := "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7"
req := derivativeExchangePB.MarketsRequest{
MarketStatus: marketStatus,
QuoteDenom: quoteDenom,
}
res, err := exchangeClient.GetDerivativeMarkets(ctx, &req)
if err != nil {
panic(err)
}
str, _ := json.MarshalIndent(res, "", "\t")
fmt.Print(string(str))
}
| Parameter | Type | Description | Required |
|---|---|---|---|
| market_status | string | Filter by market status | Yes |
| quote_denom | string | Filter by the Coin denomination of the quote currency | Yes |
| market_statuses | string array | Yes |
Response Parameters
Response Example:
[
{
"marketId": "0x1c79dac019f73e4060494ab1b4fcba734350656d6fc4d474f6a238c13c6f9ced",
"marketStatus": "active",
"ticker": "BNB/USDT PERP",
"oracleBase": "BNB",
"oracleQuote": "USDT",
"oracleType": "bandibc",
"oracleScaleFactor": 6,
"initialMarginRatio": "0.095",
"maintenanceMarginRatio": "0.05",
"quoteDenom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"quoteTokenMeta": {
"name": "Tether",
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"symbol": "USDT",
"logo": "https://static.alchemyapi.io/images/assets/825.png",
"decimals": 6,
"updatedAt": 1650978923353
},
"makerFeeRate": "0.0005",
"takerFeeRate": "0.0012",
"serviceProviderFee": "0.4",
"isPerpetual": true,
"minPriceTickSize": "10000",
"minQuantityTickSize": "0.01",
"perpetualMarketInfo": {
"hourlyFundingRateCap": "0.000625",
"hourlyInterestRate": "0.00000416666",
"nextFundingTimestamp": 1654246800,
"fundingInterval": 3600
},
"perpetualMarketFunding": {
"cumulativeFunding": "56890491.178246679699729639",
"cumulativePrice": "7.082760891515203314",
"lastTimestamp": 1654245985
},
"min_notional": "1000000",
},
{
"marketId": "0x00030df39180df04a873cb4aadc50d4135640af5c858ab637dbd4d31b147478c",
"marketStatus": "active",
"ticker": "Frontrunner Futures: Expires 5.21.2023",
"oracleBase": "FRNT",
"oracleQuote": "USDT",
"oracleType": "pricefeed",
"oracleScaleFactor": 6,
"initialMarginRatio": "0.999999999999999999",
"maintenanceMarginRatio": "0.1",
"quoteDenom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"quoteTokenMeta": {
"name": "Tether",
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"symbol": "USDT",
"logo": "https://static.alchemyapi.io/images/assets/825.png",
"decimals": 6,
"updatedAt": 1653064108501
},
"makerFeeRate": "0.005",
"takerFeeRate": "0.012",
"serviceProviderFee": "0.4",
"isPerpetual": false,
"minPriceTickSize": "0.000000000000001",
"minQuantityTickSize": "0.0001",
"expiryFuturesMarketInfo": {
"expirationTimestamp": 1684600043,
"settlementPrice": "0"
},
"min_notional": "0"
}
]
{
"markets": [
{
"market_id": "0x1c79dac019f73e4060494ab1b4fcba734350656d6fc4d474f6a238c13c6f9ced",
"market_status": "active",
"ticker": "BNB/USDT PERP",
"oracle_base": "BNB",
"oracle_quote": "USDT",
"oracle_type": "bandibc",
"oracle_scale_factor": 6,
"initial_margin_ratio": "0.095",
"maintenance_margin_ratio": "0.05",
"quote_denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"quote_token_meta": {
"name": "Tether",
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"symbol": "USDT",
"logo": "https://static.alchemyapi.io/images/assets/825.png",
"decimals": 6,
"updated_at": 1650978923353
},
"maker_fee_rate": "0.0005",
"taker_fee_rate": "0.0012",
"service_provider_fee": "0.4",
"is_perpetual": true,
"min_price_tick_size": "10000",
"min_quantity_tick_size": "0.01",
"perpetual_market_info": {
"hourly_funding_rate_cap": "0.000625",
"hourly_interest_rate": "0.00000416666",
"next_funding_timestamp": 1652864400,
"funding_interval": 3600
},
"perpetual_market_funding": {
"cumulative_funding": "48248742.484852568471323698",
"cumulative_price": "5.691379282523162906",
"last_timestamp": 1652775374
},
"min_notional": "1000000"
},
{
"market_id": "0xfb5f14852bd01af901291dd2aa65e997b3a831f957124a7fe7aa40d218ff71ae",
"market_status": "active",
"ticker": "XAG/USDT PERP",
"oracle_base": "XAG",
"oracle_quote": "USDT",
"oracle_type": "bandibc",
"oracle_scale_factor": 6,
"initial_margin_ratio": "0.8",
"maintenance_margin_ratio": "0.4",
"quote_denom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7",
"quote_token_meta": {
"name": "Tether",
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"symbol": "USDT",
"logo": "https://static.alchemyapi.io/images/assets/825.png",
"decimals": 6,
"updated_at": 1650978923534
},
"maker_fee_rate": "0.003",
"taker_fee_rate": "0.005",
"service_provider_fee": "0.4",
"is_perpetual": true,
"min_price_tick_size": "10000",
"min_quantity_tick_size": "0.01",
"perpetual_market_info": {
"hourly_funding_rate_cap": "0.000625",
"hourly_interest_rate": "0.00000416666",
"next_funding_timestamp": 1652864400,
"funding_interval": 3600
},
"perpetual_market_funding": {
"cumulative_funding": "1099659.417190990913058692",
"cumulative_price": "-4.427475055338306767",
"last_timestamp": 1652775322
},
"min_notional": "0",
}
]
}
| Parameter | Type | Description |
|---|---|---|
| markets | DerivativeMarketInfo array | Derivative Markets list |
DerivativeMarketInfo
| Parameter | Type | Description |
|---|---|---|
| market_id | string | DerivativeMarket ID is crypto.Keccak256Hash([]byte((oracleType.String() + ticker + quoteDenom + oracleBase + oracleQuote))) for perpetual markets and crypto.Keccak256Hash([]byte((oracleType.String() + ticker + quoteDenom + oracleBase + oracleQuote + strconv.Itoa(int(expiry))))) for expiry futures markets |
| market_status | string | The status of the market |
| ticker | string | A name of the pair in format AAA/BBB, where AAA is base asset, BBB is quote asset. |
| oracle_base | string | Oracle base currency |
| oracle_quote | string | Oracle quote currency |
| oracle_type | string | Oracle Type |
| oracle_scale_factor | uint32 | OracleScaleFactor |
| initial_margin_ratio | string | Defines the initial margin ratio of a derivative market |
| maintenance_margin_ratio | string | Defines the maintenance margin ratio of a derivative market |
| quote_denom | string | Coin denom used for the quote asset. |
| quote_token_meta | TokenMeta | Token metadata for quote asset |
| maker_fee_rate | string | Defines the fee percentage makers pay when trading (in quote asset) |
| taker_fee_rate | string | Defines the fee percentage takers pay when trading (in quote asset) |
| service_provider_fee | string | Percentage of the transaction fee shared with the service provider |
| is_perpetual | bool | True if the market is a perpetual swap market |
| min_price_tick_size | string | Defines the minimum required tick size for the order's price |
| min_quantity_tick_size | string | Defines the minimum required tick size for the order's quantity |
| perpetual_market_info | PerpetualMarketInfo | |
| perpetual_market_funding | PerpetualMarketFunding | |
| expiry_futures_market_info | ExpiryFuturesMarketInfo | |
| min_notional | string | Minimum notional value for the order |
| reduce_margin_ratio | string | Defines the reduce margin ratio of a derivative market |
| open_notional_cap | OpenNotionalCap | The open notional cap of the market, if any |
PerpetualMarketInfo
| Parameter | Type | Description |
|---|---|---|
| hourly_funding_rate_cap | string | Defines the default maximum absolute value of the hourly funding rate of the perpetual market. |
| hourly_interest_rate | string | Defines the hourly interest rate of the perpetual market. |
| next_funding_timestamp | int64 | Defines the next funding timestamp in seconds of a perpetual market in UNIX seconds. |
| funding_interval | int64 | Defines the funding interval in seconds of a perpetual market in seconds. |
PerpetualMarketFunding
| Parameter | Type | Description |
|---|---|---|
| cumulative_funding | string | Defines the cumulative funding of a perpetual market. |
| cumulative_price | string | Defines defines the cumulative price for the current hour up to the last timestamp. |
| last_timestamp | int64 | Defines the last funding timestamp in seconds of a perpetual market in UNIX seconds. |
| last_funding_rate | string | Defines the last funding rate of a perpetual market. |
ExpiryFuturesMarketInfo
| Parameter | Type | Description |
|---|---|---|
| expiration_timestamp | int64 | Defines the expiration time for a time expiry futures market in UNIX seconds. |
| settlement_price | string | Defines the settlement price for a time expiry futures market. |
TokenMeta
| Parameter | Type | Description |
|---|---|---|
| name | string | Token full name |
| address | string | Token contract address (native or not) |
| symbol | string | Token symbol short name |
| logo | string | URL to the logo image |
| decimals | int32 | Token decimals |
| updated_at | int64 | Token metadata fetched timestamp in UNIX millis. |
StreamMarkets
Stream live updates of derivative markets.
IP rate limit group: indexer
Request Parameters
Request Example:
import asyncio
from typing import Any, Dict
from grpc import RpcError
from pyinjective.core.network import Network
from pyinjective.indexer_client import IndexerClient
async def market_event_processor(event: Dict[str, Any]):
print(event)
def stream_error_processor(exception: RpcError):
print(f"There was an error listening to derivative markets updates ({exception})")
def stream_closed_processor():
print("The derivative markets updates stream has been closed")
async def main() -> None:
# select network: local, testnet, mainnet
network = Network.testnet()
client = IndexerClient(network)
<