Rapport de bug : ordre SELL agressif ne se fait jamais matcher (INJ/USDC spot)
Environnement
injective-py version 1.16.0
- Réseau : Mainnet
- SDK helper :
MsgBroadcasterWithPk avec MessageBasedTransactionFeeCalculator
- Marché : INJ/USDC
market_id: 0xa8c14f892f7f7d2516442220a05b652d5afee3f57a5495981dfad7c99ef78e84
base_token (INJ) decimals: 18
quote_token (USDC, erc20:0xa00C59fF5a080D2b954d0c75e46E22a0c371235a) decimals: 6
- Marché confirmé
status: Active
Résumé du problème
Un ordre MsgCreateSpotLimitOrder de type SELL, placé à un prix délibérément et massivement inférieur au meilleur bid existant (jusqu'à -50% du best bid dans nos tests), ne se fait jamais matcher/exécuter, même après plusieurs minutes d'attente. L'ordre reste fillable == quantity (0% rempli) indéfiniment jusqu'à annulation manuelle.
Ce comportement est reproductible de façon systématique (testé 4 fois, avec des paramètres de prix différents, toujours le même résultat).
Étapes de reproduction
- Récupérer le carnet d'ordres réel via
client.fetch_chain_spot_orderbook(market_id=market_id)
- Noter le meilleur bid (
buysPriceLevel[0])
- Construire un ordre SELL avec un prix nettement inférieur à ce bid (donc normalement immédiatement "marketable"/exécutable contre ce bid)
- Envoyer via
composer.msg_create_spot_limit_order(..., order_type=2) (SELL) puis broadcaster.broadcast([msg])
- Vérifier
fetch_chain_trader_spot_orders toutes les 20 secondes pendant 3 minutes
Résultat observé (dernier test, le plus complet)
Best Bid (source chain): $5.322 (raw: confirmé cohérent entre fetch_chain_spot_orderbook ET indexer.fetch_spot_orderbook_v2)
Notre prix SELL: $4.790 (~10% sous le best bid, donc censé matcher immédiatement)
Transaction de placement : broadcast retourne code: 0 (accepté), la commande consomme bien du gas réel (transaction incluse dans un bloc, vérifié via fetch_tx).
Suivi sur 3 minutes (vérifications toutes les 20s via fetch_chain_trader_spot_orders) :
[20s] fillable=5000000000000000000 / quantity=5000000000000000000
[40s] fillable=5000000000000000000 / quantity=5000000000000000000
[60s] fillable=5000000000000000000 / quantity=5000000000000000000
[80s] fillable=5000000000000000000 / quantity=5000000000000000000
[100s] fillable=5000000000000000000 / quantity=5000000000000000000
[120s] fillable=5000000000000000000 / quantity=5000000000000000000
[140s] fillable=5000000000000000000 / quantity=5000000000000000000
[160s] fillable=5000000000000000000 / quantity=5000000000000000000
[180s] fillable=5000000000000000000 / quantity=5000000000000000000
Aucune évolution. L'ordre reste intégralement non rempli malgré un prix qui devrait, sur un carnet d'ordres central classique, provoquer une exécution immédiate contre le bid existant.
Vérifications effectuées pour exclure les causes évidentes
| Hypothèse testée |
Résultat |
| Prix mal encodé (mauvais facteur de décimales) |
Exclu — comparaison en valeurs brutes (raw, sans conversion humaine) confirme que notre prix 4.7952e30 est bien inférieur au bid 5.328e30 (même échelle) |
| Bids "fantômes"/obsolètes dans le carnet |
Exclu — fetch_chain_spot_orderbook (source directe blockchain) et indexer.fetch_spot_orderbook_v2 (indexer) sont cohérents entre eux sur les mêmes niveaux de prix, avec des timestamps récents |
| Solde insuffisant |
Exclu — le SELL utilise de l'INJ déjà déposé et disponible dans le subaccount (availableBalance suffisant, confirmé avant chaque test) |
| Tick size non respecté |
Exclu — prix arrondi explicitement au tick de 0.001 avant envoi |
| Manque de patience / latence de bloc |
Exclu — attente de 3 minutes complètes (largement supérieur au temps de bloc d'Injective) sans aucune évolution |
| Ordre mal formé / rejeté silencieusement |
Exclu — transaction confirmée code: 0, incluse dans un bloc réel (height non nul, gasUsed non nul via fetch_tx), et l'ordre apparaît bien comme fillable dans fetch_chain_trader_spot_orders juste après placement |
Transactions de test (dernière série, mainnet)
- TX de placement de l'ordre SELL agressif (10% sous le bid) :
8B2AA0931D9C1968A298757F68DA35BE0E1AB905ECDFC05483A7CFEEBD2A0472
- TX d'annulation correspondante : disponible sur demande
Code minimal de reproduction
import asyncio
from decimal import Decimal
from pyinjective.async_client_v2 import AsyncClient
from pyinjective.core.network import Network
from pyinjective.core.broadcaster import (
MsgBroadcasterWithPk, StandardAccountBroadcasterConfig,
MessageBasedTransactionFeeCalculator,
)
from pyinjective.wallet import PrivateKey
async def main():
network = Network.mainnet()
client = AsyncClient(network)
composer = await client.composer()
private_key = PrivateKey.from_mnemonic("VOTRE_MNEMONIC")
address = private_key.to_public_key().to_address()
sender = address.to_acc_bech32()
account_config = StandardAccountBroadcasterConfig(private_key=private_key.to_hex())
fee_calculator = MessageBasedTransactionFeeCalculator(client=client, composer=composer)
broadcaster = MsgBroadcasterWithPk(
network=network, account_config=account_config,
client=client, fee_calculator=fee_calculator,
)
subaccount_id = address.get_subaccount_id(1)
market_id = "0xa8c14f892f7f7d2516442220a05b652d5afee3f57a5495981dfad7c99ef78e84"
orderbook = await client.fetch_chain_spot_orderbook(market_id=market_id)
best_bid_human = Decimal(orderbook["buysPriceLevel"][0]["p"]) / Decimal(10**18)
# Prix volontairement très agressif (10% sous le bid)
aggressive_price_human = (best_bid_human * Decimal("0.9")).quantize(Decimal("0.001"))
price_correction_factor = Decimal(10) ** 12 # base_decimals(18) - quote_decimals(6)
price_corrected = aggressive_price_human * price_correction_factor
await client.fetch_account(sender)
msg = composer.msg_create_spot_limit_order(
sender=sender, market_id=market_id, subaccount_id=subaccount_id,
fee_recipient=sender, price=price_corrected, quantity=Decimal("5"), order_type=2,
)
result = await broadcaster.broadcast([msg])
print(result)
# Observer: l'ordre reste fillable==quantity indéfiniment
await asyncio.sleep(60)
orders = await client.fetch_chain_trader_spot_orders(market_id=market_id, subaccount_id=subaccount_id)
print(orders)
asyncio.run(main())
Question pour l'équipe Injective
Pourquoi un ordre SELL marketable (prix nettement inférieur au meilleur bid existant) ne se fait-il jamais matcher automatiquement contre ce bid, même après plusieurs minutes ? Y a-t-il :
- Un mécanisme de traitement par lots (Frequent Batch Auction) qui nécessite une condition de déclenchement spécifique qu'on ne remplit pas ?
- Une règle anti-manipulation ou de protection spécifique aux nouveaux comptes/subaccounts ?
- Une étape supplémentaire dans le flux d'ordre qu'on aurait manquée dans la documentation du SDK Python ?
Merci d'avance pour votre aide !
Rapport de bug : ordre SELL agressif ne se fait jamais matcher (INJ/USDC spot)
Environnement
injective-pyversion 1.16.0MsgBroadcasterWithPkavecMessageBasedTransactionFeeCalculatormarket_id:0xa8c14f892f7f7d2516442220a05b652d5afee3f57a5495981dfad7c99ef78e84base_token(INJ) decimals: 18quote_token(USDC, erc20:0xa00C59fF5a080D2b954d0c75e46E22a0c371235a) decimals: 6status: ActiveRésumé du problème
Un ordre
MsgCreateSpotLimitOrderde type SELL, placé à un prix délibérément et massivement inférieur au meilleur bid existant (jusqu'à -50% du best bid dans nos tests), ne se fait jamais matcher/exécuter, même après plusieurs minutes d'attente. L'ordre restefillable == quantity(0% rempli) indéfiniment jusqu'à annulation manuelle.Ce comportement est reproductible de façon systématique (testé 4 fois, avec des paramètres de prix différents, toujours le même résultat).
Étapes de reproduction
client.fetch_chain_spot_orderbook(market_id=market_id)buysPriceLevel[0])composer.msg_create_spot_limit_order(..., order_type=2)(SELL) puisbroadcaster.broadcast([msg])fetch_chain_trader_spot_orderstoutes les 20 secondes pendant 3 minutesRésultat observé (dernier test, le plus complet)
Transaction de placement :
broadcastretournecode: 0(accepté), la commande consomme bien du gas réel (transaction incluse dans un bloc, vérifié viafetch_tx).Suivi sur 3 minutes (vérifications toutes les 20s via
fetch_chain_trader_spot_orders) :Aucune évolution. L'ordre reste intégralement non rempli malgré un prix qui devrait, sur un carnet d'ordres central classique, provoquer une exécution immédiate contre le bid existant.
Vérifications effectuées pour exclure les causes évidentes
Transactions de test (dernière série, mainnet)
8B2AA0931D9C1968A298757F68DA35BE0E1AB905ECDFC05483A7CFEEBD2A0472Code minimal de reproduction
Question pour l'équipe Injective
Pourquoi un ordre SELL marketable (prix nettement inférieur au meilleur bid existant) ne se fait-il jamais matcher automatiquement contre ce bid, même après plusieurs minutes ? Y a-t-il :
Merci d'avance pour votre aide !