From 9f64b7d5cf3bfc4f9b5ab50d0540eb810bcf2f81 Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Mon, 11 Nov 2024 00:16:30 -0500 Subject: [PATCH 01/16] add gmx bindings --- .../src/eth_typeshed/gmx/__init__.py | 0 .../src/eth_typeshed/gmx/contracts.py | 38 ++ .../src/eth_typeshed/gmx/datastore.py | 606 ++++++++++++++++++ .../src/eth_typeshed/gmx/deposit_vault.py | 81 +++ .../src/eth_typeshed/gmx/event_emitter.py | 183 ++++++ .../src/eth_typeshed/gmx/exchange_router.py | 361 +++++++++++ .../src/eth_typeshed/gmx/withdrawal_vault.py | 81 +++ 7 files changed, 1350 insertions(+) create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/datastore.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/deposit_vault.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/event_emitter.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/exchange_router.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/withdrawal_vault.py diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py b/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py new file mode 100644 index 0000000..9497274 --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py @@ -0,0 +1,38 @@ +CONTRACTS = { + "datastore": { + "arbitrum": "0xFD70de6b91282D8017aA4E741e9Ae325CAb992d8", + "avalanche": "0x2F0b22339414ADeD7D5F06f9D604c7fF5b2fe3f6" + }, + "eventemitter": { + "arbitrum": "0xC8ee91A54287DB53897056e12D9819156D3822Fb", + "avalanche": "0xDb17B211c34240B014ab6d61d4A31FA0C0e20c26" + }, + "exchangerouter": { + "arbitrum": "0x69C527fC77291722b52649E45c838e41be8Bf5d5", + "avalanche": "0x3BE24AED1a4CcaDebF2956e02C27a00726D4327d" + }, + "depositvault": { + "arbitrum": "0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55", + "avalanche": "0x90c670825d0C62ede1c5ee9571d6d9a17A722DFF" + }, + "withdrawalvault": { + "arbitrum": "0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55", + "avalanche": "0xf5F30B10141E1F63FC11eD772931A8294a591996" + }, + "ordervault": { + "arbitrum": "0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5", + "avalanche": "0xD3D60D22d415aD43b7e64b510D86A30f19B1B12C" + }, + "syntheticsreader": { + "arbitrum": "0x5Ca84c34a381434786738735265b9f3FD814b824", + "avalanche": "0xBAD04dDcc5CC284A86493aFA75D2BEb970C72216" + }, + "syntheticsrouter": { + "arbitrum": "0x7452c558d45f8afC8c83dAe62C3f8A5BE19c71f6", + "avalanche": "0x820F5FfC5b525cD4d88Cd91aCf2c28F16530Cc68" + }, + "glvreader": { + "arbitrum": "0xd4f522c4339Ae0A90a156bd716715547e44Bed65", + "avalanche": None + } +} diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/datastore.py b/packages/eth_typeshed/src/eth_typeshed/gmx/datastore.py new file mode 100644 index 0000000..af3d474 --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/datastore.py @@ -0,0 +1,606 @@ +from typing import Annotated, cast + +from eth_rpc import ProtocolBase, ContractFunc +from eth_rpc.types import primitives, Name, NoArgs + + +class Datastore(ProtocolBase): + add_address: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.address], + None + ], + Name("addAddress"), + ] + + add_bytes32: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.bytes32], + None + ], + Name("addBytes32"), + ] + + add_uint: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + None + ], + Name("addUint"), + ] + + address_array_values: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + primitives.address + ], + Name("addressArrayValues"), + ] + + address_values: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.address + ], + Name("addressValues"), + ] + + apply_bounded_delta_to_uint: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.int256], + primitives.uint256 + ], + Name("applyBoundedDeltaToUint"), + ] + + apply_delta_to_int: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.int256], + primitives.int256 + ], + Name("applyDeltaToInt"), + ] + + def apply_delta_to_uint( + self, + arg1: primitives.bytes32, + arg2: primitives.int256 | primitives.uint256, + arg3: primitives.string | None = None, + ) -> ContractFunc[ + tuple[primitives.bytes32, primitives.int256, primitives.string] | tuple[primitives.bytes32, primitives.uint256], + primitives.uint256, + ]: + if arg3 is None: + arg2 = cast(primitives.uint256, arg2) + return self.apply_delta_to_uint_2((arg1, arg2)) + arg2 = cast(primitives.int256, arg2) + return self.apply_delta_to_uint_1((arg1, arg2, arg3)) + + apply_delta_to_uint_1: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.int256, primitives.string], + primitives.uint256, + ], + Name("applyDeltaToUint"), + ] + + apply_delta_to_uint_2: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + primitives.uint256, + ], + Name("applyDeltaToUint"), + ] + + bool_array_values: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + bool + ], + Name("boolArrayValues"), + ] + + bool_values: Annotated[ + ContractFunc[ + primitives.bytes32, + bool + ], + Name("boolValues"), + ] + + bytes32_array_values: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + primitives.bytes32 + ], + Name("bytes32ArrayValues"), + ] + + bytes32_values: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.bytes32 + ], + Name("bytes32Values"), + ] + + contains_address: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.address], + bool + ], + Name("containsAddress"), + ] + + contains_bytes32: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.bytes32], + bool + ], + Name("containsBytes32"), + ] + + contains_uint: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + bool + ], + Name("containsUint"), + ] + + decrement_int: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.int256], + primitives.int256 + ], + Name("decrementInt"), + ] + + decrement_uint: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + primitives.uint256 + ], + Name("decrementUint"), + ] + + get_address: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.address + ], + Name("getAddress"), + ] + + get_address_array: Annotated[ + ContractFunc[ + primitives.bytes32, + list[primitives.address] + ], + Name("getAddressArray"), + ] + + get_address_count: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.uint256 + ], + Name("getAddressCount"), + ] + + get_address_values_at: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256, primitives.uint256], + list[primitives.address] + ], + Name("getAddressValuesAt"), + ] + + get_bool: Annotated[ + ContractFunc[ + primitives.bytes32, + bool + ], + Name("getBool"), + ] + + get_bool_array: Annotated[ + ContractFunc[ + primitives.bytes32, + list[bool] + ], + Name("getBoolArray"), + ] + + get_bytes32: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.bytes32 + ], + Name("getBytes32"), + ] + + get_bytes32_array: Annotated[ + ContractFunc[ + primitives.bytes32, + list[primitives.bytes32] + ], + Name("getBytes32Array"), + ] + + get_bytes32_count: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.uint256 + ], + Name("getBytes32Count"), + ] + + get_bytes32_values_at: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256, primitives.uint256], + list[primitives.bytes32] + ], + Name("getBytes32ValuesAt"), + ] + + get_int: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.int256 + ], + Name("getInt"), + ] + + get_int_array: Annotated[ + ContractFunc[ + primitives.bytes32, + list[primitives.int256] + ], + Name("getIntArray"), + ] + + get_string: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.string + ], + Name("getString"), + ] + + get_string_array: Annotated[ + ContractFunc[ + primitives.bytes32, + list[primitives.string] + ], + Name("getStringArray"), + ] + + get_uint: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.uint256 + ], + Name("getUint"), + ] + + get_uint_array: Annotated[ + ContractFunc[ + primitives.bytes32, + list[primitives.uint256] + ], + Name("getUintArray"), + ] + + get_uint_count: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.uint256 + ], + Name("getUintCount"), + ] + + get_uint_values_at: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256, primitives.uint256], + list[primitives.uint256] + ], + Name("getUintValuesAt"), + ] + + increment_int: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.int256], + primitives.int256 + ], + Name("incrementInt"), + ] + + increment_uint: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + primitives.uint256 + ], + Name("incrementUint"), + ] + + int_array_values: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + primitives.int256 + ], + Name("intArrayValues"), + ] + + int_values: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.int256 + ], + Name("intValues"), + ] + + remove_address: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.address], + None + ], + Name("removeAddress"), + ] + + remove_address_2: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeAddress"), + ] + + remove_address_array: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeAddressArray"), + ] + + remove_bool: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeBool"), + ] + + remove_bool_array: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeBoolArray"), + ] + + remove_bytes32: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.bytes32], + None + ], + Name("removeBytes32"), + ] + + remove_bytes32_2: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeBytes32"), + ] + + remove_bytes32_array: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeBytes32Array"), + ] + + remove_int: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeInt"), + ] + + remove_int_array: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeIntArray"), + ] + + remove_string: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeString"), + ] + + remove_string_array: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeStringArray"), + ] + + def remove_uint( + self, + arg1: primitives.bytes32, + arg2: primitives.uint256 | None = None, + ) -> ContractFunc[primitives.bytes32 | tuple[primitives.bytes32, primitives.uint256], None]: + if arg2 is None: + return self.remove_uint_1(arg1) + return self.remove_uint_2((arg1, arg2)) + + remove_uint_1: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeUint"), + ] + + remove_uint_2: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + None + ], + Name("removeUint"), + ] + + remove_uint_array: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("removeUintArray"), + ] + + role_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("roleStore"), + ] + + set_address: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.address], + primitives.address + ], + Name("setAddress"), + ] + + set_address_array: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, list[primitives.address]], + None + ], + Name("setAddressArray"), + ] + + set_bool: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, bool], + bool + ], + Name("setBool"), + ] + + set_bool_array: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, list[bool]], + None + ], + Name("setBoolArray"), + ] + + set_bytes32: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.bytes32], + primitives.bytes32 + ], + Name("setBytes32"), + ] + + set_bytes32_array: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, list[primitives.bytes32]], + None + ], + Name("setBytes32Array"), + ] + + set_int: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.int256], + primitives.int256 + ], + Name("setInt"), + ] + + set_int_array: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, list[primitives.int256]], + None + ], + Name("setIntArray"), + ] + + set_string: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.string], + primitives.string + ], + Name("setString"), + ] + + set_string_array: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, list[primitives.string]], + None + ], + Name("setStringArray"), + ] + + set_uint: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + primitives.uint256 + ], + Name("setUint"), + ] + + set_uint_array: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, list[primitives.uint256]], + None + ], + Name("setUintArray"), + ] + + string_array_values: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + primitives.string + ], + Name("stringArrayValues"), + ] + + string_values: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.string + ], + Name("stringValues"), + ] + + uint_array_values: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256], + primitives.uint256 + ], + Name("uintArrayValues"), + ] + + uint_values: Annotated[ + ContractFunc[ + primitives.bytes32, + primitives.uint256 + ], + Name("uintValues"), + ] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/deposit_vault.py b/packages/eth_typeshed/src/eth_typeshed/gmx/deposit_vault.py new file mode 100644 index 0000000..f26bf09 --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/deposit_vault.py @@ -0,0 +1,81 @@ +from typing import Annotated + +from eth_rpc import ProtocolBase, ContractFunc +from eth_rpc.types import primitives, Name, NoArgs + + +class DepositVault(ProtocolBase): + data_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("dataStore"), + ] + + record_transfer_in: Annotated[ + ContractFunc[ + primitives.address, + primitives.uint256 + ], + Name("recordTransferIn"), + ] + + role_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("roleStore"), + ] + + sync_token_balance: Annotated[ + ContractFunc[ + primitives.address, + primitives.uint256 + ], + Name("syncTokenBalance"), + ] + + token_balances: Annotated[ + ContractFunc[ + primitives.address, + primitives.uint256 + ], + Name("tokenBalances"), + ] + + def transfer_out( + self, + arg1: primitives.address, + arg2: primitives.address, + arg3: primitives.uint256, + arg4: primitives.bool | None = None, + ) -> None: + if arg4 is None: + return self.transfer_out_1((arg1, arg2, arg3)) + return self.transfer_out_2((arg1, arg2, arg3, arg4)) + + transfer_out_1: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256], + None + ], + Name("transferOut"), + ] + + transfer_out_2: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256, bool], + None + ], + Name("transferOut"), + ] + + transfer_out_native_token: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256], + None + ], + Name("transferOutNativeToken"), + ] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/event_emitter.py b/packages/eth_typeshed/src/eth_typeshed/gmx/event_emitter.py new file mode 100644 index 0000000..b785b3d --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/event_emitter.py @@ -0,0 +1,183 @@ +from typing import Annotated + +from eth_rpc import ProtocolBase, ContractFunc +from eth_rpc.types import primitives, Name, NoArgs, Struct + + +class StringKeyValue(Struct): + key: primitives.string + value: primitives.string + + +class StringArrayKeyValue(Struct): + key: primitives.string + value: list[primitives.string] + + +class BytesKeyValue(Struct): + key: primitives.string + value: bytes + +class BytesArrayKeyValue(Struct): + key: primitives.string + value: list[bytes] + + +class Bytes32KeyValue(Struct): + key: primitives.string + value: primitives.bytes32 + +class Bytes32ArrayKeyValue(Struct): + key: primitives.string + value: list[primitives.bytes32] + + +class BoolKeyValue(Struct): + key: primitives.string + value: primitives.bool + + +class BoolArrayKeyValue(Struct): + key: primitives.string + value: list[primitives.bool] + + +class IntKeyValue(Struct): + key: primitives.string + value: primitives.int256 + + +class IntArrayKeyValue(Struct): + key: primitives.string + value: list[primitives.int256] + + +class UintKeyValue(Struct): + key: primitives.string + value: primitives.uint256 + + +class UintArrayKeyValue(Struct): + key: primitives.string + value: list[primitives.uint256] + + +class AddressKeyValue(Struct): + key: primitives.string + value: primitives.address + + +class AddressArrayKeyValue(Struct): + key: primitives.string + value: list[primitives.address] + + +class StringItems(Struct): + items: list[StringKeyValue] + array_items: Annotated[list[StringArrayKeyValue], Name("arrayItems")] + + +class BytesItems(Struct): + items: list[BytesKeyValue] + array_items: Annotated[list[BytesArrayKeyValue], Name("arrayItems")] + + +class Bytes32Items(Struct): + items: list[Bytes32KeyValue] + array_items: Annotated[list[Bytes32ArrayKeyValue], Name("arrayItems")] + + +class BoolItems(Struct): + items: list[BoolKeyValue] + array_items: Annotated[list[BoolArrayKeyValue], Name("arrayItems")] + + +class IntItems(Struct): + items: list[IntKeyValue] + array_items: Annotated[list[IntArrayKeyValue], Name("arrayItems")] + + +class UintItems(Struct): + items: list[UintKeyValue] + array_items: Annotated[list[UintArrayKeyValue], Name("arrayItems")] + + +class AddressItems(Struct): + items: list[AddressKeyValue] + array_items: Annotated[list[AddressArrayKeyValue], Name("arrayItems")] + + +class EventLogData(Struct): + address_items: Annotated[AddressItems, Name("addressItems")] + uint_items: Annotated[UintItems, Name("uintItems")] + int_items: Annotated[IntItems, Name("intItems")] + bool_items: Annotated[BoolItems, Name("boolItems")] + bytes32_items: Annotated[Bytes32Items, Name("bytes32Items")] + bytes_items: Annotated[BytesItems, Name("bytesItems")] + string_items: Annotated[StringItems, Name("stringItems")] + + +class EventEmitter(ProtocolBase): + emit_data_log1: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, bytes], + None + ], + Name("emitDataLog1"), + ] + + emit_data_log2: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.bytes32, bytes], + None + ], + Name("emitDataLog2"), + ] + + emit_data_log3: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.bytes32, primitives.bytes32, bytes], + None + ], + Name("emitDataLog3"), + ] + + emit_data_log4: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.bytes32, primitives.bytes32, primitives.bytes32, bytes], + None + ], + Name("emitDataLog4"), + ] + + emit_event_log: Annotated[ + ContractFunc[ + tuple[primitives.string, EventLogData], + None + ], + Name("emitEventLog"), + ] + + emit_event_log1: Annotated[ + ContractFunc[ + tuple[primitives.string, primitives.bytes32, EventLogData], + None + ], + Name("emitEventLog1"), + ] + + emit_event_log2: Annotated[ + ContractFunc[ + tuple[primitives.string, primitives.bytes32, primitives.bytes32, EventLogData], + None + ], + Name("emitEventLog2"), + ] + + role_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("roleStore"), + ] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/exchange_router.py b/packages/eth_typeshed/src/eth_typeshed/gmx/exchange_router.py new file mode 100644 index 0000000..fe29a98 --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/exchange_router.py @@ -0,0 +1,361 @@ +from typing import Annotated + +from eth_rpc import ProtocolBase, ContractFunc +from eth_rpc.types import primitives, Name, NoArgs, Struct + + +class Props(Struct): + min: primitives.uint256 + max: primitives.uint256 + + +class CreateOrderParamsNumbers(Struct): + size_delta_usd: Annotated[primitives.uint256, Name("sizeDeltaUsd")] + initial_collateral_delta_amount: Annotated[primitives.uint256, Name("initialCollateralDeltaAmount")] + trigger_price: Annotated[primitives.uint256, Name("triggerPrice")] + acceptable_price: Annotated[primitives.uint256, Name("acceptablePrice")] + execution_fee: Annotated[primitives.uint256, Name("executionFee")] + callback_gas_limit: Annotated[primitives.uint256, Name("callbackGasLimit")] + min_output_amount: Annotated[primitives.uint256, Name("minOutputAmount")] + + +class CreateOrderParamsAddresses(Struct): + receiver: primitives.address + cancellation_receiver: Annotated[primitives.address, Name("cancellationReceiver")] + callback_contract: Annotated[primitives.address, Name("callbackContract")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + market: primitives.address + initial_collateral_token: Annotated[primitives.address, Name("initialCollateralToken")] + swap_path: Annotated[list[primitives.address], Name("swapPath")] + + +class SimulatePricesParams(Struct): + primary_tokens: Annotated[list[primitives.address], Name("primaryTokens")] + primary_prices: Annotated[list['Props'], Name("primaryPrices")] + min_timestamp: Annotated[primitives.uint256, Name("minTimestamp")] + max_timestamp: Annotated[primitives.uint256, Name("maxTimestamp")] + + +class SetPricesParams(Struct): + tokens: list[primitives.address] + providers: list[primitives.address] + data: list['bytes'] + + +class CreateWithdrawalParams(Struct): + receiver: primitives.address + callback_contract: Annotated[primitives.address, Name("callbackContract")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + market: primitives.address + long_token_swap_path: Annotated[list[primitives.address], Name("longTokenSwapPath")] + short_token_swap_path: Annotated[list[primitives.address], Name("shortTokenSwapPath")] + min_long_token_amount: Annotated[primitives.uint256, Name("minLongTokenAmount")] + min_short_token_amount: Annotated[primitives.uint256, Name("minShortTokenAmount")] + should_unwrap_native_token: Annotated[bool, Name("shouldUnwrapNativeToken")] + execution_fee: Annotated[primitives.uint256, Name("executionFee")] + callback_gas_limit: Annotated[primitives.uint256, Name("callbackGasLimit")] + + +class CreateShiftParams(Struct): + receiver: primitives.address + callback_contract: Annotated[primitives.address, Name("callbackContract")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + from_market: Annotated[primitives.address, Name("fromMarket")] + to_market: Annotated[primitives.address, Name("toMarket")] + min_market_tokens: Annotated[primitives.uint256, Name("minMarketTokens")] + execution_fee: Annotated[primitives.uint256, Name("executionFee")] + callback_gas_limit: Annotated[primitives.uint256, Name("callbackGasLimit")] + + +class CreateOrderParams(Struct): + addresses: CreateOrderParamsAddresses + numbers: CreateOrderParamsNumbers + order_type: Annotated[primitives.uint8, Name("orderType")] + decrease_position_swap_type: Annotated[primitives.uint8, Name("decreasePositionSwapType")] + is_long: Annotated[bool, Name("isLong")] + should_unwrap_native_token: Annotated[bool, Name("shouldUnwrapNativeToken")] + auto_cancel: Annotated[bool, Name("autoCancel")] + referral_code: Annotated[primitives.bytes32, Name("referralCode")] + + +class CreateDepositParams(Struct): + receiver: primitives.address + callback_contract: Annotated[primitives.address, Name("callbackContract")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + market: primitives.address + initial_long_token: Annotated[primitives.address, Name("initialLongToken")] + initial_short_token: Annotated[primitives.address, Name("initialShortToken")] + long_token_swap_path: Annotated[list[primitives.address], Name("longTokenSwapPath")] + short_token_swap_path: Annotated[list[primitives.address], Name("shortTokenSwapPath")] + min_market_tokens: Annotated[primitives.uint256, Name("minMarketTokens")] + should_unwrap_native_token: Annotated[bool, Name("shouldUnwrapNativeToken")] + execution_fee: Annotated[primitives.uint256, Name("executionFee")] + callback_gas_limit: Annotated[primitives.uint256, Name("callbackGasLimit")] + + +class ExchangeRouter(ProtocolBase): + cancel_deposit: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("cancelDeposit"), + ] + + cancel_order: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("cancelOrder"), + ] + + cancel_shift: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("cancelShift"), + ] + + cancel_withdrawal: Annotated[ + ContractFunc[ + primitives.bytes32, + None + ], + Name("cancelWithdrawal"), + ] + + claim_affiliate_rewards: Annotated[ + ContractFunc[ + tuple[list[primitives.address], list[primitives.address], primitives.address], + list[primitives.uint256] + ], + Name("claimAffiliateRewards"), + ] + + claim_collateral: Annotated[ + ContractFunc[ + tuple[list[primitives.address], list[primitives.address], list[primitives.uint256], primitives.address], + list[primitives.uint256] + ], + Name("claimCollateral"), + ] + + claim_funding_fees: Annotated[ + ContractFunc[ + tuple[list[primitives.address], list[primitives.address], primitives.address], + list[primitives.uint256] + ], + Name("claimFundingFees"), + ] + + claim_ui_fees: Annotated[ + ContractFunc[ + tuple[list[primitives.address], list[primitives.address], primitives.address], + list[primitives.uint256] + ], + Name("claimUiFees"), + ] + + create_deposit: Annotated[ + ContractFunc[ + CreateDepositParams, + primitives.bytes32 + ], + Name("createDeposit"), + ] + + create_order: Annotated[ + ContractFunc[ + CreateOrderParams, + primitives.bytes32 + ], + Name("createOrder"), + ] + + create_shift: Annotated[ + ContractFunc[ + CreateShiftParams, + primitives.bytes32 + ], + Name("createShift"), + ] + + create_withdrawal: Annotated[ + ContractFunc[ + CreateWithdrawalParams, + primitives.bytes32 + ], + Name("createWithdrawal"), + ] + + data_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("dataStore"), + ] + + deposit_handler: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("depositHandler"), + ] + + event_emitter: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("eventEmitter"), + ] + + execute_atomic_withdrawal: Annotated[ + ContractFunc[ + tuple[CreateWithdrawalParams, SetPricesParams], + None + ], + Name("executeAtomicWithdrawal"), + ] + + external_handler: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("externalHandler"), + ] + + make_external_calls: Annotated[ + ContractFunc[ + tuple[list[primitives.address], list[bytes], list[primitives.address], list[primitives.address]], + None + ], + Name("makeExternalCalls"), + ] + + multicall: ContractFunc[ # type: ignore + list[bytes], + list[bytes] + ] + + order_handler: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("orderHandler"), + ] + + role_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("roleStore"), + ] + + router: ContractFunc[ + NoArgs, + primitives.address + ] + + send_native_token: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256], + None + ], + Name("sendNativeToken"), + ] + + send_tokens: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256], + None + ], + Name("sendTokens"), + ] + + send_wnt: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256], + None + ], + Name("sendWnt"), + ] + + set_saved_callback_contract: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address], + None + ], + Name("setSavedCallbackContract"), + ] + + set_ui_fee_factor: Annotated[ + ContractFunc[ + primitives.uint256, + None + ], + Name("setUiFeeFactor"), + ] + + shift_handler: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("shiftHandler"), + ] + + simulate_execute_deposit: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, SimulatePricesParams], + None + ], + Name("simulateExecuteDeposit"), + ] + + simulate_execute_order: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, SimulatePricesParams], + None + ], + Name("simulateExecuteOrder"), + ] + + simulate_execute_shift: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, SimulatePricesParams], + None + ], + Name("simulateExecuteShift"), + ] + + simulate_execute_withdrawal: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, SimulatePricesParams, primitives.uint8], + None + ], + Name("simulateExecuteWithdrawal"), + ] + + update_order: Annotated[ + ContractFunc[ + tuple[primitives.bytes32, primitives.uint256, primitives.uint256, primitives.uint256, primitives.uint256, bool], + None + ], + Name("updateOrder"), + ] + + withdrawal_handler: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("withdrawalHandler"), + ] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/withdrawal_vault.py b/packages/eth_typeshed/src/eth_typeshed/gmx/withdrawal_vault.py new file mode 100644 index 0000000..cb49b3d --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/withdrawal_vault.py @@ -0,0 +1,81 @@ +from typing import Annotated + +from eth_rpc import ProtocolBase, ContractFunc +from eth_rpc.types import primitives, Name, NoArgs + + +class WithdrawalVault(ProtocolBase): + data_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("dataStore"), + ] + + record_transfer_in: Annotated[ + ContractFunc[ + primitives.address, + primitives.uint256 + ], + Name("recordTransferIn"), + ] + + role_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("roleStore"), + ] + + sync_token_balance: Annotated[ + ContractFunc[ + primitives.address, + primitives.uint256 + ], + Name("syncTokenBalance"), + ] + + token_balances: Annotated[ + ContractFunc[ + primitives.address, + primitives.uint256 + ], + Name("tokenBalances"), + ] + + def transfer_out( + self, + arg1: primitives.address, + arg2: primitives.address, + arg3: primitives.uint256, + arg4: primitives.bool | None = None, + ) -> None: + if arg4 is None: + return self.transfer_out_1((arg1, arg2, arg3)) + return self.transfer_out_2((arg1, arg2, arg3, arg4)) + + transfer_out_1: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256], + None + ], + Name("transferOut"), + ] + + transfer_out_2: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256, bool], + None + ], + Name("transferOut"), + ] + + transfer_out_native_token: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256], + None + ], + Name("transferOutNativeToken"), + ] From 862cfb37f7e68712b3dccc166b53fbae16d15809 Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Mon, 11 Nov 2024 01:07:54 -0500 Subject: [PATCH 02/16] gmx contracts --- .../src/eth_typeshed/gmx/glv_reader.py | 223 ++++++++ .../src/eth_typeshed/gmx/order_vault.py | 81 +++ .../src/eth_typeshed/gmx/synthetics_reader.py | 478 ++++++++++++++++++ .../src/eth_typeshed/gmx/synthetics_router.py | 22 + 4 files changed, 804 insertions(+) create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/glv_reader.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/order_vault.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_router.py diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/glv_reader.py b/packages/eth_typeshed/src/eth_typeshed/gmx/glv_reader.py new file mode 100644 index 0000000..b00241f --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/glv_reader.py @@ -0,0 +1,223 @@ +from typing import Annotated + +from eth_rpc import ProtocolBase, ContractFunc +from eth_rpc.types import primitives, Name, Struct + + +class GlvShiftNumbers(Struct): + market_token_amount: Annotated[primitives.uint256, Name("marketTokenAmount")] + min_market_tokens: Annotated[primitives.uint256, Name("minMarketTokens")] + updated_at_time: Annotated[primitives.uint256, Name("updatedAtTime")] + + +class GlvShiftAddresses(Struct): + glv: primitives.address + from_market: Annotated[primitives.address, Name("fromMarket")] + to_market: Annotated[primitives.address, Name("toMarket")] + + +class GlvWithdrawalFlags(Struct): + should_unwrap_native_token: Annotated[bool, Name("shouldUnwrapNativeToken")] + + +class GlvWithdrawalNumbers(Struct): + glv_token_amount: Annotated[primitives.uint256, Name("glvTokenAmount")] + min_long_token_amount: Annotated[primitives.uint256, Name("minLongTokenAmount")] + min_short_token_amount: Annotated[primitives.uint256, Name("minShortTokenAmount")] + updated_at_time: Annotated[primitives.uint256, Name("updatedAtTime")] + execution_fee: Annotated[primitives.uint256, Name("executionFee")] + callback_gas_limit: Annotated[primitives.uint256, Name("callbackGasLimit")] + + +class GlvWithdrawalAddresses(Struct): + glv: primitives.address + market: primitives.address + account: primitives.address + receiver: primitives.address + callback_contract: Annotated[primitives.address, Name("callbackContract")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + long_token_swap_path: Annotated[list[primitives.address], Name("longTokenSwapPath")] + short_token_swap_path: Annotated[list[primitives.address], Name("shortTokenSwapPath")] + + +class GlvDepositFlags(Struct): + should_unwrap_native_token: Annotated[bool, Name("shouldUnwrapNativeToken")] + is_market_token_deposit: Annotated[bool, Name("isMarketTokenDeposit")] + + +class GlvDepositNumbers(Struct): + market_token_amount: Annotated[primitives.uint256, Name("marketTokenAmount")] + initial_long_token_amount: Annotated[primitives.uint256, Name("initialLongTokenAmount")] + initial_short_token_amount: Annotated[primitives.uint256, Name("initialShortTokenAmount")] + min_glv_tokens: Annotated[primitives.uint256, Name("minGlvTokens")] + updated_at_time: Annotated[primitives.uint256, Name("updatedAtTime")] + execution_fee: Annotated[primitives.uint256, Name("executionFee")] + callback_gas_limit: Annotated[primitives.uint256, Name("callbackGasLimit")] + + +class GlvDepositAddresses(Struct): + glv: primitives.address + account: primitives.address + receiver: primitives.address + callback_contract: Annotated[primitives.address, Name("callbackContract")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + market: primitives.address + initial_long_token: Annotated[primitives.address, Name("initialLongToken")] + initial_short_token: Annotated[primitives.address, Name("initialShortToken")] + long_token_swap_path: Annotated[list[primitives.address], Name("longTokenSwapPath")] + short_token_swap_path: Annotated[list[primitives.address], Name("shortTokenSwapPath")] + + +class PriceProps(Struct): + min: primitives.uint256 + max: primitives.uint256 + + +class GlvShiftProps(Struct): + addresses: GlvShiftAddresses + numbers: GlvShiftNumbers + + +class GlvProps(Struct): + glv_token: Annotated[primitives.address, Name("glvToken")] + long_token: Annotated[primitives.address, Name("longToken")] + short_token: Annotated[primitives.address, Name("shortToken")] + + +class GlvReaderGlvInfo(Struct): + glv: GlvProps + markets: list[primitives.address] + + +class GlvWithdrawalProps(Struct): + addresses: GlvWithdrawalAddresses + numbers: GlvWithdrawalNumbers + flags: GlvWithdrawalFlags + + +class GlvDepositProps(Struct): + addresses: GlvDepositAddresses + numbers: GlvDepositNumbers + flags: GlvDepositFlags + + +class GLVReader(ProtocolBase): + get_account_glv_deposits: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256, primitives.uint256], + list[GlvDepositProps] + ], + Name("getAccountGlvDeposits"), + ] + + get_account_glv_withdrawals: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256, primitives.uint256], + list[GlvWithdrawalProps] + ], + Name("getAccountGlvWithdrawals"), + ] + + get_glv: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address], + GlvProps + ], + Name("getGlv"), + ] + + get_glv_by_salt: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + GlvProps + ], + Name("getGlvBySalt"), + ] + + get_glv_deposit: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + GlvDepositProps + ], + Name("getGlvDeposit"), + ] + + get_glv_deposits: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256, primitives.uint256], + list[GlvDepositProps] + ], + Name("getGlvDeposits"), + ] + + get_glv_info: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address], + GlvReaderGlvInfo + ], + Name("getGlvInfo"), + ] + + get_glv_info_list: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256, primitives.uint256], + list[GlvReaderGlvInfo] + ], + Name("getGlvInfoList"), + ] + + get_glv_shift: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + GlvShiftProps + ], + Name("getGlvShift"), + ] + + get_glv_shifts: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256, primitives.uint256], + list[GlvShiftProps] + ], + Name("getGlvShifts"), + ] + + get_glv_token_price: Annotated[ + ContractFunc[ + tuple[primitives.address, list[primitives.address], list[PriceProps], PriceProps, PriceProps, primitives.address, bool], + tuple[primitives.uint256, primitives.uint256, primitives.uint256] + ], + Name("getGlvTokenPrice"), + ] + + get_glv_value: Annotated[ + ContractFunc[ + tuple[primitives.address, list[primitives.address], list[PriceProps], PriceProps, PriceProps, primitives.address, bool], + primitives.uint256 + ], + Name("getGlvValue"), + ] + + get_glv_withdrawal: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + GlvWithdrawalProps + ], + Name("getGlvWithdrawal"), + ] + + get_glv_withdrawals: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256, primitives.uint256], + list[GlvWithdrawalProps] + ], + Name("getGlvWithdrawals"), + ] + + get_glvs: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256, primitives.uint256], + list[GlvProps] + ], + Name("getGlvs"), + ] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/order_vault.py b/packages/eth_typeshed/src/eth_typeshed/gmx/order_vault.py new file mode 100644 index 0000000..a9988e7 --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/order_vault.py @@ -0,0 +1,81 @@ +from typing import Annotated + +from eth_rpc import ProtocolBase, ContractFunc +from eth_rpc.types import primitives, Name, NoArgs + + +class OrderVault(ProtocolBase): + data_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("dataStore"), + ] + + record_transfer_in: Annotated[ + ContractFunc[ + primitives.address, + primitives.uint256 + ], + Name("recordTransferIn"), + ] + + role_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("roleStore"), + ] + + sync_token_balance: Annotated[ + ContractFunc[ + primitives.address, + primitives.uint256 + ], + Name("syncTokenBalance"), + ] + + token_balances: Annotated[ + ContractFunc[ + primitives.address, + primitives.uint256 + ], + Name("tokenBalances"), + ] + + def transfer_out( + self, + arg1: primitives.address, + arg2: primitives.address, + arg3: primitives.uint256, + arg4: primitives.bool | None = None, + ) -> None: + if arg4 is None: + return self.transfer_out_1((arg1, arg2, arg3)) + return self.transfer_out_2((arg1, arg2, arg3, arg4)) + + transfer_out_1: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256], + None + ], + Name("transferOut"), + ] + + transfer_out_2: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256, bool], + None + ], + Name("transferOut"), + ] + + transfer_out_native_token: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256], + None + ], + Name("transferOutNativeToken"), + ] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py new file mode 100644 index 0000000..4d3f15d --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py @@ -0,0 +1,478 @@ +from typing import Annotated + +from eth_rpc import ProtocolBase, ContractFunc +from eth_rpc.types import primitives, Name, Struct + + +class MarketUtilsCollateralType(Struct): + longToken: primitives.uint256 + shortToken: primitives.uint256 + + +class MarketUtilsPositionType(Struct): + long: MarketUtilsCollateralType + short: MarketUtilsCollateralType + + +class WithdrawalFlags(Struct): + should_unwrap_native_token: Annotated[bool, Name("shouldUnwrapNativeToken")] + + +class WithdrawalNumbers(Struct): + market_token_amount: Annotated[primitives.uint256, Name("marketTokenAmount")] + min_long_token_amount: Annotated[primitives.uint256, Name("minLongTokenAmount")] + min_short_token_amount: Annotated[primitives.uint256, Name("minShortTokenAmount")] + updated_at_block: Annotated[primitives.uint256, Name("updatedAtBlock")] + updated_at_time: Annotated[primitives.uint256, Name("updatedAtTime")] + execution_fee: Annotated[primitives.uint256, Name("executionFee")] + callback_gas_limit: Annotated[primitives.uint256, Name("callbackGasLimit")] + + +class WithdrawalAddresses(Struct): + account: primitives.address + receiver: primitives.address + callback_contract: Annotated[primitives.address, Name("callbackContract")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + market: primitives.address + long_token_swap_path: Annotated[list[primitives.address], Name("longTokenSwapPath")] + short_token_swap_path: Annotated[list[primitives.address], Name("shortTokenSwapPath")] + + +class ShiftNumbers(Struct): + market_token_amount: Annotated[primitives.uint256, Name("marketTokenAmount")] + min_market_tokens: Annotated[primitives.uint256, Name("minMarketTokens")] + updated_at_time: Annotated[primitives.uint256, Name("updatedAtTime")] + execution_fee: Annotated[primitives.uint256, Name("executionFee")] + callback_gas_limit: Annotated[primitives.uint256, Name("callbackGasLimit")] + + +class ShiftAddresses(Struct): + account: primitives.address + receiver: primitives.address + callback_contract: Annotated[primitives.address, Name("callbackContract")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + from_market: Annotated[primitives.address, Name("fromMarket")] + to_market: Annotated[primitives.address, Name("toMarket")] + + +class ReaderUtilsVirtualInventory(Struct): + virtual_pool_amount_for_long_token: Annotated[primitives.uint256, Name("virtualPoolAmountForLongToken")] + virtual_pool_amount_for_short_token: Annotated[primitives.uint256, Name("virtualPoolAmountForShortToken")] + virtual_inventory_for_positions: Annotated[primitives.int256, Name("virtualInventoryForPositions")] + + +class MarketUtilsGetNextFundingAmountPerSizeResult(Struct): + longs_pay_shorts: Annotated[bool, Name("longsPayShorts")] + funding_factor_per_second: Annotated[primitives.uint256, Name("fundingFactorPerSecond")] + next_saved_funding_factor_per_second: Annotated[primitives.int256, Name("nextSavedFundingFactorPerSecond")] + funding_fee_amount_per_size_delta: Annotated[MarketUtilsPositionType, Name("fundingFeeAmountPerSizeDelta")] + claimable_funding_amount_per_size_delta: Annotated[MarketUtilsPositionType, Name("claimableFundingAmountPerSizeDelta")] + + +class ReaderUtilsBaseFundingValues(Struct): + funding_fee_amount_per_size: Annotated[MarketUtilsPositionType, Name("fundingFeeAmountPerSize")] + claimable_funding_amount_per_size: Annotated[MarketUtilsPositionType, Name("claimableFundingAmountPerSize")] + + +class DepositFlags(Struct): + should_unwrap_native_token: Annotated[bool, Name("shouldUnwrapNativeToken")] + + +class DepositNumbers(Struct): + initial_long_token_amount: Annotated[primitives.uint256, Name("initialLongTokenAmount")] + initial_short_token_amount: Annotated[primitives.uint256, Name("initialShortTokenAmount")] + min_market_tokens: Annotated[primitives.uint256, Name("minMarketTokens")] + updated_at_block: Annotated[primitives.uint256, Name("updatedAtBlock")] + updated_at_time: Annotated[primitives.uint256, Name("updatedAtTime")] + execution_fee: Annotated[primitives.uint256, Name("executionFee")] + callback_gas_limit: Annotated[primitives.uint256, Name("callbackGasLimit")] + + +class DepositAddresses(Struct): + account: primitives.address + receiver: primitives.address + callback_contract: Annotated[primitives.address, Name("callbackContract")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + market: primitives.address + initial_long_token: Annotated[primitives.address, Name("initialLongToken")] + initial_short_token: Annotated[primitives.address, Name("initialShortToken")] + long_token_swap_path: Annotated[list[primitives.address], Name("longTokenSwapPath")] + short_token_swap_path: Annotated[list[primitives.address], Name("shortTokenSwapPath")] + + +class PositionFlags(Struct): + is_long: Annotated[bool, Name("isLong")] + + +class PositionNumbers(Struct): + size_in_usd: Annotated[primitives.uint256, Name("sizeInUsd")] + size_in_tokens: Annotated[primitives.uint256, Name("sizeInTokens")] + collateral_amount: Annotated[primitives.uint256, Name("collateralAmount")] + borrowing_factor: Annotated[primitives.uint256, Name("borrowingFactor")] + funding_fee_amount_per_size: Annotated[primitives.uint256, Name("fundingFeeAmountPerSize")] + long_token_claimable_funding_amount_per_size: Annotated[primitives.uint256, Name("longTokenClaimableFundingAmountPerSize")] + short_token_claimable_funding_amount_per_size: Annotated[primitives.uint256, Name("shortTokenClaimableFundingAmountPerSize")] + increased_at_block: Annotated[primitives.uint256, Name("increasedAtBlock")] + decreased_at_block: Annotated[primitives.uint256, Name("decreasedAtBlock")] + increased_at_time: Annotated[primitives.uint256, Name("increasedAtTime")] + decreased_at_time: Annotated[primitives.uint256, Name("decreasedAtTime")] + + +class PositionAddresses(Struct): + account: primitives.address + market: primitives.address + collateral_token: Annotated[primitives.address, Name("collateralToken")] + + +class PositionPricingUtilsPositionFees(Struct): + referral: PositionPricingUtilsPositionReferralFees + funding: PositionPricingUtilsPositionFundingFees + borrowing: PositionPricingUtilsPositionBorrowingFees + ui: PositionPricingUtilsPositionUiFees + collateral_token_price: Annotated[PriceProps, Name("collateralTokenPrice")] + position_fee_factor: Annotated[primitives.uint256, Name("positionFeeFactor")] + protocol_fee_amount: Annotated[primitives.uint256, Name("protocolFeeAmount")] + position_fee_receiver_factor: Annotated[primitives.uint256, Name("positionFeeReceiverFactor")] + fee_receiver_amount: Annotated[primitives.uint256, Name("feeReceiverAmount")] + fee_amount_for_pool: Annotated[primitives.uint256, Name("feeAmountForPool")] + position_fee_amount_for_pool: Annotated[primitives.uint256, Name("positionFeeAmountForPool")] + position_fee_amount: Annotated[primitives.uint256, Name("positionFeeAmount")] + total_cost_amount_excluding_funding: Annotated[primitives.uint256, Name("totalCostAmountExcludingFunding")] + total_cost_amount: Annotated[primitives.uint256, Name("totalCostAmount")] + + +class OrderFlags(Struct): + is_long: Annotated[bool, Name("isLong")] + should_unwrap_native_token: Annotated[bool, Name("shouldUnwrapNativeToken")] + is_frozen: Annotated[bool, Name("isFrozen")] + auto_cancel: Annotated[bool, Name("autoCancel")] + + +class OrderNumbers(Struct): + order_type: Annotated[primitives.uint8, Name("orderType")] + decrease_position_swap_type: Annotated[primitives.uint8, Name("decreasePositionSwapType")] + size_delta_usd: Annotated[primitives.uint256, Name("sizeDeltaUsd")] + initial_collateral_delta_amount: Annotated[primitives.uint256, Name("initialCollateralDeltaAmount")] + trigger_price: Annotated[primitives.uint256, Name("triggerPrice")] + acceptable_price: Annotated[primitives.uint256, Name("acceptablePrice")] + execution_fee: Annotated[primitives.uint256, Name("executionFee")] + callback_gas_limit: Annotated[primitives.uint256, Name("callbackGasLimit")] + min_output_amount: Annotated[primitives.uint256, Name("minOutputAmount")] + updated_at_block: Annotated[primitives.uint256, Name("updatedAtBlock")] + updated_at_time: Annotated[primitives.uint256, Name("updatedAtTime")] + + +class OrderAddresses(Struct): + account: primitives.address + receiver: primitives.address + cancellation_receiver: Annotated[primitives.address, Name("cancellationReceiver")] + callback_contract: Annotated[primitives.address, Name("callbackContract")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + market: primitives.address + initial_collateral_token: Annotated[primitives.address, Name("initialCollateralToken")] + swap_path: Annotated[list[primitives.address], Name("swapPath")] + + +class WithdrawalProps(Struct): + addresses: WithdrawalAddresses + numbers: WithdrawalNumbers + flags: WithdrawalFlags + + +class SwapPricingUtilsSwapFees(Struct): + fee_receiver_amount: Annotated[primitives.uint256, Name("feeReceiverAmount")] + fee_amount_for_pool: Annotated[primitives.uint256, Name("feeAmountForPool")] + amount_after_fees: Annotated[primitives.uint256, Name("amountAfterFees")] + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + ui_fee_receiver_factor: Annotated[primitives.uint256, Name("uiFeeReceiverFactor")] + ui_fee_amount: Annotated[primitives.uint256, Name("uiFeeAmount")] + + +class ShiftProps(Struct): + addresses: ShiftAddresses + numbers: ShiftNumbers + + +class MarketPoolValueInfoProps(Struct): + pool_value: Annotated[primitives.int256, Name("poolValue")] + long_pnl: Annotated[primitives.int256, Name("longPnl")] + short_pnl: Annotated[primitives.int256, Name("shortPnl")] + net_pnl: Annotated[primitives.int256, Name("netPnl")] + long_token_amount: Annotated[primitives.uint256, Name("longTokenAmount")] + short_token_amount: Annotated[primitives.uint256, Name("shortTokenAmount")] + long_token_usd: Annotated[primitives.uint256, Name("longTokenUsd")] + short_token_usd: Annotated[primitives.uint256, Name("shortTokenUsd")] + total_borrowing_fees: Annotated[primitives.uint256, Name("totalBorrowingFees")] + borrowing_fee_pool_factor: Annotated[primitives.uint256, Name("borrowingFeePoolFactor")] + impact_pool_amount: Annotated[primitives.uint256, Name("impactPoolAmount")] + + +class ReaderUtilsMarketInfo(Struct): + market: MarketProps + borrowing_factor_per_second_for_longs: Annotated[primitives.uint256, Name("borrowingFactorPerSecondForLongs")] + borrowing_factor_per_second_for_shorts: Annotated[primitives.uint256, Name("borrowingFactorPerSecondForShorts")] + base_funding: Annotated[ReaderUtilsBaseFundingValues, Name("baseFunding")] + next_funding: Annotated[MarketUtilsGetNextFundingAmountPerSizeResult, Name("nextFunding")] + virtual_inventory: Annotated[ReaderUtilsVirtualInventory, Name("virtualInventory")] + is_disabled: Annotated[bool, Name("isDisabled")] + + +class ReaderPricingUtilsExecutionPriceResult(Struct): + price_impact_usd: Annotated[primitives.int256, Name("priceImpactUsd")] + price_impact_diff_usd: Annotated[primitives.uint256, Name("priceImpactDiffUsd")] + execution_price: Annotated[primitives.uint256, Name("executionPrice")] + + +class PriceProps(Struct): + min: primitives.uint256 + max: primitives.uint256 + + +class MarketProps(Struct): + market_token: Annotated[primitives.address, Name("marketToken")] + index_token: Annotated[primitives.address, Name("indexToken")] + long_token: Annotated[primitives.address, Name("longToken")] + short_token: Annotated[primitives.address, Name("shortToken")] + + +class DepositProps(Struct): + addresses: DepositAddresses + numbers: DepositNumbers + flags: DepositFlags + + +class MarketUtilsMarketPrices(Struct): + index_token_price: Annotated[PriceProps, Name("indexTokenPrice")] + long_token_price: Annotated[PriceProps, Name("longTokenPrice")] + short_token_price: Annotated[PriceProps, Name("shortTokenPrice")] + + +class PositionProps(Struct): + addresses: PositionAddresses + numbers: PositionNumbers + flags: PositionFlags + + +class ReaderUtilsPositionInfo(Struct): + position: PositionProps + fees: PositionPricingUtilsPositionFees + execution_price_result: Annotated[ReaderPricingUtilsExecutionPriceResult, Name("executionPriceResult")] + base_pnl_usd: Annotated[primitives.int256, Name("basePnlUsd")] + uncapped_base_pnl_usd: Annotated[primitives.int256, Name("uncappedBasePnlUsd")] + pnl_after_price_impact_usd: Annotated[primitives.int256, Name("pnlAfterPriceImpactUsd")] + + +class OrderProps(Struct): + addresses: OrderAddresses + numbers: OrderNumbers + flags: OrderFlags + + +class SyntheticsReader(ProtocolBase): + get_account_orders: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256, primitives.uint256], + list[OrderProps] + ], + Name("getAccountOrders"), + ] + + get_account_position_info_list: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, list[primitives.bytes32], list[MarketUtilsMarketPrices], primitives.address], + list[ReaderUtilsPositionInfo] + ], + Name("getAccountPositionInfoList"), + ] + + get_account_positions: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256, primitives.uint256], + list[PositionProps] + ], + Name("getAccountPositions"), + ] + + get_adl_state: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, bool, MarketUtilsMarketPrices], + tuple[primitives.uint256, bool, primitives.int256, primitives.uint256] + ], + Name("getAdlState"), + ] + + get_deposit: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + DepositProps + ], + Name("getDeposit"), + ] + + get_deposit_amount_out: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, MarketUtilsMarketPrices, primitives.uint256, primitives.uint256, primitives.address, primitives.uint8, bool], + primitives.uint256 + ], + Name("getDepositAmountOut"), + ] + + get_execution_price: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, PriceProps, primitives.uint256, primitives.uint256, primitives.int256, bool], + ReaderPricingUtilsExecutionPriceResult + ], + Name("getExecutionPrice"), + ] + + get_market: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address], + MarketProps + ], + Name("getMarket"), + ] + + get_market_by_salt: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + MarketProps + ], + Name("getMarketBySalt"), + ] + + get_market_info: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketUtilsMarketPrices, primitives.address], + ReaderUtilsMarketInfo + ], + Name("getMarketInfo"), + ] + + get_market_info_list: Annotated[ + ContractFunc[ + tuple[primitives.address, list[MarketUtilsMarketPrices], primitives.uint256, primitives.uint256], + list[ReaderUtilsMarketInfo] + ], + Name("getMarketInfoList"), + ] + + get_market_token_price: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, PriceProps, PriceProps, PriceProps, primitives.bytes32, bool], + tuple[primitives.int256, MarketPoolValueInfoProps] + ], + Name("getMarketTokenPrice"), + ] + + get_markets: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256, primitives.uint256], + list[MarketProps] + ], + Name("getMarkets"), + ] + + get_net_pnl: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, PriceProps, bool], + primitives.int256 + ], + Name("getNetPnl"), + ] + + get_open_interest_with_pnl: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, PriceProps, bool, bool], + primitives.int256 + ], + Name("getOpenInterestWithPnl"), + ] + + get_order: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + OrderProps + ], + Name("getOrder"), + ] + + get_pnl: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, PriceProps, bool, bool], + primitives.int256 + ], + Name("getPnl"), + ] + + get_pnl_to_pool_factor: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, MarketUtilsMarketPrices, bool, bool], + primitives.int256 + ], + Name("getPnlToPoolFactor"), + ] + + get_position: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + PositionProps + ], + Name("getPosition"), + ] + + get_position_info: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.bytes32, MarketUtilsMarketPrices, primitives.uint256, primitives.address, bool], + ReaderUtilsPositionInfo + ], + Name("getPositionInfo"), + ] + + get_position_pnl_usd: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, MarketUtilsMarketPrices, primitives.bytes32, primitives.uint256], + tuple[primitives.int256, primitives.int256, primitives.uint256] + ], + Name("getPositionPnlUsd"), + ] + + get_shift: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + ShiftProps + ], + Name("getShift"), + ] + + get_swap_amount_out: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, MarketUtilsMarketPrices, primitives.address, primitives.uint256, primitives.address], + tuple[primitives.uint256, primitives.int256, SwapPricingUtilsSwapFees] + ], + Name("getSwapAmountOut"), + ] + + get_swap_price_impact: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.address, primitives.address, primitives.uint256, PriceProps, PriceProps], + tuple[primitives.int256, primitives.int256, primitives.int256] + ], + Name("getSwapPriceImpact"), + ] + + get_withdrawal: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + WithdrawalProps + ], + Name("getWithdrawal"), + ] + + get_withdrawal_amount_out: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, MarketUtilsMarketPrices, primitives.uint256, primitives.address, primitives.uint8], + tuple[primitives.uint256, primitives.uint256] + ], + Name("getWithdrawalAmountOut"), + ] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_router.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_router.py new file mode 100644 index 0000000..1ce642e --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_router.py @@ -0,0 +1,22 @@ +from typing import Annotated + +from eth_rpc import ProtocolBase, ContractFunc +from eth_rpc.types import primitives, Name, NoArgs + + +class SyntheticsRouter(ProtocolBase): + plugin_transfer: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.address, primitives.uint256], + None + ], + Name("pluginTransfer"), + ] + + role_store: Annotated[ + ContractFunc[ + NoArgs, + primitives.address + ], + Name("roleStore"), + ] From e06d0d16f9abff18a4e71aa887159a04b05cdf9a Mon Sep 17 00:00:00 2001 From: johnny-emp <164106913+johnny-emp@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:28:18 -0500 Subject: [PATCH 03/16] add handler for full struct names (#77) --- packages/eth_rpc/src/eth_rpc/codegen.py | 35 ++++++++++++++++++------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/eth_rpc/src/eth_rpc/codegen.py b/packages/eth_rpc/src/eth_rpc/codegen.py index a2ffd74..606a5d2 100644 --- a/packages/eth_rpc/src/eth_rpc/codegen.py +++ b/packages/eth_rpc/src/eth_rpc/codegen.py @@ -34,12 +34,15 @@ def object_to_type(obj): return _convert_type(obj["type"]) -def convert_types(types_): +def convert_types(types_, full_struct_names: bool = True): lst = [] models = [] for type_ in types_: if "components" in type_: - py_model_name = type_["internalType"].split(".")[-1].removeprefix("struct ") + if full_struct_names: + py_model_name = ''.join(type_["internalType"].split(".")).removeprefix("struct ") + else: + py_model_name = type_["internalType"].split(".")[-1].removeprefix("struct ") field_name = py_model_name while field_name.endswith("[]"): field_name = f"list[{py_model_name[:-2]}]" @@ -55,7 +58,7 @@ def convert_types(types_): return (tuple[*lst], models) -def codegen(abi: list[dict[str, Any]], contract_name: str) -> str: # noqa: C901 +def codegen(abi: list[dict[str, Any]], contract_name: str, full_struct_names: bool = True) -> str: # noqa: C901 """ Convert an ABI to the string implementation of a ProtocolBase. @@ -93,15 +96,18 @@ class WETH(ProtocolBase): inputs = func.get("inputs", []) outputs = func.get("outputs", []) - input_type, _models = convert_types(inputs) + input_type, _models = convert_types(inputs, full_struct_names=full_struct_names) for model_name, model in _models: if model_name not in model_dict: model_dict[model_name] = model elif model_dict[model_name] == model: continue else: - print("Warning: Duplicate model name with different fields") - model_dict[model_name + "_extra"] = model + print(f"Warning: Duplicate model name {model_name} with different fields") + count = 1 + while model_name + f"_{count}" in model_dict: + count += 1 + model_dict[model_name + f"_{count}"] = model output_type, __models = convert_types(outputs) @@ -112,8 +118,11 @@ class WETH(ProtocolBase): elif model_dict[model_name] == model[1]: continue else: - print("Warning: Duplicate model name with different fields") - model_dict[model_name + "_extra"] = model[1] + print(f"Warning: Duplicate model name {model_name} with different fields") + count = 1 + while model_name + f"_{count}" in model_dict: + count += 1 + model_dict[model_name + f"_{count}"] = model[1] has_name_annotation: bool = False alias: str @@ -149,7 +158,10 @@ class WETH(ProtocolBase): for _, fields in list(model_dict.items()): for field in fields: if field["internalType"].startswith("struct"): - model_name = field["internalType"].split(".")[-1].replace("[]", "") + if full_struct_names: + model_name = ''.join(field["internalType"].split(".")).replace("[]", "").removeprefix("struct ") + else: + model_name = field["internalType"].split(".")[-1].replace("[]", "") if model_name not in model_dict: model_dict[model_name] = field["components"] @@ -161,7 +173,10 @@ class {name}(Struct): embedded_types = [] for field in fields: if (internalType := field["internalType"]).startswith("struct"): - type_ = internalType.split(".")[-1].replace("[]", "") + if full_struct_names: + type_ = ''.join(internalType.split(".")).replace("[]", "").removeprefix("struct ") + else: + type_ = internalType.split(".")[-1].replace("[]", "") while internalType.endswith("[]"): type_ = list[type_] # type: ignore[valid-type] internalType = internalType[:-2] From 9e5f0282fe9b29240d27c8ff6e735ad9aa878d7f Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Thu, 14 Nov 2024 12:40:58 -0500 Subject: [PATCH 04/16] fix synthetics reader --- packages/eth_rpc/src/eth_rpc/codegen.py | 2 +- packages/eth_rpc/src/eth_rpc/main_cli.py | 5 +- .../src/eth_typeshed/gmx/synthetics_reader.py | 63 +++++++++++++++---- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/packages/eth_rpc/src/eth_rpc/codegen.py b/packages/eth_rpc/src/eth_rpc/codegen.py index 606a5d2..4d5d272 100644 --- a/packages/eth_rpc/src/eth_rpc/codegen.py +++ b/packages/eth_rpc/src/eth_rpc/codegen.py @@ -109,7 +109,7 @@ class WETH(ProtocolBase): count += 1 model_dict[model_name + f"_{count}"] = model - output_type, __models = convert_types(outputs) + output_type, __models = convert_types(outputs, full_struct_names=full_struct_names) for model in __models: model_name = model[0].replace("[]", "") diff --git a/packages/eth_rpc/src/eth_rpc/main_cli.py b/packages/eth_rpc/src/eth_rpc/main_cli.py index 81bbb51..8e971ba 100644 --- a/packages/eth_rpc/src/eth_rpc/main_cli.py +++ b/packages/eth_rpc/src/eth_rpc/main_cli.py @@ -42,8 +42,9 @@ def load(input_file, output, contract_name: str): @click.option("--output", "-o", default="abi.py") @click.option("--api-key", "-a", required=True) @click.option("--contract-name", "-c", default="AnonContract") +@click.option("--full-struct-names", "-f", is_flag=True, required=False) def explorer( - network: str, address: HexAddress, output: str, api_key, contract_name: str + network: str, address: HexAddress, output: str, api_key, contract_name: str, full_struct_names: bool = False ): if network.lower() not in ["ethereum", "base", "arbitrum"]: click.echo("Network not yet supported. Coming soon!") @@ -53,7 +54,7 @@ def explorer( abi = asyncio.run(network_type.get_abi(address, api_key)) with open(output, "w") as f: - f.write(codegen_cmd(abi, contract_name)) + f.write(codegen_cmd(abi, contract_name, full_struct_names=full_struct_names)) cli.add_command(codegen) diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py index 4d3f15d..811ad76 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py @@ -1,17 +1,7 @@ from typing import Annotated from eth_rpc import ProtocolBase, ContractFunc -from eth_rpc.types import primitives, Name, Struct - - -class MarketUtilsCollateralType(Struct): - longToken: primitives.uint256 - shortToken: primitives.uint256 - - -class MarketUtilsPositionType(Struct): - long: MarketUtilsCollateralType - short: MarketUtilsCollateralType +from eth_rpc.types import primitives, Name, NoArgs, Struct class WithdrawalFlags(Struct): @@ -124,6 +114,46 @@ class PositionAddresses(Struct): collateral_token: Annotated[primitives.address, Name("collateralToken")] +class PositionPricingUtilsPositionReferralFees(Struct): + referral_code: primitives.bytes32 + affiliate: primitives.address + trader: primitives.address + total_rebate_factor: Annotated[primitives.uint256, Name("totalRebateFactor")] + affiliate_reward_factor: Annotated[primitives.uint256, Name("affiliateRewardFactor")] + adjusted_affiliate_reward_factor: Annotated[primitives.uint256, Name("adjustedAffiliateRewardFactor")] + trader_discount_factor: Annotated[primitives.uint256, Name("traderDiscountFactor")] + total_rebate_amount: Annotated[primitives.uint256, Name("totalRebateAmount")] + trader_discount_amount: Annotated[primitives.uint256, Name("traderDiscountAmount")] + affiliate_reward_amount: Annotated[primitives.uint256, Name("affiliateRewardAmount")] + + +class PositionPricingUtilsPositionFundingFees(Struct): + funding_fee_amount: Annotated[primitives.uint256, Name("fundingFeeAmount")] + claimable_long_token_amount: Annotated[primitives.uint256, Name("claimableLongTokenAmount")] + claimable_short_token_amount: Annotated[primitives.uint256, Name("claimableShortTokenAmount")] + latest_funding_fee_amount_per_size: Annotated[primitives.uint256, Name("latestFundingFeeAmountPerSize")] + latest_long_token_claimable_funding_amount_per_size: Annotated[primitives.uint256, Name("latestLongTokenClaimableFundingAmountPerSize")] + latest_short_token_claimable_funding_amount_per_size: Annotated[primitives.uint256, Name("latestShortTokenClaimableFundingAmountPerSize")] + + +class PositionPricingUtilsPositionBorrowingFees(Struct): + borrowing_fee_usd: Annotated[primitives.uint256, Name("borrowingFeeUsd")] + borrowing_fee_amount: Annotated[primitives.uint256, Name("borrowingFeeAmount")] + borrowing_fee_receiver_factor: Annotated[primitives.uint256, Name("borrowingFeeReceiverFactor")] + borrowing_fee_amount_for_fee_receiver: Annotated[primitives.uint256, Name("borrowingFeeAmountForFeeReceiver")] + + +class PositionPricingUtilsPositionUiFees(Struct): + ui_fee_receiver: Annotated[primitives.address, Name("uiFeeReceiver")] + ui_fee_receiver_factor: Annotated[primitives.uint256, Name("uiFeeReceiverFactor")] + ui_fee_amount: Annotated[primitives.uint256, Name("uiFeeAmount")] + + +class PriceProps(Struct): + min: primitives.uint256 + max: primitives.uint256 + + class PositionPricingUtilsPositionFees(Struct): referral: PositionPricingUtilsPositionReferralFees funding: PositionPricingUtilsPositionFundingFees @@ -256,12 +286,21 @@ class PositionProps(Struct): class ReaderUtilsPositionInfo(Struct): position: PositionProps fees: PositionPricingUtilsPositionFees - execution_price_result: Annotated[ReaderPricingUtilsExecutionPriceResult, Name("executionPriceResult")] + execution_price_result: Annotated[ + ReaderPricingUtilsExecutionPriceResult, + Name("executionPriceResult"), + ] base_pnl_usd: Annotated[primitives.int256, Name("basePnlUsd")] uncapped_base_pnl_usd: Annotated[primitives.int256, Name("uncappedBasePnlUsd")] pnl_after_price_impact_usd: Annotated[primitives.int256, Name("pnlAfterPriceImpactUsd")] +class MarketUtilsMarketPrices[](Struct): + index_token_price: Annotated[PriceProps, Name("indexTokenPrice")] + long_token_price: Annotated[PriceProps, Name("longTokenPrice")] + short_token_price: Annotated[PriceProps, Name("shortTokenPrice")] + + class OrderProps(Struct): addresses: OrderAddresses numbers: OrderNumbers From f89cf09010d9c3e3482e419073a46a1afe9b0df0 Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Thu, 14 Nov 2024 12:46:54 -0500 Subject: [PATCH 05/16] cleanup synthetics reader types --- .../src/eth_typeshed/gmx/synthetics_reader.py | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py index 811ad76..63b0812 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py @@ -1,7 +1,7 @@ from typing import Annotated from eth_rpc import ProtocolBase, ContractFunc -from eth_rpc.types import primitives, Name, NoArgs, Struct +from eth_rpc.types import primitives, Name, Struct class WithdrawalFlags(Struct): @@ -51,6 +51,16 @@ class ReaderUtilsVirtualInventory(Struct): virtual_inventory_for_positions: Annotated[primitives.int256, Name("virtualInventoryForPositions")] +class MarketUtilsCollateralType(Struct): + long_token: Annotated[primitives.uint256, Name("longToken")] + short_token: Annotated[primitives.uint256, Name("shortToken")] + + +class MarketUtilsPositionType(Struct): + long: Annotated[MarketUtilsCollateralType, Name("long")] + short: Annotated[MarketUtilsCollateralType, Name("short")] + + class MarketUtilsGetNextFundingAmountPerSizeResult(Struct): longs_pay_shorts: Annotated[bool, Name("longsPayShorts")] funding_factor_per_second: Annotated[primitives.uint256, Name("fundingFactorPerSecond")] @@ -237,6 +247,13 @@ class MarketPoolValueInfoProps(Struct): impact_pool_amount: Annotated[primitives.uint256, Name("impactPoolAmount")] +class MarketProps(Struct): + market_token: Annotated[primitives.address, Name("marketToken")] + index_token: Annotated[primitives.address, Name("indexToken")] + long_token: Annotated[primitives.address, Name("longToken")] + short_token: Annotated[primitives.address, Name("shortToken")] + + class ReaderUtilsMarketInfo(Struct): market: MarketProps borrowing_factor_per_second_for_longs: Annotated[primitives.uint256, Name("borrowingFactorPerSecondForLongs")] @@ -253,18 +270,6 @@ class ReaderPricingUtilsExecutionPriceResult(Struct): execution_price: Annotated[primitives.uint256, Name("executionPrice")] -class PriceProps(Struct): - min: primitives.uint256 - max: primitives.uint256 - - -class MarketProps(Struct): - market_token: Annotated[primitives.address, Name("marketToken")] - index_token: Annotated[primitives.address, Name("indexToken")] - long_token: Annotated[primitives.address, Name("longToken")] - short_token: Annotated[primitives.address, Name("shortToken")] - - class DepositProps(Struct): addresses: DepositAddresses numbers: DepositNumbers @@ -295,12 +300,6 @@ class ReaderUtilsPositionInfo(Struct): pnl_after_price_impact_usd: Annotated[primitives.int256, Name("pnlAfterPriceImpactUsd")] -class MarketUtilsMarketPrices[](Struct): - index_token_price: Annotated[PriceProps, Name("indexTokenPrice")] - long_token_price: Annotated[PriceProps, Name("longTokenPrice")] - short_token_price: Annotated[PriceProps, Name("shortTokenPrice")] - - class OrderProps(Struct): addresses: OrderAddresses numbers: OrderNumbers From 49ceb9a1ae6296120691582d6c2d91b1fc2ec43d Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Thu, 14 Nov 2024 13:51:35 -0500 Subject: [PATCH 06/16] add gmx to eth_protocols --- .vscode/settings.json | 4 +- .../src/eth_protocols/gmx/__init__.py | 0 .../src/eth_protocols/gmx/keys.py | 172 +++++++++++++ .../eth_protocols/gmx/synthetics_reader.py | 57 +++++ .../src/eth_protocols/gmx/utils/__init__.py | 0 .../src/eth_protocols/gmx/utils/manager.py | 182 ++++++++++++++ .../eth_protocols/src/eth_protocols/py.typed | 0 .../eth_rpc/src/eth_rpc/networks/__init__.py | 3 + .../eth_rpc/src/eth_rpc/networks/avalanche.py | 24 ++ .../src/eth_typeshed/gmx/__init__.py | 10 + .../src/eth_typeshed/gmx/contracts.py | 129 +++++++--- .../gmx/synthetics_reader/__init__.py | 17 ++ .../gmx/synthetics_reader/enums.py | 26 ++ .../gmx/synthetics_reader/schemas.py | 57 +++++ .../synthetics_reader/synthetics_reader.py | 237 ++++++++++++++++++ .../types.py} | 211 ---------------- 16 files changed, 881 insertions(+), 248 deletions(-) create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/__init__.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/keys.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py create mode 100644 packages/eth_protocols/src/eth_protocols/py.typed create mode 100644 packages/eth_rpc/src/eth_rpc/networks/avalanche.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/enums.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py create mode 100644 packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py rename packages/eth_typeshed/src/eth_typeshed/gmx/{synthetics_reader.py => synthetics_reader/types.py} (70%) diff --git a/.vscode/settings.json b/.vscode/settings.json index f1f8fac..4a8a94c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,5 @@ { - "python.envFile": "${workspaceFolder}/.env" + "python.envFile": "${workspaceFolder}/.env", + "editor.tabSize": 4, + "editor.insertSpaces": true } diff --git a/packages/eth_protocols/src/eth_protocols/gmx/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/eth_protocols/src/eth_protocols/gmx/keys.py b/packages/eth_protocols/src/eth_protocols/gmx/keys.py new file mode 100644 index 0000000..d5a4787 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/keys.py @@ -0,0 +1,172 @@ +from .gmx_utils import create_hash_string, create_hash, get_datastore_contract + +ACCOUNT_POSITION_LIST = create_hash_string("ACCOUNT_POSITION_LIST") +CLAIMABLE_FEE_AMOUNT = create_hash_string("CLAIMABLE_FEE_AMOUNT") +DECREASE_ORDER_GAS_LIMIT = create_hash_string("DECREASE_ORDER_GAS_LIMIT") +DEPOSIT_GAS_LIMIT = create_hash_string("DEPOSIT_GAS_LIMIT") + +WITHDRAWAL_GAS_LIMIT = create_hash_string("WITHDRAWAL_GAS_LIMIT") + +EXECUTION_GAS_FEE_BASE_AMOUNT = create_hash_string("EXECUTION_GAS_FEE_BASE_AMOUNT") +EXECUTION_GAS_FEE_MULTIPLIER_FACTOR = create_hash_string( + "EXECUTION_GAS_FEE_MULTIPLIER_FACTOR") +INCREASE_ORDER_GAS_LIMIT = create_hash_string("INCREASE_ORDER_GAS_LIMIT") +MAX_OPEN_INTEREST = create_hash_string("MAX_OPEN_INTEREST") +MAX_POSITION_IMPACT_FACTOR_FOR_LIQUIDATIONS_KEY = create_hash_string( + "MAX_POSITION_IMPACT_FACTOR_FOR_LIQUIDATIONS" +) +MAX_PNL_FACTOR_FOR_TRADERS = create_hash_string("MAX_PNL_FACTOR_FOR_TRADERS") +MAX_PNL_FACTOR_FOR_DEPOSITS = create_hash_string("MAX_PNL_FACTOR_FOR_DEPOSITS") +MAX_PNL_FACTOR_FOR_WITHDRAWALS = create_hash_string("MAX_PNL_FACTOR_FOR_WITHDRAWALS") +MIN_ADDITIONAL_GAS_FOR_EXECUTION = create_hash_string("MIN_ADDITIONAL_GAS_FOR_EXECUTION") +MIN_COLLATERAL_USD = create_hash_string("MIN_COLLATERAL_USD") +MIN_COLLATERAL_FACTOR_KEY = create_hash_string("MIN_COLLATERAL_FACTOR") +MIN_POSITION_SIZE_USD = create_hash_string("MIN_POSITION_SIZE_USD") +OPEN_INTEREST_IN_TOKENS = create_hash_string("OPEN_INTEREST_IN_TOKENS") +OPEN_INTEREST = create_hash_string("OPEN_INTEREST") +OPEN_INTEREST_RESERVE_FACTOR = create_hash_string( + "OPEN_INTEREST_RESERVE_FACTOR" +) +POOL_AMOUNT = create_hash_string("POOL_AMOUNT") +RESERVE_FACTOR = create_hash_string("RESERVE_FACTOR") +SINGLE_SWAP_GAS_LIMIT = create_hash_string("SINGLE_SWAP_GAS_LIMIT") +SWAP_ORDER_GAS_LIMIT = create_hash_string("SWAP_ORDER_GAS_LIMIT") +VIRTUAL_TOKEN_ID = create_hash_string("VIRTUAL_TOKEN_ID") + + +def accountPositionListKey(account): + return create_hash( + ["bytes32", "address"], + [ACCOUNT_POSITION_LIST, account] + ) + + +def claimable_fee_amount_key(market: str, token: str): + return create_hash( + ["bytes32", "address", "address"], + [CLAIMABLE_FEE_AMOUNT, market, token] + ) + + +def decrease_order_gas_limit_key(): + return DECREASE_ORDER_GAS_LIMIT + + +def deposit_gas_limit_key(): + return DEPOSIT_GAS_LIMIT + + +def execution_gas_fee_base_amount_key(): + return EXECUTION_GAS_FEE_BASE_AMOUNT + + +def execution_gas_fee_multiplier_key(): + return EXECUTION_GAS_FEE_MULTIPLIER_FACTOR + + +def increase_order_gas_limit_key(): + return INCREASE_ORDER_GAS_LIMIT + + +def min_additional_gas_for_execution_key(): + return MIN_ADDITIONAL_GAS_FOR_EXECUTION + + +def min_collateral(): + return MIN_COLLATERAL_USD + + +def min_collateral_factor_key(market): + return create_hash(["bytes32", "address"], [MIN_COLLATERAL_FACTOR_KEY, market]) + + +def max_open_interest_key(market: str, + is_long: bool): + + return create_hash( + ["bytes32", "address", "bool"], + [MAX_OPEN_INTEREST, market, is_long] + ) + + +def max_position_impact_factor_for_liquidations_key(market): + return create_hash(["bytes32", "address"], + [MAX_POSITION_IMPACT_FACTOR_FOR_LIQUIDATIONS_KEY, market]) + + +def open_interest_in_tokens_key( + market: str, + collateral_token: str, + is_long: bool +): + return create_hash( + ["bytes32", "address", "address", "bool"], + [OPEN_INTEREST_IN_TOKENS, market, collateral_token, is_long] + ) + + +def open_interest_key( + market: str, + collateral_token: str, + is_long: bool +): + return create_hash( + ["bytes32", "address", "address", "bool"], + [OPEN_INTEREST, market, collateral_token, is_long] + ) + + +def open_interest_reserve_factor_key( + market: str, + is_long: bool +): + return create_hash( + ["bytes32", "address", "bool"], + [OPEN_INTEREST_RESERVE_FACTOR, market, is_long] + ) + + +def pool_amount_key( + market: str, + token: str +): + return create_hash( + ["bytes32", "address", "address"], + [POOL_AMOUNT, market, token] + ) + + +def reserve_factor_key( + market: str, + is_long: bool +): + return create_hash( + ["bytes32", "address", "bool"], + [RESERVE_FACTOR, market, is_long] + ) + + +def single_swap_gas_limit_key(): + return SINGLE_SWAP_GAS_LIMIT + + +def swap_order_gas_limit_key(): + return SWAP_ORDER_GAS_LIMIT + + +def virtualTokenIdKey(token: str): + return create_hash(["bytes32", "address"], [VIRTUAL_TOKEN_ID, token]) + + +def withdraw_gas_limit_key(): + return WITHDRAWAL_GAS_LIMIT + + +if __name__ == "__main__": + # market = '0x70d95587d40A2caf56bd97485aB3Eec10Bee6336' + # token = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1' + # token = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' + + token = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1" + + hash_data = virtualTokenIdKey(token) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py new file mode 100644 index 0000000..c74fa36 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py @@ -0,0 +1,57 @@ +from typing import ClassVar, Literal + +from pydantic import BaseModel, PrivateAttr + +from eth_rpc.networks import Arbitrum, Avalanche +from eth_typeshed.gmx import SyntheticsReader as SyntheticsReaderContract, ExecutionPriceParams +from eth_typeshed.gmx.synthetics_reader.schemas import ( + DepositAmountOutParams, + SwapAmountOutParams, + SwapAmountOutResponse, + WithdrawalAmountOutParams, + WithdrawalAmountOutResponse, +) + +PRECISION = 30 + +class ExecutionPriceResult(BaseModel): + execution_price: float + price_impact_usd: float + + +class SyntheticsReader(BaseModel): + """ + SyntheticsReader contract + """ + contract_addresses: ClassVar[dict[Literal["arbitrum", "avalanche"], str]] = { + "arbitrum": "0x5Ca84c34a381434786738735265b9f3FD814b824", + "avalanche": "0xBAD04dDcc5CC284A86493aFA75D2BEb970C72216" + } + + network: Literal["arbitrum", "avalanche"] + _contract: SyntheticsReaderContract = PrivateAttr() + + @property + def network_type(self) -> type[Arbitrum] | type[Avalanche]: + return Arbitrum if self.network == "arbitrum" else Avalanche + + def model_post_init(self, __context): + contract_address = self.contract_addresses[self.network] + self._contract = SyntheticsReaderContract[self.network_type](address=contract_address) + + async def get_execution_price(self, params: ExecutionPriceParams, decimals: int = 18) -> ExecutionPriceResult: + result = await self._contract.get_execution_price(params).get() + + return ExecutionPriceResult( + execution_price=result.execution_price / 10**(PRECISION - decimals), + price_impact_usd=result.price_impact_usd / 10**PRECISION + ) + + async def get_estimated_swap_output(self, params: SwapAmountOutParams) -> SwapAmountOutResponse: + return await self._contract.get_swap_amount_out(params).get() + + async def get_estimated_deposit_amount_out(self, params: DepositAmountOutParams): + return await self._contract.get_deposit_amount_out(params).get() + + async def get_estimated_withdrawal_amount_out(self, params: WithdrawalAmountOutParams) -> WithdrawalAmountOutResponse: + return await self._contract.get_withdrawal_amount_out(params).get() diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py new file mode 100644 index 0000000..215db2a --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py @@ -0,0 +1,182 @@ +from typing import Literal + +import httpx + + +async def get_tokens_address_dict(chain: Literal["arbitrum", "avalanche"] = "arbitrum"): + """ + Query the GMX infra api for to generate dictionary of tokens available on v2 + + Parameters + ---------- + chain : avalanche | arbitrum + + Returns + ------- + token_address_dict : dict + dictionary containing available tokens to trade on GMX. + + """ + url = { + "arbitrum": "https://arbitrum-api.gmxinfra.io/tokens", + "avalanche": "https://avalanche-api.gmxinfra.io/tokens" + } + + try: + async with httpx.AsyncClient() as client: + response = await client.get(url[chain]) + + # Check if the request was successful (status code 200) + if response.status_code == 200: + token_infos = response.json()['tokens'] + else: + print(f"Error: {response.status_code}") + except httpx.RequestError as e: + print(f"Error: {e}") + + token_address_dict = {} + + for token_info in token_infos: + token_address_dict[token_info['address']] = token_info + + return token_address_dict + + +def find_dictionary_by_key_value(outer_dict: dict, key: str, value: str): + """ + For a given dictionary, find a value which matches a set of keys + + Parameters + ---------- + outer_dict : dict + dictionary to filter through. + key : str + keys to search for. + value : str + required key to match. + + """ + for inner_dict in outer_dict.values(): + if key in inner_dict and inner_dict[key] == value: + return inner_dict + return None + + +PRECISION = 30 + + +def apply_factor(value, factor): + return value * factor / 10**30 + + +def get_funding_factor_per_period( + market_info: dict, is_long: bool, period_in_seconds: int, + long_interest_usd: int, short_interest_usd: int +): + """ + For a given market, calculate the funding factor for a given period + + Parameters + ---------- + market_info : dict + market parameters returned from the reader contract. + is_long : bool + direction of the position. + period_in_seconds : int + Want percentage rate we want to output to be in. + long_interest_usd : int + expanded decimal long interest. + short_interest_usd : int + expanded decimal short interest. + + """ + + funding_factor_per_second = ( + market_info['funding_factor_per_second'] * 10**-28 + ) + + long_pays_shorts = market_info['is_long_pays_short'] + + if is_long: + is_larger_side = long_pays_shorts + else: + is_larger_side = not long_pays_shorts + + if is_larger_side: + factor_per_second = funding_factor_per_second * -1 + else: + if long_pays_shorts: + larger_interest_usd = long_interest_usd + smaller_interest_usd = short_interest_usd + + else: + larger_interest_usd = short_interest_usd + smaller_interest_usd = long_interest_usd + + if smaller_interest_usd > 0: + ratio = larger_interest_usd * 10**30 / smaller_interest_usd + + else: + ratio = 0 + + factor_per_second = apply_factor(ratio, funding_factor_per_second) + + return factor_per_second * period_in_seconds + + +def determine_swap_route(markets: dict, in_token: str, out_token: str): + """ + Using the available markets, find the list of GMX markets required + to swap from token in to token out + + Parameters + ---------- + markets : dict + dictionary of markets output by getMarketInfo. + in_token : str + contract address of in token. + out_token : str + contract address of out token. + + Returns + ------- + list + list of GMX markets to swap through. + is_requires_multi_swap : TYPE + requires more than one market to pass thru. + + """ + + if in_token == "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f": + in_token = "0x47904963fc8b2340414262125aF798B9655E58Cd" + + if out_token == "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f": + out_token = "0x47904963fc8b2340414262125aF798B9655E58Cd" + + if in_token == "0xaf88d065e77c8cC2239327C5EDb3A432268e5831": + gmx_market_address = find_dictionary_by_key_value( + markets, + "index_token_address", + out_token + )['gmx_market_address'] + else: + gmx_market_address = find_dictionary_by_key_value( + markets, + "index_token_address", + in_token + )['gmx_market_address'] + + is_requires_multi_swap = False + + if out_token != "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" and \ + in_token != "0xaf88d065e77c8cC2239327C5EDb3A432268e5831": + is_requires_multi_swap = True + second_gmx_market_address = find_dictionary_by_key_value( + markets, + "index_token_address", + out_token + )['gmx_market_address'] + + return [gmx_market_address, second_gmx_market_address], is_requires_multi_swap + + return [gmx_market_address], is_requires_multi_swap diff --git a/packages/eth_protocols/src/eth_protocols/py.typed b/packages/eth_protocols/src/eth_protocols/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/packages/eth_rpc/src/eth_rpc/networks/__init__.py b/packages/eth_rpc/src/eth_rpc/networks/__init__.py index 56b8c1f..1da4480 100644 --- a/packages/eth_rpc/src/eth_rpc/networks/__init__.py +++ b/packages/eth_rpc/src/eth_rpc/networks/__init__.py @@ -1,6 +1,7 @@ from eth_rpc.types import Network from .arbitrum import Arbitrum, ArbitrumSepolia +from .avalanche import Avalanche from .base import Base, BaseSepolia from .ethereum import Ethereum from .linea import Linea @@ -15,6 +16,7 @@ 23294: Sapphire, 23295: SapphireTestnet, 59144: Linea, + 43114: Avalanche, } @@ -38,6 +40,7 @@ def get_network_by_chain_id(chain_id): __all__ = [ "Arbitrum", "ArbitrumSepolia", + "Avalanche", "Base", "BaseSepolia", "Ethereum", diff --git a/packages/eth_rpc/src/eth_rpc/networks/avalanche.py b/packages/eth_rpc/src/eth_rpc/networks/avalanche.py new file mode 100644 index 0000000..b98463a --- /dev/null +++ b/packages/eth_rpc/src/eth_rpc/networks/avalanche.py @@ -0,0 +1,24 @@ +from typing import ClassVar + +from eth_rpc.types import BlockExplorer, Network, Rpcs, RpcUrl +from pydantic.networks import Url + + +class Avalanche(Network): + chain_id: ClassVar[int] = 43114 + name: ClassVar[str] = "Avalanche" + native_currency: ClassVar[str] = "AVAX" + rpc: ClassVar[Rpcs] = Rpcs( + default=RpcUrl( + http=Url("https://avalanche-fuji.publicnode.com"), + wss=None, + # wss=Url("wss://test.com"), + ) + ) + block_explorer: ClassVar[BlockExplorer] = BlockExplorer( + name="Snowtrace", + url="https://snowtrace.io", + api_url="https://api.snowtrace.io/api", + ) + alchemy_str: ClassVar[str | None] = "avalanche-fuji" + apprx_block_time: ClassVar[float] = 3.0 diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py index e69de29..fc0fc6e 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py @@ -0,0 +1,10 @@ +from .contracts import CONTRACTS +from .synthetics_reader import SyntheticsReader, DepositAmountOutParams, ExecutionPriceParams, ReaderPricingUtilsExecutionPriceResult + +__all__ = [ + "CONTRACTS", + "DepositAmountOutParams", + "ExecutionPriceParams", + "ReaderPricingUtilsExecutionPriceResult", + "SyntheticsReader", +] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py b/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py index 9497274..ad6f499 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py @@ -1,38 +1,95 @@ +from pydantic import BaseModel + +from eth_rpc import ProtocolBase +from eth_rpc.networks import get_network_by_name + +from .datastore import Datastore +from .event_emitter import EventEmitter +from .exchange_router import ExchangeRouter +from .deposit_vault import DepositVault +from .withdrawal_vault import WithdrawalVault +from .order_vault import OrderVault +from .synthetics_reader.synthetics_reader import SyntheticsReader +from .synthetics_router import SyntheticsRouter +from .glv_reader import GLVReader + + +class GMXContract(BaseModel): + contract: type[ProtocolBase] + networks: dict[str, str | None] + + def get_instance(self, network: str) -> "ProtocolBase": + network_type = get_network_by_name(network) + if network not in self.networks: + raise ValueError(f"Contract not found for network: {network}") + + contract_address = self.networks[network] + contract = self.contract[network_type](address=contract_address) + return contract + + CONTRACTS = { - "datastore": { - "arbitrum": "0xFD70de6b91282D8017aA4E741e9Ae325CAb992d8", - "avalanche": "0x2F0b22339414ADeD7D5F06f9D604c7fF5b2fe3f6" - }, - "eventemitter": { - "arbitrum": "0xC8ee91A54287DB53897056e12D9819156D3822Fb", - "avalanche": "0xDb17B211c34240B014ab6d61d4A31FA0C0e20c26" - }, - "exchangerouter": { - "arbitrum": "0x69C527fC77291722b52649E45c838e41be8Bf5d5", - "avalanche": "0x3BE24AED1a4CcaDebF2956e02C27a00726D4327d" - }, - "depositvault": { - "arbitrum": "0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55", - "avalanche": "0x90c670825d0C62ede1c5ee9571d6d9a17A722DFF" - }, - "withdrawalvault": { - "arbitrum": "0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55", - "avalanche": "0xf5F30B10141E1F63FC11eD772931A8294a591996" - }, - "ordervault": { - "arbitrum": "0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5", - "avalanche": "0xD3D60D22d415aD43b7e64b510D86A30f19B1B12C" - }, - "syntheticsreader": { - "arbitrum": "0x5Ca84c34a381434786738735265b9f3FD814b824", - "avalanche": "0xBAD04dDcc5CC284A86493aFA75D2BEb970C72216" - }, - "syntheticsrouter": { - "arbitrum": "0x7452c558d45f8afC8c83dAe62C3f8A5BE19c71f6", - "avalanche": "0x820F5FfC5b525cD4d88Cd91aCf2c28F16530Cc68" - }, - "glvreader": { - "arbitrum": "0xd4f522c4339Ae0A90a156bd716715547e44Bed65", - "avalanche": None - } + "datastore": GMXContract( + networks={ + "arbitrum": "0xFD70de6b91282D8017aA4E741e9Ae325CAb992d8", + "avalanche": "0x2F0b22339414ADeD7D5F06f9D604c7fF5b2fe3f6" + }, + contract=Datastore + ), + "eventemitter": GMXContract( + networks={ + "arbitrum": "0xC8ee91A54287DB53897056e12D9819156D3822Fb", + "avalanche": "0xDb17B211c34240B014ab6d61d4A31FA0C0e20c26" + }, + contract=EventEmitter + ), + "exchangerouter": GMXContract( + networks={ + "arbitrum": "0x69C527fC77291722b52649E45c838e41be8Bf5d5", + "avalanche": "0x3BE24AED1a4CcaDebF2956e02C27a00726D4327d" + }, + contract=ExchangeRouter + ), + "depositvault": GMXContract( + networks={ + "arbitrum": "0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55", + "avalanche": "0x90c670825d0C62ede1c5ee9571d6d9a17A722DFF" + }, + contract=DepositVault + ), + "withdrawalvault": GMXContract( + networks={ + "arbitrum": "0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55", + "avalanche": "0xf5F30B10141E1F63FC11eD772931A8294a591996" + }, + contract=WithdrawalVault + ), + "ordervault": GMXContract( + networks={ + "arbitrum": "0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5", + "avalanche": "0xD3D60D22d415aD43b7e64b510D86A30f19B1B12C" + }, + contract=OrderVault + ), + "syntheticsreader": GMXContract( + networks={ + "arbitrum": "0x5Ca84c34a381434786738735265b9f3FD814b824", + "avalanche": "0xBAD04dDcc5CC284A86493aFA75D2BEb970C72216" + }, + contract=SyntheticsReader + ), + "syntheticsrouter": GMXContract( + networks={ + "arbitrum": "0x7452c558d45f8afC8c83dAe62C3f8A5BE19c71f6", + "avalanche": "0x820F5FfC5b525cD4d88Cd91aCf2c28F16530Cc68" + }, + contract=SyntheticsRouter + ), + "glvreader": GMXContract( + networks={ + "arbitrum": "0xd4f522c4339Ae0A90a156bd716715547e44Bed65", + "avalanche": None + }, + contract=GLVReader + ) } diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py new file mode 100644 index 0000000..73ce7aa --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py @@ -0,0 +1,17 @@ +from .schemas import DepositAmountOutParams, ExecutionPriceParams +from .synthetics_reader import SyntheticsReader +from .types import ( + OrderProps, + ReaderUtilsPositionInfo, + ReaderPricingUtilsExecutionPriceResult, +) + + +__all__ = [ + "DepositAmountOutParams", + "ExecutionPriceParams", + "SyntheticsReader", + "OrderProps", + "ReaderUtilsPositionInfo", + "ReaderPricingUtilsExecutionPriceResult", +] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/enums.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/enums.py new file mode 100644 index 0000000..bb104b7 --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/enums.py @@ -0,0 +1,26 @@ +from enum import IntEnum + + +class SwapPricingType(IntEnum): + Swap = 0 + Shift = 1 + Atomic = 2 + Deposit = 3 + Withdrawal = 4 + + +class OrderType(IntEnum): + MarketSwap = 0 + LimitSwap = 1 + MarketIncrease = 2 + LimitIncrease = 3 + MarketDecrease = 4 + LimitDecrease = 5 + StopLossDecrease = 6 + Liquidation = 7 + + +class DecreasePositionSwapType(IntEnum): + NoSwap = 0 + SwapPnlTokenToCollateralToken = 1 + SwapCollateralTokenToPnlToken = 2 diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py new file mode 100644 index 0000000..08f4e72 --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py @@ -0,0 +1,57 @@ +from typing import Annotated + +from pydantic import BaseModel + +from eth_rpc.types import primitives, Name +from .enums import SwapPricingType +from .types import MarketProps, MarketUtilsMarketPrices, PriceProps, SwapPricingUtilsSwapFees + + +class ExecutionPriceParams(BaseModel): + data_store: Annotated[primitives.address, Name("dataStore")] + market_key: Annotated[primitives.address, Name("marketKey")] + index_token_price: Annotated[PriceProps, Name("indexTokenPrice")] + position_size_in_usd: Annotated[primitives.uint256, Name("positionSizeInUsd")] + position_size_in_tokens: Annotated[primitives.uint256, Name("positionSizeInTokens")] + size_delta_usd: Annotated[primitives.int256, Name("sizeDeltaUsd")] + is_long: Annotated[bool, Name("isLong")] + + +class SwapAmountOutParams(BaseModel): + data_store: Annotated[primitives.address, Name("dataStore")] + market: MarketProps + prices: MarketUtilsMarketPrices + token_in: primitives.address + amount_in: primitives.address + ui_fee_receiver: primitives.address + + +class SwapAmountOutResponse(BaseModel): + cache_amount_out: primitives.uint256 + impactAmount: primitives.uint256 + fees: SwapPricingUtilsSwapFees + + +class DepositAmountOutParams(BaseModel): + data_store: primitives.address + market: MarketProps + prices: MarketUtilsMarketPrices + long_token_amount: primitives.uint256 + short_token_amount: primitives.uint256 + ui_fee_receiver: primitives.address + swap_pricing_type: SwapPricingType + include_virtual_inventory_impact: bool + + +class WithdrawalAmountOutParams(BaseModel): + data_store: primitives.address + market: MarketProps + prices: MarketUtilsMarketPrices + market_token_amount: primitives.uint256 + ui_fee_receiver: primitives.address + swap_pricing_type: SwapPricingType + + +class WithdrawalAmountOutResponse(BaseModel): + long_amount_after_fees: primitives.uint256 + short_amount_after_fees: primitives.uint256 diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py new file mode 100644 index 0000000..4cdbb20 --- /dev/null +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py @@ -0,0 +1,237 @@ +from typing import Annotated + +from eth_rpc import ProtocolBase, ContractFunc +from eth_rpc.types import primitives, Name + +from .schemas import ( + DepositAmountOutParams, + ExecutionPriceParams, + SwapAmountOutParams, + SwapAmountOutResponse, + WithdrawalAmountOutParams, + WithdrawalAmountOutResponse +) +from .types import ( + OrderProps, + ReaderUtilsPositionInfo, + ReaderPricingUtilsExecutionPriceResult, + PositionProps, + MarketProps, + MarketPoolValueInfoProps, + DepositProps, + WithdrawalProps, + ShiftProps, + PriceProps, + MarketUtilsMarketPrices, + ReaderUtilsMarketInfo +) + + +class SyntheticsReader(ProtocolBase): + get_account_orders: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256, primitives.uint256], + list[OrderProps] + ], + Name("getAccountOrders"), + ] + + get_account_position_info_list: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, list[primitives.bytes32], list[MarketUtilsMarketPrices], primitives.address], + list[ReaderUtilsPositionInfo] + ], + Name("getAccountPositionInfoList"), + ] + + get_account_positions: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.uint256, primitives.uint256], + list[PositionProps] + ], + Name("getAccountPositions"), + ] + + get_adl_state: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, bool, MarketUtilsMarketPrices], + tuple[primitives.uint256, bool, primitives.int256, primitives.uint256] + ], + Name("getAdlState"), + ] + + get_deposit: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + DepositProps + ], + Name("getDeposit"), + ] + + get_deposit_amount_out: Annotated[ + ContractFunc[ + DepositAmountOutParams, + primitives.uint256 + ], + Name("getDepositAmountOut"), + ] + + get_execution_price: Annotated[ + ContractFunc[ + ExecutionPriceParams, + ReaderPricingUtilsExecutionPriceResult + ], + Name("getExecutionPrice"), + ] + + get_market: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address], + MarketProps + ], + Name("getMarket"), + ] + + get_market_by_salt: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + MarketProps + ], + Name("getMarketBySalt"), + ] + + get_market_info: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketUtilsMarketPrices, primitives.address], + ReaderUtilsMarketInfo + ], + Name("getMarketInfo"), + ] + + get_market_info_list: Annotated[ + ContractFunc[ + tuple[primitives.address, list[MarketUtilsMarketPrices], primitives.uint256, primitives.uint256], + list[ReaderUtilsMarketInfo] + ], + Name("getMarketInfoList"), + ] + + get_market_token_price: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, PriceProps, PriceProps, PriceProps, primitives.bytes32, bool], + tuple[primitives.int256, MarketPoolValueInfoProps] + ], + Name("getMarketTokenPrice"), + ] + + get_markets: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.uint256, primitives.uint256], + list[MarketProps] + ], + Name("getMarkets"), + ] + + get_net_pnl: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, PriceProps, bool], + primitives.int256 + ], + Name("getNetPnl"), + ] + + get_open_interest_with_pnl: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, PriceProps, bool, bool], + primitives.int256 + ], + Name("getOpenInterestWithPnl"), + ] + + get_order: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + OrderProps + ], + Name("getOrder"), + ] + + get_pnl: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, PriceProps, bool, bool], + primitives.int256 + ], + Name("getPnl"), + ] + + get_pnl_to_pool_factor: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, MarketUtilsMarketPrices, bool, bool], + primitives.int256 + ], + Name("getPnlToPoolFactor"), + ] + + get_position: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + PositionProps + ], + Name("getPosition"), + ] + + get_position_info: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.bytes32, MarketUtilsMarketPrices, primitives.uint256, primitives.address, bool], + ReaderUtilsPositionInfo + ], + Name("getPositionInfo"), + ] + + get_position_pnl_usd: Annotated[ + ContractFunc[ + tuple[primitives.address, MarketProps, MarketUtilsMarketPrices, primitives.bytes32, primitives.uint256], + tuple[primitives.int256, primitives.int256, primitives.uint256] + ], + Name("getPositionPnlUsd"), + ] + + get_shift: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + ShiftProps + ], + Name("getShift"), + ] + + get_swap_amount_out: Annotated[ + ContractFunc[ + SwapAmountOutParams, + SwapAmountOutResponse, + ], + Name("getSwapAmountOut"), + ] + + get_swap_price_impact: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.address, primitives.address, primitives.address, primitives.uint256, PriceProps, PriceProps], + tuple[primitives.int256, primitives.int256, primitives.int256] + ], + Name("getSwapPriceImpact"), + ] + + get_withdrawal: Annotated[ + ContractFunc[ + tuple[primitives.address, primitives.bytes32], + WithdrawalProps + ], + Name("getWithdrawal"), + ] + + get_withdrawal_amount_out: Annotated[ + ContractFunc[ + WithdrawalAmountOutParams, + WithdrawalAmountOutResponse, + ], + Name("getWithdrawalAmountOut"), + ] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/types.py similarity index 70% rename from packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py rename to packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/types.py index 63b0812..f1ea024 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/types.py @@ -1,6 +1,5 @@ from typing import Annotated -from eth_rpc import ProtocolBase, ContractFunc from eth_rpc.types import primitives, Name, Struct @@ -304,213 +303,3 @@ class OrderProps(Struct): addresses: OrderAddresses numbers: OrderNumbers flags: OrderFlags - - -class SyntheticsReader(ProtocolBase): - get_account_orders: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.address, primitives.uint256, primitives.uint256], - list[OrderProps] - ], - Name("getAccountOrders"), - ] - - get_account_position_info_list: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.address, list[primitives.bytes32], list[MarketUtilsMarketPrices], primitives.address], - list[ReaderUtilsPositionInfo] - ], - Name("getAccountPositionInfoList"), - ] - - get_account_positions: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.address, primitives.uint256, primitives.uint256], - list[PositionProps] - ], - Name("getAccountPositions"), - ] - - get_adl_state: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.address, bool, MarketUtilsMarketPrices], - tuple[primitives.uint256, bool, primitives.int256, primitives.uint256] - ], - Name("getAdlState"), - ] - - get_deposit: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.bytes32], - DepositProps - ], - Name("getDeposit"), - ] - - get_deposit_amount_out: Annotated[ - ContractFunc[ - tuple[primitives.address, MarketProps, MarketUtilsMarketPrices, primitives.uint256, primitives.uint256, primitives.address, primitives.uint8, bool], - primitives.uint256 - ], - Name("getDepositAmountOut"), - ] - - get_execution_price: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.address, PriceProps, primitives.uint256, primitives.uint256, primitives.int256, bool], - ReaderPricingUtilsExecutionPriceResult - ], - Name("getExecutionPrice"), - ] - - get_market: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.address], - MarketProps - ], - Name("getMarket"), - ] - - get_market_by_salt: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.bytes32], - MarketProps - ], - Name("getMarketBySalt"), - ] - - get_market_info: Annotated[ - ContractFunc[ - tuple[primitives.address, MarketUtilsMarketPrices, primitives.address], - ReaderUtilsMarketInfo - ], - Name("getMarketInfo"), - ] - - get_market_info_list: Annotated[ - ContractFunc[ - tuple[primitives.address, list[MarketUtilsMarketPrices], primitives.uint256, primitives.uint256], - list[ReaderUtilsMarketInfo] - ], - Name("getMarketInfoList"), - ] - - get_market_token_price: Annotated[ - ContractFunc[ - tuple[primitives.address, MarketProps, PriceProps, PriceProps, PriceProps, primitives.bytes32, bool], - tuple[primitives.int256, MarketPoolValueInfoProps] - ], - Name("getMarketTokenPrice"), - ] - - get_markets: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.uint256, primitives.uint256], - list[MarketProps] - ], - Name("getMarkets"), - ] - - get_net_pnl: Annotated[ - ContractFunc[ - tuple[primitives.address, MarketProps, PriceProps, bool], - primitives.int256 - ], - Name("getNetPnl"), - ] - - get_open_interest_with_pnl: Annotated[ - ContractFunc[ - tuple[primitives.address, MarketProps, PriceProps, bool, bool], - primitives.int256 - ], - Name("getOpenInterestWithPnl"), - ] - - get_order: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.bytes32], - OrderProps - ], - Name("getOrder"), - ] - - get_pnl: Annotated[ - ContractFunc[ - tuple[primitives.address, MarketProps, PriceProps, bool, bool], - primitives.int256 - ], - Name("getPnl"), - ] - - get_pnl_to_pool_factor: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.address, MarketUtilsMarketPrices, bool, bool], - primitives.int256 - ], - Name("getPnlToPoolFactor"), - ] - - get_position: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.bytes32], - PositionProps - ], - Name("getPosition"), - ] - - get_position_info: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.address, primitives.bytes32, MarketUtilsMarketPrices, primitives.uint256, primitives.address, bool], - ReaderUtilsPositionInfo - ], - Name("getPositionInfo"), - ] - - get_position_pnl_usd: Annotated[ - ContractFunc[ - tuple[primitives.address, MarketProps, MarketUtilsMarketPrices, primitives.bytes32, primitives.uint256], - tuple[primitives.int256, primitives.int256, primitives.uint256] - ], - Name("getPositionPnlUsd"), - ] - - get_shift: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.bytes32], - ShiftProps - ], - Name("getShift"), - ] - - get_swap_amount_out: Annotated[ - ContractFunc[ - tuple[primitives.address, MarketProps, MarketUtilsMarketPrices, primitives.address, primitives.uint256, primitives.address], - tuple[primitives.uint256, primitives.int256, SwapPricingUtilsSwapFees] - ], - Name("getSwapAmountOut"), - ] - - get_swap_price_impact: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.address, primitives.address, primitives.address, primitives.uint256, PriceProps, PriceProps], - tuple[primitives.int256, primitives.int256, primitives.int256] - ], - Name("getSwapPriceImpact"), - ] - - get_withdrawal: Annotated[ - ContractFunc[ - tuple[primitives.address, primitives.bytes32], - WithdrawalProps - ], - Name("getWithdrawal"), - ] - - get_withdrawal_amount_out: Annotated[ - ContractFunc[ - tuple[primitives.address, MarketProps, MarketUtilsMarketPrices, primitives.uint256, primitives.address, primitives.uint8], - tuple[primitives.uint256, primitives.uint256] - ], - Name("getWithdrawalAmountOut"), - ] From 0290d72f37b7cf39f87057fa0237d92e576dff23 Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Thu, 14 Nov 2024 14:13:21 -0500 Subject: [PATCH 07/16] add prices oracle --- .../src/eth_protocols/gmx/keys.py | 3 +- .../src/eth_protocols/gmx/prices/oracle.py | 91 +++++++++++++++++++ .../src/eth_protocols/gmx/utils/__init__.py | 3 + .../src/eth_protocols/gmx/utils/hashing.py | 23 +++++ .../src/eth_protocols/gmx/utils/manager.py | 3 - 5 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/prices/oracle.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/utils/hashing.py diff --git a/packages/eth_protocols/src/eth_protocols/gmx/keys.py b/packages/eth_protocols/src/eth_protocols/gmx/keys.py index d5a4787..fc8314f 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/keys.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/keys.py @@ -1,4 +1,5 @@ -from .gmx_utils import create_hash_string, create_hash, get_datastore_contract +from .utils import create_hash_string, create_hash + ACCOUNT_POSITION_LIST = create_hash_string("ACCOUNT_POSITION_LIST") CLAIMABLE_FEE_AMOUNT = create_hash_string("CLAIMABLE_FEE_AMOUNT") diff --git a/packages/eth_protocols/src/eth_protocols/gmx/prices/oracle.py b/packages/eth_protocols/src/eth_protocols/gmx/prices/oracle.py new file mode 100644 index 0000000..9c28b70 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/prices/oracle.py @@ -0,0 +1,91 @@ +from typing import Literal + +import httpx +from eth_rpc.networks import get_network_by_name +from eth_typing import HexAddress +from pydantic import BaseModel, ConfigDict, Field, PrivateAttr +from pydantic.alias_generators import to_camel + + +class OraclePrice(BaseModel): + model_config = ConfigDict( + alias_generator=to_camel + ) + + id: int + min_block_number: int | None + min_block_hash: str | None + oracle_decimals: int | None + token_symbol: str + token_address: HexAddress + min_price: str | None + max_price: str | None + signer: str | None + signature: str | None = None + signature_without_block_hash: str | None + created_at: str + min_block_timestamp: int + oracle_keeper_key: str + max_block_timestamp: int + max_block_number: int | None + max_block_hash: str | None + max_price_full: str + min_price_full: str + oracle_keeper_record_id: str | None + oracle_keeper_fetch_type: str + oracle_type: str + blob: str + + +class OraclePriceResponse(BaseModel): + signed_prices: list[OraclePrice] = Field(alias="signedPrices") + + +class PriceOracle(BaseModel): + network: Literal["arbitrum", "avalanche"] = "arbitrum" + _oracle_url: str = PrivateAttr() + + @property + def network_type(self): + return get_network_by_name(self.network) + + def model_post_init(self, __context): + self._oracle_url = ( + "https://arbitrum-api.gmxinfra.io/signed_prices/latest" + if self.network == "arbitrum" + else "https://avalanche-api.gmxinfra.io/signed_prices/latest" + ) + + async def get_recent_prices(self): + """ + Get raw output of the GMX rest v2 api for signed prices + + Returns + ------- + dict + dictionary containing raw output for each token as its keys. + + """ + raw_output = await self._make_query() + return self._process_output(raw_output) + + async def _make_query(self) -> OraclePriceResponse: + """ + Make request using oracle url + + Returns + ------- + requests.models.Response + raw request response. + + """ + async with httpx.AsyncClient() as client: + response = await client.get(self._oracle_url) + return OraclePriceResponse(**response.json()) + + def _process_output(self, output: OraclePriceResponse) -> dict[HexAddress, OraclePrice]: + processed: dict[str, OraclePriceResponse] = {} + for i in output.signed_prices: + processed[i.token_address] = i + + return processed diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py index e69de29..e725924 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py @@ -0,0 +1,3 @@ +from .hashing import create_hash_string, create_hash + +__all__ = ["create_hash_string", "create_hash"] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/hashing.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/hashing.py new file mode 100644 index 0000000..eb567a0 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/hashing.py @@ -0,0 +1,23 @@ +from eth_abi import encode +from eth_hash.auto import keccak + + +def create_hash(types: list[str], values: list[str]): + return keccak(encode(types, values)) + + +def create_hash_string(string: str): + """ + Value to hash + + Parameters + ---------- + string : str + string to hash. + + Returns + ------- + bytes + hashed string. + """ + return keccak(encode(["string"], [string])) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py index 215db2a..37a03ba 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py @@ -62,9 +62,6 @@ def find_dictionary_by_key_value(outer_dict: dict, key: str, value: str): return None -PRECISION = 30 - - def apply_factor(value, factor): return value * factor / 10**30 From 36f59c41093d94f4cc156b8caf169e9557629f5f Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Thu, 14 Nov 2024 14:55:58 -0500 Subject: [PATCH 08/16] refactor synthetics reader --- .../eth_protocols/gmx/extensions/__init__.py | 5 + .../gmx/{prices => extensions}/oracle.py | 39 +---- .../src/eth_protocols/gmx/loaders/__init__.py | 0 .../src/eth_protocols/gmx/loaders/markets.py | 54 +++++++ .../eth_protocols/gmx/synthetics_reader.py | 102 ++++++++++-- .../src/eth_protocols/gmx/types/__init__.py | 6 + .../src/eth_protocols/gmx/types/oracle.py | 37 +++++ .../src/eth_protocols/gmx/utils/__init__.py | 8 +- .../gmx/utils/{manager.py => tokens.py} | 41 +++-- .../src/eth_typeshed/gmx/__init__.py | 10 +- .../src/eth_typeshed/gmx/contracts.py | 146 +++++++----------- .../gmx/synthetics_reader/__init__.py | 2 + 12 files changed, 283 insertions(+), 167 deletions(-) create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/extensions/__init__.py rename packages/eth_protocols/src/eth_protocols/gmx/{prices => extensions}/oracle.py (60%) create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/loaders/__init__.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/loaders/markets.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/types/__init__.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/types/oracle.py rename packages/eth_protocols/src/eth_protocols/gmx/utils/{manager.py => tokens.py} (86%) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/extensions/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/extensions/__init__.py new file mode 100644 index 0000000..9f57c23 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/extensions/__init__.py @@ -0,0 +1,5 @@ +from .oracle import PriceOracle + +__all__ = [ + "PriceOracle", +] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/prices/oracle.py b/packages/eth_protocols/src/eth_protocols/gmx/extensions/oracle.py similarity index 60% rename from packages/eth_protocols/src/eth_protocols/gmx/prices/oracle.py rename to packages/eth_protocols/src/eth_protocols/gmx/extensions/oracle.py index 9c28b70..09484dd 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/prices/oracle.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/extensions/oracle.py @@ -3,42 +3,9 @@ import httpx from eth_rpc.networks import get_network_by_name from eth_typing import HexAddress -from pydantic import BaseModel, ConfigDict, Field, PrivateAttr -from pydantic.alias_generators import to_camel +from pydantic import BaseModel, PrivateAttr - -class OraclePrice(BaseModel): - model_config = ConfigDict( - alias_generator=to_camel - ) - - id: int - min_block_number: int | None - min_block_hash: str | None - oracle_decimals: int | None - token_symbol: str - token_address: HexAddress - min_price: str | None - max_price: str | None - signer: str | None - signature: str | None = None - signature_without_block_hash: str | None - created_at: str - min_block_timestamp: int - oracle_keeper_key: str - max_block_timestamp: int - max_block_number: int | None - max_block_hash: str | None - max_price_full: str - min_price_full: str - oracle_keeper_record_id: str | None - oracle_keeper_fetch_type: str - oracle_type: str - blob: str - - -class OraclePriceResponse(BaseModel): - signed_prices: list[OraclePrice] = Field(alias="signedPrices") +from ..types import OraclePrice, OraclePriceResponse class PriceOracle(BaseModel): @@ -56,7 +23,7 @@ def model_post_init(self, __context): else "https://avalanche-api.gmxinfra.io/signed_prices/latest" ) - async def get_recent_prices(self): + async def get_recent_prices(self) -> dict[HexAddress, OraclePrice]: """ Get raw output of the GMX rest v2 api for signed prices diff --git a/packages/eth_protocols/src/eth_protocols/gmx/loaders/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/loaders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/eth_protocols/src/eth_protocols/gmx/loaders/markets.py b/packages/eth_protocols/src/eth_protocols/gmx/loaders/markets.py new file mode 100644 index 0000000..28c00ed --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/loaders/markets.py @@ -0,0 +1,54 @@ +from typing import TYPE_CHECKING, Literal + +from eth_typing import HexAddress +from pydantic import BaseModel + +from ..extensions.oracle import PriceOracle + +if TYPE_CHECKING: + from ..synthetics_reader import MarketInfo + + +class Markets(BaseModel): + _info: dict[HexAddress, "MarketInfo"] | None = None + network: Literal["arbitrum", "avalanche"] = "arbitrum" + + @property + def info(self): + return self._info + + async def load(self): + from ..synthetics_reader import SyntheticsReader + + self._info = await SyntheticsReader(network=self.network).get_available_markets() + + def get_index_token_address(self, market_key: str) -> str: + return self.info[market_key].index_token_address + + def get_long_token_address(self, market_key: str) -> str: + return self.info[market_key].long_token_address + + def get_short_token_address(self, market_key: str) -> str: + return self.info[market_key].short_token_address + + def get_market_symbol(self, market_key: str) -> str: + return self.info[market_key].market_symbol + + def get_decimal_factor( + self, market_key: HexAddress, long: bool = False, short: bool = False + ) -> int: + if long: + return self.info[market_key].long_token_metadata.decimals + elif short: + return self.info[market_key].short_token_metadata.decimals + else: + return self.info[market_key].market_metadata.decimals + + def is_synthetic(self, market_key: str) -> bool: + return self.info[market_key].market_metadata['synthetic'] + + async def _check_if_index_token_in_signed_prices_api(self, index_token_address: HexAddress) -> bool: + if index_token_address == "0x0000000000000000000000000000000000000000": + return True + prices = await PriceOracle(network=self.network).get_recent_prices() + return index_token_address in prices diff --git a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py index c74fa36..a49a14b 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py @@ -1,9 +1,10 @@ -from typing import ClassVar, Literal +from typing import Literal +from eth_typing import HexAddress, HexStr from pydantic import BaseModel, PrivateAttr from eth_rpc.networks import Arbitrum, Avalanche -from eth_typeshed.gmx import SyntheticsReader as SyntheticsReaderContract, ExecutionPriceParams +from eth_typeshed.gmx import MarketProps, GMXEnvironment, SyntheticsReader as SyntheticsReaderContract, ExecutionPriceParams from eth_typeshed.gmx.synthetics_reader.schemas import ( DepositAmountOutParams, SwapAmountOutParams, @@ -12,6 +13,9 @@ WithdrawalAmountOutResponse, ) +from .extensions.oracle import PriceOracle +from .utils import TokenInfo, get_tokens_address_dict + PRECISION = 30 class ExecutionPriceResult(BaseModel): @@ -19,25 +23,35 @@ class ExecutionPriceResult(BaseModel): price_impact_usd: float -class SyntheticsReader(BaseModel): - """ - SyntheticsReader contract - """ - contract_addresses: ClassVar[dict[Literal["arbitrum", "avalanche"], str]] = { - "arbitrum": "0x5Ca84c34a381434786738735265b9f3FD814b824", - "avalanche": "0xBAD04dDcc5CC284A86493aFA75D2BEb970C72216" - } +class MarketInfo(BaseModel): + gmx_market_address: HexAddress + market_symbol: str + index_token_address: HexAddress + market_metadata: TokenInfo + long_token_metadata: TokenInfo + long_token_address: HexAddress + short_token_metadata: TokenInfo + short_token_address: HexAddress + +class SyntheticsReader(PriceOracle): network: Literal["arbitrum", "avalanche"] + _environment: GMXEnvironment = PrivateAttr() _contract: SyntheticsReaderContract = PrivateAttr() @property def network_type(self) -> type[Arbitrum] | type[Avalanche]: return Arbitrum if self.network == "arbitrum" else Avalanche + @property + def datastore(self) -> HexAddress: + return self._environment.datastore + def model_post_init(self, __context): - contract_address = self.contract_addresses[self.network] - self._contract = SyntheticsReaderContract[self.network_type](address=contract_address) + super().model_post_init(__context) + + self._environment = GMXEnvironment.get_environment(self.network) + self._contract = SyntheticsReaderContract[self.network_type](address=self._environment.synthetics_reader) async def get_execution_price(self, params: ExecutionPriceParams, decimals: int = 18) -> ExecutionPriceResult: result = await self._contract.get_execution_price(params).get() @@ -55,3 +69,67 @@ async def get_estimated_deposit_amount_out(self, params: DepositAmountOutParams) async def get_estimated_withdrawal_amount_out(self, params: WithdrawalAmountOutParams) -> WithdrawalAmountOutResponse: return await self._contract.get_withdrawal_amount_out(params).get() + + async def _get_raw_markets(self) -> list[MarketProps]: + return await self._contract.get_markets( + self.datastore, + 0, + 35, + ).get() + + async def get_available_markets(self) -> dict[HexAddress, MarketInfo]: + markets = await self._get_raw_markets() + token_address_dict = await get_tokens_address_dict(self.network) + decoded_markets = {} + + for market in markets: + try: + + if not self._check_if_index_token_in_signed_prices_api( + market.index_token + ): + continue + market_symbol = token_address_dict[market.index_token].symbol + + if market.long_token == market.short_token: + market_symbol = f"{market_symbol}2" + + decoded_markets[market.market_token] = MarketInfo( + gmx_market_address=market.market_token, + market_symbol=market_symbol, + index_token_address=market.index_token, + market_metadata=token_address_dict[market.index_token], + long_token_metadata=token_address_dict[market.long_token], + long_token_address=market.long_token, + short_token_metadata=token_address_dict[market.short_token], + short_token_address=market.short_token + ) + if market.market_token == "0x0Cf1fb4d1FF67A3D8Ca92c9d6643F8F9be8e03E5": + decoded_markets[market.market_token].market_symbol = "wstETH" + decoded_markets[market.market_token].index_token_address = HexAddress(HexStr("0x5979D7b546E38E414F7E9822514be443A4800529")) + + # If KeyError it is because there is no market symbol and it is a swap market + except KeyError: + if not self._check_if_index_token_in_signed_prices_api( + market.index_token + ): + continue + + decoded_markets[market.market_token] = MarketInfo( + gmx_market_address=market.market_token, + market_symbol=f'SWAP {token_address_dict[market.long_token].symbol}-{token_address_dict[market.short_token].symbol}', + index_token_address=market.index_token, + market_metadata={'symbol': f'SWAP {token_address_dict[market.long_token].symbol}-{token_address_dict[market.short_token].symbol}'}, + long_token_metadata=token_address_dict[market.long_token], + long_token_address=market.long_token, + short_token_metadata=token_address_dict[market.short_token], + short_token_address=market.short_token + ) + + return decoded_markets + + async def _check_if_index_token_in_signed_prices_api(self, index_token_address: HexAddress) -> bool: + if index_token_address == HexAddress(HexStr("0x0000000000000000000000000000000000000000")): + return True + prices = await self.get_recent_prices() + return index_token_address in prices diff --git a/packages/eth_protocols/src/eth_protocols/gmx/types/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/types/__init__.py new file mode 100644 index 0000000..5684753 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/types/__init__.py @@ -0,0 +1,6 @@ +from .oracle import OraclePrice, OraclePriceResponse + +__all__ = [ + "OraclePrice", + "OraclePriceResponse", +] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/types/oracle.py b/packages/eth_protocols/src/eth_protocols/gmx/types/oracle.py new file mode 100644 index 0000000..c6424ab --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/types/oracle.py @@ -0,0 +1,37 @@ +from eth_typing import HexAddress +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel + + +class OraclePrice(BaseModel): + model_config = ConfigDict( + alias_generator=to_camel + ) + + id: int + min_block_number: int | None + min_block_hash: str | None + oracle_decimals: int | None + token_symbol: str + token_address: HexAddress + min_price: str | None + max_price: str | None + signer: str | None + signature: str | None = None + signature_without_block_hash: str | None + created_at: str + min_block_timestamp: int + oracle_keeper_key: str + max_block_timestamp: int + max_block_number: int | None + max_block_hash: str | None + max_price_full: str + min_price_full: str + oracle_keeper_record_id: str | None + oracle_keeper_fetch_type: str + oracle_type: str + blob: str + + +class OraclePriceResponse(BaseModel): + signed_prices: list[OraclePrice] = Field(alias="signedPrices") diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py index e725924..59b69c5 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py @@ -1,3 +1,9 @@ from .hashing import create_hash_string, create_hash +from .tokens import TokenInfo, get_tokens_address_dict -__all__ = ["create_hash_string", "create_hash"] +__all__ = [ + "TokenInfo", + "create_hash_string", + "create_hash", + "get_tokens_address_dict", +] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/tokens.py similarity index 86% rename from packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py rename to packages/eth_protocols/src/eth_protocols/gmx/utils/tokens.py index 37a03ba..1bd2a26 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/utils/manager.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/tokens.py @@ -1,44 +1,40 @@ from typing import Literal import httpx +from eth_typing import HexAddress +from pydantic import BaseModel +from eth_typeshed.gmx.contracts import GMXEnvironment -async def get_tokens_address_dict(chain: Literal["arbitrum", "avalanche"] = "arbitrum"): - """ - Query the GMX infra api for to generate dictionary of tokens available on v2 - Parameters - ---------- - chain : avalanche | arbitrum +class TokenInfo(BaseModel): + symbol: str + address: HexAddress + decimals: int - Returns - ------- - token_address_dict : dict - dictionary containing available tokens to trade on GMX. - """ - url = { - "arbitrum": "https://arbitrum-api.gmxinfra.io/tokens", - "avalanche": "https://avalanche-api.gmxinfra.io/tokens" - } +class TokensInfo(BaseModel): + tokens: list[TokenInfo] + + +async def get_tokens_address_dict(chain: Literal["arbitrum", "avalanche"] = "arbitrum") -> dict[HexAddress, TokenInfo]: + environment = GMXEnvironment.get_environment(chain) try: async with httpx.AsyncClient() as client: - response = await client.get(url[chain]) + response = await client.get(environment.tokens_url) # Check if the request was successful (status code 200) if response.status_code == 200: - token_infos = response.json()['tokens'] + token_infos = TokensInfo(**response.json()) else: print(f"Error: {response.status_code}") except httpx.RequestError as e: print(f"Error: {e}") - token_address_dict = {} - - for token_info in token_infos: - token_address_dict[token_info['address']] = token_info - + token_address_dict: dict[HexAddress, TokenInfo] = {} + for token_info in token_infos.tokens: + token_address_dict[token_info.address] = token_info return token_address_dict @@ -85,7 +81,6 @@ def get_funding_factor_per_period( expanded decimal long interest. short_interest_usd : int expanded decimal short interest. - """ funding_factor_per_second = ( diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py index fc0fc6e..715d269 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py @@ -1,10 +1,14 @@ -from .contracts import CONTRACTS -from .synthetics_reader import SyntheticsReader, DepositAmountOutParams, ExecutionPriceParams, ReaderPricingUtilsExecutionPriceResult +from .contracts import GMXEnvironment, GMXArbitrum, GMXAvalanche +from .synthetics_reader import SyntheticsReader, DepositAmountOutParams, ExecutionPriceParams, ReaderPricingUtilsExecutionPriceResult, MarketProps + __all__ = [ - "CONTRACTS", + "GMXEnvironment", + "GMXArbitrum", + "GMXAvalanche", "DepositAmountOutParams", "ExecutionPriceParams", + "MarketProps", "ReaderPricingUtilsExecutionPriceResult", "SyntheticsReader", ] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py b/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py index ad6f499..4fafec4 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py @@ -1,95 +1,57 @@ -from pydantic import BaseModel - -from eth_rpc import ProtocolBase -from eth_rpc.networks import get_network_by_name - -from .datastore import Datastore -from .event_emitter import EventEmitter -from .exchange_router import ExchangeRouter -from .deposit_vault import DepositVault -from .withdrawal_vault import WithdrawalVault -from .order_vault import OrderVault -from .synthetics_reader.synthetics_reader import SyntheticsReader -from .synthetics_router import SyntheticsRouter -from .glv_reader import GLVReader - +from typing import Literal -class GMXContract(BaseModel): - contract: type[ProtocolBase] - networks: dict[str, str | None] - - def get_instance(self, network: str) -> "ProtocolBase": - network_type = get_network_by_name(network) - if network not in self.networks: - raise ValueError(f"Contract not found for network: {network}") - - contract_address = self.networks[network] - contract = self.contract[network_type](address=contract_address) - return contract +from eth_typing import HexAddress +from pydantic import BaseModel -CONTRACTS = { - "datastore": GMXContract( - networks={ - "arbitrum": "0xFD70de6b91282D8017aA4E741e9Ae325CAb992d8", - "avalanche": "0x2F0b22339414ADeD7D5F06f9D604c7fF5b2fe3f6" - }, - contract=Datastore - ), - "eventemitter": GMXContract( - networks={ - "arbitrum": "0xC8ee91A54287DB53897056e12D9819156D3822Fb", - "avalanche": "0xDb17B211c34240B014ab6d61d4A31FA0C0e20c26" - }, - contract=EventEmitter - ), - "exchangerouter": GMXContract( - networks={ - "arbitrum": "0x69C527fC77291722b52649E45c838e41be8Bf5d5", - "avalanche": "0x3BE24AED1a4CcaDebF2956e02C27a00726D4327d" - }, - contract=ExchangeRouter - ), - "depositvault": GMXContract( - networks={ - "arbitrum": "0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55", - "avalanche": "0x90c670825d0C62ede1c5ee9571d6d9a17A722DFF" - }, - contract=DepositVault - ), - "withdrawalvault": GMXContract( - networks={ - "arbitrum": "0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55", - "avalanche": "0xf5F30B10141E1F63FC11eD772931A8294a591996" - }, - contract=WithdrawalVault - ), - "ordervault": GMXContract( - networks={ - "arbitrum": "0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5", - "avalanche": "0xD3D60D22d415aD43b7e64b510D86A30f19B1B12C" - }, - contract=OrderVault - ), - "syntheticsreader": GMXContract( - networks={ - "arbitrum": "0x5Ca84c34a381434786738735265b9f3FD814b824", - "avalanche": "0xBAD04dDcc5CC284A86493aFA75D2BEb970C72216" - }, - contract=SyntheticsReader - ), - "syntheticsrouter": GMXContract( - networks={ - "arbitrum": "0x7452c558d45f8afC8c83dAe62C3f8A5BE19c71f6", - "avalanche": "0x820F5FfC5b525cD4d88Cd91aCf2c28F16530Cc68" - }, - contract=SyntheticsRouter - ), - "glvreader": GMXContract( - networks={ - "arbitrum": "0xd4f522c4339Ae0A90a156bd716715547e44Bed65", - "avalanche": None - }, - contract=GLVReader - ) -} +class GMXEnvironment(BaseModel): + chain: Literal["arbitrum", "avalanche"] + datastore: HexAddress + event_emitter: HexAddress + exchange_router: HexAddress + deposit_vault: HexAddress + withdrawal_vault: HexAddress + order_vault: HexAddress + synthetics_reader: HexAddress + synthetics_router: HexAddress + glv_reader: HexAddress + + tokens_url: str + + @classmethod + def get_environment(cls, network: Literal["arbitrum", "avalanche"]) -> "GMXEnvironment": + if network == "arbitrum": + return GMXArbitrum + elif network == "avalanche": + return GMXAvalanche + else: + raise ValueError(f"Invalid network: {network}") + + +GMXArbitrum = GMXEnvironment( + chain="arbitrum", + datastore="0xFD70de6b91282D8017aA4E741e9Ae325CAb992d8", + event_emitter="0xC8ee91A54287DB53897056e12D9819156D3822Fb", + exchange_router="0x69C527fC77291722b52649E45c838e41be8Bf5d5", + deposit_vault="0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55", + withdrawal_vault="0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55", + order_vault="0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5", + synthetics_reader="0x5Ca84c34a381434786738735265b9f3FD814b824", + synthetics_router="0x7452c558d45f8afC8c83dAe62C3f8A5BE19c71f6", + glv_reader="0xd4f522c4339Ae0A90a156bd716715547e44Bed65", + tokens_url="https://arbitrum-api.gmxinfra.io/tokens", +) + +GMXAvalanche = GMXEnvironment( + chain="avalanche", + datastore="0x2F0b22339414ADeD7D5F06f9D604c7fF5b2fe3f6", + event_emitter="0xDb17B211c34240B014ab6d61d4A31FA0C0e20c26", + exchange_router="0x3BE24AED1a4CcaDebF2956e02C27a00726D4327d", + deposit_vault="0x90c670825d0C62ede1c5ee9571d6d9a17A722DFF", + withdrawal_vault="0xf5F30B10141E1F63FC11eD772931A8294a591996", + order_vault="0xD3D60D22d415aD43b7e64b510D86A30f19B1B12C", + synthetics_reader="0xBAD04dDcc5CC284A86493aFA75D2BEb970C72216", + synthetics_router="0x820F5FfC5b525cD4d88Cd91aCf2c28F16530Cc68", + glv_reader=None, + tokens_url="https://avalanche-api.gmxinfra.io/tokens", +) diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py index 73ce7aa..bdcc4d1 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py @@ -1,6 +1,7 @@ from .schemas import DepositAmountOutParams, ExecutionPriceParams from .synthetics_reader import SyntheticsReader from .types import ( + MarketProps, OrderProps, ReaderUtilsPositionInfo, ReaderPricingUtilsExecutionPriceResult, @@ -10,6 +11,7 @@ __all__ = [ "DepositAmountOutParams", "ExecutionPriceParams", + "MarketProps", "SyntheticsReader", "OrderProps", "ReaderUtilsPositionInfo", From fe1f1922a3f09d59e80c6111fd6e9b42181fd9bd Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Thu, 14 Nov 2024 16:08:30 -0500 Subject: [PATCH 09/16] cleanup type hints --- .../eth_protocols/gmx/extensions/oracle.py | 24 ++--- .../src/eth_protocols/gmx/keys.py | 2 +- .../eth_protocols/gmx/synthetics_reader.py | 89 +++++++++---------- .../src/eth_protocols/gmx/types/__init__.py | 6 ++ .../src/eth_protocols/gmx/types/base.py | 9 ++ .../src/eth_protocols/gmx/types/oracle.py | 10 +++ .../src/eth_protocols/gmx/types/reader.py | 20 +++++ .../src/eth_protocols/gmx/types/tokens.py | 13 +++ .../src/eth_protocols/gmx/utils/hashing.py | 2 +- .../src/eth_protocols/gmx/utils/tokens.py | 60 ++----------- packages/eth_protocols/tests/test_gmx.py | 10 +++ .../src/eth_typeshed/gmx/__init__.py | 2 +- .../gmx/{contracts.py => _environment.py} | 2 +- .../gmx/synthetics_reader/__init__.py | 3 +- .../gmx/synthetics_reader/schemas.py | 7 ++ .../synthetics_reader/synthetics_reader.py | 3 +- 16 files changed, 138 insertions(+), 124 deletions(-) create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/types/base.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/types/reader.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/types/tokens.py create mode 100644 packages/eth_protocols/tests/test_gmx.py rename packages/eth_typeshed/src/eth_typeshed/gmx/{contracts.py => _environment.py} (98%) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/extensions/oracle.py b/packages/eth_protocols/src/eth_protocols/gmx/extensions/oracle.py index 09484dd..d32e0c6 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/extensions/oracle.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/extensions/oracle.py @@ -2,7 +2,8 @@ import httpx from eth_rpc.networks import get_network_by_name -from eth_typing import HexAddress +from eth_rpc.utils import to_checksum +from eth_typing import ChecksumAddress from pydantic import BaseModel, PrivateAttr from ..types import OraclePrice, OraclePriceResponse @@ -23,15 +24,9 @@ def model_post_init(self, __context): else "https://avalanche-api.gmxinfra.io/signed_prices/latest" ) - async def get_recent_prices(self) -> dict[HexAddress, OraclePrice]: + async def get_recent_prices(self) -> dict[ChecksumAddress, OraclePrice]: """ Get raw output of the GMX rest v2 api for signed prices - - Returns - ------- - dict - dictionary containing raw output for each token as its keys. - """ raw_output = await self._make_query() return self._process_output(raw_output) @@ -39,20 +34,13 @@ async def get_recent_prices(self) -> dict[HexAddress, OraclePrice]: async def _make_query(self) -> OraclePriceResponse: """ Make request using oracle url - - Returns - ------- - requests.models.Response - raw request response. - """ async with httpx.AsyncClient() as client: response = await client.get(self._oracle_url) return OraclePriceResponse(**response.json()) - def _process_output(self, output: OraclePriceResponse) -> dict[HexAddress, OraclePrice]: - processed: dict[str, OraclePriceResponse] = {} + def _process_output(self, output: OraclePriceResponse) -> dict[ChecksumAddress, OraclePrice]: + processed: dict[ChecksumAddress, OraclePrice] = {} for i in output.signed_prices: - processed[i.token_address] = i - + processed[to_checksum(i.token_address)] = i return processed diff --git a/packages/eth_protocols/src/eth_protocols/gmx/keys.py b/packages/eth_protocols/src/eth_protocols/gmx/keys.py index fc8314f..d947c5c 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/keys.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/keys.py @@ -86,7 +86,7 @@ def max_open_interest_key(market: str, return create_hash( ["bytes32", "address", "bool"], - [MAX_OPEN_INTEREST, market, is_long] + [MAX_OPEN_INTEREST, market, is_long], ) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py index a49a14b..47398da 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py @@ -1,12 +1,15 @@ from typing import Literal -from eth_typing import HexAddress, HexStr -from pydantic import BaseModel, PrivateAttr +from eth_typing import ChecksumAddress, HexAddress, HexStr +from pydantic import Field, PrivateAttr from eth_rpc.networks import Arbitrum, Avalanche +from eth_rpc.types.primitives import address, uint256 +from eth_rpc.utils import to_checksum from eth_typeshed.gmx import MarketProps, GMXEnvironment, SyntheticsReader as SyntheticsReaderContract, ExecutionPriceParams from eth_typeshed.gmx.synthetics_reader.schemas import ( DepositAmountOutParams, + GetMarketsParams, SwapAmountOutParams, SwapAmountOutResponse, WithdrawalAmountOutParams, @@ -14,28 +17,14 @@ ) from .extensions.oracle import PriceOracle +from .types import ExecutionPriceResult, MarketInfo from .utils import TokenInfo, get_tokens_address_dict PRECISION = 30 -class ExecutionPriceResult(BaseModel): - execution_price: float - price_impact_usd: float - - -class MarketInfo(BaseModel): - gmx_market_address: HexAddress - market_symbol: str - index_token_address: HexAddress - market_metadata: TokenInfo - long_token_metadata: TokenInfo - long_token_address: HexAddress - short_token_metadata: TokenInfo - short_token_address: HexAddress - class SyntheticsReader(PriceOracle): - network: Literal["arbitrum", "avalanche"] + network: Literal["arbitrum", "avalanche"] = Field(default="arbitrum") _environment: GMXEnvironment = PrivateAttr() _contract: SyntheticsReaderContract = PrivateAttr() @@ -70,66 +59,74 @@ async def get_estimated_deposit_amount_out(self, params: DepositAmountOutParams) async def get_estimated_withdrawal_amount_out(self, params: WithdrawalAmountOutParams) -> WithdrawalAmountOutResponse: return await self._contract.get_withdrawal_amount_out(params).get() - async def _get_raw_markets(self) -> list[MarketProps]: - return await self._contract.get_markets( - self.datastore, - 0, - 35, - ).get() - - async def get_available_markets(self) -> dict[HexAddress, MarketInfo]: + async def get_available_markets(self) -> dict[ChecksumAddress, MarketInfo]: markets = await self._get_raw_markets() token_address_dict = await get_tokens_address_dict(self.network) - decoded_markets = {} + decoded_markets: dict[ChecksumAddress, MarketInfo] = {} for market in markets: try: - - if not self._check_if_index_token_in_signed_prices_api( + if not await self._check_if_index_token_in_signed_prices_api( market.index_token ): continue - market_symbol = token_address_dict[market.index_token].symbol + market_symbol = token_address_dict[to_checksum(market.index_token)].symbol if market.long_token == market.short_token: market_symbol = f"{market_symbol}2" - decoded_markets[market.market_token] = MarketInfo( + decoded_markets[to_checksum(market.market_token)] = MarketInfo( gmx_market_address=market.market_token, market_symbol=market_symbol, index_token_address=market.index_token, - market_metadata=token_address_dict[market.index_token], - long_token_metadata=token_address_dict[market.long_token], + market_metadata=token_address_dict[to_checksum(market.index_token)], + long_token_metadata=token_address_dict[to_checksum(market.long_token)], long_token_address=market.long_token, - short_token_metadata=token_address_dict[market.short_token], + short_token_metadata=token_address_dict[to_checksum(market.short_token)], short_token_address=market.short_token ) - if market.market_token == "0x0Cf1fb4d1FF67A3D8Ca92c9d6643F8F9be8e03E5": - decoded_markets[market.market_token].market_symbol = "wstETH" - decoded_markets[market.market_token].index_token_address = HexAddress(HexStr("0x5979D7b546E38E414F7E9822514be443A4800529")) + if market.market_token == HexAddress(HexStr("0x0Cf1fb4d1FF67A3D8Ca92c9d6643F8F9be8e03E5")): + decoded_markets[to_checksum(market.market_token)].market_symbol = "wstETH" + decoded_markets[to_checksum(market.market_token)].index_token_address = HexAddress(HexStr("0x5979D7b546E38E414F7E9822514be443A4800529")) # If KeyError it is because there is no market symbol and it is a swap market except KeyError: - if not self._check_if_index_token_in_signed_prices_api( + if not await self._check_if_index_token_in_signed_prices_api( market.index_token ): continue - decoded_markets[market.market_token] = MarketInfo( + long_symbol = token_address_dict[to_checksum(market.long_token)].symbol + short_symbol = token_address_dict[to_checksum(market.short_token)].symbol + decoded_markets[to_checksum(market.market_token)] = MarketInfo( gmx_market_address=market.market_token, - market_symbol=f'SWAP {token_address_dict[market.long_token].symbol}-{token_address_dict[market.short_token].symbol}', + market_symbol=f'SWAP {long_symbol}-{short_symbol}', index_token_address=market.index_token, - market_metadata={'symbol': f'SWAP {token_address_dict[market.long_token].symbol}-{token_address_dict[market.short_token].symbol}'}, - long_token_metadata=token_address_dict[market.long_token], + market_metadata=TokenInfo( + symbol=f'SWAP {long_symbol}-{short_symbol}', + address=market.market_token, + decimals=18, + ), + long_token_metadata=token_address_dict[to_checksum(market.long_token)], long_token_address=market.long_token, - short_token_metadata=token_address_dict[market.short_token], + short_token_metadata=token_address_dict[to_checksum(market.short_token)], short_token_address=market.short_token ) - return decoded_markets - async def _check_if_index_token_in_signed_prices_api(self, index_token_address: HexAddress) -> bool: - if index_token_address == HexAddress(HexStr("0x0000000000000000000000000000000000000000")): + # Private Methods + + async def _check_if_index_token_in_signed_prices_api(self, index_token_address: HexAddress | address) -> bool: + if str(index_token_address) == "0x0000000000000000000000000000000000000000": return True prices = await self.get_recent_prices() return index_token_address in prices + + async def _get_raw_markets(self) -> list[MarketProps]: + return await self._contract.get_markets( + GetMarketsParams( + data_store=self.datastore, + start_index=uint256(0), + end_index=uint256(35), + ) + ).get() diff --git a/packages/eth_protocols/src/eth_protocols/gmx/types/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/types/__init__.py index 5684753..759ce63 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/types/__init__.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/types/__init__.py @@ -1,6 +1,12 @@ from .oracle import OraclePrice, OraclePriceResponse +from .reader import ExecutionPriceResult, MarketInfo +from .tokens import TokenInfo, TokensInfo __all__ = [ "OraclePrice", "OraclePriceResponse", + "TokenInfo", + "TokensInfo", + "ExecutionPriceResult", + "MarketInfo", ] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/types/base.py b/packages/eth_protocols/src/eth_protocols/gmx/types/base.py new file mode 100644 index 0000000..e4d4b41 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/types/base.py @@ -0,0 +1,9 @@ +from typing import Annotated + +from eth_typing import HexAddress +from pydantic import BeforeValidator + +from eth_rpc.utils import to_checksum + + +ChecksumAddress = Annotated[HexAddress, BeforeValidator(lambda x, info: to_checksum(x))] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/types/oracle.py b/packages/eth_protocols/src/eth_protocols/gmx/types/oracle.py index c6424ab..1a28547 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/types/oracle.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/types/oracle.py @@ -32,6 +32,16 @@ class OraclePrice(BaseModel): oracle_type: str blob: str + def __repr__(self) -> str: + return f"" + + __str__ = __repr__ + class OraclePriceResponse(BaseModel): signed_prices: list[OraclePrice] = Field(alias="signedPrices") + + def __repr__(self) -> str: + return f"" + + __str__ = __repr__ diff --git a/packages/eth_protocols/src/eth_protocols/gmx/types/reader.py b/packages/eth_protocols/src/eth_protocols/gmx/types/reader.py new file mode 100644 index 0000000..36e8e73 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/types/reader.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel + +from .base import ChecksumAddress +from .tokens import TokenInfo + + +class ExecutionPriceResult(BaseModel): + execution_price: float + price_impact_usd: float + + +class MarketInfo(BaseModel): + gmx_market_address: ChecksumAddress + market_symbol: str + index_token_address: ChecksumAddress + market_metadata: TokenInfo + long_token_metadata: TokenInfo + long_token_address: ChecksumAddress + short_token_metadata: TokenInfo + short_token_address: ChecksumAddress diff --git a/packages/eth_protocols/src/eth_protocols/gmx/types/tokens.py b/packages/eth_protocols/src/eth_protocols/gmx/types/tokens.py new file mode 100644 index 0000000..cd8b65d --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/types/tokens.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel + +from .base import ChecksumAddress + + +class TokenInfo(BaseModel): + symbol: str + address: ChecksumAddress + decimals: int + + +class TokensInfo(BaseModel): + tokens: list[TokenInfo] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/hashing.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/hashing.py index eb567a0..4dfb433 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/utils/hashing.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/hashing.py @@ -2,7 +2,7 @@ from eth_hash.auto import keccak -def create_hash(types: list[str], values: list[str]): +def create_hash(types: list[str], values: list[str | bytes | bool]): return keccak(encode(types, values)) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/tokens.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/tokens.py index 1bd2a26..a493d43 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/utils/tokens.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/tokens.py @@ -1,23 +1,14 @@ from typing import Literal import httpx -from eth_typing import HexAddress -from pydantic import BaseModel +from eth_typing import ChecksumAddress +from eth_rpc.utils import to_checksum from eth_typeshed.gmx.contracts import GMXEnvironment +from ..types import TokensInfo, TokenInfo -class TokenInfo(BaseModel): - symbol: str - address: HexAddress - decimals: int - - -class TokensInfo(BaseModel): - tokens: list[TokenInfo] - - -async def get_tokens_address_dict(chain: Literal["arbitrum", "avalanche"] = "arbitrum") -> dict[HexAddress, TokenInfo]: +async def get_tokens_address_dict(chain: Literal["arbitrum", "avalanche"] = "arbitrum") -> dict[ChecksumAddress, TokenInfo]: environment = GMXEnvironment.get_environment(chain) try: @@ -32,9 +23,9 @@ async def get_tokens_address_dict(chain: Literal["arbitrum", "avalanche"] = "arb except httpx.RequestError as e: print(f"Error: {e}") - token_address_dict: dict[HexAddress, TokenInfo] = {} + token_address_dict: dict[ChecksumAddress, TokenInfo] = {} for token_info in token_infos.tokens: - token_address_dict[token_info.address] = token_info + token_address_dict[to_checksum(token_info.address)] = token_info return token_address_dict @@ -66,23 +57,6 @@ def get_funding_factor_per_period( market_info: dict, is_long: bool, period_in_seconds: int, long_interest_usd: int, short_interest_usd: int ): - """ - For a given market, calculate the funding factor for a given period - - Parameters - ---------- - market_info : dict - market parameters returned from the reader contract. - is_long : bool - direction of the position. - period_in_seconds : int - Want percentage rate we want to output to be in. - long_interest_usd : int - expanded decimal long interest. - short_interest_usd : int - expanded decimal short interest. - """ - funding_factor_per_second = ( market_info['funding_factor_per_second'] * 10**-28 ) @@ -117,28 +91,6 @@ def get_funding_factor_per_period( def determine_swap_route(markets: dict, in_token: str, out_token: str): - """ - Using the available markets, find the list of GMX markets required - to swap from token in to token out - - Parameters - ---------- - markets : dict - dictionary of markets output by getMarketInfo. - in_token : str - contract address of in token. - out_token : str - contract address of out token. - - Returns - ------- - list - list of GMX markets to swap through. - is_requires_multi_swap : TYPE - requires more than one market to pass thru. - - """ - if in_token == "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f": in_token = "0x47904963fc8b2340414262125aF798B9655E58Cd" diff --git a/packages/eth_protocols/tests/test_gmx.py b/packages/eth_protocols/tests/test_gmx.py new file mode 100644 index 0000000..f7dfbb7 --- /dev/null +++ b/packages/eth_protocols/tests/test_gmx.py @@ -0,0 +1,10 @@ +import pytest + +from eth_protocols.gmx.synthetics_reader import SyntheticsReader + + +@pytest.mark.asyncio(scope="session") +async def test_synthetics_reader() -> None: + reader = SyntheticsReader(network="arbitrum") + markets = await reader.get_available_markets() + print(markets) diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py index 715d269..a9521e1 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py @@ -1,4 +1,4 @@ -from .contracts import GMXEnvironment, GMXArbitrum, GMXAvalanche +from ._environment import GMXEnvironment, GMXArbitrum, GMXAvalanche from .synthetics_reader import SyntheticsReader, DepositAmountOutParams, ExecutionPriceParams, ReaderPricingUtilsExecutionPriceResult, MarketProps diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py b/packages/eth_typeshed/src/eth_typeshed/gmx/_environment.py similarity index 98% rename from packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py rename to packages/eth_typeshed/src/eth_typeshed/gmx/_environment.py index 4fafec4..1fcb6ae 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/contracts.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/_environment.py @@ -14,7 +14,7 @@ class GMXEnvironment(BaseModel): order_vault: HexAddress synthetics_reader: HexAddress synthetics_router: HexAddress - glv_reader: HexAddress + glv_reader: HexAddress | None = None tokens_url: str diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py index bdcc4d1..32a0193 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py @@ -1,4 +1,4 @@ -from .schemas import DepositAmountOutParams, ExecutionPriceParams +from .schemas import DepositAmountOutParams, ExecutionPriceParams, GetMarketsParams from .synthetics_reader import SyntheticsReader from .types import ( MarketProps, @@ -11,6 +11,7 @@ __all__ = [ "DepositAmountOutParams", "ExecutionPriceParams", + "GetMarketsParams", "MarketProps", "SyntheticsReader", "OrderProps", diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py index 08f4e72..31618d3 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py @@ -3,6 +3,7 @@ from pydantic import BaseModel from eth_rpc.types import primitives, Name +from eth_typing import HexAddress from .enums import SwapPricingType from .types import MarketProps, MarketUtilsMarketPrices, PriceProps, SwapPricingUtilsSwapFees @@ -55,3 +56,9 @@ class WithdrawalAmountOutParams(BaseModel): class WithdrawalAmountOutResponse(BaseModel): long_amount_after_fees: primitives.uint256 short_amount_after_fees: primitives.uint256 + + +class GetMarketsParams(BaseModel): + data_store: HexAddress + start_index: primitives.uint256 + end_index: primitives.uint256 diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py index 4cdbb20..2982ea9 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py @@ -6,6 +6,7 @@ from .schemas import ( DepositAmountOutParams, ExecutionPriceParams, + GetMarketsParams, SwapAmountOutParams, SwapAmountOutResponse, WithdrawalAmountOutParams, @@ -126,7 +127,7 @@ class SyntheticsReader(ProtocolBase): get_markets: Annotated[ ContractFunc[ - tuple[primitives.address, primitives.uint256, primitives.uint256], + GetMarketsParams, list[MarketProps] ], Name("getMarkets"), From 60d428087f0a0fe76733ad3cfc2d0f86ec33e1ac Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Fri, 15 Nov 2024 10:49:19 -0500 Subject: [PATCH 10/16] add get markets --- .../src/eth_protocols/gmx/loaders/data.py | 132 ++++++++++++++++++ .../src/eth_protocols/gmx/loaders/markets.py | 23 ++- .../eth_protocols/gmx/synthetics_reader.py | 14 +- .../src/eth_typeshed/gmx/__init__.py | 3 +- .../gmx/synthetics_reader/__init__.py | 6 +- .../gmx/synthetics_reader/schemas.py | 24 +++- .../synthetics_reader/synthetics_reader.py | 9 +- 7 files changed, 189 insertions(+), 22 deletions(-) create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/loaders/data.py diff --git a/packages/eth_protocols/src/eth_protocols/gmx/loaders/data.py b/packages/eth_protocols/src/eth_protocols/gmx/loaders/data.py new file mode 100644 index 0000000..9e76b0d --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/loaders/data.py @@ -0,0 +1,132 @@ +from typing import Any, Literal + +from eth_typing import HexAddress, ChecksumAddress +from pydantic import BaseModel, Field, PrivateAttr + +from eth_rpc.utils import to_checksum +from eth_typeshed.gmx.synthetics_reader.types import ReaderUtilsMarketInfo, MarketUtilsMarketPrices +from eth_typeshed.gmx.synthetics_reader.schemas import ( + GetMarketParams, + GetOpenInterestParams, + GetPnlParams, + MarketProps, + PriceProps, +) +from .markets import MarketsLoader +from ..synthetics_reader import SyntheticsReader + + +class DataLoader(BaseModel): + network: Literal["arbitrum", "avalanche"] = Field(default="arbitrum") + filter_swap_markets: bool = Field(default=True) + + output: dict[str, dict[str, dict[str, Any]]] = { + "long": {}, + "short": {} + } + _markets: MarketsLoader = PrivateAttr() + _synthetics_reader: SyntheticsReader = PrivateAttr() + _long_token_address: None | ChecksumAddress = PrivateAttr(default=None) + _short_token_address: None | ChecksumAddress = PrivateAttr(default=None) + + def model_post_init(self, __context): + self._synthetics_reader = SyntheticsReader(network=self.network) + self._markets = MarketsLoader(network=self.network) + + async def get_data(self): + if self.filter_swap_markets: + await self._filter_swap_markets() + data = await self._get_data_processing() + return data + + async def _get_data_processing(self): + pass + + async def _get_token_addresses(self, market_key: str): + self._long_token_address = self._markets.get_long_token_address(market_key) + self._short_token_address = self._markets.get_short_token_address(market_key) + + async def _filter_swap_markets(self): + # TODO: Move to markets MAYBE + for market_key in self.markets.info: + market_symbol = self.markets.get_market_symbol(market_key) + if 'SWAP' in market_symbol: + # Remove swap markets from dict + del self.markets.info[market_key] + + async def _get_pnl( + self, market: MarketProps, prices_list: PriceProps, is_long: bool, maximize: bool = False, + ) -> tuple[int, int]: + open_interest_pnl = await self._synthetics_reader.get_open_interest_with_pnl( + GetOpenInterestParams( + data_store=self._synthetics_reader.datastore, + market=market, + index_token_price=prices_list, + is_long=is_long, + maximize=maximize + ) + ) + + pnl = await self._synthetics_reader.get_pnl( + GetPnlParams( + data_store=self._synthetics_reader.datastore, + market=market, + index_token_price=prices_list, + is_long=is_long, + maximize=maximize + ) + ) + + return open_interest_pnl, pnl + + async def _get_oracle_prices( + self, + market_key: str, + index_token_address: HexAddress, + ) -> ReaderUtilsMarketInfo: + oracle_prices_dict = await self._synthetics_reader.get_recent_prices() + index_token: ChecksumAddress = to_checksum(index_token_address) + + assert self._long_token_address is not None + assert self._short_token_address is not None + + try: + prices = MarketUtilsMarketPrices( + index_token_price=PriceProps( + min=oracle_prices_dict[index_token].min_price_full, + max=oracle_prices_dict[index_token].max_price_full + ), + long_token_price=PriceProps( + min=oracle_prices_dict[self._long_token_address].min_price_full, + max=oracle_prices_dict[self._long_token_address].max_price_full + ), + short_token_price=PriceProps( + min=oracle_prices_dict[self._short_token_address].min_price_full, + max=oracle_prices_dict[self._short_token_address].max_price_full + ) + ) + + # TODO - this needs to be here until GMX add stables to signed price API + except KeyError: + prices = MarketUtilsMarketPrices( + index_token_price=PriceProps( + min=oracle_prices_dict[index_token].min_price_full, + max=oracle_prices_dict[index_token].max_price_full + ), + long_token_price=PriceProps( + min=oracle_prices_dict[self._long_token_address].min_price_full, + max=oracle_prices_dict[self._long_token_address].max_price_full + ), + short_token_price=PriceProps( + min=int(1000000000000000000000000), + max=int(1000000000000000000000000), + ), + ) + + return await self._synthetics_reader.get_market_info( + GetMarketParams( + data_store=self._synthetics_reader.datastore, + prices=prices, + market_key=market_key, + ), + ) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/loaders/markets.py b/packages/eth_protocols/src/eth_protocols/gmx/loaders/markets.py index 28c00ed..ae7ef3d 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/loaders/markets.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/loaders/markets.py @@ -1,18 +1,17 @@ from typing import TYPE_CHECKING, Literal -from eth_typing import HexAddress -from pydantic import BaseModel - -from ..extensions.oracle import PriceOracle +from eth_typing import HexAddress, ChecksumAddress +from pydantic import BaseModel, PrivateAttr if TYPE_CHECKING: from ..synthetics_reader import MarketInfo -class Markets(BaseModel): - _info: dict[HexAddress, "MarketInfo"] | None = None +class MarketsLoader(BaseModel): network: Literal["arbitrum", "avalanche"] = "arbitrum" + _info: dict[HexAddress, "MarketInfo"] | None = PrivateAttr(default=None) + @property def info(self): return self._info @@ -22,13 +21,13 @@ async def load(self): self._info = await SyntheticsReader(network=self.network).get_available_markets() - def get_index_token_address(self, market_key: str) -> str: + def get_index_token_address(self, market_key: str) -> ChecksumAddress: return self.info[market_key].index_token_address - def get_long_token_address(self, market_key: str) -> str: + def get_long_token_address(self, market_key: str) -> ChecksumAddress: return self.info[market_key].long_token_address - def get_short_token_address(self, market_key: str) -> str: + def get_short_token_address(self, market_key: str) -> ChecksumAddress: return self.info[market_key].short_token_address def get_market_symbol(self, market_key: str) -> str: @@ -46,9 +45,3 @@ def get_decimal_factor( def is_synthetic(self, market_key: str) -> bool: return self.info[market_key].market_metadata['synthetic'] - - async def _check_if_index_token_in_signed_prices_api(self, index_token_address: HexAddress) -> bool: - if index_token_address == "0x0000000000000000000000000000000000000000": - return True - prices = await PriceOracle(network=self.network).get_recent_prices() - return index_token_address in prices diff --git a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py index 47398da..13c23a2 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py @@ -6,10 +6,13 @@ from eth_rpc.networks import Arbitrum, Avalanche from eth_rpc.types.primitives import address, uint256 from eth_rpc.utils import to_checksum -from eth_typeshed.gmx import MarketProps, GMXEnvironment, SyntheticsReader as SyntheticsReaderContract, ExecutionPriceParams +from eth_typeshed.gmx import MarketProps, GMXEnvironment, SyntheticsReader as SyntheticsReaderContract, ExecutionPriceParams, ReaderUtilsMarketInfo from eth_typeshed.gmx.synthetics_reader.schemas import ( DepositAmountOutParams, GetMarketsParams, + GetMarketParams, + GetOpenInterestParams, + GetPnlParams, SwapAmountOutParams, SwapAmountOutResponse, WithdrawalAmountOutParams, @@ -114,6 +117,15 @@ async def get_available_markets(self) -> dict[ChecksumAddress, MarketInfo]: ) return decoded_markets + async def get_open_interest_with_pnl(self, params: GetOpenInterestParams) -> int: + return await self._contract.get_open_interest_with_pnl(params).get() + + async def get_pnl(self, params: GetPnlParams) -> int: + return await self._contract.get_pnl(params).get() + + async def get_market_info(self, params: GetMarketParams) -> ReaderUtilsMarketInfo: + return await self._contract.get_market_info(params).get() + # Private Methods async def _check_if_index_token_in_signed_prices_api(self, index_token_address: HexAddress | address) -> bool: diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py index a9521e1..cfdce47 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py @@ -1,5 +1,5 @@ from ._environment import GMXEnvironment, GMXArbitrum, GMXAvalanche -from .synthetics_reader import SyntheticsReader, DepositAmountOutParams, ExecutionPriceParams, ReaderPricingUtilsExecutionPriceResult, MarketProps +from .synthetics_reader import SyntheticsReader, DepositAmountOutParams, ExecutionPriceParams, ReaderPricingUtilsExecutionPriceResult, MarketProps, ReaderUtilsMarketInfo __all__ = [ @@ -10,5 +10,6 @@ "ExecutionPriceParams", "MarketProps", "ReaderPricingUtilsExecutionPriceResult", + "ReaderUtilsMarketInfo", "SyntheticsReader", ] diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py index 32a0193..2007356 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/__init__.py @@ -1,8 +1,9 @@ -from .schemas import DepositAmountOutParams, ExecutionPriceParams, GetMarketsParams +from .schemas import DepositAmountOutParams, ExecutionPriceParams, GetMarketsParams, GetOpenInterestParams, GetPnlParams from .synthetics_reader import SyntheticsReader from .types import ( MarketProps, OrderProps, + ReaderUtilsMarketInfo, ReaderUtilsPositionInfo, ReaderPricingUtilsExecutionPriceResult, ) @@ -12,7 +13,10 @@ "DepositAmountOutParams", "ExecutionPriceParams", "GetMarketsParams", + "GetOpenInterestParams", + "GetPnlParams", "MarketProps", + "ReaderUtilsMarketInfo", "SyntheticsReader", "OrderProps", "ReaderUtilsPositionInfo", diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py index 31618d3..86e95c0 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py @@ -1,9 +1,9 @@ from typing import Annotated +from eth_typing import HexAddress from pydantic import BaseModel from eth_rpc.types import primitives, Name -from eth_typing import HexAddress from .enums import SwapPricingType from .types import MarketProps, MarketUtilsMarketPrices, PriceProps, SwapPricingUtilsSwapFees @@ -62,3 +62,25 @@ class GetMarketsParams(BaseModel): data_store: HexAddress start_index: primitives.uint256 end_index: primitives.uint256 + + +class GetOpenInterestParams(BaseModel): + data_store: HexAddress + market: MarketProps + index_token_price: PriceProps + is_long: bool + maximize: bool + + +class GetPnlParams(BaseModel): + data_store: HexAddress + market: MarketProps + index_token_price: PriceProps + is_long: bool + maximize: bool + + +class GetMarketParams(BaseModel): + data_store: HexAddress + prices: MarketUtilsMarketPrices + market_key: HexAddress diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py index 2982ea9..2926220 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py @@ -7,6 +7,9 @@ DepositAmountOutParams, ExecutionPriceParams, GetMarketsParams, + GetMarketParams, + GetOpenInterestParams, + GetPnlParams, SwapAmountOutParams, SwapAmountOutResponse, WithdrawalAmountOutParams, @@ -103,7 +106,7 @@ class SyntheticsReader(ProtocolBase): get_market_info: Annotated[ ContractFunc[ - tuple[primitives.address, MarketUtilsMarketPrices, primitives.address], + GetMarketParams, ReaderUtilsMarketInfo ], Name("getMarketInfo"), @@ -143,7 +146,7 @@ class SyntheticsReader(ProtocolBase): get_open_interest_with_pnl: Annotated[ ContractFunc[ - tuple[primitives.address, MarketProps, PriceProps, bool, bool], + GetOpenInterestParams, primitives.int256 ], Name("getOpenInterestWithPnl"), @@ -159,7 +162,7 @@ class SyntheticsReader(ProtocolBase): get_pnl: Annotated[ ContractFunc[ - tuple[primitives.address, MarketProps, PriceProps, bool, bool], + GetPnlParams, primitives.int256 ], Name("getPnl"), From efc4df00d1f741047935b4470a49c6452a31c9c6 Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Mon, 18 Nov 2024 12:47:03 -0500 Subject: [PATCH 11/16] gmx loaders --- .../src/eth_protocols/gmx/datastore.py | 22 ++++ .../eth_protocols/gmx/extensions/__init__.py | 5 - .../src/eth_protocols/gmx/gas.py | 56 ++++++++ .../eth_protocols/gmx/loaders/borrow_apr.py | 44 +++++++ .../src/eth_protocols/gmx/loaders/data.py | 18 ++- .../src/eth_protocols/gmx/loaders/gm_price.py | 121 ++++++++++++++++++ .../eth_protocols/gmx/loaders/positions.py | 38 ++++++ .../gmx/{extensions => }/oracle.py | 2 +- .../eth_protocols/gmx/synthetics_reader.py | 27 ++++ .../src/eth_protocols/gmx/utils/__init__.py | 2 + .../src/eth_protocols/gmx/utils/scaling.py | 2 + .../src/eth_typeshed/gmx/__init__.py | 11 +- .../gmx/synthetics_reader/schemas.py | 17 ++- .../synthetics_reader/synthetics_reader.py | 11 +- 14 files changed, 359 insertions(+), 17 deletions(-) create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/datastore.py delete mode 100644 packages/eth_protocols/src/eth_protocols/gmx/extensions/__init__.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/gas.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/loaders/borrow_apr.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/loaders/gm_price.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/loaders/positions.py rename packages/eth_protocols/src/eth_protocols/gmx/{extensions => }/oracle.py (96%) create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/utils/scaling.py diff --git a/packages/eth_protocols/src/eth_protocols/gmx/datastore.py b/packages/eth_protocols/src/eth_protocols/gmx/datastore.py new file mode 100644 index 0000000..dac5043 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/datastore.py @@ -0,0 +1,22 @@ +from typing import Literal + +from pydantic import BaseModel, Field, PrivateAttr + +from eth_typeshed.gmx import GMXEnvironment, Datastore as DatastoreContract + +PRECISION = 30 + + +class Datastore(BaseModel): + network: Literal["arbitrum", "avalanche"] = Field(default="arbitrum") + _environment: GMXEnvironment = PrivateAttr() + _contract: DatastoreContract = PrivateAttr() + + def model_post_init(self, __context): + super().model_post_init(__context) + + self._environment = GMXEnvironment.get_environment(self.network) + self._contract = DatastoreContract[self.network_type](address=self._environment.datastore) + + async def get_uint(self, key: str) -> int: + return await self._contract.get_uint(key).get() diff --git a/packages/eth_protocols/src/eth_protocols/gmx/extensions/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/extensions/__init__.py deleted file mode 100644 index 9f57c23..0000000 --- a/packages/eth_protocols/src/eth_protocols/gmx/extensions/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .oracle import PriceOracle - -__all__ = [ - "PriceOracle", -] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/gas.py b/packages/eth_protocols/src/eth_protocols/gmx/gas.py new file mode 100644 index 0000000..acec251 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/gas.py @@ -0,0 +1,56 @@ +from .datastore import Datastore +from .keys import ( + decrease_order_gas_limit_key, increase_order_gas_limit_key, + execution_gas_fee_base_amount_key, execution_gas_fee_multiplier_key, + single_swap_gas_limit_key, swap_order_gas_limit_key, deposit_gas_limit_key, + withdraw_gas_limit_key +) +from .utils import apply_factor + + +async def get_execution_fee(datastore: Datastore, estimated_gas_limit: int, gas_price: int) -> float: + """ + Given a dictionary of gas_limits, the uncalled datastore object of a given operation, and the + latest gas price, calculate the minimum execution fee required to perform an action + + Parameters + ---------- + gas_limits : dict + dictionary of uncalled datastore limit obkects. + estimated_gas_limit : datastore_object + the uncalled datastore object specific to operation that will be undertaken. + gas_price : int + latest gas price. + + """ + + base_gas_limit = await datastore.get_uint(execution_gas_fee_base_amount_key()) + multiplier_factor = await datastore.get_uint(execution_gas_fee_multiplier_key()) + adjusted_gas_limit = base_gas_limit + apply_factor(estimated_gas_limit, multiplier_factor) + + return adjusted_gas_limit * gas_price + + +async def get_gas_limits(datastore: Datastore) -> dict[str, int]: + """ + Given a Web3 contract object of the datstore, return a dictionary with the uncalled gas limits + that correspond to various operations that will require the execution fee to calculated for. + + Parameters + ---------- + datastore_object : web3 object + contract connection. + """ + gas_limits: dict[str, int] = { + "deposit": await datastore.get_uint(deposit_gas_limit_key()), + "withdraw": await datastore.get_uint(withdraw_gas_limit_key()), + "single_swap": await datastore.get_uint(single_swap_gas_limit_key()), + "swap_order": await datastore.get_uint(swap_order_gas_limit_key()), + "increase_order": await datastore.get_uint(increase_order_gas_limit_key()), + "decrease_order": await datastore.get_uint(decrease_order_gas_limit_key()), + "estimated_fee_base_gas_limit": await datastore.get_uint( + execution_gas_fee_base_amount_key()), + "estimated_fee_multiplier_factor": await datastore.get_uint( + execution_gas_fee_multiplier_key())} + + return gas_limits diff --git a/packages/eth_protocols/src/eth_protocols/gmx/loaders/borrow_apr.py b/packages/eth_protocols/src/eth_protocols/gmx/loaders/borrow_apr.py new file mode 100644 index 0000000..1581d8c --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/loaders/borrow_apr.py @@ -0,0 +1,44 @@ +from typing import Any + +from eth_typeshed.gmx.synthetics_reader.types import ReaderUtilsMarketInfo + +from .data import DataLoader + + +class GetBorrowAPR(DataLoader): + async def _get_data_processing(self) -> dict[str, Any]: + """ + Generate the dictionary of borrow APR data + + Returns + ------- + funding_apr : dict + dictionary of borrow data. + + """ + output_list: list[ReaderUtilsMarketInfo] = [] + mapper = [] + for market_key in self.markets.info: + index_token_address = self.markets.get_index_token_address( + market_key + ) + + await self._get_token_addresses(market_key) + output = await self.get_market_info( + market_key, + index_token_address, + ) + + output_list.append(output) + mapper.append(self.markets.get_market_symbol(market_key)) + + for key, output in zip(mapper, output_list): + self.output["long"][key] = ( + output.borrowing_factor_per_second_for_longs / 10 ** 28 + ) * 3600 + self.output["short"][key] = ( + output.borrowing_factor_per_second_for_shorts / 10 ** 28 + ) * 3600 + + self.output['parameter'] = "borrow_apr" + return self.output diff --git a/packages/eth_protocols/src/eth_protocols/gmx/loaders/data.py b/packages/eth_protocols/src/eth_protocols/gmx/loaders/data.py index 9e76b0d..d566efb 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/loaders/data.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/loaders/data.py @@ -20,7 +20,7 @@ class DataLoader(BaseModel): network: Literal["arbitrum", "avalanche"] = Field(default="arbitrum") filter_swap_markets: bool = Field(default=True) - output: dict[str, dict[str, dict[str, Any]]] = { + output: dict[str, Any] = { "long": {}, "short": {} } @@ -29,18 +29,24 @@ class DataLoader(BaseModel): _long_token_address: None | ChecksumAddress = PrivateAttr(default=None) _short_token_address: None | ChecksumAddress = PrivateAttr(default=None) + @property + def markets(self): + return self._markets + def model_post_init(self, __context): self._synthetics_reader = SyntheticsReader(network=self.network) self._markets = MarketsLoader(network=self.network) async def get_data(self): + await self._markets.load() if self.filter_swap_markets: await self._filter_swap_markets() data = await self._get_data_processing() return data - async def _get_data_processing(self): - pass + # @abstrctmethod + # async def _get_data_processing(self, pnl_factor_type: bytes): + # pass async def _get_token_addresses(self, market_key: str): self._long_token_address = self._markets.get_long_token_address(market_key) @@ -81,9 +87,8 @@ async def _get_pnl( async def _get_oracle_prices( self, - market_key: str, index_token_address: HexAddress, - ) -> ReaderUtilsMarketInfo: + ) -> MarketUtilsMarketPrices: oracle_prices_dict = await self._synthetics_reader.get_recent_prices() index_token: ChecksumAddress = to_checksum(index_token_address) @@ -122,7 +127,10 @@ async def _get_oracle_prices( max=int(1000000000000000000000000), ), ) + return prices + async def get_market_info(self, market_key: str, index_token_address: HexAddress) -> ReaderUtilsMarketInfo: + prices = await self._get_oracle_prices(index_token_address) return await self._synthetics_reader.get_market_info( GetMarketParams( data_store=self._synthetics_reader.datastore, diff --git a/packages/eth_protocols/src/eth_protocols/gmx/loaders/gm_price.py b/packages/eth_protocols/src/eth_protocols/gmx/loaders/gm_price.py new file mode 100644 index 0000000..59043dd --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/loaders/gm_price.py @@ -0,0 +1,121 @@ +from eth_typeshed.gmx.synthetics_reader.schemas import MarketProps, PriceProps, GetMarketTokenPriceResponse + +from .data import DataLoader +from ..keys import ( + MAX_PNL_FACTOR_FOR_TRADERS, MAX_PNL_FACTOR_FOR_DEPOSITS, + MAX_PNL_FACTOR_FOR_WITHDRAWALS +) + + +class GMPrices(DataLoader): + async def get_price_withdraw(self): + """ + Get GM price if withdrawing from LP + """ + pnl_factor_type = MAX_PNL_FACTOR_FOR_WITHDRAWALS + + return await self._get_data_processing(pnl_factor_type) + + async def get_price_deposit(self): + """ + Get GM price if depositing to LP + """ + pnl_factor_type = MAX_PNL_FACTOR_FOR_DEPOSITS + return await self._get_data_processing(pnl_factor_type) + + async def get_price_traders(self): + """ + Get GM price if trading from LP + """ + pnl_factor_type = MAX_PNL_FACTOR_FOR_TRADERS + return await self._get_data_processing(pnl_factor_type) + + async def _get_data_processing(self, pnl_factor_type: bytes) -> dict[str, float]: + output_list: list[GetMarketTokenPriceResponse] = [] + mapper: list[str] = [] + await self._filter_swap_markets() + + for market_key in self.markets.info: + await self._get_token_addresses(market_key) + index_token_address = self.markets.get_index_token_address( + market_key + ) + oracle_prices = await self._get_oracle_prices( + index_token_address, + ) + + market = MarketProps( + market_token=market_key, + index_token=index_token_address, + long_token=self._long_token_address, + short_token=self._short_token_address + ) + + output = await self._make_market_token_price_query( + market, + oracle_prices.index_token_price, + oracle_prices.long_token_price, + oracle_prices.short_token_price, + pnl_factor_type + ) + + # add the uncalled web3 object to list + output_list.append(output) + + # add the market symbol to a list to use to map to dictionary later + mapper.append(self.markets.get_market_symbol(market_key)) + + for key, output in zip(mapper, output_list): + # divide by 10**30 to turn into USD value + self.output[key] = output.market_token_price / 10**30 + + self.output['parameter'] = "gm_prices" + del self.output["long"] + del self.output["short"] + + return self.output + + async def _make_market_token_price_query( + self, + market: MarketProps, + index_price_tuple: PriceProps, + long_price_tuple: PriceProps, + short_price_tuple: PriceProps, + pnl_factor_type: bytes, + ) -> GetMarketTokenPriceResponse: + """ + Get the raw GM price from the reader contract for a given market tuple, + index, long, and + short max/min price tuples, and the pnl factor hash. + + Parameters + ---------- + market : list + list containing contract addresses of the market. + index_price_tuple : tuple + tuple of min and max prices. + long_price_tuple : tuple + tuple of min and max prices.. + short_price_tuple : tuple + tuple of min and max prices.. + pnl_factor_type : hash + descriptor for datastore. + + Returns + ------- + output : TYPE + DESCRIPTION. + + """ + # maximize to take max prices in calculation + maximize = True + output = await self._synthetics_reader.get_market_token_price( + market, + index_price_tuple, + long_price_tuple, + short_price_tuple, + pnl_factor_type, + maximize, + ) + + return output diff --git a/packages/eth_protocols/src/eth_protocols/gmx/loaders/positions.py b/packages/eth_protocols/src/eth_protocols/gmx/loaders/positions.py new file mode 100644 index 0000000..4c015b3 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/loaders/positions.py @@ -0,0 +1,38 @@ +import logging +from typing import Literal + +from pydantic import BaseModel, Field, PrivateAttr + +from ..types.base import ChecksumAddress +from ..synthetics_reader import SyntheticsReader + + +class OpenPositions(BaseModel): + network: Literal["arbitrum", "avalanche"] = Field(default="arbitrum") + address: ChecksumAddress + _synthetics_reader: SyntheticsReader = PrivateAttr() + + def model_post_init(self, __context): + self._synthetics_reader = SyntheticsReader(network=self.network) + + async def get_positions(self): + processed_positions = {} + raw_positions = await self._synthetics_reader.get_account_positions(self.address) + + for raw_position in raw_positions: + try: + processed_position = self._get_data_processing(raw_position) + + # TODO - maybe a better way of building the key? + if processed_position['is_long']: + direction = 'long' + else: + direction = 'short' + + key = "{}_{}".format( + processed_position['market_symbol'][0], + direction + ) + processed_positions[key] = processed_position + except KeyError as e: + logging.error(f"Incompatible market: {e}") diff --git a/packages/eth_protocols/src/eth_protocols/gmx/extensions/oracle.py b/packages/eth_protocols/src/eth_protocols/gmx/oracle.py similarity index 96% rename from packages/eth_protocols/src/eth_protocols/gmx/extensions/oracle.py rename to packages/eth_protocols/src/eth_protocols/gmx/oracle.py index d32e0c6..92ff635 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/extensions/oracle.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/oracle.py @@ -6,7 +6,7 @@ from eth_typing import ChecksumAddress from pydantic import BaseModel, PrivateAttr -from ..types import OraclePrice, OraclePriceResponse +from .types import OraclePrice, OraclePriceResponse class PriceOracle(BaseModel): diff --git a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py index 13c23a2..ba51b58 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py @@ -7,12 +7,15 @@ from eth_rpc.types.primitives import address, uint256 from eth_rpc.utils import to_checksum from eth_typeshed.gmx import MarketProps, GMXEnvironment, SyntheticsReader as SyntheticsReaderContract, ExecutionPriceParams, ReaderUtilsMarketInfo +from eth_typeshed.gmx.synthetics_reader.types import PositionProps, PriceProps from eth_typeshed.gmx.synthetics_reader.schemas import ( DepositAmountOutParams, GetMarketsParams, GetMarketParams, GetOpenInterestParams, GetPnlParams, + GetMarketTokenPriceParams, + GetMarketTokenPriceResponse, SwapAmountOutParams, SwapAmountOutResponse, WithdrawalAmountOutParams, @@ -126,6 +129,30 @@ async def get_pnl(self, params: GetPnlParams) -> int: async def get_market_info(self, params: GetMarketParams) -> ReaderUtilsMarketInfo: return await self._contract.get_market_info(params).get() + async def get_market_token_price( + self, + market: MarketProps, + index_token_price: PriceProps, + long_token_price: PriceProps, + short_token_price: PriceProps, + pnl_factor_type: bytes, + maximize: bool + ) -> GetMarketTokenPriceResponse: + return await self._contract.get_market_token_price(GetMarketTokenPriceParams( + data_store=self.datastore, + market=market, + index_token_price=index_token_price, + long_token_price=long_token_price, + short_token_price=short_token_price, + pnl_factor_type=pnl_factor_type, + maximize=maximize + )).get() + + async def get_account_positions(self, address: ChecksumAddress, start: int = 0, end: int = 10) -> list[PositionProps]: + return await self._contract.get_account_positions( + (self.datastore, address, uint256(start), uint256(end)), + ).get() + # Private Methods async def _check_if_index_token_in_signed_prices_api(self, index_token_address: HexAddress | address) -> bool: diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py index 59b69c5..0ebda20 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py @@ -1,8 +1,10 @@ from .hashing import create_hash_string, create_hash +from .scaling import apply_factor from .tokens import TokenInfo, get_tokens_address_dict __all__ = [ "TokenInfo", + "apply_factor", "create_hash_string", "create_hash", "get_tokens_address_dict", diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/scaling.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/scaling.py new file mode 100644 index 0000000..41a0c00 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/scaling.py @@ -0,0 +1,2 @@ +def apply_factor(value: int, factor: int) -> float: + return (value * factor) / 10**30 diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py index cfdce47..9317a2d 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py @@ -1,11 +1,20 @@ from ._environment import GMXEnvironment, GMXArbitrum, GMXAvalanche -from .synthetics_reader import SyntheticsReader, DepositAmountOutParams, ExecutionPriceParams, ReaderPricingUtilsExecutionPriceResult, MarketProps, ReaderUtilsMarketInfo +from .datastore import Datastore +from .synthetics_reader import ( + SyntheticsReader, + DepositAmountOutParams, + ExecutionPriceParams, + ReaderPricingUtilsExecutionPriceResult, + MarketProps, + ReaderUtilsMarketInfo, +) __all__ = [ "GMXEnvironment", "GMXArbitrum", "GMXAvalanche", + "Datastore", "DepositAmountOutParams", "ExecutionPriceParams", "MarketProps", diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py index 86e95c0..7146a62 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/schemas.py @@ -5,7 +5,7 @@ from eth_rpc.types import primitives, Name from .enums import SwapPricingType -from .types import MarketProps, MarketUtilsMarketPrices, PriceProps, SwapPricingUtilsSwapFees +from .types import MarketProps, MarketUtilsMarketPrices, MarketPoolValueInfoProps, PriceProps, SwapPricingUtilsSwapFees class ExecutionPriceParams(BaseModel): @@ -84,3 +84,18 @@ class GetMarketParams(BaseModel): data_store: HexAddress prices: MarketUtilsMarketPrices market_key: HexAddress + + +class GetMarketTokenPriceParams(BaseModel): + data_store: primitives.address + market: MarketProps + index_token_price: PriceProps + long_token_price: PriceProps + short_token_price: PriceProps + pnl_factor_type: primitives.bytes32 + maximize: bool + + +class GetMarketTokenPriceResponse(BaseModel): + market_token_price: primitives.int256 + pool_value_info: MarketPoolValueInfoProps diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py index 2926220..3786243 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/synthetics_reader/synthetics_reader.py @@ -1,5 +1,7 @@ from typing import Annotated +from eth_typing import HexAddress + from eth_rpc import ProtocolBase, ContractFunc from eth_rpc.types import primitives, Name @@ -10,6 +12,8 @@ GetMarketParams, GetOpenInterestParams, GetPnlParams, + GetMarketTokenPriceParams, + GetMarketTokenPriceResponse, SwapAmountOutParams, SwapAmountOutResponse, WithdrawalAmountOutParams, @@ -21,7 +25,6 @@ ReaderPricingUtilsExecutionPriceResult, PositionProps, MarketProps, - MarketPoolValueInfoProps, DepositProps, WithdrawalProps, ShiftProps, @@ -50,7 +53,7 @@ class SyntheticsReader(ProtocolBase): get_account_positions: Annotated[ ContractFunc[ - tuple[primitives.address, primitives.address, primitives.uint256, primitives.uint256], + tuple[HexAddress, HexAddress, primitives.uint256, primitives.uint256], list[PositionProps] ], Name("getAccountPositions"), @@ -122,8 +125,8 @@ class SyntheticsReader(ProtocolBase): get_market_token_price: Annotated[ ContractFunc[ - tuple[primitives.address, MarketProps, PriceProps, PriceProps, PriceProps, primitives.bytes32, bool], - tuple[primitives.int256, MarketPoolValueInfoProps] + GetMarketTokenPriceParams, + GetMarketTokenPriceResponse, ], Name("getMarketTokenPrice"), ] From 79dccb13ab8656c4d27aa845bebc6a990155748a Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Mon, 18 Nov 2024 12:48:31 -0500 Subject: [PATCH 12/16] fixup --- .../eth_protocols/src/eth_protocols/gmx/synthetics_reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py index ba51b58..c9516c2 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/synthetics_reader.py @@ -22,7 +22,7 @@ WithdrawalAmountOutResponse, ) -from .extensions.oracle import PriceOracle +from .oracle import PriceOracle from .types import ExecutionPriceResult, MarketInfo from .utils import TokenInfo, get_tokens_address_dict From 0660ec334f4c846caaeb0afa2cac4b9d13dbc037 Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Mon, 18 Nov 2024 23:08:43 -0500 Subject: [PATCH 13/16] add deposit methods --- .../src/eth_protocols/gmx/exchange_router.py | 51 +++ .../eth_protocols/gmx/executors/approve.py | 54 ++++ .../eth_protocols/gmx/executors/deposit.py | 305 ++++++++++++++++++ .../src/eth_protocols/gmx/loaders/__init__.py | 4 + .../src/eth_protocols/gmx/utils/__init__.py | 2 + .../src/eth_protocols/gmx/utils/swap.py | 76 +++++ .../src/eth_typeshed/erc20/erc20.py | 2 +- .../src/eth_typeshed/gmx/__init__.py | 2 + 8 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/exchange_router.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/executors/approve.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/utils/swap.py diff --git a/packages/eth_protocols/src/eth_protocols/gmx/exchange_router.py b/packages/eth_protocols/src/eth_protocols/gmx/exchange_router.py new file mode 100644 index 0000000..fcc8952 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/exchange_router.py @@ -0,0 +1,51 @@ +from typing import Literal + +from eth_rpc import PrivateKeyWallet +from eth_rpc.networks import Arbitrum, Avalanche +from eth_typeshed.gmx import GMXEnvironment, ExchangeRouter as ExchangeRouterContract +from eth_typeshed.gmx.exchange_router import CreateDepositParams +from eth_typing import HexAddress +from pydantic import BaseModel, Field, PrivateAttr +from .loaders import MarketsLoader + + +class ExchangeRouter(BaseModel): + network: Literal["arbitrum", "avalanche"] = Field(default="arbitrum") + _environment: GMXEnvironment = PrivateAttr() + _contract: ExchangeRouterContract + + @property + def network_type(self) -> type[Arbitrum] | type[Avalanche]: + return Arbitrum if self.network == "arbitrum" else Avalanche + + def model_post_init(self, __context): + super().model_post_init(__context) + + self._environment = GMXEnvironment.get_environment(self.network) + self._contract = ExchangeRouterContract[self.network_type](address=self._environment.exchange_router) + + async def load_markets(self): + self.all_markets_info = MarketsLoader(network=self.network).get_available_markets() + + def encode_send_tokens(self, token_address: HexAddress, amount: int): + return self._contract.send_tokens( + token_address, + '0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55', + amount + ).encode() + + def encode_send_wnt(self, amount: int): + return self._contract.send_wnt( + '0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55', + amount + ).encode() + + def encode_create_deposit(self, params: CreateDepositParams): + return self._contract.create_deposit( + params, + ).encode() + + async def multicall(self, calls: list[bytes], wallet: PrivateKeyWallet, **kwargs): + return await self._contract.multicall( + calls, + ).execute(wallet, **kwargs) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/executors/approve.py b/packages/eth_protocols/src/eth_protocols/gmx/executors/approve.py new file mode 100644 index 0000000..6139ddd --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/executors/approve.py @@ -0,0 +1,54 @@ +from eth_typing import HexAddress, HexStr + +from eth_rpc import PrivateKeyWallet +from eth_rpc.utils import to_checksum +from eth_typeshed.erc20 import ERC20, OwnerSpenderRequest, ApproveRequest + + +async def check_if_approved( + wallet: PrivateKeyWallet, + spender: HexAddress, + token: HexAddress, + amount: int, + max_fee_per_gas: int | None = None, + approve: bool = True, +): + if token == "0x47904963fc8b2340414262125aF798B9655E58Cd": + token = HexAddress(HexStr("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f")) + + token_checksum_address = to_checksum(token) + + # TODO - for AVAX support this will need to incl WAVAX address + if token_checksum_address == "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1": + balance_of = await wallet.balance() + else: + balance_of = await ERC20(address=token).balance_of(wallet.address).get() + + if balance_of < amount: + raise Exception("Insufficient balance!") + + amount_approved = await ERC20(address=token).allowance( + OwnerSpenderRequest(owner=wallet.address, spender=spender) + ).get() + + print("Checking coins for approval..") + if amount_approved < amount and approve: + print('Approving contract "{}" to spend {} tokens belonging to token address: {}'.format( + spender, amount, token_checksum_address)) + + txn_hash = await ERC20(address=token).approve( + ApproveRequest( + spender=spender, + amount=amount, + ) + ).execute(wallet, max_fee_per_gas=int(max_fee_per_gas)) + + print("Txn submitted!") + print("Check status: https://arbiscan.io/tx/{}".format(txn_hash)) + + if amount_approved < amount and not approve: + raise Exception("Token not approved for spend, please allow first!") + + print('Contract "{}" approved to spend {} tokens belonging to token address: {}'.format( + spender, amount, token_checksum_address)) + print("Coins Approved for spend!") diff --git a/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py b/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py new file mode 100644 index 0000000..e468c9f --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py @@ -0,0 +1,305 @@ +from hexbytes import HexBytes + +from eth_rpc import Block, PrivateKeyWallet +from eth_rpc.utils import to_checksum +from eth_typing import HexAddress, HexStr +from eth_typeshed.gmx.exchange_router import CreateDepositParams +from eth_typeshed.gmx.synthetics_reader import DepositAmountOutParams +from eth_typeshed.gmx.synthetics_reader.schemas import MarketProps, MarketUtilsMarketPrices, PriceProps +from eth_typeshed.gmx.synthetics_reader.enums import SwapPricingType +from pydantic import BaseModel, Field, PrivateAttr + +from ..loaders import MarketsLoader +from ..synthetics_reader import SyntheticsReader +from .approve import check_if_approved +from ..gas import get_execution_fee +from ..types import MarketInfo +from ..exchange_router import ExchangeRouter +from ..utils import determine_swap_route + + +class Deposit(BaseModel): + market_key: str + initial_long_token: HexAddress + initial_short_token: HexAddress + long_token_amount: int + short_token_amount: int + max_fee_per_gas: int | None = None + debug_mode: bool = False + execution_buffer: float = 1.1 + long_token_swap_path: list[str] = Field(default_factory=list) + short_token_swap_path: list[str] = Field(default_factory=list) + all_markets_info: dict[HexAddress, MarketInfo] | None = None + + _reader: SyntheticsReader = PrivateAttr() + _exchange_router: ExchangeRouter = PrivateAttr() + _market_loader: MarketsLoader = PrivateAttr() + + def model_post_init(self, __context): + super().model_post_init(__context) + self._market_loader = MarketsLoader(network=self.network) + self._exchange_router = ExchangeRouter(network=self.network) + + async def load(self): + if self.max_fee_per_gas is None: + block = await Block.latest() + self.max_fee_per_gas = block.base_fee_per_gas * 1.35 + await self._market_loader.load() + self.all_markets_info = self._market_loader._info + + async def check_for_approval(self, wallet: PrivateKeyWallet): + """ + Check for Approval + + """ + spender = self._reader._environment.synthetics_router + + if self.long_token_amount > 0: + await check_if_approved( + wallet, + spender, + self.initial_long_token, + self.long_token_amount, + self.max_fee_per_gas, + approve=True, + ) + + if self.short_token_amount > 0: + await check_if_approved( + wallet, + spender, + self.initial_short_token, + self.short_token_amount, + self.max_fee_per_gas, + approve=True, + ) + + async def _submit_transaction( + self, + wallet: PrivateKeyWallet, + value: float, + multicall_args: list, + gas_limits: dict, + ) -> HexStr: + tx_hash = await self._exchange_router.multicall( + multicall_args, + wallet=wallet, + value=value, + max_fee_per_gas=self.max_fee_per_gas, + ) + return tx_hash + + async def create_deposit_order(self, wallet: PrivateKeyWallet): + await self.check_for_approval(wallet) + + should_unwrap_native_token = True + eth_zero_address = "0x0000000000000000000000000000000000000000" + ui_ref_address = "0x0000000000000000000000000000000000000000" + + user_wallet_address = wallet.address + eth_zero_address = to_checksum(eth_zero_address) + ui_ref_address = to_checksum(ui_ref_address) + + # Minimum number of GM tokens we should expect + min_market_tokens = await self._estimate_deposit() + + # Giving a 10% buffer here + execution_fee = int( + get_execution_fee( + self._gas_limits, + self._gas_limits_order_type, + self._connection.eth.gas_price + ) * self.execution_buffer + ) + + callback_gas_limit = 0 + + # If we havent defined either long/short set it to market default + self._check_initial_tokens() + + # build swap paths for long/short deposit + self._determine_swap_paths() + + arguments = ( + user_wallet_address, + eth_zero_address, + ui_ref_address, + self.market_key, + self.initial_long_token, + self.initial_short_token, + self.long_token_swap_path, + self.short_token_swap_path, + min_market_tokens, + should_unwrap_native_token, + execution_fee, + callback_gas_limit + ) + + multicall_args: list = [] + wnt_amount = 0 + + # Send long side of deposit if more than 0 tokens + if self.long_token_amount > 0: + if self.initial_long_token != "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1": + multicall_args = multicall_args + [HexBytes( + self._send_tokens( + self.initial_long_token, + self.long_token_amount + ) + )] + + # If adding long side with native token append to wnt_amount + else: + wnt_amount = wnt_amount + self.long_token_amount + + # Send short side of deposit if more than 0 tokens + if self.short_token_amount > 0: + if self.initial_short_token != "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1": + multicall_args = multicall_args + [HexBytes( + self._send_tokens( + self.initial_short_token, + self.short_token_amount + ) + )] + + # If adding short side with native token append to wnt_amount + else: + wnt_amount = wnt_amount + self.short_token_amount + + # Send wnt_amount, incl any deposit + multicall_args = multicall_args + [HexBytes( + self._send_wnt( + int(wnt_amount + execution_fee) + ) + )] + + # send our deposit parameters + multicall_args = multicall_args + [HexBytes( + self._create_order( + arguments + ) + )] + + await self._submit_transaction( + wallet, + int(wnt_amount + execution_fee), + multicall_args, + self._gas_limits + ) + + def _check_initial_tokens(self): + """ + Check if we need to set the long or short token address + when depositing + """ + + if self.long_token_amount == 0: + self.initial_long_token = self.all_markets_info[ + self.market_key + ]['long_token_address'] + + if self.short_token_amount == 0: + self.initial_short_token = self.all_markets_info[ + self.market_key + ]['short_token_address'] + + def _determine_swap_paths(self): + """ + Check the required markets we need to swap our tokens through + to deposit on the long or short side + """ + + market = self.all_markets_info[self.market_key] + + if market['long_token_address'] != self.initial_long_token: + + self.long_token_swap_path, requires_multi_swap = determine_swap_route( + self.all_markets_info, + self.initial_long_token, + market['long_token_address'] + ) + + if market['short_token_address'] != self.initial_short_token: + self.short_token_swap_path, requires_multi_swap = determine_swap_route( + self.all_markets_info, + self.initial_short_token, + market['short_token_address'] + ) + + def _create_order(self, args: CreateDepositParams): + """ + Create Order + """ + return self._exchange_router.encode_create_deposit( + args, + ) + + def _send_tokens(self, token_address, amount): + """ + Send tokens + """ + return self._exchange_router.encode_send_tokens( + token_address, + '0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55', + amount + ) + + def _send_wnt(self, amount): + """ + Send WNT + """ + return self._exchange_router.encode_send_wnt(amount) + + async def _estimate_deposit(self): + """ + Given the amount of tokens we have to deposit, estimate + the amount of GM we expect out + + Returns + ------- + int + amount of GM tokens. + + """ + data_store_contract_address = self._reader.datastore + + market = self.all_markets_info[self.market_key] + oracle_prices_dict = await self._reader.get_recent_prices() + + index_token_address = market.index_token_address + long_token_address = market.long_token_address + short_token_address = market.short_token_address + + market_addresses = MarketProps( + market_token=self.market_key, + index_token=index_token_address, + long_token=long_token_address, + short_token=short_token_address, + ) + prices = MarketUtilsMarketPrices( + index_token_price=PriceProps( + int(oracle_prices_dict[index_token_address]['minPriceFull']), + int(oracle_prices_dict[index_token_address]['maxPriceFull']) + ), + long_token_price=PriceProps( + int(oracle_prices_dict[long_token_address]['minPriceFull']), + int(oracle_prices_dict[long_token_address]['maxPriceFull']) + ), + short_token_price=PriceProps( + int(oracle_prices_dict[short_token_address]['minPriceFull']), + int(oracle_prices_dict[short_token_address]['maxPriceFull']) + ) + ) + + parameters = DepositAmountOutParams( + data_store=data_store_contract_address, + market=market_addresses, + prices=prices, + long_token_amount=self.long_token_amount, + short_token_amount=self.short_token_amount, + ui_fee_receiver="0x0000000000000000000000000000000000000000", + swap_pricing_type=SwapPricingType.Deposit, + include_virtual_inventory_impact=True, + ) + + return await self._reader.get_estimated_deposit_amount_out(parameters) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/loaders/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/loaders/__init__.py index e69de29..1b224ef 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/loaders/__init__.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/loaders/__init__.py @@ -0,0 +1,4 @@ +from .markets import MarketsLoader + + +__all__ = ["MarketsLoader"] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py index 0ebda20..d25d9e8 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py @@ -1,5 +1,6 @@ from .hashing import create_hash_string, create_hash from .scaling import apply_factor +from .swap import determine_swap_route from .tokens import TokenInfo, get_tokens_address_dict __all__ = [ @@ -7,5 +8,6 @@ "apply_factor", "create_hash_string", "create_hash", + "determine_swap_route", "get_tokens_address_dict", ] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/swap.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/swap.py new file mode 100644 index 0000000..89693c9 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/swap.py @@ -0,0 +1,76 @@ +def determine_swap_route(markets: dict, in_token: str, out_token: str): + """ + Using the available markets, find the list of GMX markets required + to swap from token in to token out + + Parameters + ---------- + markets : dict + dictionary of markets output by getMarketInfo. + in_token : str + contract address of in token. + out_token : str + contract address of out token. + + Returns + ------- + list + list of GMX markets to swap through. + is_requires_multi_swap : TYPE + requires more than one market to pass thru. + + """ + + if in_token == "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f": + in_token = "0x47904963fc8b2340414262125aF798B9655E58Cd" + + if out_token == "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f": + out_token = "0x47904963fc8b2340414262125aF798B9655E58Cd" + + if in_token == "0xaf88d065e77c8cC2239327C5EDb3A432268e5831": + gmx_market_address = find_dictionary_by_key_value( + markets, + "index_token_address", + out_token + )['gmx_market_address'] + else: + gmx_market_address = find_dictionary_by_key_value( + markets, + "index_token_address", + in_token + )['gmx_market_address'] + + is_requires_multi_swap = False + + if out_token != "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" and \ + in_token != "0xaf88d065e77c8cC2239327C5EDb3A432268e5831": + is_requires_multi_swap = True + second_gmx_market_address = find_dictionary_by_key_value( + markets, + "index_token_address", + out_token + )['gmx_market_address'] + + return [gmx_market_address, second_gmx_market_address], is_requires_multi_swap + + return [gmx_market_address], is_requires_multi_swap + + +def find_dictionary_by_key_value(outer_dict: dict, key: str, value: str): + """ + For a given dictionary, find a value which matches a set of keys + + Parameters + ---------- + outer_dict : dict + dictionary to filter through. + key : str + keys to search for. + value : str + required key to match. + + """ + for inner_dict in outer_dict.values(): + if key in inner_dict and inner_dict[key] == value: + return inner_dict + return None diff --git a/packages/eth_typeshed/src/eth_typeshed/erc20/erc20.py b/packages/eth_typeshed/src/eth_typeshed/erc20/erc20.py index 7d2ebce..027381f 100644 --- a/packages/eth_typeshed/src/eth_typeshed/erc20/erc20.py +++ b/packages/eth_typeshed/src/eth_typeshed/erc20/erc20.py @@ -62,7 +62,7 @@ async def load_async(self): class ERC20(ERC20Metadata): balance_of: Annotated[ ContractFunc[ - Annotated[primitives.address, Name("owner")], + Annotated[HexAddress, Name("owner")], Annotated[primitives.uint256, Name("amount")], ], Name("balanceOf"), diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py index 9317a2d..bd5c988 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/__init__.py @@ -1,5 +1,6 @@ from ._environment import GMXEnvironment, GMXArbitrum, GMXAvalanche from .datastore import Datastore +from .exchange_router import ExchangeRouter from .synthetics_reader import ( SyntheticsReader, DepositAmountOutParams, @@ -16,6 +17,7 @@ "GMXAvalanche", "Datastore", "DepositAmountOutParams", + "ExchangeRouter", "ExecutionPriceParams", "MarketProps", "ReaderPricingUtilsExecutionPriceResult", From bbb1d1ee79288fff75f065fd528a64983d09d904 Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Mon, 18 Nov 2024 23:58:53 -0500 Subject: [PATCH 14/16] fix deposit --- .../eth_protocols/gmx/executors/deposit.py | 59 +++++++++++-------- .../src/eth_protocols/gmx/utils/__init__.py | 2 + .../src/eth_protocols/gmx/{ => utils}/gas.py | 37 +++++++----- 3 files changed, 60 insertions(+), 38 deletions(-) rename packages/eth_protocols/src/eth_protocols/gmx/{ => utils}/gas.py (61%) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py b/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py index e468c9f..c5450cf 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py @@ -1,24 +1,28 @@ -from hexbytes import HexBytes +from typing import Literal from eth_rpc import Block, PrivateKeyWallet +from eth_rpc.networks import Arbitrum, Avalanche from eth_rpc.utils import to_checksum from eth_typing import HexAddress, HexStr from eth_typeshed.gmx.exchange_router import CreateDepositParams from eth_typeshed.gmx.synthetics_reader import DepositAmountOutParams from eth_typeshed.gmx.synthetics_reader.schemas import MarketProps, MarketUtilsMarketPrices, PriceProps from eth_typeshed.gmx.synthetics_reader.enums import SwapPricingType +from hexbytes import HexBytes from pydantic import BaseModel, Field, PrivateAttr from ..loaders import MarketsLoader from ..synthetics_reader import SyntheticsReader from .approve import check_if_approved -from ..gas import get_execution_fee +from ..utils.gas import get_execution_fee from ..types import MarketInfo from ..exchange_router import ExchangeRouter -from ..utils import determine_swap_route +from ..utils import determine_swap_route, get_gas_limits +from ..datastore import Datastore class Deposit(BaseModel): + network: Literal["arbitrum", "avalanche"] market_key: str initial_long_token: HexAddress initial_short_token: HexAddress @@ -31,14 +35,20 @@ class Deposit(BaseModel): short_token_swap_path: list[str] = Field(default_factory=list) all_markets_info: dict[HexAddress, MarketInfo] | None = None + _datastore: Datastore = PrivateAttr() _reader: SyntheticsReader = PrivateAttr() _exchange_router: ExchangeRouter = PrivateAttr() _market_loader: MarketsLoader = PrivateAttr() + @property + def network_type(self) -> type[Arbitrum] | type[Avalanche]: + return Arbitrum if self.network == "arbitrum" else Avalanche + def model_post_init(self, __context): super().model_post_init(__context) self._market_loader = MarketsLoader(network=self.network) self._exchange_router = ExchangeRouter(network=self.network) + self._datastore = Datastore(network=self.network) async def load(self): if self.max_fee_per_gas is None: @@ -78,8 +88,7 @@ async def _submit_transaction( self, wallet: PrivateKeyWallet, value: float, - multicall_args: list, - gas_limits: dict, + multicall_args: list[bytes], ) -> HexStr: tx_hash = await self._exchange_router.multicall( multicall_args, @@ -103,12 +112,15 @@ async def create_deposit_order(self, wallet: PrivateKeyWallet): # Minimum number of GM tokens we should expect min_market_tokens = await self._estimate_deposit() + base_fee_per_gas = (await Block[self.network_type].latest()).base_fee_per_gas + assert base_fee_per_gas, "base fee must be set" + # Giving a 10% buffer here execution_fee = int( - get_execution_fee( - self._gas_limits, - self._gas_limits_order_type, - self._connection.eth.gas_price + await get_execution_fee( + self._datastore, + await get_gas_limits(self._datastore, "deposit"), + base_fee_per_gas, ) * self.execution_buffer ) @@ -120,22 +132,22 @@ async def create_deposit_order(self, wallet: PrivateKeyWallet): # build swap paths for long/short deposit self._determine_swap_paths() - arguments = ( - user_wallet_address, - eth_zero_address, - ui_ref_address, - self.market_key, - self.initial_long_token, - self.initial_short_token, - self.long_token_swap_path, - self.short_token_swap_path, - min_market_tokens, - should_unwrap_native_token, - execution_fee, - callback_gas_limit + arguments = CreateDepositParams( + receiver=user_wallet_address, + callback_contract=eth_zero_address, + ui_fee_receiver=ui_ref_address, + market=self.market_key, + initial_long_token=self.initial_long_token, + initial_short_token=self.initial_short_token, + long_token_swap_path=self.long_token_swap_path, + short_token_swap_path=self.short_token_swap_path, + min_market_tokens=min_market_tokens, + should_unwrap_native_token=should_unwrap_native_token, + execution_fee=execution_fee, + callback_gas_limit=callback_gas_limit ) - multicall_args: list = [] + multicall_args: list[bytes] = [] wnt_amount = 0 # Send long side of deposit if more than 0 tokens @@ -184,7 +196,6 @@ async def create_deposit_order(self, wallet: PrivateKeyWallet): wallet, int(wnt_amount + execution_fee), multicall_args, - self._gas_limits ) def _check_initial_tokens(self): diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py index d25d9e8..2007cff 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py @@ -1,3 +1,4 @@ +from .gas import get_gas_limits from .hashing import create_hash_string, create_hash from .scaling import apply_factor from .swap import determine_swap_route @@ -9,5 +10,6 @@ "create_hash_string", "create_hash", "determine_swap_route", + "get_gas_limits", "get_tokens_address_dict", ] diff --git a/packages/eth_protocols/src/eth_protocols/gmx/gas.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/gas.py similarity index 61% rename from packages/eth_protocols/src/eth_protocols/gmx/gas.py rename to packages/eth_protocols/src/eth_protocols/gmx/utils/gas.py index acec251..23d01aa 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/gas.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/gas.py @@ -1,11 +1,13 @@ -from .datastore import Datastore -from .keys import ( +from typing import Literal + +from ..datastore import Datastore +from ..keys import ( decrease_order_gas_limit_key, increase_order_gas_limit_key, execution_gas_fee_base_amount_key, execution_gas_fee_multiplier_key, single_swap_gas_limit_key, swap_order_gas_limit_key, deposit_gas_limit_key, withdraw_gas_limit_key ) -from .utils import apply_factor +from . import apply_factor async def get_execution_fee(datastore: Datastore, estimated_gas_limit: int, gas_price: int) -> float: @@ -31,7 +33,13 @@ async def get_execution_fee(datastore: Datastore, estimated_gas_limit: int, gas_ return adjusted_gas_limit * gas_price -async def get_gas_limits(datastore: Datastore) -> dict[str, int]: +async def get_gas_limits( + datastore: Datastore, + name: Literal[ + "deposit", "withdraw", "single_swap", "swap_order", "increase_order", + "decrease_order", "estimated_fee_base_gas_limit", "estimated_fee_multiplier_factor" + ], +) -> int: """ Given a Web3 contract object of the datstore, return a dictionary with the uncalled gas limits that correspond to various operations that will require the execution fee to calculated for. @@ -42,15 +50,16 @@ async def get_gas_limits(datastore: Datastore) -> dict[str, int]: contract connection. """ gas_limits: dict[str, int] = { - "deposit": await datastore.get_uint(deposit_gas_limit_key()), - "withdraw": await datastore.get_uint(withdraw_gas_limit_key()), - "single_swap": await datastore.get_uint(single_swap_gas_limit_key()), - "swap_order": await datastore.get_uint(swap_order_gas_limit_key()), - "increase_order": await datastore.get_uint(increase_order_gas_limit_key()), - "decrease_order": await datastore.get_uint(decrease_order_gas_limit_key()), - "estimated_fee_base_gas_limit": await datastore.get_uint( + "deposit": datastore.get_uint(deposit_gas_limit_key()), + "withdraw": datastore.get_uint(withdraw_gas_limit_key()), + "single_swap": datastore.get_uint(single_swap_gas_limit_key()), + "swap_order": datastore.get_uint(swap_order_gas_limit_key()), + "increase_order": datastore.get_uint(increase_order_gas_limit_key()), + "decrease_order": datastore.get_uint(decrease_order_gas_limit_key()), + "estimated_fee_base_gas_limit": datastore.get_uint( execution_gas_fee_base_amount_key()), - "estimated_fee_multiplier_factor": await datastore.get_uint( - execution_gas_fee_multiplier_key())} + "estimated_fee_multiplier_factor": datastore.get_uint( + execution_gas_fee_multiplier_key()) + } - return gas_limits + return await gas_limits[name] From 5475da4efde201c2ac4c91c78918073e39853a7a Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Sun, 24 Nov 2024 22:20:18 -0500 Subject: [PATCH 15/16] improve protocol support --- .../src/eth_protocols/gmx/datastore.py | 8 +- .../src/eth_protocols/gmx/exchange_router.py | 14 +- .../eth_protocols/gmx/executors/approve.py | 2 +- .../eth_protocols/gmx/executors/deposit.py | 9 +- .../src/eth_protocols/gmx/executors/order.py | 377 ++++++++++++++++++ .../src/eth_protocols/gmx/utils/__init__.py | 3 +- .../src/eth_protocols/uniswap_v2/factory.py | 6 +- 7 files changed, 404 insertions(+), 15 deletions(-) create mode 100644 packages/eth_protocols/src/eth_protocols/gmx/executors/order.py diff --git a/packages/eth_protocols/src/eth_protocols/gmx/datastore.py b/packages/eth_protocols/src/eth_protocols/gmx/datastore.py index dac5043..1607614 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/datastore.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/datastore.py @@ -1,5 +1,7 @@ from typing import Literal +from eth_typing import HexAddress +from eth_rpc.types.primitives import bytes32 from pydantic import BaseModel, Field, PrivateAttr from eth_typeshed.gmx import GMXEnvironment, Datastore as DatastoreContract @@ -12,11 +14,15 @@ class Datastore(BaseModel): _environment: GMXEnvironment = PrivateAttr() _contract: DatastoreContract = PrivateAttr() + @property + def address(self) -> HexAddress: + return self._environment.datastore + def model_post_init(self, __context): super().model_post_init(__context) self._environment = GMXEnvironment.get_environment(self.network) self._contract = DatastoreContract[self.network_type](address=self._environment.datastore) - async def get_uint(self, key: str) -> int: + async def get_uint(self, key: bytes32) -> int: return await self._contract.get_uint(key).get() diff --git a/packages/eth_protocols/src/eth_protocols/gmx/exchange_router.py b/packages/eth_protocols/src/eth_protocols/gmx/exchange_router.py index fcc8952..5d5508e 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/exchange_router.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/exchange_router.py @@ -4,7 +4,7 @@ from eth_rpc.networks import Arbitrum, Avalanche from eth_typeshed.gmx import GMXEnvironment, ExchangeRouter as ExchangeRouterContract from eth_typeshed.gmx.exchange_router import CreateDepositParams -from eth_typing import HexAddress +from eth_typing import HexAddress, HexStr from pydantic import BaseModel, Field, PrivateAttr from .loaders import MarketsLoader @@ -27,17 +27,17 @@ def model_post_init(self, __context): async def load_markets(self): self.all_markets_info = MarketsLoader(network=self.network).get_available_markets() - def encode_send_tokens(self, token_address: HexAddress, amount: int): + def encode_send_tokens(self, token_address: HexAddress, amount: int) -> bytes: return self._contract.send_tokens( token_address, - '0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55', - amount + HexAddress(HexStr('0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55')), + amount, ).encode() - def encode_send_wnt(self, amount: int): + def encode_send_wnt(self, amount: int) -> bytes: return self._contract.send_wnt( - '0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55', - amount + HexAddress(HexStr('0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55')), + amount, ).encode() def encode_create_deposit(self, params: CreateDepositParams): diff --git a/packages/eth_protocols/src/eth_protocols/gmx/executors/approve.py b/packages/eth_protocols/src/eth_protocols/gmx/executors/approve.py index 6139ddd..3ab08b7 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/executors/approve.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/executors/approve.py @@ -41,7 +41,7 @@ async def check_if_approved( spender=spender, amount=amount, ) - ).execute(wallet, max_fee_per_gas=int(max_fee_per_gas)) + ).execute(wallet, max_fee_per_gas=max_fee_per_gas) print("Txn submitted!") print("Check status: https://arbiscan.io/tx/{}".format(txn_hash)) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py b/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py index c5450cf..acadbef 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/executors/deposit.py @@ -13,12 +13,12 @@ from ..loaders import MarketsLoader from ..synthetics_reader import SyntheticsReader -from .approve import check_if_approved from ..utils.gas import get_execution_fee from ..types import MarketInfo from ..exchange_router import ExchangeRouter from ..utils import determine_swap_route, get_gas_limits from ..datastore import Datastore +from .approve import check_if_approved class Deposit(BaseModel): @@ -64,6 +64,10 @@ async def check_for_approval(self, wallet: PrivateKeyWallet): """ spender = self._reader._environment.synthetics_router + if not self.max_fee_per_gas: + await self.load() + assert self.max_fee_per_gas + if self.long_token_amount > 0: await check_if_approved( wallet, @@ -112,7 +116,8 @@ async def create_deposit_order(self, wallet: PrivateKeyWallet): # Minimum number of GM tokens we should expect min_market_tokens = await self._estimate_deposit() - base_fee_per_gas = (await Block[self.network_type].latest()).base_fee_per_gas + network = self.network_type + base_fee_per_gas = (await Block[network].latest()).base_fee_per_gas # type: ignore[valid-type] assert base_fee_per_gas, "base fee must be set" # Giving a 10% buffer here diff --git a/packages/eth_protocols/src/eth_protocols/gmx/executors/order.py b/packages/eth_protocols/src/eth_protocols/gmx/executors/order.py new file mode 100644 index 0000000..06714b8 --- /dev/null +++ b/packages/eth_protocols/src/eth_protocols/gmx/executors/order.py @@ -0,0 +1,377 @@ +from abc import ABC, abstractmethod +from typing import Literal + +from eth_rpc import Block, PrivateKeyWallet +from eth_rpc.utils import to_checksum +from eth_rpc.networks import Arbitrum, Avalanche +from eth_typing import ChecksumAddress, HexAddress, HexStr +from hexbytes import HexBytes +from pydantic import BaseModel, PrivateAttr + +from eth_typeshed.gmx.synthetics_reader.enums import OrderType, DecreasePositionSwapType +from eth_typeshed.gmx.synthetics_reader.schemas import ExecutionPriceParams, PriceProps + +from ..loaders import MarketsLoader +from ..synthetics_reader import PRECISION, SyntheticsReader +from ..types import OraclePrice, MarketInfo +from ..exchange_router import ExchangeRouter +from ..datastore import Datastore +from ..utils import get_execution_fee, get_gas_limits +from .approve import check_if_approved + + +class OrderT(ABC, BaseModel): + network: Literal["arbitrum", "avalanche"] + + market_key: str + collateral_address: HexAddress + index_token_address: HexAddress + is_long: bool + size_delta: float + initial_collateral_delta_amount: int + slippage_percent: float + swap_path: list[HexAddress] + max_fee_per_gas: int | None = None + debug_mode: bool = False + auto_cancel: bool = False + execution_buffer: float = 1.3 + + _datastore: Datastore = PrivateAttr() + _reader: SyntheticsReader = PrivateAttr() + _exchange_router: ExchangeRouter = PrivateAttr() + _market_loader: MarketsLoader = PrivateAttr() + + @abstractmethod + async def estimated_swap_output(self, market: MarketInfo, token_in: HexAddress, amount_in: int) -> dict: + pass + + @property + def network_type(self) -> type[Arbitrum] | type[Avalanche]: + return Arbitrum if self.network == "arbitrum" else Avalanche + + def model_post_init(self, __context): + super().model_post_init(__context) + self._market_loader = MarketsLoader(network=self.network) + self._exchange_router = ExchangeRouter(network=self.network) + self._datastore = Datastore(network=self.network) + + async def load(self): + if self.max_fee_per_gas is None: + block = await Block[self.network_type].latest() + self.max_fee_per_gas = block['baseFeePerGas'] * 1.35 + self._is_swap = False + + @abstractmethod + async def determine_gas_limits(self): + pass + + async def check_for_approval(self, wallet: PrivateKeyWallet): + """ + Check for Approval + """ + spender = self._exchange_router._contract.address + + await check_if_approved( + wallet, + spender, + self.collateral_address, + self.initial_collateral_delta_amount, + self.max_fee_per_gas, + approve=True, + ) + + async def _submit_transaction( + self, + wallet: PrivateKeyWallet, + multicall_args: list[bytes], + **kwargs, + ) -> HexStr: + """ + Submit Transaction + """ + tx_hash = await self._exchange_router.multicall( + multicall_args, + wallet, + **kwargs, + ) + return tx_hash + + def _get_prices( + self, + decimals: float, + prices: dict, + is_open: bool = False, + is_close: bool = False, + is_swap: bool = False, + ): + """ + Get Prices + """ + price = sum( + prices[self.index_token_address].max_price_full + prices[self.index_token_address].min_price_full + ) / 2 + + # Depending on if open/close & long/short, we need to account for + # slippage in a different way + if is_open: + if self.is_long: + slippage = str( + int(float(price) + float(price) * self.slippage_percent) + ) + else: + slippage = str( + int(float(price) - float(price) * self.slippage_percent) + ) + elif is_close: + if self.is_long: + slippage = str( + int(float(price) - float(price) * self.slippage_percent) + ) + else: + slippage = str( + int(float(price) + float(price) * self.slippage_percent) + ) + else: + slippage = 0 + + acceptable_price_in_usd = ( + int(slippage) * 10 ** (decimals - PRECISION) + ) + + return price, int(slippage), acceptable_price_in_usd + + async def order_builder(self, wallet: PrivateKeyWallet, is_open=False, is_close=False, is_swap=False) -> HexStr: + """ + Create Order + """ + + await self.determine_gas_limits() + await self.load() + assert self.max_fee_per_gas, "max fee per gas must be set" + + execution_fee = int( + await get_execution_fee( + self._datastore, + await get_gas_limits(self._datastore, "increase_order"), + self.max_fee_per_gas, + ) + ) + + # Dont need to check approval when closing + if not is_close and not self.debug_mode: + await self.check_for_approval(wallet) + + execution_fee = int(execution_fee * self.execution_buffer) + + await self._market_loader.load() + markets = await self._market_loader.info + initial_collateral_delta_amount = self.initial_collateral_delta_amount + prices: dict[ChecksumAddress, OraclePrice] = await self._reader.get_recent_prices() + size_delta_price_price_impact = self.size_delta + + # when decreasing size delta must be negative + if is_close: + size_delta_price_price_impact = size_delta_price_price_impact * -1 + + callback_gas_limit = 0 + min_output_amount = 0 + + if is_open: + order_type = OrderType.MarketIncrease + elif is_close: + order_type = OrderType.MarketDecrease + elif is_swap: + order_type = OrderType.MarketSwap + + # Estimate amount of token out using a reader function, necessary + # for multi swap + estimated_output = await self.estimated_swap_output( + markets[self.swap_path[0]], + self.collateral_address, + initial_collateral_delta_amount + ) + + # this var will help to calculate the cost gas depending on the + # operation + self._get_limits_order_type = self._gas_limits['single_swap'] + if len(self.swap_path) > 1: + estimated_output = self.estimated_swap_output( + markets[self.swap_path[1]], + "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + int( + estimated_output[ + "out_token_amount" + ] - estimated_output[ + "out_token_amount" + ] * self.slippage_percent + ) + ) + self._get_limits_order_type = self._gas_limits['swap_order'] + + min_output_amount = estimated_output["out_token_amount"] - \ + estimated_output["out_token_amount"] * self.slippage_percent + + decrease_position_swap_type = DecreasePositionSwapType.NoSwap + + should_unwrap_native_token = True + referral_code = HexBytes( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ) + user_wallet_address = wallet.address + eth_zero_address = "0x0000000000000000000000000000000000000000" + ui_ref_address = "0x0000000000000000000000000000000000000000" + try: + gmx_market_address = to_checksum(self.market_key) + except AttributeError: + gmx_market_address = to_checksum(self.market_key) + + # parameters using to calculate execution price + execution_price_parameters = ExecutionPriceParams( + data_store=self._datastore._contract.address, + market_key=self.market_key, + index_token_price=PriceProps( + max=int(prices[self.index_token_address].max_price_full), + min=int(prices[self.index_token_address].min_price_full) + ), + position_size_in_usd=0, + position_size_in_tokens=0, + size_delta_usd=size_delta_price_price_impact, + is_long=self.is_long + ) + decimals = markets[self.market_key]['market_metadata']['decimals'] + + price, acceptable_price, acceptable_price_in_usd = self._get_prices( + decimals, + prices, + is_open, + is_close, + is_swap + ) + + mark_price = 0 + + # mark price should be actual price when opening + if is_open: + mark_price = int(price) + + # Market address and acceptable price not important for swap + if is_swap: + acceptable_price = 0 + gmx_market_address = "0x0000000000000000000000000000000000000000" + + execution_price_and_price_impact_dict = await self._reader.get_execution_price( + execution_price_parameters, + decimals + ) + + # Prevent txn from being submitted if execution price falls outside acceptable + if is_open: + if self.is_long: + if execution_price_and_price_impact_dict.execution_price > acceptable_price_in_usd: + raise Exception("Execution price falls outside acceptable price!") + elif not self.is_long: + if execution_price_and_price_impact_dict.execution_price < acceptable_price_in_usd: + raise Exception("Execution price falls outside acceptable price!") + elif is_close: + if self.is_long: + if execution_price_and_price_impact_dict.execution_price < acceptable_price_in_usd: + raise Exception("Execution price falls outside acceptable price!") + elif not self.is_long: + if execution_price_and_price_impact_dict.execution_price > acceptable_price_in_usd: + raise Exception("Execution price falls outside acceptable price!") + + user_wallet_address = to_checksum(user_wallet_address) + cancellation_receiver = user_wallet_address + + eth_zero_address = to_checksum(eth_zero_address) + ui_ref_address = to_checksum(ui_ref_address) + collateral_address = to_checksum(self.collateral_address) + + auto_cancel = self.auto_cancel + + arguments = ( + ( + user_wallet_address, + cancellation_receiver, + eth_zero_address, + ui_ref_address, + gmx_market_address, + collateral_address, + self.swap_path + ), + ( + self.size_delta, + self.initial_collateral_delta_amount, + mark_price, + acceptable_price, + execution_fee, + callback_gas_limit, + int(min_output_amount) + ), + order_type, + decrease_position_swap_type, + self.is_long, + should_unwrap_native_token, + auto_cancel, + referral_code + ) + + # If the collateral is not native token (ie ETH/Arbitrum or AVAX/AVAX) + # need to send tokens to vault + + value_amount = execution_fee + if self.collateral_address != '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1' and not is_close: + + multicall_args = [ + HexBytes(self._send_wnt(value_amount)), + HexBytes( + self._send_tokens( + self.collateral_address, + initial_collateral_delta_amount + ) + ), + HexBytes(self._create_order(arguments)) + ] + + else: + + # send start token and execute fee if token is ETH or AVAX + if is_open or is_swap: + + value_amount = initial_collateral_delta_amount + execution_fee + + multicall_args = [ + HexBytes(self._send_wnt(value_amount)), + HexBytes(self._create_order(arguments)) + ] + + return await self._submit_transaction( + wallet, value_amount, multicall_args, self._gas_limits + ) + + def _create_order(self, arguments): + """ + Create Order + """ + return self._exchange_router.encode_create_order( + arguments, + ) + + def _send_tokens(self, amount): + """ + Send tokens + """ + return self._exchange_router.encode_send_tokens( + self.collateral_address, + "0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5", + amount + ) + + def _send_wnt(self, amount): + """ + Send WNT + """ + return self._exchange_router.encode_send_wnt( + "0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5", + amount + ) diff --git a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py index 2007cff..b393ed2 100644 --- a/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py +++ b/packages/eth_protocols/src/eth_protocols/gmx/utils/__init__.py @@ -1,4 +1,4 @@ -from .gas import get_gas_limits +from .gas import get_gas_limits, get_execution_fee from .hashing import create_hash_string, create_hash from .scaling import apply_factor from .swap import determine_swap_route @@ -11,5 +11,6 @@ "create_hash", "determine_swap_route", "get_gas_limits", + "get_execution_fee", "get_tokens_address_dict", ] diff --git a/packages/eth_protocols/src/eth_protocols/uniswap_v2/factory.py b/packages/eth_protocols/src/eth_protocols/uniswap_v2/factory.py index 3476fe2..789646b 100644 --- a/packages/eth_protocols/src/eth_protocols/uniswap_v2/factory.py +++ b/packages/eth_protocols/src/eth_protocols/uniswap_v2/factory.py @@ -14,12 +14,12 @@ class V2Factory(BaseModel): pairs: ClassVar[ dict[tuple[Network, ChecksumAddress, ChecksumAddress], "ChecksumAddress"] ] = Field({}) - _network: ClassVar[Network | None] = None + _network: ClassVar[type[Network] | None] = None _contract: UniswapV2Factory = PrivateAttr() address: HexAddress - def __class_getitem__(cls, network: Network): # type: ignore + def __class_getitem__(cls, network: type[Network]): # type: ignore cls._network = network return cls @@ -28,7 +28,7 @@ def model_post_init(self, __context): @classmethod async def load_pair(cls, token0: HexAddress, token1: HexAddress) -> "V2Pair": - network: Network = cls._network or get_current_network() + network: type[Network] = cls._network or get_current_network() token0 = to_checksum(token0) token1 = to_checksum(token1) key = (network, token0, token1) From dff31dd7c9cd3a92d9818a07b71ca44dbbce6004 Mon Sep 17 00:00:00 2001 From: johnny-emp Date: Sun, 24 Nov 2024 22:20:27 -0500 Subject: [PATCH 16/16] exchange router --- packages/eth_typeshed/src/eth_typeshed/gmx/exchange_router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eth_typeshed/src/eth_typeshed/gmx/exchange_router.py b/packages/eth_typeshed/src/eth_typeshed/gmx/exchange_router.py index fe29a98..630e007 100644 --- a/packages/eth_typeshed/src/eth_typeshed/gmx/exchange_router.py +++ b/packages/eth_typeshed/src/eth_typeshed/gmx/exchange_router.py @@ -1,9 +1,9 @@ from typing import Annotated +from eth_typing import HexAddress from eth_rpc import ProtocolBase, ContractFunc from eth_rpc.types import primitives, Name, NoArgs, Struct - class Props(Struct): min: primitives.uint256 max: primitives.uint256 @@ -274,7 +274,7 @@ class ExchangeRouter(ProtocolBase): send_tokens: Annotated[ ContractFunc[ - tuple[primitives.address, primitives.address, primitives.uint256], + tuple[HexAddress, HexAddress, primitives.uint256], None ], Name("sendTokens"),