diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d855fe0..8d470639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,15 @@ All notable changes to this project will be documented in this file. -## [1.0.1] - 2023-11-07 +## [1.0.1] - 2023-12-11 +### Added +- Added low level API components for all modules (chain, exchain and explorer) to make the Python SDK compatible with the TypeScript SDK. + ### Changed - Updated proto definitions to injective-core v1.12.2-testnet and injective-indexer v1.12.45-rc3 +- Added new functions in AsyncClient to interact with chain, exchange and explorer using the low level API components +- Marked old function sin AsyncClient as deprecated (the functions will be removed in a future version) +- Updated all API examples to use the new AsyncClient functions ## [1.0] - 2023-11-01 ### Added diff --git a/examples/chain_client/0_LocalOrderHash.py b/examples/chain_client/0_LocalOrderHash.py index e731be69..c81c3e2e 100644 --- a/examples/chain_client/0_LocalOrderHash.py +++ b/examples/chain_client/0_LocalOrderHash.py @@ -22,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) subaccount_id_2 = address.get_subaccount_id(index=1) @@ -118,7 +118,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) @@ -155,7 +155,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) @@ -245,7 +245,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/13_MsgIncreasePositionMargin.py b/examples/chain_client/13_MsgIncreasePositionMargin.py index 530be2b4..27d42884 100644 --- a/examples/chain_client/13_MsgIncreasePositionMargin.py +++ b/examples/chain_client/13_MsgIncreasePositionMargin.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -48,14 +50,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -69,7 +72,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/15_MsgWithdraw.py b/examples/chain_client/15_MsgWithdraw.py index 83a516be..9fc1e359 100644 --- a/examples/chain_client/15_MsgWithdraw.py +++ b/examples/chain_client/15_MsgWithdraw.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare tx msg @@ -39,14 +41,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -60,7 +63,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/16_MsgSubaccountTransfer.py b/examples/chain_client/16_MsgSubaccountTransfer.py index 4f7b94c9..5a18b1dd 100644 --- a/examples/chain_client/16_MsgSubaccountTransfer.py +++ b/examples/chain_client/16_MsgSubaccountTransfer.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) dest_subaccount_id = address.get_subaccount_id(index=1) @@ -46,14 +48,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -67,7 +70,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/17_MsgBatchUpdateOrders.py b/examples/chain_client/17_MsgBatchUpdateOrders.py index e832197e..25147452 100644 --- a/examples/chain_client/17_MsgBatchUpdateOrders.py +++ b/examples/chain_client/17_MsgBatchUpdateOrders.py @@ -1,6 +1,8 @@ import asyncio import uuid +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -131,18 +133,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -156,7 +159,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/18_MsgBid.py b/examples/chain_client/18_MsgBid.py index 30274761..9e51e075 100644 --- a/examples/chain_client/18_MsgBid.py +++ b/examples/chain_client/18_MsgBid.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg msg = composer.MsgBid(sender=address.to_acc_bech32(), round=16250, bid_amount=1) @@ -38,14 +40,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -59,7 +62,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/19_MsgGrant.py b/examples/chain_client/19_MsgGrant.py index 7c0630dd..0d00d489 100644 --- a/examples/chain_client/19_MsgGrant.py +++ b/examples/chain_client/19_MsgGrant.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # subaccount_id = address.get_subaccount_id(index=0) # market_ids = ["0x0511ddc4e6586f3bfe1acb2dd905f8b8a82c97e1edaef654b12ca7e6031ca0fa"] @@ -57,14 +59,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -78,7 +81,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/1_MsgSend.py b/examples/chain_client/1_MsgSend.py index e6801ea0..9b9651f7 100644 --- a/examples/chain_client/1_MsgSend.py +++ b/examples/chain_client/1_MsgSend.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg msg = composer.MsgSend( @@ -43,14 +45,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -64,7 +67,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/20_MsgExec.py b/examples/chain_client/20_MsgExec.py index 8da619cd..a2876d6a 100644 --- a/examples/chain_client/20_MsgExec.py +++ b/examples/chain_client/20_MsgExec.py @@ -1,6 +1,8 @@ import asyncio import uuid +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("5d386fbdbf11f1141010f81a46b40f94887367562bd33b452bbaa6ce1cd1381e") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" @@ -56,20 +58,23 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) - data = sim_res_msg[0] - unpacked_msg_res = composer.UnpackMsgExecResponse(msg_type=msg0.__class__.__name__, data=data) + sim_res_msgs = sim_res["result"]["msgResponses"] + data = sim_res_msgs[0] + unpacked_msg_res = composer.unpack_msg_exec_response( + underlying_msg_type=msg0.__class__.__name__, msg_exec_response=data + ) print("simulation msg response") print(unpacked_msg_res) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -83,7 +88,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/21_MsgRevoke.py b/examples/chain_client/21_MsgRevoke.py index f73a4ba5..2cafab7f 100644 --- a/examples/chain_client/21_MsgRevoke.py +++ b/examples/chain_client/21_MsgRevoke.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg msg = composer.MsgRevoke( @@ -42,14 +44,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -63,7 +66,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/22_MsgSendToEth.py b/examples/chain_client/22_MsgSendToEth.py index f37515f8..1cb1e677 100644 --- a/examples/chain_client/22_MsgSendToEth.py +++ b/examples/chain_client/22_MsgSendToEth.py @@ -1,6 +1,7 @@ import asyncio import requests +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE @@ -22,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare msg asset = "injective-protocol" @@ -53,14 +54,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -74,7 +76,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/23_MsgRelayPriceFeedPrice.py b/examples/chain_client/23_MsgRelayPriceFeedPrice.py index db4c9243..38b389de 100644 --- a/examples/chain_client/23_MsgRelayPriceFeedPrice.py +++ b/examples/chain_client/23_MsgRelayPriceFeedPrice.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) price = 100 price_to_send = [str(int(price * 10**18))] @@ -43,14 +45,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -64,7 +67,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/24_MsgRewardsOptOut.py b/examples/chain_client/24_MsgRewardsOptOut.py index edba421e..bbc8d348 100644 --- a/examples/chain_client/24_MsgRewardsOptOut.py +++ b/examples/chain_client/24_MsgRewardsOptOut.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg msg = composer.MsgRewardsOptOut(sender=address.to_acc_bech32()) @@ -38,14 +40,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -59,7 +62,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/25_MsgDelegate.py b/examples/chain_client/25_MsgDelegate.py index f8314399..dc95f035 100644 --- a/examples/chain_client/25_MsgDelegate.py +++ b/examples/chain_client/25_MsgDelegate.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg validator_address = "injvaloper1ultw9r29l8nxy5u6thcgusjn95vsy2caw722q5" @@ -43,14 +45,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -64,7 +67,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/26_MsgWithdrawDelegatorReward.py b/examples/chain_client/26_MsgWithdrawDelegatorReward.py index 5e6e982f..77210421 100644 --- a/examples/chain_client/26_MsgWithdrawDelegatorReward.py +++ b/examples/chain_client/26_MsgWithdrawDelegatorReward.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg validator_address = "injvaloper1ultw9r29l8nxy5u6thcgusjn95vsy2caw722q5" @@ -42,14 +44,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -63,7 +66,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/27_Grants.py b/examples/chain_client/27_Grants.py index dca8ea42..331980af 100644 --- a/examples/chain_client/27_Grants.py +++ b/examples/chain_client/27_Grants.py @@ -10,7 +10,7 @@ async def main() -> None: granter = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" grantee = "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" msg_type_url = "/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder" - authorizations = await client.get_grants(granter=granter, grantee=grantee, msg_type_url=msg_type_url) + authorizations = await client.fetch_grants(granter=granter, grantee=grantee, msg_type_url=msg_type_url) print(authorizations) diff --git a/examples/chain_client/28_BankBalances.py b/examples/chain_client/28_BankBalances.py index 4e6ec895..6ead7b13 100644 --- a/examples/chain_client/28_BankBalances.py +++ b/examples/chain_client/28_BankBalances.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) address = "inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r" - all_bank_balances = await client.get_bank_balances(address=address) + all_bank_balances = await client.fetch_bank_balances(address=address) print(all_bank_balances) diff --git a/examples/chain_client/29_BankBalance.py b/examples/chain_client/29_BankBalance.py index 959d1c72..af17dfac 100644 --- a/examples/chain_client/29_BankBalance.py +++ b/examples/chain_client/29_BankBalance.py @@ -9,7 +9,7 @@ async def main() -> None: client = AsyncClient(network) address = "inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r" denom = "inj" - bank_balance = await client.get_bank_balance(address=address, denom=denom) + bank_balance = await client.fetch_bank_balance(address=address, denom=denom) print(bank_balance) diff --git a/examples/chain_client/2_MsgDeposit.py b/examples/chain_client/2_MsgDeposit.py index 8afa3d60..9c137b49 100644 --- a/examples/chain_client/2_MsgDeposit.py +++ b/examples/chain_client/2_MsgDeposit.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare tx msg @@ -39,14 +41,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -60,7 +63,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/30_ExternalTransfer.py b/examples/chain_client/30_ExternalTransfer.py index 9d98fbd1..d657c15a 100644 --- a/examples/chain_client/30_ExternalTransfer.py +++ b/examples/chain_client/30_ExternalTransfer.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) dest_subaccount_id = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000" @@ -46,14 +48,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -67,7 +70,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py b/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py index a2e6e714..137529ea 100644 --- a/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py +++ b/examples/chain_client/31_MsgCreateBinaryOptionsLimitOrder.py @@ -1,6 +1,8 @@ import asyncio import uuid +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -22,7 +24,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -59,18 +61,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -84,7 +87,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py b/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py index 98645c7f..67004f17 100644 --- a/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py +++ b/examples/chain_client/32_MsgCreateBinaryOptionsMarketOrder.py @@ -1,6 +1,8 @@ import asyncio import uuid +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -53,18 +55,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -78,7 +81,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py b/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py index 9ae1d009..2631ab26 100644 --- a/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py +++ b/examples/chain_client/33_MsgCancelBinaryOptionsOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -44,18 +46,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -69,7 +72,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py b/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py index cf7a6356..d9eb1e18 100644 --- a/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py +++ b/examples/chain_client/34_MsgAdminUpdateBinaryOptionsMarket.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare trade info market_id = "0xfafec40a7b93331c1fc89c23f66d11fbb48f38dfdd78f7f4fc4031fad90f6896" @@ -52,18 +54,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -77,7 +80,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py b/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py index 8e043d64..e0881db5 100644 --- a/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py +++ b/examples/chain_client/35_MsgInstantBinaryOptionsMarketLaunch.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg msg = composer.MsgInstantBinaryOptionsMarketLaunch( @@ -54,18 +56,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -79,7 +82,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/36_MsgRelayProviderPrices.py b/examples/chain_client/36_MsgRelayProviderPrices.py index 0fe33f7f..0fb26b4f 100644 --- a/examples/chain_client/36_MsgRelayProviderPrices.py +++ b/examples/chain_client/36_MsgRelayProviderPrices.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) provider = "ufc" symbols = ["KHABIB-TKO-05/30/2023", "KHABIB-TKO-05/26/2023"] @@ -44,18 +46,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -69,7 +72,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/37_GetTx.py b/examples/chain_client/37_GetTx.py index ece40b65..f645dba7 100644 --- a/examples/chain_client/37_GetTx.py +++ b/examples/chain_client/37_GetTx.py @@ -7,8 +7,8 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) - tx_hash = "7746BC12EB82B4D59D036FBFF2F67BDCA6F62A20B3DBC25661707DD61D4DC1B7" - tx_logs = await client.get_tx(tx_hash=tx_hash) + tx_hash = "D265527E3171C47D01D7EC9B839A95F8F794D4E683F26F5564025961C96EFDDA" + tx_logs = await client.fetch_tx(hash=tx_hash) print(tx_logs) diff --git a/examples/chain_client/39_Account.py b/examples/chain_client/39_Account.py index 43692ba2..ed193834 100644 --- a/examples/chain_client/39_Account.py +++ b/examples/chain_client/39_Account.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) address = "inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr" - acc = await client.get_account(address=address) + acc = await client.fetch_account(address=address) print(acc) diff --git a/examples/chain_client/3_MsgCreateSpotLimitOrder.py b/examples/chain_client/3_MsgCreateSpotLimitOrder.py index 95ad7970..8d873354 100644 --- a/examples/chain_client/3_MsgCreateSpotLimitOrder.py +++ b/examples/chain_client/3_MsgCreateSpotLimitOrder.py @@ -1,6 +1,8 @@ import asyncio import uuid +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -55,18 +57,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -80,7 +83,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/40_MsgExecuteContract.py b/examples/chain_client/40_MsgExecuteContract.py index 677d9cc2..fef42954 100644 --- a/examples/chain_client/40_MsgExecuteContract.py +++ b/examples/chain_client/40_MsgExecuteContract.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg # NOTE: COIN MUST BE SORTED IN ALPHABETICAL ORDER BY DENOMS @@ -53,14 +55,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -74,7 +77,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/41_MsgCreateInsuranceFund.py b/examples/chain_client/41_MsgCreateInsuranceFund.py index d287638c..b402dc26 100644 --- a/examples/chain_client/41_MsgCreateInsuranceFund.py +++ b/examples/chain_client/41_MsgCreateInsuranceFund.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) msg = composer.MsgCreateInsuranceFund( sender=address.to_acc_bech32(), @@ -47,14 +49,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -68,7 +71,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/42_MsgUnderwrite.py b/examples/chain_client/42_MsgUnderwrite.py index 6764e933..9a14b3ce 100644 --- a/examples/chain_client/42_MsgUnderwrite.py +++ b/examples/chain_client/42_MsgUnderwrite.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) msg = composer.MsgUnderwrite( sender=address.to_acc_bech32(), @@ -43,14 +45,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -64,7 +67,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/43_MsgRequestRedemption.py b/examples/chain_client/43_MsgRequestRedemption.py index db3f31fe..6ba0fe31 100644 --- a/examples/chain_client/43_MsgRequestRedemption.py +++ b/examples/chain_client/43_MsgRequestRedemption.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) msg = composer.MsgRequestRedemption( sender=address.to_acc_bech32(), @@ -43,14 +45,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -64,7 +67,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py b/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py index 879f705a..b46ca371 100644 --- a/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py +++ b/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.composer import Composer as ProtoMsgComposer from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE @@ -23,7 +25,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) # prepare tx msg validator_address = "injvaloper1ultw9r29l8nxy5u6thcgusjn95vsy2caw722q5" @@ -47,14 +49,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -68,7 +71,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/49_ChainStream.py b/examples/chain_client/49_ChainStream.py index b4c60213..05a5bbdc 100644 --- a/examples/chain_client/49_ChainStream.py +++ b/examples/chain_client/49_ChainStream.py @@ -1,12 +1,25 @@ import asyncio +from typing import Any, Dict -from google.protobuf import json_format +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.composer import Composer from pyinjective.core.network import Network +async def chain_stream_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to chain stream updates ({exception})") + + +def stream_closed_processor(): + print("The chain stream updates stream has been closed") + + async def main() -> None: network = Network.testnet() @@ -38,24 +51,27 @@ 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"]) - stream = await client.chain_stream( - bank_balances_filter=bank_balances_filter, - subaccount_deposits_filter=subaccount_deposits_filter, - spot_trades_filter=spot_trades_filter, - derivative_trades_filter=derivative_trades_filter, - spot_orders_filter=spot_orders_filter, - derivative_orders_filter=derivative_orders_filter, - spot_orderbooks_filter=spot_orderbooks_filter, - derivative_orderbooks_filter=derivative_orderbooks_filter, - positions_filter=positions_filter, - oracle_price_filter=oracle_price_filter, - ) - async for event in stream: - print( - json_format.MessageToJson( - message=event, including_default_value_fields=True, preserving_proto_field_name=True - ) + + task = asyncio.get_event_loop().create_task( + client.listen_chain_stream_updates( + callback=chain_stream_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + bank_balances_filter=bank_balances_filter, + subaccount_deposits_filter=subaccount_deposits_filter, + spot_trades_filter=spot_trades_filter, + derivative_trades_filter=derivative_trades_filter, + spot_orders_filter=spot_orders_filter, + derivative_orders_filter=derivative_orders_filter, + spot_orderbooks_filter=spot_orderbooks_filter, + derivative_orderbooks_filter=derivative_orderbooks_filter, + positions_filter=positions_filter, + oracle_price_filter=oracle_price_filter, ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/chain_client/4_MsgCreateSpotMarketOrder.py b/examples/chain_client/4_MsgCreateSpotMarketOrder.py index a721fed5..d689362f 100644 --- a/examples/chain_client/4_MsgCreateSpotMarketOrder.py +++ b/examples/chain_client/4_MsgCreateSpotMarketOrder.py @@ -1,6 +1,8 @@ import asyncio import uuid +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -53,18 +55,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -78,7 +81,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/5_MsgCancelSpotOrder.py b/examples/chain_client/5_MsgCancelSpotOrder.py index fdb43f12..a6fabc01 100644 --- a/examples/chain_client/5_MsgCancelSpotOrder.py +++ b/examples/chain_client/5_MsgCancelSpotOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -45,14 +47,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -66,7 +69,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py b/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py index c50e1340..b875d339 100644 --- a/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py +++ b/examples/chain_client/6_MsgCreateDerivativeLimitOrder.py @@ -1,6 +1,8 @@ import asyncio import uuid +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -55,18 +57,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -80,7 +83,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py b/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py index b8bc6b8a..744b5ff9 100644 --- a/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py +++ b/examples/chain_client/7_MsgCreateDerivativeMarketOrder.py @@ -1,6 +1,8 @@ import asyncio import uuid +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -21,7 +23,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -54,18 +56,19 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return - sim_res_msg = composer.MsgResponses(sim_res, simulation=True) + sim_res_msg = sim_res["result"]["msgResponses"] print("---Simulation Response---") print(sim_res_msg) # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -79,7 +82,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print("---Transaction Response---") print(res) print("gas wanted: {}".format(gas_limit)) diff --git a/examples/chain_client/8_MsgCancelDerivativeOrder.py b/examples/chain_client/8_MsgCancelDerivativeOrder.py index 98c648c7..b784b536 100644 --- a/examples/chain_client/8_MsgCancelDerivativeOrder.py +++ b/examples/chain_client/8_MsgCancelDerivativeOrder.py @@ -1,5 +1,7 @@ import asyncio +from grpc import RpcError + from pyinjective.async_client import AsyncClient from pyinjective.constant import GAS_FEE_BUFFER_AMOUNT, GAS_PRICE from pyinjective.core.network import Network @@ -20,7 +22,7 @@ async def main() -> None: priv_key = PrivateKey.from_hex("f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3") pub_key = priv_key.to_public_key() address = pub_key.to_address() - await client.get_account(address.to_acc_bech32()) + await client.fetch_account(address.to_acc_bech32()) subaccount_id = address.get_subaccount_id(index=0) # prepare trade info @@ -45,14 +47,15 @@ async def main() -> None: sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key) # simulate tx - (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) - if not success: - print(sim_res) + try: + sim_res = await client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + print(ex) return # build tx gas_price = GAS_PRICE - gas_limit = sim_res.gas_info.gas_used + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation + gas_limit = int(sim_res["gasInfo"]["gasUsed"]) + GAS_FEE_BUFFER_AMOUNT # add buffer for gas fee computation gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") fee = [ composer.Coin( @@ -66,7 +69,7 @@ async def main() -> None: tx_raw_bytes = tx.get_tx_data(sig, pub_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode - res = await client.send_tx_sync_mode(tx_raw_bytes) + res = await client.broadcast_tx_sync_mode(tx_raw_bytes) print(res) print("gas wanted: {}".format(gas_limit)) print("gas fee: {} INJ".format(gas_fee)) diff --git a/examples/exchange_client/accounts_rpc/1_StreamSubaccountBalance.py b/examples/exchange_client/accounts_rpc/1_StreamSubaccountBalance.py index 9a0364eb..2a9e9c23 100644 --- a/examples/exchange_client/accounts_rpc/1_StreamSubaccountBalance.py +++ b/examples/exchange_client/accounts_rpc/1_StreamSubaccountBalance.py @@ -1,18 +1,41 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +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 = AsyncClient(network) subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001" denoms = ["inj", "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"] - subaccount = await client.stream_subaccount_balance(subaccount_id=subaccount_id, denoms=denoms) - async for balance in subaccount: - print("Subaccount balance Update:\n") - print(balance) + 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__": diff --git a/examples/exchange_client/accounts_rpc/2_SubaccountBalance.py b/examples/exchange_client/accounts_rpc/2_SubaccountBalance.py index dd48b9f3..ecec1061 100644 --- a/examples/exchange_client/accounts_rpc/2_SubaccountBalance.py +++ b/examples/exchange_client/accounts_rpc/2_SubaccountBalance.py @@ -9,7 +9,7 @@ async def main() -> None: client = AsyncClient(network) subaccount_id = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000" denom = "inj" - balance = await client.get_subaccount_balance(subaccount_id=subaccount_id, denom=denom) + balance = await client.fetch_subaccount_balance(subaccount_id=subaccount_id, denom=denom) print(balance) diff --git a/examples/exchange_client/accounts_rpc/3_SubaccountsList.py b/examples/exchange_client/accounts_rpc/3_SubaccountsList.py index af523e2d..c7243509 100644 --- a/examples/exchange_client/accounts_rpc/3_SubaccountsList.py +++ b/examples/exchange_client/accounts_rpc/3_SubaccountsList.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) account_address = "inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt" - subacc_list = await client.get_subaccount_list(account_address) + subacc_list = await client.fetch_subaccounts_list(account_address) print(subacc_list) diff --git a/examples/exchange_client/accounts_rpc/4_SubaccountBalancesList.py b/examples/exchange_client/accounts_rpc/4_SubaccountBalancesList.py index 501d327e..7df3be17 100644 --- a/examples/exchange_client/accounts_rpc/4_SubaccountBalancesList.py +++ b/examples/exchange_client/accounts_rpc/4_SubaccountBalancesList.py @@ -9,7 +9,7 @@ async def main() -> None: client = AsyncClient(network) subaccount = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000" denoms = ["inj", "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5"] - subacc_balances_list = await client.get_subaccount_balances_list(subaccount_id=subaccount, denoms=denoms) + subacc_balances_list = await client.fetch_subaccount_balances_list(subaccount_id=subaccount, denoms=denoms) print(subacc_balances_list) diff --git a/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py b/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py index e67d2ab6..a71951cf 100644 --- a/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py +++ b/examples/exchange_client/accounts_rpc/5_SubaccountHistory.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -13,8 +14,12 @@ async def main() -> None: skip = 1 limit = 15 end_time = 1665118340224 - subacc_history = await client.get_subaccount_history( - subaccount_id=subaccount, denom=denom, transfer_types=transfer_types, skip=skip, limit=limit, end_time=end_time + 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(subacc_history) diff --git a/examples/exchange_client/accounts_rpc/6_SubaccountOrderSummary.py b/examples/exchange_client/accounts_rpc/6_SubaccountOrderSummary.py index 9026e4e1..a150cd22 100644 --- a/examples/exchange_client/accounts_rpc/6_SubaccountOrderSummary.py +++ b/examples/exchange_client/accounts_rpc/6_SubaccountOrderSummary.py @@ -10,7 +10,7 @@ async def main() -> None: subaccount = "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000" order_direction = "buy" market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - subacc_order_summary = await client.get_subaccount_order_summary( + subacc_order_summary = await client.fetch_subaccount_order_summary( subaccount_id=subaccount, order_direction=order_direction, market_id=market_id ) print(subacc_order_summary) diff --git a/examples/exchange_client/accounts_rpc/7_OrderStates.py b/examples/exchange_client/accounts_rpc/7_OrderStates.py index e78dc842..038d42d5 100644 --- a/examples/exchange_client/accounts_rpc/7_OrderStates.py +++ b/examples/exchange_client/accounts_rpc/7_OrderStates.py @@ -15,7 +15,7 @@ async def main() -> None: "0x82113f3998999bdc3892feaab2c4e53ba06c5fe887a2d5f9763397240f24da50", "0xbb1f036001378cecb5fff1cc69303919985b5bf058c32f37d5aaf9b804c07a06", ] - orders = await client.get_order_states( + orders = await client.fetch_order_states( spot_order_hashes=spot_order_hashes, derivative_order_hashes=derivative_order_hashes ) print(orders) diff --git a/examples/exchange_client/accounts_rpc/8_Portfolio.py b/examples/exchange_client/accounts_rpc/8_Portfolio.py index 0a922054..90cd9565 100644 --- a/examples/exchange_client/accounts_rpc/8_Portfolio.py +++ b/examples/exchange_client/accounts_rpc/8_Portfolio.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" - portfolio = await client.get_portfolio(account_address=account_address) + portfolio = await client.fetch_portfolio(account_address=account_address) print(portfolio) diff --git a/examples/exchange_client/accounts_rpc/9_Rewards.py b/examples/exchange_client/accounts_rpc/9_Rewards.py index b3a486cf..10012c2a 100644 --- a/examples/exchange_client/accounts_rpc/9_Rewards.py +++ b/examples/exchange_client/accounts_rpc/9_Rewards.py @@ -7,12 +7,9 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) - # account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" + account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" epoch = -1 - rewards = await client.get_rewards( - # account_address=account_address, - epoch=epoch - ) + rewards = await client.fetch_rewards(account_address=account_address, epoch=epoch) print(rewards) diff --git a/examples/exchange_client/auctions_rpc/1_Auction.py b/examples/exchange_client/auctions_rpc/1_Auction.py index 94f73e16..71fbd36c 100644 --- a/examples/exchange_client/auctions_rpc/1_Auction.py +++ b/examples/exchange_client/auctions_rpc/1_Auction.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) bid_round = 31 - auction = await client.get_auction(bid_round=bid_round) + auction = await client.fetch_auction(round=bid_round) print(auction) diff --git a/examples/exchange_client/auctions_rpc/2_Auctions.py b/examples/exchange_client/auctions_rpc/2_Auctions.py index 30576a89..f2b7f7bf 100644 --- a/examples/exchange_client/auctions_rpc/2_Auctions.py +++ b/examples/exchange_client/auctions_rpc/2_Auctions.py @@ -8,7 +8,7 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - auctions = await client.get_auctions() + auctions = await client.fetch_auctions() print(auctions) diff --git a/examples/exchange_client/auctions_rpc/3_StreamBids.py b/examples/exchange_client/auctions_rpc/3_StreamBids.py index 8306becb..8bfceba1 100644 --- a/examples/exchange_client/auctions_rpc/3_StreamBids.py +++ b/examples/exchange_client/auctions_rpc/3_StreamBids.py @@ -1,16 +1,39 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def bid_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to bids updates ({exception})") + + +def stream_closed_processor(): + print("The bids updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - bids = await client.stream_bids() - async for bid in bids: - print(bid) + + task = asyncio.get_event_loop().create_task( + client.listen_bids_updates( + callback=bid_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/10_StreamHistoricalOrders.py b/examples/exchange_client/derivative_exchange_rpc/10_StreamHistoricalOrders.py index b58448d2..140639fe 100644 --- a/examples/exchange_client/derivative_exchange_rpc/10_StreamHistoricalOrders.py +++ b/examples/exchange_client/derivative_exchange_rpc/10_StreamHistoricalOrders.py @@ -1,17 +1,41 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +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 derivative orders history updates ({exception})") + + +def stream_closed_processor(): + print("The derivative orders history updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - orders = await client.stream_historical_derivative_orders(market_id=market_id) - async for order in orders: - print(order) + + task = asyncio.get_event_loop().create_task( + client.listen_derivative_orders_history_updates( + callback=order_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + market_id=market_id, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/11_Trades.py b/examples/exchange_client/derivative_exchange_rpc/11_Trades.py index 5ae822a9..3befd828 100644 --- a/examples/exchange_client/derivative_exchange_rpc/11_Trades.py +++ b/examples/exchange_client/derivative_exchange_rpc/11_Trades.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -8,9 +9,14 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - subaccount_id = "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000" - trades = await client.get_derivative_trades(market_id=market_id, subaccount_id=subaccount_id) + market_ids = ["0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"] + subaccount_ids = ["0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000"] + skip = 0 + limit = 4 + pagination = PaginationOption(skip=skip, limit=limit) + trades = await client.fetch_derivative_trades( + market_ids=market_ids, subaccount_ids=subaccount_ids, pagination=pagination + ) print(trades) diff --git a/examples/exchange_client/derivative_exchange_rpc/12_StreamTrades.py b/examples/exchange_client/derivative_exchange_rpc/12_StreamTrades.py index 8d6246bb..9ccf11de 100644 --- a/examples/exchange_client/derivative_exchange_rpc/12_StreamTrades.py +++ b/examples/exchange_client/derivative_exchange_rpc/12_StreamTrades.py @@ -1,21 +1,46 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +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 trades updates ({exception})") + + +def stream_closed_processor(): + print("The derivative trades updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) market_ids = [ "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", - "0xd5e4b12b19ecf176e4e14b42944731c27677819d2ed93be4104ad7025529c7ff", + "0x70bc8d7feab38b23d5fdfb12b9c3726e400c265edbcbf449b6c80c31d63d3a02", ] - subaccount_id = "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000" - trades = await client.stream_derivative_trades(market_id=market_ids[0], subaccount_id=subaccount_id) - async for trade in trades: - print(trade) + subaccount_ids = ["0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000"] + + task = asyncio.get_event_loop().create_task( + client.listen_derivative_trades_updates( + callback=market_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/13_SubaccountOrdersList.py b/examples/exchange_client/derivative_exchange_rpc/13_SubaccountOrdersList.py index a7f06db7..d219a0a2 100644 --- a/examples/exchange_client/derivative_exchange_rpc/13_SubaccountOrdersList.py +++ b/examples/exchange_client/derivative_exchange_rpc/13_SubaccountOrdersList.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -12,8 +13,9 @@ async def main() -> None: market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" skip = 1 limit = 2 - orders = await client.get_derivative_subaccount_orders( - subaccount_id=subaccount_id, market_id=market_id, skip=skip, limit=limit + pagination = PaginationOption(skip=skip, limit=limit) + orders = await client.fetch_subaccount_orders_list( + subaccount_id=subaccount_id, market_id=market_id, pagination=pagination ) print(orders) diff --git a/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py b/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py index 4cd55f06..ed90a456 100644 --- a/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py +++ b/examples/exchange_client/derivative_exchange_rpc/14_SubaccountTradesList.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -14,13 +15,13 @@ async def main() -> None: direction = "sell" skip = 10 limit = 2 - trades = await client.get_derivative_subaccount_trades( + pagination = PaginationOption(skip=skip, limit=limit) + trades = await client.fetch_derivative_subaccount_trades_list( subaccount_id=subaccount_id, market_id=market_id, execution_type=execution_type, direction=direction, - skip=skip, - limit=limit, + pagination=pagination, ) print(trades) diff --git a/examples/exchange_client/derivative_exchange_rpc/15_FundingPayments.py b/examples/exchange_client/derivative_exchange_rpc/15_FundingPayments.py index 8f352c9e..5321d723 100644 --- a/examples/exchange_client/derivative_exchange_rpc/15_FundingPayments.py +++ b/examples/exchange_client/derivative_exchange_rpc/15_FundingPayments.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -8,15 +9,16 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" + market_ids = ["0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"] subaccount_id = "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000" skip = 0 limit = 3 end_time = 1676426400125 - funding = await client.get_funding_payments( - market_id=market_id, subaccount_id=subaccount_id, skip=skip, limit=limit, end_time=end_time + pagination = PaginationOption(skip=skip, limit=limit, end_time=end_time) + funding_payments = await client.fetch_funding_payments( + market_ids=market_ids, subaccount_id=subaccount_id, pagination=pagination ) - print(funding) + print(funding_payments) if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/17_FundingRates.py b/examples/exchange_client/derivative_exchange_rpc/17_FundingRates.py index 2a8cbcd1..f7bc3b7f 100644 --- a/examples/exchange_client/derivative_exchange_rpc/17_FundingRates.py +++ b/examples/exchange_client/derivative_exchange_rpc/17_FundingRates.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -12,7 +13,8 @@ async def main() -> None: skip = 0 limit = 3 end_time = 1675717201465 - funding_rates = await client.get_funding_rates(market_id=market_id, skip=skip, limit=limit, end_time=end_time) + pagination = PaginationOption(skip=skip, limit=limit, end_time=end_time) + funding_rates = await client.fetch_funding_rates(market_id=market_id, pagination=pagination) print(funding_rates) diff --git a/examples/exchange_client/derivative_exchange_rpc/18_Orderbooks.py b/examples/exchange_client/derivative_exchange_rpc/18_Orderbooks.py deleted file mode 100644 index 49b00e02..00000000 --- a/examples/exchange_client/derivative_exchange_rpc/18_Orderbooks.py +++ /dev/null @@ -1,20 +0,0 @@ -import asyncio - -from pyinjective.async_client import AsyncClient -from pyinjective.core.network import Network - - -async def main() -> None: - # select network: local, testnet, mainnet - network = Network.testnet() - client = AsyncClient(network) - market_ids = [ - "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", - "0xd5e4b12b19ecf176e4e14b42944731c27677819d2ed93be4104ad7025529c7ff", - ] - markets = await client.get_derivative_orderbooks(market_ids=market_ids) - print(markets) - - -if __name__ == "__main__": - asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/exchange_client/derivative_exchange_rpc/19_Binary_Options_Markets.py b/examples/exchange_client/derivative_exchange_rpc/19_Binary_Options_Markets.py index 9ed36e38..97b90bca 100644 --- a/examples/exchange_client/derivative_exchange_rpc/19_Binary_Options_Markets.py +++ b/examples/exchange_client/derivative_exchange_rpc/19_Binary_Options_Markets.py @@ -9,7 +9,7 @@ async def main() -> None: client = AsyncClient(network) market_status = "active" quote_denom = "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7" - market = await client.get_binary_options_markets(market_status=market_status, quote_denom=quote_denom) + market = await client.fetch_binary_options_markets(market_status=market_status, quote_denom=quote_denom) print(market) diff --git a/examples/exchange_client/derivative_exchange_rpc/1_Market.py b/examples/exchange_client/derivative_exchange_rpc/1_Market.py index f0cdf499..dd5b2091 100644 --- a/examples/exchange_client/derivative_exchange_rpc/1_Market.py +++ b/examples/exchange_client/derivative_exchange_rpc/1_Market.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - market = await client.get_derivative_market(market_id=market_id) + market = await client.fetch_derivative_market(market_id=market_id) print(market) diff --git a/examples/exchange_client/derivative_exchange_rpc/20_Binary_Options_Market.py b/examples/exchange_client/derivative_exchange_rpc/20_Binary_Options_Market.py index 6c14d93a..18edbbed 100644 --- a/examples/exchange_client/derivative_exchange_rpc/20_Binary_Options_Market.py +++ b/examples/exchange_client/derivative_exchange_rpc/20_Binary_Options_Market.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x175513943b8677368d138e57bcd6bef53170a0da192e7eaa8c2cd4509b54f8db" - market = await client.get_binary_options_market(market_id=market_id) + market = await client.fetch_binary_options_market(market_id=market_id) print(market) diff --git a/examples/exchange_client/derivative_exchange_rpc/21_Historical_Orders.py b/examples/exchange_client/derivative_exchange_rpc/21_Historical_Orders.py index 9dc22f6f..3e8641ae 100644 --- a/examples/exchange_client/derivative_exchange_rpc/21_Historical_Orders.py +++ b/examples/exchange_client/derivative_exchange_rpc/21_Historical_Orders.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -8,17 +9,17 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" + market_ids = ["0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"] subaccount_id = "0x295639d56c987f0e24d21bb167872b3542a6e05a000000000000000000000000" is_conditional = "false" skip = 10 limit = 3 - orders = await client.get_historical_derivative_orders( - market_id=market_id, + pagination = PaginationOption(skip=skip, limit=limit) + orders = await client.fetch_derivative_orders_history( subaccount_id=subaccount_id, - skip=skip, - limit=limit, + market_ids=market_ids, is_conditional=is_conditional, + pagination=pagination, ) print(orders) diff --git a/examples/exchange_client/derivative_exchange_rpc/22_OrderbooksV2.py b/examples/exchange_client/derivative_exchange_rpc/22_OrderbooksV2.py index 40ae4598..def49ded 100644 --- a/examples/exchange_client/derivative_exchange_rpc/22_OrderbooksV2.py +++ b/examples/exchange_client/derivative_exchange_rpc/22_OrderbooksV2.py @@ -12,7 +12,7 @@ async def main() -> None: "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", "0xd5e4b12b19ecf176e4e14b42944731c27677819d2ed93be4104ad7025529c7ff", ] - orderbooks = await client.get_derivative_orderbooksV2(market_ids=market_ids) + orderbooks = await client.fetch_derivative_orderbooks_v2(market_ids=market_ids) print(orderbooks) diff --git a/examples/exchange_client/derivative_exchange_rpc/23_LiquidablePositions.py b/examples/exchange_client/derivative_exchange_rpc/23_LiquidablePositions.py index e87f67b1..55e76492 100644 --- a/examples/exchange_client/derivative_exchange_rpc/23_LiquidablePositions.py +++ b/examples/exchange_client/derivative_exchange_rpc/23_LiquidablePositions.py @@ -1,8 +1,7 @@ import asyncio -from google.protobuf import json_format - from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -12,18 +11,12 @@ async def main() -> None: market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" skip = 10 limit = 3 - positions = await client.get_derivative_liquidable_positions( + pagination = PaginationOption(skip=skip, limit=limit) + positions = await client.fetch_derivative_liquidable_positions( market_id=market_id, - skip=skip, - limit=limit, - ) - print( - json_format.MessageToJson( - message=positions, - including_default_value_fields=True, - preserving_proto_field_name=True, - ) + pagination=pagination, ) + print(positions) if __name__ == "__main__": diff --git a/examples/exchange_client/derivative_exchange_rpc/2_Markets.py b/examples/exchange_client/derivative_exchange_rpc/2_Markets.py index 949695e1..b5a5999a 100644 --- a/examples/exchange_client/derivative_exchange_rpc/2_Markets.py +++ b/examples/exchange_client/derivative_exchange_rpc/2_Markets.py @@ -8,9 +8,9 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_status = "active" + market_statuses = ["active"] quote_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" - market = await client.get_derivative_markets(market_status=market_status, quote_denom=quote_denom) + market = await client.fetch_derivative_markets(market_statuses=market_statuses, quote_denom=quote_denom) print(market) diff --git a/examples/exchange_client/derivative_exchange_rpc/3_StreamMarket.py b/examples/exchange_client/derivative_exchange_rpc/3_StreamMarket.py index b5b7cbb0..d8663ba2 100644 --- a/examples/exchange_client/derivative_exchange_rpc/3_StreamMarket.py +++ b/examples/exchange_client/derivative_exchange_rpc/3_StreamMarket.py @@ -1,16 +1,39 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +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 = AsyncClient(network) - markets = await client.stream_derivative_markets() - async for market in markets: - print(market) + + task = asyncio.get_event_loop().create_task( + client.listen_derivative_market_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__": diff --git a/examples/exchange_client/derivative_exchange_rpc/4_Orderbook.py b/examples/exchange_client/derivative_exchange_rpc/4_Orderbook.py index 49602701..01443459 100644 --- a/examples/exchange_client/derivative_exchange_rpc/4_Orderbook.py +++ b/examples/exchange_client/derivative_exchange_rpc/4_Orderbook.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - market = await client.get_derivative_orderbook(market_id=market_id) + market = await client.fetch_derivative_orderbook_v2(market_id=market_id) print(market) diff --git a/examples/exchange_client/derivative_exchange_rpc/5_StreamOrderbooks.py b/examples/exchange_client/derivative_exchange_rpc/5_StreamOrderbooks.py index ab9c5927..e2044eae 100644 --- a/examples/exchange_client/derivative_exchange_rpc/5_StreamOrderbooks.py +++ b/examples/exchange_client/derivative_exchange_rpc/5_StreamOrderbooks.py @@ -1,17 +1,41 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +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 derivative orderbook snapshots ({exception})") + + +def stream_closed_processor(): + print("The derivative orderbook snapshots stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - markets = await client.stream_derivative_orderbook_snapshot(market_ids=[market_id]) - async for market in markets: - print(market) + market_ids = ["0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"] + + task = asyncio.get_event_loop().create_task( + client.listen_derivative_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__": diff --git a/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py b/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py index 235e75d2..93b00a0a 100644 --- a/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py +++ b/examples/exchange_client/derivative_exchange_rpc/6_StreamOrderbookUpdate.py @@ -1,10 +1,21 @@ import asyncio from decimal import Decimal +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to derivative orderbook updates ({exception})") + + +def stream_closed_processor(): + print("The derivative orderbook updates stream has been closed") + + class PriceLevel: def __init__(self, price: Decimal, quantity: Decimal, timestamp: int): self.price = price @@ -24,24 +35,24 @@ def __init__(self, market_id: str): async def load_orderbook_snapshot(async_client: AsyncClient, orderbook: Orderbook): # load the snapshot - res = await async_client.get_derivative_orderbooksV2(market_ids=[orderbook.market_id]) - for snapshot in res.orderbooks: - if snapshot.market_id != orderbook.market_id: + res = await async_client.fetch_derivative_orderbooks_v2(market_ids=[orderbook.market_id]) + for snapshot in res["orderbooks"]: + if snapshot["marketId"] != orderbook.market_id: raise Exception("unexpected snapshot") - orderbook.sequence = int(snapshot.orderbook.sequence) + 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=buy.timestamp, + 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=sell.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"]), ) break @@ -53,74 +64,91 @@ async def main() -> None: market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" 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 - stream = await async_client.stream_derivative_orderbook_update(market_ids=[market_id]) - first_update = None - async for update in stream: - first_update = update.orderbook_level_updates - break + task = asyncio.get_event_loop().create_task( + async_client.listen_derivative_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(async_client=async_client, orderbook=orderbook) - # start consuming updates again to process them - apply_orderbook_update(orderbook, first_update) - async for update in stream: - apply_orderbook_update(orderbook, update.orderbook_level_updates) + 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() -def apply_orderbook_update(orderbook: Orderbook, updates): - # discard old updates - if updates.sequence <= orderbook.sequence: - return - print(" * * * * * * * * * * * * * * * * * * *") +async def apply_orderbook_update(orderbook: Orderbook, updates_queue: asyncio.Queue): + while True: + updates = await updates_queue.get() + update = updates["orderbookLevelUpdates"] - # ensure we have not missed any update - if updates.sequence > (orderbook.sequence + 1): - raise Exception( - "missing orderbook update events from stream, must restart: {} vs {}".format( - updates.sequence, (orderbook.sequence + 1) - ) - ) + # discard updates older than the snapshot + if int(update["sequence"]) <= orderbook.sequence: + return - print("updating orderbook with updates at sequence {}".format(updates.sequence)) + print(" * * * * * * * * * * * * * * * * * * *") - # update orderbook - orderbook.sequence = updates.sequence - for direction, levels in {"buys": updates.buys, "sells": updates.sells}.items(): - for level in levels: - if level.is_active: - # upsert level - orderbook.levels[direction][level.price] = PriceLevel( - price=Decimal(level.price), quantity=Decimal(level.quantity), timestamp=level.timestamp + # 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) ) - 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("====================================") + ) + + 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__": diff --git a/examples/exchange_client/derivative_exchange_rpc/7_Positions.py b/examples/exchange_client/derivative_exchange_rpc/7_Positions.py index fddd7b3a..e0ffb8b1 100644 --- a/examples/exchange_client/derivative_exchange_rpc/7_Positions.py +++ b/examples/exchange_client/derivative_exchange_rpc/7_Positions.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -17,13 +18,13 @@ async def main() -> None: subaccount_total_positions = False skip = 4 limit = 4 - positions = await client.get_derivative_positions( + pagination = PaginationOption(skip=skip, limit=limit) + positions = await client.fetch_derivative_positions( market_ids=market_ids, - ubaccount_id=subaccount_id, + subaccount_id=subaccount_id, direction=direction, subaccount_total_positions=subaccount_total_positions, - skip=skip, - limit=limit, + pagination=pagination, ) print(positions) diff --git a/examples/exchange_client/derivative_exchange_rpc/9_StreamPositions.py b/examples/exchange_client/derivative_exchange_rpc/9_StreamPositions.py index cc808a7e..a035008e 100644 --- a/examples/exchange_client/derivative_exchange_rpc/9_StreamPositions.py +++ b/examples/exchange_client/derivative_exchange_rpc/9_StreamPositions.py @@ -1,18 +1,43 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def positions_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to derivative positions updates ({exception})") + + +def stream_closed_processor(): + print("The derivative positions updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" - subaccount_id = "0xea98e3aa091a6676194df40ac089e40ab4604bf9000000000000000000000000" - positions = await client.stream_derivative_positions(market_id=market_id, subaccount_id=subaccount_id) - async for position in positions: - print(position) + market_ids = ["0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6"] + subaccount_ids = ["0xea98e3aa091a6676194df40ac089e40ab4604bf9000000000000000000000000"] + + task = asyncio.get_event_loop().create_task( + client.listen_derivative_positions_updates( + callback=positions_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/explorer_rpc/10_GetIBCTransfers.py b/examples/exchange_client/explorer_rpc/10_GetIBCTransfers.py index 62ca0c24..b6219b1c 100644 --- a/examples/exchange_client/explorer_rpc/10_GetIBCTransfers.py +++ b/examples/exchange_client/explorer_rpc/10_GetIBCTransfers.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -16,15 +17,15 @@ async def main() -> None: dest_port = "transfer" limit = 1 skip = 10 - ibc_transfers = await client.get_ibc_transfers( + pagination = PaginationOption(limit=limit, skip=skip) + ibc_transfers = await client.fetch_ibc_transfer_txs( sender=sender, receiver=receiver, src_channel=src_channel, src_port=src_port, - destination_channel=destination_channel, + dest_channel=destination_channel, dest_port=dest_port, - limit=limit, - skip=skip, + pagination=pagination, ) print(ibc_transfers) diff --git a/examples/exchange_client/explorer_rpc/1_GetTxByHash.py b/examples/exchange_client/explorer_rpc/1_GetTxByHash.py index 33e6abe4..f1158993 100644 --- a/examples/exchange_client/explorer_rpc/1_GetTxByHash.py +++ b/examples/exchange_client/explorer_rpc/1_GetTxByHash.py @@ -11,10 +11,10 @@ async def main() -> None: client = AsyncClient(network) composer = Composer(network=network.string()) tx_hash = "0F3EBEC1882E1EEAC5B7BDD836E976250F1CD072B79485877CEACCB92ACDDF52" - transaction_response = await client.get_tx_by_hash(tx_hash=tx_hash) + transaction_response = await client.fetch_tx_by_tx_hash(tx_hash=tx_hash) print(transaction_response) - transaction_messages = composer.UnpackTransactionMessages(transaction=transaction_response.data) + transaction_messages = composer.unpack_transaction_messages(transaction_data=transaction_response["data"]) print(transaction_messages) first_message = transaction_messages[0] print(first_message) diff --git a/examples/exchange_client/explorer_rpc/2_AccountTxs.py b/examples/exchange_client/explorer_rpc/2_AccountTxs.py index b1e39445..f743e302 100644 --- a/examples/exchange_client/explorer_rpc/2_AccountTxs.py +++ b/examples/exchange_client/explorer_rpc/2_AccountTxs.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.composer import Composer from pyinjective.core.network import Network @@ -13,9 +14,14 @@ async def main() -> None: address = "inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex" message_type = "cosmos.bank.v1beta1.MsgSend" limit = 2 - transactions_response = await client.get_account_txs(address=address, type=message_type, limit=limit) + pagination = PaginationOption(limit=limit) + transactions_response = await client.fetch_account_txs( + address=address, + message_type=message_type, + pagination=pagination, + ) print(transactions_response) - first_transaction_messages = composer.UnpackTransactionMessages(transaction=transactions_response.data[0]) + first_transaction_messages = composer.unpack_transaction_messages(transaction_data=transactions_response["data"][0]) print(first_transaction_messages) first_message = first_transaction_messages[0] print(first_message) diff --git a/examples/exchange_client/explorer_rpc/3_Blocks.py b/examples/exchange_client/explorer_rpc/3_Blocks.py index 72739096..4807030c 100644 --- a/examples/exchange_client/explorer_rpc/3_Blocks.py +++ b/examples/exchange_client/explorer_rpc/3_Blocks.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -9,8 +10,9 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) limit = 2 - block = await client.get_blocks(limit=limit) - print(block) + pagination = PaginationOption(limit=limit) + blocks = await client.fetch_blocks(pagination=pagination) + print(blocks) if __name__ == "__main__": diff --git a/examples/exchange_client/explorer_rpc/4_Block.py b/examples/exchange_client/explorer_rpc/4_Block.py index e043bdcd..b41d5595 100644 --- a/examples/exchange_client/explorer_rpc/4_Block.py +++ b/examples/exchange_client/explorer_rpc/4_Block.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) block_height = "5825046" - block = await client.get_block(block_height=block_height) + block = await client.fetch_block(block_id=block_height) print(block) diff --git a/examples/exchange_client/explorer_rpc/5_TxsRequest.py b/examples/exchange_client/explorer_rpc/5_TxsRequest.py index 8282816b..70b91f68 100644 --- a/examples/exchange_client/explorer_rpc/5_TxsRequest.py +++ b/examples/exchange_client/explorer_rpc/5_TxsRequest.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -9,7 +10,8 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) limit = 2 - txs = await client.get_txs(limit=limit) + pagination = PaginationOption(limit=limit) + txs = await client.fetch_txs(pagination=pagination) print(txs) diff --git a/examples/exchange_client/explorer_rpc/6_StreamTxs.py b/examples/exchange_client/explorer_rpc/6_StreamTxs.py index f96a595e..07851daa 100644 --- a/examples/exchange_client/explorer_rpc/6_StreamTxs.py +++ b/examples/exchange_client/explorer_rpc/6_StreamTxs.py @@ -1,16 +1,39 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def tx_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to txs updates ({exception})") + + +def stream_closed_processor(): + print("The txs updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - stream_txs = await client.stream_txs() - async for tx in stream_txs: - print(tx) + + task = asyncio.get_event_loop().create_task( + client.listen_txs_updates( + callback=tx_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/explorer_rpc/7_StreamBlocks.py b/examples/exchange_client/explorer_rpc/7_StreamBlocks.py index f5169511..b9665742 100644 --- a/examples/exchange_client/explorer_rpc/7_StreamBlocks.py +++ b/examples/exchange_client/explorer_rpc/7_StreamBlocks.py @@ -1,16 +1,39 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def block_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to blocks updates ({exception})") + + +def stream_closed_processor(): + print("The blocks updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - stream_blocks = await client.stream_blocks() - async for block in stream_blocks: - print(block) + + task = asyncio.get_event_loop().create_task( + client.listen_blocks_updates( + callback=block_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/explorer_rpc/8_GetPeggyDeposits.py b/examples/exchange_client/explorer_rpc/8_GetPeggyDeposits.py index 4fde668f..936ede47 100644 --- a/examples/exchange_client/explorer_rpc/8_GetPeggyDeposits.py +++ b/examples/exchange_client/explorer_rpc/8_GetPeggyDeposits.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) receiver = "inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex" - peggy_deposits = await client.get_peggy_deposits(receiver=receiver) + peggy_deposits = await client.fetch_peggy_deposit_txs(receiver=receiver) print(peggy_deposits) diff --git a/examples/exchange_client/explorer_rpc/9_GetPeggyWithdrawals.py b/examples/exchange_client/explorer_rpc/9_GetPeggyWithdrawals.py index def1fedf..0c38e9dc 100644 --- a/examples/exchange_client/explorer_rpc/9_GetPeggyWithdrawals.py +++ b/examples/exchange_client/explorer_rpc/9_GetPeggyWithdrawals.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -10,7 +11,8 @@ async def main() -> None: client = AsyncClient(network) sender = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" limit = 2 - peggy_deposits = await client.get_peggy_withdrawals(sender=sender, limit=limit) + pagination = PaginationOption(limit=limit) + peggy_deposits = await client.fetch_peggy_withdrawal_txs(sender=sender, pagination=pagination) print(peggy_deposits) diff --git a/examples/exchange_client/insurance_rpc/1_InsuranceFunds.py b/examples/exchange_client/insurance_rpc/1_InsuranceFunds.py index 167cae93..4f962b08 100644 --- a/examples/exchange_client/insurance_rpc/1_InsuranceFunds.py +++ b/examples/exchange_client/insurance_rpc/1_InsuranceFunds.py @@ -8,7 +8,7 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - insurance_funds = await client.get_insurance_funds() + insurance_funds = await client.fetch_insurance_funds() print(insurance_funds) diff --git a/examples/exchange_client/insurance_rpc/2_Redemptions.py b/examples/exchange_client/insurance_rpc/2_Redemptions.py index 794e4b02..2118f210 100644 --- a/examples/exchange_client/insurance_rpc/2_Redemptions.py +++ b/examples/exchange_client/insurance_rpc/2_Redemptions.py @@ -11,9 +11,7 @@ async def main() -> None: redeemer = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" redemption_denom = "share4" status = "disbursed" - insurance_redemptions = await client.get_redemptions( - redeemer=redeemer, redemption_denom=redemption_denom, status=status - ) + insurance_redemptions = await client.fetch_redemptions(address=redeemer, denom=redemption_denom, status=status) print(insurance_redemptions) diff --git a/examples/exchange_client/meta_rpc/1_Ping.py b/examples/exchange_client/meta_rpc/1_Ping.py index 3b75cbae..46feca3e 100644 --- a/examples/exchange_client/meta_rpc/1_Ping.py +++ b/examples/exchange_client/meta_rpc/1_Ping.py @@ -8,7 +8,7 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - resp = await client.ping() + resp = await client.fetch_ping() print("Health OK?", resp) diff --git a/examples/exchange_client/meta_rpc/2_Version.py b/examples/exchange_client/meta_rpc/2_Version.py index 025a931b..11681dc2 100644 --- a/examples/exchange_client/meta_rpc/2_Version.py +++ b/examples/exchange_client/meta_rpc/2_Version.py @@ -8,7 +8,7 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - resp = await client.version() + resp = await client.fetch_version() print("Version:", resp) diff --git a/examples/exchange_client/meta_rpc/3_Info.py b/examples/exchange_client/meta_rpc/3_Info.py index 8f799da7..3e103e51 100644 --- a/examples/exchange_client/meta_rpc/3_Info.py +++ b/examples/exchange_client/meta_rpc/3_Info.py @@ -9,10 +9,10 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - resp = await client.info() + resp = await client.fetch_info() print("[!] Info:") print(resp) - latency = int(round(time.time() * 1000)) - resp.timestamp + latency = int(time.time() * 1000) - int(resp["timestamp"]) print(f"Server Latency: {latency}ms") diff --git a/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py b/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py index 4abb3e20..84a22eb8 100644 --- a/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py +++ b/examples/exchange_client/meta_rpc/4_StreamKeepAlive.py @@ -1,39 +1,57 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to keepalive updates ({exception})") + + +def stream_closed_processor(): + print("The keepalive stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) + tasks = [] + + async def keepalive_event_processor(event: Dict[str, Any]): + print("Server announce:", event) + for task in tasks: + task.cancel() + print("Cancelled all tasks") - task1 = asyncio.get_event_loop().create_task(get_markets(client)) - task2 = asyncio.get_event_loop().create_task(keepalive(client, [task1])) + market_task = asyncio.get_event_loop().create_task(get_markets(client)) + tasks.append(market_task) + keepalive_task = asyncio.get_event_loop().create_task( + client.listen_keepalive( + callback=keepalive_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) try: - await asyncio.gather( - task1, - task2, - ) + await asyncio.gather(market_task, keepalive_task) except asyncio.CancelledError: print("main(): get_markets is cancelled now") async def get_markets(client): - stream = await client.stream_spot_markets() - async for market in stream: - print(market) - - -async def keepalive(client, tasks: list): - stream = await client.stream_keepalive() - async for announce in stream: - print("Server announce:", announce) - async for task in tasks: - task.cancel() - print("Cancelled all tasks") + async def print_market_updates(event: Dict[str, Any]): + print(event) + + await client.listen_spot_markets_updates( + callback=print_market_updates, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) if __name__ == "__main__": diff --git a/examples/exchange_client/oracle_rpc/1_StreamPrices.py b/examples/exchange_client/oracle_rpc/1_StreamPrices.py index ca7f9de5..0a453690 100644 --- a/examples/exchange_client/oracle_rpc/1_StreamPrices.py +++ b/examples/exchange_client/oracle_rpc/1_StreamPrices.py @@ -1,21 +1,45 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def price_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to oracle prices updates ({exception})") + + +def stream_closed_processor(): + print("The oracle prices updates stream has been closed") + + async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - base_symbol = "BTC" + base_symbol = "INJ" quote_symbol = "USDT" oracle_type = "bandibc" - oracle_prices = await client.stream_oracle_prices( - base_symbol=base_symbol, quote_symbol=quote_symbol, oracle_type=oracle_type + + task = asyncio.get_event_loop().create_task( + client.listen_oracle_prices_updates( + callback=price_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + ) ) - async for oracle in oracle_prices: - print(oracle) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/oracle_rpc/2_Price.py b/examples/exchange_client/oracle_rpc/2_Price.py index f3a5921e..b5a44a48 100644 --- a/examples/exchange_client/oracle_rpc/2_Price.py +++ b/examples/exchange_client/oracle_rpc/2_Price.py @@ -12,7 +12,7 @@ async def main() -> None: quote_symbol = "USDT" oracle_type = "bandibc" oracle_scale_factor = 6 - oracle_prices = await client.get_oracle_prices( + oracle_prices = await client.fetch_oracle_price( base_symbol=base_symbol, quote_symbol=quote_symbol, oracle_type=oracle_type, diff --git a/examples/exchange_client/oracle_rpc/3_OracleList.py b/examples/exchange_client/oracle_rpc/3_OracleList.py index 558ac66d..db2b7f03 100644 --- a/examples/exchange_client/oracle_rpc/3_OracleList.py +++ b/examples/exchange_client/oracle_rpc/3_OracleList.py @@ -8,7 +8,7 @@ async def main() -> None: # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - oracle_list = await client.get_oracle_list() + oracle_list = await client.fetch_oracle_list() print(oracle_list) diff --git a/examples/exchange_client/portfolio_rpc/1_AccountPortfolio.py b/examples/exchange_client/portfolio_rpc/1_AccountPortfolio.py index 965d40c7..232f8582 100644 --- a/examples/exchange_client/portfolio_rpc/1_AccountPortfolio.py +++ b/examples/exchange_client/portfolio_rpc/1_AccountPortfolio.py @@ -9,7 +9,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) account_address = "inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt" - portfolio = await client.get_account_portfolio(account_address=account_address) + portfolio = await client.fetch_account_portfolio(account_address=account_address) print(portfolio) diff --git a/examples/exchange_client/portfolio_rpc/2_StreamAccountPortfolio.py b/examples/exchange_client/portfolio_rpc/2_StreamAccountPortfolio.py index 53d7c4da..4b3b6a04 100644 --- a/examples/exchange_client/portfolio_rpc/2_StreamAccountPortfolio.py +++ b/examples/exchange_client/portfolio_rpc/2_StreamAccountPortfolio.py @@ -1,17 +1,40 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +async def account_portfolio_event_processor(event: Dict[str, Any]): + print(event) + + +def stream_error_processor(exception: RpcError): + print(f"There was an error listening to account portfolio updates ({exception})") + + +def stream_closed_processor(): + print("The account portfolio updates stream has been closed") + + async def main() -> None: network = Network.testnet() client = AsyncClient(network) account_address = "inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt" - updates = await client.stream_account_portfolio(account_address=account_address) - async for update in updates: - print("Account portfolio Update:\n") - print(update) + + task = asyncio.get_event_loop().create_task( + client.listen_account_portfolio_updates( + account_address=account_address, + callback=account_portfolio_event_processor, + on_end_callback=stream_closed_processor, + on_status_callback=stream_error_processor, + ) + ) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/spot_exchange_rpc/10_StreamTrades.py b/examples/exchange_client/spot_exchange_rpc/10_StreamTrades.py index 8bf4a82a..a1d63511 100644 --- a/examples/exchange_client/spot_exchange_rpc/10_StreamTrades.py +++ b/examples/exchange_client/spot_exchange_rpc/10_StreamTrades.py @@ -1,9 +1,24 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +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 = AsyncClient(network) @@ -15,15 +30,22 @@ async def main() -> None: direction = "sell" subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001" execution_types = ["limitMatchRestingOrder"] - trades = await client.stream_spot_trades( - market_ids=market_ids, - execution_side=execution_side, - direction=direction, - subaccount_id=subaccount_id, - execution_types=execution_types, + + 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, + ) ) - async for trade in trades: - print(trade) + + await asyncio.sleep(delay=60) + task.cancel() if __name__ == "__main__": diff --git a/examples/exchange_client/spot_exchange_rpc/11_SubaccountOrdersList.py b/examples/exchange_client/spot_exchange_rpc/11_SubaccountOrdersList.py index 03e901e9..760119dc 100644 --- a/examples/exchange_client/spot_exchange_rpc/11_SubaccountOrdersList.py +++ b/examples/exchange_client/spot_exchange_rpc/11_SubaccountOrdersList.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -12,8 +13,9 @@ async def main() -> None: market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" skip = 10 limit = 10 - orders = await client.get_spot_subaccount_orders( - subaccount_id=subaccount_id, market_id=market_id, skip=skip, limit=limit + 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(orders) diff --git a/examples/exchange_client/spot_exchange_rpc/12_SubaccountTradesList.py b/examples/exchange_client/spot_exchange_rpc/12_SubaccountTradesList.py index a9670e58..d35a12e9 100644 --- a/examples/exchange_client/spot_exchange_rpc/12_SubaccountTradesList.py +++ b/examples/exchange_client/spot_exchange_rpc/12_SubaccountTradesList.py @@ -1,6 +1,7 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network @@ -14,13 +15,13 @@ async def main() -> None: direction = "buy" skip = 2 limit = 3 - trades = await client.get_spot_subaccount_trades( + 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, - skip=skip, - limit=limit, + pagination=pagination, ) print(trades) diff --git a/examples/exchange_client/spot_exchange_rpc/13_StreamOrderbooks.py b/examples/exchange_client/spot_exchange_rpc/13_StreamOrderbooks.py deleted file mode 100644 index 82fe35e7..00000000 --- a/examples/exchange_client/spot_exchange_rpc/13_StreamOrderbooks.py +++ /dev/null @@ -1,20 +0,0 @@ -import asyncio - -from pyinjective.async_client import AsyncClient -from pyinjective.core.network import Network - - -async def main() -> None: - network = Network.testnet() - client = AsyncClient(network) - market_ids = [ - "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", - "0x7a57e705bb4e09c88aecfc295569481dbf2fe1d5efe364651fbe72385938e9b0", - ] - orderbook = await client.stream_spot_orderbook_snapshot(market_ids=market_ids) - async for orders in orderbook: - print(orders) - - -if __name__ == "__main__": - asyncio.get_event_loop().run_until_complete(main()) diff --git a/examples/exchange_client/spot_exchange_rpc/14_Orderbooks.py b/examples/exchange_client/spot_exchange_rpc/14_Orderbooks.py index 2a99f818..ac672940 100644 --- a/examples/exchange_client/spot_exchange_rpc/14_Orderbooks.py +++ b/examples/exchange_client/spot_exchange_rpc/14_Orderbooks.py @@ -11,7 +11,7 @@ async def main() -> None: "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", "0x7a57e705bb4e09c88aecfc295569481dbf2fe1d5efe364651fbe72385938e9b0", ] - orderbooks = await client.get_spot_orderbooksV2(market_ids=market_ids) + orderbooks = await client.fetch_spot_orderbooks_v2(market_ids=market_ids) print(orderbooks) diff --git a/examples/exchange_client/spot_exchange_rpc/15_HistoricalOrders.py b/examples/exchange_client/spot_exchange_rpc/15_HistoricalOrders.py index 0a39f80d..bac4b2c1 100644 --- a/examples/exchange_client/spot_exchange_rpc/15_HistoricalOrders.py +++ b/examples/exchange_client/spot_exchange_rpc/15_HistoricalOrders.py @@ -1,19 +1,24 @@ import asyncio from pyinjective.async_client import AsyncClient +from pyinjective.client.model.pagination import PaginationOption from pyinjective.core.network import Network async def main() -> None: network = Network.testnet() client = AsyncClient(network) - market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + market_ids = ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"] subaccount_id = "0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000" skip = 10 limit = 3 order_types = ["buy_po"] - orders = await client.get_historical_spot_orders( - market_id=market_id, subaccount_id=subaccount_id, skip=skip, limit=limit, order_types=order_types + 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(orders) diff --git a/examples/exchange_client/spot_exchange_rpc/1_Market.py b/examples/exchange_client/spot_exchange_rpc/1_Market.py index d926e97c..e8705d68 100644 --- a/examples/exchange_client/spot_exchange_rpc/1_Market.py +++ b/examples/exchange_client/spot_exchange_rpc/1_Market.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" - market = await client.get_spot_market(market_id=market_id) + market = await client.fetch_spot_market(market_id=market_id) print(market) diff --git a/examples/exchange_client/spot_exchange_rpc/2_Markets.py b/examples/exchange_client/spot_exchange_rpc/2_Markets.py index dc37d25f..3dc815eb 100644 --- a/examples/exchange_client/spot_exchange_rpc/2_Markets.py +++ b/examples/exchange_client/spot_exchange_rpc/2_Markets.py @@ -10,7 +10,9 @@ async def main() -> None: market_status = "active" base_denom = "inj" quote_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" - market = await client.get_spot_markets(market_status=market_status, base_denom=base_denom, quote_denom=quote_denom) + market = await client.fetch_spot_markets( + market_statuses=[market_status], base_denom=base_denom, quote_denom=quote_denom + ) print(market) diff --git a/examples/exchange_client/spot_exchange_rpc/3_StreamMarkets.py b/examples/exchange_client/spot_exchange_rpc/3_StreamMarkets.py index 16633271..ac56d2d7 100644 --- a/examples/exchange_client/spot_exchange_rpc/3_StreamMarkets.py +++ b/examples/exchange_client/spot_exchange_rpc/3_StreamMarkets.py @@ -1,16 +1,39 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +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.testnet() + network = Network.mainnet() client = AsyncClient(network) - markets = await client.stream_spot_markets() - async for market in markets: - print(market) + + 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__": diff --git a/examples/exchange_client/spot_exchange_rpc/4_Orderbook.py b/examples/exchange_client/spot_exchange_rpc/4_Orderbook.py index 1de0c539..9e4e08eb 100644 --- a/examples/exchange_client/spot_exchange_rpc/4_Orderbook.py +++ b/examples/exchange_client/spot_exchange_rpc/4_Orderbook.py @@ -8,7 +8,7 @@ async def main() -> None: network = Network.testnet() client = AsyncClient(network) market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" - orderbook = await client.get_spot_orderbookV2(market_id=market_id) + orderbook = await client.fetch_spot_orderbook_v2(market_id=market_id) print(orderbook) diff --git a/examples/exchange_client/spot_exchange_rpc/6_Trades.py b/examples/exchange_client/spot_exchange_rpc/6_Trades.py index 6c035553..029a4f90 100644 --- a/examples/exchange_client/spot_exchange_rpc/6_Trades.py +++ b/examples/exchange_client/spot_exchange_rpc/6_Trades.py @@ -10,13 +10,13 @@ async def main() -> None: market_ids = ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"] execution_side = "taker" direction = "buy" - subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001" + subaccount_ids = ["0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001"] execution_types = ["limitMatchNewOrder", "market"] - orders = await client.get_spot_trades( + orders = await client.fetch_spot_trades( market_ids=market_ids, + subaccount_ids=subaccount_ids, execution_side=execution_side, direction=direction, - subaccount_id=subaccount_id, execution_types=execution_types, ) print(orders) diff --git a/examples/exchange_client/spot_exchange_rpc/7_StreamOrderbookSnapshot.py b/examples/exchange_client/spot_exchange_rpc/7_StreamOrderbookSnapshot.py index 5c4ed593..7eacf4df 100644 --- a/examples/exchange_client/spot_exchange_rpc/7_StreamOrderbookSnapshot.py +++ b/examples/exchange_client/spot_exchange_rpc/7_StreamOrderbookSnapshot.py @@ -1,17 +1,43 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +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: - # select network: local, testnet, mainnet network = Network.testnet() client = AsyncClient(network) - market_ids = ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"] - orderbooks = await client.stream_spot_orderbook_snapshot(market_ids=market_ids) - async for orderbook in orderbooks: - print(orderbook) + 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__": diff --git a/examples/exchange_client/spot_exchange_rpc/8_StreamOrderbookUpdate.py b/examples/exchange_client/spot_exchange_rpc/8_StreamOrderbookUpdate.py index 13a789fd..6a7fb247 100644 --- a/examples/exchange_client/spot_exchange_rpc/8_StreamOrderbookUpdate.py +++ b/examples/exchange_client/spot_exchange_rpc/8_StreamOrderbookUpdate.py @@ -1,10 +1,21 @@ import asyncio from decimal import Decimal +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +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 @@ -24,24 +35,24 @@ def __init__(self, market_id: str): async def load_orderbook_snapshot(async_client: AsyncClient, orderbook: Orderbook): # load the snapshot - res = await async_client.get_spot_orderbooksV2(market_ids=[orderbook.market_id]) - for snapshot in res.orderbooks: - if snapshot.market_id != orderbook.market_id: + res = await async_client.fetch_spot_orderbooks_v2(market_ids=[orderbook.market_id]) + for snapshot in res["orderbooks"]: + if snapshot["marketId"] != orderbook.market_id: raise Exception("unexpected snapshot") - orderbook.sequence = int(snapshot.orderbook.sequence) + 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=buy.timestamp, + 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=sell.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"]), ) break @@ -53,74 +64,91 @@ async def main() -> None: 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 - stream = await async_client.stream_spot_orderbook_update(market_ids=[market_id]) - first_update = None - async for update in stream: - first_update = update.orderbook_level_updates - break + task = asyncio.get_event_loop().create_task( + async_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(async_client=async_client, orderbook=orderbook) - # start consuming updates again to process them - apply_orderbook_update(orderbook, first_update) - async for update in stream: - apply_orderbook_update(orderbook, update.orderbook_level_updates) + 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() -def apply_orderbook_update(orderbook: Orderbook, updates): - # discard updates older than the snapshot - if updates.sequence <= orderbook.sequence: - return - print(" * * * * * * * * * * * * * * * * * * *") +async def apply_orderbook_update(orderbook: Orderbook, updates_queue: asyncio.Queue): + while True: + updates = await updates_queue.get() + update = updates["orderbookLevelUpdates"] - # ensure we have not missed any update - if updates.sequence > (orderbook.sequence + 1): - raise Exception( - "missing orderbook update events from stream, must restart: {} vs {}".format( - updates.sequence, (orderbook.sequence + 1) - ) - ) + # discard updates older than the snapshot + if int(update["sequence"]) <= orderbook.sequence: + return - print("updating orderbook with updates at sequence {}".format(updates.sequence)) + print(" * * * * * * * * * * * * * * * * * * *") - # update orderbook - orderbook.sequence = updates.sequence - for direction, levels in {"buys": updates.buys, "sells": updates.sells}.items(): - for level in levels: - if level.is_active: - # upsert level - orderbook.levels[direction][level.price] = PriceLevel( - price=Decimal(level.price), quantity=Decimal(level.quantity), timestamp=level.timestamp + # 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) ) - 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("====================================") + ) + + 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__": diff --git a/examples/exchange_client/spot_exchange_rpc/9_StreamHistoricalOrders.py b/examples/exchange_client/spot_exchange_rpc/9_StreamHistoricalOrders.py index 95dcdb43..91ed7810 100644 --- a/examples/exchange_client/spot_exchange_rpc/9_StreamHistoricalOrders.py +++ b/examples/exchange_client/spot_exchange_rpc/9_StreamHistoricalOrders.py @@ -1,17 +1,42 @@ import asyncio +from typing import Any, Dict + +from grpc import RpcError from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network +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 = AsyncClient(network) market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" - order_side = "buy" - orders = await client.stream_historical_spot_orders(market_id=market_id, order_side=order_side) - async for order in orders: - print(order) + 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__": diff --git a/poetry.lock b/poetry.lock index a1c65b85..cc8e201b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2663,4 +2663,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "3f60f1e4f2c0c5fe4f53f91d5320395e7063c477dd50524accd5fd3f9677bcf0" +content-hash = "5f05b8a2490ff9d4f5d2e82bb11fc71b8f8f50eb9618600bf8b28c85fe3b83bd" diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 7540f6f9..388e7457 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -2,50 +2,76 @@ import time from copy import deepcopy from decimal import Decimal -from typing import Coroutine, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Coroutine, Dict, List, Optional, Tuple, Union +from warnings import warn import grpc - +from google.protobuf import json_format + +from pyinjective import constant +from pyinjective.client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi +from pyinjective.client.chain.grpc.chain_grpc_authz_api import ChainGrpcAuthZApi +from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi +from pyinjective.client.chain.grpc_stream.chain_grpc_chain_stream import ChainGrpcChainStream +from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi +from pyinjective.client.indexer.grpc.indexer_grpc_auction_api import IndexerGrpcAuctionApi +from pyinjective.client.indexer.grpc.indexer_grpc_derivative_api import IndexerGrpcDerivativeApi +from pyinjective.client.indexer.grpc.indexer_grpc_explorer_api import IndexerGrpcExplorerApi +from pyinjective.client.indexer.grpc.indexer_grpc_insurance_api import IndexerGrpcInsuranceApi +from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi +from pyinjective.client.indexer.grpc.indexer_grpc_oracle_api import IndexerGrpcOracleApi +from pyinjective.client.indexer.grpc.indexer_grpc_portfolio_api import IndexerGrpcPortfolioApi +from pyinjective.client.indexer.grpc.indexer_grpc_spot_api import IndexerGrpcSpotApi +from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_auction_stream import IndexerGrpcAuctionStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_derivative_stream import IndexerGrpcDerivativeStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_explorer_stream import IndexerGrpcExplorerStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_oracle_stream import IndexerGrpcOracleStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_portfolio_stream import IndexerGrpcPortfolioStream +from pyinjective.client.indexer.grpc_stream.indexer_grpc_spot_stream import IndexerGrpcSpotStream +from pyinjective.client.model.pagination import PaginationOption from pyinjective.composer import Composer -from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_query -from pyinjective.proto.injective.stream.v1beta1 import query_pb2_grpc as stream_rpc_grpc - -from . import constant -from .core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket -from .core.network import Network -from .core.token import Token -from .exceptions import NotFoundError -from .proto.cosmos.auth.v1beta1 import query_pb2 as auth_query -from .proto.cosmos.auth.v1beta1 import query_pb2_grpc as auth_query_grpc -from .proto.cosmos.authz.v1beta1 import query_pb2 as authz_query -from .proto.cosmos.authz.v1beta1 import query_pb2_grpc as authz_query_grpc -from .proto.cosmos.bank.v1beta1 import query_pb2 as bank_query -from .proto.cosmos.bank.v1beta1 import query_pb2_grpc as bank_query_grpc -from .proto.cosmos.base.abci.v1beta1 import abci_pb2 as abci_type -from .proto.cosmos.base.tendermint.v1beta1 import query_pb2 as tendermint_query -from .proto.cosmos.base.tendermint.v1beta1 import query_pb2_grpc as tendermint_query_grpc -from .proto.cosmos.tx.v1beta1 import service_pb2 as tx_service -from .proto.cosmos.tx.v1beta1 import service_pb2_grpc as tx_service_grpc -from .proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_rpc_pb -from .proto.exchange import injective_accounts_rpc_pb2_grpc as exchange_accounts_rpc_grpc -from .proto.exchange import injective_auction_rpc_pb2 as auction_rpc_pb -from .proto.exchange import injective_auction_rpc_pb2_grpc as auction_rpc_grpc -from .proto.exchange import injective_derivative_exchange_rpc_pb2 as derivative_exchange_rpc_pb -from .proto.exchange import injective_derivative_exchange_rpc_pb2_grpc as derivative_exchange_rpc_grpc -from .proto.exchange import injective_explorer_rpc_pb2 as explorer_rpc_pb -from .proto.exchange import injective_explorer_rpc_pb2_grpc as explorer_rpc_grpc -from .proto.exchange import injective_insurance_rpc_pb2 as insurance_rpc_pb -from .proto.exchange import injective_insurance_rpc_pb2_grpc as insurance_rpc_grpc -from .proto.exchange import injective_meta_rpc_pb2 as exchange_meta_rpc_pb -from .proto.exchange import injective_meta_rpc_pb2_grpc as exchange_meta_rpc_grpc -from .proto.exchange import injective_oracle_rpc_pb2 as oracle_rpc_pb -from .proto.exchange import injective_oracle_rpc_pb2_grpc as oracle_rpc_grpc -from .proto.exchange import injective_portfolio_rpc_pb2 as portfolio_rpc_pb -from .proto.exchange import injective_portfolio_rpc_pb2_grpc as portfolio_rpc_grpc -from .proto.exchange import injective_spot_exchange_rpc_pb2 as spot_exchange_rpc_pb -from .proto.exchange import injective_spot_exchange_rpc_pb2_grpc as spot_exchange_rpc_grpc -from .proto.injective.types.v1beta1 import account_pb2 -from .utils.logger import LoggerProvider +from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket +from pyinjective.core.network import Network +from pyinjective.core.token import Token +from pyinjective.core.tx.grpc.tx_grpc_api import TxGrpcApi +from pyinjective.exceptions import NotFoundError +from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query, query_pb2_grpc as auth_query_grpc +from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query, query_pb2_grpc as authz_query_grpc +from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query, query_pb2_grpc as bank_query_grpc +from pyinjective.proto.cosmos.base.abci.v1beta1 import abci_pb2 as abci_type +from pyinjective.proto.cosmos.base.tendermint.v1beta1 import ( + query_pb2 as tendermint_query, + query_pb2_grpc as tendermint_query_grpc, +) +from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, service_pb2_grpc as tx_service_grpc +from pyinjective.proto.exchange import ( + injective_accounts_rpc_pb2 as exchange_accounts_rpc_pb, + injective_accounts_rpc_pb2_grpc as exchange_accounts_rpc_grpc, + injective_auction_rpc_pb2 as auction_rpc_pb, + injective_auction_rpc_pb2_grpc as auction_rpc_grpc, + injective_derivative_exchange_rpc_pb2 as derivative_exchange_rpc_pb, + injective_derivative_exchange_rpc_pb2_grpc as derivative_exchange_rpc_grpc, + injective_explorer_rpc_pb2 as explorer_rpc_pb, + injective_explorer_rpc_pb2_grpc as explorer_rpc_grpc, + injective_insurance_rpc_pb2 as insurance_rpc_pb, + injective_insurance_rpc_pb2_grpc as insurance_rpc_grpc, + injective_meta_rpc_pb2 as exchange_meta_rpc_pb, + injective_meta_rpc_pb2_grpc as exchange_meta_rpc_grpc, + injective_oracle_rpc_pb2 as oracle_rpc_pb, + injective_oracle_rpc_pb2_grpc as oracle_rpc_grpc, + injective_portfolio_rpc_pb2 as portfolio_rpc_pb, + injective_portfolio_rpc_pb2_grpc as portfolio_rpc_grpc, + injective_spot_exchange_rpc_pb2 as spot_exchange_rpc_pb, + injective_spot_exchange_rpc_pb2_grpc as spot_exchange_rpc_grpc, +) +from pyinjective.proto.injective.stream.v1beta1 import ( + query_pb2 as chain_stream_query, + query_pb2_grpc as stream_rpc_grpc, +) +from pyinjective.proto.injective.types.v1beta1 import account_pb2 +from pyinjective.utils.logger import LoggerProvider DEFAULT_TIMEOUTHEIGHT_SYNC_INTERVAL = 20 # seconds DEFAULT_TIMEOUTHEIGHT = 30 # blocks @@ -57,10 +83,16 @@ class AsyncClient: def __init__( self, network: Network, - insecure: bool = False, + insecure: Optional[bool] = None, credentials=grpc.ssl_channel_credentials(), ): # the `insecure` parameter is ignored and will be deprecated soon. The value is taken directly from `network` + if insecure is not None: + warn( + "insecure parameter in AsyncClient is no longer used and will be deprecated", + DeprecationWarning, + stacklevel=2, + ) self.addr = "" self.number = 0 @@ -126,6 +158,143 @@ def __init__( self._derivative_markets: Optional[Dict[str, DerivativeMarket]] = None self._binary_option_markets: Optional[Dict[str, BinaryOptionMarket]] = None + self.bank_api = ChainGrpcBankApi( + channel=self.chain_channel, + metadata_provider=lambda: self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) + self.auth_api = ChainGrpcAuthApi( + channel=self.chain_channel, + metadata_provider=lambda: self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) + self.authz_api = ChainGrpcAuthZApi( + channel=self.chain_channel, + metadata_provider=lambda: self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) + self.tx_api = TxGrpcApi( + channel=self.chain_channel, + metadata_provider=lambda: self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) + + self.chain_stream_api = ChainGrpcChainStream( + channel=self.chain_stream_channel, + metadata_provider=lambda: self.network.chain_metadata( + metadata_query_provider=self._chain_cookie_metadata_requestor + ), + ) + + self.exchange_account_api = IndexerGrpcAccountApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_auction_api = IndexerGrpcAuctionApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_derivative_api = IndexerGrpcDerivativeApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_insurance_api = IndexerGrpcInsuranceApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_meta_api = IndexerGrpcMetaApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_oracle_api = IndexerGrpcOracleApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_portfolio_api = IndexerGrpcPortfolioApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_spot_api = IndexerGrpcSpotApi( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + + self.exchange_account_stream_api = IndexerGrpcAccountStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_auction_stream_api = IndexerGrpcAuctionStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_derivative_stream_api = IndexerGrpcDerivativeStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_meta_stream_api = IndexerGrpcMetaStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_oracle_stream_api = IndexerGrpcOracleStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_portfolio_stream_api = IndexerGrpcPortfolioStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + self.exchange_spot_stream_api = IndexerGrpcSpotStream( + channel=self.exchange_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._exchange_cookie_metadata_requestor + ), + ) + + self.exchange_explorer_api = IndexerGrpcExplorerApi( + channel=self.explorer_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._explorer_cookie_metadata_requestor + ), + ) + self.exchange_explorer_stream_api = IndexerGrpcExplorerStream( + channel=self.explorer_channel, + metadata_provider=lambda: self.network.exchange_metadata( + metadata_query_provider=self._explorer_cookie_metadata_requestor + ), + ) + async def all_tokens(self) -> Dict[str, Token]: if self._tokens_by_symbol is None: async with self._tokens_and_markets_initialization_lock: @@ -163,8 +332,15 @@ def get_number(self): return self.number async def get_tx(self, tx_hash): + """ + This method is deprecated and will be removed soon. Please use `fetch_tx` instead + """ + warn("This method is deprecated. Use fetch_tx instead", DeprecationWarning, stacklevel=2) return await self.stubTx.GetTx(tx_service.GetTxRequest(hash=tx_hash)) + async def fetch_tx(self, hash: str) -> Dict[str, Any]: + return await self.tx_api.fetch_tx(hash=hash) + async def close_exchange_channel(self): await self.exchange_channel.close() self._cancel_timeout_height_sync_task() @@ -189,6 +365,11 @@ async def get_latest_block(self) -> tendermint_query.GetLatestBlockResponse: return await self.stubCosmosTendermint.GetLatestBlock(req) async def get_account(self, address: str) -> Optional[account_pb2.EthAccount]: + """ + This method is deprecated and will be removed soon. Please use `fetch_account` instead + """ + warn("This method is deprecated. Use fetch_account instead", DeprecationWarning, stacklevel=2) + try: metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) account_any = ( @@ -205,22 +386,45 @@ async def get_account(self, address: str) -> Optional[account_pb2.EthAccount]: ) return None - async def get_request_id_by_tx_hash(self, tx_hash: bytes) -> List[int]: - tx = await self.stubTx.GetTx(tx_service.GetTxRequest(hash=tx_hash)) + async def fetch_account(self, address: str) -> Optional[account_pb2.EthAccount]: + result_account = None + try: + account = await self.auth_api.fetch_account(address=address) + parsed_account = account_pb2.EthAccount() + if parsed_account.DESCRIPTOR.full_name in account["account"]["@type"]: + json_format.ParseDict(js_dict=account["account"], message=parsed_account, ignore_unknown_fields=True) + self.number = parsed_account.base_account.account_number + self.sequence = parsed_account.base_account.sequence + result_account = parsed_account + except Exception as e: + LoggerProvider().logger_for_class(logging_class=self.__class__).debug( + f"error while fetching sequence and number {e}" + ) + + return result_account + + async def get_request_id_by_tx_hash(self, tx_hash: str) -> List[int]: + tx = await self.tx_api.fetch_tx(hash=tx_hash) request_ids = [] - for tx in tx.tx_response.logs: - request_event = [event for event in tx.events if event.type == "request" or event.type == "report"] + for log in tx["txResponse"].get("logs", []): + request_event = [ + event for event in log.get("events", []) if event["type"] == "request" or event["type"] == "report" + ] if len(request_event) == 1: - attrs = request_event[0].attributes - attr_id = [attr for attr in attrs if attr.key == "id"] + attrs = request_event[0].get("attributes", []) + attr_id = [attr for attr in attrs if attr["key"] == "id"] if len(attr_id) == 1: - request_id = attr_id[0].value + request_id = attr_id[0]["value"] request_ids.append(int(request_id)) if len(request_ids) == 0: raise NotFoundError("Request Id is not found") return request_ids async def simulate_tx(self, tx_byte: bytes) -> Tuple[Union[abci_type.SimulationResponse, grpc.RpcError], bool]: + """ + This method is deprecated and will be removed soon. Please use `simulate` instead + """ + warn("This method is deprecated. Use simulate instead", DeprecationWarning, stacklevel=2) try: req = tx_service.SimulateRequest(tx_bytes=tx_byte) metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) @@ -228,19 +432,40 @@ async def simulate_tx(self, tx_byte: bytes) -> Tuple[Union[abci_type.SimulationR except grpc.RpcError as err: return err, False + async def simulate(self, tx_bytes: bytes) -> Dict[str, Any]: + return await self.tx_api.simulate(tx_bytes=tx_bytes) + async def send_tx_sync_mode(self, tx_byte: bytes) -> abci_type.TxResponse: + """ + This method is deprecated and will be removed soon. Please use `broadcast_tx_sync_mode` instead + """ + warn("This method is deprecated. Use broadcast_tx_sync_mode instead", DeprecationWarning, stacklevel=2) req = tx_service.BroadcastTxRequest(tx_bytes=tx_byte, mode=tx_service.BroadcastMode.BROADCAST_MODE_SYNC) metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) result = await self.stubTx.BroadcastTx(request=req, metadata=metadata) return result.tx_response + async def broadcast_tx_sync_mode(self, tx_bytes: bytes) -> Dict[str, Any]: + return await self.tx_api.broadcast(tx_bytes=tx_bytes, mode=tx_service.BroadcastMode.BROADCAST_MODE_SYNC) + async def send_tx_async_mode(self, tx_byte: bytes) -> abci_type.TxResponse: + """ + This method is deprecated and will be removed soon. Please use `broadcast_tx_async_mode` instead + """ + warn("This method is deprecated. Use broadcast_tx_async_mode instead", DeprecationWarning, stacklevel=2) req = tx_service.BroadcastTxRequest(tx_bytes=tx_byte, mode=tx_service.BroadcastMode.BROADCAST_MODE_ASYNC) metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) result = await self.stubTx.BroadcastTx(request=req, metadata=metadata) return result.tx_response + async def broadcast_tx_async_mode(self, tx_bytes: bytes) -> Dict[str, Any]: + return await self.tx_api.broadcast(tx_bytes=tx_bytes, mode=tx_service.BroadcastMode.BROADCAST_MODE_ASYNC) + async def send_tx_block_mode(self, tx_byte: bytes) -> abci_type.TxResponse: + """ + This method is deprecated and will be removed soon. BLOCK broadcast mode should not be used + """ + warn("This method is deprecated. BLOCK broadcast mode should not be used", DeprecationWarning, stacklevel=2) req = tx_service.BroadcastTxRequest(tx_bytes=tx_byte, mode=tx_service.BroadcastMode.BROADCAST_MODE_BLOCK) metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) result = await self.stubTx.BroadcastTx(request=req, metadata=metadata) @@ -251,6 +476,10 @@ async def get_chain_id(self) -> str: return latest_block.block.header.chain_id async def get_grants(self, granter: str, grantee: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_grants` instead + """ + warn("This method is deprecated. Use fetch_grants instead", DeprecationWarning, stacklevel=2) return await self.stubAuthz.Grants( authz_query.QueryGrantsRequest( granter=granter, @@ -259,55 +488,162 @@ async def get_grants(self, granter: str, grantee: str, **kwargs): ) ) + async def fetch_grants( + self, + granter: str, + grantee: str, + msg_type_url: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.authz_api.fetch_grants( + granter=granter, + grantee=grantee, + msg_type_url=msg_type_url, + pagination=pagination, + ) + async def get_bank_balances(self, address: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_balances` instead + """ + warn("This method is deprecated. Use fetch_bank_balances instead", DeprecationWarning, stacklevel=2) return await self.stubBank.AllBalances(bank_query.QueryAllBalancesRequest(address=address)) + async def fetch_bank_balances(self, address: str) -> Dict[str, Any]: + return await self.bank_api.fetch_balances(account_address=address) + async def get_bank_balance(self, address: str, denom: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_bank_balance` instead + """ + warn("This method is deprecated. Use fetch_bank_balance instead", DeprecationWarning, stacklevel=2) return await self.stubBank.Balance(bank_query.QueryBalanceRequest(address=address, denom=denom)) + async def fetch_bank_balance(self, address: str, denom: str) -> Dict[str, Any]: + return await self.bank_api.fetch_balance(account_address=address, denom=denom) + # Injective Exchange client methods # Auction RPC async def get_auction(self, bid_round: int): + """ + This method is deprecated and will be removed soon. Please use `fetch_auction` instead + """ + warn("This method is deprecated. Use fetch_auction instead", DeprecationWarning, stacklevel=2) req = auction_rpc_pb.AuctionEndpointRequest(round=bid_round) return await self.stubAuction.AuctionEndpoint(req) + async def fetch_auction(self, round: int) -> Dict[str, Any]: + return await self.exchange_auction_api.fetch_auction(round=round) + async def get_auctions(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_auctions` instead + """ + warn("This method is deprecated. Use fetch_auctions instead", DeprecationWarning, stacklevel=2) req = auction_rpc_pb.AuctionsRequest() return await self.stubAuction.Auctions(req) + async def fetch_auctions(self) -> Dict[str, Any]: + return await self.exchange_auction_api.fetch_auctions() + async def stream_bids(self): + """ + This method is deprecated and will be removed soon. Please use `listen_bids_updates` instead + """ + warn("This method is deprecated. Use listen_bids_updates instead", DeprecationWarning, stacklevel=2) req = auction_rpc_pb.StreamBidsRequest() return self.stubAuction.StreamBids(req) + async def listen_bids_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_auction_stream_api.stream_bids( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + # Meta RPC async def ping(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_ping` instead + """ + warn("This method is deprecated. Use fetch_ping instead", DeprecationWarning, stacklevel=2) req = exchange_meta_rpc_pb.PingRequest() return await self.stubMeta.Ping(req) + async def fetch_ping(self) -> Dict[str, Any]: + return await self.exchange_meta_api.fetch_ping() + async def version(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_version` instead + """ + warn("This method is deprecated. Use fetch_version instead", DeprecationWarning, stacklevel=2) req = exchange_meta_rpc_pb.VersionRequest() return await self.stubMeta.Version(req) + async def fetch_version(self) -> Dict[str, Any]: + return await self.exchange_meta_api.fetch_version() + async def info(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_info` instead + """ + warn("This method is deprecated. Use fetch_info instead", DeprecationWarning, stacklevel=2) req = exchange_meta_rpc_pb.InfoRequest( timestamp=int(time.time() * 1000), ) return await self.stubMeta.Info(req) + async def fetch_info(self) -> Dict[str, Any]: + return await self.exchange_meta_api.fetch_info() + async def stream_keepalive(self): + """ + This method is deprecated and will be removed soon. Please use `listen_keepalive` instead + """ + warn("This method is deprecated. Use listen_keepalive instead", DeprecationWarning, stacklevel=2) req = exchange_meta_rpc_pb.StreamKeepaliveRequest() return self.stubMeta.StreamKeepalive(req) + async def listen_keepalive( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_meta_stream_api.stream_keepalive( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + # Explorer RPC async def get_tx_by_hash(self, tx_hash: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_tx_by_tx_hash` instead + """ + warn("This method is deprecated. Use fetch_tx_by_tx_hash instead", DeprecationWarning, stacklevel=2) + req = explorer_rpc_pb.GetTxByTxHashRequest(hash=tx_hash) return await self.stubExplorer.GetTxByTxHash(req) + async def fetch_tx_by_tx_hash(self, tx_hash: str) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_tx_by_tx_hash(tx_hash=tx_hash) + async def get_account_txs(self, address: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_account_txs` instead + """ + warn("This method is deprecated. Use fetch_account_txs instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetAccountTxsRequest( address=address, before=kwargs.get("before"), @@ -319,7 +655,35 @@ async def get_account_txs(self, address: str, **kwargs): ) return await self.stubExplorer.GetAccountTxs(req) + async def fetch_account_txs( + self, + address: str, + before: Optional[int] = None, + after: Optional[int] = None, + message_type: Optional[str] = None, + module: Optional[str] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + status: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_account_txs( + address=address, + before=before, + after=after, + message_type=message_type, + module=module, + from_number=from_number, + to_number=to_number, + status=status, + pagination=pagination, + ) + async def get_blocks(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_blocks` instead + """ + warn("This method is deprecated. Use fetch_blocks instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetBlocksRequest( before=kwargs.get("before"), after=kwargs.get("after"), @@ -327,11 +691,30 @@ async def get_blocks(self, **kwargs): ) return await self.stubExplorer.GetBlocks(req) + async def fetch_blocks( + self, + before: Optional[int] = None, + after: Optional[int] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_blocks(before=before, after=after, pagination=pagination) + async def get_block(self, block_height: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_block` instead + """ + warn("This method is deprecated. Use fetch_block instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetBlockRequest(id=block_height) return await self.stubExplorer.GetBlock(req) + async def fetch_block(self, block_id: str) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_block(block_id=block_id) + async def get_txs(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_txs` instead + """ + warn("This method is deprecated. Use fetch_txs instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetTxsRequest( before=kwargs.get("before"), after=kwargs.get("after"), @@ -342,15 +725,73 @@ async def get_txs(self, **kwargs): ) return await self.stubExplorer.GetTxs(req) + async def fetch_txs( + self, + before: Optional[int] = None, + after: Optional[int] = None, + message_type: Optional[str] = None, + module: Optional[str] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + status: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_txs( + before=before, + after=after, + message_type=message_type, + module=module, + from_number=from_number, + to_number=to_number, + status=status, + pagination=pagination, + ) + async def stream_txs(self): + """ + This method is deprecated and will be removed soon. Please use `listen_txs_updates` instead + """ + warn("This method is deprecated. Use listen_txs_updates instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.StreamTxsRequest() return self.stubExplorer.StreamTxs(req) + async def listen_txs_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_explorer_stream_api.stream_txs( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def stream_blocks(self): + """ + This method is deprecated and will be removed soon. Please use `listen_blocks_updates` instead + """ + warn("This method is deprecated. Use listen_blocks_updates instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.StreamBlocksRequest() return self.stubExplorer.StreamBlocks(req) + async def listen_blocks_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_explorer_stream_api.stream_blocks( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def get_peggy_deposits(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_peggy_deposit_txs` instead + """ + warn("This method is deprecated. Use fetch_peggy_deposit_txs instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetPeggyDepositTxsRequest( sender=kwargs.get("sender"), receiver=kwargs.get("receiver"), @@ -359,7 +800,23 @@ async def get_peggy_deposits(self, **kwargs): ) return await self.stubExplorer.GetPeggyDepositTxs(req) + async def fetch_peggy_deposit_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_peggy_deposit_txs( + sender=sender, + receiver=receiver, + pagination=pagination, + ) + async def get_peggy_withdrawals(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_peggy_withdrawal_txs` instead + """ + warn("This method is deprecated. Use fetch_peggy_withdrawal_txs instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetPeggyWithdrawalTxsRequest( sender=kwargs.get("sender"), receiver=kwargs.get("receiver"), @@ -368,7 +825,23 @@ async def get_peggy_withdrawals(self, **kwargs): ) return await self.stubExplorer.GetPeggyWithdrawalTxs(req) + async def fetch_peggy_withdrawal_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_peggy_withdrawal_txs( + sender=sender, + receiver=receiver, + pagination=pagination, + ) + async def get_ibc_transfers(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_ibc_transfer_txs` instead + """ + warn("This method is deprecated. Use fetch_ibc_transfer_txs instead", DeprecationWarning, stacklevel=2) req = explorer_rpc_pb.GetIBCTransferTxsRequest( sender=kwargs.get("sender"), receiver=kwargs.get("receiver"), @@ -381,29 +854,100 @@ async def get_ibc_transfers(self, **kwargs): ) return await self.stubExplorer.GetIBCTransferTxs(req) + async def fetch_ibc_transfer_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + src_channel: Optional[str] = None, + src_port: Optional[str] = None, + dest_channel: Optional[str] = None, + dest_port: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_explorer_api.fetch_ibc_transfer_txs( + sender=sender, + receiver=receiver, + src_channel=src_channel, + src_port=src_port, + dest_channel=dest_channel, + dest_port=dest_port, + pagination=pagination, + ) + # AccountsRPC async def stream_subaccount_balance(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_subaccount_balance_updates` instead + """ + warn( + "This method is deprecated. Use listen_subaccount_balance_updates instead", DeprecationWarning, stacklevel=2 + ) req = exchange_accounts_rpc_pb.StreamSubaccountBalanceRequest( subaccount_id=subaccount_id, denoms=kwargs.get("denoms") ) return self.stubExchangeAccount.StreamSubaccountBalance(req) + async def listen_subaccount_balance_updates( + self, + subaccount_id: str, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + denoms: Optional[List[str]] = None, + ): + await self.exchange_account_stream_api.stream_subaccount_balance( + subaccount_id=subaccount_id, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + denoms=denoms, + ) + async def get_subaccount_balance(self, subaccount_id: str, denom: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_subaccount_balance` instead + """ + warn("This method is deprecated. Use fetch_subaccount_balance instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.SubaccountBalanceEndpointRequest(subaccount_id=subaccount_id, denom=denom) return await self.stubExchangeAccount.SubaccountBalanceEndpoint(req) + async def fetch_subaccount_balance(self, subaccount_id: str, denom: str) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_subaccount_balance(subaccount_id=subaccount_id, denom=denom) + async def get_subaccount_list(self, account_address: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_subaccounts_list` instead + """ + warn("This method is deprecated. Use fetch_subaccounts_list instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.SubaccountsListRequest(account_address=account_address) return await self.stubExchangeAccount.SubaccountsList(req) + async def fetch_subaccounts_list(self, address: str) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_subaccounts_list(address=address) + async def get_subaccount_balances_list(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_subaccount_balances_list` instead + """ + warn("This method is deprecated. Use fetch_subaccount_balances_list instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.SubaccountBalancesListRequest( subaccount_id=subaccount_id, denoms=kwargs.get("denoms") ) return await self.stubExchangeAccount.SubaccountBalancesList(req) + async def fetch_subaccount_balances_list( + self, subaccount_id: str, denoms: Optional[List[str]] = None + ) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_subaccount_balances_list( + subaccount_id=subaccount_id, denoms=denoms + ) + async def get_subaccount_history(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_subaccount_history` instead + """ + warn("This method is deprecated. Use fetch_subaccount_history instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.SubaccountHistoryRequest( subaccount_id=subaccount_id, denom=kwargs.get("denom"), @@ -414,7 +958,25 @@ async def get_subaccount_history(self, subaccount_id: str, **kwargs): ) return await self.stubExchangeAccount.SubaccountHistory(req) + async def fetch_subaccount_history( + self, + subaccount_id: str, + denom: Optional[str] = None, + transfer_types: Optional[List[str]] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_subaccount_history( + subaccount_id=subaccount_id, + denom=denom, + transfer_types=transfer_types, + pagination=pagination, + ) + async def get_subaccount_order_summary(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_subaccount_order_summary` instead + """ + warn("This method is deprecated. Use fetch_subaccount_order_summary instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.SubaccountOrderSummaryRequest( subaccount_id=subaccount_id, order_direction=kwargs.get("order_direction"), @@ -422,31 +984,94 @@ async def get_subaccount_order_summary(self, subaccount_id: str, **kwargs): ) return await self.stubExchangeAccount.SubaccountOrderSummary(req) + async def fetch_subaccount_order_summary( + self, + subaccount_id: str, + market_id: Optional[str] = None, + order_direction: Optional[str] = None, + ) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_subaccount_order_summary( + subaccount_id=subaccount_id, + market_id=market_id, + order_direction=order_direction, + ) + async def get_order_states(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_order_states` instead + """ + warn("This method is deprecated. Use fetch_order_states instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.OrderStatesRequest( spot_order_hashes=kwargs.get("spot_order_hashes"), derivative_order_hashes=kwargs.get("derivative_order_hashes"), ) return await self.stubExchangeAccount.OrderStates(req) + async def fetch_order_states( + self, + spot_order_hashes: Optional[List[str]] = None, + derivative_order_hashes: Optional[List[str]] = None, + ) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_order_states( + spot_order_hashes=spot_order_hashes, + derivative_order_hashes=derivative_order_hashes, + ) + async def get_portfolio(self, account_address: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_portfolio` instead + """ + warn("This method is deprecated. Use fetch_portfolio instead", DeprecationWarning, stacklevel=2) + req = exchange_accounts_rpc_pb.PortfolioRequest(account_address=account_address) return await self.stubExchangeAccount.Portfolio(req) + async def fetch_portfolio(self, account_address: str) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_portfolio(account_address=account_address) + async def get_rewards(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_rewards` instead + """ + warn("This method is deprecated. Use fetch_rewards instead", DeprecationWarning, stacklevel=2) req = exchange_accounts_rpc_pb.RewardsRequest( account_address=kwargs.get("account_address"), epoch=kwargs.get("epoch") ) return await self.stubExchangeAccount.Rewards(req) + async def fetch_rewards(self, account_address: Optional[str] = None, epoch: Optional[int] = None) -> Dict[str, Any]: + return await self.exchange_account_api.fetch_rewards(account_address=account_address, epoch=epoch) + # OracleRPC async def stream_oracle_prices(self, base_symbol: str, quote_symbol: str, oracle_type: str): + """ + This method is deprecated and will be removed soon. Please use `listen_subaccount_balance_updates` instead + """ + warn("This method is deprecated. Use listen_oracle_prices_updates instead", DeprecationWarning, stacklevel=2) req = oracle_rpc_pb.StreamPricesRequest( base_symbol=base_symbol, quote_symbol=quote_symbol, oracle_type=oracle_type ) return self.stubOracle.StreamPrices(req) + async def listen_oracle_prices_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + base_symbol: Optional[str] = None, + quote_symbol: Optional[str] = None, + oracle_type: Optional[str] = None, + ): + await self.exchange_oracle_stream_api.stream_oracle_prices( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + ) + async def get_oracle_prices( self, base_symbol: str, @@ -454,6 +1079,10 @@ async def get_oracle_prices( oracle_type: str, oracle_scale_factor: int, ): + """ + This method is deprecated and will be removed soon. Please use `fetch_oracle_price` instead + """ + warn("This method is deprecated. Use fetch_oracle_price instead", DeprecationWarning, stacklevel=2) req = oracle_rpc_pb.PriceRequest( base_symbol=base_symbol, quote_symbol=quote_symbol, @@ -462,17 +1091,49 @@ async def get_oracle_prices( ) return await self.stubOracle.Price(req) + async def fetch_oracle_price( + self, + base_symbol: Optional[str] = None, + quote_symbol: Optional[str] = None, + oracle_type: Optional[str] = None, + oracle_scale_factor: Optional[int] = None, + ) -> Dict[str, Any]: + return await self.exchange_oracle_api.fetch_oracle_price( + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + oracle_scale_factor=oracle_scale_factor, + ) + async def get_oracle_list(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_oracle_list` instead + """ + warn("This method is deprecated. Use fetch_oracle_list instead", DeprecationWarning, stacklevel=2) req = oracle_rpc_pb.OracleListRequest() return await self.stubOracle.OracleList(req) + async def fetch_oracle_list(self) -> Dict[str, Any]: + return await self.exchange_oracle_api.fetch_oracle_list() + # InsuranceRPC async def get_insurance_funds(self): + """ + This method is deprecated and will be removed soon. Please use `fetch_insurance_funds` instead + """ + warn("This method is deprecated. Use fetch_insurance_funds instead", DeprecationWarning, stacklevel=2) req = insurance_rpc_pb.FundsRequest() return await self.stubInsurance.Funds(req) + async def fetch_insurance_funds(self) -> Dict[str, Any]: + return await self.exchange_insurance_api.fetch_insurance_funds() + async def get_redemptions(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_redemptions` instead + """ + warn("This method is deprecated. Use fetch_redemptions instead", DeprecationWarning, stacklevel=2) req = insurance_rpc_pb.RedemptionsRequest( redeemer=kwargs.get("redeemer"), redemption_denom=kwargs.get("redemption_denom"), @@ -480,13 +1141,36 @@ async def get_redemptions(self, **kwargs): ) return await self.stubInsurance.Redemptions(req) + async def fetch_redemptions( + self, + address: Optional[str] = None, + denom: Optional[str] = None, + status: Optional[str] = None, + ) -> Dict[str, Any]: + return await self.exchange_insurance_api.fetch_redemptions( + address=address, + denom=denom, + status=status, + ) + # SpotRPC async def get_spot_market(self, market_id: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_market` instead + """ + warn("This method is deprecated. Use fetch_spot_market instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.MarketRequest(market_id=market_id) return await self.stubSpotExchange.Market(req) + async def fetch_spot_market(self, market_id: str) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_market(market_id=market_id) + async def get_spot_markets(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_markets` instead + """ + warn("This method is deprecated. Use fetch_spot_markets instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.MarketsRequest( market_status=kwargs.get("market_status"), base_denom=kwargs.get("base_denom"), @@ -494,22 +1178,69 @@ async def get_spot_markets(self, **kwargs): ) return await self.stubSpotExchange.Markets(req) + async def fetch_spot_markets( + self, + market_statuses: Optional[List[str]] = None, + base_denom: Optional[str] = None, + quote_denom: Optional[str] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_markets( + market_statuses=market_statuses, base_denom=base_denom, quote_denom=quote_denom + ) + async def stream_spot_markets(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_markets_updates` instead + """ + warn("This method is deprecated. Use listen_spot_markets_updates instead", DeprecationWarning, stacklevel=2) + req = spot_exchange_rpc_pb.StreamMarketsRequest(market_ids=kwargs.get("market_ids")) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubSpotExchange.StreamMarkets(request=req, metadata=metadata) + async def listen_spot_markets_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + ): + await self.exchange_spot_stream_api.stream_markets( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + ) + async def get_spot_orderbookV2(self, market_id: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_orderbook_v2` instead + """ + warn("This method is deprecated. Use fetch_spot_orderbook_v2 instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.OrderbookV2Request(market_id=market_id) return await self.stubSpotExchange.OrderbookV2(req) + async def fetch_spot_orderbook_v2(self, market_id: str) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_orderbook_v2(market_id=market_id) + async def get_spot_orderbooksV2(self, market_ids: List): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_orderbooks_v2` instead + """ + warn("This method is deprecated. Use fetch_spot_orderbooks_v2 instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.OrderbooksV2Request(market_ids=market_ids) return await self.stubSpotExchange.OrderbooksV2(req) + async def fetch_spot_orderbooks_v2(self, market_ids: List[str]) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_orderbooks_v2(market_ids=market_ids) + async def get_spot_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_orders` instead + """ + warn("This method is deprecated. Use fetch_spot_orders instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.OrdersRequest( market_id=market_id, order_side=kwargs.get("order_side"), @@ -526,7 +1257,33 @@ async def get_spot_orders(self, market_id: str, **kwargs): ) return await self.stubSpotExchange.Orders(req) + async def fetch_spot_orders( + self, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_orders( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + pagination=pagination, + ) + async def get_historical_spot_orders(self, market_id: Optional[str] = None, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_orders_history` instead + """ + warn("This method is deprecated. Use fetch_spot_orders_history instead", DeprecationWarning, stacklevel=2) market_ids = kwargs.get("market_ids", []) if market_id is not None: market_ids.append(market_id) @@ -551,7 +1308,37 @@ async def get_historical_spot_orders(self, market_id: Optional[str] = None, **kw ) return await self.stubSpotExchange.OrdersHistory(req) + async def fetch_spot_orders_history( + self, + subaccount_id: Optional[str] = None, + market_ids: Optional[List[str]] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + active_markets_only: Optional[bool] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_orders_history( + subaccount_id=subaccount_id, + market_ids=market_ids, + order_types=order_types, + direction=direction, + state=state, + execution_types=execution_types, + trade_id=trade_id, + active_markets_only=active_markets_only, + cid=cid, + pagination=pagination, + ) + async def get_spot_trades(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_trades` instead + """ + warn("This method is deprecated. Use fetch_spot_trades instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.TradesRequest( market_id=kwargs.get("market_id"), execution_side=kwargs.get("execution_side"), @@ -570,21 +1357,85 @@ async def get_spot_trades(self, **kwargs): ) return await self.stubSpotExchange.Trades(req) + async def fetch_spot_trades( + self, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_trades( + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + pagination=pagination, + ) + async def stream_spot_orderbook_snapshot(self, market_ids: List[str]): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_orderbook_snapshots` instead + """ + warn("This method is deprecated. Use listen_spot_orderbook_snapshots instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.StreamOrderbookV2Request(market_ids=market_ids) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubSpotExchange.StreamOrderbookV2(request=req, metadata=metadata) + async def listen_spot_orderbook_snapshots( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_spot_stream_api.stream_orderbook_v2( + market_ids=market_ids, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def stream_spot_orderbook_update(self, market_ids: List[str]): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_orderbook_updates` instead + """ + warn("This method is deprecated. Use listen_spot_orderbook_updates instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.StreamOrderbookUpdateRequest(market_ids=market_ids) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubSpotExchange.StreamOrderbookUpdate(request=req, metadata=metadata) + async def listen_spot_orderbook_updates( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_spot_stream_api.stream_orderbook_update( + market_ids=market_ids, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def stream_spot_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_orders_updates` instead + """ + warn("This method is deprecated. Use listen_spot_orders_updates instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.StreamOrdersRequest( market_id=market_id, order_side=kwargs.get("order_side"), @@ -604,7 +1455,43 @@ async def stream_spot_orders(self, market_id: str, **kwargs): ) return self.stubSpotExchange.StreamOrders(request=req, metadata=metadata) + async def listen_spot_orders_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[PaginationOption] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + await self.exchange_spot_stream_api.stream_orders( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + pagination=pagination, + ) + async def stream_historical_spot_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_orders_history_updates` instead + """ + warn( + "This method is deprecated. Use listen_spot_orders_history_updates instead", + DeprecationWarning, + stacklevel=2, + ) req = spot_exchange_rpc_pb.StreamOrdersHistoryRequest( market_id=market_id, direction=kwargs.get("direction"), @@ -618,7 +1505,40 @@ async def stream_historical_spot_orders(self, market_id: str, **kwargs): ) return self.stubSpotExchange.StreamOrdersHistory(request=req, metadata=metadata) + async def listen_spot_orders_history_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + market_id: Optional[str] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + ): + await self.exchange_spot_stream_api.stream_orders_history( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + subaccount_id=subaccount_id, + market_id=market_id, + order_types=order_types, + direction=direction, + state=state, + execution_types=execution_types, + ) + async def stream_historical_derivative_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. + Please use `listen_derivative_orders_history_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_orders_history_updates instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.StreamOrdersHistoryRequest( market_id=market_id, direction=kwargs.get("direction"), @@ -632,7 +1552,35 @@ async def stream_historical_derivative_orders(self, market_id: str, **kwargs): ) return self.stubDerivativeExchange.StreamOrdersHistory(request=req, metadata=metadata) + async def listen_derivative_orders_history_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + market_id: Optional[str] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + ): + await self.exchange_derivative_stream_api.stream_orders_history( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + subaccount_id=subaccount_id, + market_id=market_id, + order_types=order_types, + direction=direction, + state=state, + execution_types=execution_types, + ) + async def stream_spot_trades(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_spot_trades_updates` instead + """ + warn("This method is deprecated. Use listen_spot_trades_updates instead", DeprecationWarning, stacklevel=2) req = spot_exchange_rpc_pb.StreamTradesRequest( market_id=kwargs.get("market_id"), execution_side=kwargs.get("execution_side"), @@ -654,7 +1602,43 @@ async def stream_spot_trades(self, **kwargs): ) return self.stubSpotExchange.StreamTrades(request=req, metadata=metadata) + async def listen_spot_trades_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + await self.exchange_spot_stream_api.stream_trades( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + pagination=pagination, + ) + async def get_spot_subaccount_orders(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_subaccount_orders_list` instead + """ + warn( + "This method is deprecated. Use fetch_spot_subaccount_orders_list instead", DeprecationWarning, stacklevel=2 + ) req = spot_exchange_rpc_pb.SubaccountOrdersListRequest( subaccount_id=subaccount_id, market_id=kwargs.get("market_id"), @@ -663,7 +1647,23 @@ async def get_spot_subaccount_orders(self, subaccount_id: str, **kwargs): ) return await self.stubSpotExchange.SubaccountOrdersList(req) + async def fetch_spot_subaccount_orders_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_subaccount_orders_list( + subaccount_id=subaccount_id, market_id=market_id, pagination=pagination + ) + async def get_spot_subaccount_trades(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_spot_subaccount_trades_list` instead + """ + warn( + "This method is deprecated. Use fetch_spot_subaccount_trades_list instead", DeprecationWarning, stacklevel=2 + ) req = spot_exchange_rpc_pb.SubaccountTradesListRequest( subaccount_id=subaccount_id, market_id=kwargs.get("market_id"), @@ -674,39 +1674,110 @@ async def get_spot_subaccount_trades(self, subaccount_id: str, **kwargs): ) return await self.stubSpotExchange.SubaccountTradesList(req) + async def fetch_spot_subaccount_trades_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + execution_type: Optional[str] = None, + direction: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_spot_api.fetch_subaccount_trades_list( + subaccount_id=subaccount_id, + market_id=market_id, + execution_type=execution_type, + direction=direction, + pagination=pagination, + ) + # DerivativeRPC async def get_derivative_market(self, market_id: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_market` instead + """ + warn("This method is deprecated. Use fetch_derivative_market instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.MarketRequest(market_id=market_id) return await self.stubDerivativeExchange.Market(req) + async def fetch_derivative_market(self, market_id: str) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_market(market_id=market_id) + async def get_derivative_markets(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_markets` instead + """ + warn("This method is deprecated. Use fetch_derivative_markets instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.MarketsRequest( market_status=kwargs.get("market_status"), quote_denom=kwargs.get("quote_denom"), ) return await self.stubDerivativeExchange.Markets(req) + async def fetch_derivative_markets( + self, + market_statuses: Optional[List[str]] = None, + quote_denom: Optional[str] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_markets( + market_statuses=market_statuses, + quote_denom=quote_denom, + ) + async def stream_derivative_markets(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_market_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_market_updates instead", DeprecationWarning, stacklevel=2 + ) req = derivative_exchange_rpc_pb.StreamMarketRequest(market_ids=kwargs.get("market_ids")) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubDerivativeExchange.StreamMarket(request=req, metadata=metadata) + async def listen_derivative_market_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + ): + await self.exchange_derivative_stream_api.stream_markets( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + ) + async def get_derivative_orderbook(self, market_id: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_orderbook_v2` instead + """ + warn("This method is deprecated. Use fetch_derivative_orderbook_v2 instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.OrderbookV2Request(market_id=market_id) return await self.stubDerivativeExchange.OrderbookV2(req) - async def get_derivative_orderbooks(self, market_ids: List): - req = derivative_exchange_rpc_pb.OrderbooksV2Request(market_ids=market_ids) - return await self.stubDerivativeExchange.OrderbooksV2(req) + async def fetch_derivative_orderbook_v2(self, market_id: str) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_orderbook_v2(market_id=market_id) - async def get_derivative_orderbooksV2(self, market_ids: List): + async def get_derivative_orderbooksV2(self, market_ids: List[str]): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_orderbooks_v2` instead + """ + warn("This method is deprecated. Use fetch_derivative_orderbooks_v2 instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.OrderbooksV2Request(market_ids=market_ids) return await self.stubDerivativeExchange.OrderbooksV2(req) + async def fetch_derivative_orderbooks_v2(self, market_ids: List[str]) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_orderbooks_v2(market_ids=market_ids) + async def get_derivative_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_orders` instead + """ + warn("This method is deprecated. Use fetch_derivative_orders instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.OrdersRequest( market_id=market_id, order_side=kwargs.get("order_side"), @@ -725,7 +1796,37 @@ async def get_derivative_orders(self, market_id: str, **kwargs): ) return await self.stubDerivativeExchange.Orders(req) + async def fetch_derivative_orders( + self, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[str] = None, + is_conditional: Optional[str] = None, + order_type: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_orders( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + is_conditional=is_conditional, + order_type=order_type, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + pagination=pagination, + ) + async def get_historical_derivative_orders(self, market_id: Optional[str] = None, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_orders_history` instead + """ + warn("This method is deprecated. Use fetch_derivative_orders_history instead", DeprecationWarning, stacklevel=2) market_ids = kwargs.get("market_ids", []) if market_id is not None: market_ids.append(market_id) @@ -751,7 +1852,39 @@ async def get_historical_derivative_orders(self, market_id: Optional[str] = None ) return await self.stubDerivativeExchange.OrdersHistory(req) + async def fetch_derivative_orders_history( + self, + subaccount_id: Optional[str] = None, + market_ids: Optional[List[str]] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + is_conditional: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + active_markets_only: Optional[bool] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_orders_history( + subaccount_id=subaccount_id, + market_ids=market_ids, + order_types=order_types, + direction=direction, + is_conditional=is_conditional, + state=state, + execution_types=execution_types, + trade_id=trade_id, + active_markets_only=active_markets_only, + cid=cid, + pagination=pagination, + ) + async def get_derivative_trades(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_trades` instead + """ + warn("This method is deprecated. Use fetch_derivative_trades instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.TradesRequest( market_id=kwargs.get("market_id"), execution_side=kwargs.get("execution_side"), @@ -770,35 +1903,109 @@ async def get_derivative_trades(self, **kwargs): ) return await self.stubDerivativeExchange.Trades(req) + async def fetch_derivative_trades( + self, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_trades( + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + pagination=pagination, + ) + async def stream_derivative_orderbook_snapshot(self, market_ids: List[str]): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_orderbook_snapshots` instead + """ + warn( + "This method is deprecated. Use listen_derivative_orderbook_snapshots instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.StreamOrderbookV2Request(market_ids=market_ids) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubDerivativeExchange.StreamOrderbookV2(request=req, metadata=metadata) + async def listen_derivative_orderbook_snapshots( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_derivative_stream_api.stream_orderbook_v2( + market_ids=market_ids, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def stream_derivative_orderbook_update(self, market_ids: List[str]): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_orderbook_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_orderbook_updates instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.StreamOrderbookUpdateRequest(market_ids=market_ids) metadata = await self.network.exchange_metadata( metadata_query_provider=self._exchange_cookie_metadata_requestor ) return self.stubDerivativeExchange.StreamOrderbookUpdate(request=req, metadata=metadata) + async def listen_derivative_orderbook_updates( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + await self.exchange_derivative_stream_api.stream_orderbook_update( + market_ids=market_ids, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + async def stream_derivative_orders(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_orders_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_orders_updates instead", DeprecationWarning, stacklevel=2 + ) req = derivative_exchange_rpc_pb.StreamOrdersRequest( market_id=market_id, - execution_side=kwargs.get("execution_side"), - direction=kwargs.get("direction"), + order_side=kwargs.get("order_side"), subaccount_id=kwargs.get("subaccount_id"), skip=kwargs.get("skip"), limit=kwargs.get("limit"), start_time=kwargs.get("start_time"), end_time=kwargs.get("end_time"), market_ids=kwargs.get("market_ids"), - subaccount_ids=kwargs.get("subaccount_ids"), - execution_types=kwargs.get("execution_types"), + is_conditional=kwargs.get("is_conditional"), + order_type=kwargs.get("order_type"), + include_inactive=kwargs.get("include_inactive"), + subaccount_total_orders=kwargs.get("subaccount_total_orders"), trade_id=kwargs.get("trade_id"), - account_address=kwargs.get("account_address"), cid=kwargs.get("cid"), ) metadata = await self.network.exchange_metadata( @@ -806,7 +2013,45 @@ async def stream_derivative_orders(self, market_id: str, **kwargs): ) return self.stubDerivativeExchange.StreamOrders(request=req, metadata=metadata) + async def listen_derivative_orders_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[PaginationOption] = None, + is_conditional: Optional[str] = None, + order_type: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + await self.exchange_derivative_stream_api.stream_orders( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + is_conditional=is_conditional, + order_type=order_type, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + pagination=pagination, + ) + async def stream_derivative_trades(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_trades_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_trades_updates instead", DeprecationWarning, stacklevel=2 + ) req = derivative_exchange_rpc_pb.StreamTradesRequest( market_id=kwargs.get("market_id"), execution_side=kwargs.get("execution_side"), @@ -828,7 +2073,41 @@ async def stream_derivative_trades(self, **kwargs): ) return self.stubDerivativeExchange.StreamTrades(request=req, metadata=metadata) + async def listen_derivative_trades_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + subaccount_ids: Optional[List[str]] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + return await self.exchange_derivative_stream_api.stream_trades( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + pagination=pagination, + ) + async def get_derivative_positions(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_positions` instead + """ + warn("This method is deprecated. Use fetch_derivative_positions instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.PositionsRequest( market_id=kwargs.get("market_id"), market_ids=kwargs.get("market_ids"), @@ -840,7 +2119,31 @@ async def get_derivative_positions(self, **kwargs): ) return await self.stubDerivativeExchange.Positions(req) + async def fetch_derivative_positions( + self, + market_ids: Optional[List[str]] = None, + subaccount_id: Optional[str] = None, + direction: Optional[str] = None, + subaccount_total_positions: Optional[bool] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_positions( + market_ids=market_ids, + subaccount_id=subaccount_id, + direction=direction, + subaccount_total_positions=subaccount_total_positions, + pagination=pagination, + ) + async def stream_derivative_positions(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_derivative_positions_updates` instead + """ + warn( + "This method is deprecated. Use listen_derivative_positions_updates instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.StreamPositionsRequest( market_id=kwargs.get("market_id"), market_ids=kwargs.get("market_ids"), @@ -852,7 +2155,31 @@ async def stream_derivative_positions(self, **kwargs): ) return self.stubDerivativeExchange.StreamPositions(request=req, metadata=metadata) + async def listen_derivative_positions_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + ): + await self.exchange_derivative_stream_api.stream_positions( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + ) + async def get_derivative_liquidable_positions(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_liquidable_positions` instead + """ + warn( + "This method is deprecated. Use fetch_derivative_liquidable_positions instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.LiquidablePositionsRequest( market_id=kwargs.get("market_id"), skip=kwargs.get("skip"), @@ -860,7 +2187,25 @@ async def get_derivative_liquidable_positions(self, **kwargs): ) return await self.stubDerivativeExchange.LiquidablePositions(req) + async def fetch_derivative_liquidable_positions( + self, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_liquidable_positions( + market_id=market_id, + pagination=pagination, + ) + async def get_derivative_subaccount_orders(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_subaccount_orders` instead + """ + warn( + "This method is deprecated. Use fetch_derivative_subaccount_orders instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.SubaccountOrdersListRequest( subaccount_id=subaccount_id, market_id=kwargs.get("market_id"), @@ -869,7 +2214,25 @@ async def get_derivative_subaccount_orders(self, subaccount_id: str, **kwargs): ) return await self.stubDerivativeExchange.SubaccountOrdersList(req) + async def fetch_subaccount_orders_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_subaccount_orders_list( + subaccount_id=subaccount_id, market_id=market_id, pagination=pagination + ) + async def get_derivative_subaccount_trades(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_derivative_subaccount_trades` instead + """ + warn( + "This method is deprecated. Use fetch_derivative_subaccount_trades instead", + DeprecationWarning, + stacklevel=2, + ) req = derivative_exchange_rpc_pb.SubaccountTradesListRequest( subaccount_id=subaccount_id, market_id=kwargs.get("market_id"), @@ -880,7 +2243,27 @@ async def get_derivative_subaccount_trades(self, subaccount_id: str, **kwargs): ) return await self.stubDerivativeExchange.SubaccountTradesList(req) + async def fetch_derivative_subaccount_trades_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + execution_type: Optional[str] = None, + direction: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_subaccount_trades_list( + subaccount_id=subaccount_id, + market_id=market_id, + execution_type=execution_type, + direction=direction, + pagination=pagination, + ) + async def get_funding_payments(self, subaccount_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_funding_payments` instead + """ + warn("This method is deprecated. Use fetch_funding_payments instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.FundingPaymentsRequest( subaccount_id=subaccount_id, market_id=kwargs.get("market_id"), @@ -891,7 +2274,21 @@ async def get_funding_payments(self, subaccount_id: str, **kwargs): ) return await self.stubDerivativeExchange.FundingPayments(req) + async def fetch_funding_payments( + self, + market_ids: Optional[List[str]] = None, + subaccount_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_funding_payments( + market_ids=market_ids, subaccount_id=subaccount_id, pagination=pagination + ) + async def get_funding_rates(self, market_id: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_funding_rates` instead + """ + warn("This method is deprecated. Use fetch_funding_rates instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.FundingRatesRequest( market_id=market_id, skip=kwargs.get("skip"), @@ -900,7 +2297,18 @@ async def get_funding_rates(self, market_id: str, **kwargs): ) return await self.stubDerivativeExchange.FundingRates(req) + async def fetch_funding_rates( + self, + market_id: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_funding_rates(market_id=market_id, pagination=pagination) + async def get_binary_options_markets(self, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `fetch_binary_options_markets` instead + """ + warn("This method is deprecated. Use fetch_binary_options_markets instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.BinaryOptionsMarketsRequest( market_status=kwargs.get("market_status"), quote_denom=kwargs.get("quote_denom"), @@ -909,17 +2317,49 @@ async def get_binary_options_markets(self, **kwargs): ) return await self.stubDerivativeExchange.BinaryOptionsMarkets(req) + async def fetch_binary_options_markets( + self, + market_status: Optional[str] = None, + quote_denom: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_binary_options_markets( + market_status=market_status, + quote_denom=quote_denom, + pagination=pagination, + ) + async def get_binary_options_market(self, market_id: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_binary_options_market` instead + """ + warn("This method is deprecated. Use fetch_binary_options_market instead", DeprecationWarning, stacklevel=2) req = derivative_exchange_rpc_pb.BinaryOptionsMarketRequest(market_id=market_id) return await self.stubDerivativeExchange.BinaryOptionsMarket(req) + async def fetch_binary_options_market(self, market_id: str) -> Dict[str, Any]: + return await self.exchange_derivative_api.fetch_binary_options_market(market_id=market_id) + # PortfolioRPC async def get_account_portfolio(self, account_address: str): + """ + This method is deprecated and will be removed soon. Please use `fetch_account_portfolio` instead + """ + warn("This method is deprecated. Use fetch_account_portfolio instead", DeprecationWarning, stacklevel=2) req = portfolio_rpc_pb.AccountPortfolioRequest(account_address=account_address) return await self.stubPortfolio.AccountPortfolio(req) + async def fetch_account_portfolio(self, account_address: str) -> Dict[str, Any]: + return await self.exchange_portfolio_api.fetch_account_portfolio(account_address=account_address) + async def stream_account_portfolio(self, account_address: str, **kwargs): + """ + This method is deprecated and will be removed soon. Please use `listen_account_portfolio_updates` instead + """ + warn( + "This method is deprecated. Use listen_account_portfolio_updates instead", DeprecationWarning, stacklevel=2 + ) req = portfolio_rpc_pb.StreamAccountPortfolioRequest( account_address=account_address, subaccount_id=kwargs.get("subaccount_id"), type=kwargs.get("type") ) @@ -928,6 +2368,24 @@ async def stream_account_portfolio(self, account_address: str, **kwargs): ) return self.stubPortfolio.StreamAccountPortfolio(request=req, metadata=metadata) + async def listen_account_portfolio_updates( + self, + account_address: str, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + update_type: Optional[str] = None, + ): + await self.exchange_portfolio_stream_api.stream_account_portfolio( + account_address=account_address, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + subaccount_id=subaccount_id, + update_type=update_type, + ) + async def chain_stream( self, bank_balances_filter: Optional[chain_stream_query.BankBalancesFilter] = None, @@ -941,6 +2399,10 @@ async def chain_stream( positions_filter: Optional[chain_stream_query.PositionsFilter] = None, oracle_price_filter: Optional[chain_stream_query.OraclePriceFilter] = None, ): + """ + This method is deprecated and will be removed soon. Please use `listen_chain_stream_updates` instead + """ + warn("This method is deprecated. Use listen_chain_stream_updates instead", DeprecationWarning, stacklevel=2) request = chain_stream_query.StreamRequest( bank_balances_filter=bank_balances_filter, subaccount_deposits_filter=subaccount_deposits_filter, @@ -956,6 +2418,38 @@ async def chain_stream( metadata = await self.network.chain_metadata(metadata_query_provider=self._chain_cookie_metadata_requestor) return self.chain_stream_stub.Stream(request=request, metadata=metadata) + async def listen_chain_stream_updates( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + bank_balances_filter: Optional[chain_stream_query.BankBalancesFilter] = None, + subaccount_deposits_filter: Optional[chain_stream_query.SubaccountDepositsFilter] = None, + spot_trades_filter: Optional[chain_stream_query.TradesFilter] = None, + derivative_trades_filter: Optional[chain_stream_query.TradesFilter] = None, + spot_orders_filter: Optional[chain_stream_query.OrdersFilter] = None, + derivative_orders_filter: Optional[chain_stream_query.OrdersFilter] = None, + spot_orderbooks_filter: Optional[chain_stream_query.OrderbookFilter] = None, + derivative_orderbooks_filter: Optional[chain_stream_query.OrderbookFilter] = None, + positions_filter: Optional[chain_stream_query.PositionsFilter] = None, + oracle_price_filter: Optional[chain_stream_query.OraclePriceFilter] = None, + ): + return await self.chain_stream_api.stream( + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + bank_balances_filter=bank_balances_filter, + subaccount_deposits_filter=subaccount_deposits_filter, + spot_trades_filter=spot_trades_filter, + derivative_trades_filter=derivative_trades_filter, + spot_orders_filter=spot_orders_filter, + derivative_orders_filter=derivative_orders_filter, + spot_orderbooks_filter=spot_orderbooks_filter, + derivative_orderbooks_filter=derivative_orderbooks_filter, + positions_filter=positions_filter, + oracle_price_filter=oracle_price_filter, + ) + async def composer(self): return Composer( network=self.network.string(), @@ -971,106 +2465,113 @@ async def _initialize_tokens_and_markets(self): binary_option_markets = dict() tokens_by_symbol = dict() tokens_by_denom = dict() - markets_info = (await self.get_spot_markets(market_status="active")).markets + markets_info = (await self.fetch_spot_markets(market_statuses=["active"]))["markets"] valid_markets = ( market_info for market_info in markets_info - if len(market_info.base_token_meta.SerializeToString()) > 0 - and len(market_info.quote_token_meta.SerializeToString()) > 0 + if len(market_info.get("baseTokenMeta", {}).get("symbol", "")) > 0 + and len(market_info.get("quoteTokenMeta", {}).get("symbol", "")) > 0 ) for market_info in valid_markets: - if "/" in market_info.ticker: - base_token_symbol, quote_token_symbol = market_info.ticker.split(constant.TICKER_TOKENS_SEPARATOR) + ticker = market_info["ticker"] + if "/" in ticker: + base_token_symbol, quote_token_symbol = ticker.split(constant.TICKER_TOKENS_SEPARATOR) else: - base_token_symbol = market_info.base_token_meta.symbol - quote_token_symbol = market_info.quote_token_meta.symbol + base_token_symbol = market_info["baseTokenMeta"]["symbol"] + quote_token_symbol = market_info["quoteTokenMeta"]["symbol"] base_token = self._token_representation( symbol=base_token_symbol, - token_meta=market_info.base_token_meta, - denom=market_info.base_denom, + token_meta=market_info["baseTokenMeta"], + denom=market_info["baseDenom"], tokens_by_denom=tokens_by_denom, tokens_by_symbol=tokens_by_symbol, ) quote_token = self._token_representation( symbol=quote_token_symbol, - token_meta=market_info.quote_token_meta, - denom=market_info.quote_denom, + token_meta=market_info["quoteTokenMeta"], + denom=market_info["quoteDenom"], tokens_by_denom=tokens_by_denom, tokens_by_symbol=tokens_by_symbol, ) market = SpotMarket( - id=market_info.market_id, - status=market_info.market_status, - ticker=market_info.ticker, + id=market_info["marketId"], + status=market_info["marketStatus"], + ticker=market_info["ticker"], base_token=base_token, quote_token=quote_token, - maker_fee_rate=Decimal(market_info.maker_fee_rate), - taker_fee_rate=Decimal(market_info.taker_fee_rate), - service_provider_fee=Decimal(market_info.service_provider_fee), - min_price_tick_size=Decimal(market_info.min_price_tick_size), - min_quantity_tick_size=Decimal(market_info.min_quantity_tick_size), + maker_fee_rate=Decimal(market_info["makerFeeRate"]), + taker_fee_rate=Decimal(market_info["takerFeeRate"]), + service_provider_fee=Decimal(market_info["serviceProviderFee"]), + min_price_tick_size=Decimal(market_info["minPriceTickSize"]), + min_quantity_tick_size=Decimal(market_info["minQuantityTickSize"]), ) spot_markets[market.id] = market - markets_info = (await self.get_derivative_markets(market_status="active")).markets + markets_info = (await self.fetch_derivative_markets(market_statuses=["active"]))["markets"] valid_markets = ( - market_info for market_info in markets_info if len(market_info.quote_token_meta.SerializeToString()) > 0 + market_info + for market_info in markets_info + if len(market_info.get("quoteTokenMeta", {}).get("symbol", "")) > 0 ) + for market_info in valid_markets: - quote_token_symbol = market_info.quote_token_meta.symbol + quote_token_symbol = market_info["quoteTokenMeta"]["symbol"] quote_token = self._token_representation( symbol=quote_token_symbol, - token_meta=market_info.quote_token_meta, - denom=market_info.quote_denom, + token_meta=market_info["quoteTokenMeta"], + denom=market_info["quoteDenom"], tokens_by_denom=tokens_by_denom, tokens_by_symbol=tokens_by_symbol, ) market = DerivativeMarket( - id=market_info.market_id, - status=market_info.market_status, - ticker=market_info.ticker, - oracle_base=market_info.oracle_base, - oracle_quote=market_info.oracle_quote, - oracle_type=market_info.oracle_type, - oracle_scale_factor=market_info.oracle_scale_factor, - initial_margin_ratio=Decimal(market_info.initial_margin_ratio), - maintenance_margin_ratio=Decimal(market_info.maintenance_margin_ratio), + id=market_info["marketId"], + status=market_info["marketStatus"], + ticker=market_info["ticker"], + oracle_base=market_info["oracleBase"], + oracle_quote=market_info["oracleQuote"], + oracle_type=market_info["oracleType"], + oracle_scale_factor=market_info["oracleScaleFactor"], + initial_margin_ratio=Decimal(market_info["initialMarginRatio"]), + maintenance_margin_ratio=Decimal(market_info["maintenanceMarginRatio"]), quote_token=quote_token, - maker_fee_rate=Decimal(market_info.maker_fee_rate), - taker_fee_rate=Decimal(market_info.taker_fee_rate), - service_provider_fee=Decimal(market_info.service_provider_fee), - min_price_tick_size=Decimal(market_info.min_price_tick_size), - min_quantity_tick_size=Decimal(market_info.min_quantity_tick_size), + maker_fee_rate=Decimal(market_info["makerFeeRate"]), + taker_fee_rate=Decimal(market_info["takerFeeRate"]), + service_provider_fee=Decimal(market_info["serviceProviderFee"]), + min_price_tick_size=Decimal(market_info["minPriceTickSize"]), + min_quantity_tick_size=Decimal(market_info["minQuantityTickSize"]), ) derivative_markets[market.id] = market - markets_info = (await self.get_binary_options_markets()).markets + markets_info = (await self.fetch_binary_options_markets(market_status="active"))["markets"] for market_info in markets_info: - quote_token = tokens_by_denom.get(market_info.quote_denom, None) + quote_token = tokens_by_denom.get(market_info["quoteDenom"], None) market = BinaryOptionMarket( - id=market_info.market_id, - status=market_info.market_status, - ticker=market_info.ticker, - oracle_symbol=market_info.oracle_symbol, - oracle_provider=market_info.oracle_provider, - oracle_type=market_info.oracle_type, - oracle_scale_factor=market_info.oracle_scale_factor, - expiration_timestamp=market_info.expiration_timestamp, - settlement_timestamp=market_info.settlement_timestamp, + id=market_info["marketId"], + status=market_info["marketStatus"], + ticker=market_info["ticker"], + oracle_symbol=market_info["oracleSymbol"], + oracle_provider=market_info["oracleProvider"], + oracle_type=market_info["oracleType"], + oracle_scale_factor=market_info["oracleScaleFactor"], + expiration_timestamp=market_info["expirationTimestamp"], + settlement_timestamp=market_info["settlementTimestamp"], quote_token=quote_token, - maker_fee_rate=Decimal(market_info.maker_fee_rate), - taker_fee_rate=Decimal(market_info.taker_fee_rate), - service_provider_fee=Decimal(market_info.service_provider_fee), - min_price_tick_size=Decimal(market_info.min_price_tick_size), - min_quantity_tick_size=Decimal(market_info.min_quantity_tick_size), + maker_fee_rate=Decimal(market_info["makerFeeRate"]), + taker_fee_rate=Decimal(market_info["takerFeeRate"]), + service_provider_fee=Decimal(market_info["serviceProviderFee"]), + min_price_tick_size=Decimal(market_info["minPriceTickSize"]), + min_quantity_tick_size=Decimal(market_info["minQuantityTickSize"]), + settlement_price=None + if market_info["settlementPrice"] == "" + else Decimal(market_info["settlementPrice"]), ) binary_option_markets[market.id] = market @@ -1084,26 +2585,26 @@ async def _initialize_tokens_and_markets(self): def _token_representation( self, symbol: str, - token_meta, + token_meta: Dict[str, Any], denom: str, tokens_by_denom: Dict[str, Token], tokens_by_symbol: Dict[str, Token], ) -> Token: if denom not in tokens_by_denom: unique_symbol = denom - for symbol_candidate in [symbol, token_meta.symbol, token_meta.name]: + for symbol_candidate in [symbol, token_meta["symbol"], token_meta["name"]]: if symbol_candidate not in tokens_by_symbol: unique_symbol = symbol_candidate break token = Token( - name=token_meta.name, + name=token_meta["name"], symbol=symbol, denom=denom, - address=token_meta.address, - decimals=token_meta.decimals, - logo=token_meta.logo, - updated=token_meta.updated_at, + address=token_meta["address"], + decimals=token_meta["decimals"], + logo=token_meta["logo"], + updated=int(token_meta["updatedAt"]), ) tokens_by_denom[denom] = token @@ -1119,6 +2620,10 @@ def _exchange_cookie_metadata_requestor(self) -> Coroutine: request = exchange_meta_rpc_pb.VersionRequest() return self.stubMeta.Version(request).initial_metadata() + def _explorer_cookie_metadata_requestor(self) -> Coroutine: + request = explorer_rpc_pb.GetBlocksRequest() + return self.stubExplorer.GetBlocks(request).initial_metadata() + def _initialize_timeout_height_sync_task(self): self._cancel_timeout_height_sync_task() self._timeout_height_sync_task = asyncio.get_event_loop().create_task(self._timeout_height_sync_process()) diff --git a/pyinjective/client/__init__.py b/pyinjective/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/__init__.py b/pyinjective/client/chain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/grpc/__init__.py b/pyinjective/client/chain/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/grpc/chain_grpc_auction_api.py b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py new file mode 100644 index 00000000..3f9d89fb --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_auction_api.py @@ -0,0 +1,36 @@ +from typing import Any, Callable, Dict + +from grpc.aio import Channel + +from pyinjective.proto.injective.auction.v1beta1 import ( + query_pb2 as auction_query_pb, + query_pb2_grpc as auction_query_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class ChainGrpcAuctionApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = auction_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = auction_query_pb.QueryAuctionParamsRequest() + response = await self._execute_call(call=self._stub.AuctionParams, request=request) + + return response + + async def fetch_module_state(self) -> Dict[str, Any]: + request = auction_query_pb.QueryModuleStateRequest() + response = await self._execute_call(call=self._stub.AuctionModuleState, request=request) + + return response + + async def fetch_current_basket(self) -> Dict[str, Any]: + request = auction_query_pb.QueryCurrentAuctionBasketRequest() + response = await self._execute_call(call=self._stub.CurrentAuctionBasket, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/chain/grpc/chain_grpc_auth_api.py b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py new file mode 100644 index 00000000..7b0c73dc --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_auth_api.py @@ -0,0 +1,34 @@ +from typing import Any, Callable, Dict + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query_pb, query_pb2_grpc as auth_query_grpc +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class ChainGrpcAuthApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = auth_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = auth_query_pb.QueryParamsRequest() + response = await self._execute_call(call=self._stub.Params, request=request) + + return response + + async def fetch_account(self, address: str) -> Dict[str, Any]: + request = auth_query_pb.QueryAccountRequest(address=address) + response = await self._execute_call(call=self._stub.Account, request=request) + + return response + + async def fetch_accounts(self, pagination_option: PaginationOption) -> Dict[str, Any]: + request = auth_query_pb.QueryAccountsRequest(pagination=pagination_option.create_pagination_request()) + response = await self._execute_call(call=self._stub.Accounts, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/chain/grpc/chain_grpc_authz_api.py b/pyinjective/client/chain/grpc/chain_grpc_authz_api.py new file mode 100644 index 00000000..dae2e3c5 --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_authz_api.py @@ -0,0 +1,62 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query, query_pb2_grpc as authz_query_grpc +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class ChainGrpcAuthZApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = authz_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_grants( + self, + granter: str, + grantee: str, + msg_type_url: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = authz_query.QueryGrantsRequest( + granter=granter, grantee=grantee, msg_type_url=msg_type_url, pagination=pagination_request + ) + + response = await self._execute_call(call=self._stub.Grants, request=request) + + return response + + async def fetch_granter_grants( + self, + granter: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = authz_query.QueryGranterGrantsRequest(granter=granter, pagination=pagination_request) + + response = await self._execute_call(call=self._stub.GranterGrants, request=request) + + return response + + async def fetch_grantee_grants( + self, + grantee: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination_request = None + if pagination is not None: + pagination_request = pagination.create_pagination_request() + request = authz_query.QueryGranteeGrantsRequest(grantee=grantee, pagination=pagination_request) + + response = await self._execute_call(call=self._stub.GranteeGrants, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/chain/grpc/chain_grpc_bank_api.py b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py new file mode 100644 index 00000000..b774707f --- /dev/null +++ b/pyinjective/client/chain/grpc/chain_grpc_bank_api.py @@ -0,0 +1,39 @@ +from typing import Any, Callable, Dict + +from grpc.aio import Channel + +from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query_pb, query_pb2_grpc as bank_query_grpc +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class ChainGrpcBankApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = bank_query_grpc.QueryStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_module_params(self) -> Dict[str, Any]: + request = bank_query_pb.QueryParamsRequest() + response = await self._execute_call(call=self._stub.Params, request=request) + + return response + + async def fetch_balance(self, account_address: str, denom: str) -> Dict[str, Any]: + request = bank_query_pb.QueryBalanceRequest(address=account_address, denom=denom) + response = await self._execute_call(call=self._stub.Balance, request=request) + + return response + + async def fetch_balances(self, account_address: str) -> Dict[str, Any]: + request = bank_query_pb.QueryAllBalancesRequest(address=account_address) + response = await self._execute_call(call=self._stub.AllBalances, request=request) + + return response + + async def fetch_total_supply(self) -> Dict[str, Any]: + request = bank_query_pb.QueryTotalSupplyRequest() + response = await self._execute_call(call=self._stub.TotalSupply, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/chain/grpc_stream/__init__.py b/pyinjective/client/chain/grpc_stream/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/grpc_stream/chain_grpc_chain_stream.py b/pyinjective/client/chain/grpc_stream/chain_grpc_chain_stream.py new file mode 100644 index 00000000..46b99780 --- /dev/null +++ b/pyinjective/client/chain/grpc_stream/chain_grpc_chain_stream.py @@ -0,0 +1,49 @@ +from typing import Callable, Optional + +from grpc.aio import Channel + +from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_pb, query_pb2_grpc as chain_stream_grpc +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class ChainGrpcChainStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = chain_stream_grpc.StreamStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + bank_balances_filter: Optional[chain_stream_pb.BankBalancesFilter] = None, + subaccount_deposits_filter: Optional[chain_stream_pb.SubaccountDepositsFilter] = None, + spot_trades_filter: Optional[chain_stream_pb.TradesFilter] = None, + derivative_trades_filter: Optional[chain_stream_pb.TradesFilter] = None, + spot_orders_filter: Optional[chain_stream_pb.OrdersFilter] = None, + derivative_orders_filter: Optional[chain_stream_pb.OrdersFilter] = None, + spot_orderbooks_filter: Optional[chain_stream_pb.OrderbookFilter] = None, + derivative_orderbooks_filter: Optional[chain_stream_pb.OrderbookFilter] = None, + positions_filter: Optional[chain_stream_pb.PositionsFilter] = None, + oracle_price_filter: Optional[chain_stream_pb.OraclePriceFilter] = None, + ): + request = chain_stream_pb.StreamRequest( + bank_balances_filter=bank_balances_filter, + subaccount_deposits_filter=subaccount_deposits_filter, + spot_trades_filter=spot_trades_filter, + derivative_trades_filter=derivative_trades_filter, + spot_orders_filter=spot_orders_filter, + derivative_orders_filter=derivative_orders_filter, + spot_orderbooks_filter=spot_orderbooks_filter, + derivative_orderbooks_filter=derivative_orderbooks_filter, + positions_filter=positions_filter, + oracle_price_filter=oracle_price_filter, + ) + + await self._assistant.listen_stream( + call=self._stub.Stream, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/chain/model/__init__.py b/pyinjective/client/chain/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/chain/model/account.py b/pyinjective/client/chain/model/account.py new file mode 100644 index 00000000..5aa401c3 --- /dev/null +++ b/pyinjective/client/chain/model/account.py @@ -0,0 +1,42 @@ +from google.protobuf import any_pb2 + +from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb + + +class Account: + def __init__( + self, + address: str, + pub_key_type_url: str, + pub_key_value: bytes, + account_number: int, + sequence: int, + code_hash: str, + ): + super().__init__() + self.address = address + self.pub_key_type_url = pub_key_type_url + self.pub_key_value = pub_key_value + self.account_number = account_number + self.sequence = sequence + self.code_hash = code_hash + + @classmethod + def from_proto(cls, proto_account: any_pb2.Any): + eth_account = account_pb.EthAccount() + proto_account.Unpack(eth_account) + pub_key_type_url = None + pub_key_value = None + + if eth_account.base_account.pub_key is not None: + pub_key_type_url = eth_account.base_account.pub_key.type_url + pub_key_value = eth_account.base_account.pub_key.value + + return cls( + address=eth_account.base_account.address, + pub_key_type_url=pub_key_type_url, + pub_key_value=pub_key_value, + account_number=eth_account.base_account.account_number, + sequence=eth_account.base_account.sequence, + code_hash=f"0x{eth_account.code_hash.hex()}", + ) diff --git a/pyinjective/client/chain/model/auth_params.py b/pyinjective/client/chain/model/auth_params.py new file mode 100644 index 00000000..4d238cff --- /dev/null +++ b/pyinjective/client/chain/model/auth_params.py @@ -0,0 +1,28 @@ +from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query_pb + + +class AuthParams: + def __init__( + self, + max_memo_characters: int, + tx_sig_limit: int, + tx_size_cost_per_byte: int, + sig_verify_cost_ed25519: int, + sig_verify_cost_secp256k1: int, + ): + super().__init__() + self.max_memo_characters = max_memo_characters + self.tx_sig_limit = tx_sig_limit + self.tx_size_cost_per_byte = tx_size_cost_per_byte + self.sig_verify_cost_ed25519 = sig_verify_cost_ed25519 + self.sig_verify_cost_secp256k1 = sig_verify_cost_secp256k1 + + @classmethod + def from_proto_response(cls, response: auth_query_pb.QueryParamsResponse): + return cls( + max_memo_characters=response.params.max_memo_characters, + tx_sig_limit=response.params.tx_sig_limit, + tx_size_cost_per_byte=response.params.tx_size_cost_per_byte, + sig_verify_cost_ed25519=response.params.sig_verify_cost_ed25519, + sig_verify_cost_secp256k1=response.params.sig_verify_cost_secp256k1, + ) diff --git a/pyinjective/client/indexer/__init__.py b/pyinjective/client/indexer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/indexer/grpc/__init__.py b/pyinjective/client/indexer/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py new file mode 100644 index 00000000..8fc387c1 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_account_api.py @@ -0,0 +1,107 @@ +from typing import Any, Callable, Dict, List, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.exchange import ( + injective_accounts_rpc_pb2 as exchange_accounts_pb, + injective_accounts_rpc_pb2_grpc as exchange_accounts_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcAccountApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_accounts_grpc.InjectiveAccountsRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_portfolio(self, account_address: str) -> Dict[str, Any]: + request = exchange_accounts_pb.PortfolioRequest(account_address=account_address) + response = await self._execute_call(call=self._stub.Portfolio, request=request) + + return response + + async def fetch_order_states( + self, + spot_order_hashes: Optional[List[str]] = None, + derivative_order_hashes: Optional[List[str]] = None, + ) -> Dict[str, Any]: + spot_order_hashes = spot_order_hashes or [] + derivative_order_hashes = derivative_order_hashes or [] + + request = exchange_accounts_pb.OrderStatesRequest( + spot_order_hashes=spot_order_hashes, derivative_order_hashes=derivative_order_hashes + ) + response = await self._execute_call(call=self._stub.OrderStates, request=request) + + return response + + async def fetch_subaccounts_list(self, address: str) -> Dict[str, Any]: + request = exchange_accounts_pb.SubaccountsListRequest(account_address=address) + response = await self._execute_call(call=self._stub.SubaccountsList, request=request) + + return response + + async def fetch_subaccount_balances_list( + self, subaccount_id: str, denoms: Optional[List[str]] = None + ) -> Dict[str, Any]: + request = exchange_accounts_pb.SubaccountBalancesListRequest( + subaccount_id=subaccount_id, + denoms=denoms, + ) + response = await self._execute_call(call=self._stub.SubaccountBalancesList, request=request) + + return response + + async def fetch_subaccount_balance(self, subaccount_id: str, denom: str) -> Dict[str, Any]: + request = exchange_accounts_pb.SubaccountBalanceEndpointRequest( + subaccount_id=subaccount_id, + denom=denom, + ) + response = await self._execute_call(call=self._stub.SubaccountBalanceEndpoint, request=request) + + return response + + async def fetch_subaccount_history( + self, + subaccount_id: str, + denom: Optional[str] = None, + transfer_types: Optional[List[str]] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_accounts_pb.SubaccountHistoryRequest( + subaccount_id=subaccount_id, + denom=denom, + transfer_types=transfer_types, + skip=pagination.skip, + limit=pagination.limit, + end_time=pagination.end_time, + ) + response = await self._execute_call(call=self._stub.SubaccountHistory, request=request) + + return response + + async def fetch_subaccount_order_summary( + self, + subaccount_id: str, + market_id: Optional[str] = None, + order_direction: Optional[str] = None, + ) -> Dict[str, Any]: + request = exchange_accounts_pb.SubaccountOrderSummaryRequest( + subaccount_id=subaccount_id, + market_id=market_id, + order_direction=order_direction, + ) + response = await self._execute_call(call=self._stub.SubaccountOrderSummary, request=request) + + return response + + async def fetch_rewards(self, account_address: Optional[str] = None, epoch: Optional[int] = None) -> Dict[str, Any]: + request = exchange_accounts_pb.RewardsRequest(account_address=account_address, epoch=epoch) + response = await self._execute_call(call=self._stub.Rewards, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_auction_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_auction_api.py new file mode 100644 index 00000000..c9b9d853 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_auction_api.py @@ -0,0 +1,30 @@ +from typing import Any, Callable, Dict + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_auction_rpc_pb2 as exchange_auction_pb, + injective_auction_rpc_pb2_grpc as exchange_auction_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcAuctionApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_auction_grpc.InjectiveAuctionRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_auction(self, round: int) -> Dict[str, Any]: + request = exchange_auction_pb.AuctionEndpointRequest(round=round) + response = await self._execute_call(call=self._stub.AuctionEndpoint, request=request) + + return response + + async def fetch_auctions(self) -> Dict[str, Any]: + request = exchange_auction_pb.AuctionsRequest() + response = await self._execute_call(call=self._stub.Auctions, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_derivative_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_derivative_api.py new file mode 100644 index 00000000..a79f9cac --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_derivative_api.py @@ -0,0 +1,291 @@ +from typing import Any, Callable, Dict, List, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.exchange import ( + injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb, + injective_derivative_exchange_rpc_pb2_grpc as exchange_derivative_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcDerivativeApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_derivative_grpc.InjectiveDerivativeExchangeRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_markets( + self, + market_statuses: Optional[List[str]] = None, + quote_denom: Optional[str] = None, + ) -> Dict[str, Any]: + request = exchange_derivative_pb.MarketsRequest( + market_statuses=market_statuses, + quote_denom=quote_denom, + ) + response = await self._execute_call(call=self._stub.Markets, request=request) + + return response + + async def fetch_market(self, market_id: str) -> Dict[str, Any]: + request = exchange_derivative_pb.MarketRequest(market_id=market_id) + response = await self._execute_call(call=self._stub.Market, request=request) + + return response + + async def fetch_binary_options_markets( + self, + market_status: Optional[str] = None, + quote_denom: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.BinaryOptionsMarketsRequest( + market_status=market_status, + quote_denom=quote_denom, + skip=pagination.skip, + limit=pagination.limit, + ) + response = await self._execute_call(call=self._stub.BinaryOptionsMarkets, request=request) + + return response + + async def fetch_binary_options_market(self, market_id: str) -> Dict[str, Any]: + request = exchange_derivative_pb.BinaryOptionsMarketRequest(market_id=market_id) + response = await self._execute_call(call=self._stub.BinaryOptionsMarket, request=request) + + return response + + async def fetch_orderbook_v2(self, market_id: str) -> Dict[str, Any]: + request = exchange_derivative_pb.OrderbookV2Request(market_id=market_id) + response = await self._execute_call(call=self._stub.OrderbookV2, request=request) + + return response + + async def fetch_orderbooks_v2(self, market_ids: List[str]) -> Dict[str, Any]: + request = exchange_derivative_pb.OrderbooksV2Request(market_ids=market_ids) + response = await self._execute_call(call=self._stub.OrderbooksV2, request=request) + + return response + + async def fetch_orders( + self, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[str] = None, + is_conditional: Optional[str] = None, + order_type: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.OrdersRequest( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + is_conditional=is_conditional, + order_type=order_type, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.Orders, request=request) + + return response + + async def fetch_positions( + self, + market_ids: Optional[List[str]] = None, + subaccount_id: Optional[str] = None, + direction: Optional[str] = None, + subaccount_total_positions: Optional[bool] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.PositionsRequest( + market_ids=market_ids, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + direction=direction, + subaccount_total_positions=subaccount_total_positions, + ) + + response = await self._execute_call(call=self._stub.Positions, request=request) + + return response + + async def fetch_liquidable_positions( + self, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.LiquidablePositionsRequest( + market_id=market_id, + skip=pagination.skip, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.LiquidablePositions, request=request) + + return response + + async def fetch_funding_payments( + self, + market_ids: Optional[List[str]] = None, + subaccount_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.FundingPaymentsRequest( + market_ids=market_ids, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + end_time=pagination.end_time, + ) + + response = await self._execute_call(call=self._stub.FundingPayments, request=request) + + return response + + async def fetch_funding_rates( + self, + market_id: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.FundingRatesRequest( + market_id=market_id, + skip=pagination.skip, + limit=pagination.limit, + end_time=pagination.end_time, + ) + + response = await self._execute_call(call=self._stub.FundingRates, request=request) + + return response + + async def fetch_trades( + self, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.TradesRequest( + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.Trades, request=request) + + return response + + async def fetch_subaccount_orders_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.SubaccountOrdersListRequest( + subaccount_id=subaccount_id, + market_id=market_id, + skip=pagination.skip, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.SubaccountOrdersList, request=request) + + return response + + async def fetch_subaccount_trades_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + execution_type: Optional[str] = None, + direction: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.SubaccountTradesListRequest( + subaccount_id=subaccount_id, + market_id=market_id, + execution_type=execution_type, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.SubaccountTradesList, request=request) + + return response + + async def fetch_orders_history( + self, + subaccount_id: Optional[str] = None, + market_ids: Optional[List[str]] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + is_conditional: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + active_markets_only: Optional[bool] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.OrdersHistoryRequest( + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + order_types=order_types, + direction=direction, + start_time=pagination.start_time, + end_time=pagination.end_time, + is_conditional=is_conditional, + state=state, + execution_types=execution_types, + market_ids=market_ids, + trade_id=trade_id, + active_markets_only=active_markets_only, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.OrdersHistory, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_explorer_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_explorer_api.py new file mode 100644 index 00000000..89bfc3c7 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_explorer_api.py @@ -0,0 +1,329 @@ +from typing import Any, Callable, Dict, List, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.exchange import ( + injective_explorer_rpc_pb2 as exchange_explorer_pb, + injective_explorer_rpc_pb2_grpc as exchange_explorer_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcExplorerApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_explorer_grpc.InjectiveExplorerRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_account_txs( + self, + address: str, + before: Optional[int] = None, + after: Optional[int] = None, + message_type: Optional[str] = None, + module: Optional[str] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + status: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetAccountTxsRequest( + address=address, + before=before, + after=after, + limit=pagination.limit, + skip=pagination.skip, + type=message_type, + module=module, + from_number=from_number, + to_number=to_number, + start_time=pagination.start_time, + end_time=pagination.end_time, + status=status, + ) + + response = await self._execute_call(call=self._stub.GetAccountTxs, request=request) + + return response + + async def fetch_contract_txs( + self, + address: str, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetAccountTxsRequest( + address=address, + limit=pagination.limit, + skip=pagination.skip, + from_number=from_number, + to_number=to_number, + ) + + response = await self._execute_call(call=self._stub.GetContractTxs, request=request) + + return response + + async def fetch_blocks( + self, + before: Optional[int] = None, + after: Optional[int] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetBlocksRequest( + before=before, + after=after, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.GetBlocks, request=request) + + return response + + async def fetch_block(self, block_id: str) -> Dict[str, Any]: + request = exchange_explorer_pb.GetBlockRequest(id=block_id) + + response = await self._execute_call(call=self._stub.GetBlock, request=request) + + return response + + async def fetch_validators(self) -> Dict[str, Any]: + request = exchange_explorer_pb.GetValidatorsRequest() + + response = await self._execute_call(call=self._stub.GetValidators, request=request) + + return response + + async def fetch_validator(self, address: str) -> Dict[str, Any]: + request = exchange_explorer_pb.GetValidatorRequest(address=address) + + response = await self._execute_call(call=self._stub.GetValidator, request=request) + + return response + + async def fetch_validator_uptime(self, address: str) -> Dict[str, Any]: + request = exchange_explorer_pb.GetValidatorUptimeRequest(address=address) + + response = await self._execute_call(call=self._stub.GetValidatorUptime, request=request) + + return response + + async def fetch_txs( + self, + before: Optional[int] = None, + after: Optional[int] = None, + message_type: Optional[str] = None, + module: Optional[str] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + status: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetTxsRequest( + before=before, + after=after, + limit=pagination.limit, + skip=pagination.skip, + type=message_type, + module=module, + from_number=from_number, + to_number=to_number, + start_time=pagination.start_time, + end_time=pagination.end_time, + status=status, + ) + + response = await self._execute_call(call=self._stub.GetTxs, request=request) + + return response + + async def fetch_tx_by_tx_hash(self, tx_hash: str) -> Dict[str, Any]: + request = exchange_explorer_pb.GetTxByTxHashRequest(hash=tx_hash) + + response = await self._execute_call(call=self._stub.GetTxByTxHash, request=request) + + return response + + async def fetch_peggy_deposit_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetPeggyDepositTxsRequest( + sender=sender, + receiver=receiver, + limit=pagination.limit, + skip=pagination.skip, + ) + + response = await self._execute_call(call=self._stub.GetPeggyDepositTxs, request=request) + + return response + + async def fetch_peggy_withdrawal_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetPeggyWithdrawalTxsRequest( + sender=sender, + receiver=receiver, + limit=pagination.limit, + skip=pagination.skip, + ) + + response = await self._execute_call(call=self._stub.GetPeggyWithdrawalTxs, request=request) + + return response + + async def fetch_ibc_transfer_txs( + self, + sender: Optional[str] = None, + receiver: Optional[str] = None, + src_channel: Optional[str] = None, + src_port: Optional[str] = None, + dest_channel: Optional[str] = None, + dest_port: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetIBCTransferTxsRequest( + sender=sender, + receiver=receiver, + src_channel=src_channel, + src_port=src_port, + dest_channel=dest_channel, + dest_port=dest_port, + limit=pagination.limit, + skip=pagination.skip, + ) + + response = await self._execute_call(call=self._stub.GetIBCTransferTxs, request=request) + + return response + + async def fetch_wasm_codes( + self, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetWasmCodesRequest( + limit=pagination.limit, + from_number=from_number, + to_number=to_number, + ) + + response = await self._execute_call(call=self._stub.GetWasmCodes, request=request) + + return response + + async def fetch_wasm_code_by_id( + self, + code_id: int, + ) -> Dict[str, Any]: + request = exchange_explorer_pb.GetWasmCodeByIDRequest(code_id=code_id) + + response = await self._execute_call(call=self._stub.GetWasmCodeByID, request=request) + + return response + + async def fetch_wasm_contracts( + self, + code_id: Optional[int] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + assets_only: Optional[bool] = None, + label: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetWasmContractsRequest( + limit=pagination.limit, + code_id=code_id, + from_number=from_number, + to_number=to_number, + assets_only=assets_only, + skip=pagination.skip, + label=label, + ) + + response = await self._execute_call(call=self._stub.GetWasmContracts, request=request) + + return response + + async def fetch_wasm_contract_by_address( + self, + address: str, + ) -> Dict[str, Any]: + request = exchange_explorer_pb.GetWasmContractByAddressRequest(contract_address=address) + + response = await self._execute_call(call=self._stub.GetWasmContractByAddress, request=request) + + return response + + async def fetch_cw20_balance( + self, + address: str, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetCw20BalanceRequest( + address=address, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.GetCw20Balance, request=request) + + return response + + async def fetch_relayers( + self, + market_ids: Optional[List[str]] = None, + ) -> Dict[str, Any]: + request = exchange_explorer_pb.RelayersRequest(market_i_ds=market_ids) + + response = await self._execute_call(call=self._stub.Relayers, request=request) + + return response + + async def fetch_bank_transfers( + self, + senders: Optional[List[str]] = None, + recipients: Optional[List[str]] = None, + is_community_pool_related: Optional[bool] = None, + address: Optional[List[str]] = None, + per_page: Optional[int] = None, + token: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_explorer_pb.GetBankTransfersRequest( + senders=senders, + recipients=recipients, + is_community_pool_related=is_community_pool_related, + limit=pagination.limit, + skip=pagination.skip, + start_time=pagination.start_time, + end_time=pagination.end_time, + address=address, + per_page=per_page, + token=token, + ) + + response = await self._execute_call(call=self._stub.GetBankTransfers, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_insurance_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_insurance_api.py new file mode 100644 index 00000000..95476677 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_insurance_api.py @@ -0,0 +1,39 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_insurance_rpc_pb2 as exchange_insurance_pb, + injective_insurance_rpc_pb2_grpc as exchange_insurance_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcInsuranceApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_insurance_grpc.InjectiveInsuranceRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_insurance_funds(self) -> Dict[str, Any]: + request = exchange_insurance_pb.FundsRequest() + response = await self._execute_call(call=self._stub.Funds, request=request) + + return response + + async def fetch_redemptions( + self, + address: Optional[str] = None, + denom: Optional[str] = None, + status: Optional[str] = None, + ) -> Dict[str, Any]: + request = exchange_insurance_pb.RedemptionsRequest( + redeemer=address, + redemption_denom=denom, + status=status, + ) + response = await self._execute_call(call=self._stub.Redemptions, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_meta_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_meta_api.py new file mode 100644 index 00000000..27e108e1 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_meta_api.py @@ -0,0 +1,37 @@ +import time +from typing import Any, Callable, Dict + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_meta_rpc_pb2 as exchange_meta_pb, + injective_meta_rpc_pb2_grpc as exchange_meta_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcMetaApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_meta_grpc.InjectiveMetaRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_ping(self) -> Dict[str, Any]: + request = exchange_meta_pb.PingRequest() + response = await self._execute_call(call=self._stub.Ping, request=request) + + return response + + async def fetch_version(self) -> Dict[str, Any]: + request = exchange_meta_pb.VersionRequest() + response = await self._execute_call(call=self._stub.Version, request=request) + + return response + + async def fetch_info(self) -> Dict[str, Any]: + request = exchange_meta_pb.InfoRequest(timestamp=int(time.time() * 1000)) + response = await self._execute_call(call=self._stub.Info, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_oracle_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_oracle_api.py new file mode 100644 index 00000000..72416be7 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_oracle_api.py @@ -0,0 +1,41 @@ +from typing import Any, Callable, Dict, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_oracle_rpc_pb2 as exchange_oracle_pb, + injective_oracle_rpc_pb2_grpc as exchange_oracle_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcOracleApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_oracle_grpc.InjectiveOracleRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_oracle_list(self) -> Dict[str, Any]: + request = exchange_oracle_pb.OracleListRequest() + response = await self._execute_call(call=self._stub.OracleList, request=request) + + return response + + async def fetch_oracle_price( + self, + base_symbol: Optional[str] = None, + quote_symbol: Optional[str] = None, + oracle_type: Optional[str] = None, + oracle_scale_factor: Optional[int] = None, + ) -> Dict[str, Any]: + request = exchange_oracle_pb.PriceRequest( + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + oracle_scale_factor=oracle_scale_factor, + ) + response = await self._execute_call(call=self._stub.Price, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_portfolio_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_portfolio_api.py new file mode 100644 index 00000000..a8fd319c --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_portfolio_api.py @@ -0,0 +1,24 @@ +from typing import Any, Callable, Dict + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_portfolio_rpc_pb2 as exchange_portfolio_pb, + injective_portfolio_rpc_pb2_grpc as exchange_portfolio_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcPortfolioApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_portfolio_grpc.InjectivePortfolioRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_account_portfolio(self, account_address: str) -> Dict[str, Any]: + request = exchange_portfolio_pb.AccountPortfolioRequest(account_address=account_address) + response = await self._execute_call(call=self._stub.AccountPortfolio, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py b/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py new file mode 100644 index 00000000..5b9a0b40 --- /dev/null +++ b/pyinjective/client/indexer/grpc/indexer_grpc_spot_api.py @@ -0,0 +1,208 @@ +from typing import Any, Callable, Dict, List, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.exchange import ( + injective_spot_exchange_rpc_pb2 as exchange_spot_pb, + injective_spot_exchange_rpc_pb2_grpc as exchange_spot_grpc, +) +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class IndexerGrpcSpotApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_spot_grpc.InjectiveSpotExchangeRPCStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def fetch_markets( + self, + market_statuses: Optional[List[str]] = None, + base_denom: Optional[str] = None, + quote_denom: Optional[str] = None, + ) -> Dict[str, Any]: + request = exchange_spot_pb.MarketsRequest( + market_statuses=market_statuses, + base_denom=base_denom, + quote_denom=quote_denom, + ) + response = await self._execute_call(call=self._stub.Markets, request=request) + + return response + + async def fetch_market(self, market_id: str) -> Dict[str, Any]: + request = exchange_spot_pb.MarketRequest(market_id=market_id) + response = await self._execute_call(call=self._stub.Market, request=request) + + return response + + async def fetch_orderbook_v2(self, market_id: str) -> Dict[str, Any]: + request = exchange_spot_pb.OrderbookV2Request(market_id=market_id) + response = await self._execute_call(call=self._stub.OrderbookV2, request=request) + + return response + + async def fetch_orderbooks_v2(self, market_ids: List[str]) -> Dict[str, Any]: + request = exchange_spot_pb.OrderbooksV2Request(market_ids=market_ids) + response = await self._execute_call(call=self._stub.OrderbooksV2, request=request) + + return response + + async def fetch_orders( + self, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.OrdersRequest( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.Orders, request=request) + + return response + + async def fetch_trades( + self, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.TradesRequest( + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_side=execution_side, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.Trades, request=request) + + return response + + async def fetch_subaccount_orders_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.SubaccountOrdersListRequest( + subaccount_id=subaccount_id, + market_id=market_id, + skip=pagination.skip, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.SubaccountOrdersList, request=request) + + return response + + async def fetch_subaccount_trades_list( + self, + subaccount_id: str, + market_id: Optional[str] = None, + execution_type: Optional[str] = None, + direction: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.SubaccountTradesListRequest( + subaccount_id=subaccount_id, + market_id=market_id, + execution_type=execution_type, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + ) + + response = await self._execute_call(call=self._stub.SubaccountTradesList, request=request) + + return response + + async def fetch_orders_history( + self, + subaccount_id: Optional[str] = None, + market_ids: Optional[List[str]] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + active_markets_only: Optional[bool] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.OrdersHistoryRequest( + subaccount_id=subaccount_id, + market_ids=market_ids, + skip=pagination.skip, + limit=pagination.limit, + order_types=order_types, + direction=direction, + start_time=pagination.start_time, + end_time=pagination.end_time, + state=state, + execution_types=execution_types, + trade_id=trade_id, + active_markets_only=active_markets_only, + cid=cid, + ) + + response = await self._execute_call(call=self._stub.OrdersHistory, request=request) + + return response + + async def fetch_atomic_swap_history( + self, + address: str, + contract_address: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ) -> Dict[str, Any]: + pagination = pagination or PaginationOption() + request = exchange_spot_pb.AtomicSwapHistoryRequest( + address=address, + contract_address=contract_address, + skip=pagination.skip, + limit=pagination.limit, + from_number=pagination.from_number, + to_number=pagination.to_number, + ) + + response = await self._execute_call(call=self._stub.AtomicSwapHistory, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/client/indexer/grpc_stream/__init__.py b/pyinjective/client/indexer/grpc_stream/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py new file mode 100644 index 00000000..04ba1af3 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_account_stream.py @@ -0,0 +1,36 @@ +from typing import Callable, List, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_accounts_rpc_pb2 as exchange_accounts_pb, + injective_accounts_rpc_pb2_grpc as exchange_accounts_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcAccountStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_accounts_grpc.InjectiveAccountsRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_subaccount_balance( + self, + subaccount_id: str, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + denoms: Optional[List[str]] = None, + ): + request = exchange_accounts_pb.StreamSubaccountBalanceRequest( + subaccount_id=subaccount_id, + denoms=denoms, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamSubaccountBalance, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_auction_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_auction_stream.py new file mode 100644 index 00000000..43009ee9 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_auction_stream.py @@ -0,0 +1,31 @@ +from typing import Callable, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_auction_rpc_pb2 as exchange_auction_pb, + injective_auction_rpc_pb2_grpc as exchange_auction_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcAuctionStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_auction_grpc.InjectiveAuctionRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_bids( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_auction_pb.StreamBidsRequest() + + await self._assistant.listen_stream( + call=self._stub.StreamBids, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_derivative_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_derivative_stream.py new file mode 100644 index 00000000..a14a6e30 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_derivative_stream.py @@ -0,0 +1,196 @@ +from typing import Callable, List, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.exchange import ( + injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb, + injective_derivative_exchange_rpc_pb2_grpc as exchange_derivative_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcDerivativeStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_derivative_grpc.InjectiveDerivativeExchangeRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_market( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + ): + request = exchange_derivative_pb.StreamMarketRequest( + market_ids=market_ids, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamMarket, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orderbook_v2( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_derivative_pb.StreamOrderbookV2Request(market_ids=market_ids) + + await self._assistant.listen_stream( + call=self._stub.StreamOrderbookV2, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orderbook_update( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_derivative_pb.StreamOrderbookUpdateRequest(market_ids=market_ids) + + await self._assistant.listen_stream( + call=self._stub.StreamOrderbookUpdate, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_positions( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + ): + request = exchange_derivative_pb.StreamPositionsRequest(market_ids=market_ids, subaccount_ids=subaccount_ids) + + await self._assistant.listen_stream( + call=self._stub.StreamPositions, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orders( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[PaginationOption] = None, + is_conditional: Optional[str] = None, + order_type: Optional[str] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.StreamOrdersRequest( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + is_conditional=is_conditional, + order_type=order_type, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamOrders, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_trades( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + subaccount_ids: Optional[List[str]] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + pagination = pagination or PaginationOption() + request = exchange_derivative_pb.StreamTradesRequest( + execution_side=execution_side, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamTrades, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orders_history( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + market_id: Optional[str] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + ): + request = exchange_derivative_pb.StreamOrdersHistoryRequest( + subaccount_id=subaccount_id, + market_id=market_id, + order_types=order_types, + direction=direction, + state=state, + execution_types=execution_types, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamOrdersHistory, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_explorer_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_explorer_stream.py new file mode 100644 index 00000000..aa4a69ae --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_explorer_stream.py @@ -0,0 +1,47 @@ +from typing import Callable, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_explorer_rpc_pb2 as exchange_explorer_pb, + injective_explorer_rpc_pb2_grpc as exchange_explorer_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcExplorerStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_explorer_grpc.InjectiveExplorerRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_txs( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_explorer_pb.StreamTxsRequest() + + await self._assistant.listen_stream( + call=self._stub.StreamTxs, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_blocks( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_explorer_pb.StreamBlocksRequest() + + await self._assistant.listen_stream( + call=self._stub.StreamBlocks, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py new file mode 100644 index 00000000..aeff1132 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_meta_stream.py @@ -0,0 +1,31 @@ +from typing import Callable, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_meta_rpc_pb2 as exchange_meta_pb, + injective_meta_rpc_pb2_grpc as exchange_meta_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcMetaStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_meta_grpc.InjectiveMetaRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_keepalive( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_meta_pb.StreamKeepaliveRequest() + + await self._assistant.listen_stream( + call=self._stub.StreamKeepalive, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py new file mode 100644 index 00000000..0bad6e35 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_oracle_stream.py @@ -0,0 +1,57 @@ +from typing import Callable, List, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_oracle_rpc_pb2 as exchange_oracle_pb, + injective_oracle_rpc_pb2_grpc as exchange_oracle_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcOracleStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_oracle_grpc.InjectiveOracleRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_oracle_prices( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + base_symbol: Optional[str] = None, + quote_symbol: Optional[str] = None, + oracle_type: Optional[str] = None, + ): + request = exchange_oracle_pb.StreamPricesRequest( + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamPrices, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_oracle_prices_by_markets( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_oracle_pb.StreamPricesByMarketsRequest( + market_ids=market_ids, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamPricesByMarkets, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_portfolio_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_portfolio_stream.py new file mode 100644 index 00000000..59b0acfa --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_portfolio_stream.py @@ -0,0 +1,38 @@ +from typing import Callable, Optional + +from grpc.aio import Channel + +from pyinjective.proto.exchange import ( + injective_portfolio_rpc_pb2 as exchange_portfolio_pb, + injective_portfolio_rpc_pb2_grpc as exchange_portfolio_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcPortfolioStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_portfolio_grpc.InjectivePortfolioRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_account_portfolio( + self, + account_address: str, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + update_type: Optional[str] = None, + ): + request = exchange_portfolio_pb.StreamAccountPortfolioRequest( + account_address=account_address, + subaccount_id=subaccount_id, + type=update_type, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamAccountPortfolio, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/indexer/grpc_stream/indexer_grpc_spot_stream.py b/pyinjective/client/indexer/grpc_stream/indexer_grpc_spot_stream.py new file mode 100644 index 00000000..f8772460 --- /dev/null +++ b/pyinjective/client/indexer/grpc_stream/indexer_grpc_spot_stream.py @@ -0,0 +1,174 @@ +from typing import Callable, List, Optional + +from grpc.aio import Channel + +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.proto.exchange import ( + injective_spot_exchange_rpc_pb2 as exchange_spot_pb, + injective_spot_exchange_rpc_pb2_grpc as exchange_spot_grpc, +) +from pyinjective.utils.grpc_api_stream_assistant import GrpcApiStreamAssistant + + +class IndexerGrpcSpotStream: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = self._stub = exchange_spot_grpc.InjectiveSpotExchangeRPCStub(channel) + self._assistant = GrpcApiStreamAssistant(metadata_provider=metadata_provider) + + async def stream_markets( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + ): + request = exchange_spot_pb.StreamMarketsRequest( + market_ids=market_ids, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamMarkets, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orderbook_v2( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_spot_pb.StreamOrderbookV2Request(market_ids=market_ids) + + await self._assistant.listen_stream( + call=self._stub.StreamOrderbookV2, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orderbook_update( + self, + market_ids: List[str], + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + request = exchange_spot_pb.StreamOrderbookUpdateRequest(market_ids=market_ids) + + await self._assistant.listen_stream( + call=self._stub.StreamOrderbookUpdate, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orders( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + order_side: Optional[str] = None, + subaccount_id: Optional[PaginationOption] = None, + include_inactive: Optional[bool] = None, + subaccount_total_orders: Optional[bool] = None, + trade_id: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + pagination = pagination or PaginationOption() + request = exchange_spot_pb.StreamOrdersRequest( + market_ids=market_ids, + order_side=order_side, + subaccount_id=subaccount_id, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + include_inactive=include_inactive, + subaccount_total_orders=subaccount_total_orders, + trade_id=trade_id, + cid=cid, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamOrders, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_trades( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + market_ids: Optional[List[str]] = None, + subaccount_ids: Optional[List[str]] = None, + execution_side: Optional[str] = None, + direction: Optional[str] = None, + execution_types: Optional[List[str]] = None, + trade_id: Optional[str] = None, + account_address: Optional[str] = None, + cid: Optional[str] = None, + pagination: Optional[PaginationOption] = None, + ): + pagination = pagination or PaginationOption() + request = exchange_spot_pb.StreamTradesRequest( + execution_side=execution_side, + direction=direction, + skip=pagination.skip, + limit=pagination.limit, + start_time=pagination.start_time, + end_time=pagination.end_time, + market_ids=market_ids, + subaccount_ids=subaccount_ids, + execution_types=execution_types, + trade_id=trade_id, + account_address=account_address, + cid=cid, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamTrades, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) + + async def stream_orders_history( + self, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + subaccount_id: Optional[str] = None, + market_id: Optional[str] = None, + order_types: Optional[List[str]] = None, + direction: Optional[str] = None, + state: Optional[str] = None, + execution_types: Optional[List[str]] = None, + ): + request = exchange_spot_pb.StreamOrdersHistoryRequest( + subaccount_id=subaccount_id, + market_id=market_id, + order_types=order_types, + direction=direction, + state=state, + execution_types=execution_types, + ) + + await self._assistant.listen_stream( + call=self._stub.StreamOrdersHistory, + request=request, + callback=callback, + on_end_callback=on_end_callback, + on_status_callback=on_status_callback, + ) diff --git a/pyinjective/client/model/__init__.py b/pyinjective/client/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/client/model/pagination.py b/pyinjective/client/model/pagination.py new file mode 100644 index 00000000..f3607c17 --- /dev/null +++ b/pyinjective/client/model/pagination.py @@ -0,0 +1,68 @@ +from typing import Optional + +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb + + +class PaginationOption: + def __init__( + self, + key: Optional[str] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + reverse: Optional[bool] = None, + count_total: Optional[bool] = None, + from_number: Optional[int] = None, + to_number: Optional[int] = None, + ): + super().__init__() + self.key = key + self.skip = skip + self.limit = limit + self.start_time = start_time + self.end_time = end_time + self.reverse = reverse + self.count_total = count_total + self.from_number = from_number + self.to_number = to_number + + def create_pagination_request(self) -> pagination_pb.PageRequest: + page_request = pagination_pb.PageRequest() + + if self.key is not None: + page_request.key = bytes.fromhex(self.key) + if self.skip is not None: + page_request.offset = self.skip + if self.limit is not None: + page_request.limit = self.limit + if self.reverse is not None: + page_request.reverse = self.reverse + if self.count_total is not None: + page_request.count_total = self.count_total + + return page_request + + +class Pagination: + def __init__( + self, + next: Optional[str] = None, + total: Optional[int] = None, + ): + super().__init__() + self.next = next + self.total = total + + @classmethod + def from_proto(cls, proto_pagination: pagination_pb.PageResponse): + next = None + + if proto_pagination.next_key is not None: + next = f"0x{proto_pagination.next_key.hex()}" + total = proto_pagination.total + + return cls( + next=next, + total=total, + ) diff --git a/pyinjective/composer.py b/pyinjective/composer.py index b1826e16..56491b50 100644 --- a/pyinjective/composer.py +++ b/pyinjective/composer.py @@ -2,33 +2,87 @@ from configparser import ConfigParser from decimal import Decimal from time import time -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional from google.protobuf import any_pb2, json_format, timestamp_pb2 from pyinjective import constant +from pyinjective.constant import ADDITIONAL_CHAIN_FORMAT_DECIMALS, INJ_DENOM +from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket +from pyinjective.core.token import Token +from pyinjective.proto.cosmos.authz.v1beta1 import authz_pb2 as cosmos_authz_pb, tx_pb2 as cosmos_authz_tx_pb +from pyinjective.proto.cosmos.bank.v1beta1 import tx_pb2 as cosmos_bank_tx_pb from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as cosmos_dot_base_dot_v1beta1_dot_coin__pb2 +from pyinjective.proto.cosmos.distribution.v1beta1 import tx_pb2 as cosmos_distribution_tx_pb +from pyinjective.proto.cosmos.gov.v1beta1 import tx_pb2 as cosmos_gov_tx_pb +from pyinjective.proto.cosmos.staking.v1beta1 import tx_pb2 as cosmos_staking_tx_pb +from pyinjective.proto.cosmwasm.wasm.v1 import tx_pb2 as wasm_tx_pb +from pyinjective.proto.exchange import injective_explorer_rpc_pb2 as explorer_pb2 +from pyinjective.proto.injective.auction.v1beta1 import tx_pb2 as injective_auction_tx_pb from pyinjective.proto.injective.exchange.v1beta1 import ( + authz_pb2 as injective_authz_pb, exchange_pb2 as injective_dot_exchange_dot_v1beta1_dot_exchange__pb2, + tx_pb2 as injective_exchange_tx_pb, ) +from pyinjective.proto.injective.insurance.v1beta1 import tx_pb2 as injective_insurance_tx_pb +from pyinjective.proto.injective.oracle.v1beta1 import tx_pb2 as injective_oracle_tx_pb +from pyinjective.proto.injective.peggy.v1 import msgs_pb2 as injective_peggy_tx_pb from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_query -from .constant import ADDITIONAL_CHAIN_FORMAT_DECIMALS, INJ_DENOM -from .core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket -from .core.token import Token -from .proto.cosmos.authz.v1beta1 import authz_pb2 as cosmos_authz_pb -from .proto.cosmos.authz.v1beta1 import tx_pb2 as cosmos_authz_tx_pb -from .proto.cosmos.bank.v1beta1 import tx_pb2 as cosmos_bank_tx_pb -from .proto.cosmos.distribution.v1beta1 import tx_pb2 as cosmos_distribution_tx_pb -from .proto.cosmos.gov.v1beta1 import tx_pb2 as cosmos_gov_tx_pb -from .proto.cosmos.staking.v1beta1 import tx_pb2 as cosmos_staking_tx_pb -from .proto.cosmwasm.wasm.v1 import tx_pb2 as wasm_tx_pb -from .proto.injective.auction.v1beta1 import tx_pb2 as injective_auction_tx_pb -from .proto.injective.exchange.v1beta1 import authz_pb2 as injective_authz_pb -from .proto.injective.exchange.v1beta1 import tx_pb2 as injective_exchange_tx_pb -from .proto.injective.insurance.v1beta1 import tx_pb2 as injective_insurance_tx_pb -from .proto.injective.oracle.v1beta1 import tx_pb2 as injective_oracle_tx_pb -from .proto.injective.peggy.v1 import msgs_pb2 as injective_peggy_tx_pb +REQUEST_TO_RESPONSE_TYPE_MAP = { + "MsgCreateSpotLimitOrder": injective_exchange_tx_pb.MsgCreateSpotLimitOrderResponse, + "MsgCreateSpotMarketOrder": injective_exchange_tx_pb.MsgCreateSpotMarketOrderResponse, + "MsgCreateDerivativeLimitOrder": injective_exchange_tx_pb.MsgCreateDerivativeLimitOrderResponse, + "MsgCreateDerivativeMarketOrder": injective_exchange_tx_pb.MsgCreateDerivativeMarketOrderResponse, + "MsgCancelSpotOrder": injective_exchange_tx_pb.MsgCancelSpotOrderResponse, + "MsgCancelDerivativeOrder": injective_exchange_tx_pb.MsgCancelDerivativeOrderResponse, + "MsgBatchCancelSpotOrders": injective_exchange_tx_pb.MsgBatchCancelSpotOrdersResponse, + "MsgBatchCancelDerivativeOrders": injective_exchange_tx_pb.MsgBatchCancelDerivativeOrdersResponse, + "MsgBatchCreateSpotLimitOrders": injective_exchange_tx_pb.MsgBatchCreateSpotLimitOrdersResponse, + "MsgBatchCreateDerivativeLimitOrders": injective_exchange_tx_pb.MsgBatchCreateDerivativeLimitOrdersResponse, + "MsgBatchUpdateOrders": injective_exchange_tx_pb.MsgBatchUpdateOrdersResponse, + "MsgDeposit": injective_exchange_tx_pb.MsgDepositResponse, + "MsgWithdraw": injective_exchange_tx_pb.MsgWithdrawResponse, + "MsgSubaccountTransfer": injective_exchange_tx_pb.MsgSubaccountTransferResponse, + "MsgLiquidatePosition": injective_exchange_tx_pb.MsgLiquidatePositionResponse, + "MsgIncreasePositionMargin": injective_exchange_tx_pb.MsgIncreasePositionMarginResponse, + "MsgCreateBinaryOptionsLimitOrder": injective_exchange_tx_pb.MsgCreateBinaryOptionsLimitOrderResponse, + "MsgCreateBinaryOptionsMarketOrder": injective_exchange_tx_pb.MsgCreateBinaryOptionsMarketOrderResponse, + "MsgCancelBinaryOptionsOrder": injective_exchange_tx_pb.MsgCancelBinaryOptionsOrderResponse, + "MsgAdminUpdateBinaryOptionsMarket": injective_exchange_tx_pb.MsgAdminUpdateBinaryOptionsMarketResponse, + "MsgInstantBinaryOptionsMarketLaunch": injective_exchange_tx_pb.MsgInstantBinaryOptionsMarketLaunchResponse, +} + +GRPC_MESSAGE_TYPE_TO_CLASS_MAP = { + "/injective.exchange.v1beta1.MsgCreateSpotLimitOrder": injective_exchange_tx_pb.MsgCreateSpotLimitOrder, + "/injective.exchange.v1beta1.MsgCreateSpotMarketOrder": injective_exchange_tx_pb.MsgCreateSpotMarketOrder, + "/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder": injective_exchange_tx_pb.MsgCreateDerivativeLimitOrder, + "/injective.exchange.v1beta1.MsgCreateDerivativeMarketOrder": injective_exchange_tx_pb.MsgCreateDerivativeMarketOrder, # noqa: 121 + "/injective.exchange.v1beta1.MsgCancelSpotOrder": injective_exchange_tx_pb.MsgCancelSpotOrder, + "/injective.exchange.v1beta1.MsgCancelDerivativeOrder": injective_exchange_tx_pb.MsgCancelDerivativeOrder, + "/injective.exchange.v1beta1.MsgBatchCancelSpotOrders": injective_exchange_tx_pb.MsgBatchCancelSpotOrders, + "/injective.exchange.v1beta1.MsgBatchCancelDerivativeOrders": injective_exchange_tx_pb.MsgBatchCancelDerivativeOrders, # noqa: 121 + "/injective.exchange.v1beta1.MsgBatchCreateSpotLimitOrders": injective_exchange_tx_pb.MsgBatchCreateSpotLimitOrders, + "/injective.exchange.v1beta1.MsgBatchCreateDerivativeLimitOrders": injective_exchange_tx_pb.MsgBatchCreateDerivativeLimitOrders, # noqa: 121 + "/injective.exchange.v1beta1.MsgBatchUpdateOrders": injective_exchange_tx_pb.MsgBatchUpdateOrders, + "/injective.exchange.v1beta1.MsgDeposit": injective_exchange_tx_pb.MsgDeposit, + "/injective.exchange.v1beta1.MsgWithdraw": injective_exchange_tx_pb.MsgWithdraw, + "/injective.exchange.v1beta1.MsgSubaccountTransfer": injective_exchange_tx_pb.MsgSubaccountTransfer, + "/injective.exchange.v1beta1.MsgLiquidatePosition": injective_exchange_tx_pb.MsgLiquidatePosition, + "/injective.exchange.v1beta1.MsgIncreasePositionMargin": injective_exchange_tx_pb.MsgIncreasePositionMargin, + "/injective.auction.v1beta1.MsgBid": injective_auction_tx_pb.MsgBid, + "/injective.exchange.v1beta1.MsgCreateBinaryOptionsLimitOrder": injective_exchange_tx_pb.MsgCreateBinaryOptionsLimitOrder, # noqa: 121 + "/injective.exchange.v1beta1.MsgCreateBinaryOptionsMarketOrder": injective_exchange_tx_pb.MsgCreateBinaryOptionsMarketOrder, # noqa: 121 + "/injective.exchange.v1beta1.MsgCancelBinaryOptionsOrder": injective_exchange_tx_pb.MsgCancelBinaryOptionsOrder, + "/injective.exchange.v1beta1.MsgAdminUpdateBinaryOptionsMarket": injective_exchange_tx_pb.MsgAdminUpdateBinaryOptionsMarket, # noqa: 121 + "/injective.exchange.v1beta1.MsgInstantBinaryOptionsMarketLaunch": injective_exchange_tx_pb.MsgInstantBinaryOptionsMarketLaunch, # noqa: 121 + "/cosmos.bank.v1beta1.MsgSend": cosmos_bank_tx_pb.MsgSend, + "/cosmos.authz.v1beta1.MsgGrant": cosmos_authz_tx_pb.MsgGrant, + "/cosmos.authz.v1beta1.MsgExec": cosmos_authz_tx_pb.MsgExec, + "/cosmos.authz.v1beta1.MsgRevoke": cosmos_authz_tx_pb.MsgRevoke, + "/injective.oracle.v1beta1.MsgRelayPriceFeedPrice": injective_oracle_tx_pb.MsgRelayPriceFeedPrice, + "/injective.oracle.v1beta1.MsgRelayProviderPrices": injective_oracle_tx_pb.MsgRelayProviderPrices, +} class Composer: @@ -1033,119 +1087,33 @@ def MsgResponses(response, simulation=False): @staticmethod def UnpackMsgExecResponse(msg_type, data): - # fmt: off - header_map = { - "MsgCreateSpotLimitOrder": - injective_exchange_tx_pb.MsgCreateSpotLimitOrderResponse, - "MsgCreateSpotMarketOrder": - injective_exchange_tx_pb.MsgCreateSpotMarketOrderResponse, - "MsgCreateDerivativeLimitOrder": - injective_exchange_tx_pb.MsgCreateDerivativeLimitOrderResponse, - "MsgCreateDerivativeMarketOrder": - injective_exchange_tx_pb.MsgCreateDerivativeMarketOrderResponse, - "MsgCancelSpotOrder": - injective_exchange_tx_pb.MsgCancelSpotOrderResponse, - "MsgCancelDerivativeOrder": - injective_exchange_tx_pb.MsgCancelDerivativeOrderResponse, - "MsgBatchCancelSpotOrders": - injective_exchange_tx_pb.MsgBatchCancelSpotOrdersResponse, - "MsgBatchCancelDerivativeOrders": - injective_exchange_tx_pb.MsgBatchCancelDerivativeOrdersResponse, - "MsgBatchCreateSpotLimitOrders": - injective_exchange_tx_pb.MsgBatchCreateSpotLimitOrdersResponse, - "MsgBatchCreateDerivativeLimitOrders": - injective_exchange_tx_pb.MsgBatchCreateDerivativeLimitOrdersResponse, - "MsgBatchUpdateOrders": - injective_exchange_tx_pb.MsgBatchUpdateOrdersResponse, - "MsgDeposit": - injective_exchange_tx_pb.MsgDepositResponse, - "MsgWithdraw": - injective_exchange_tx_pb.MsgWithdrawResponse, - "MsgSubaccountTransfer": - injective_exchange_tx_pb.MsgSubaccountTransferResponse, - "MsgLiquidatePosition": - injective_exchange_tx_pb.MsgLiquidatePositionResponse, - "MsgIncreasePositionMargin": - injective_exchange_tx_pb.MsgIncreasePositionMarginResponse, - "MsgCreateBinaryOptionsLimitOrder": - injective_exchange_tx_pb.MsgCreateBinaryOptionsLimitOrderResponse, - "MsgCreateBinaryOptionsMarketOrder": - injective_exchange_tx_pb.MsgCreateBinaryOptionsMarketOrderResponse, - "MsgCancelBinaryOptionsOrder": - injective_exchange_tx_pb.MsgCancelBinaryOptionsOrderResponse, - "MsgAdminUpdateBinaryOptionsMarket": - injective_exchange_tx_pb.MsgAdminUpdateBinaryOptionsMarketResponse, - "MsgInstantBinaryOptionsMarketLaunch": - injective_exchange_tx_pb.MsgInstantBinaryOptionsMarketLaunchResponse, - } - # fmt: on + responses = [] + dict_message = json_format.MessageToDict(message=data, including_default_value_fields=True) + json_responses = Composer.unpack_msg_exec_response(underlying_msg_type=msg_type, msg_exec_response=dict_message) + for json_response in json_responses: + response = REQUEST_TO_RESPONSE_TYPE_MAP[msg_type]() + json_format.ParseDict(js_dict=json_response, message=response, ignore_unknown_fields=True) + responses.append(response) + return responses + + @staticmethod + def unpack_msg_exec_response(underlying_msg_type: str, msg_exec_response: Dict[str, Any]) -> List[Dict[str, Any]]: + grpc_response = cosmos_authz_tx_pb.MsgExecResponse() + json_format.ParseDict(js_dict=msg_exec_response, message=grpc_response, ignore_unknown_fields=True) + responses = [ + json_format.MessageToDict( + message=REQUEST_TO_RESPONSE_TYPE_MAP[underlying_msg_type].FromString(result), + including_default_value_fields=True, + ) + for result in grpc_response.results + ] - responses = [header_map[msg_type].FromString(result) for result in data.results] return responses @staticmethod def UnpackTransactionMessages(transaction): meta_messages = json.loads(transaction.messages.decode()) - # fmt: off - header_map = { - "/injective.exchange.v1beta1.MsgCreateSpotLimitOrder": - injective_exchange_tx_pb.MsgCreateSpotLimitOrder, - "/injective.exchange.v1beta1.MsgCreateSpotMarketOrder": - injective_exchange_tx_pb.MsgCreateSpotMarketOrder, - "/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder": - injective_exchange_tx_pb.MsgCreateDerivativeLimitOrder, - "/injective.exchange.v1beta1.MsgCreateDerivativeMarketOrder": - injective_exchange_tx_pb.MsgCreateDerivativeMarketOrder, - "/injective.exchange.v1beta1.MsgCancelSpotOrder": - injective_exchange_tx_pb.MsgCancelSpotOrder, - "/injective.exchange.v1beta1.MsgCancelDerivativeOrder": - injective_exchange_tx_pb.MsgCancelDerivativeOrder, - "/injective.exchange.v1beta1.MsgBatchCancelSpotOrders": - injective_exchange_tx_pb.MsgBatchCancelSpotOrders, - "/injective.exchange.v1beta1.MsgBatchCancelDerivativeOrders": - injective_exchange_tx_pb.MsgBatchCancelDerivativeOrders, - "/injective.exchange.v1beta1.MsgBatchCreateSpotLimitOrders": - injective_exchange_tx_pb.MsgBatchCreateSpotLimitOrders, - "/injective.exchange.v1beta1.MsgBatchCreateDerivativeLimitOrders": - injective_exchange_tx_pb.MsgBatchCreateDerivativeLimitOrders, - "/injective.exchange.v1beta1.MsgBatchUpdateOrders": - injective_exchange_tx_pb.MsgBatchUpdateOrders, - "/injective.exchange.v1beta1.MsgDeposit": - injective_exchange_tx_pb.MsgDeposit, - "/injective.exchange.v1beta1.MsgWithdraw": - injective_exchange_tx_pb.MsgWithdraw, - "/injective.exchange.v1beta1.MsgSubaccountTransfer": - injective_exchange_tx_pb.MsgSubaccountTransfer, - "/injective.exchange.v1beta1.MsgLiquidatePosition": - injective_exchange_tx_pb.MsgLiquidatePosition, - "/injective.exchange.v1beta1.MsgIncreasePositionMargin": - injective_exchange_tx_pb.MsgIncreasePositionMargin, - "/injective.auction.v1beta1.MsgBid": - injective_auction_tx_pb.MsgBid, - "/injective.exchange.v1beta1.MsgCreateBinaryOptionsLimitOrder": - injective_exchange_tx_pb.MsgCreateBinaryOptionsLimitOrder, - "/injective.exchange.v1beta1.MsgCreateBinaryOptionsMarketOrder": - injective_exchange_tx_pb.MsgCreateBinaryOptionsMarketOrder, - "/injective.exchange.v1beta1.MsgCancelBinaryOptionsOrder": - injective_exchange_tx_pb.MsgCancelBinaryOptionsOrder, - "/injective.exchange.v1beta1.MsgAdminUpdateBinaryOptionsMarket": - injective_exchange_tx_pb.MsgAdminUpdateBinaryOptionsMarket, - "/injective.exchange.v1beta1.MsgInstantBinaryOptionsMarketLaunch": - injective_exchange_tx_pb.MsgInstantBinaryOptionsMarketLaunch, - "/cosmos.bank.v1beta1.MsgSend": - cosmos_bank_tx_pb.MsgSend, - "/cosmos.authz.v1beta1.MsgGrant": - cosmos_authz_tx_pb.MsgGrant, - "/cosmos.authz.v1beta1.MsgExec": - cosmos_authz_tx_pb.MsgExec, - "/cosmos.authz.v1beta1.MsgRevoke": - cosmos_authz_tx_pb.MsgRevoke, - "/injective.oracle.v1beta1.MsgRelayPriceFeedPrice": - injective_oracle_tx_pb.MsgRelayPriceFeedPrice, - "/injective.oracle.v1beta1.MsgRelayProviderPrices": - injective_oracle_tx_pb.MsgRelayProviderPrices, - } - # fmt: on + header_map = GRPC_MESSAGE_TYPE_TO_CLASS_MAP msgs = [] for msg in meta_messages: msg_as_string_dict = json.dumps(msg["value"]) @@ -1153,6 +1121,24 @@ def UnpackTransactionMessages(transaction): return msgs + @staticmethod + def unpack_transaction_messages(transaction_data: Dict[str, Any]) -> List[Dict[str, Any]]: + grpc_tx = explorer_pb2.TxDetailData() + json_format.ParseDict(js_dict=transaction_data, message=grpc_tx, ignore_unknown_fields=True) + meta_messages = json.loads(grpc_tx.messages.decode()) + msgs = [] + for msg in meta_messages: + msg_as_string_dict = json.dumps(msg["value"]) + grpc_message = json_format.Parse(msg_as_string_dict, GRPC_MESSAGE_TYPE_TO_CLASS_MAP[msg["type"]]()) + msgs.append( + { + "type": msg["type"], + "value": json_format.MessageToDict(message=grpc_message, including_default_value_fields=True), + } + ) + + return msgs + def _initialize_markets_and_tokens_from_files(self): config: ConfigParser = constant.CONFIGS[self.network] spot_markets = dict() diff --git a/pyinjective/core/broadcaster.py b/pyinjective/core/broadcaster.py index a121ef22..45ca0fca 100644 --- a/pyinjective/core/broadcaster.py +++ b/pyinjective/core/broadcaster.py @@ -4,6 +4,7 @@ from typing import List, Optional from google.protobuf import any_pb2 +from grpc import RpcError from pyinjective import PrivateKey, PublicKey, Transaction from pyinjective.async_client import AsyncClient @@ -151,7 +152,7 @@ async def broadcast(self, messages: List[any_pb2.Any]): if self._client.timeout_height == 1: await self._client.sync_timeout_height() if self._client.number == 0: - await self._client.get_account(self._account_config.trading_injective_address) + await self._client.fetch_account(self._account_config.trading_injective_address) messages_for_transaction = self._account_config.messages_prepared_for_transaction(messages=messages) @@ -174,7 +175,7 @@ async def broadcast(self, messages: List[any_pb2.Any]): tx_raw_bytes = transaction.get_tx_data(sig, self._account_config.trading_public_key) # broadcast tx: send_tx_async_mode, send_tx_sync_mode - transaction_result = await self._client.send_tx_sync_mode(tx_raw_bytes) + transaction_result = await self._client.broadcast_tx_sync_mode(tx_raw_bytes) return transaction_result @@ -254,11 +255,12 @@ async def configure_gas_fee_for_transaction( sim_tx_raw_bytes = transaction.get_tx_data(sim_sig, public_key) # simulate tx - (sim_res, success) = await self._client.simulate_tx(sim_tx_raw_bytes) - if not success: - raise RuntimeError(f"Transaction simulation error: {sim_res}") + try: + sim_res = await self._client.simulate(sim_tx_raw_bytes) + except RpcError as ex: + raise RuntimeError(f"Transaction simulation error: {ex}") - gas_limit = math.ceil(Decimal(str(sim_res.gas_info.gas_used)) * self._gas_limit_adjustment_multiplier) + gas_limit = math.ceil(Decimal(str(sim_res["gasInfo"]["gasUsed"])) * self._gas_limit_adjustment_multiplier) fee = [ self._composer.Coin( diff --git a/pyinjective/core/market.py b/pyinjective/core/market.py index 228a3a52..3faf21d2 100644 --- a/pyinjective/core/market.py +++ b/pyinjective/core/market.py @@ -125,6 +125,7 @@ class BinaryOptionMarket: service_provider_fee: Decimal min_price_tick_size: Decimal min_quantity_tick_size: Decimal + settlement_price: Optional[Decimal] = None def quantity_to_chain_format(self, human_readable_value: Decimal, special_denom: Optional[Denom] = None) -> Decimal: # Binary option markets do not have a base market to provide the number of decimals diff --git a/pyinjective/core/tx/__init__.py b/pyinjective/core/tx/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/core/tx/grpc/__init__.py b/pyinjective/core/tx/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyinjective/core/tx/grpc/tx_grpc_api.py b/pyinjective/core/tx/grpc/tx_grpc_api.py new file mode 100644 index 00000000..d984dbf6 --- /dev/null +++ b/pyinjective/core/tx/grpc/tx_grpc_api.py @@ -0,0 +1,33 @@ +from typing import Any, Callable, Dict + +from grpc.aio import Channel + +from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, service_pb2_grpc as tx_service_grpc +from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant + + +class TxGrpcApi: + def __init__(self, channel: Channel, metadata_provider: Callable): + self._stub = tx_service_grpc.ServiceStub(channel) + self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider) + + async def simulate(self, tx_bytes: bytes) -> Dict[str, Any]: + request = tx_service.SimulateRequest(tx_bytes=tx_bytes) + response = await self._execute_call(call=self._stub.Simulate, request=request) + + return response + + async def fetch_tx(self, hash: str) -> Dict[str, Any]: + request = tx_service.GetTxRequest(hash=hash) + response = await self._execute_call(call=self._stub.GetTx, request=request) + + return response + + async def broadcast(self, tx_bytes: bytes, mode: int = tx_service.BROADCAST_MODE_ASYNC) -> Dict[str, Any]: + request = tx_service.BroadcastTxRequest(tx_bytes=tx_bytes, mode=mode) + response = await self._execute_call(call=self._stub.BroadcastTx, request=request) + + return response + + async def _execute_call(self, call: Callable, request) -> Dict[str, Any]: + return await self._assistant.execute_call(call=call, request=request) diff --git a/pyinjective/utils/grpc_api_request_assistant.py b/pyinjective/utils/grpc_api_request_assistant.py new file mode 100644 index 00000000..5ed3c5fa --- /dev/null +++ b/pyinjective/utils/grpc_api_request_assistant.py @@ -0,0 +1,20 @@ +from typing import Any, Callable, Dict + +from google.protobuf import json_format + + +class GrpcApiRequestAssistant: + def __init__(self, metadata_provider: Callable): + super().__init__() + self._metadata_provider = metadata_provider + + async def execute_call(self, call: Callable, request) -> Dict[str, Any]: + metadata = await self._metadata_provider() + response = await call(request, metadata=metadata) + + result = json_format.MessageToDict( + message=response, + including_default_value_fields=True, + ) + + return result diff --git a/pyinjective/utils/grpc_api_stream_assistant.py b/pyinjective/utils/grpc_api_stream_assistant.py new file mode 100644 index 00000000..00a8c331 --- /dev/null +++ b/pyinjective/utils/grpc_api_stream_assistant.py @@ -0,0 +1,47 @@ +import asyncio +from typing import Callable, Optional + +from google.protobuf import json_format +from grpc import RpcError + + +class GrpcApiStreamAssistant: + def __init__(self, metadata_provider: Callable): + super().__init__() + self._metadata_provider = metadata_provider + + async def listen_stream( + self, + call: Callable, + request, + callback: Callable, + on_end_callback: Optional[Callable] = None, + on_status_callback: Optional[Callable] = None, + ): + metadata = await self._metadata_provider() + stream = call(request, metadata=metadata) + + try: + async for event in stream: + parsed_event = json_format.MessageToDict( + message=event, + including_default_value_fields=True, + ) + if asyncio.iscoroutinefunction(callback): + await callback(parsed_event) + else: + callback(parsed_event) + except RpcError as ex: + if on_status_callback is not None: + if asyncio.iscoroutinefunction(on_status_callback): + await on_status_callback(ex) + else: + on_status_callback(ex) + except Exception as all_ex: + print(all_ex) + + if on_end_callback is not None: + if asyncio.iscoroutinefunction(on_end_callback): + await on_end_callback() + else: + on_end_callback() diff --git a/pyproject.toml b/pyproject.toml index 90016724..8ad2fe1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "injective-py" -version = "1.0.1-rc3" +version = "1.0.1-rc4" description = "Injective Python SDK, with Exchange API Client" authors = ["Injective Labs "] license = "Apache-2.0" @@ -36,7 +36,7 @@ mnemonic = "*" protobuf = "*" requests = "*" safe-pysha3 = "*" -urllib3 = "<2" +urllib3 = ">=1.26.18,<2" websockets = "*" web3 = "^6.0" @@ -62,7 +62,7 @@ max_line_length = 120 # list of plugins and rules for them [tool.flakeheaven.plugins] -pycodestyle = ["+*", "-W503"] +pycodestyle = ["+*", "-W503", "-E731"] pyflakes = ["+*"] [tool.flakeheaven.exceptions."tests/"] @@ -70,8 +70,13 @@ pyflakes = ["-F811"] # disable a plugin [tool.isort] -profile = "black" line_length = 120 +multi_line_output = 3 +combine_as_imports = true +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true skip_glob = ["pyinjective/proto/*", ".idea/*"] @@ -96,7 +101,7 @@ omit = ["pyinjective/proto/*"] [tool.coverage.report] skip_empty = true -fail_under = 50 +fail_under = 65 precision = 2 [tool.pytest.ini_options] diff --git a/tests/client/__init__.py b/tests/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/__init__.py b/tests/client/chain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/grpc/__init__.py b/tests/client/chain/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/grpc/configurable_auction_query_servicer.py b/tests/client/chain/grpc/configurable_auction_query_servicer.py new file mode 100644 index 00000000..66d3269b --- /dev/null +++ b/tests/client/chain/grpc/configurable_auction_query_servicer.py @@ -0,0 +1,25 @@ +from collections import deque + +from pyinjective.proto.injective.auction.v1beta1 import ( + query_pb2 as auction_query_pb, + query_pb2_grpc as auction_query_grpc, +) + + +class ConfigurableAuctionQueryServicer(auction_query_grpc.QueryServicer): + def __init__(self): + super().__init__() + self.auction_params = deque() + self.module_states = deque() + self.current_baskets = deque() + + async def AuctionParams(self, request: auction_query_pb.QueryAuctionParamsRequest, context=None, metadata=None): + return self.auction_params.pop() + + async def AuctionModuleState(self, request: auction_query_pb.QueryModuleStateRequest, context=None, metadata=None): + return self.module_states.pop() + + async def CurrentAuctionBasket( + self, request: auction_query_pb.QueryCurrentAuctionBasketRequest, context=None, metadata=None + ): + return self.current_baskets.pop() diff --git a/tests/client/chain/grpc/configurable_auth_query_servicer.py b/tests/client/chain/grpc/configurable_auth_query_servicer.py new file mode 100644 index 00000000..af7783e2 --- /dev/null +++ b/tests/client/chain/grpc/configurable_auth_query_servicer.py @@ -0,0 +1,20 @@ +from collections import deque + +from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query_pb, query_pb2_grpc as auth_query_grpc + + +class ConfigurableAuthQueryServicer(auth_query_grpc.QueryServicer): + def __init__(self): + super().__init__() + self.auth_params = deque() + self.account_responses = deque() + self.accounts_responses = deque() + + async def Params(self, request: auth_query_pb.QueryParamsRequest, context=None, metadata=None): + return self.auth_params.pop() + + async def Account(self, request: auth_query_pb.QueryAccountRequest, context=None, metadata=None): + return self.account_responses.pop() + + async def Accounts(self, request: auth_query_pb.QueryAccountsRequest, context=None, metadata=None): + return self.accounts_responses.pop() diff --git a/tests/client/chain/grpc/configurable_autz_query_servicer.py b/tests/client/chain/grpc/configurable_autz_query_servicer.py new file mode 100644 index 00000000..550e3226 --- /dev/null +++ b/tests/client/chain/grpc/configurable_autz_query_servicer.py @@ -0,0 +1,20 @@ +from collections import deque + +from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query, query_pb2_grpc as authz_query_grpc + + +class ConfigurableAuthZQueryServicer(authz_query_grpc.QueryServicer): + def __init__(self): + super().__init__() + self.grants_responses = deque() + self.granter_grants_responses = deque() + self.grantee_grants_responses = deque() + + async def Grants(self, request: authz_query.QueryGrantsRequest, context=None, metadata=None): + return self.grants_responses.pop() + + async def GranterGrants(self, request: authz_query.QueryGranterGrantsRequest, context=None, metadata=None): + return self.granter_grants_responses.pop() + + async def GranteeGrants(self, request: authz_query.QueryGranteeGrantsRequest, context=None, metadata=None): + return self.grantee_grants_responses.pop() diff --git a/tests/client/chain/grpc/configurable_bank_query_servicer.py b/tests/client/chain/grpc/configurable_bank_query_servicer.py new file mode 100644 index 00000000..015ce2c8 --- /dev/null +++ b/tests/client/chain/grpc/configurable_bank_query_servicer.py @@ -0,0 +1,24 @@ +from collections import deque + +from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query_pb, query_pb2_grpc as bank_query_grpc + + +class ConfigurableBankQueryServicer(bank_query_grpc.QueryServicer): + def __init__(self): + super().__init__() + self.bank_params = deque() + self.balance_responses = deque() + self.balances_responses = deque() + self.total_supply_responses = deque() + + async def Params(self, request: bank_query_pb.QueryParamsRequest, context=None, metadata=None): + return self.bank_params.pop() + + async def Balance(self, request: bank_query_pb.QueryBalanceRequest, context=None, metadata=None): + return self.balance_responses.pop() + + async def AllBalances(self, request: bank_query_pb.QueryAllBalancesRequest, context=None, metadata=None): + return self.balances_responses.pop() + + async def TotalSupply(self, request: bank_query_pb.QueryTotalSupplyRequest, context=None, metadata=None): + return self.total_supply_responses.pop() diff --git a/tests/client/chain/grpc/test_chain_grpc_auction_api.py b/tests/client/chain/grpc/test_chain_grpc_auction_api.py new file mode 100644 index 00000000..968ee238 --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_auction_api.py @@ -0,0 +1,166 @@ +import grpc +import pytest + +from pyinjective.client.chain.grpc.chain_grpc_auction_api import ChainGrpcAuctionApi +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.injective.auction.v1beta1 import ( + auction_pb2 as auction_pb, + genesis_pb2 as genesis_pb, + query_pb2 as auction_query_pb, +) +from tests.client.chain.grpc.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer + + +@pytest.fixture +def auction_servicer(): + return ConfigurableAuctionQueryServicer() + + +class TestChainGrpcAuctionApi: + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + auction_servicer, + ): + params = auction_pb.Params(auction_period=604800, min_next_bid_increment_rate="2500000000000000") + auction_servicer.auction_params.append(auction_query_pb.QueryAuctionParamsResponse(params=params)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auction_servicer + + module_params = await api.fetch_module_params() + expected_params = {"params": {"auctionPeriod": "604800", "minNextBidIncrementRate": "2500000000000000"}} + + assert expected_params == module_params + + @pytest.mark.asyncio + async def test_fetch_module_state( + self, + auction_servicer, + ): + params = auction_pb.Params(auction_period=604800, min_next_bid_increment_rate="2500000000000000") + highest_bid = auction_pb.Bid( + bidder="inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + amount="\n\003inj\022\0232347518723906280000", + ) + state = genesis_pb.GenesisState( + params=params, + auction_round=50, + highest_bid=highest_bid, + auction_ending_timestamp=1687504387, + ) + auction_servicer.module_states.append(auction_query_pb.QueryModuleStateResponse(state=state)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auction_servicer + + module_state = await api.fetch_module_state() + expected_state = { + "state": { + "auctionEndingTimestamp": "1687504387", + "auctionRound": "50", + "highestBid": { + "amount": "\n\x03inj\x12\x132347518723906280000", + "bidder": "inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + }, + "params": { + "auctionPeriod": "604800", + "minNextBidIncrementRate": "2500000000000000", + }, + } + } + + assert expected_state == module_state + + @pytest.mark.asyncio + async def test_fetch_module_state_when_no_highest_bid_present( + self, + auction_servicer, + ): + params = auction_pb.Params(auction_period=604800, min_next_bid_increment_rate="2500000000000000") + state = genesis_pb.GenesisState( + params=params, + auction_round=50, + auction_ending_timestamp=1687504387, + ) + auction_servicer.module_states.append(auction_query_pb.QueryModuleStateResponse(state=state)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auction_servicer + + module_state = await api.fetch_module_state() + expected_state = { + "state": { + "auctionEndingTimestamp": "1687504387", + "auctionRound": "50", + "params": { + "auctionPeriod": "604800", + "minNextBidIncrementRate": "2500000000000000", + }, + } + } + + assert expected_state == module_state + + @pytest.mark.asyncio + async def test_fetch_current_basket( + self, + auction_servicer, + ): + first_amount = coin_pb.Coin( + amount="15059786755", + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + ) + second_amount = coin_pb.Coin( + amount="200000", + denom="peggy0xf9152067989BDc8783fF586624124C05A529A5D1", + ) + + auction_servicer.current_baskets.append( + auction_query_pb.QueryCurrentAuctionBasketResponse( + amount=[first_amount, second_amount], + auctionRound=50, + auctionClosingTime=1687504387, + highestBidder="inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + highestBidAmount="2347518723906280000", + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auction_servicer + + current_basket = await api.fetch_current_basket() + expected_basket = { + "amount": [ + { + "amount": "15059786755", + "denom": "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + }, + { + "amount": "200000", + "denom": "peggy0xf9152067989BDc8783fF586624124C05A529A5D1", + }, + ], + "auctionClosingTime": "1687504387", + "auctionRound": "50", + "highestBidAmount": "2347518723906280000", + "highestBidder": "inj1pvt70tt7epjudnurkqlxu62flfgy46j2ytj7j5", + } + + assert expected_basket == current_basket + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/chain/grpc/test_chain_grpc_auth_api.py b/tests/client/chain/grpc/test_chain_grpc_auth_api.py new file mode 100644 index 00000000..03032cb1 --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_auth_api.py @@ -0,0 +1,174 @@ +import base64 + +import grpc +import pytest +from google.protobuf import any_pb2 + +from pyinjective.client.chain.grpc.chain_grpc_auth_api import ChainGrpcAuthApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.auth.v1beta1 import auth_pb2 as auth_pb, query_pb2 as auth_query_pb +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from pyinjective.proto.injective.crypto.v1beta1.ethsecp256k1 import keys_pb2 as keys_pb +from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb +from tests.client.chain.grpc.configurable_auth_query_servicer import ConfigurableAuthQueryServicer + + +@pytest.fixture +def auth_servicer(): + return ConfigurableAuthQueryServicer() + + +class TestChainGrpcAuthApi: + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + auth_servicer, + ): + params = auth_pb.Params( + max_memo_characters=256, + tx_sig_limit=7, + tx_size_cost_per_byte=10, + sig_verify_cost_ed25519=590, + sig_verify_cost_secp256k1=1000, + ) + auth_servicer.auth_params.append(auth_query_pb.QueryParamsResponse(params=params)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auth_servicer + + module_params = await api.fetch_module_params() + expected_params = { + "params": { + "maxMemoCharacters": "256", + "sigVerifyCostEd25519": "590", + "sigVerifyCostSecp256k1": "1000", + "txSigLimit": "7", + "txSizeCostPerByte": "10", + } + } + + assert expected_params == module_params + + @pytest.mark.asyncio + async def test_fetch_account( + self, + auth_servicer, + ): + pub_key = keys_pb.PubKey(key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375") + any_pub_key = any_pb2.Any() + any_pub_key.Pack(pub_key, type_url_prefix="") + + base_account = auth_pb.BaseAccount( + address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr", + pub_key=any_pub_key, + account_number=39, + sequence=697457, + ) + account = account_pb.EthAccount( + base_account=base_account, + code_hash=b"\305\322F\001\206\367#<\222~}\262\334\307\003\300\345\000\266S\312\202';{" + b"\372\330\004]\205\244p", + ) + + any_account = any_pb2.Any() + any_account.Pack(account, type_url_prefix="") + auth_servicer.account_responses.append(auth_query_pb.QueryAccountResponse(account=any_account)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auth_servicer + + response_account = await api.fetch_account(address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr") + expected_account = { + "account": { + "@type": "/injective.types.v1beta1.EthAccount", + "baseAccount": { + "accountNumber": str(base_account.account_number), + "address": base_account.address, + "pubKey": { + "@type": "/injective.crypto.v1beta1.ethsecp256k1.PubKey", + "key": base64.b64encode(pub_key.key).decode(), + }, + "sequence": str(base_account.sequence), + }, + "codeHash": base64.b64encode(account.code_hash).decode(), + } + } + + assert expected_account == response_account + + @pytest.mark.asyncio + async def test_fetch_accounts( + self, + auth_servicer, + ): + pub_key = keys_pb.PubKey(key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375") + any_pub_key = any_pb2.Any() + any_pub_key.Pack(pub_key, type_url_prefix="") + + base_account = auth_pb.BaseAccount( + address="inj1knhahceyp57j5x7xh69p7utegnnnfgxavmahjr", + pub_key=any_pub_key, + account_number=39, + sequence=697457, + ) + account = account_pb.EthAccount( + base_account=base_account, + code_hash=b"\305\322F\001\206\367#<\222~}\262\334\307\003\300\345\000\266S\312\202';{" + b"\372\330\004]\205\244p", + ) + + result_pagination = pagination_pb.PageResponse( + next_key=b"\001\032\264\007Z\224$]\377s8\343\004-\265\267\314?B\341", + total=16036, + ) + + any_account = any_pb2.Any() + any_account.Pack(account, type_url_prefix="") + auth_servicer.accounts_responses.append( + auth_query_pb.QueryAccountsResponse( + accounts=[any_account], + pagination=result_pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auth_servicer + + pagination_option = PaginationOption( + key="011ab4075a94245dff7338e3042db5b7cc3f42e1", + skip=10, + limit=30, + reverse=False, + count_total=True, + ) + + response = await api.fetch_accounts(pagination_option=pagination_option) + response_accounts = response["accounts"] + response_pagination = response["pagination"] + + assert 1 == len(response_accounts) + + response_account = response_accounts[0] + + assert account.code_hash == base64.b64decode(response_account["codeHash"]) + assert base_account.address == response_account["baseAccount"]["address"] + assert any_pub_key.type_url == response_account["baseAccount"]["pubKey"]["@type"] + assert pub_key.key == base64.b64decode(response_account["baseAccount"]["pubKey"]["key"]) + assert base_account.account_number == int(response_account["baseAccount"]["accountNumber"]) + assert base_account.sequence == int(response_account["baseAccount"]["sequence"]) + + assert result_pagination.next_key == base64.b64decode(response_pagination["nextKey"]) + assert result_pagination.total == int(response_pagination["total"]) + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/chain/grpc/test_chain_grpc_authz_api.py b/tests/client/chain/grpc/test_chain_grpc_authz_api.py new file mode 100644 index 00000000..e097ff66 --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_authz_api.py @@ -0,0 +1,202 @@ +import grpc +import pytest +from google.protobuf import any_pb2 + +from pyinjective.client.chain.grpc.chain_grpc_authz_api import ChainGrpcAuthZApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.authz.v1beta1 import authz_pb2, query_pb2 as authz_query +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from tests.client.chain.grpc.configurable_autz_query_servicer import ConfigurableAuthZQueryServicer + + +@pytest.fixture +def authz_servicer(): + return ConfigurableAuthZQueryServicer() + + +class TestChainGrpcAuthZApi: + @pytest.mark.asyncio + async def test_fetch_grants( + self, + authz_servicer, + ): + authorization = any_pb2.Any( + type_url="/injective.exchange.v1beta1.CreateSpotMarketOrderAuthz", + value=( + "\nB0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000\022" + "B0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + ).encode(), + ) + + grant = authz_pb2.Grant(authorization=authorization) + + page_response = pagination_pb.PageResponse(total=1) + + authz_servicer.grants_responses.append( + authz_query.QueryGrantsResponse( + grants=[grant], + pagination=page_response, + ) + ) + + pagination_option = PaginationOption( + skip=10, + limit=30, + reverse=False, + count_total=True, + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthZApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = authz_servicer + + result_grants = await api.fetch_grants( + granter="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + grantee="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + msg_type_url="/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder", + pagination=pagination_option, + ) + expected_grants = { + "grants": [ + { + "authorization": { + "@type": authorization.type_url, + "subaccountId": "0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000", + "marketIds": ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"], + } + } + ], + "pagination": {"nextKey": "", "total": str(page_response.total)}, + } + + assert result_grants == expected_grants + + @pytest.mark.asyncio + async def test_fetch_granter_grants( + self, + authz_servicer, + ): + authorization = any_pb2.Any( + type_url="/injective.exchange.v1beta1.CreateSpotMarketOrderAuthz", + value=( + "\nB0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000\022" + "B0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + ).encode(), + ) + + grant_authorization = authz_pb2.GrantAuthorization( + granter="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + grantee="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + authorization=authorization, + ) + + page_response = pagination_pb.PageResponse(total=1) + + authz_servicer.granter_grants_responses.append( + authz_query.QueryGranterGrantsResponse( + grants=[grant_authorization], + pagination=page_response, + ) + ) + + pagination_option = PaginationOption( + skip=10, + limit=30, + reverse=False, + count_total=True, + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthZApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = authz_servicer + + result_grants = await api.fetch_granter_grants( + granter="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + pagination=pagination_option, + ) + expected_grants = { + "grants": [ + { + "grantee": grant_authorization.grantee, + "granter": grant_authorization.granter, + "authorization": { + "@type": authorization.type_url, + "subaccountId": "0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000", + "marketIds": ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"], + }, + } + ], + "pagination": {"nextKey": "", "total": str(page_response.total)}, + } + + assert result_grants == expected_grants + + @pytest.mark.asyncio + async def test_fetch_grantee_grants( + self, + authz_servicer, + ): + authorization = any_pb2.Any( + type_url="/injective.exchange.v1beta1.CreateSpotMarketOrderAuthz", + value=( + "\nB0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000\022" + "B0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + ).encode(), + ) + + grant_authorization = authz_pb2.GrantAuthorization( + granter="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + grantee="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + authorization=authorization, + ) + + page_response = pagination_pb.PageResponse(total=1) + + authz_servicer.grantee_grants_responses.append( + authz_query.QueryGranteeGrantsResponse( + grants=[grant_authorization], + pagination=page_response, + ) + ) + + pagination_option = PaginationOption( + skip=10, + limit=30, + reverse=False, + count_total=True, + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcAuthZApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = authz_servicer + + result_grants = await api.fetch_grantee_grants( + grantee="inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", + pagination=pagination_option, + ) + expected_grants = { + "grants": [ + { + "grantee": grant_authorization.grantee, + "granter": grant_authorization.granter, + "authorization": { + "@type": authorization.type_url, + "subaccountId": "0xf5099d25e6e7e8c6584b67826127b04c9de3e554000000000000000000000000", + "marketIds": ["0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"], + }, + } + ], + "pagination": {"nextKey": "", "total": str(page_response.total)}, + } + + assert result_grants == expected_grants + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/chain/grpc/test_chain_grpc_bank_api.py b/tests/client/chain/grpc/test_chain_grpc_bank_api.py new file mode 100644 index 00000000..1c8c0ffa --- /dev/null +++ b/tests/client/chain/grpc/test_chain_grpc_bank_api.py @@ -0,0 +1,168 @@ +import base64 + +import grpc +import pytest + +from pyinjective.client.chain.grpc.chain_grpc_bank_api import ChainGrpcBankApi +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.bank.v1beta1 import bank_pb2 as bank_pb, query_pb2 as bank_query_pb +from pyinjective.proto.cosmos.base.query.v1beta1 import pagination_pb2 as pagination_pb +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer + + +@pytest.fixture +def bank_servicer(): + return ConfigurableBankQueryServicer() + + +class TestChainGrpcBankApi: + @pytest.mark.asyncio + async def test_fetch_module_params( + self, + bank_servicer, + ): + params = bank_pb.Params(default_send_enabled=True) + bank_servicer.bank_params.append(bank_query_pb.QueryParamsResponse(params=params)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = bank_servicer + + module_params = await api.fetch_module_params() + expected_params = { + "params": { + "defaultSendEnabled": True, + "sendEnabled": [], + } + } + + assert expected_params == module_params + + @pytest.mark.asyncio + async def test_fetch_balance( + self, + bank_servicer, + ): + balance = coin_pb.Coin(denom="inj", amount="988987297011197594664") + bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse(balance=balance)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = bank_servicer + + bank_balance = await api.fetch_balance( + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", denom="inj" + ) + expected_balance = {"denom": "inj", "amount": "988987297011197594664"} + + assert expected_balance == bank_balance + + @pytest.mark.asyncio + async def test_fetch_balance( + self, + bank_servicer, + ): + balance = coin_pb.Coin(denom="inj", amount="988987297011197594664") + bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse(balance=balance)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = bank_servicer + + bank_balance = await api.fetch_balance( + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", denom="inj" + ) + expected_balance = { + "balance": { + "denom": "inj", + "amount": "988987297011197594664", + } + } + + assert expected_balance == bank_balance + + @pytest.mark.asyncio + async def test_fetch_balances( + self, + bank_servicer, + ): + first_balance = coin_pb.Coin(denom="inj", amount="988987297011197594664") + second_balance = coin_pb.Coin(denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", amount="54497408") + pagination = pagination_pb.PageResponse(total=2) + + bank_servicer.balances_responses.append( + bank_query_pb.QueryAllBalancesResponse( + balances=[first_balance, second_balance], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = bank_servicer + + bank_balances = await api.fetch_balances( + account_address="inj1cml96vmptgw99syqrrz8az79xer2pcgp0a885r", + ) + expected_balances = { + "balances": [{"denom": coin.denom, "amount": coin.amount} for coin in [first_balance, second_balance]], + "pagination": {"nextKey": "", "total": "2"}, + } + + assert expected_balances == bank_balances + + @pytest.mark.asyncio + async def test_fetch_total_supply( + self, + bank_servicer, + ): + first_supply = coin_pb.Coin( + denom="factory/inj108t3mlej0dph8er6ca2lq5cs9pdgzva5mqsn5p/position", amount="5556700000000000000" + ) + second_supply = coin_pb.Coin( + denom="factory/inj10uycavvkc4uqr8tns3599r0t2xux4rz3y8apym/1684002313InjUsdt1d110C", + amount="1123456789111100000", + ) + pagination = pagination_pb.PageResponse( + next_key=( + "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/" "inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + ).encode(), + total=179, + ) + + bank_servicer.total_supply_responses.append( + bank_query_pb.QueryTotalSupplyResponse( + supply=[first_supply, second_supply], + pagination=pagination, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = ChainGrpcBankApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = bank_servicer + + total_supply = await api.fetch_total_supply() + next_key = "factory/inj1vkrp72xd67plcggcfjtjelqa4t5a093xljf2vj/inj1spw6nd0pj3kd3fgjljhuqpc8tv72a9v89myuja" + expected_supply = { + "supply": [{"denom": coin.denom, "amount": coin.amount} for coin in [first_supply, second_supply]], + "pagination": { + "nextKey": base64.b64encode(next_key.encode()).decode(), + "total": "179", + }, + } + + assert expected_supply == total_supply + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/chain/stream_grpc/__init__.py b/tests/client/chain/stream_grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/chain/stream_grpc/configurable_chain_stream_query_servicer.py b/tests/client/chain/stream_grpc/configurable_chain_stream_query_servicer.py new file mode 100644 index 00000000..fe27f667 --- /dev/null +++ b/tests/client/chain/stream_grpc/configurable_chain_stream_query_servicer.py @@ -0,0 +1,13 @@ +from collections import deque + +from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_pb, query_pb2_grpc as chain_stream_grpc + + +class ConfigurableChainStreamQueryServicer(chain_stream_grpc.StreamServicer): + def __init__(self): + super().__init__() + self.stream_responses = deque() + + async def Stream(self, request: chain_stream_pb.StreamRequest, context=None, metadata=None): + for event in self.stream_responses: + yield event diff --git a/tests/client/chain/stream_grpc/test_chain_grpc_chain_stream.py b/tests/client/chain/stream_grpc/test_chain_grpc_chain_stream.py new file mode 100644 index 00000000..8d160b14 --- /dev/null +++ b/tests/client/chain/stream_grpc/test_chain_grpc_chain_stream.py @@ -0,0 +1,408 @@ +import asyncio +import base64 + +import grpc +import pytest + +from pyinjective.client.chain.grpc_stream.chain_grpc_chain_stream import ChainGrpcChainStream +from pyinjective.composer import Composer +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.injective.exchange.v1beta1 import exchange_pb2 as exchange_pb +from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_pb +from tests.client.chain.stream_grpc.configurable_chain_stream_query_servicer import ConfigurableChainStreamQueryServicer + + +@pytest.fixture +def chain_stream_servicer(): + return ConfigurableChainStreamQueryServicer() + + +class TestChainGrpcChainStream: + @pytest.mark.asyncio + async def test_stream( + self, + chain_stream_servicer, + ): + block_height = 19114391 + block_time = 1701457189786 + balance_coin = coin_pb.Coin( + denom="inj", + amount="6941221373191000000000", + ) + bank_balance = chain_stream_pb.BankBalance( + account="inj1qaq7mkvuc474k2nyjm2suwyes06fdm4kt26ks4", + balances=[balance_coin], + ) + deposit = exchange_pb.Deposit( + available_balance="112292968420000000000000", + total_balance="73684013968420000000000000", + ) + subaccount_deposit = chain_stream_pb.SubaccountDeposit( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + deposit=deposit, + ) + subaccount_deposits = chain_stream_pb.SubaccountDeposits( + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000007", + deposits=[subaccount_deposit], + ) + spot_trade = chain_stream_pb.SpotTrade( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + is_buy=False, + executionType="LimitMatchNewOrder", + quantity="7000000000000000000000000000000000", + price="18215000", + subaccount_id="0x893f2abf8034627e50cbc63923120b1122503ce0000000000000000000000001", + fee="76503000000000000000", + order_hash=b"\xaa\xb0Ju\xa3)@\xfe\xd58N\xba\xdfG\xfd\xd8}\xe4\r\xf4\xf8a\xd9\n\xa9\xd6x+V\x9b\x02&", + fee_recipient_address="inj13ylj40uqx338u5xtccujxystzy39q08q2gz3dx", + cid="HBOTSIJUT60b77b9c56f0456af96c5c6c0d8", + ) + position_delta = exchange_pb.PositionDelta( + is_long=True, + execution_quantity="5000000", + execution_price="13945600", + execution_margin="69728000", + ) + derivative_trade = chain_stream_pb.DerivativeTrade( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + is_buy=False, + executionType="LimitMatchNewOrder", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + position_delta=position_delta, + payout="0", + fee="76503000000000000000", + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + fee_recipient_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + cid="cid1", + ) + spot_order_info = exchange_pb.OrderInfo( + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + price="18775000", + quantity="54606542000000000000000000000000000000000", + cid="cid2", + ) + spot_limit_order = exchange_pb.SpotLimitOrder( + order_info=spot_order_info, + order_type=exchange_pb.OrderType.SELL_PO, + fillable="54606542000000000000000000000000000000000", + trigger_price="", + order_hash=( + b"\xf9\xc7\xd8v8\x84-\x9b\x99s\xf5\xdfX\xc9\xf9V\x9a\xf7\xf9\xc3\xa1\x00h\t\xc17<\xd1k\x9d\x12\xed" + ), + ) + spot_order = chain_stream_pb.SpotOrder( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + order=spot_limit_order, + ) + spot_order_update = chain_stream_pb.SpotOrderUpdate( + status="Booked", + order_hash=( + b"\xf9\xc7\xd8v8\x84-\x9b\x99s\xf5\xdfX\xc9\xf9V\x9a\xf7\xf9\xc3\xa1\x00h\t\xc17<\xd1k\x9d\x12\xed" + ), + cid="cid2", + order=spot_order, + ) + derivative_order_info = exchange_pb.OrderInfo( + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + price="18775000", + quantity="54606542000000000000000000000000000000000", + cid="cid2", + ) + derivative_limit_order = exchange_pb.DerivativeLimitOrder( + order_info=derivative_order_info, + order_type=exchange_pb.OrderType.SELL_PO, + margin="54606542000000000000000000000000000000000", + fillable="54606542000000000000000000000000000000000", + trigger_price="", + order_hash=b"\x03\xc9\xf8G*Q-G%\xf1\xbcF3\xe89g\xbe\xeag\xd8Y\x7f\x87\x8a\xa5\xac\x8ew\x8a\x91\xa2F", + ) + derivative_order = chain_stream_pb.DerivativeOrder( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + order=derivative_limit_order, + is_market=False, + ) + derivative_order_update = chain_stream_pb.DerivativeOrderUpdate( + status="Booked", + order_hash=b"\x03\xc9\xf8G*Q-G%\xf1\xbcF3\xe89g\xbe\xeag\xd8Y\x7f\x87\x8a\xa5\xac\x8ew\x8a\x91\xa2F", + cid="cid3", + order=derivative_order, + ) + spot_buy_level = exchange_pb.Level(p="17280000", q="44557734000000000000000000000000000000000") + spot_sell_level = exchange_pb.Level( + p="18207000", + q="22196395000000000000000000000000000000000", + ) + spot_orderbook = chain_stream_pb.Orderbook( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + buy_levels=[spot_buy_level], + sell_levels=[spot_sell_level], + ) + spot_orderbook_update = chain_stream_pb.OrderbookUpdate( + seq=6645013, + orderbook=spot_orderbook, + ) + derivative_buy_level = exchange_pb.Level(p="17280000", q="44557734000000000000000000000000000000000") + derivative_sell_level = exchange_pb.Level( + p="18207000", + q="22196395000000000000000000000000000000000", + ) + derivative_orderbook = chain_stream_pb.Orderbook( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + buy_levels=[derivative_buy_level], + sell_levels=[derivative_sell_level], + ) + derivative_orderbook_update = chain_stream_pb.OrderbookUpdate( + seq=6645013, + orderbook=derivative_orderbook, + ) + position = chain_stream_pb.Position( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + isLong=True, + quantity="22196395000000000000000000000000000000000", + entry_price="18207000", + margin="22196395000000000000000000000000000000000", + cumulative_funding_entry="0", + ) + oracle_price = chain_stream_pb.OraclePrice( + symbol="0x41f3625971ca2ed2263e78573fe5ce23e13d2558ed3f2e47ab0f84fb9e7ae722", + price="999910860000000000", + type="pyth", + ) + + chain_stream_servicer.stream_responses.append( + chain_stream_pb.StreamResponse( + block_height=block_height, + block_time=block_time, + bank_balances=[bank_balance], + subaccount_deposits=[subaccount_deposits], + spot_trades=[spot_trade], + derivative_trades=[derivative_trade], + spot_orders=[spot_order_update], + derivative_orders=[derivative_order_update], + spot_orderbook_updates=[spot_orderbook_update], + derivative_orderbook_updates=[derivative_orderbook_update], + positions=[position], + oracle_prices=[oracle_price], + ) + ) + + network = Network.devnet() + composer = Composer(network=network.string()) + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = ChainGrpcChainStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = chain_stream_servicer + + events = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: events.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + bank_balances_filter = composer.chain_stream_bank_balances_filter() + subaccount_deposits_filter = composer.chain_stream_subaccount_deposits_filter() + spot_trades_filter = composer.chain_stream_trades_filter() + derivative_trades_filter = composer.chain_stream_trades_filter() + spot_orders_filter = composer.chain_stream_orders_filter() + derivative_orders_filter = composer.chain_stream_orders_filter() + spot_orderbooks_filter = composer.chain_stream_orderbooks_filter() + derivative_orderbooks_filter = composer.chain_stream_orderbooks_filter() + positions_filter = composer.chain_stream_positions_filter() + oracle_price_filter = composer.chain_stream_oracle_price_filter() + + expected_update = { + "blockHeight": str(block_height), + "blockTime": str(block_time), + "bankBalances": [ + { + "account": bank_balance.account, + "balances": [ + { + "denom": balance_coin.denom, + "amount": balance_coin.amount, + } + ], + }, + ], + "subaccountDeposits": [ + { + "subaccountId": subaccount_deposits.subaccount_id, + "deposits": [ + { + "denom": subaccount_deposit.denom, + "deposit": { + "availableBalance": deposit.available_balance, + "totalBalance": deposit.total_balance, + }, + } + ], + } + ], + "spotTrades": [ + { + "marketId": spot_trade.market_id, + "isBuy": spot_trade.is_buy, + "executionType": spot_trade.executionType, + "quantity": spot_trade.quantity, + "price": spot_trade.price, + "subaccountId": spot_trade.subaccount_id, + "fee": spot_trade.fee, + "orderHash": base64.b64encode(spot_trade.order_hash).decode(), + "feeRecipientAddress": spot_trade.fee_recipient_address, + "cid": spot_trade.cid, + }, + ], + "derivativeTrades": [ + { + "marketId": derivative_trade.market_id, + "isBuy": derivative_trade.is_buy, + "executionType": derivative_trade.executionType, + "subaccountId": derivative_trade.subaccount_id, + "positionDelta": { + "isLong": position_delta.is_long, + "executionMargin": position_delta.execution_margin, + "executionQuantity": position_delta.execution_quantity, + "executionPrice": position_delta.execution_price, + }, + "payout": derivative_trade.payout, + "fee": derivative_trade.fee, + "orderHash": derivative_trade.order_hash, + "feeRecipientAddress": derivative_trade.fee_recipient_address, + "cid": derivative_trade.cid, + } + ], + "spotOrders": [ + { + "status": "Booked", + "orderHash": base64.b64encode(spot_order_update.order_hash).decode(), + "cid": spot_order_update.cid, + "order": { + "marketId": spot_order.market_id, + "order": { + "orderInfo": { + "subaccountId": spot_order_info.subaccount_id, + "feeRecipient": spot_order_info.fee_recipient, + "price": spot_order_info.price, + "quantity": spot_order_info.quantity, + "cid": spot_order_info.cid, + }, + "orderType": "SELL_PO", + "fillable": spot_limit_order.fillable, + "triggerPrice": spot_limit_order.trigger_price, + "orderHash": base64.b64encode(spot_limit_order.order_hash).decode(), + }, + }, + }, + ], + "derivativeOrders": [ + { + "status": "Booked", + "orderHash": base64.b64encode(derivative_order_update.order_hash).decode(), + "cid": derivative_order_update.cid, + "order": { + "marketId": derivative_order.market_id, + "order": { + "orderInfo": { + "subaccountId": derivative_order_info.subaccount_id, + "feeRecipient": derivative_order_info.fee_recipient, + "price": derivative_order_info.price, + "quantity": derivative_order_info.quantity, + "cid": derivative_order_info.cid, + }, + "orderType": "SELL_PO", + "margin": derivative_limit_order.margin, + "fillable": derivative_limit_order.fillable, + "triggerPrice": derivative_limit_order.trigger_price, + "orderHash": base64.b64encode(derivative_limit_order.order_hash).decode(), + }, + "isMarket": derivative_order.is_market, + }, + }, + ], + "spotOrderbookUpdates": [ + { + "seq": str(spot_orderbook_update.seq), + "orderbook": { + "marketId": spot_orderbook.market_id, + "buyLevels": [ + { + "p": spot_buy_level.p, + "q": spot_buy_level.q, + }, + ], + "sellLevels": [ + {"p": spot_sell_level.p, "q": spot_sell_level.q}, + ], + }, + }, + ], + "derivativeOrderbookUpdates": [ + { + "seq": str(derivative_orderbook_update.seq), + "orderbook": { + "marketId": derivative_orderbook.market_id, + "buyLevels": [ + { + "p": derivative_buy_level.p, + "q": derivative_buy_level.q, + }, + ], + "sellLevels": [ + { + "p": derivative_sell_level.p, + "q": derivative_sell_level.q, + }, + ], + }, + }, + ], + "positions": [ + { + "marketId": position.market_id, + "subaccountId": position.subaccount_id, + "isLong": position.isLong, + "quantity": position.quantity, + "entryPrice": position.entry_price, + "margin": position.margin, + "cumulativeFundingEntry": position.cumulative_funding_entry, + } + ], + "oraclePrices": [ + { + "symbol": oracle_price.symbol, + "price": oracle_price.price, + "type": oracle_price.type, + }, + ], + } + + asyncio.get_event_loop().create_task( + api.stream( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + bank_balances_filter=bank_balances_filter, + subaccount_deposits_filter=subaccount_deposits_filter, + spot_trades_filter=spot_trades_filter, + derivative_trades_filter=derivative_trades_filter, + spot_orders_filter=spot_orders_filter, + derivative_orders_filter=derivative_orders_filter, + spot_orderbooks_filter=spot_orderbooks_filter, + derivative_orderbooks_filter=derivative_orderbooks_filter, + positions_filter=positions_filter, + oracle_price_filter=oracle_price_filter, + ) + ) + + first_update = await asyncio.wait_for(events.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/__init__.py b/tests/client/indexer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/indexer/configurable_account_query_servicer.py b/tests/client/indexer/configurable_account_query_servicer.py new file mode 100644 index 00000000..161947c2 --- /dev/null +++ b/tests/client/indexer/configurable_account_query_servicer.py @@ -0,0 +1,58 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_accounts_rpc_pb2 as exchange_accounts_pb, + injective_accounts_rpc_pb2_grpc as exchange_accounts_grpc, +) + + +class ConfigurableAccountQueryServicer(exchange_accounts_grpc.InjectiveAccountsRPCServicer): + def __init__(self): + super().__init__() + self.portfolio_responses = deque() + self.order_states_responses = deque() + self.subaccounts_list_responses = deque() + self.subaccount_balances_list_responses = deque() + self.subaccount_balance_responses = deque() + self.subaccount_history_responses = deque() + self.subaccount_order_summary_responses = deque() + self.rewards_responses = deque() + self.stream_subaccount_balance_responses = deque() + + async def Portfolio(self, request: exchange_accounts_pb.PortfolioRequest, context=None, metadata=None): + return self.portfolio_responses.pop() + + async def OrderStates(self, request: exchange_accounts_pb.OrderStatesRequest, context=None, metadata=None): + return self.order_states_responses.pop() + + async def SubaccountsList(self, request: exchange_accounts_pb.SubaccountsListRequest, context=None, metadata=None): + return self.subaccounts_list_responses.pop() + + async def SubaccountBalancesList( + self, request: exchange_accounts_pb.SubaccountBalancesListRequest, context=None, metadata=None + ): + return self.subaccount_balances_list_responses.pop() + + async def SubaccountBalanceEndpoint( + self, request: exchange_accounts_pb.SubaccountBalanceEndpointRequest, context=None, metadata=None + ): + return self.subaccount_balance_responses.pop() + + async def SubaccountHistory( + self, request: exchange_accounts_pb.SubaccountHistoryRequest, context=None, metadata=None + ): + return self.subaccount_history_responses.pop() + + async def SubaccountOrderSummary( + self, request: exchange_accounts_pb.SubaccountOrderSummaryRequest, context=None, metadata=None + ): + return self.subaccount_order_summary_responses.pop() + + async def Rewards(self, request: exchange_accounts_pb.RewardsRequest, context=None, metadata=None): + return self.rewards_responses.pop() + + async def StreamSubaccountBalance( + self, request: exchange_accounts_pb.SubaccountOrderSummaryRequest, context=None, metadata=None + ): + for event in self.stream_subaccount_balance_responses: + yield event diff --git a/tests/client/indexer/configurable_auction_query_servicer.py b/tests/client/indexer/configurable_auction_query_servicer.py new file mode 100644 index 00000000..606e2e0d --- /dev/null +++ b/tests/client/indexer/configurable_auction_query_servicer.py @@ -0,0 +1,24 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_auction_rpc_pb2 as exchange_auction_pb, + injective_auction_rpc_pb2_grpc as exchange_auction_grpc, +) + + +class ConfigurableAuctionQueryServicer(exchange_auction_grpc.InjectiveAuctionRPCServicer): + def __init__(self): + super().__init__() + self.auction_endpoint_responses = deque() + self.auctions_responses = deque() + self.stream_bids_responses = deque() + + async def AuctionEndpoint(self, request: exchange_auction_pb.AuctionEndpointRequest, context=None, metadata=None): + return self.auction_endpoint_responses.pop() + + async def Auctions(self, request: exchange_auction_pb.AuctionsRequest, context=None, metadata=None): + return self.auctions_responses.pop() + + async def StreamBids(self, request: exchange_auction_pb.StreamBidsRequest, context=None, metadata=None): + for event in self.stream_bids_responses: + yield event diff --git a/tests/client/indexer/configurable_derivative_query_servicer.py b/tests/client/indexer/configurable_derivative_query_servicer.py new file mode 100644 index 00000000..dfb44179 --- /dev/null +++ b/tests/client/indexer/configurable_derivative_query_servicer.py @@ -0,0 +1,127 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb, + injective_derivative_exchange_rpc_pb2_grpc as exchange_derivative_grpc, +) + + +class ConfigurableDerivativeQueryServicer(exchange_derivative_grpc.InjectiveDerivativeExchangeRPCServicer): + def __init__(self): + super().__init__() + self.markets_responses = deque() + self.market_responses = deque() + self.binary_options_markets_responses = deque() + self.binary_options_market_responses = deque() + self.orderbook_v2_responses = deque() + self.orderbooks_v2_responses = deque() + self.orders_responses = deque() + self.positions_responses = deque() + self.liquidable_positions_responses = deque() + self.funding_payments_responses = deque() + self.funding_rates_responses = deque() + self.trades_responses = deque() + self.subaccount_orders_list_responses = deque() + self.subaccount_trades_list_responses = deque() + self.orders_history_responses = deque() + + self.stream_market_responses = deque() + self.stream_orderbook_v2_responses = deque() + self.stream_orderbook_update_responses = deque() + self.stream_positions_responses = deque() + self.stream_orders_responses = deque() + self.stream_trades_responses = deque() + self.stream_orders_history_responses = deque() + + async def Markets(self, request: exchange_derivative_pb.MarketsRequest, context=None, metadata=None): + return self.markets_responses.pop() + + async def Market(self, request: exchange_derivative_pb.MarketRequest, context=None, metadata=None): + return self.market_responses.pop() + + async def BinaryOptionsMarkets( + self, request: exchange_derivative_pb.BinaryOptionsMarketsRequest, context=None, metadata=None + ): + return self.binary_options_markets_responses.pop() + + async def BinaryOptionsMarket( + self, request: exchange_derivative_pb.BinaryOptionsMarketRequest, context=None, metadata=None + ): + return self.binary_options_market_responses.pop() + + async def OrderbookV2(self, request: exchange_derivative_pb.OrderbookV2Request, context=None, metadata=None): + return self.orderbook_v2_responses.pop() + + async def OrderbooksV2(self, request: exchange_derivative_pb.OrderbooksV2Request, context=None, metadata=None): + return self.orderbooks_v2_responses.pop() + + async def Orders(self, request: exchange_derivative_pb.OrdersRequest, context=None, metadata=None): + return self.orders_responses.pop() + + async def Positions(self, request: exchange_derivative_pb.PositionsRequest, context=None, metadata=None): + return self.positions_responses.pop() + + async def LiquidablePositions( + self, request: exchange_derivative_pb.LiquidablePositionsRequest, context=None, metadata=None + ): + return self.liquidable_positions_responses.pop() + + async def FundingPayments( + self, request: exchange_derivative_pb.FundingPaymentsRequest, context=None, metadata=None + ): + return self.funding_payments_responses.pop() + + async def FundingRates(self, request: exchange_derivative_pb.FundingRatesRequest, context=None, metadata=None): + return self.funding_rates_responses.pop() + + async def Trades(self, request: exchange_derivative_pb.TradesRequest, context=None, metadata=None): + return self.trades_responses.pop() + + async def SubaccountOrdersList( + self, request: exchange_derivative_pb.SubaccountOrdersListRequest, context=None, metadata=None + ): + return self.subaccount_orders_list_responses.pop() + + async def SubaccountTradesList( + self, request: exchange_derivative_pb.SubaccountTradesListRequest, context=None, metadata=None + ): + return self.subaccount_trades_list_responses.pop() + + async def OrdersHistory(self, request: exchange_derivative_pb.OrdersHistoryRequest, context=None, metadata=None): + return self.orders_history_responses.pop() + + async def StreamMarket(self, request: exchange_derivative_pb.StreamMarketRequest, context=None, metadata=None): + for event in self.stream_market_responses: + yield event + + async def StreamOrderbookV2( + self, request: exchange_derivative_pb.StreamOrderbookV2Request, context=None, metadata=None + ): + for event in self.stream_orderbook_v2_responses: + yield event + + async def StreamOrderbookUpdate( + self, request: exchange_derivative_pb.StreamOrderbookUpdateRequest, context=None, metadata=None + ): + for event in self.stream_orderbook_update_responses: + yield event + + async def StreamPositions( + self, request: exchange_derivative_pb.StreamPositionsRequest, context=None, metadata=None + ): + for event in self.stream_positions_responses: + yield event + + async def StreamOrders(self, request: exchange_derivative_pb.StreamOrdersRequest, context=None, metadata=None): + for event in self.stream_orders_responses: + yield event + + async def StreamTrades(self, request: exchange_derivative_pb.StreamTradesRequest, context=None, metadata=None): + for event in self.stream_trades_responses: + yield event + + async def StreamOrdersHistory( + self, request: exchange_derivative_pb.StreamOrdersHistoryRequest, context=None, metadata=None + ): + for event in self.stream_orders_history_responses: + yield event diff --git a/tests/client/indexer/configurable_explorer_query_servicer.py b/tests/client/indexer/configurable_explorer_query_servicer.py new file mode 100644 index 00000000..ce240bf3 --- /dev/null +++ b/tests/client/indexer/configurable_explorer_query_servicer.py @@ -0,0 +1,112 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_explorer_rpc_pb2 as exchange_explorer_pb, + injective_explorer_rpc_pb2_grpc as exchange_explorer_grpc, +) + + +class ConfigurableExplorerQueryServicer(exchange_explorer_grpc.InjectiveExplorerRPCServicer): + def __init__(self): + super().__init__() + self.account_txs_responses = deque() + self.contract_txs_responses = deque() + self.blocks_responses = deque() + self.block_responses = deque() + self.validators_responses = deque() + self.validator_responses = deque() + self.validator_uptime_responses = deque() + self.txs_responses = deque() + self.tx_by_tx_hash_responses = deque() + self.peggy_deposit_txs_responses = deque() + self.peggy_withdrawal_txs_responses = deque() + self.ibc_transfer_txs_responses = deque() + self.wasm_codes_responses = deque() + self.wasm_code_by_id_responses = deque() + self.wasm_contracts_responses = deque() + self.wasm_contract_by_address_responses = deque() + self.cw20_balance_responses = deque() + self.relayers_responses = deque() + self.bank_transfers_responses = deque() + + self.stream_txs_responses = deque() + self.stream_blocks_responses = deque() + + async def GetAccountTxs(self, request: exchange_explorer_pb.GetAccountTxsRequest, context=None, metadata=None): + return self.account_txs_responses.pop() + + async def GetContractTxs(self, request: exchange_explorer_pb.GetContractTxsRequest, context=None, metadata=None): + return self.contract_txs_responses.pop() + + async def GetBlocks(self, request: exchange_explorer_pb.GetBlocksRequest, context=None, metadata=None): + return self.blocks_responses.pop() + + async def GetBlock(self, request: exchange_explorer_pb.GetBlockRequest, context=None, metadata=None): + return self.block_responses.pop() + + async def GetValidators(self, request: exchange_explorer_pb.GetValidatorsRequest, context=None, metadata=None): + return self.validators_responses.pop() + + async def GetValidator(self, request: exchange_explorer_pb.GetValidatorRequest, context=None, metadata=None): + return self.validator_responses.pop() + + async def GetValidatorUptime( + self, request: exchange_explorer_pb.GetValidatorUptimeRequest, context=None, metadata=None + ): + return self.validator_uptime_responses.pop() + + async def GetTxs(self, request: exchange_explorer_pb.GetTxsRequest, context=None, metadata=None): + return self.txs_responses.pop() + + async def GetTxByTxHash(self, request: exchange_explorer_pb.GetTxByTxHashRequest, context=None, metadata=None): + return self.tx_by_tx_hash_responses.pop() + + async def GetPeggyDepositTxs( + self, request: exchange_explorer_pb.GetPeggyDepositTxsRequest, context=None, metadata=None + ): + return self.peggy_deposit_txs_responses.pop() + + async def GetPeggyWithdrawalTxs( + self, request: exchange_explorer_pb.GetPeggyWithdrawalTxsRequest, context=None, metadata=None + ): + return self.peggy_withdrawal_txs_responses.pop() + + async def GetIBCTransferTxs( + self, request: exchange_explorer_pb.GetIBCTransferTxsRequest, context=None, metadata=None + ): + return self.ibc_transfer_txs_responses.pop() + + async def GetWasmCodes(self, request: exchange_explorer_pb.GetWasmCodesRequest, context=None, metadata=None): + return self.wasm_codes_responses.pop() + + async def GetWasmCodeByID(self, request: exchange_explorer_pb.GetWasmCodeByIDRequest, context=None, metadata=None): + return self.wasm_code_by_id_responses.pop() + + async def GetWasmContracts( + self, request: exchange_explorer_pb.GetWasmContractsRequest, context=None, metadata=None + ): + return self.wasm_contracts_responses.pop() + + async def GetWasmContractByAddress( + self, request: exchange_explorer_pb.GetWasmContractByAddressRequest, context=None, metadata=None + ): + return self.wasm_contract_by_address_responses.pop() + + async def GetCw20Balance(self, request: exchange_explorer_pb.GetCw20BalanceRequest, context=None, metadata=None): + return self.cw20_balance_responses.pop() + + async def Relayers(self, request: exchange_explorer_pb.RelayersRequest, context=None, metadata=None): + return self.relayers_responses.pop() + + async def GetBankTransfers( + self, request: exchange_explorer_pb.GetBankTransfersRequest, context=None, metadata=None + ): + return self.bank_transfers_responses.pop() + + async def StreamTxs(self, request: exchange_explorer_pb.StreamTxsRequest, context=None, metadata=None): + for event in self.stream_txs_responses: + yield event + + async def StreamBlocks(self, request: exchange_explorer_pb.StreamBlocksRequest, context=None, metadata=None): + for event in self.stream_blocks_responses: + yield event diff --git a/tests/client/indexer/configurable_insurance_query_servicer.py b/tests/client/indexer/configurable_insurance_query_servicer.py new file mode 100644 index 00000000..aa4e0491 --- /dev/null +++ b/tests/client/indexer/configurable_insurance_query_servicer.py @@ -0,0 +1,19 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_insurance_rpc_pb2 as exchange_insurance_pb, + injective_insurance_rpc_pb2_grpc as exchange_insurance_grpc, +) + + +class ConfigurableInsuranceQueryServicer(exchange_insurance_grpc.InjectiveInsuranceRPCServicer): + def __init__(self): + super().__init__() + self.funds_responses = deque() + self.redemptions_responses = deque() + + async def Funds(self, request: exchange_insurance_pb.FundsRequest, context=None, metadata=None): + return self.funds_responses.pop() + + async def Redemptions(self, request: exchange_insurance_pb.RedemptionsRequest, context=None, metadata=None): + return self.redemptions_responses.pop() diff --git a/tests/client/indexer/configurable_meta_query_servicer.py b/tests/client/indexer/configurable_meta_query_servicer.py new file mode 100644 index 00000000..8c8997d0 --- /dev/null +++ b/tests/client/indexer/configurable_meta_query_servicer.py @@ -0,0 +1,28 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_meta_rpc_pb2 as exchange_meta_pb, + injective_meta_rpc_pb2_grpc as exchange_meta_grpc, +) + + +class ConfigurableMetaQueryServicer(exchange_meta_grpc.InjectiveMetaRPCServicer): + def __init__(self): + super().__init__() + self.ping_responses = deque() + self.version_responses = deque() + self.info_responses = deque() + self.stream_keepalive_responses = deque() + + async def Ping(self, request: exchange_meta_pb.PingRequest, context=None, metadata=None): + return self.ping_responses.pop() + + async def Version(self, request: exchange_meta_pb.VersionRequest, context=None, metadata=None): + return self.version_responses.pop() + + async def Info(self, request: exchange_meta_pb.InfoRequest, context=None, metadata=None): + return self.info_responses.pop() + + async def StreamKeepalive(self, request: exchange_meta_pb.StreamKeepaliveRequest, context=None, metadata=None): + for event in self.stream_keepalive_responses: + yield event diff --git a/tests/client/indexer/configurable_oracle_query_servicer.py b/tests/client/indexer/configurable_oracle_query_servicer.py new file mode 100644 index 00000000..c7c06820 --- /dev/null +++ b/tests/client/indexer/configurable_oracle_query_servicer.py @@ -0,0 +1,31 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_oracle_rpc_pb2 as exchange_oracle_pb, + injective_oracle_rpc_pb2_grpc as exchange_oracle_grpc, +) + + +class ConfigurableOracleQueryServicer(exchange_oracle_grpc.InjectiveOracleRPCServicer): + def __init__(self): + super().__init__() + self.oracle_list_responses = deque() + self.price_responses = deque() + self.stream_prices_responses = deque() + self.stream_prices_by_markets_responses = deque() + + async def OracleList(self, request: exchange_oracle_pb.OracleListRequest, context=None, metadata=None): + return self.oracle_list_responses.pop() + + async def Price(self, request: exchange_oracle_pb.PriceRequest, context=None, metadata=None): + return self.price_responses.pop() + + async def StreamPrices(self, request: exchange_oracle_pb.StreamPricesRequest, context=None, metadata=None): + for event in self.stream_prices_responses: + yield event + + async def StreamPricesByMarkets( + self, request: exchange_oracle_pb.StreamPricesByMarketsRequest, context=None, metadata=None + ): + for event in self.stream_prices_by_markets_responses: + yield event diff --git a/tests/client/indexer/configurable_portfolio_query_servicer.py b/tests/client/indexer/configurable_portfolio_query_servicer.py new file mode 100644 index 00000000..50d329ca --- /dev/null +++ b/tests/client/indexer/configurable_portfolio_query_servicer.py @@ -0,0 +1,24 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_portfolio_rpc_pb2 as exchange_portfolio_pb, + injective_portfolio_rpc_pb2_grpc as exchange_portfolio_grpc, +) + + +class ConfigurablePortfolioQueryServicer(exchange_portfolio_grpc.InjectivePortfolioRPCServicer): + def __init__(self): + super().__init__() + self.account_portfolio_responses = deque() + self.stream_account_portfolio_responses = deque() + + async def AccountPortfolio( + self, request: exchange_portfolio_pb.AccountPortfolioRequest, context=None, metadata=None + ): + return self.account_portfolio_responses.pop() + + async def StreamAccountPortfolio( + self, request: exchange_portfolio_pb.StreamAccountPortfolioRequest, context=None, metadata=None + ): + for event in self.stream_account_portfolio_responses: + yield event diff --git a/tests/client/indexer/configurable_spot_query_servicer.py b/tests/client/indexer/configurable_spot_query_servicer.py new file mode 100644 index 00000000..7104d4da --- /dev/null +++ b/tests/client/indexer/configurable_spot_query_servicer.py @@ -0,0 +1,90 @@ +from collections import deque + +from pyinjective.proto.exchange import ( + injective_spot_exchange_rpc_pb2 as exchange_spot_pb, + injective_spot_exchange_rpc_pb2_grpc as exchange_spot_grpc, +) + + +class ConfigurableSpotQueryServicer(exchange_spot_grpc.InjectiveSpotExchangeRPCServicer): + def __init__(self): + super().__init__() + self.markets_responses = deque() + self.market_responses = deque() + self.orderbook_v2_responses = deque() + self.orderbooks_v2_responses = deque() + self.orders_responses = deque() + self.trades_responses = deque() + self.subaccount_orders_list_responses = deque() + self.subaccount_trades_list_responses = deque() + self.orders_history_responses = deque() + self.atomic_swap_history_responses = deque() + + self.stream_markets_responses = deque() + self.stream_orderbook_v2_responses = deque() + self.stream_orderbook_update_responses = deque() + self.stream_orders_responses = deque() + self.stream_trades_responses = deque() + self.stream_orders_history_responses = deque() + + async def Markets(self, request: exchange_spot_pb.MarketsRequest, context=None, metadata=None): + return self.markets_responses.pop() + + async def Market(self, request: exchange_spot_pb.MarketRequest, context=None, metadata=None): + return self.market_responses.pop() + + async def OrderbookV2(self, request: exchange_spot_pb.OrderbookV2Request, context=None, metadata=None): + return self.orderbook_v2_responses.pop() + + async def OrderbooksV2(self, request: exchange_spot_pb.OrderbooksV2Request, context=None, metadata=None): + return self.orderbooks_v2_responses.pop() + + async def Orders(self, request: exchange_spot_pb.OrdersRequest, context=None, metadata=None): + return self.orders_responses.pop() + + async def Trades(self, request: exchange_spot_pb.TradesRequest, context=None, metadata=None): + return self.trades_responses.pop() + + async def SubaccountOrdersList( + self, request: exchange_spot_pb.SubaccountOrdersListRequest, context=None, metadata=None + ): + return self.subaccount_orders_list_responses.pop() + + async def SubaccountTradesList( + self, request: exchange_spot_pb.SubaccountTradesListRequest, context=None, metadata=None + ): + return self.subaccount_trades_list_responses.pop() + + async def OrdersHistory(self, request: exchange_spot_pb.OrdersHistoryRequest, context=None, metadata=None): + return self.orders_history_responses.pop() + + async def AtomicSwapHistory(self, request: exchange_spot_pb.AtomicSwapHistoryRequest, context=None, metadata=None): + return self.atomic_swap_history_responses.pop() + + async def StreamMarkets(self, request: exchange_spot_pb.StreamMarketsRequest, context=None, metadata=None): + for event in self.stream_markets_responses: + yield event + + async def StreamOrderbookV2(self, request: exchange_spot_pb.StreamOrderbookV2Request, context=None, metadata=None): + for event in self.stream_orderbook_v2_responses: + yield event + + async def StreamOrderbookUpdate( + self, request: exchange_spot_pb.StreamOrderbookUpdateRequest, context=None, metadata=None + ): + for event in self.stream_orderbook_update_responses: + yield event + + async def StreamOrders(self, request: exchange_spot_pb.StreamOrdersRequest, context=None, metadata=None): + for event in self.stream_orders_responses: + yield event + + async def StreamTrades(self, request: exchange_spot_pb.StreamTradesRequest, context=None, metadata=None): + for event in self.stream_trades_responses: + yield event + + async def StreamOrdersHistory( + self, request: exchange_spot_pb.StreamOrdersHistoryRequest, context=None, metadata=None + ): + for event in self.stream_orders_history_responses: + yield event diff --git a/tests/client/indexer/grpc/__init__.py b/tests/client/indexer/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/indexer/grpc/test_indexer_grpc_account_api.py b/tests/client/indexer/grpc/test_indexer_grpc_account_api.py new file mode 100644 index 00000000..8466c29b --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_account_api.py @@ -0,0 +1,396 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_account_api import IndexerGrpcAccountApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_pb +from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer + + +@pytest.fixture +def account_servicer(): + return ConfigurableAccountQueryServicer() + + +class TestIndexerGrpcAccountApi: + @pytest.mark.asyncio + async def test_fetch_portfolio( + self, + account_servicer, + ): + subaccount_portfolio = exchange_accounts_pb.SubaccountPortfolio( + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000006", + available_balance="1", + locked_balance="2", + unrealized_pnl="3", + ) + portfolio = exchange_accounts_pb.AccountPortfolio( + portfolio_value="173706.418", + available_balance="99.8782", + locked_balance="186055.7038", + unrealized_pnl="-12449.1635", + subaccounts=[subaccount_portfolio], + ) + account_servicer.portfolio_responses.append(exchange_accounts_pb.PortfolioResponse(portfolio=portfolio)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = account_servicer + + account_address = "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku" + result_portfolio = await api.fetch_portfolio(account_address=account_address) + expected_portfolio = { + "portfolio": { + "portfolioValue": portfolio.portfolio_value, + "availableBalance": portfolio.available_balance, + "lockedBalance": portfolio.locked_balance, + "unrealizedPnl": portfolio.unrealized_pnl, + "subaccounts": [ + { + "subaccountId": subaccount_portfolio.subaccount_id, + "availableBalance": subaccount_portfolio.available_balance, + "lockedBalance": subaccount_portfolio.locked_balance, + "unrealizedPnl": subaccount_portfolio.unrealized_pnl, + }, + ], + } + } + + assert expected_portfolio == result_portfolio + + @pytest.mark.asyncio + async def test_order_states( + self, + account_servicer, + ): + order_state = exchange_accounts_pb.OrderStateRecord( + order_hash="0xce0d9b701f77cd6ddfda5dd3a4fe7b2d53ba83e5d6c054fb2e9e886200b7b7bb", + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000006", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + order_type="buy_po", + order_side="buy", + state="canceled", + quantity_filled="0", + quantity_remaining="1000000000000000", + created_at=1669998526840, + updated_at=1670919410587, + ) + account_servicer.order_states_responses.append( + exchange_accounts_pb.OrderStatesResponse(spot_order_states=[order_state]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = account_servicer + + result_order_states = await api.fetch_order_states(spot_order_hashes=[order_state.order_hash]) + expected_order_states = { + "spotOrderStates": [ + { + "orderHash": order_state.order_hash, + "subaccountId": order_state.subaccount_id, + "marketId": order_state.market_id, + "orderType": order_state.order_type, + "orderSide": order_state.order_side, + "state": order_state.state, + "quantityFilled": order_state.quantity_filled, + "quantityRemaining": order_state.quantity_remaining, + "createdAt": str(order_state.created_at), + "updatedAt": str(order_state.updated_at), + } + ], + "derivativeOrderStates": [], + } + + assert result_order_states == expected_order_states + + @pytest.mark.asyncio + async def test_subaccounts_list( + self, + account_servicer, + ): + account_servicer.subaccounts_list_responses.append( + exchange_accounts_pb.SubaccountsListResponse( + subaccounts=["0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000006"] + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = account_servicer + + result_subaccounts_list = await api.fetch_subaccounts_list(address="testAddress") + expected_subaccounts_list = { + "subaccounts": ["0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000006"] + } + + assert result_subaccounts_list == expected_subaccounts_list + + @pytest.mark.asyncio + async def test_subaccount_balances_list( + self, + account_servicer, + ): + deposit = exchange_accounts_pb.SubaccountDeposit( + total_balance="20", + available_balance="10", + ) + balance = exchange_accounts_pb.SubaccountBalance( + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000", + account_address="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + denom="inj", + deposit=deposit, + ) + account_servicer.subaccount_balances_list_responses.append( + exchange_accounts_pb.SubaccountBalancesListResponse(balances=[balance]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = account_servicer + + result_subaccount_balances_list = await api.fetch_subaccount_balances_list( + subaccount_id=balance.subaccount_id, denoms=[balance.denom] + ) + expected_subaccount_balances_list = { + "balances": [ + { + "subaccountId": balance.subaccount_id, + "accountAddress": balance.account_address, + "denom": balance.denom, + "deposit": { + "totalBalance": deposit.total_balance, + "availableBalance": deposit.available_balance, + }, + }, + ] + } + + assert result_subaccount_balances_list == expected_subaccount_balances_list + + @pytest.mark.asyncio + async def test_subaccount_balance( + self, + account_servicer, + ): + deposit = exchange_accounts_pb.SubaccountDeposit( + total_balance="20", + available_balance="10", + ) + balance = exchange_accounts_pb.SubaccountBalance( + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000", + account_address="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + denom="inj", + deposit=deposit, + ) + account_servicer.subaccount_balance_responses.append( + exchange_accounts_pb.SubaccountBalanceEndpointResponse(balance=balance) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = account_servicer + + result_subaccount_balance = await api.fetch_subaccount_balance( + subaccount_id=balance.subaccount_id, + denom=balance.denom, + ) + expected_subaccount_balance = { + "balance": { + "subaccountId": balance.subaccount_id, + "accountAddress": balance.account_address, + "denom": balance.denom, + "deposit": { + "totalBalance": deposit.total_balance, + "availableBalance": deposit.available_balance, + }, + }, + } + + assert result_subaccount_balance == expected_subaccount_balance + + @pytest.mark.asyncio + async def test_subaccount_history( + self, + account_servicer, + ): + transfer = exchange_accounts_pb.SubaccountBalanceTransfer( + transfer_type="deposit", + src_account_address="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + dst_subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000", + amount=exchange_accounts_pb.CosmosCoin( + denom="inj", + amount="2000000000000000000", + ), + executed_at=1665117493543, + src_subaccount_id="", + dst_account_address="", + ) + + paging = exchange_accounts_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + account_servicer.subaccount_history_responses.append( + exchange_accounts_pb.SubaccountHistoryResponse(transfers=[transfer], paging=paging) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = account_servicer + + result_subaccount_history = await api.fetch_subaccount_history( + subaccount_id=transfer.dst_subaccount_id, + denom=transfer.amount.denom, + transfer_types=[transfer.transfer_type], + pagination=PaginationOption( + skip=0, + limit=100, + end_time=1699744939364, + ), + ) + expected_subaccount_history = { + "transfers": [ + { + "transferType": transfer.transfer_type, + "srcAccountAddress": transfer.src_account_address, + "dstSubaccountId": transfer.dst_subaccount_id, + "amount": {"denom": transfer.amount.denom, "amount": "2000000000000000000"}, + "executedAt": str(transfer.executed_at), + "srcSubaccountId": transfer.src_subaccount_id, + "dstAccountAddress": transfer.dst_account_address, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_subaccount_history == expected_subaccount_history + + @pytest.mark.asyncio + async def test_subaccount_order_summary( + self, + account_servicer, + ): + account_servicer.subaccount_order_summary_responses.append( + exchange_accounts_pb.SubaccountOrderSummaryResponse(spot_orders_total=0, derivative_orders_total=20) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = account_servicer + + result_subaccount_order_summary = await api.fetch_subaccount_order_summary( + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + order_direction="buy", + ) + expected_subaccount_order_summary = {"derivativeOrdersTotal": "20", "spotOrdersTotal": "0"} + + assert result_subaccount_order_summary == expected_subaccount_order_summary + + @pytest.mark.asyncio + async def test_fetch_rewards( + self, + account_servicer, + ): + single_reward = exchange_accounts_pb.Coin( + denom="inj", + amount="2000000000000000000", + ) + + reward = exchange_accounts_pb.Reward( + account_address="inj1qra8c03h70y36j85dpvtj05juxe9z7acuvz6vg", + rewards=[single_reward], + distributed_at=1672218001897, + ) + + account_servicer.rewards_responses.append(exchange_accounts_pb.RewardsResponse(rewards=[reward])) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = account_servicer + + result_rewards = await api.fetch_rewards(account_address=reward.account_address, epoch=1) + expected_rewards = { + "rewards": [ + { + "accountAddress": reward.account_address, + "rewards": [ + { + "denom": single_reward.denom, + "amount": single_reward.amount, + } + ], + "distributedAt": str(reward.distributed_at), + } + ] + } + + assert result_rewards == expected_rewards + + @pytest.mark.asyncio + async def test_fetch_rewards( + self, + account_servicer, + ): + single_reward = exchange_accounts_pb.Coin( + denom="inj", + amount="2000000000000000000", + ) + + reward = exchange_accounts_pb.Reward( + account_address="inj1qra8c03h70y36j85dpvtj05juxe9z7acuvz6vg", + rewards=[single_reward], + distributed_at=1672218001897, + ) + + account_servicer.rewards_responses.append(exchange_accounts_pb.RewardsResponse(rewards=[reward])) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = account_servicer + + result_rewards = await api.fetch_rewards(account_address=reward.account_address, epoch=1) + expected_rewards = { + "rewards": [ + { + "accountAddress": reward.account_address, + "rewards": [ + { + "denom": single_reward.denom, + "amount": single_reward.amount, + } + ], + "distributedAt": str(reward.distributed_at), + } + ] + } + + assert result_rewards == expected_rewards + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/grpc/test_indexer_grpc_auction_api.py b/tests/client/indexer/grpc/test_indexer_grpc_auction_api.py new file mode 100644 index 00000000..8be68ddb --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_auction_api.py @@ -0,0 +1,121 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_auction_api import IndexerGrpcAuctionApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_auction_rpc_pb2 as exchange_auction_pb +from tests.client.indexer.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer + + +@pytest.fixture +def auction_servicer(): + return ConfigurableAuctionQueryServicer() + + +class TestIndexerGrpcAuctionApi: + @pytest.mark.asyncio + async def test_fetch_auction( + self, + auction_servicer, + ): + coin = exchange_auction_pb.Coin( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + amount="2322098", + ) + auction = exchange_auction_pb.Auction( + winner="inj1uyk56r3xdcf60jwrmn7p9rgla9dc4gam56ajrq", + basket=[coin], + winning_bid_amount="2000000000000000000", + round=31, + end_timestamp=1676013187000, + updated_at=1677075140258, + ) + + bid = exchange_auction_pb.Bid( + bidder="inj1pdxq82m20fzkjn2th2mm5jp7t5ex6j6klf9cs5", + amount="1000000000000000000", + timestamp=1675426622603, + ) + + auction_servicer.auction_endpoint_responses.append( + exchange_auction_pb.AuctionEndpointResponse( + auction=auction, + bids=[bid], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auction_servicer + + result_auction = await api.fetch_auction(round=auction.round) + expected_auction = { + "auction": { + "winner": auction.winner, + "basket": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "winningBidAmount": auction.winning_bid_amount, + "round": str(auction.round), + "endTimestamp": str(auction.end_timestamp), + "updatedAt": str(auction.updated_at), + }, + "bids": [{"amount": bid.amount, "bidder": bid.bidder, "timestamp": str(bid.timestamp)}], + } + + assert result_auction == expected_auction + + @pytest.mark.asyncio + async def test_fetch_auctions( + self, + auction_servicer, + ): + coin = exchange_auction_pb.Coin( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + amount="2322098", + ) + auction = exchange_auction_pb.Auction( + winner="inj1uyk56r3xdcf60jwrmn7p9rgla9dc4gam56ajrq", + basket=[coin], + winning_bid_amount="2000000000000000000", + round=31, + end_timestamp=1676013187000, + updated_at=1677075140258, + ) + + auction_servicer.auctions_responses.append(exchange_auction_pb.AuctionsResponse(auctions=[auction])) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAuctionApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auction_servicer + + result_auctions = await api.fetch_auctions() + expected_auctions = { + "auctions": [ + { + "winner": auction.winner, + "basket": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "winningBidAmount": auction.winning_bid_amount, + "round": str(auction.round), + "endTimestamp": str(auction.end_timestamp), + "updatedAt": str(auction.updated_at), + } + ] + } + + assert result_auctions == expected_auctions + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/grpc/test_indexer_grpc_derivative_api.py b/tests/client/indexer/grpc/test_indexer_grpc_derivative_api.py new file mode 100644 index 00000000..4ba28c2b --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_derivative_api.py @@ -0,0 +1,1236 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_derivative_api import IndexerGrpcDerivativeApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb +from tests.client.indexer.configurable_derivative_query_servicer import ConfigurableDerivativeQueryServicer + + +@pytest.fixture +def derivative_servicer(): + return ConfigurableDerivativeQueryServicer() + + +class TestIndexerGrpcDerivativeApi: + @pytest.mark.asyncio + async def test_fetch_markets( + self, + derivative_servicer, + ): + quote_token_meta = exchange_derivative_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + perpetual_market_info = exchange_derivative_pb.PerpetualMarketInfo( + hourly_funding_rate_cap="0.000625", + hourly_interest_rate="0.00000416666", + next_funding_timestamp=1700064000, + funding_interval=3600, + ) + perpetual_market_funding = exchange_derivative_pb.PerpetualMarketFunding( + cumulative_funding="-82680.076492986572881307", + cumulative_price="-78.41752505919454668", + last_timestamp=1700004260, + ) + + market = exchange_derivative_pb.DerivativeMarketInfo( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + market_status="active", + ticker="INJ/USDT PERP", + oracle_base="0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3", + oracle_quote="0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588", + oracle_type="pyth", + oracle_scale_factor=6, + initial_margin_ratio="0.05", + maintenance_margin_ratio="0.02", + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + is_perpetual=True, + min_price_tick_size="100", + min_quantity_tick_size="0.0001", + perpetual_market_info=perpetual_market_info, + perpetual_market_funding=perpetual_market_funding, + ) + + derivative_servicer.markets_responses.append( + exchange_derivative_pb.MarketsResponse( + markets=[market], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_markets = await api.fetch_markets( + market_statuses=[market.market_status], + quote_denom=market.quote_denom, + ) + expected_markets = { + "markets": [ + { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "oracleBase": market.oracle_base, + "oracleQuote": market.oracle_quote, + "oracleType": market.oracle_type, + "oracleScaleFactor": market.oracle_scale_factor, + "initialMarginRatio": market.initial_margin_ratio, + "maintenanceMarginRatio": market.maintenance_margin_ratio, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "makerFeeRate": market.maker_fee_rate, + "takerFeeRate": market.taker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "isPerpetual": market.is_perpetual, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + "perpetualMarketInfo": { + "hourlyFundingRateCap": perpetual_market_info.hourly_funding_rate_cap, + "hourlyInterestRate": str(perpetual_market_info.hourly_interest_rate), + "nextFundingTimestamp": str(perpetual_market_info.next_funding_timestamp), + "fundingInterval": str(perpetual_market_info.funding_interval), + }, + "perpetualMarketFunding": { + "cumulativeFunding": perpetual_market_funding.cumulative_funding, + "cumulativePrice": perpetual_market_funding.cumulative_price, + "lastTimestamp": str(perpetual_market_funding.last_timestamp), + }, + } + ] + } + + assert result_markets == expected_markets + + @pytest.mark.asyncio + async def test_fetch_market( + self, + derivative_servicer, + ): + quote_token_meta = exchange_derivative_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + perpetual_market_info = exchange_derivative_pb.PerpetualMarketInfo( + hourly_funding_rate_cap="0.000625", + hourly_interest_rate="0.00000416666", + next_funding_timestamp=1700064000, + funding_interval=3600, + ) + perpetual_market_funding = exchange_derivative_pb.PerpetualMarketFunding( + cumulative_funding="-82680.076492986572881307", + cumulative_price="-78.41752505919454668", + last_timestamp=1700004260, + ) + + market = exchange_derivative_pb.DerivativeMarketInfo( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + market_status="active", + ticker="INJ/USDT PERP", + oracle_base="0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3", + oracle_quote="0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588", + oracle_type="pyth", + oracle_scale_factor=6, + initial_margin_ratio="0.05", + maintenance_margin_ratio="0.02", + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + is_perpetual=True, + min_price_tick_size="100", + min_quantity_tick_size="0.0001", + perpetual_market_info=perpetual_market_info, + perpetual_market_funding=perpetual_market_funding, + ) + + derivative_servicer.market_responses.append( + exchange_derivative_pb.MarketResponse( + market=market, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_market = await api.fetch_market(market_id=market.market_id) + expected_market = { + "market": { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "oracleBase": market.oracle_base, + "oracleQuote": market.oracle_quote, + "oracleType": market.oracle_type, + "oracleScaleFactor": market.oracle_scale_factor, + "initialMarginRatio": market.initial_margin_ratio, + "maintenanceMarginRatio": market.maintenance_margin_ratio, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "makerFeeRate": market.maker_fee_rate, + "takerFeeRate": market.taker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "isPerpetual": market.is_perpetual, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + "perpetualMarketInfo": { + "hourlyFundingRateCap": perpetual_market_info.hourly_funding_rate_cap, + "hourlyInterestRate": str(perpetual_market_info.hourly_interest_rate), + "nextFundingTimestamp": str(perpetual_market_info.next_funding_timestamp), + "fundingInterval": str(perpetual_market_info.funding_interval), + }, + "perpetualMarketFunding": { + "cumulativeFunding": perpetual_market_funding.cumulative_funding, + "cumulativePrice": perpetual_market_funding.cumulative_price, + "lastTimestamp": str(perpetual_market_funding.last_timestamp), + }, + } + } + + assert result_market == expected_market + + @pytest.mark.asyncio + async def test_fetch_binary_options_markets( + self, + derivative_servicer, + ): + quote_token_meta = exchange_derivative_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + + market = exchange_derivative_pb.BinaryOptionsMarketInfo( + market_id="0xaea3b04b88ad7972b6afcd676791eaa1872a8cf5ab7c5be93da755fd7fac9196", + market_status="active", + ticker="Long-Lived 7/8/22 1", + oracle_symbol="Long-Lived 7/8/22 1", + oracle_provider="Frontrunner", + oracle_type="provider", + oracle_scale_factor=6, + expiration_timestamp=1657311861, + settlement_timestamp=1657311862, + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + min_price_tick_size="0.01", + min_quantity_tick_size="1", + settlement_price="1000", + ) + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.binary_options_markets_responses.append( + exchange_derivative_pb.BinaryOptionsMarketsResponse(markets=[market], paging=paging) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_markets = await api.fetch_binary_options_markets( + market_status=market.market_status, + quote_denom=market.quote_denom, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_markets = { + "markets": [ + { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "oracleSymbol": market.oracle_symbol, + "oracleProvider": market.oracle_provider, + "oracleType": market.oracle_type, + "oracleScaleFactor": market.oracle_scale_factor, + "expirationTimestamp": str(market.expiration_timestamp), + "settlementTimestamp": str(market.settlement_timestamp), + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "makerFeeRate": market.maker_fee_rate, + "takerFeeRate": market.taker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + "settlementPrice": market.settlement_price, + } + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_markets == expected_markets + + @pytest.mark.asyncio + async def test_fetch_binary_options_market( + self, + derivative_servicer, + ): + quote_token_meta = exchange_derivative_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + + market = exchange_derivative_pb.BinaryOptionsMarketInfo( + market_id="0xaea3b04b88ad7972b6afcd676791eaa1872a8cf5ab7c5be93da755fd7fac9196", + market_status="active", + ticker="Long-Lived 7/8/22 1", + oracle_symbol="Long-Lived 7/8/22 1", + oracle_provider="Frontrunner", + oracle_type="provider", + oracle_scale_factor=6, + expiration_timestamp=1657311861, + settlement_timestamp=1657311862, + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + min_price_tick_size="0.01", + min_quantity_tick_size="1", + settlement_price="1000", + ) + + derivative_servicer.binary_options_market_responses.append( + exchange_derivative_pb.BinaryOptionsMarketResponse(market=market) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_markets = await api.fetch_binary_options_market(market_id=market.market_id) + expected_markets = { + "market": { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "oracleSymbol": market.oracle_symbol, + "oracleProvider": market.oracle_provider, + "oracleType": market.oracle_type, + "oracleScaleFactor": market.oracle_scale_factor, + "expirationTimestamp": str(market.expiration_timestamp), + "settlementTimestamp": str(market.settlement_timestamp), + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "makerFeeRate": market.maker_fee_rate, + "takerFeeRate": market.taker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + "settlementPrice": market.settlement_price, + } + } + + assert result_markets == expected_markets + + @pytest.mark.asyncio + async def test_fetch_orderbook_v2( + self, + derivative_servicer, + ): + buy = exchange_derivative_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_derivative_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_derivative_pb.DerivativeLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + derivative_servicer.orderbook_v2_responses.append( + exchange_derivative_pb.OrderbookV2Response( + orderbook=orderbook, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orderbook = await api.fetch_orderbook_v2( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" + ) + expected_orderbook = { + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + } + } + + assert result_orderbook == expected_orderbook + + @pytest.mark.asyncio + async def test_fetch_orderbooks_v2( + self, + derivative_servicer, + ): + buy = exchange_derivative_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_derivative_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_derivative_pb.DerivativeLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + single_orderbook = exchange_derivative_pb.SingleDerivativeLimitOrderbookV2( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + orderbook=orderbook, + ) + + derivative_servicer.orderbooks_v2_responses.append( + exchange_derivative_pb.OrderbooksV2Response( + orderbooks=[single_orderbook], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orderbook = await api.fetch_orderbooks_v2(market_ids=[single_orderbook.market_id]) + expected_orderbook = { + "orderbooks": [ + { + "marketId": single_orderbook.market_id, + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + }, + } + ] + } + + assert result_orderbook == expected_orderbook + + @pytest.mark.asyncio + async def test_fetch_orders( + self, + derivative_servicer, + ): + order = exchange_derivative_pb.DerivativeLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + is_reduce_only=False, + margin="2280000000", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + order_number=0, + order_type="", + is_conditional=False, + trigger_at=0, + placed_order_hash="", + execution_type="", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.orders_responses.append( + exchange_derivative_pb.OrdersResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_orders( + market_ids=[order.market_id], + order_side=order.order_side, + subaccount_id=order.subaccount_id, + is_conditional="true" if order.is_conditional else "false", + order_type=order.order_type, + include_inactive=True, + subaccount_total_orders=True, + trade_id="7959737_3_0", + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "isReduceOnly": order.is_reduce_only, + "margin": order.margin, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "orderNumber": str(order.order_number), + "orderType": order.order_type, + "isConditional": order.is_conditional, + "triggerAt": str(order.trigger_at), + "placedOrderHash": order.placed_order_hash, + "executionType": order.execution_type, + "txHash": order.tx_hash, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_positions( + self, + derivative_servicer, + ): + position = exchange_derivative_pb.DerivativePosition( + ticker="INJ/USDT PERP", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x1383dabde57e5aed55960ee43e158ae7118057d3000000000000000000000000", + direction="short", + quantity="0.070294765766186502", + entry_price="15980281.340438795311756847", + margin="561065.540974", + liquidation_price="23492052.224777", + mark_price="16197000", + aggregate_reduce_only_quantity="0", + updated_at=1700161202147, + created_at=-62135596800000, + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.positions_responses.append( + exchange_derivative_pb.PositionsResponse( + positions=[position], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_positions( + market_ids=[position.market_id], + subaccount_id=position.subaccount_id, + direction=position.direction, + subaccount_total_positions=True, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_orders = { + "positions": [ + { + "ticker": position.ticker, + "marketId": position.market_id, + "subaccountId": position.subaccount_id, + "direction": position.direction, + "quantity": position.quantity, + "entryPrice": position.entry_price, + "margin": position.margin, + "liquidationPrice": position.liquidation_price, + "markPrice": position.mark_price, + "aggregateReduceOnlyQuantity": position.aggregate_reduce_only_quantity, + "createdAt": str(position.created_at), + "updatedAt": str(position.updated_at), + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_liquidable_positions( + self, + derivative_servicer, + ): + position = exchange_derivative_pb.DerivativePosition( + ticker="INJ/USDT PERP", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x1383dabde57e5aed55960ee43e158ae7118057d3000000000000000000000000", + direction="short", + quantity="0.070294765766186502", + entry_price="15980281.340438795311756847", + margin="561065.540974", + liquidation_price="23492052.224777", + mark_price="16197000", + aggregate_reduce_only_quantity="0", + updated_at=1700161202147, + created_at=-62135596800000, + ) + + derivative_servicer.liquidable_positions_responses.append( + exchange_derivative_pb.LiquidablePositionsResponse(positions=[position]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_liquidable_positions( + market_id=position.market_id, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_orders = { + "positions": [ + { + "ticker": position.ticker, + "marketId": position.market_id, + "subaccountId": position.subaccount_id, + "direction": position.direction, + "quantity": position.quantity, + "entryPrice": position.entry_price, + "margin": position.margin, + "liquidationPrice": position.liquidation_price, + "markPrice": position.mark_price, + "aggregateReduceOnlyQuantity": position.aggregate_reduce_only_quantity, + "createdAt": str(position.created_at), + "updatedAt": str(position.updated_at), + }, + ] + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_funding_payments( + self, + derivative_servicer, + ): + payment = exchange_derivative_pb.FundingPayment( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x1383dabde57e5aed55960ee43e158ae7118057d3000000000000000000000000", + amount="0.018466", + timestamp=1700186400645, + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.funding_payments_responses.append( + exchange_derivative_pb.FundingPaymentsResponse( + payments=[payment], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_funding_payments( + market_ids=[payment.market_id], + subaccount_id=payment.subaccount_id, + pagination=PaginationOption( + skip=0, + limit=100, + end_time=1699744939364, + ), + ) + expected_orders = { + "payments": [ + { + "marketId": payment.market_id, + "subaccountId": payment.subaccount_id, + "amount": payment.amount, + "timestamp": str(payment.timestamp), + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_funding_rates( + self, + derivative_servicer, + ): + funding_rate = exchange_derivative_pb.FundingRate( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + rate="0.000004", + timestamp=1700186400645, + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.funding_rates_responses.append( + exchange_derivative_pb.FundingRatesResponse( + funding_rates=[funding_rate], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_funding_rates( + market_id=funding_rate.market_id, + pagination=PaginationOption( + skip=0, + limit=100, + end_time=1699744939364, + ), + ) + expected_orders = { + "fundingRates": [ + { + "marketId": funding_rate.market_id, + "rate": funding_rate.rate, + "timestamp": str(funding_rate.timestamp), + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_trades( + self, + derivative_servicer, + ): + position_delta = exchange_derivative_pb.PositionDelta( + trade_direction="buy", + execution_price="13945600", + execution_quantity="5", + execution_margin="69728000", + ) + + trade = exchange_derivative_pb.DerivativeTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + trade_execution_type="limitMatchNewOrder", + is_liquidation=False, + position_delta=position_delta, + payout="0", + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.trades_responses.append( + exchange_derivative_pb.TradesResponse( + trades=[trade], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_trades = await api.fetch_trades( + market_ids=[trade.market_id], + subaccount_ids=[trade.subaccount_id], + execution_side=trade.execution_side, + direction=position_delta.trade_direction, + execution_types=[trade.trade_execution_type], + trade_id=trade.trade_id, + account_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + cid=trade.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_trades = { + "trades": [ + { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "isLiquidation": trade.is_liquidation, + "positionDelta": { + "tradeDirection": position_delta.trade_direction, + "executionPrice": position_delta.execution_price, + "executionQuantity": position_delta.execution_quantity, + "executionMargin": position_delta.execution_margin, + }, + "payout": trade.payout, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_trades == expected_trades + + @pytest.mark.asyncio + async def test_fetch_subaccount_orders_list( + self, + derivative_servicer, + ): + order = exchange_derivative_pb.DerivativeLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + is_reduce_only=False, + margin="2280000000", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + order_number=0, + order_type="", + is_conditional=False, + trigger_at=0, + placed_order_hash="", + execution_type="", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.subaccount_orders_list_responses.append( + exchange_derivative_pb.SubaccountOrdersListResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_subaccount_orders_list( + subaccount_id=order.subaccount_id, + market_id=order.market_id, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "isReduceOnly": order.is_reduce_only, + "margin": order.margin, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "orderNumber": str(order.order_number), + "orderType": order.order_type, + "isConditional": order.is_conditional, + "triggerAt": str(order.trigger_at), + "placedOrderHash": order.placed_order_hash, + "executionType": order.execution_type, + "txHash": order.tx_hash, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_subaccount_trades_list( + self, + derivative_servicer, + ): + position_delta = exchange_derivative_pb.PositionDelta( + trade_direction="buy", + execution_price="13945600", + execution_quantity="5", + execution_margin="69728000", + ) + + trade = exchange_derivative_pb.DerivativeTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + trade_execution_type="limitMatchNewOrder", + is_liquidation=False, + position_delta=position_delta, + payout="0", + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + derivative_servicer.subaccount_trades_list_responses.append( + exchange_derivative_pb.SubaccountTradesListResponse( + trades=[trade], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_trades = await api.fetch_subaccount_trades_list( + subaccount_id=trade.subaccount_id, + market_id=trade.market_id, + execution_type=trade.trade_execution_type, + direction=position_delta.trade_direction, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_trades = { + "trades": [ + { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "isLiquidation": trade.is_liquidation, + "positionDelta": { + "tradeDirection": position_delta.trade_direction, + "executionPrice": position_delta.execution_price, + "executionQuantity": position_delta.execution_quantity, + "executionMargin": position_delta.execution_margin, + }, + "payout": trade.payout, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + ], + } + + assert result_trades == expected_trades + + @pytest.mark.asyncio + async def test_fetch_orders_history( + self, + derivative_servicer, + ): + order = exchange_derivative_pb.DerivativeOrderHistory( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + is_active=True, + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + execution_type="limit", + order_type="buy_po", + price="0.000000000017541", + trigger_price="0", + quantity="50955000000000000000", + filled_quantity="1000000000000000", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + is_reduce_only=False, + direction="buy", + is_conditional=False, + trigger_at=0, + placed_order_hash="", + margin="2280000000", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_derivative_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + derivative_servicer.orders_history_responses.append( + exchange_derivative_pb.OrdersHistoryResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + result_orders = await api.fetch_orders_history( + subaccount_id=order.subaccount_id, + market_ids=[order.market_id], + order_types=[order.order_type], + direction=order.direction, + is_conditional="true" if order.is_conditional else "false", + state=order.state, + execution_types=[order.execution_type], + trade_id="8662464_1_0", + active_markets_only=True, + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "marketId": order.market_id, + "isActive": order.is_active, + "subaccountId": order.subaccount_id, + "executionType": order.execution_type, + "orderType": order.order_type, + "price": order.price, + "triggerPrice": order.trigger_price, + "quantity": order.quantity, + "filledQuantity": order.filled_quantity, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "isReduceOnly": order.is_reduce_only, + "direction": order.direction, + "isConditional": order.is_conditional, + "triggerAt": str(order.trigger_at), + "placedOrderHash": order.placed_order_hash, + "margin": order.margin, + "txHash": order.tx_hash, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/grpc/test_indexer_grpc_explorer_api.py b/tests/client/indexer/grpc/test_indexer_grpc_explorer_api.py new file mode 100644 index 00000000..3572174d --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_explorer_api.py @@ -0,0 +1,1582 @@ +import base64 + +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_explorer_api import IndexerGrpcExplorerApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_explorer_rpc_pb2 as exchange_explorer_pb +from tests.client.indexer.configurable_explorer_query_servicer import ConfigurableExplorerQueryServicer + + +@pytest.fixture +def explorer_servicer(): + return ConfigurableExplorerQueryServicer() + + +class TestIndexerGrpcExplorerApi: + @pytest.mark.asyncio + async def test_fetch_account_txs( + self, + explorer_servicer, + ): + code = 5 + coin = exchange_explorer_pb.CosmosCoin( + denom="inj", + amount="200000000000000", + ) + gas_fee = exchange_explorer_pb.GasFee( + amount=[coin], gas_limit=400000, payer="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", granter="test granter" + ) + event = exchange_explorer_pb.Event(type="test event type", attributes={"first_attribute": "attribute 1"}) + signature = exchange_explorer_pb.Signature( + pubkey="02c33c539e2aea9f97137e8168f6e22f57b829876823fa04b878a2b7c2010465d9", + address="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", + sequence=223460, + signature="gFXPJ5QENzq9SUHshE8g++aRLIlRCRVcOsYq+EOr3T4QgAAs5bVHf8NhugBjJP9B+AfQjQNNneHXPF9dEp4Uehs=", + ) + claim_id = 100 + + tx_data = exchange_explorer_pb.TxDetailData( + id="test id", + block_number=18138926, + block_timestamp="2023-11-07 23:19:55.371 +0000 UTC", + hash="0x3790ade2bea6c8605851ec89fa968adf2a2037a5ecac11ca95e99260508a3b7e", + code=code, + data=b"\022&\n$/cosmos.bank.v1beta1.MsgSendResponse", + info="test info", + gas_wanted=400000, + gas_used=93696, + gas_fee=gas_fee, + codespace="test codespace", + events=[event], + tx_type="injective-web3", + messages=b'[{"type":"/cosmos.bank.v1beta1.MsgSend","value":{' + b'"from_address":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex",' + b'"to_address":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc",' + b'"amount":[{"denom":"factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth",' + b'"amount":"100000000000000000"}]}}]', + signatures=[signature], + memo="test memo", + tx_number=221429, + block_unix_timestamp=1699399195371, + error_log="", + logs=b'[{"msg_index":0,"events":[{"type":"message","attributes":[{"key":"action",' + b'"value":"/cosmos.bank.v1beta1.MsgSend"},{"key":"sender",' + b'"value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},{"key":"module","value":"bank"}]},' + b'{"type":"coin_spent","attributes":[{"key":"spender",' + b'"value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},{"key":"amount",' + b'"value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}]},' + b'{"type":"coin_received","attributes":[{"key":"receiver",' + b'"value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},{"key":"amount",' + b'"value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}]},' + b'{"type":"transfer","attributes":[{"key":"recipient",' + b'"value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"message","attributes":[{"key":"sender",' + b'"value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"}]}]}]', + claim_ids=[claim_id], + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.account_txs_responses.append( + exchange_explorer_pb.GetAccountTxsResponse( + data=[tx_data], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_txs = await api.fetch_account_txs( + address="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", + before=221439, + after=221419, + message_type="cosmos.bank.v1beta1.MsgSend", + module="bank", + from_number=221419, + to_number=221439, + status="status", + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_txs = { + "data": [ + { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "code": tx_data.code, + "data": base64.b64encode(tx_data.data).decode(), + "info": tx_data.info, + "gasWanted": str(tx_data.gas_wanted), + "gasUsed": str(tx_data.gas_used), + "gasFee": { + "amount": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "gasLimit": str(gas_fee.gas_limit), + "payer": gas_fee.payer, + "granter": gas_fee.granter, + }, + "codespace": tx_data.codespace, + "events": [ + { + "type": event.type, + "attributes": event.attributes, + } + ], + "txType": tx_data.tx_type, + "messages": base64.b64encode(tx_data.messages).decode(), + "signatures": [ + { + "pubkey": signature.pubkey, + "address": signature.address, + "sequence": str(signature.sequence), + "signature": signature.signature, + } + ], + "memo": tx_data.memo, + "txNumber": str(tx_data.tx_number), + "blockUnixTimestamp": str(tx_data.block_unix_timestamp), + "errorLog": tx_data.error_log, + "logs": base64.b64encode(tx_data.logs).decode(), + "claimIds": [str(claim_id)], + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_txs == expected_txs + + @pytest.mark.asyncio + async def test_fetch_contract_txs( + self, + explorer_servicer, + ): + code = 5 + coin = exchange_explorer_pb.CosmosCoin( + denom="inj", + amount="200000000000000", + ) + gas_fee = exchange_explorer_pb.GasFee( + amount=[coin], gas_limit=400000, payer="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", granter="test granter" + ) + event = exchange_explorer_pb.Event(type="test event type", attributes={"first_attribute": "attribute 1"}) + signature = exchange_explorer_pb.Signature( + pubkey="02c33c539e2aea9f97137e8168f6e22f57b829876823fa04b878a2b7c2010465d9", + address="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", + sequence=223460, + signature="gFXPJ5QENzq9SUHshE8g++aRLIlRCRVcOsYq+EOr3T4QgAAs5bVHf8NhugBjJP9B+AfQjQNNneHXPF9dEp4Uehs=", + ) + claim_id = 100 + + tx_data = exchange_explorer_pb.TxDetailData( + id="test id", + block_number=18138926, + block_timestamp="2023-11-07 23:19:55.371 +0000 UTC", + hash="0x3790ade2bea6c8605851ec89fa968adf2a2037a5ecac11ca95e99260508a3b7e", + code=code, + data=b"\022&\n$/cosmos.bank.v1beta1.MsgSendResponse", + info="test info", + gas_wanted=400000, + gas_used=93696, + gas_fee=gas_fee, + codespace="test codespace", + events=[event], + tx_type="injective-web3", + messages=b'[{"type":"/cosmos.bank.v1beta1.MsgSend","value":{' + b'"from_address":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex",' + b'"to_address":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc",' + b'"amount":[{"denom":"factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth",' + b'"amount":"100000000000000000"}]}}]', + signatures=[signature], + memo="test memo", + tx_number=221429, + block_unix_timestamp=1699399195371, + error_log="", + logs=b'[{"msg_index":0,"events":[{"type":"message","attributes":[' + b'{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"module","value":"bank"}]},{"type":"coin_spent","attributes":[' + b'{"key":"spender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"coin_received","attributes":[' + b'{"key":"receiver","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"transfer","attributes":[' + b'{"key":"recipient","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"message","attributes":[' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"}]}]}]', + claim_ids=[claim_id], + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.contract_txs_responses.append( + exchange_explorer_pb.GetContractTxsResponse( + data=[tx_data], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_contract_txs = await api.fetch_contract_txs( + address="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", + from_number=221419, + to_number=221439, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_contract_txs = { + "data": [ + { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "code": tx_data.code, + "data": base64.b64encode(tx_data.data).decode(), + "info": tx_data.info, + "gasWanted": str(tx_data.gas_wanted), + "gasUsed": str(tx_data.gas_used), + "gasFee": { + "amount": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "gasLimit": str(gas_fee.gas_limit), + "payer": gas_fee.payer, + "granter": gas_fee.granter, + }, + "codespace": tx_data.codespace, + "events": [ + { + "type": event.type, + "attributes": event.attributes, + } + ], + "txType": tx_data.tx_type, + "messages": base64.b64encode(tx_data.messages).decode(), + "signatures": [ + { + "pubkey": signature.pubkey, + "address": signature.address, + "sequence": str(signature.sequence), + "signature": signature.signature, + } + ], + "memo": tx_data.memo, + "txNumber": str(tx_data.tx_number), + "blockUnixTimestamp": str(tx_data.block_unix_timestamp), + "errorLog": tx_data.error_log, + "logs": base64.b64encode(tx_data.logs).decode(), + "claimIds": [str(claim_id)], + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_contract_txs == expected_contract_txs + + @pytest.mark.asyncio + async def test_fetch_blocks( + self, + explorer_servicer, + ): + block_info = exchange_explorer_pb.BlockInfo( + height=19034578, + proposer="injvalcons18x63wcw5hjxlf535lgn4qy20yer7mm0qedu0la", + moniker="InjectiveNode1", + block_hash="0x7f7bfe8caaa0eed042315d1447ef1ed726a80f5da23fdbe6831fc66775197db1", + parent_hash="0x44287ba5fad21d0109a3ec6f19d447580763e5a709e5a5ceb767174e99ae3bd8", + num_pre_commits=20, + num_txs=4, + timestamp="2023-11-29 20:23:33.842 +0000 UTC", + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.blocks_responses.append( + exchange_explorer_pb.GetBlocksResponse( + data=[block_info], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_blocks = await api.fetch_blocks( + before=221419, + after=221439, + pagination=PaginationOption( + limit=100, + ), + ) + expected_blocks = { + "data": [ + { + "height": str(block_info.height), + "proposer": block_info.proposer, + "moniker": block_info.moniker, + "blockHash": block_info.block_hash, + "parentHash": block_info.parent_hash, + "numPreCommits": str(block_info.num_pre_commits), + "numTxs": str(block_info.num_txs), + "txs": [], + "timestamp": block_info.timestamp, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_blocks == expected_blocks + + @pytest.mark.asyncio + async def test_fetch_block( + self, + explorer_servicer, + ): + tx_data = exchange_explorer_pb.TxData( + id="tx id", + block_number=5825046, + block_timestamp="2022-12-11 22:06:49.182 +0000 UTC", + hash="0xbe8c8ca9a41196adf59b88fe9efd78e7532e04169152e779be3dc14ba7c360d9", + messages=b"null", + tx_number=994979, + tx_msg_types=b'["/injective.exchange.v1beta1.MsgCreateBinaryOptionsLimitOrder"]', + ) + block_info = exchange_explorer_pb.BlockDetailInfo( + height=19034578, + proposer="injvalcons18x63wcw5hjxlf535lgn4qy20yer7mm0qedu0la", + moniker="InjectiveNode1", + block_hash="0x7f7bfe8caaa0eed042315d1447ef1ed726a80f5da23fdbe6831fc66775197db1", + parent_hash="0x44287ba5fad21d0109a3ec6f19d447580763e5a709e5a5ceb767174e99ae3bd8", + num_pre_commits=20, + num_txs=4, + total_txs=5, + txs=[tx_data], + timestamp="2023-11-29 20:23:33.842 +0000 UTC", + ) + + explorer_servicer.block_responses.append( + exchange_explorer_pb.GetBlockResponse( + s="ok", + errmsg="test error message", + data=block_info, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_block = await api.fetch_block(block_id=str(block_info.height)) + expected_block = { + "s": "ok", + "errmsg": "test error message", + "data": { + "height": str(block_info.height), + "proposer": block_info.proposer, + "moniker": block_info.moniker, + "blockHash": block_info.block_hash, + "parentHash": block_info.parent_hash, + "numPreCommits": str(block_info.num_pre_commits), + "numTxs": str(block_info.num_txs), + "totalTxs": str(block_info.total_txs), + "txs": [ + { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "codespace": tx_data.codespace, + "messages": base64.b64encode(tx_data.messages).decode(), + "txNumber": str(tx_data.tx_number), + "errorLog": tx_data.error_log, + "code": tx_data.code, + "txMsgTypes": base64.b64encode(tx_data.tx_msg_types).decode(), + "logs": base64.b64encode(tx_data.logs).decode(), + "claimIds": tx_data.claim_ids, + } + ], + "timestamp": block_info.timestamp, + }, + } + + assert result_block == expected_block + + @pytest.mark.asyncio + async def test_fetch_validators( + self, + explorer_servicer, + ): + validator_description = exchange_explorer_pb.ValidatorDescription(moniker="InjectiveNode0") + validator = exchange_explorer_pb.Validator( + id="test id", + moniker="InjectiveNode0", + operator_address="injvaloper156t3yxd4udv0h9gwagfcmwnmm3quy0nph7tyh5", + consensus_address="injvalcons1xwg7xkmpqp8q804c37sa4dzyfwgnh4a74ll9pz", + jailed=False, + status=3, + tokens="200059138606549756596244963211573", + delegator_shares="200079146521201876783922319320744.623595039617821538", + description=validator_description, + unbonding_height=2489050, + unbonding_time="2022-09-18 14:44:56.825 +0000 UTC", + commission_rate="0.100000000000000000", + commission_max_rate="1.000000000000000000", + commission_max_change_rate="1.000000000000000000", + commission_update_time="2022-07-05 00:43:31.747 +0000 UTC", + proposed=4140681, + signed=10764141, + missed=0, + timestamp="2023-11-30 15:17:26.124 +0000 UTC", + uptime_percentage=99.906641771138965, + ) + + explorer_servicer.validators_responses.append( + exchange_explorer_pb.GetValidatorsResponse( + s="ok", + errmsg="test error message", + data=[validator], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_validators = await api.fetch_validators() + expected_validators = { + "s": "ok", + "errmsg": "test error message", + "data": [ + { + "id": validator.id, + "moniker": validator.moniker, + "operatorAddress": validator.operator_address, + "consensusAddress": validator.consensus_address, + "jailed": validator.jailed, + "status": validator.status, + "tokens": validator.tokens, + "delegatorShares": validator.delegator_shares, + "description": { + "moniker": validator_description.moniker, + "identity": validator_description.identity, + "website": validator_description.website, + "securityContact": validator_description.security_contact, + "details": validator_description.details, + "imageUrl": validator_description.image_url, + }, + "unbondingHeight": str(validator.unbonding_height), + "unbondingTime": validator.unbonding_time, + "commissionRate": validator.commission_rate, + "commissionMaxRate": validator.commission_max_rate, + "commissionMaxChangeRate": validator.commission_max_change_rate, + "commissionUpdateTime": validator.commission_update_time, + "proposed": str(validator.proposed), + "signed": str(validator.signed), + "missed": str(validator.missed), + "timestamp": validator.timestamp, + "uptimes": validator.uptimes, + "slashingEvents": validator.slashing_events, + "uptimePercentage": validator.uptime_percentage, + "imageUrl": validator.image_url, + }, + ], + } + + assert result_validators == expected_validators + + @pytest.mark.asyncio + async def test_fetch_validator( + self, + explorer_servicer, + ): + validator_description = exchange_explorer_pb.ValidatorDescription(moniker="InjectiveNode0") + validator = exchange_explorer_pb.Validator( + id="test id", + moniker="InjectiveNode0", + operator_address="injvaloper156t3yxd4udv0h9gwagfcmwnmm3quy0nph7tyh5", + consensus_address="injvalcons1xwg7xkmpqp8q804c37sa4dzyfwgnh4a74ll9pz", + jailed=False, + status=3, + tokens="200059138606549756596244963211573", + delegator_shares="200079146521201876783922319320744.623595039617821538", + description=validator_description, + unbonding_height=2489050, + unbonding_time="2022-09-18 14:44:56.825 +0000 UTC", + commission_rate="0.100000000000000000", + commission_max_rate="1.000000000000000000", + commission_max_change_rate="1.000000000000000000", + commission_update_time="2022-07-05 00:43:31.747 +0000 UTC", + proposed=4140681, + signed=10764141, + missed=0, + timestamp="2023-11-30 15:17:26.124 +0000 UTC", + uptime_percentage=99.906641771138965, + ) + + explorer_servicer.validator_responses.append( + exchange_explorer_pb.GetValidatorResponse( + s="ok", + errmsg="test error message", + data=validator, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_validator = await api.fetch_validator(address=validator.operator_address) + expected_validator = { + "s": "ok", + "errmsg": "test error message", + "data": { + "id": validator.id, + "moniker": validator.moniker, + "operatorAddress": validator.operator_address, + "consensusAddress": validator.consensus_address, + "jailed": validator.jailed, + "status": validator.status, + "tokens": validator.tokens, + "delegatorShares": validator.delegator_shares, + "description": { + "moniker": validator_description.moniker, + "identity": validator_description.identity, + "website": validator_description.website, + "securityContact": validator_description.security_contact, + "details": validator_description.details, + "imageUrl": validator_description.image_url, + }, + "unbondingHeight": str(validator.unbonding_height), + "unbondingTime": validator.unbonding_time, + "commissionRate": validator.commission_rate, + "commissionMaxRate": validator.commission_max_rate, + "commissionMaxChangeRate": validator.commission_max_change_rate, + "commissionUpdateTime": validator.commission_update_time, + "proposed": str(validator.proposed), + "signed": str(validator.signed), + "missed": str(validator.missed), + "timestamp": validator.timestamp, + "uptimes": validator.uptimes, + "slashingEvents": validator.slashing_events, + "uptimePercentage": validator.uptime_percentage, + "imageUrl": validator.image_url, + }, + } + + assert result_validator == expected_validator + + @pytest.mark.asyncio + async def test_fetch_validator_uptime( + self, + explorer_servicer, + ): + validator_uptime = exchange_explorer_pb.ValidatorUptime( + block_number=2489050, + status="3", + ) + + explorer_servicer.validator_uptime_responses.append( + exchange_explorer_pb.GetValidatorUptimeResponse( + s="ok", + errmsg="test error message", + data=[validator_uptime], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_validator = await api.fetch_validator_uptime(address="injvaloper156t3yxd4udv0h9gwagfcmwnmm3quy0nph7tyh5") + expected_validator = { + "s": "ok", + "errmsg": "test error message", + "data": [ + { + "blockNumber": str(validator_uptime.block_number), + "status": validator_uptime.status, + }, + ], + } + + assert result_validator == expected_validator + + @pytest.mark.asyncio + async def test_fetch_txs( + self, + explorer_servicer, + ): + code = 5 + claim_id = 100 + + tx_data = exchange_explorer_pb.TxData( + id="test id", + block_number=18138926, + block_timestamp="2023-11-07 23:19:55.371 +0000 UTC", + hash="0x3790ade2bea6c8605851ec89fa968adf2a2037a5ecac11ca95e99260508a3b7e", + codespace="test codespace", + messages=b'[{"type":"/cosmos.bank.v1beta1.MsgSend",' + b'"value":{"from_address":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex",' + b'"to_address":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc",' + b'"amount":[{"denom":"factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth",' + b'"amount":"100000000000000000"}]}}]', + tx_number=221429, + error_log="", + code=code, + tx_msg_types=b'["/injective.exchange.v1beta1.MsgCreateBinaryOptionsLimitOrder"]', + logs=b'[{"msg_index":0,"events":[{"type":"message","attributes":[' + b'{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"module","value":"bank"}]},{"type":"coin_spent","attributes":[' + b'{"key":"spender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"coin_received","attributes":[' + b'{"key":"receiver","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"transfer","attributes":[' + b'{"key":"recipient","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"message","attributes":[' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"}]}]}]', + claim_ids=[claim_id], + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.txs_responses.append( + exchange_explorer_pb.GetTxsResponse( + data=[tx_data], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_txs = await api.fetch_txs( + before=221439, + after=221419, + message_type="cosmos.bank.v1beta1.MsgSend", + module="bank", + from_number=221419, + to_number=221439, + status="status", + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_txs = { + "data": [ + { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "codespace": tx_data.codespace, + "messages": base64.b64encode(tx_data.messages).decode(), + "txNumber": str(tx_data.tx_number), + "errorLog": tx_data.error_log, + "code": tx_data.code, + "txMsgTypes": base64.b64encode(tx_data.tx_msg_types).decode(), + "logs": base64.b64encode(tx_data.logs).decode(), + "claimIds": [str(claim_id)], + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_txs == expected_txs + + @pytest.mark.asyncio + async def test_fetch_tx_by_hash( + self, + explorer_servicer, + ): + code = 5 + coin = exchange_explorer_pb.CosmosCoin( + denom="inj", + amount="200000000000000", + ) + gas_fee = exchange_explorer_pb.GasFee( + amount=[coin], gas_limit=400000, payer="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", granter="test granter" + ) + event = exchange_explorer_pb.Event(type="test event type", attributes={"first_attribute": "attribute 1"}) + signature = exchange_explorer_pb.Signature( + pubkey="02c33c539e2aea9f97137e8168f6e22f57b829876823fa04b878a2b7c2010465d9", + address="inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex", + sequence=223460, + signature="gFXPJ5QENzq9SUHshE8g++aRLIlRCRVcOsYq+EOr3T4QgAAs5bVHf8NhugBjJP9B+AfQjQNNneHXPF9dEp4Uehs=", + ) + claim_id = 100 + + tx_data = exchange_explorer_pb.TxDetailData( + id="test id", + block_number=18138926, + block_timestamp="2023-11-07 23:19:55.371 +0000 UTC", + hash="0x3790ade2bea6c8605851ec89fa968adf2a2037a5ecac11ca95e99260508a3b7e", + code=code, + data=b"\022&\n$/cosmos.bank.v1beta1.MsgSendResponse", + info="test info", + gas_wanted=400000, + gas_used=93696, + gas_fee=gas_fee, + codespace="test codespace", + events=[event], + tx_type="injective-web3", + messages=b'[{"type":"/cosmos.bank.v1beta1.MsgSend",' + b'"value":{"from_address":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex",' + b'"to_address":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc",' + b'"amount":[{"denom":"factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth",' + b'"amount":"100000000000000000"}]}}]', + signatures=[signature], + memo="test memo", + tx_number=221429, + block_unix_timestamp=1699399195371, + error_log="", + logs=b'[{"msg_index":0,"events":[{"type":"message","attributes":[' + b'{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"module","value":"bank"}]},{"type":"coin_spent","attributes":[' + b'{"key":"spender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"coin_received","attributes":[' + b'{"key":"receiver","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"transfer","attributes":[' + b'{"key":"recipient","value":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc"},' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"},' + b'{"key":"amount","value":"100000000000000000factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth"}' + b']},{"type":"message","attributes":[' + b'{"key":"sender","value":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex"}]}]}]', + claim_ids=[claim_id], + ) + + explorer_servicer.tx_by_tx_hash_responses.append( + exchange_explorer_pb.GetTxByTxHashResponse( + s="ok", + errmsg="test error message", + data=tx_data, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_tx = await api.fetch_tx_by_tx_hash(tx_hash=tx_data.hash) + expected_tx = { + "s": "ok", + "errmsg": "test error message", + "data": { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "code": tx_data.code, + "data": base64.b64encode(tx_data.data).decode(), + "info": tx_data.info, + "gasWanted": str(tx_data.gas_wanted), + "gasUsed": str(tx_data.gas_used), + "gasFee": { + "amount": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "gasLimit": str(gas_fee.gas_limit), + "payer": gas_fee.payer, + "granter": gas_fee.granter, + }, + "codespace": tx_data.codespace, + "events": [ + { + "type": event.type, + "attributes": event.attributes, + } + ], + "txType": tx_data.tx_type, + "messages": base64.b64encode(tx_data.messages).decode(), + "signatures": [ + { + "pubkey": signature.pubkey, + "address": signature.address, + "sequence": str(signature.sequence), + "signature": signature.signature, + } + ], + "memo": tx_data.memo, + "txNumber": str(tx_data.tx_number), + "blockUnixTimestamp": str(tx_data.block_unix_timestamp), + "errorLog": tx_data.error_log, + "logs": base64.b64encode(tx_data.logs).decode(), + "claimIds": [str(claim_id)], + }, + } + + assert result_tx == expected_tx + + @pytest.mark.asyncio + async def test_fetch_peggy_deposit_txs( + self, + explorer_servicer, + ): + tx_hash = "0x028a43ad2089cad45a8855143508f7381787d7f17cc19e3cda1bc2300c1d043f" + tx_data = exchange_explorer_pb.PeggyDepositTx( + sender="0x197E6c3f19951eA0bA90Ddf465bcC79790cDD12d", + receiver="inj1r9lxc0cej502pw5smh6xt0x8j7gvm5fdrj6xhk", + event_nonce=624, + event_height=10122722, + amount="500000000000000000", + denom="0xAD1794307245443B3Cb55d88e79EEE4d8a548C03", + orchestrator_address="inj1c8rpu79mr70hqsgzutdd6rhvzhej9vntm6fqku", + state="Completed", + claim_type=1, + tx_hashes=[tx_hash], + created_at="2023-11-28 16:55:54.841 +0000 UTC", + updated_at="2023-11-28 16:56:07.944 +0000 UTC", + ) + + explorer_servicer.peggy_deposit_txs_responses.append( + exchange_explorer_pb.GetPeggyDepositTxsResponse(field=[tx_data]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_tx = await api.fetch_peggy_deposit_txs( + sender=tx_data.sender, + receiver=tx_data.receiver, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_tx = { + "field": [ + { + "sender": tx_data.sender, + "receiver": tx_data.receiver, + "eventNonce": str(tx_data.event_nonce), + "eventHeight": str(tx_data.event_height), + "amount": tx_data.amount, + "denom": tx_data.denom, + "orchestratorAddress": tx_data.orchestrator_address, + "state": tx_data.state, + "claimType": tx_data.claim_type, + "txHashes": [tx_hash], + "createdAt": tx_data.created_at, + "updatedAt": tx_data.updated_at, + }, + ] + } + + assert result_tx == expected_tx + + @pytest.mark.asyncio + async def test_fetch_peggy_withdrawal_txs( + self, + explorer_servicer, + ): + tx_hash = "0x028a43ad2089cad45a8855143508f7381787d7f17cc19e3cda1bc2300c1d043f" + tx_data = exchange_explorer_pb.PeggyWithdrawalTx( + sender="0x197E6c3f19951eA0bA90Ddf465bcC79790cDD12d", + receiver="inj1r9lxc0cej502pw5smh6xt0x8j7gvm5fdrj6xhk", + amount="500000000000000000", + denom="0xAD1794307245443B3Cb55d88e79EEE4d8a548C03", + bridge_fee="575043128234617596", + outgoing_tx_id=1136, + batch_timeout=10125614, + batch_nonce=1600, + orchestrator_address="inj1c8rpu79mr70hqsgzutdd6rhvzhej9vntm6fqku", + event_nonce=624, + event_height=10122722, + state="Completed", + claim_type=1, + tx_hashes=[tx_hash], + created_at="2023-11-28 16:55:54.841 +0000 UTC", + updated_at="2023-11-28 16:56:07.944 +0000 UTC", + ) + + explorer_servicer.peggy_withdrawal_txs_responses.append( + exchange_explorer_pb.GetPeggyWithdrawalTxsResponse(field=[tx_data]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_tx = await api.fetch_peggy_withdrawal_txs( + sender=tx_data.sender, + receiver=tx_data.receiver, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_tx = { + "field": [ + { + "sender": tx_data.sender, + "receiver": tx_data.receiver, + "amount": tx_data.amount, + "denom": tx_data.denom, + "bridgeFee": tx_data.bridge_fee, + "outgoingTxId": str(tx_data.outgoing_tx_id), + "batchTimeout": str(tx_data.batch_timeout), + "batchNonce": str(tx_data.batch_nonce), + "orchestratorAddress": tx_data.orchestrator_address, + "eventNonce": str(tx_data.event_nonce), + "eventHeight": str(tx_data.event_height), + "state": tx_data.state, + "claimType": tx_data.claim_type, + "txHashes": [tx_hash], + "createdAt": tx_data.created_at, + "updatedAt": tx_data.updated_at, + }, + ] + } + + assert result_tx == expected_tx + + @pytest.mark.asyncio + async def test_fetch_ibc_transfer_txs( + self, + explorer_servicer, + ): + tx_hash = "0x028a43ad2089cad45a8855143508f7381787d7f17cc19e3cda1bc2300c1d043f" + tx_data = exchange_explorer_pb.IBCTransferTx( + sender="0x197E6c3f19951eA0bA90Ddf465bcC79790cDD12d", + receiver="inj1r9lxc0cej502pw5smh6xt0x8j7gvm5fdrj6xhk", + source_port="transfer", + source_channel="channel-74", + destination_port="transfer", + destination_channel="channel-33", + amount="500000000000000000", + denom="0xAD1794307245443B3Cb55d88e79EEE4d8a548C03", + timeout_height="0-0", + timeout_timestamp=1701460751755119600, + packet_sequence=16607, + data_hex=b"7b22616d6f756e74223a2231303030303030222c2264656e6f6d223a227472616e736665722f6368616e6e656c2d3734" + b"2f756e6f6973222c227265636569766572223a226e6f6973316d7675757067726537706a78336b35746d353732396672" + b"6b6e396e766a75367067737861776334377067616d63747970647a6c736d3768673930222c2273656e646572223a2269" + b"6e6a31346e656e6474737a306334306e3778747a776b6a6d646338646b757a3833356a64796478686e227d", + state="Completed", + tx_hashes=[tx_hash], + created_at="2023-11-28 16:55:54.841 +0000 UTC", + updated_at="2023-11-28 16:56:07.944 +0000 UTC", + ) + + explorer_servicer.ibc_transfer_txs_responses.append( + exchange_explorer_pb.GetIBCTransferTxsResponse(field=[tx_data]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_tx = await api.fetch_ibc_transfer_txs( + sender=tx_data.sender, + receiver=tx_data.receiver, + src_channel=tx_data.source_channel, + src_port=tx_data.source_port, + dest_channel=tx_data.destination_channel, + dest_port=tx_data.destination_port, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_tx = { + "field": [ + { + "sender": tx_data.sender, + "receiver": tx_data.receiver, + "sourcePort": tx_data.source_port, + "sourceChannel": tx_data.source_channel, + "destinationPort": tx_data.destination_port, + "destinationChannel": tx_data.destination_channel, + "amount": tx_data.amount, + "denom": tx_data.denom, + "timeoutHeight": tx_data.timeout_height, + "timeoutTimestamp": str(tx_data.timeout_timestamp), + "packetSequence": str(tx_data.packet_sequence), + "dataHex": base64.b64encode(tx_data.data_hex).decode(), + "state": tx_data.state, + "txHashes": [tx_hash], + "createdAt": tx_data.created_at, + "updatedAt": tx_data.updated_at, + }, + ] + } + + assert result_tx == expected_tx + + @pytest.mark.asyncio + async def test_fetch_wasm_codes( + self, + explorer_servicer, + ): + checksum = exchange_explorer_pb.Checksum( + algorithm="sha256", + hash="0xadecb2d943c03eeee77e111791df61198a9dee097f47f14a811b8f9657122624", + ) + permission = exchange_explorer_pb.ContractPermission( + access_type=3, + address="test address", + ) + wasm_code = exchange_explorer_pb.WasmCode( + code_id=245, + tx_hash="0xa5da295f9252dc932861be6f2a4dbc9a8c0f44bb42a473ded5ec349407a1c708", + checksum=checksum, + created_at=1701373663980, + contract_type="test contract type", + version="test version", + permission=permission, + code_schema="test code schema", + code_view="test code view", + instantiates=0, + creator="inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c", + code_number=253, + proposal_id=0, + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.wasm_codes_responses.append( + exchange_explorer_pb.GetWasmCodesResponse(paging=paging, data=[wasm_code]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_codes = await api.fetch_wasm_codes( + from_number=1, + to_number=1000, + pagination=PaginationOption( + limit=100, + ), + ) + expected_wasm_codes = { + "data": [ + { + "codeId": str(wasm_code.code_id), + "txHash": wasm_code.tx_hash, + "checksum": { + "algorithm": checksum.algorithm, + "hash": checksum.hash, + }, + "createdAt": str(wasm_code.created_at), + "contractType": wasm_code.contract_type, + "version": wasm_code.version, + "permission": { + "accessType": permission.access_type, + "address": permission.address, + }, + "codeSchema": wasm_code.code_schema, + "codeView": wasm_code.code_view, + "instantiates": str(wasm_code.instantiates), + "creator": wasm_code.creator, + "codeNumber": str(wasm_code.code_number), + "proposalId": str(wasm_code.proposal_id), + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_wasm_codes == expected_wasm_codes + + @pytest.mark.asyncio + async def test_fetch_wasm_code_by_id( + self, + explorer_servicer, + ): + checksum = exchange_explorer_pb.Checksum( + algorithm="sha256", + hash="0xadecb2d943c03eeee77e111791df61198a9dee097f47f14a811b8f9657122624", + ) + permission = exchange_explorer_pb.ContractPermission( + access_type=3, + address="test address", + ) + wasm_code = exchange_explorer_pb.GetWasmCodeByIDResponse( + code_id=245, + tx_hash="0xa5da295f9252dc932861be6f2a4dbc9a8c0f44bb42a473ded5ec349407a1c708", + checksum=checksum, + created_at=1701373663980, + contract_type="test contract type", + version="test version", + permission=permission, + code_schema="test code schema", + code_view="test code view", + instantiates=0, + creator="inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c", + code_number=253, + proposal_id=0, + ) + + explorer_servicer.wasm_code_by_id_responses.append(wasm_code) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_code = await api.fetch_wasm_code_by_id(code_id=wasm_code.code_id) + expected_wasm_code = { + "codeId": str(wasm_code.code_id), + "txHash": wasm_code.tx_hash, + "checksum": { + "algorithm": checksum.algorithm, + "hash": checksum.hash, + }, + "createdAt": str(wasm_code.created_at), + "contractType": wasm_code.contract_type, + "version": wasm_code.version, + "permission": { + "accessType": permission.access_type, + "address": permission.address, + }, + "codeSchema": wasm_code.code_schema, + "codeView": wasm_code.code_view, + "instantiates": str(wasm_code.instantiates), + "creator": wasm_code.creator, + "codeNumber": str(wasm_code.code_number), + "proposalId": str(wasm_code.proposal_id), + } + + assert result_wasm_code == expected_wasm_code + + @pytest.mark.asyncio + async def test_fetch_wasm_contracts( + self, + explorer_servicer, + ): + wasm_contract = exchange_explorer_pb.WasmContract( + label="Talis candy machine", + address="inj1t4lnxfu9gtyd50uqmf0ahpwk3vtg5yk9pe7uj4", + tx_hash="0x7462ce393fd7691c5179107dcd5ee47c79e7a348538c0c976e160bbbfdae338c", + creator="inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj", + executes=23, + instantiated_at=1701320950004, + init_message='{"admin":"inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3","codeId":"104",' + '"label":"Talis candy machine","msg":"",' + '"sender":"inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj","fundsList":[],' + '"contract_address":"inj1mhsrt6ulz07wnesppy39wwygjntk0stmk39ftg",' + '"owner":"inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj",' + '"fee_collector":"inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3",' + '"operator_pubkey":"Aq9ExLymJrae0ol4Pq13vZkDARZeunbFWJGXgsHtkzkx",' + '"public_phase":{"id":0,"private":false,"start":1701363602,"end":1701489600,' + '"price":{"native":[{"denom":"inj","amount":"100000000000000000"}]},"mint_limit":5},' + '"reserved_tokens":11,"total_tokens":111}', + last_executed_at=1701395446228, + funds=[], + code_id=104, + admin="inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3", + current_migrate_message="", + contract_number=1037, + version="test version", + type="test_type", + proposal_id=0, + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.wasm_contracts_responses.append( + exchange_explorer_pb.GetWasmContractsResponse(paging=paging, data=[wasm_contract]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_contracts = await api.fetch_wasm_contracts( + code_id=wasm_contract.code_id, + from_number=1, + to_number=1000, + assets_only=False, + label=wasm_contract.label, + pagination=PaginationOption( + limit=100, + skip=10, + ), + ) + expected_wasm_contracts = { + "data": [ + { + "label": wasm_contract.label, + "address": wasm_contract.address, + "txHash": wasm_contract.tx_hash, + "creator": wasm_contract.creator, + "executes": str(wasm_contract.executes), + "instantiatedAt": str(wasm_contract.instantiated_at), + "initMessage": wasm_contract.init_message, + "lastExecutedAt": str(wasm_contract.last_executed_at), + "funds": wasm_contract.funds, + "codeId": str(wasm_contract.code_id), + "admin": wasm_contract.admin, + "currentMigrateMessage": wasm_contract.current_migrate_message, + "contractNumber": str(wasm_contract.contract_number), + "version": wasm_contract.version, + "type": wasm_contract.type, + "proposalId": str(wasm_contract.proposal_id), + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_wasm_contracts == expected_wasm_contracts + + @pytest.mark.asyncio + async def test_fetch_wasm_contract_by_address( + self, + explorer_servicer, + ): + wasm_contract = exchange_explorer_pb.GetWasmContractByAddressResponse( + label="Talis candy machine", + address="inj1t4lnxfu9gtyd50uqmf0ahpwk3vtg5yk9pe7uj4", + tx_hash="0x7462ce393fd7691c5179107dcd5ee47c79e7a348538c0c976e160bbbfdae338c", + creator="inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj", + executes=23, + instantiated_at=1701320950004, + init_message='{"admin":"inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3","codeId":"104",' + '"label":"Talis candy machine","msg":"","sender":"inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj",' + '"fundsList":[],"contract_address":"inj1mhsrt6ulz07wnesppy39wwygjntk0stmk39ftg",' + '"owner":"inj1fh92xcg28rat7apzhw5aw8x4x83wrprq4sp3tj",' + '"fee_collector":"inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3",' + '"operator_pubkey":"Aq9ExLymJrae0ol4Pq13vZkDARZeunbFWJGXgsHtkzkx",' + '"public_phase":{"id":0,"private":false,"start":1701363602,"end":1701489600,' + '"price":{"native":[{"denom":"inj","amount":"100000000000000000"}]},"mint_limit":5},' + '"reserved_tokens":11,"total_tokens":111}', + last_executed_at=1701395446228, + funds=[], + code_id=104, + admin="inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3", + current_migrate_message="", + contract_number=1037, + version="test version", + type="test_type", + proposal_id=0, + ) + + explorer_servicer.wasm_contract_by_address_responses.append(wasm_contract) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_contract = await api.fetch_wasm_contract_by_address(address=wasm_contract.address) + expected_wasm_contract = { + "label": wasm_contract.label, + "address": wasm_contract.address, + "txHash": wasm_contract.tx_hash, + "creator": wasm_contract.creator, + "executes": str(wasm_contract.executes), + "instantiatedAt": str(wasm_contract.instantiated_at), + "initMessage": wasm_contract.init_message, + "lastExecutedAt": str(wasm_contract.last_executed_at), + "funds": wasm_contract.funds, + "codeId": str(wasm_contract.code_id), + "admin": wasm_contract.admin, + "currentMigrateMessage": wasm_contract.current_migrate_message, + "contractNumber": str(wasm_contract.contract_number), + "version": wasm_contract.version, + "type": wasm_contract.type, + "proposalId": str(wasm_contract.proposal_id), + } + + assert result_wasm_contract == expected_wasm_contract + + @pytest.mark.asyncio + async def test_fetch_cw20_balance( + self, + explorer_servicer, + ): + token_info = exchange_explorer_pb.Cw20TokenInfo( + name="Tether", + symbol="USDT", + decimals=6, + total_supply="100000000000", + ) + marketing_info = exchange_explorer_pb.Cw20MarketingInfo( + project="Tether", + description="Tether project", + logo="test logo", + marketing=b"Test marketing info", + ) + cw20_metadata = exchange_explorer_pb.Cw20Metadata( + token_info=token_info, + marketing_info=marketing_info, + ) + wasm_balance = exchange_explorer_pb.WasmCw20Balance( + account="0xaf79152ac5df276d9a8e1e2e22822f9713474902", + balance="1000", + contract_address="inj1t4lnxfu9gtyd50uqmf0ahpwk3vtg5yk9pe7uj4", + cw20_metadata=cw20_metadata, + updated_at=1701395446228, + ) + + explorer_servicer.cw20_balance_responses.append( + exchange_explorer_pb.GetCw20BalanceResponse(field=[wasm_balance]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_contract = await api.fetch_cw20_balance( + address=wasm_balance.account, + pagination=PaginationOption( + limit=100, + ), + ) + expected_wasm_contract = { + "field": [ + { + "account": wasm_balance.account, + "balance": wasm_balance.balance, + "contractAddress": wasm_balance.contract_address, + "cw20Metadata": { + "tokenInfo": { + "name": token_info.name, + "symbol": token_info.symbol, + "decimals": str(token_info.decimals), + "totalSupply": token_info.total_supply, + }, + "marketingInfo": { + "project": marketing_info.project, + "description": marketing_info.description, + "logo": marketing_info.logo, + "marketing": base64.b64encode(marketing_info.marketing).decode(), + }, + }, + "updatedAt": str(wasm_balance.updated_at), + }, + ] + } + + assert result_wasm_contract == expected_wasm_contract + + @pytest.mark.asyncio + async def test_fetch_relayers( + self, + explorer_servicer, + ): + relayer = exchange_explorer_pb.Relayer( + name="Injdojo", + cta="https://injdojo.exchange", + ) + relayers = exchange_explorer_pb.RelayerMarkets( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", relayers=[relayer] + ) + + explorer_servicer.relayers_responses.append(exchange_explorer_pb.RelayersResponse(field=[relayers])) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_wasm_contract = await api.fetch_relayers( + market_ids=[relayers.market_id], + ) + expected_wasm_contract = { + "field": [ + { + "marketId": relayers.market_id, + "relayers": [ + { + "name": relayer.name, + "cta": relayer.cta, + }, + ], + }, + ] + } + + assert result_wasm_contract == expected_wasm_contract + + @pytest.mark.asyncio + async def test_fetch_bank_transfers( + self, + explorer_servicer, + ): + coin = exchange_explorer_pb.Coin( + denom="inj", + amount="200000000000000", + ) + bank_transfer = exchange_explorer_pb.BankTransfer( + sender="inj17xpfvakm2amg962yls6f84z3kell8c5l6s5ye9", + recipient="inj1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8dkncm8", + amounts=[coin], + block_number=52990746, + block_timestamp="2023-12-01 14:25:28.266 +0000 UTC", + ) + + paging = exchange_explorer_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + explorer_servicer.bank_transfers_responses.append( + exchange_explorer_pb.GetBankTransfersResponse(paging=paging, data=[bank_transfer]) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + result_transfers = await api.fetch_bank_transfers( + senders=[bank_transfer.sender], + recipients=[bank_transfer.recipient], + is_community_pool_related=False, + address=["inj1t4lnxfu9gtyd50uqmf0ahpwk3vtg5yk9pe7uj4"], + per_page=20, + token="inj", + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_transfers = { + "data": [ + { + "sender": bank_transfer.sender, + "recipient": bank_transfer.recipient, + "amounts": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "blockNumber": str(bank_transfer.block_number), + "blockTimestamp": bank_transfer.block_timestamp, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_transfers == expected_transfers + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/grpc/test_indexer_grpc_insurance_api.py b/tests/client/indexer/grpc/test_indexer_grpc_insurance_api.py new file mode 100644 index 00000000..2499b7db --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_insurance_api.py @@ -0,0 +1,123 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_insurance_api import IndexerGrpcInsuranceApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_insurance_rpc_pb2 as exchange_insurance_pb +from tests.client.indexer.configurable_insurance_query_servicer import ConfigurableInsuranceQueryServicer + + +@pytest.fixture +def insurance_servicer(): + return ConfigurableInsuranceQueryServicer() + + +class TestIndexerGrpcInsuranceApi: + @pytest.mark.asyncio + async def test_fetch_insurance_funds( + self, + insurance_servicer, + ): + insurance_fund = exchange_insurance_pb.InsuranceFund( + market_ticker="inj/usdt", + market_id="0x7f15b4f4484e6820fc446e42cd447ca6d9bfd7c0592304294270c2bef5f589cd", + deposit_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + pool_token_denom="share132", + redemption_notice_period_duration=1209600, + balance="920389040000", + total_share="1000000000000000000", + oracle_base="0x31775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae", + oracle_quote="USD", + oracle_type="coinbase", + expiry=1696539600, + ) + + insurance_servicer.funds_responses.append( + exchange_insurance_pb.FundsResponse( + funds=[insurance_fund], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcInsuranceApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = insurance_servicer + + result_insurance_list = await api.fetch_insurance_funds() + expected_insurance_list = { + "funds": [ + { + "marketTicker": insurance_fund.market_ticker, + "marketId": insurance_fund.market_id, + "depositDenom": insurance_fund.deposit_denom, + "poolTokenDenom": insurance_fund.pool_token_denom, + "redemptionNoticePeriodDuration": str(insurance_fund.redemption_notice_period_duration), + "balance": insurance_fund.balance, + "totalShare": insurance_fund.total_share, + "oracleBase": insurance_fund.oracle_base, + "oracleQuote": insurance_fund.oracle_quote, + "oracleType": insurance_fund.oracle_type, + "expiry": str(insurance_fund.expiry), + } + ] + } + + assert result_insurance_list == expected_insurance_list + + @pytest.mark.asyncio + async def test_fetch_redemptions( + self, + insurance_servicer, + ): + redemption_schedule = exchange_insurance_pb.RedemptionSchedule( + redemption_id=1, + status="disbursed", + redeemer="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + claimable_redemption_time=1674798129093000, + redemption_amount="500000000000000000", + redemption_denom="share4", + requested_at=1673588529093000, + disbursed_amount="5000000", + disbursed_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + disbursed_at=1674798130965000, + ) + + insurance_servicer.redemptions_responses.append( + exchange_insurance_pb.RedemptionsResponse( + redemption_schedules=[redemption_schedule], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcInsuranceApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = insurance_servicer + + result_insurance_list = await api.fetch_redemptions( + address=redemption_schedule.redeemer, + denom=redemption_schedule.redemption_denom, + status=redemption_schedule.status, + ) + expected_insurance_list = { + "redemptionSchedules": [ + { + "redemptionId": str(redemption_schedule.redemption_id), + "status": redemption_schedule.status, + "redeemer": redemption_schedule.redeemer, + "claimableRedemptionTime": str(redemption_schedule.claimable_redemption_time), + "redemptionAmount": str(redemption_schedule.redemption_amount), + "redemptionDenom": redemption_schedule.redemption_denom, + "requestedAt": str(redemption_schedule.requested_at), + "disbursedAmount": redemption_schedule.disbursed_amount, + "disbursedDenom": redemption_schedule.disbursed_denom, + "disbursedAt": str(redemption_schedule.disbursed_at), + } + ] + } + + assert result_insurance_list == expected_insurance_list + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/grpc/test_indexer_grpc_meta_api.py b/tests/client/indexer/grpc/test_indexer_grpc_meta_api.py new file mode 100644 index 00000000..e9fe8d94 --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_meta_api.py @@ -0,0 +1,99 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_meta_api import IndexerGrpcMetaApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_meta_rpc_pb2 as exchange_meta_pb +from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer + + +@pytest.fixture +def meta_servicer(): + return ConfigurableMetaQueryServicer() + + +class TestIndexerGrpcMetaApi: + @pytest.mark.asyncio + async def test_fetch_portfolio( + self, + meta_servicer, + ): + meta_servicer.ping_responses.append(exchange_meta_pb.PingResponse()) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcMetaApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = meta_servicer + + result_ping = await api.fetch_ping() + expected_ping = {} + + assert result_ping == expected_ping + + @pytest.mark.asyncio + async def test_fetch_version( + self, + meta_servicer, + ): + version = "v1.12.28" + build = { + "GoVersion": "go1.20.5", + "GoArch": "amd64", + } + meta_servicer.version_responses.append( + exchange_meta_pb.VersionResponse( + version=version, + build=build, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcMetaApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = meta_servicer + + result_version = await api.fetch_version() + expected_version = {"build": build, "version": version} + + assert result_version == expected_version + + @pytest.mark.asyncio + async def test_fetch_info( + self, + meta_servicer, + ): + timestamp = 1698440196320 + server_time = 1698440197744 + version = "v1.12.28" + build = { + "GoVersion": "go1.20.5", + "GoArch": "amd64", + } + region = "test region" + meta_servicer.info_responses.append( + exchange_meta_pb.InfoResponse( + timestamp=timestamp, server_time=server_time, version=version, build=build, region=region + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcMetaApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = meta_servicer + + result_info = await api.fetch_info() + expected_info = { + "timestamp": str(timestamp), + "serverTime": str(server_time), + "version": version, + "build": build, + "region": region, + } + + assert result_info == expected_info + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py b/tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py new file mode 100644 index 00000000..25c39733 --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_oracle_api.py @@ -0,0 +1,86 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_oracle_api import IndexerGrpcOracleApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_oracle_rpc_pb2 as exchange_oracle_pb +from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer + + +@pytest.fixture +def oracle_servicer(): + return ConfigurableOracleQueryServicer() + + +class TestIndexerGrpcOracleApi: + @pytest.mark.asyncio + async def test_fetch_oracle_list( + self, + oracle_servicer, + ): + oracle = exchange_oracle_pb.Oracle( + symbol="Gold/USDT", + base_symbol="Gold", + quote_symbol="USDT", + oracle_type="pricefeed", + price="1", + ) + + oracle_servicer.oracle_list_responses.append( + exchange_oracle_pb.OracleListResponse( + oracles=[oracle], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcOracleApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = oracle_servicer + + result_oracle_list = await api.fetch_oracle_list() + expected_oracle_list = { + "oracles": [ + { + "symbol": oracle.symbol, + "baseSymbol": oracle.base_symbol, + "quoteSymbol": oracle.quote_symbol, + "oracleType": oracle.oracle_type, + "price": oracle.price, + } + ] + } + + assert result_oracle_list == expected_oracle_list + + @pytest.mark.asyncio + async def test_fetch_oracle_price( + self, + oracle_servicer, + ): + price = "0.00000002" + + oracle_servicer.price_responses.append( + exchange_oracle_pb.PriceResponse( + price=price, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcOracleApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = oracle_servicer + + result_oracle_list = await api.fetch_oracle_price( + base_symbol="Gold", + quote_symbol="USDT", + oracle_type="pricefeed", + oracle_scale_factor=6, + ) + expected_oracle_list = {"price": price} + + assert result_oracle_list == expected_oracle_list + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/grpc/test_indexer_grpc_portfolio_api.py b/tests/client/indexer/grpc/test_indexer_grpc_portfolio_api.py new file mode 100644 index 00000000..86a50382 --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_portfolio_api.py @@ -0,0 +1,117 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_portfolio_api import IndexerGrpcPortfolioApi +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_portfolio_rpc_pb2 as exchange_portfolio_pb +from tests.client.indexer.configurable_portfolio_query_servicer import ConfigurablePortfolioQueryServicer + + +@pytest.fixture +def portfolio_servicer(): + return ConfigurablePortfolioQueryServicer() + + +class TestIndexerGrpcPortfolioApi: + @pytest.mark.asyncio + async def test_fetch_account_portfolio( + self, + portfolio_servicer, + ): + coin = exchange_portfolio_pb.Coin( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + amount="2322098", + ) + subaccount_deposit = exchange_portfolio_pb.SubaccountDeposit( + total_balance="0.170858923182467801", + available_balance="0.170858923182467801", + ) + subaccount_balance = exchange_portfolio_pb.SubaccountBalanceV2( + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000000", + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + deposit=subaccount_deposit, + ) + position = exchange_portfolio_pb.DerivativePosition( + ticker="INJ/USDT PERP", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x1383dabde57e5aed55960ee43e158ae7118057d3000000000000000000000000", + direction="short", + quantity="0.070294765766186502", + entry_price="15980281.340438795311756847", + margin="561065.540974", + liquidation_price="23492052.224777", + mark_price="16197000", + aggregate_reduce_only_quantity="0", + updated_at=1700161202147, + created_at=-62135596800000, + ) + positions_with_upnl = exchange_portfolio_pb.PositionsWithUPNL( + position=position, + unrealized_pnl="-364.479654577777780880", + ) + + portfolio = exchange_portfolio_pb.Portfolio( + account_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + bank_balances=[coin], + subaccounts=[subaccount_balance], + positions_with_upnl=[positions_with_upnl], + ) + + portfolio_servicer.account_portfolio_responses.append( + exchange_portfolio_pb.AccountPortfolioResponse( + portfolio=portfolio, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcPortfolioApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = portfolio_servicer + + result_auction = await api.fetch_account_portfolio(account_address=portfolio.account_address) + expected_auction = { + "portfolio": { + "accountAddress": portfolio.account_address, + "bankBalances": [ + { + "denom": coin.denom, + "amount": coin.amount, + } + ], + "subaccounts": [ + { + "subaccountId": subaccount_balance.subaccount_id, + "denom": subaccount_balance.denom, + "deposit": { + "totalBalance": subaccount_deposit.total_balance, + "availableBalance": subaccount_deposit.available_balance, + }, + } + ], + "positionsWithUpnl": [ + { + "position": { + "ticker": position.ticker, + "marketId": position.market_id, + "subaccountId": position.subaccount_id, + "direction": position.direction, + "quantity": position.quantity, + "entryPrice": position.entry_price, + "margin": position.margin, + "liquidationPrice": position.liquidation_price, + "markPrice": position.mark_price, + "aggregateReduceOnlyQuantity": position.aggregate_reduce_only_quantity, + "createdAt": str(position.created_at), + "updatedAt": str(position.updated_at), + }, + "unrealizedPnl": positions_with_upnl.unrealized_pnl, + }, + ], + } + } + + assert result_auction == expected_auction + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py b/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py new file mode 100644 index 00000000..b84d30d9 --- /dev/null +++ b/tests/client/indexer/grpc/test_indexer_grpc_spot_api.py @@ -0,0 +1,806 @@ +import grpc +import pytest + +from pyinjective.client.indexer.grpc.indexer_grpc_spot_api import IndexerGrpcSpotApi +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_spot_exchange_rpc_pb2 as exchange_spot_pb +from tests.client.indexer.configurable_spot_query_servicer import ConfigurableSpotQueryServicer + + +@pytest.fixture +def spot_servicer(): + return ConfigurableSpotQueryServicer() + + +class TestIndexerGrpcSpotApi: + @pytest.mark.asyncio + async def test_fetch_markets( + self, + spot_servicer, + ): + base_token_meta = exchange_spot_pb.TokenMeta( + name="Injective Protocol", + address="0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", + symbol="INJ", + logo="https://static.alchemyapi.io/images/assets/7226.png", + decimals=18, + updated_at=1683119359318, + ) + quote_token_meta = exchange_spot_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + + market = exchange_spot_pb.SpotMarketInfo( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + market_status="active", + ticker="INJ/USDT", + base_denom="inj", + base_token_meta=base_token_meta, + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + min_price_tick_size="0.000000000000001", + min_quantity_tick_size="1000000000000000", + ) + + spot_servicer.markets_responses.append( + exchange_spot_pb.MarketsResponse( + markets=[market], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_markets = await api.fetch_markets( + market_statuses=[market.market_status], + base_denom=market.base_denom, + quote_denom=market.quote_denom, + ) + expected_markets = { + "markets": [ + { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "baseDenom": market.base_denom, + "baseTokenMeta": { + "name": market.base_token_meta.name, + "address": market.base_token_meta.address, + "symbol": market.base_token_meta.symbol, + "logo": market.base_token_meta.logo, + "decimals": market.base_token_meta.decimals, + "updatedAt": str(market.base_token_meta.updated_at), + }, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "takerFeeRate": market.taker_fee_rate, + "makerFeeRate": market.maker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + } + ] + } + + assert result_markets == expected_markets + + @pytest.mark.asyncio + async def test_fetch_market( + self, + spot_servicer, + ): + base_token_meta = exchange_spot_pb.TokenMeta( + name="Injective Protocol", + address="0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", + symbol="INJ", + logo="https://static.alchemyapi.io/images/assets/7226.png", + decimals=18, + updated_at=1683119359318, + ) + quote_token_meta = exchange_spot_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + + market = exchange_spot_pb.SpotMarketInfo( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + market_status="active", + ticker="INJ/USDT", + base_denom="inj", + base_token_meta=base_token_meta, + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + min_price_tick_size="0.000000000000001", + min_quantity_tick_size="1000000000000000", + ) + + spot_servicer.market_responses.append( + exchange_spot_pb.MarketResponse( + market=market, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_market = await api.fetch_market(market_id=market.market_id) + expected_market = { + "market": { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "baseDenom": market.base_denom, + "baseTokenMeta": { + "name": market.base_token_meta.name, + "address": market.base_token_meta.address, + "symbol": market.base_token_meta.symbol, + "logo": market.base_token_meta.logo, + "decimals": market.base_token_meta.decimals, + "updatedAt": str(market.base_token_meta.updated_at), + }, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "takerFeeRate": market.taker_fee_rate, + "makerFeeRate": market.maker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + } + } + + assert result_market == expected_market + + @pytest.mark.asyncio + async def test_fetch_orderbook_v2( + self, + spot_servicer, + ): + buy = exchange_spot_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_spot_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_spot_pb.SpotLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + spot_servicer.orderbook_v2_responses.append( + exchange_spot_pb.OrderbookV2Response( + orderbook=orderbook, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_orderbook = await api.fetch_orderbook_v2( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + ) + expected_orderbook = { + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + } + } + + assert result_orderbook == expected_orderbook + + @pytest.mark.asyncio + async def test_fetch_orderbooks_v2( + self, + spot_servicer, + ): + buy = exchange_spot_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_spot_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_spot_pb.SpotLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + single_orderbook = exchange_spot_pb.SingleSpotLimitOrderbookV2( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + orderbook=orderbook, + ) + + spot_servicer.orderbooks_v2_responses.append( + exchange_spot_pb.OrderbooksV2Response( + orderbooks=[single_orderbook], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_orderbook = await api.fetch_orderbooks_v2(market_ids=[single_orderbook.market_id]) + expected_orderbook = { + "orderbooks": [ + { + "marketId": single_orderbook.market_id, + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + }, + } + ] + } + + assert result_orderbook == expected_orderbook + + @pytest.mark.asyncio + async def test_fetch_orders( + self, + spot_servicer, + ): + order = exchange_spot_pb.SpotLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_spot_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + spot_servicer.orders_responses.append( + exchange_spot_pb.OrdersResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_orders = await api.fetch_orders( + market_ids=[order.market_id], + order_side=order.order_side, + subaccount_id=order.subaccount_id, + include_inactive=True, + subaccount_total_orders=True, + trade_id="7959737_3_0", + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "txHash": order.tx_hash, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_trades( + self, + spot_servicer, + ): + price = exchange_spot_pb.PriceLevel( + price="0.000000000006024", + quantity="10000000000000000", + timestamp=1677563766350, + ) + + trade = exchange_spot_pb.SpotTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + trade_execution_type="limitMatchNewOrder", + trade_direction="buy", + price=price, + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + paging = exchange_spot_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + spot_servicer.trades_responses.append( + exchange_spot_pb.TradesResponse( + trades=[trade], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_trades = await api.fetch_trades( + market_ids=[trade.market_id], + subaccount_ids=[trade.subaccount_id], + execution_side=trade.execution_side, + direction=trade.trade_direction, + execution_types=[trade.trade_execution_type], + trade_id=trade.trade_id, + account_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + cid=trade.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_trades = { + "trades": [ + { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "tradeDirection": trade.trade_direction, + "price": { + "price": price.price, + "quantity": price.quantity, + "timestamp": str(price.timestamp), + }, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_trades == expected_trades + + @pytest.mark.asyncio + async def test_fetch_subaccount_orders_list( + self, + spot_servicer, + ): + order = exchange_spot_pb.SpotLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_spot_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + spot_servicer.subaccount_orders_list_responses.append( + exchange_spot_pb.SubaccountOrdersListResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_orders = await api.fetch_subaccount_orders_list( + subaccount_id=order.subaccount_id, + market_id=order.market_id, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "txHash": order.tx_hash, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_subaccount_trades_list( + self, + spot_servicer, + ): + price = exchange_spot_pb.PriceLevel( + price="0.000000000006024", + quantity="10000000000000000", + timestamp=1677563766350, + ) + + trade = exchange_spot_pb.SpotTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + trade_execution_type="limitMatchNewOrder", + trade_direction="buy", + price=price, + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + spot_servicer.subaccount_trades_list_responses.append( + exchange_spot_pb.SubaccountTradesListResponse( + trades=[trade], + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_trades = await api.fetch_subaccount_trades_list( + subaccount_id=trade.subaccount_id, + market_id=trade.market_id, + execution_type=trade.trade_execution_type, + direction=trade.trade_direction, + pagination=PaginationOption( + skip=0, + limit=100, + ), + ) + expected_trades = { + "trades": [ + { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "tradeDirection": trade.trade_direction, + "price": { + "price": price.price, + "quantity": price.quantity, + "timestamp": str(price.timestamp), + }, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + ], + } + + assert result_trades == expected_trades + + @pytest.mark.asyncio + async def test_fetch_orders_history( + self, + spot_servicer, + ): + order = exchange_spot_pb.SpotOrderHistory( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + is_active=True, + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + execution_type="limit", + order_type="buy_po", + price="0.000000000017541", + trigger_price="0", + quantity="50955000000000000000", + filled_quantity="1000000000000000", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + direction="buy", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + paging = exchange_spot_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + spot_servicer.orders_history_responses.append( + exchange_spot_pb.OrdersHistoryResponse( + orders=[order], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_orders = await api.fetch_orders_history( + subaccount_id=order.subaccount_id, + market_ids=[order.market_id], + order_types=[order.order_type], + direction=order.direction, + state=order.state, + execution_types=[order.execution_type], + trade_id="8662464_1_0", + active_markets_only=True, + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + expected_orders = { + "orders": [ + { + "orderHash": order.order_hash, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "executionType": order.execution_type, + "orderType": order.order_type, + "price": order.price, + "triggerPrice": order.trigger_price, + "quantity": order.quantity, + "filledQuantity": order.filled_quantity, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "direction": order.direction, + "txHash": order.tx_hash, + "isActive": order.is_active, + "cid": order.cid, + }, + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_orders == expected_orders + + @pytest.mark.asyncio + async def test_fetch_atomic_swap_history( + self, + spot_servicer, + ): + source_coin = exchange_spot_pb.Coin(denom="inj", amount="988987297011197594664") + dest_coin = exchange_spot_pb.Coin(denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", amount="54497408") + fee = exchange_spot_pb.Coin(denom="inj", amount="100000") + + atomic_swap = exchange_spot_pb.AtomicSwap( + sender="sender", + route="route", + source_coin=source_coin, + dest_coin=dest_coin, + fees=[fee], + contract_address="contract address", + index_by_sender=1, + index_by_sender_contract=2, + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + executed_at=1699644939364, + refund_amount="0", + ) + paging = exchange_spot_pb.Paging(total=5, to=5, count_by_subaccount=10, next=["next1", "next2"]) + setattr(paging, "from", 1) + + spot_servicer.atomic_swap_history_responses.append( + exchange_spot_pb.AtomicSwapHistoryResponse( + data=[atomic_swap], + paging=paging, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + result_history = await api.fetch_atomic_swap_history( + address=atomic_swap.sender, + contract_address=atomic_swap.contract_address, + pagination=PaginationOption( + skip=0, + limit=100, + from_number=1, + to_number=100, + ), + ) + expected_history = { + "data": [ + { + "contractAddress": atomic_swap.contract_address, + "destCoin": {"amount": dest_coin.amount, "denom": dest_coin.denom}, + "executedAt": str(atomic_swap.executed_at), + "fees": [{"amount": fee.amount, "denom": fee.denom}], + "indexBySender": atomic_swap.index_by_sender, + "indexBySenderContract": atomic_swap.index_by_sender_contract, + "refundAmount": atomic_swap.refund_amount, + "route": atomic_swap.route, + "sender": atomic_swap.sender, + "sourceCoin": {"amount": source_coin.amount, "denom": source_coin.denom}, + "txHash": atomic_swap.tx_hash, + } + ], + "paging": { + "total": str(paging.total), + "from": getattr(paging, "from"), + "to": paging.to, + "countBySubaccount": str(paging.count_by_subaccount), + "next": paging.next, + }, + } + + assert result_history == expected_history + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/__init__.py b/tests/client/indexer/stream_grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py new file mode 100644 index 00000000..f5579bee --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_account_stream.py @@ -0,0 +1,78 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_account_stream import IndexerGrpcAccountStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_accounts_rpc_pb2 as exchange_accounts_pb +from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer + + +@pytest.fixture +def account_servicer(): + return ConfigurableAccountQueryServicer() + + +class TestIndexerGrpcAccountStream: + @pytest.mark.asyncio + async def test_fetch_portfolio( + self, + account_servicer, + ): + deposit = exchange_accounts_pb.SubaccountDeposit( + total_balance="20", + available_balance="10", + ) + balance = exchange_accounts_pb.SubaccountBalance( + subaccount_id="0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000", + account_address="inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku", + denom="inj", + deposit=deposit, + ) + account_servicer.stream_subaccount_balance_responses.append( + exchange_accounts_pb.StreamSubaccountBalanceResponse(balance=balance, timestamp=1672218001897) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAccountStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = account_servicer + + balance_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: balance_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_subaccount_balance( + subaccount_id=balance.subaccount_id, + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + denoms=["inj"], + ) + ) + expected_balance_update = { + "balance": { + "accountAddress": balance.account_address, + "denom": balance.denom, + "deposit": { + "availableBalance": balance.deposit.available_balance, + "totalBalance": balance.deposit.total_balance, + }, + "subaccountId": balance.subaccount_id, + }, + "timestamp": "1672218001897", + } + + first_balance_update = await asyncio.wait_for(balance_updates.get(), timeout=1) + + assert first_balance_update == expected_balance_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_auction_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_auction_stream.py new file mode 100644 index 00000000..8c88d717 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_auction_stream.py @@ -0,0 +1,65 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_auction_stream import IndexerGrpcAuctionStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_auction_rpc_pb2 as exchange_auction_pb +from tests.client.indexer.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer + + +@pytest.fixture +def auction_servicer(): + return ConfigurableAuctionQueryServicer() + + +class TestIndexerGrpcAuctionStream: + @pytest.mark.asyncio + async def test_stream_oracle_prices_by_markets( + self, + auction_servicer, + ): + bidder = "inj1pdxq82m20fzkjn2th2mm5jp7t5ex6j6klf9cs5" + amount = "1000000000000000000" + round = 1 + timestamp = 1675426622603 + + auction_servicer.stream_bids_responses.append( + exchange_auction_pb.StreamBidsResponse(bidder=bidder, bid_amount=amount, round=round, timestamp=timestamp) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcAuctionStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = auction_servicer + + bid_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: bid_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_bids( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "bidAmount": amount, + "bidder": bidder, + "round": str(round), + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(bid_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_derivative_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_derivative_stream.py new file mode 100644 index 00000000..5dc2f79b --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_derivative_stream.py @@ -0,0 +1,709 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_derivative_stream import IndexerGrpcDerivativeStream +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb +from tests.client.indexer.configurable_derivative_query_servicer import ConfigurableDerivativeQueryServicer + + +@pytest.fixture +def derivative_servicer(): + return ConfigurableDerivativeQueryServicer() + + +class TestIndexerGrpcDerivativeStream: + @pytest.mark.asyncio + async def test_stream_market( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + quote_token_meta = exchange_derivative_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + perpetual_market_info = exchange_derivative_pb.PerpetualMarketInfo( + hourly_funding_rate_cap="0.000625", + hourly_interest_rate="0.00000416666", + next_funding_timestamp=1700064000, + funding_interval=3600, + ) + perpetual_market_funding = exchange_derivative_pb.PerpetualMarketFunding( + cumulative_funding="-82680.076492986572881307", + cumulative_price="-78.41752505919454668", + last_timestamp=1700004260, + ) + + market = exchange_derivative_pb.DerivativeMarketInfo( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + market_status="active", + ticker="INJ/USDT PERP", + oracle_base="0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3", + oracle_quote="0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588", + oracle_type="pyth", + oracle_scale_factor=6, + initial_margin_ratio="0.05", + maintenance_margin_ratio="0.02", + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + is_perpetual=True, + min_price_tick_size="100", + min_quantity_tick_size="0.0001", + perpetual_market_info=perpetual_market_info, + perpetual_market_funding=perpetual_market_funding, + ) + + derivative_servicer.stream_market_responses.append( + exchange_derivative_pb.StreamMarketResponse( + market=market, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + market_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: market_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_market( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[market.market_id], + ) + ) + expected_update = { + "market": { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "oracleBase": market.oracle_base, + "oracleQuote": market.oracle_quote, + "oracleType": market.oracle_type, + "oracleScaleFactor": market.oracle_scale_factor, + "initialMarginRatio": market.initial_margin_ratio, + "maintenanceMarginRatio": market.maintenance_margin_ratio, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "makerFeeRate": market.maker_fee_rate, + "takerFeeRate": market.taker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "isPerpetual": market.is_perpetual, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + "perpetualMarketInfo": { + "hourlyFundingRateCap": perpetual_market_info.hourly_funding_rate_cap, + "hourlyInterestRate": str(perpetual_market_info.hourly_interest_rate), + "nextFundingTimestamp": str(perpetual_market_info.next_funding_timestamp), + "fundingInterval": str(perpetual_market_info.funding_interval), + }, + "perpetualMarketFunding": { + "cumulativeFunding": perpetual_market_funding.cumulative_funding, + "cumulativePrice": perpetual_market_funding.cumulative_price, + "lastTimestamp": str(perpetual_market_funding.last_timestamp), + }, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(market_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orderbook_v2( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" + + buy = exchange_derivative_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_derivative_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_derivative_pb.DerivativeLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + derivative_servicer.stream_orderbook_v2_responses.append( + exchange_derivative_pb.StreamOrderbookV2Response( + orderbook=orderbook, + operation_type=operation_type, + timestamp=timestamp, + market_id=market_id, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + orderbook_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orderbook_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orderbook_v2( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[market_id], + ) + ) + expected_update = { + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + }, + "operationType": operation_type, + "timestamp": str(timestamp), + "marketId": market_id, + } + + first_update = await asyncio.wait_for(orderbook_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orderbook_update( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + buy = exchange_derivative_pb.PriceLevelUpdate( + price="0.000000000014198", + quantity="142000000000000000000", + is_active=True, + timestamp=1698982052141, + ) + sell = exchange_derivative_pb.PriceLevelUpdate( + price="0.00000000095699", + quantity="189000000000000000", + is_active=True, + timestamp=1698920369246, + ) + + level_updates = exchange_derivative_pb.OrderbookLevelUpdates( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + sequence=5506752, + buys=[buy], + sells=[sell], + updated_at=1698982083606, + ) + + derivative_servicer.stream_orderbook_update_responses.append( + exchange_derivative_pb.StreamOrderbookUpdateResponse( + orderbook_level_updates=level_updates, + operation_type=operation_type, + timestamp=timestamp, + market_id=level_updates.market_id, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + orderbook_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orderbook_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orderbook_update( + market_ids=[level_updates.market_id], + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "orderbookLevelUpdates": { + "marketId": level_updates.market_id, + "sequence": str(level_updates.sequence), + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "isActive": buy.is_active, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "isActive": sell.is_active, + "timestamp": str(sell.timestamp), + } + ], + "updatedAt": str(level_updates.updated_at), + }, + "operationType": operation_type, + "timestamp": str(timestamp), + "marketId": level_updates.market_id, + } + + first_update = await asyncio.wait_for(orderbook_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_positions( + self, + derivative_servicer, + ): + timestamp = 1672218001897 + + position = exchange_derivative_pb.DerivativePosition( + ticker="INJ/USDT PERP", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + subaccount_id="0x1383dabde57e5aed55960ee43e158ae7118057d3000000000000000000000000", + direction="short", + quantity="0.070294765766186502", + entry_price="15980281.340438795311756847", + margin="561065.540974", + liquidation_price="23492052.224777", + mark_price="16197000", + aggregate_reduce_only_quantity="0", + updated_at=1700161202147, + created_at=-62135596800000, + ) + + derivative_servicer.stream_positions_responses.append( + exchange_derivative_pb.StreamPositionsResponse( + position=position, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + positions = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: positions.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_positions( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[position.market_id], + subaccount_ids=[position.subaccount_id], + ) + ) + expected_update = { + "position": { + "ticker": position.ticker, + "marketId": position.market_id, + "subaccountId": position.subaccount_id, + "direction": position.direction, + "quantity": position.quantity, + "entryPrice": position.entry_price, + "margin": position.margin, + "liquidationPrice": position.liquidation_price, + "markPrice": position.mark_price, + "aggregateReduceOnlyQuantity": position.aggregate_reduce_only_quantity, + "createdAt": str(position.created_at), + "updatedAt": str(position.updated_at), + }, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(positions.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orders( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + order = exchange_derivative_pb.DerivativeLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + is_reduce_only=False, + margin="2280000000", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + order_number=0, + order_type="", + is_conditional=False, + trigger_at=0, + placed_order_hash="", + execution_type="", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + derivative_servicer.stream_orders_responses.append( + exchange_derivative_pb.StreamOrdersResponse( + order=order, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + orders_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orders_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orders( + market_ids=[order.market_id], + order_side=order.order_side, + subaccount_id=order.subaccount_id, + is_conditional="true", + order_type="", + include_inactive=True, + subaccount_total_orders=True, + trade_id="7959737_3_0", + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "order": { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "isReduceOnly": order.is_reduce_only, + "margin": order.margin, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "orderNumber": str(order.order_number), + "orderType": order.order_type, + "isConditional": order.is_conditional, + "triggerAt": str(order.trigger_at), + "placedOrderHash": order.placed_order_hash, + "executionType": order.execution_type, + "txHash": order.tx_hash, + "cid": order.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(orders_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_trades( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + position_delta = exchange_derivative_pb.PositionDelta( + trade_direction="buy", + execution_price="13945600", + execution_quantity="5", + execution_margin="69728000", + ) + + trade = exchange_derivative_pb.DerivativeTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", + trade_execution_type="limitMatchNewOrder", + is_liquidation=False, + position_delta=position_delta, + payout="0", + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + derivative_servicer.stream_trades_responses.append( + exchange_derivative_pb.StreamTradesResponse( + trade=trade, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + trade_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: trade_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_trades( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[trade.market_id], + subaccount_ids=[trade.subaccount_id], + execution_side=trade.execution_side, + direction=position_delta.trade_direction, + execution_types=[trade.trade_execution_type], + trade_id="7959737_3_0", + account_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + cid=trade.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + ) + expected_update = { + "trade": { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "isLiquidation": trade.is_liquidation, + "positionDelta": { + "tradeDirection": position_delta.trade_direction, + "executionPrice": position_delta.execution_price, + "executionQuantity": position_delta.execution_quantity, + "executionMargin": position_delta.execution_margin, + }, + "payout": trade.payout, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(trade_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orders_history( + self, + derivative_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + order = exchange_derivative_pb.DerivativeOrderHistory( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + is_active=True, + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + execution_type="limit", + order_type="buy_po", + price="0.000000000017541", + trigger_price="0", + quantity="50955000000000000000", + filled_quantity="1000000000000000", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + is_reduce_only=False, + direction="buy", + is_conditional=False, + trigger_at=0, + placed_order_hash="", + margin="2280000000", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + derivative_servicer.stream_orders_history_responses.append( + exchange_derivative_pb.StreamOrdersHistoryResponse( + order=order, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcDerivativeStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = derivative_servicer + + orders_history_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orders_history_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orders_history( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + subaccount_id=order.subaccount_id, + market_id=order.market_id, + order_types=[order.order_type], + direction=order.direction, + state=order.state, + execution_types=[order.execution_type], + ) + ) + expected_update = { + "order": { + "orderHash": order.order_hash, + "marketId": order.market_id, + "isActive": order.is_active, + "subaccountId": order.subaccount_id, + "executionType": order.execution_type, + "orderType": order.order_type, + "price": order.price, + "triggerPrice": order.trigger_price, + "quantity": order.quantity, + "filledQuantity": order.filled_quantity, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "isReduceOnly": order.is_reduce_only, + "direction": order.direction, + "isConditional": order.is_conditional, + "triggerAt": str(order.trigger_at), + "placedOrderHash": order.placed_order_hash, + "margin": order.margin, + "txHash": order.tx_hash, + "cid": order.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(orders_history_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_explorer_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_explorer_stream.py new file mode 100644 index 00000000..98eed906 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_explorer_stream.py @@ -0,0 +1,138 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_explorer_stream import IndexerGrpcExplorerStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_explorer_rpc_pb2 as exchange_explorer_pb +from tests.client.indexer.configurable_explorer_query_servicer import ConfigurableExplorerQueryServicer + + +@pytest.fixture +def explorer_servicer(): + return ConfigurableExplorerQueryServicer() + + +class TestIndexerGrpcAuctionStream: + @pytest.mark.asyncio + async def test_stream_txs( + self, + explorer_servicer, + ): + code = 5 + claim_id = 100 + tx_data = exchange_explorer_pb.StreamTxsResponse( + id="test id", + block_number=18138926, + block_timestamp="2023-11-07 23:19:55.371 +0000 UTC", + hash="0x3790ade2bea6c8605851ec89fa968adf2a2037a5ecac11ca95e99260508a3b7e", + codespace="test codespace", + messages='[{"type":"/cosmos.bank.v1beta1.MsgSend",' + '"value":{"from_address":"inj1phd706jqzd9wznkk5hgsfkrc8jqxv0kmlj0kex",' + '"to_address":"inj1d6qx83nhx3a3gx7e654x4su8hur5s83u84h2xc",' + '"amount":[{"denom":"factory/inj17vytdwqczqz72j65saukplrktd4gyfme5agf6c/weth",' + '"amount":"100000000000000000"}]}}]', + tx_number=221429, + error_log="", + code=code, + claim_ids=[claim_id], + ) + + explorer_servicer.stream_txs_responses.append(tx_data) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + txs_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: txs_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_txs( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "id": tx_data.id, + "blockNumber": str(tx_data.block_number), + "blockTimestamp": tx_data.block_timestamp, + "hash": tx_data.hash, + "codespace": tx_data.codespace, + "messages": tx_data.messages, + "txNumber": str(tx_data.tx_number), + "errorLog": tx_data.error_log, + "code": tx_data.code, + "claimIds": [str(claim_id)], + } + + first_update = await asyncio.wait_for(txs_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_blocks( + self, + explorer_servicer, + ): + block_info = exchange_explorer_pb.StreamBlocksResponse( + height=19034578, + proposer="injvalcons18x63wcw5hjxlf535lgn4qy20yer7mm0qedu0la", + moniker="InjectiveNode1", + block_hash="0x7f7bfe8caaa0eed042315d1447ef1ed726a80f5da23fdbe6831fc66775197db1", + parent_hash="0x44287ba5fad21d0109a3ec6f19d447580763e5a709e5a5ceb767174e99ae3bd8", + num_pre_commits=20, + num_txs=4, + timestamp="2023-11-29 20:23:33.842 +0000 UTC", + ) + + explorer_servicer.stream_blocks_responses.append(block_info) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcExplorerStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = explorer_servicer + + blocks_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: blocks_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_blocks( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "height": str(block_info.height), + "proposer": block_info.proposer, + "moniker": block_info.moniker, + "blockHash": block_info.block_hash, + "parentHash": block_info.parent_hash, + "numPreCommits": str(block_info.num_pre_commits), + "numTxs": str(block_info.num_txs), + "txs": [], + "timestamp": block_info.timestamp, + } + + first_update = await asyncio.wait_for(blocks_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_meta_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_meta_stream.py new file mode 100644 index 00000000..2a003c62 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_meta_stream.py @@ -0,0 +1,63 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_meta_stream import IndexerGrpcMetaStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_meta_rpc_pb2 as exchange_meta_pb +from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer + + +@pytest.fixture +def meta_servicer(): + return ConfigurableMetaQueryServicer() + + +class TestIndexerGrpcMetaStream: + @pytest.mark.asyncio + async def test_fetch_portfolio( + self, + meta_servicer, + ): + event = "test event" + new_endpoint = "new test endpoint" + timestamp = 1672218001897 + + meta_servicer.stream_keepalive_responses.append( + exchange_meta_pb.StreamKeepaliveResponse( + event=event, + new_endpoint=new_endpoint, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcMetaStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = meta_servicer + + keepalive_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: keepalive_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_keepalive( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = {"event": event, "newEndpoint": new_endpoint, "timestamp": str(timestamp)} + + first_update = await asyncio.wait_for(keepalive_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_oracle_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_oracle_stream.py new file mode 100644 index 00000000..3389948a --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_oracle_stream.py @@ -0,0 +1,109 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_oracle_stream import IndexerGrpcOracleStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_oracle_rpc_pb2 as exchange_oracle_pb +from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer + + +@pytest.fixture +def oracle_servicer(): + return ConfigurableOracleQueryServicer() + + +class TestIndexerGrpcOracleStream: + @pytest.mark.asyncio + async def test_stream_oracle_prices( + self, + oracle_servicer, + ): + price = "0.00000000000002" + timestamp = 1672218001897 + + oracle_servicer.stream_prices_responses.append( + exchange_oracle_pb.StreamPricesResponse( + price=price, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcOracleStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = oracle_servicer + + price_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: price_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_oracle_prices( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + base_symbol="Gold", + quote_symbol="USDT", + oracle_type="pricefeed", + ) + ) + expected_update = {"price": price, "timestamp": str(timestamp)} + + first_update = await asyncio.wait_for(price_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_oracle_prices_by_markets( + self, + oracle_servicer, + ): + price = "0.00000000000002" + timestamp = 1672218001897 + market_id = "0xa43d2be9861efb0d188b136cef0ae2150f80e08ec318392df654520dd359fcd7" + + oracle_servicer.stream_prices_by_markets_responses.append( + exchange_oracle_pb.StreamPricesByMarketsResponse( + price=price, + timestamp=timestamp, + market_id=market_id, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcOracleStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = oracle_servicer + + price_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: price_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_oracle_prices_by_markets( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[market_id], + ) + ) + expected_update = {"price": price, "timestamp": str(timestamp), "marketId": market_id} + + first_update = await asyncio.wait_for(price_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_portfolio_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_portfolio_stream.py new file mode 100644 index 00000000..2f09ed40 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_portfolio_stream.py @@ -0,0 +1,76 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_portfolio_stream import IndexerGrpcPortfolioStream +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_portfolio_rpc_pb2 as exchange_portfolio_pb +from tests.client.indexer.configurable_portfolio_query_servicer import ConfigurablePortfolioQueryServicer + + +@pytest.fixture +def portfolio_servicer(): + return ConfigurablePortfolioQueryServicer() + + +class TestIndexerGrpcPortfolioStream: + @pytest.mark.asyncio + async def test_stream_account_portfolio( + self, + portfolio_servicer, + ): + update_type = "total_balance" + denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" + amount = "1000000000000000000" + subaccount_id = "0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000000" + timestamp = 1675426622603 + + portfolio_servicer.stream_account_portfolio_responses.append( + exchange_portfolio_pb.StreamAccountPortfolioResponse( + type=update_type, + denom=denom, + amount=amount, + subaccount_id=subaccount_id, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcPortfolioStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = portfolio_servicer + + portfolio_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: portfolio_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_account_portfolio( + account_address="test_address", + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + subaccount_id=subaccount_id, + update_type=update_type, + ) + ) + expected_update = { + "type": update_type, + "denom": denom, + "amount": amount, + "subaccountId": subaccount_id, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(portfolio_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/client/indexer/stream_grpc/test_indexer_grpc_spot_stream.py b/tests/client/indexer/stream_grpc/test_indexer_grpc_spot_stream.py new file mode 100644 index 00000000..d980e143 --- /dev/null +++ b/tests/client/indexer/stream_grpc/test_indexer_grpc_spot_stream.py @@ -0,0 +1,584 @@ +import asyncio + +import grpc +import pytest + +from pyinjective.client.indexer.grpc_stream.indexer_grpc_spot_stream import IndexerGrpcSpotStream +from pyinjective.client.model.pagination import PaginationOption +from pyinjective.core.network import Network +from pyinjective.proto.exchange import injective_spot_exchange_rpc_pb2 as exchange_spot_pb +from tests.client.indexer.configurable_spot_query_servicer import ConfigurableSpotQueryServicer + + +@pytest.fixture +def spot_servicer(): + return ConfigurableSpotQueryServicer() + + +class TestIndexerGrpcSpotStream: + @pytest.mark.asyncio + async def test_stream_markets( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + base_token_meta = exchange_spot_pb.TokenMeta( + name="Injective Protocol", + address="0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", + symbol="INJ", + logo="https://static.alchemyapi.io/images/assets/7226.png", + decimals=18, + updated_at=1683119359318, + ) + quote_token_meta = exchange_spot_pb.TokenMeta( + name="Testnet Tether USDT", + address="0x0000000000000000000000000000000000000000", + symbol="USDT", + logo="https://static.alchemyapi.io/images/assets/825.png", + decimals=6, + updated_at=1683119359320, + ) + + market = exchange_spot_pb.SpotMarketInfo( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + market_status="active", + ticker="INJ/USDT", + base_denom="inj", + base_token_meta=base_token_meta, + quote_denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + quote_token_meta=quote_token_meta, + maker_fee_rate="-0.0001", + taker_fee_rate="0.001", + service_provider_fee="0.4", + min_price_tick_size="0.000000000000001", + min_quantity_tick_size="1000000000000000", + ) + + spot_servicer.stream_markets_responses.append( + exchange_spot_pb.StreamMarketsResponse( + market=market, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + market_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: market_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_markets( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[market.market_id], + ) + ) + expected_update = { + "market": { + "marketId": market.market_id, + "marketStatus": market.market_status, + "ticker": market.ticker, + "baseDenom": market.base_denom, + "baseTokenMeta": { + "name": market.base_token_meta.name, + "address": market.base_token_meta.address, + "symbol": market.base_token_meta.symbol, + "logo": market.base_token_meta.logo, + "decimals": market.base_token_meta.decimals, + "updatedAt": str(market.base_token_meta.updated_at), + }, + "quoteDenom": market.quote_denom, + "quoteTokenMeta": { + "name": market.quote_token_meta.name, + "address": market.quote_token_meta.address, + "symbol": market.quote_token_meta.symbol, + "logo": market.quote_token_meta.logo, + "decimals": market.quote_token_meta.decimals, + "updatedAt": str(market.quote_token_meta.updated_at), + }, + "takerFeeRate": market.taker_fee_rate, + "makerFeeRate": market.maker_fee_rate, + "serviceProviderFee": market.service_provider_fee, + "minPriceTickSize": market.min_price_tick_size, + "minQuantityTickSize": market.min_quantity_tick_size, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(market_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orderbook_v2( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" + + buy = exchange_spot_pb.PriceLevel( + price="0.000000000014198", + quantity="142000000000000000000", + timestamp=1698982052141, + ) + sell = exchange_spot_pb.PriceLevel( + price="0.00000000095699", + quantity="189000000000000000", + timestamp=1698920369246, + ) + + orderbook = exchange_spot_pb.SpotLimitOrderbookV2( + buys=[buy], + sells=[sell], + sequence=5506752, + timestamp=1698982083606, + ) + + spot_servicer.stream_orderbook_v2_responses.append( + exchange_spot_pb.StreamOrderbookV2Response( + orderbook=orderbook, + operation_type=operation_type, + timestamp=timestamp, + market_id=market_id, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + orderbook_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orderbook_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orderbook_v2( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[market_id], + ) + ) + expected_update = { + "orderbook": { + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "timestamp": str(sell.timestamp), + } + ], + "sequence": str(orderbook.sequence), + "timestamp": str(orderbook.timestamp), + }, + "operationType": operation_type, + "timestamp": str(timestamp), + "marketId": market_id, + } + + first_update = await asyncio.wait_for(orderbook_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orderbook_update( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + buy = exchange_spot_pb.PriceLevelUpdate( + price="0.000000000014198", + quantity="142000000000000000000", + is_active=True, + timestamp=1698982052141, + ) + sell = exchange_spot_pb.PriceLevelUpdate( + price="0.00000000095699", + quantity="189000000000000000", + is_active=True, + timestamp=1698920369246, + ) + + level_updates = exchange_spot_pb.OrderbookLevelUpdates( + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + sequence=5506752, + buys=[buy], + sells=[sell], + updated_at=1698982083606, + ) + + spot_servicer.stream_orderbook_update_responses.append( + exchange_spot_pb.StreamOrderbookUpdateResponse( + orderbook_level_updates=level_updates, + operation_type=operation_type, + timestamp=timestamp, + market_id=level_updates.market_id, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + orderbook_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orderbook_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orderbook_update( + market_ids=[level_updates.market_id], + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "orderbookLevelUpdates": { + "marketId": level_updates.market_id, + "sequence": str(level_updates.sequence), + "buys": [ + { + "price": buy.price, + "quantity": buy.quantity, + "isActive": buy.is_active, + "timestamp": str(buy.timestamp), + } + ], + "sells": [ + { + "price": sell.price, + "quantity": sell.quantity, + "isActive": sell.is_active, + "timestamp": str(sell.timestamp), + } + ], + "updatedAt": str(level_updates.updated_at), + }, + "operationType": operation_type, + "timestamp": str(timestamp), + "marketId": level_updates.market_id, + } + + first_update = await asyncio.wait_for(orderbook_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orders( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + order = exchange_spot_pb.SpotLimitOrder( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + order_side="buy", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + price="0.000000000017541", + quantity="50955000000000000000", + unfilled_quantity="50955000000000000000", + trigger_price="0", + fee_recipient="inj1tcjf7r5vksr0g80pdcdada44teauwkkahelyfy", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + spot_servicer.stream_orders_responses.append( + exchange_spot_pb.StreamOrdersResponse( + order=order, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + orders_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orders_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orders( + market_ids=[order.market_id], + order_side=order.order_side, + subaccount_id=order.subaccount_id, + include_inactive=True, + subaccount_total_orders=True, + trade_id="7959737_3_0", + cid=order.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + ) + ) + expected_update = { + "order": { + "orderHash": order.order_hash, + "orderSide": order.order_side, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "price": order.price, + "quantity": order.quantity, + "unfilledQuantity": order.unfilled_quantity, + "triggerPrice": order.trigger_price, + "feeRecipient": order.fee_recipient, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "txHash": order.tx_hash, + "cid": order.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(orders_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_trades( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + price = exchange_spot_pb.PriceLevel( + price="0.000000000006024", + quantity="10000000000000000", + timestamp=1677563766350, + ) + + trade = exchange_spot_pb.SpotTrade( + order_hash="0xe549e4750287c93fcc8dec24f319c15025e07e89a8d0937be2b3865ed79d9da7", + subaccount_id="0xc7dca7c15c364865f77a4fb67ab11dc95502e6fe000000000000000000000001", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + trade_execution_type="limitMatchNewOrder", + trade_direction="buy", + price=price, + fee="36.144", + executed_at=1677563766350, + fee_recipient="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + trade_id="8662464_1_0", + execution_side="taker", + cid="cid1", + ) + + spot_servicer.stream_trades_responses.append( + exchange_spot_pb.StreamTradesResponse( + trade=trade, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + trade_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: trade_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_trades( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + market_ids=[trade.market_id], + subaccount_ids=[trade.subaccount_id], + execution_side=trade.execution_side, + direction=trade.trade_direction, + execution_types=[trade.trade_execution_type], + trade_id="7959737_3_0", + account_address="inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt", + cid=trade.cid, + pagination=PaginationOption( + skip=0, + limit=100, + start_time=1699544939364, + end_time=1699744939364, + ), + ) + ) + expected_update = { + "trade": { + "orderHash": trade.order_hash, + "subaccountId": trade.subaccount_id, + "marketId": trade.market_id, + "tradeExecutionType": trade.trade_execution_type, + "tradeDirection": trade.trade_direction, + "price": { + "price": price.price, + "quantity": price.quantity, + "timestamp": str(price.timestamp), + }, + "fee": trade.fee, + "executedAt": str(trade.executed_at), + "feeRecipient": trade.fee_recipient, + "tradeId": trade.trade_id, + "executionSide": trade.execution_side, + "cid": trade.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(trade_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + @pytest.mark.asyncio + async def test_stream_orders_history( + self, + spot_servicer, + ): + operation_type = "update" + timestamp = 1672218001897 + + order = exchange_spot_pb.SpotOrderHistory( + order_hash="0x14e43adbb3302db28bcd0619068227ebca880cdd66cdfc8b4a662bcac0777849", + market_id="0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", + is_active=True, + subaccount_id="0x5e249f0e8cb406f41de16e1bd6f6b55e7bc75add000000000000000000000004", + execution_type="limit", + order_type="buy_po", + price="0.000000000017541", + trigger_price="0", + quantity="50955000000000000000", + filled_quantity="1000000000000000", + state="booked", + created_at=1699644939364, + updated_at=1699644939364, + direction="buy", + tx_hash="0x0000000000000000000000000000000000000000000000000000000000000000", + cid="cid1", + ) + + spot_servicer.stream_orders_history_responses.append( + exchange_spot_pb.StreamOrdersHistoryResponse( + order=order, + operation_type=operation_type, + timestamp=timestamp, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + + api = IndexerGrpcSpotStream(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = spot_servicer + + orders_history_updates = asyncio.Queue() + end_event = asyncio.Event() + + callback = lambda update: orders_history_updates.put_nowait(update) + error_callback = lambda exception: pytest.fail(str(exception)) + end_callback = lambda: end_event.set() + + asyncio.get_event_loop().create_task( + api.stream_orders_history( + callback=callback, + on_end_callback=end_callback, + on_status_callback=error_callback, + subaccount_id=order.subaccount_id, + market_id=order.market_id, + order_types=[order.order_type], + direction=order.direction, + state=order.state, + execution_types=[order.execution_type], + ) + ) + expected_update = { + "order": { + "orderHash": order.order_hash, + "marketId": order.market_id, + "subaccountId": order.subaccount_id, + "executionType": order.execution_type, + "orderType": order.order_type, + "price": order.price, + "triggerPrice": order.trigger_price, + "quantity": order.quantity, + "filledQuantity": order.filled_quantity, + "state": order.state, + "createdAt": str(order.created_at), + "updatedAt": str(order.updated_at), + "direction": order.direction, + "txHash": order.tx_hash, + "isActive": order.is_active, + "cid": order.cid, + }, + "operationType": operation_type, + "timestamp": str(timestamp), + } + + first_update = await asyncio.wait_for(orders_history_updates.get(), timeout=1) + + assert first_update == expected_update + assert end_event.is_set() + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/core/tx/__init__.py b/tests/core/tx/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/core/tx/grpc/__init__.py b/tests/core/tx/grpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/core/tx/grpc/configurable_tx_query_servicer.py b/tests/core/tx/grpc/configurable_tx_query_servicer.py new file mode 100644 index 00000000..db8267e5 --- /dev/null +++ b/tests/core/tx/grpc/configurable_tx_query_servicer.py @@ -0,0 +1,20 @@ +from collections import deque + +from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, service_pb2_grpc as tx_service_grpc + + +class ConfigurableTxQueryServicer(tx_service_grpc.ServiceServicer): + def __init__(self): + super().__init__() + self.simulate_responses = deque() + self.get_tx_responses = deque() + self.broadcast_responses = deque() + + async def Simulate(self, request: tx_service.SimulateRequest, context=None, metadata=None): + return self.simulate_responses.pop() + + async def GetTx(self, request: tx_service.GetTxRequest, context=None, metadata=None): + return self.get_tx_responses.pop() + + async def BroadcastTx(self, request: tx_service.BroadcastTxRequest, context=None, metadata=None): + return self.broadcast_responses.pop() diff --git a/tests/core/tx/grpc/test_tx_grpc_api.py b/tests/core/tx/grpc/test_tx_grpc_api.py new file mode 100644 index 00000000..84b62817 --- /dev/null +++ b/tests/core/tx/grpc/test_tx_grpc_api.py @@ -0,0 +1,207 @@ +import base64 + +import grpc +import pytest +from google.protobuf import any_pb2 + +from pyinjective.core.network import Network +from pyinjective.core.tx.grpc.tx_grpc_api import TxGrpcApi +from pyinjective.proto.cosmos.base.abci.v1beta1 import abci_pb2 as abci_type +from pyinjective.proto.cosmos.base.v1beta1 import coin_pb2 as coin_pb +from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, tx_pb2 +from pyinjective.proto.injective.crypto.v1beta1.ethsecp256k1 import keys_pb2 as keys_pb +from tests.core.tx.grpc.configurable_tx_query_servicer import ConfigurableTxQueryServicer + + +@pytest.fixture +def tx_servicer(): + return ConfigurableTxQueryServicer() + + +class TestTxGrpcApi: + @pytest.mark.asyncio + async def test_simulate( + self, + tx_servicer, + ): + gas_info = abci_type.GasInfo( + gas_wanted=130000, + gas_used=120000, + ) + simulation_result = abci_type.Result(log="Result log") + + tx_servicer.simulate_responses.append( + tx_service.SimulateResponse( + gas_info=gas_info, + result=simulation_result, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = TxGrpcApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = tx_servicer + + result_simulate = await api.simulate(tx_bytes="Transaction content".encode()) + expected_simulate = { + "gasInfo": {"gasUsed": str(gas_info.gas_used), "gasWanted": str(gas_info.gas_wanted)}, + "result": { + "data": "", + "events": [], + "log": simulation_result.log, + "msgResponses": [], + }, + } + + assert result_simulate == expected_simulate + + @pytest.mark.asyncio + async def test_get_tx( + self, + tx_servicer, + ): + tx_body = tx_pb2.TxBody( + memo="test memo", + timeout_height=17518637, + ) + + pub_key = keys_pb.PubKey(key=b"\002\200T< /\340\341IC\260n\372\373\314&\3751A\034HfMk\255[ai\334\3303t\375") + any_pub_key = any_pb2.Any() + any_pub_key.Pack(pub_key, type_url_prefix="") + + signer_info = tx_pb2.SignerInfo( + public_key=any_pub_key, + sequence=211255, + ) + fee = tx_pb2.Fee( + amount=[coin_pb.Coin(denom="inj", amount="988987297011197594664")], + gas_limit=104757, + ) + auth_info = tx_pb2.AuthInfo( + signer_infos=[signer_info], + fee=fee, + ) + signature = ( + "\036~\024\202^t\252\346KB\377\333\266jV\030\300\353\340^\021_\227\236hc\010m\316U\314-:kK\0" + "07\337$K\275\303O\310\007\016\r\305c1\rcl\204L\323T\230\222\373\266\007/\261'" + ).encode() + transaction = tx_pb2.Tx(body=tx_body, auth_info=auth_info, signatures=[signature]) + + transaction_response = abci_type.TxResponse( + height=17518608, + txhash="D265527E3171C47D01D7EC9B839A95F8F794D4E683F26F5564025961C96EFDDA", + data=( + "126F0A252F636F736D6F732E617574687A2E763162657461312E4D736745786563526573706F6E736512460A440A42307834" + "3166303165366232666464336234633036316638343232356661656530333335366462386431376562653736313566613932" + "32663132363861666434316136" + ), + ) + + tx_servicer.get_tx_responses.append(tx_service.GetTxResponse(tx=transaction, tx_response=transaction_response)) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = TxGrpcApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = tx_servicer + + result_tx = await api.fetch_tx(hash=transaction_response.txhash) + expected_tx = { + "tx": { + "authInfo": { + "fee": { + "amount": [{"amount": fee.amount[0].amount, "denom": fee.amount[0].denom}], + "gasLimit": str(fee.gas_limit), + "granter": "", + "payer": "", + }, + "signerInfos": [ + { + "publicKey": { + "@type": "/injective.crypto.v1beta1.ethsecp256k1.PubKey", + "key": base64.b64encode(pub_key.key).decode(), + }, + "sequence": str(signer_info.sequence), + } + ], + }, + "body": { + "extensionOptions": [], + "memo": tx_body.memo, + "messages": [], + "nonCriticalExtensionOptions": [], + "timeoutHeight": str(tx_body.timeout_height), + }, + "signatures": [base64.b64encode(signature).decode()], + }, + "txResponse": { + "code": 0, + "codespace": "", + "data": transaction_response.data, + "events": [], + "gasUsed": "0", + "gasWanted": "0", + "height": str(transaction_response.height), + "info": "", + "logs": [], + "rawLog": "", + "timestamp": "", + "txhash": transaction_response.txhash, + }, + } + + assert result_tx == expected_tx + + @pytest.mark.asyncio + async def test_broadcast( + self, + tx_servicer, + ): + transaction_response = abci_type.TxResponse( + height=17518608, + txhash="D265527E3171C47D01D7EC9B839A95F8F794D4E683F26F5564025961C96EFDDA", + data=( + "126F0A252F636F736D6F732E617574687A2E763162657461312E4D736745786563526573706F6E736512460A440A42307834" + "3166303165366232666464336234633036316638343232356661656530333335366462386431376562653736313566613932" + "32663132363861666434316136" + ), + ) + + tx_servicer.broadcast_responses.append( + tx_service.BroadcastTxResponse( + tx_response=transaction_response, + ) + ) + + network = Network.devnet() + channel = grpc.aio.insecure_channel(network.grpc_endpoint) + + api = TxGrpcApi(channel=channel, metadata_provider=lambda: self._dummy_metadata_provider()) + api._stub = tx_servicer + + result_broadcast = await api.broadcast( + tx_bytes="Transaction content".encode(), + mode=tx_service.BroadcastMode.BROADCAST_MODE_SYNC, + ) + expected_broadcast = { + "txResponse": { + "code": 0, + "codespace": "", + "data": transaction_response.data, + "events": [], + "gasUsed": "0", + "gasWanted": "0", + "height": str(transaction_response.height), + "info": "", + "logs": [], + "rawLog": "", + "timestamp": "", + "txhash": transaction_response.txhash, + } + } + + assert result_broadcast == expected_broadcast + + async def _dummy_metadata_provider(self): + return None diff --git a/tests/rpc_fixtures/configurable_servicers.py b/tests/rpc_fixtures/configurable_servicers.py deleted file mode 100644 index 8c46dd2d..00000000 --- a/tests/rpc_fixtures/configurable_servicers.py +++ /dev/null @@ -1,29 +0,0 @@ -from collections import deque - -from pyinjective.proto.exchange import injective_derivative_exchange_rpc_pb2, injective_spot_exchange_rpc_pb2 -from pyinjective.proto.exchange.injective_derivative_exchange_rpc_pb2_grpc import InjectiveDerivativeExchangeRPCServicer -from pyinjective.proto.exchange.injective_spot_exchange_rpc_pb2_grpc import InjectiveSpotExchangeRPCServicer - - -class ConfigurableInjectiveSpotExchangeRPCServicer(InjectiveSpotExchangeRPCServicer): - def __init__(self): - super().__init__() - self.markets_queue = deque() - - async def Markets(self, request: injective_spot_exchange_rpc_pb2.MarketsRequest, context=None): - return self.markets_queue.pop() - - -class ConfigurableInjectiveDerivativeExchangeRPCServicer(InjectiveDerivativeExchangeRPCServicer): - def __init__(self): - super().__init__() - self.markets_queue = deque() - self.binary_option_markets_queue = deque() - - async def Markets(self, request: injective_derivative_exchange_rpc_pb2.MarketsRequest, context=None): - return self.markets_queue.pop() - - async def BinaryOptionsMarkets( - self, request: injective_derivative_exchange_rpc_pb2.BinaryOptionsMarketsRequest, context=None - ): - return self.binary_option_markets_queue.pop() diff --git a/tests/test_async_client.py b/tests/test_async_client.py index c2bc2335..7780e088 100644 --- a/tests/test_async_client.py +++ b/tests/test_async_client.py @@ -5,10 +5,8 @@ from pyinjective.async_client import AsyncClient from pyinjective.core.network import Network from pyinjective.proto.exchange import injective_derivative_exchange_rpc_pb2, injective_spot_exchange_rpc_pb2 -from tests.rpc_fixtures.configurable_servicers import ( - ConfigurableInjectiveDerivativeExchangeRPCServicer, - ConfigurableInjectiveSpotExchangeRPCServicer, -) +from tests.client.indexer.configurable_derivative_query_servicer import ConfigurableDerivativeQueryServicer +from tests.client.indexer.configurable_spot_query_servicer import ConfigurableSpotQueryServicer from tests.rpc_fixtures.markets_fixtures import ape_token_meta # noqa: F401 from tests.rpc_fixtures.markets_fixtures import ape_usdt_spot_market_meta # noqa: F401 from tests.rpc_fixtures.markets_fixtures import btc_usdt_perp_market_meta # noqa: F401 @@ -24,12 +22,12 @@ @pytest.fixture def spot_servicer(): - return ConfigurableInjectiveSpotExchangeRPCServicer() + return ConfigurableSpotQueryServicer() @pytest.fixture def derivative_servicer(): - return ConfigurableInjectiveDerivativeExchangeRPCServicer() + return ConfigurableDerivativeQueryServicer() class TestAsyncClient: @@ -60,7 +58,7 @@ async def test_get_account_logs_exception(self, caplog): ) with caplog.at_level(logging.DEBUG): - await client.get_account(address="") + await client.fetch_account(address="") expected_log_message_prefix = "error while fetching sequence and number " found_log = next( @@ -81,15 +79,15 @@ async def test_initialize_tokens_and_markets( btc_usdt_perp_market_meta, first_match_bet_market_meta, ): - spot_servicer.markets_queue.append( + spot_servicer.markets_responses.append( injective_spot_exchange_rpc_pb2.MarketsResponse( markets=[inj_usdt_spot_market_meta, ape_usdt_spot_market_meta] ) ) - derivative_servicer.markets_queue.append( + derivative_servicer.markets_responses.append( injective_derivative_exchange_rpc_pb2.MarketsResponse(markets=[btc_usdt_perp_market_meta]) ) - derivative_servicer.binary_option_markets_queue.append( + derivative_servicer.binary_options_markets_responses.append( injective_derivative_exchange_rpc_pb2.BinaryOptionsMarketsResponse(markets=[first_match_bet_market_meta]) ) @@ -98,8 +96,8 @@ async def test_initialize_tokens_and_markets( insecure=False, ) - client.stubSpotExchange = spot_servicer - client.stubDerivativeExchange = derivative_servicer + client.exchange_spot_api._stub = spot_servicer + client.exchange_derivative_api._stub = derivative_servicer await client._initialize_tokens_and_markets() diff --git a/tests/test_async_client_deprecation_warnings.py b/tests/test_async_client_deprecation_warnings.py new file mode 100644 index 00000000..13ced627 --- /dev/null +++ b/tests/test_async_client_deprecation_warnings.py @@ -0,0 +1,1698 @@ +from warnings import catch_warnings + +import pytest + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network +from pyinjective.proto.cosmos.authz.v1beta1 import query_pb2 as authz_query +from pyinjective.proto.cosmos.bank.v1beta1 import query_pb2 as bank_query_pb +from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service +from pyinjective.proto.exchange import ( + injective_accounts_rpc_pb2 as exchange_accounts_pb, + injective_auction_rpc_pb2 as exchange_auction_pb, + injective_derivative_exchange_rpc_pb2 as exchange_derivative_pb, + injective_explorer_rpc_pb2 as exchange_explorer_pb, + injective_insurance_rpc_pb2 as exchange_insurance_pb, + injective_meta_rpc_pb2 as exchange_meta_pb, + injective_oracle_rpc_pb2 as exchange_oracle_pb, + injective_portfolio_rpc_pb2 as exchange_portfolio_pb, + injective_spot_exchange_rpc_pb2 as exchange_spot_pb, +) +from pyinjective.proto.injective.stream.v1beta1 import query_pb2 as chain_stream_pb +from pyinjective.proto.injective.types.v1beta1 import account_pb2 as account_pb +from tests.client.chain.grpc.configurable_auth_query_servicer import ConfigurableAuthQueryServicer +from tests.client.chain.grpc.configurable_autz_query_servicer import ConfigurableAuthZQueryServicer +from tests.client.chain.grpc.configurable_bank_query_servicer import ConfigurableBankQueryServicer +from tests.client.chain.stream_grpc.configurable_chain_stream_query_servicer import ConfigurableChainStreamQueryServicer +from tests.client.indexer.configurable_account_query_servicer import ConfigurableAccountQueryServicer +from tests.client.indexer.configurable_auction_query_servicer import ConfigurableAuctionQueryServicer +from tests.client.indexer.configurable_derivative_query_servicer import ConfigurableDerivativeQueryServicer +from tests.client.indexer.configurable_explorer_query_servicer import ConfigurableExplorerQueryServicer +from tests.client.indexer.configurable_insurance_query_servicer import ConfigurableInsuranceQueryServicer +from tests.client.indexer.configurable_meta_query_servicer import ConfigurableMetaQueryServicer +from tests.client.indexer.configurable_oracle_query_servicer import ConfigurableOracleQueryServicer +from tests.client.indexer.configurable_portfolio_query_servicer import ConfigurablePortfolioQueryServicer +from tests.client.indexer.configurable_spot_query_servicer import ConfigurableSpotQueryServicer +from tests.core.tx.grpc.configurable_tx_query_servicer import ConfigurableTxQueryServicer + + +@pytest.fixture +def account_servicer(): + return ConfigurableAccountQueryServicer() + + +@pytest.fixture +def auction_servicer(): + return ConfigurableAuctionQueryServicer() + + +@pytest.fixture +def auth_servicer(): + return ConfigurableAuthQueryServicer() + + +@pytest.fixture +def authz_servicer(): + return ConfigurableAuthZQueryServicer() + + +@pytest.fixture +def bank_servicer(): + return ConfigurableBankQueryServicer() + + +@pytest.fixture +def chain_stream_servicer(): + return ConfigurableChainStreamQueryServicer() + + +@pytest.fixture +def derivative_servicer(): + return ConfigurableDerivativeQueryServicer() + + +@pytest.fixture +def explorer_servicer(): + return ConfigurableExplorerQueryServicer() + + +@pytest.fixture +def insurance_servicer(): + return ConfigurableInsuranceQueryServicer() + + +@pytest.fixture +def meta_servicer(): + return ConfigurableMetaQueryServicer() + + +@pytest.fixture +def oracle_servicer(): + return ConfigurableOracleQueryServicer() + + +@pytest.fixture +def portfolio_servicer(): + return ConfigurablePortfolioQueryServicer() + + +@pytest.fixture +def spot_servicer(): + return ConfigurableSpotQueryServicer() + + +@pytest.fixture +def tx_servicer(): + return ConfigurableTxQueryServicer() + + +class TestAsyncClientDeprecationWarnings: + def test_insecure_parameter_deprecation_warning( + self, + auth_servicer, + ): + with catch_warnings(record=True) as all_warnings: + AsyncClient( + network=Network.local(), + insecure=False, + ) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "insecure parameter in AsyncClient is no longer used and will be deprecated" + ) + + @pytest.mark.asyncio + async def test_get_account_deprecation_warning( + self, + auth_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubAuth = auth_servicer + auth_servicer.account_responses.append(account_pb.EthAccount()) + + with catch_warnings(record=True) as all_warnings: + await client.get_account(address="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_account instead" + + @pytest.mark.asyncio + async def test_get_bank_balance_deprecation_warning( + self, + bank_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubBank = bank_servicer + bank_servicer.balance_responses.append(bank_query_pb.QueryBalanceResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_bank_balance(address="", denom="inj") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_bank_balance instead" + + @pytest.mark.asyncio + async def test_get_bank_balances_deprecation_warning( + self, + bank_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubBank = bank_servicer + bank_servicer.balances_responses.append(bank_query_pb.QueryAllBalancesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_bank_balances(address="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_bank_balances instead" + + @pytest.mark.asyncio + async def test_get_order_states_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + account_servicer.order_states_responses.append(exchange_accounts_pb.OrderStatesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_order_states(spot_order_hashes=["hash1"], derivative_order_hashes=["hash2"]) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_order_states instead" + + @pytest.mark.asyncio + async def test_get_subaccount_list_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + account_servicer.subaccounts_list_responses.append(exchange_accounts_pb.SubaccountsListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_subaccount_list(account_address="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_subaccounts_list instead" + + @pytest.mark.asyncio + async def test_get_subaccount_balances_list_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + account_servicer.subaccount_balances_list_responses.append( + exchange_accounts_pb.SubaccountBalancesListResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.get_subaccount_balances_list(subaccount_id="", denoms=[]) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_subaccount_balances_list instead" + ) + + @pytest.mark.asyncio + async def test_get_subaccount_balance_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + account_servicer.subaccount_balance_responses.append(exchange_accounts_pb.SubaccountBalanceEndpointResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_subaccount_balance(subaccount_id="", denom="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_balance instead" + + @pytest.mark.asyncio + async def test_get_subaccount_history_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + account_servicer.subaccount_history_responses.append(exchange_accounts_pb.SubaccountHistoryResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_subaccount_history(subaccount_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_subaccount_history instead" + + @pytest.mark.asyncio + async def test_get_subaccount_order_summary_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + account_servicer.subaccount_order_summary_responses.append( + exchange_accounts_pb.SubaccountOrderSummaryResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.get_subaccount_order_summary(subaccount_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_subaccount_order_summary instead" + ) + + @pytest.mark.asyncio + async def test_get_portfolio_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + account_servicer.portfolio_responses.append(exchange_accounts_pb.PortfolioResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_portfolio(account_address="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_portfolio instead" + + @pytest.mark.asyncio + async def test_get_rewards_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + account_servicer.rewards_responses.append(exchange_accounts_pb.RewardsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_rewards(account_address="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_rewards instead" + + @pytest.mark.asyncio + async def test_stream_subaccount_balance_deprecation_warning( + self, + account_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + account_servicer.stream_subaccount_balance_responses.append( + exchange_accounts_pb.StreamSubaccountBalanceResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.stream_subaccount_balance(subaccount_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_subaccount_balance_updates instead" + ) + + @pytest.mark.asyncio + async def test_get_grants_deprecation_warning( + self, + authz_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubAuthz = authz_servicer + authz_servicer.grants_responses.append(authz_query.QueryGrantsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_grants(granter="granter", grantee="grantee") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_grants instead" + + @pytest.mark.asyncio + async def test_simulate_deprecation_warning( + self, + tx_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubTx = tx_servicer + tx_servicer.simulate_responses.append(tx_service.SimulateResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.simulate_tx(tx_byte="".encode()) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use simulate instead" + + @pytest.mark.asyncio + async def test_get_tx_deprecation_warning( + self, + tx_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubTx = tx_servicer + tx_servicer.get_tx_responses.append(tx_service.GetTxResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_tx(tx_hash="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_tx instead" + + @pytest.mark.asyncio + async def test_send_tx_sync_mode_deprecation_warning( + self, + tx_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubTx = tx_servicer + tx_servicer.broadcast_responses.append(tx_service.BroadcastTxResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.send_tx_sync_mode(tx_byte="".encode()) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use broadcast_tx_sync_mode instead" + + @pytest.mark.asyncio + async def test_send_tx_async_mode_deprecation_warning( + self, + tx_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubTx = tx_servicer + tx_servicer.broadcast_responses.append(tx_service.BroadcastTxResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.send_tx_async_mode(tx_byte="".encode()) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use broadcast_tx_async_mode instead" + + @pytest.mark.asyncio + async def test_send_tx_block_mode_deprecation_warning( + self, + tx_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubTx = tx_servicer + tx_servicer.broadcast_responses.append(tx_service.BroadcastTxResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.send_tx_block_mode(tx_byte="".encode()) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. BLOCK broadcast mode should not be used" + ) + + @pytest.mark.asyncio + async def test_ping_deprecation_warning( + self, + meta_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubMeta = meta_servicer + meta_servicer.ping_responses.append(exchange_meta_pb.PingResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.ping() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_ping instead" + + @pytest.mark.asyncio + async def test_version_deprecation_warning( + self, + meta_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubMeta = meta_servicer + meta_servicer.version_responses.append(exchange_meta_pb.VersionResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.version() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_version instead" + + @pytest.mark.asyncio + async def test_info_deprecation_warning( + self, + meta_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubMeta = meta_servicer + meta_servicer.info_responses.append(exchange_meta_pb.InfoResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.info() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_info instead" + + @pytest.mark.asyncio + async def test_stream_keepalive_deprecation_warning( + self, + meta_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExchangeAccount = account_servicer + meta_servicer.stream_keepalive_responses.append(exchange_meta_pb.StreamKeepaliveResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_keepalive() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_keepalive instead" + + @pytest.mark.asyncio + async def test_oracle_list_deprecation_warning( + self, + oracle_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubOracle = oracle_servicer + oracle_servicer.oracle_list_responses.append(exchange_oracle_pb.OracleListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_oracle_list() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_oracle_list instead" + + @pytest.mark.asyncio + async def test_get_oracle_list_deprecation_warning( + self, + oracle_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubOracle = oracle_servicer + oracle_servicer.oracle_list_responses.append(exchange_oracle_pb.OracleListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_oracle_list() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_oracle_list instead" + + @pytest.mark.asyncio + async def test_get_oracle_prices_deprecation_warning( + self, + oracle_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubOracle = oracle_servicer + oracle_servicer.price_responses.append(exchange_oracle_pb.PriceResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_oracle_prices( + base_symbol="Gold", + quote_symbol="USDT", + oracle_type="pricefeed", + oracle_scale_factor=6, + ) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_oracle_price instead" + + @pytest.mark.asyncio + async def test_stream_keepalive_deprecation_warning( + self, + oracle_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubOracle = oracle_servicer + oracle_servicer.stream_prices_responses.append(exchange_oracle_pb.StreamPricesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_oracle_prices( + base_symbol="Gold", + quote_symbol="USDT", + oracle_type="pricefeed", + ) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_oracle_prices_updates instead" + ) + + @pytest.mark.asyncio + async def test_get_insurance_funds_deprecation_warning( + self, + insurance_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubInsurance = insurance_servicer + insurance_servicer.funds_responses.append(exchange_insurance_pb.FundsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_insurance_funds() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_insurance_funds instead" + + @pytest.mark.asyncio + async def test_get_redemptions_deprecation_warning( + self, + insurance_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubInsurance = insurance_servicer + insurance_servicer.redemptions_responses.append(exchange_insurance_pb.RedemptionsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_redemptions() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_redemptions instead" + + @pytest.mark.asyncio + async def test_get_auction_deprecation_warning( + self, + auction_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubAuction = auction_servicer + auction_servicer.auction_endpoint_responses.append(exchange_auction_pb.AuctionEndpointResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_auction(bid_round=1) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_auction instead" + + @pytest.mark.asyncio + async def test_get_auctions_deprecation_warning( + self, + auction_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubAuction = auction_servicer + auction_servicer.auctions_responses.append(exchange_auction_pb.AuctionsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_auctions() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_auctions instead" + + @pytest.mark.asyncio + async def test_stream_bids_deprecation_warning( + self, + auction_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubAuction = auction_servicer + auction_servicer.stream_bids_responses.append(exchange_auction_pb.StreamBidsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_bids() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_bids_updates instead" + + @pytest.mark.asyncio + async def test_get_spot_markets_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.markets_responses.append(exchange_spot_pb.MarketsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_markets() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_markets instead" + + @pytest.mark.asyncio + async def test_get_spot_market_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.market_responses.append(exchange_spot_pb.MarketResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_market(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_market instead" + + @pytest.mark.asyncio + async def test_get_spot_orderbookV2_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.orderbook_v2_responses.append(exchange_spot_pb.OrderbookV2Response()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_orderbookV2(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_orderbook_v2 instead" + + @pytest.mark.asyncio + async def test_get_spot_orderbooksV2_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.orderbooks_v2_responses.append(exchange_spot_pb.OrderbooksV2Response()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_orderbooksV2(market_ids=[]) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_orderbooks_v2 instead" + + @pytest.mark.asyncio + async def test_get_spot_orders_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.orders_responses.append(exchange_spot_pb.OrdersResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_orders(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_orders instead" + + @pytest.mark.asyncio + async def test_get_spot_trades_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.trades_responses.append(exchange_spot_pb.TradesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_trades() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_trades instead" + + @pytest.mark.asyncio + async def test_get_spot_subaccount_orders_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.subaccount_orders_list_responses.append(exchange_spot_pb.SubaccountOrdersListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_subaccount_orders(subaccount_id="", market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_spot_subaccount_orders_list instead" + ) + + @pytest.mark.asyncio + async def test_get_spot_subaccount_trades_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.subaccount_trades_list_responses.append(exchange_spot_pb.SubaccountTradesListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_spot_subaccount_trades(subaccount_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_spot_subaccount_trades_list instead" + ) + + @pytest.mark.asyncio + async def test_get_historical_spot_orders_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.orders_history_responses.append(exchange_spot_pb.SubaccountTradesListResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_historical_spot_orders() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_spot_orders_history instead" + ) + + @pytest.mark.asyncio + async def test_stream_spot_markets_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_markets_responses.append(exchange_spot_pb.StreamMarketsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_spot_markets() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_spot_markets_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_spot_orderbook_snapshot_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_orderbook_v2_responses.append(exchange_spot_pb.StreamOrderbookV2Response()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_spot_orderbook_snapshot(market_ids=[]) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_spot_orderbook_snapshots instead" + ) + + @pytest.mark.asyncio + async def test_stream_spot_orderbook_update_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_orderbook_update_responses.append(exchange_spot_pb.StreamOrderbookUpdateRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_spot_orderbook_update(market_ids=[]) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_spot_orderbook_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_spot_orders_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_orders_responses.append(exchange_spot_pb.StreamOrdersRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_spot_orders(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_spot_orders_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_spot_trades_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_orders_responses.append(exchange_spot_pb.StreamTradesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_spot_trades() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_spot_trades_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_historical_spot_orders_deprecation_warning( + self, + spot_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + spot_servicer.stream_orders_history_responses.append(exchange_spot_pb.StreamOrdersHistoryRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_historical_spot_orders(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_spot_orders_history_updates instead" + ) + + @pytest.mark.asyncio + async def test_get_derivative_markets_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.markets_responses.append(exchange_derivative_pb.MarketsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_markets() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_markets instead" + + @pytest.mark.asyncio + async def test_get_derivative_market_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.market_responses.append(exchange_derivative_pb.MarketResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_market(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_market instead" + + @pytest.mark.asyncio + async def test_get_binary_options_markets_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.binary_options_markets_responses.append( + exchange_derivative_pb.BinaryOptionsMarketsResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.get_binary_options_markets() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_binary_options_markets instead" + ) + + @pytest.mark.asyncio + async def test_get_binary_options_market_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.binary_options_market_responses.append(exchange_derivative_pb.BinaryOptionsMarketResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_binary_options_market(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_binary_options_market instead" + ) + + @pytest.mark.asyncio + async def test_get_derivative_orderbook_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.orderbook_v2_responses.append(exchange_derivative_pb.OrderbookV2Request()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_orderbook(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_orderbook_v2 instead" + ) + + @pytest.mark.asyncio + async def test_get_derivative_orderbooksV2_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.orderbooks_v2_responses.append(exchange_derivative_pb.OrderbooksV2Request()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_orderbooksV2(market_ids=[]) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_orderbooks_v2 instead" + ) + + @pytest.mark.asyncio + async def test_get_derivative_orders_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.orders_responses.append(exchange_derivative_pb.OrdersResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_orders(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_orders instead" + + @pytest.mark.asyncio + async def test_get_derivative_positions_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.positions_responses.append(exchange_derivative_pb.PositionsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_positions() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_positions instead" + ) + + @pytest.mark.asyncio + async def test_get_derivative_liquidable_positions_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.liquidable_positions_responses.append(exchange_derivative_pb.LiquidablePositionsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_liquidable_positions() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_liquidable_positions instead" + ) + + @pytest.mark.asyncio + async def test_get_funding_payments_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.funding_payments_responses.append(exchange_derivative_pb.FundingPaymentsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_funding_payments(subaccount_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_funding_payments instead" + + @pytest.mark.asyncio + async def test_get_funding_rates_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.funding_rates_responses.append(exchange_derivative_pb.FundingRatesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_funding_rates(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_funding_rates instead" + + @pytest.mark.asyncio + async def test_get_derivative_trades_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.trades_responses.append(exchange_derivative_pb.TradesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_trades() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_derivative_trades instead" + + @pytest.mark.asyncio + async def test_get_derivative_subaccount_orders_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.subaccount_orders_list_responses.append( + exchange_derivative_pb.SubaccountOrdersListResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_subaccount_orders(subaccount_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_subaccount_orders instead" + ) + + @pytest.mark.asyncio + async def test_get_derivative_subaccount_trades_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.subaccount_trades_list_responses.append( + exchange_derivative_pb.SubaccountTradesListResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.get_derivative_subaccount_trades(subaccount_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_subaccount_trades instead" + ) + + @pytest.mark.asyncio + async def test_get_historical_derivative_orders_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.orders_history_responses.append(exchange_derivative_pb.OrdersHistoryResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_historical_derivative_orders() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use fetch_derivative_orders_history instead" + ) + + @pytest.mark.asyncio + async def test_stream_derivative_markets_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.stream_market_responses.append(exchange_derivative_pb.StreamMarketResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_markets() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_market_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_derivative_orderbook_snapshot_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = spot_servicer + derivative_servicer.stream_orderbook_v2_responses.append(exchange_derivative_pb.StreamOrderbookV2Response()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_orderbook_snapshot(market_ids=[]) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_orderbook_snapshots instead" + ) + + @pytest.mark.asyncio + async def test_stream_derivative_orderbook_update_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.stream_orderbook_update_responses.append( + exchange_derivative_pb.StreamOrderbookUpdateRequest() + ) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_orderbook_update(market_ids=[]) + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_orderbook_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_derivative_positions_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubDerivativeExchange = derivative_servicer + derivative_servicer.stream_positions_responses.append(exchange_derivative_pb.StreamPositionsRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_positions() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_positions_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_derivative_orders_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = derivative_servicer + derivative_servicer.stream_orders_responses.append(exchange_derivative_pb.StreamOrdersRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_orders(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_orders_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_derivative_trades_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = derivative_servicer + derivative_servicer.stream_orders_responses.append(exchange_derivative_pb.StreamTradesResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_derivative_trades() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_trades_updates instead" + ) + + @pytest.mark.asyncio + async def test_stream_historical_derivative_orders_deprecation_warning( + self, + derivative_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubSpotExchange = derivative_servicer + derivative_servicer.stream_orders_history_responses.append(exchange_spot_pb.StreamOrdersHistoryRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_historical_derivative_orders(market_id="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_derivative_orders_history_updates instead" + ) + + @pytest.mark.asyncio + async def test_get_account_portfolio_deprecation_warning( + self, + portfolio_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubPortfolio = portfolio_servicer + portfolio_servicer.account_portfolio_responses.append(exchange_portfolio_pb.AccountPortfolioResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_account_portfolio(account_address="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_account_portfolio instead" + + @pytest.mark.asyncio + async def test_stream_account_portfolio_deprecation_warning( + self, + portfolio_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubPortfolio = portfolio_servicer + portfolio_servicer.stream_account_portfolio_responses.append( + exchange_portfolio_pb.StreamAccountPortfolioResponse() + ) + + with catch_warnings(record=True) as all_warnings: + await client.stream_account_portfolio(account_address="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) + == "This method is deprecated. Use listen_account_portfolio_updates instead" + ) + + @pytest.mark.asyncio + async def test_get_account_txs_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.account_txs_responses.append(exchange_explorer_pb.GetAccountTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_account_txs(address="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_account_txs instead" + + @pytest.mark.asyncio + async def test_get_blocks_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.blocks_responses.append(exchange_explorer_pb.GetBlocksResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_blocks() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_blocks instead" + + @pytest.mark.asyncio + async def test_get_block_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.block_responses.append(exchange_explorer_pb.GetBlockResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_block(block_height="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_block instead" + + @pytest.mark.asyncio + async def test_get_txs_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.txs_responses.append(exchange_explorer_pb.GetTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_txs() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_txs instead" + + @pytest.mark.asyncio + async def test_get_tx_by_hash_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.tx_by_tx_hash_responses.append(exchange_explorer_pb.GetTxByTxHashResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_tx_by_hash(tx_hash="") + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_tx_by_tx_hash instead" + + @pytest.mark.asyncio + async def test_get_peggy_deposits_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.peggy_deposit_txs_responses.append(exchange_explorer_pb.GetPeggyDepositTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_peggy_deposits() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_peggy_deposit_txs instead" + + @pytest.mark.asyncio + async def test_get_peggy_withdrawals_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.peggy_withdrawal_txs_responses.append(exchange_explorer_pb.GetPeggyWithdrawalTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_peggy_withdrawals() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_peggy_withdrawal_txs instead" + ) + + @pytest.mark.asyncio + async def test_get_ibc_transfers_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubExplorer = explorer_servicer + explorer_servicer.ibc_transfer_txs_responses.append(exchange_explorer_pb.GetIBCTransferTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.get_ibc_transfers() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use fetch_ibc_transfer_txs instead" + + @pytest.mark.asyncio + async def test_stream_txs_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubPortfolio = explorer_servicer + explorer_servicer.stream_txs_responses.append(exchange_explorer_pb.StreamTxsResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_txs() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_txs_updates instead" + + @pytest.mark.asyncio + async def test_stream_blocks_deprecation_warning( + self, + explorer_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.stubPortfolio = explorer_servicer + explorer_servicer.stream_blocks_responses.append(exchange_explorer_pb.StreamBlocksResponse()) + + with catch_warnings(record=True) as all_warnings: + await client.stream_blocks() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_blocks_updates instead" + + @pytest.mark.asyncio + async def test_chain_stream_deprecation_warning( + self, + chain_stream_servicer, + ): + client = AsyncClient( + network=Network.local(), + ) + client.chain_stream_stub = chain_stream_servicer + chain_stream_servicer.stream_responses.append(chain_stream_pb.StreamRequest()) + + with catch_warnings(record=True) as all_warnings: + await client.chain_stream() + + deprecation_warnings = [warning for warning in all_warnings if issubclass(warning.category, DeprecationWarning)] + assert len(deprecation_warnings) == 1 + assert ( + str(deprecation_warnings[0].message) == "This method is deprecated. Use listen_chain_stream_updates instead" + )