Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

## [Unreleased] - 9999-99-99

## [1.12.0] - 2025-11-10
### Changed
- Updated all compiled protos for compatibility with Injective core v1.17.0 and Indexer v1.17.16

## [1.11.2] - 2025-09-24
### Added
- Added support in v2 Composer to create the new exchange module MsgCancelPostOnlyMode message
Expand Down
7 changes: 1 addition & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,14 @@ fix-generated-proto-imports:
@find ./pyinjective/proto -type f -name "*.py" -exec sed -i "" -e "s/from google.api/from pyinjective.proto.google.api/g" {} \;

define clean_repos
rm -Rf cosmos-sdk
rm -Rf ibc-go
rm -Rf cometbft
rm -Rf wasmd
rm -Rf injective-core
rm -Rf injective-indexer
endef
Comment on lines 21 to 23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that cleanup consolidation doesn't break the build workflow.

The removal of cosmos-sdk, ibc-go, cometbft, wasmd, and injective-core from clean_repos appears intentional, given that clone-all now only clones injective-indexer. However, confirm that:

  1. All necessary proto dependencies are now available in injective-indexer v1.17.0-beta
  2. No other build targets or workflows reference the removed repositories
  3. buf.gen.yaml does not reference the removed repos

🏁 Script executed:

#!/bin/bash
# Verify that buf.gen.yaml and the build process don't reference removed repos

echo "=== Checking buf.gen.yaml for references to removed repos ==="
if [ -f buf.gen.yaml ]; then
  grep -i "cosmos-sdk\|ibc-go\|cometbft\|wasmd" buf.gen.yaml || echo "No references found (OK)"
else
  echo "buf.gen.yaml not found"
fi

echo ""
echo "=== Checking for any clone targets for removed repos in Makefile ==="
grep -n "clone-" Makefile | grep -v "clone-all\|clone-injective-indexer" || echo "No other clone targets found (OK)"

echo ""
echo "=== Checking for any references to removed repos in Makefile ==="
grep -i "cosmos-sdk\|ibc-go\|cometbft\|wasmd\|injective-core" Makefile || echo "No references found (OK)"

Length of output: 618


I found a critical inconsistency. Let me verify the build process to confirm the impact.

Now let me verify how the build process uses buf.gen.yaml:

Let me search the Makefile for how proto generation is invoked:These results don't show how the specific repository uses buf.gen.yaml in the Makefile. Let me search the Makefile more directly for the actual proto target:

[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Look for proto, gen, or buf references in Makefile

echo "=== Full Makefile content (first 50 lines) ==="
head -n 50 Makefile

echo ""
echo "=== Lines mentioning proto, gen, buf, or pb ==="
grep -n -i "proto\|gen\|buf\|\.pb" Makefile || echo "No matches found"

echo ""
echo "=== Looking for all targets in Makefile ==="
grep -n "^[a-z-]*:" Makefile | head -20

Length of output: 2412


buf.gen.yaml still references removed repositories—this will break the build.

The Makefile cleanup removes cloning of cosmos-sdk, ibc-go, cometbft, and wasmd, but buf.gen.yaml (line 7, invoked via buf generate --template buf.gen.yaml) still contains active git_repo entries for these same repositories:

  • https://github.com/InjectiveLabs/ibc-go
  • https://github.com/InjectiveLabs/wasmd
  • https://github.com/InjectiveLabs/cometbft
  • https://github.com/InjectiveLabs/cosmos-sdk

Additionally, PROTO_MODULES (line 12) still references modules corresponding to these removed repos (cometbft, ibc, cosmos, tendermint).

Either:

  1. Update buf.gen.yaml to remove the git_repo entries for these repositories, OR
  2. Verify that injective-indexer v1.17.0-beta bundles all necessary proto files and update buf.gen.yaml accordingly
🤖 Prompt for AI Agents
In Makefile around lines 21 to 23, the cleanup target removes cloning of
external repos but buf.gen.yaml (used by buf generate at line 7) still
references git_repo entries for cosmos-sdk, ibc-go, cometbft and wasmd and
PROTO_MODULES still lists modules from those repos; update buf.gen.yaml to
either remove those git_repo entries and modules or replace them with the
bundled injective-indexer v1.17.0-beta proto sources if that bundle truly
contains all required proto files—verify bundle completeness, then remove the
obsolete git_repo entries and corresponding PROTO_MODULES or update them to
point to the bundled module names so buf generate no longer expects the removed
repositories.


clean-all:
$(call clean_repos)

clone-injective-indexer:
git clone https://github.com/InjectiveLabs/injective-indexer.git -b v1.16.91 --depth 1 --single-branch
git clone https://github.com/InjectiveLabs/injective-indexer.git -b v1.17.16 --depth 1 --single-branch

clone-all: clone-injective-indexer

Expand Down
13 changes: 8 additions & 5 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ inputs:
- module: buf.build/googleapis/googleapis
- module: buf.build/cosmos/ics23
- git_repo: https://github.com/InjectiveLabs/ibc-go
tag: v8.7.0-evm-comet1-inj
tag: v8.7.0-inj.3
- git_repo: https://github.com/InjectiveLabs/wasmd
tag: v0.53.3-evm-comet1-inj
tag: v0.53.3-inj.2
- git_repo: https://github.com/InjectiveLabs/cometbft
tag: v1.0.1-inj.3
tag: v1.0.1-inj.4
- git_repo: https://github.com/InjectiveLabs/cosmos-sdk
tag: v0.50.13-evm-comet1-inj.6
tag: v0.50.14-inj
# - git_repo: https://github.com/InjectiveLabs/wasmd
# branch: v0.51.x-inj
# subdir: proto
- git_repo: https://github.com/InjectiveLabs/hyperlane-cosmos
tag: v1.0.1-inj
subdir: proto
- git_repo: https://github.com/InjectiveLabs/injective-core
tag: v1.16.4
tag: v1.17.0
subdir: proto
# - git_repo: https://github.com/InjectiveLabs/injective-core
# branch: master
Expand Down
8 changes: 8 additions & 0 deletions examples/chain_client/7_ChainStream.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ async def main() -> None:
subaccount_ids=[subaccount_id], market_ids=[inj_usdt_perp_market]
)
oracle_price_filter = composer.chain_stream_oracle_price_filter(symbols=["INJ", "USDT"])
order_failures_filter = composer.chain_stream_order_failures_filter(
accounts=["inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r"]
)
conditional_order_trigger_failures_filter = composer.chain_stream_conditional_order_trigger_failures_filter(
subaccount_ids=[subaccount_id], market_ids=[inj_usdt_perp_market]
)

task = asyncio.get_event_loop().create_task(
client.listen_chain_stream_updates(
Expand All @@ -66,6 +72,8 @@ async def main() -> None:
derivative_orderbooks_filter=derivative_orderbooks_filter,
positions_filter=positions_filter,
oracle_price_filter=oracle_price_filter,
order_failures_filter=order_failures_filter,
conditional_order_trigger_failures_filter=conditional_order_trigger_failures_filter,
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ async def main() -> None:
min_price_tick_size=Decimal("0.01"),
min_quantity_tick_size=Decimal("0.01"),
min_notional=Decimal("1"),
open_notional_cap=composer.uncapped_open_notional_cap(),
)

# broadcast the transaction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ async def main() -> None:
new_initial_margin_ratio=Decimal("0.40"),
new_maintenance_margin_ratio=Decimal("0.085"),
new_reduce_margin_ratio=Decimal("3.5"),
new_open_notional_cap=composer.uncapped_open_notional_cap(),
)

# broadcast the transaction
Expand Down
68 changes: 68 additions & 0 deletions examples/chain_client/exchange/30_MsgOffsetPosition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import asyncio
import json
import os

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()
configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY")

# select network: local, testnet, mainnet
network = Network.testnet()

# initialize grpc client
client = AsyncClient(network)
await client.initialize_tokens_from_chain_denoms()
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=configured_private_key,
gas_price=gas_price,
client=client,
composer=composer,
)

# load account
priv_key = PrivateKey.from_hex(configured_private_key)
pub_key = priv_key.to_public_key()
address = pub_key.to_address()
await client.fetch_account(address.to_acc_bech32())

offsetting_subaccount_ids = {
"0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000",
"0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
}

# prepare tx msg
message = composer.msg_offset_position(
sender=address.to_acc_bech32(),
subaccount_id=address.get_subaccount_id(index=0),
market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6",
offsetting_subaccount_ids=offsetting_subaccount_ids,
)

# broadcast the transaction
result = await message_broadcaster.broadcast([message])
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())
Comment on lines +1 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Prevent runtime errors; use list for repeated field; round gas price up.

  • Validate INJECTIVE_PRIVATE_KEY before use.
  • Pass a list (not a set) for offsetting_subaccount_ids to preserve order and match type hints.
  • Use ceil when bumping gas price to avoid rounding down.
  • Prefer asyncio.run for simplicity.
@@
-import asyncio
+import asyncio
+import math
 import json
 import os
@@
     dotenv.load_dotenv()
-    configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY")
+    configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY")
+    if not configured_private_key:
+        raise ValueError("INJECTIVE_PRIVATE_KEY env var is required for this example.")
@@
-    gas_price = int(gas_price * 1.1)
+    gas_price = math.ceil(gas_price * 1.1)
@@
-    offsetting_subaccount_ids = {
+    offsetting_subaccount_ids = [
         "0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000",
         "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
-    }
+    ]
@@
-    gas_price = int(gas_price * 1.1)
+    gas_price = math.ceil(gas_price * 1.1)
@@
-if __name__ == "__main__":
-    asyncio.get_event_loop().run_until_complete(main())
+if __name__ == "__main__":
+    asyncio.run(main())

Optional: document python-dotenv as a requirement for running examples.



---

🏁 Script executed:

```shell
#!/bin/bash
# Find the file and verify it exists
fd -t f "30_MsgOffsetPosition.py"

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Search for msg_offset_position method definition to check parameter types
rg -n "def msg_offset_position" -A 10 --type=py

Length of output: 866


🏁 Script executed:

#!/bin/bash
# Check env var validation patterns in other examples
rg -n "getenv\(" examples/ -B 2 -A 2 --type=py | head -60

Length of output: 4230


🏁 Script executed:

#!/bin/bash
# Look for requirements or setup files documenting python-dotenv
fd -t f -E "*.pyc" "\.(txt|cfg|toml|yaml|yml)$" | xargs rg -l "python-dotenv|dotenv" 2>/dev/null | head -20

Length of output: 14


🏁 Script executed:

#!/bin/bash
# Check other examples for gas price calculation patterns
rg -n "gas_price.*\*|math\.ceil" examples/ -B 2 -A 2 --type=py | head -80

Length of output: 7566


🏁 Script executed:

#!/bin/bash
# Check for asyncio patterns - asyncio.run vs get_event_loop
rg -n "asyncio\.(run|get_event_loop)" examples/ --type=py

Length of output: 39792


Fix set-to-list conversion for repeated field; validate env var to prevent crashes.

The code passes a set for offsetting_subaccount_ids, but the composer method signature requires List[str]. This type mismatch will cause runtime errors. Additionally, missing INJECTIVE_PRIVATE_KEY environment variable will crash without validation.

  • Convert offsetting_subaccount_ids from set {...} to list [...].
  • Validate INJECTIVE_PRIVATE_KEY is not None before use.

Optional improvements noted in original review (gas price rounding, asyncio.run) diverge from current codebase patterns but are reasonable modernizations for consistency across future examples.

@@
+import math
 import os
 import dotenv
@@
-    configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY")
+    configured_private_key = os.getenv("INJECTIVE_PRIVATE_KEY")
+    if not configured_private_key:
+        raise ValueError("INJECTIVE_PRIVATE_KEY env var is required for this example.")
@@
-    offsetting_subaccount_ids = {
+    offsetting_subaccount_ids = [
         "0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000",
         "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000",
-    }
+    ]
🤖 Prompt for AI Agents
In examples/chain_client/exchange/30_MsgOffsetPosition.py around lines 1 to 68,
the example constructs offsetting_subaccount_ids as a Python set and passes it
to composer.msg_offset_position which expects a List[str], and it uses
INJECTIVE_PRIVATE_KEY without validating it; change the
offsetting_subaccount_ids to a list literal (square brackets) so the repeated
field receives a list of strings, and add a guard after reading
INJECTIVE_PRIVATE_KEY to check it is not None/empty and exit or raise a clear
error if missing before using it.

Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ async def main() -> None:
min_price_tick_size=Decimal("0.001"),
min_quantity_tick_size=Decimal("0.01"),
min_notional=Decimal("1"),
open_notional_cap=composer.uncapped_open_notional_cap(),
)

# broadcast the transaction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ async def main() -> None:
min_price_tick_size=Decimal("0.001"),
min_quantity_tick_size=Decimal("0.01"),
min_notional=Decimal("1"),
open_notional_cap=composer.uncapped_open_notional_cap(),
)

# broadcast the transaction
Expand Down
29 changes: 29 additions & 0 deletions examples/chain_client/exchange/9_MsgBatchUpdateOrders.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ async def main() -> None:
),
]

derivative_market_orders_to_create = [
composer.derivative_order(
market_id=derivative_market_id_create,
subaccount_id=subaccount_id,
fee_recipient=fee_recipient,
price=Decimal(25100),
quantity=Decimal(0.1),
margin=composer.calculate_margin(
quantity=Decimal(0.1), price=Decimal(25100), leverage=Decimal(1), is_reduce_only=False
),
Comment on lines +113 to +116
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid Decimal(float); use string literal to prevent FP rounding (RUF032).

-            quantity=Decimal(0.1),
+            quantity=Decimal("0.1"),
@@
-                quantity=Decimal(0.1), price=Decimal(25100), leverage=Decimal(1), is_reduce_only=False
+                quantity=Decimal("0.1"), price=Decimal(25100), leverage=Decimal(1), is_reduce_only=False
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
quantity=Decimal(0.1),
margin=composer.calculate_margin(
quantity=Decimal(0.1), price=Decimal(25100), leverage=Decimal(1), is_reduce_only=False
),
quantity=Decimal("0.1"),
margin=composer.calculate_margin(
quantity=Decimal("0.1"), price=Decimal(25100), leverage=Decimal(1), is_reduce_only=False
),
🧰 Tools
🪛 Ruff (0.14.1)

113-113: Decimal() called with float literal argument

Replace with string literal

(RUF032)


115-115: Decimal() called with float literal argument

Replace with string literal

(RUF032)

🤖 Prompt for AI Agents
In examples/chain_client/exchange/9_MsgBatchUpdateOrders.py around lines 113 to
116, Decimal is being constructed from a float (Decimal(0.1)), which can
introduce FP rounding; replace Decimal(0.1) with a string literal like
Decimal("0.1") (and similarly use Decimal("1") if you want to construct from a
numeric literal consistently) and ensure any Decimal created from non-integer
values uses string literals instead of float arguments.

order_type="BUY",
cid=str(uuid.uuid4()),
),
]

spot_orders_to_create = [
composer.spot_order(
market_id=spot_market_id_create,
Expand All @@ -125,13 +140,27 @@ async def main() -> None:
),
]

spot_market_orders_to_create = [
composer.spot_order(
market_id=spot_market_id_create,
subaccount_id=subaccount_id,
fee_recipient=fee_recipient,
price=Decimal("3.5"),
quantity=Decimal("1"),
order_type="BUY",
cid=str(uuid.uuid4()),
),
]

# prepare tx msg
msg = composer.msg_batch_update_orders(
sender=address.to_acc_bech32(),
derivative_orders_to_create=derivative_orders_to_create,
spot_orders_to_create=spot_orders_to_create,
derivative_orders_to_cancel=derivative_orders_to_cancel,
spot_orders_to_cancel=spot_orders_to_cancel,
spot_market_orders_to_create=spot_market_orders_to_create,
derivative_market_orders_to_create=derivative_market_orders_to_create,
)

# broadcast the transaction
Expand Down
24 changes: 24 additions & 0 deletions examples/chain_client/exchange/query/66_OpenInterest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import asyncio
import json

from pyinjective.async_client_v2 import AsyncClient
from pyinjective.core.network import Network


async def main() -> None:
"""
Demonstrate fetching denom min notionals using AsyncClient.
"""
Comment on lines +9 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix example docstring to match behavior.

-    """
-    Demonstrate fetching denom min notionals using AsyncClient.
-    """
+    """
+    Demonstrate fetching open interest using AsyncClient.
+    """
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"""
Demonstrate fetching denom min notionals using AsyncClient.
"""
"""
Demonstrate fetching open interest using AsyncClient.
"""
🤖 Prompt for AI Agents
In examples/chain_client/exchange/query/66_OpenInterest.py around lines 9-11,
the module docstring currently says "Demonstrate fetching denom min notionals
using AsyncClient" which doesn't match the file's purpose; update the docstring
to accurately describe the script (e.g., "Demonstrate fetching open interest
using AsyncClient" or similar), ensure it references AsyncClient only if the
code is asynchronous, and keep it brief and aligned with the implemented
behavior.

# Select network: choose between Network.mainnet(), Network.testnet(), or Network.devnet()
network = Network.testnet()

# Initialize the Async Client
client = AsyncClient(network)

market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"
open_interest = await client.fetch_open_interest(market_id=market_id)
print(json.dumps(open_interest, indent=2))


if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ async def main() -> None:
# initialize grpc client
client = AsyncClient(network)

deposits = await client.fetch_denom_decimal(denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5")
deposits = await client.fetch_auction_exchange_transfer_denom_decimal(
denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"
)
print(deposits)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ async def main() -> None:
# initialize grpc client
client = AsyncClient(network)

deposits = await client.fetch_denom_decimals(denoms=["inj", "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"])
deposits = await client.fetch_auction_exchange_transfer_denom_decimals(
denoms=["inj", "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"]
)
print(deposits)


Expand Down
Loading