├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── PKG-INFO ├── README.md ├── README_V3.md ├── apexpro ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-39.pyc │ ├── constants.cpython-39.pyc │ ├── errors.cpython-39.pyc │ ├── eth.cpython-39.pyc │ ├── exceptions.cpython-39.pyc │ ├── http_private.cpython-39.pyc │ ├── http_public.cpython-39.pyc │ └── models.cpython-39.pyc ├── _websocket_stream.py ├── abi │ ├── erc20.json │ └── starkware-perpetuals.json ├── constants.py ├── errors.py ├── eth.py ├── eth_signing │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-39.pyc │ │ ├── eth_prive_action.cpython-39.pyc │ │ ├── onboarding_action.cpython-39.pyc │ │ ├── sign_off_chain_action.cpython-39.pyc │ │ ├── signers.cpython-39.pyc │ │ └── util.cpython-39.pyc │ ├── eth_prive_action.py │ ├── onboarding_action.py │ ├── sign_off_chain_action.py │ ├── signers.py │ └── util.py ├── exceptions.py ├── helpers │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-39.pyc │ │ └── request_helpers.cpython-39.pyc │ ├── db.py │ ├── request_helpers.py │ ├── requests.py │ └── util.py ├── http_private.py ├── http_private_sign.py ├── http_private_stark_key_sign.py ├── http_private_v3.py ├── http_public.py ├── libzklink_sdk-arm.dylib ├── libzklink_sdk-x86.dylib ├── libzklink_sdk.dylib ├── libzklink_sdk.so ├── models.py ├── starkex │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-39.pyc │ │ ├── constants.cpython-39.pyc │ │ └── helpers.cpython-39.pyc │ ├── conditional_transfer.py │ ├── constants.py │ ├── helpers.py │ ├── order.py │ ├── signable.py │ ├── starkex_resources │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-39.pyc │ │ │ ├── cpp_signature.cpython-39.pyc │ │ │ ├── math_utils.cpython-39.pyc │ │ │ ├── proxy.cpython-39.pyc │ │ │ └── python_signature.cpython-39.pyc │ │ ├── cpp_signature.py │ │ ├── math_utils.py │ │ ├── pedersen_params.json │ │ ├── proxy.py │ │ └── python_signature.py │ ├── transfer.py │ └── withdrawal.py ├── websocket_api.py ├── zklink_sdk-arm.py ├── zklink_sdk-pc.py ├── zklink_sdk-x86.py ├── zklink_sdk.dll └── zklink_sdk.py ├── pyproject.toml ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── account_value.py ├── account_value_ws.py ├── constants.py ├── demo_create_order_v3.py ├── demo_deposit.py ├── demo_private.py ├── demo_private_apikeys_v3.py ├── demo_private_v2.py ├── demo_private_v3.py ├── demo_public.py ├── demo_public_v2.py ├── demo_public_v3.py ├── demo_register.py ├── demo_register_mul_address.py ├── demo_register_mul_address_v3_step1.py ├── demo_register_mul_address_v3_step2.py ├── demo_register_v2.py ├── demo_register_v3.py ├── demo_sign.py ├── demo_stark_key_sign.py ├── demo_stark_key_sign_v2.py ├── demo_transfer_v3.py ├── demo_ws.py ├── demo_ws_depthdata.py ├── demo_ws_v3.py └── test_onboarding.py └── tox.ini /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.0.0] - 2022-06-03 9 | 10 | ### Added 11 | 12 | - The `apexpro` module. 13 | - MANIFEST, README, LICENSE, and CHANGELOG files. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright for portions of aprxpro are held by verata-veritatis, 2020. Since 4 | version 2.0.0, all other copyright are held by apex.exchange, 2022. 5 | 6 | Copyright (c) 2020 verata-veritatis 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include CHANGELOG.md 4 | include setup.cfg 5 | include tox.ini 6 | include pyproject.toml 7 | recursive-include apexpro *.json 8 | recursive-include apexpro *.dylib 9 | recursive-include apexpro *.dll 10 | recursive-include apexpro *.so -------------------------------------------------------------------------------- /PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.4 2 | Name: ApexOmni 3 | Version: 3.1.0 4 | Summary: Python3 ApexOmni HTTP/WebSocket API Connector 5 | Home-page: https://github.com/ApeX-Protocol/apexpro-openapi 6 | Author: Dexter Dickinson 7 | License: MIT License 8 | Keywords: apex omni api connector 9 | Platform: UNKNOWN 10 | Classifier: Development Status :: 4 - Beta 11 | Classifier: Intended Audience :: Developers 12 | Classifier: Topic :: Software Development :: Libraries :: Python Modules 13 | Classifier: License :: OSI Approved :: MIT License 14 | Classifier: Programming Language :: Python :: 3.6 15 | Classifier: Programming Language :: Python :: 3.7 16 | Classifier: Programming Language :: Python :: 3.8 17 | Classifier: Programming Language :: Python :: 3.9 18 | Classifier: Programming Language :: Python :: 3.10 19 | Classifier: Programming Language :: Python :: 3.11 20 | Classifier: Programming Language :: Python :: 3.12 21 | Requires-Python: >=3.6 22 | Description-Content-Type: text/markdown 23 | License-File: LICENSE 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apex omni 2 | 3 | Official Python3 API connector for Apex omni's HTTP and WebSockets APIs. 4 | You can get Api information from 5 | [OpenApi-SDK](https://api-docs.pro.apex.exchange/#introduction) 6 | 7 | ## About 8 | Put simply, `apex omni` is the official lightweight one-stop-shop module for the Apex omni HTTP and WebSocket APIs. 9 | 10 | ## Development 11 | - `apex omni` is being actively developed, and new API changes should arrive on `apex omni` very quickly. `apex omni` uses `requests` and `websocket` for its methods, alongside other built-in modules. Anyone is welcome to branch/fork the repository and add their own upgrades. If you think you've made substantial improvements to the module, submit a pull request and we'll gladly take a look. 12 | - If the user's computer using arm chip, change (libzklink_sdk-arm.dylib and zklink_sdk-arm.py) to (libzklink_sdk.dylib, zklink_sdk.py) and replace old (libzklink_sdk.dylib and zklink_sdk.py) in the directory ./apexpro/ and ./test/ 13 | - If the user's computer using x86 chip, change (libzklink_sdk-x86.dylib and zklink_sdk-x86.py) to (libzklink_sdk.dylib, zklink_sdk.py) and replace old (libzklink_sdk.dylib and zklink_sdk.py) in the directory ./apexpro/ and ./test/ 14 | - If the user's computer OS is windows, copy zklink_sdk.dll in the directory ./apexpro/ and ./test/. Change zklink_sdk-pc.py to zklink_sdk.py and replace and zklink_sdk.py in the directory ./apexpro/ and ./test/ 15 | - If the user's computer OS is Linux and x86 chip, copy zklink_sdk.so in the directory ./apexpro/ and ./test/. Change zklink_sdk-pc.py to zklink_sdk.py and replace and zklink_sdk.py in the directory ./apexpro/ and ./test/ 16 | 17 | ## Installation 18 | `apex omni` supports Python versions from 3.6 to 3.12. The module can be installed manually or via [apexomni-arm](https://pypi.org/project/apexomni-arm/) or [apexomni-x86](https://pypi.org/project/apexomni-x86/) with `pip`: 19 | ``` 20 | pip3 install apexomni-arm 21 | pip3 install apexomni-x86-mac 22 | pip3 install apexomni-x86-windows-linux 23 | ``` 24 | ## New Basic Usage V3 25 | You can create an HTTP session for Inverse on APEX_OMNI_HTTP_TEST or APEX_OMNI_HTTP_MAIN: 26 | ```python 27 | from apexpro.constants import APEX_OMNI_HTTP_TEST 28 | from apexpro.http_public import HttpPublic 29 | 30 | client = HttpPublic(APEX_OMNI_HTTP_TEST) 31 | ``` 32 | ### Public endpoints V3 33 | 34 | You can get no authentication information from public endpoints. 35 | Please refer to [demo_public_v3](https://github.com/ApeX-Protocol/apexpro-openapi/blob/main/tests/demo_public_v3.py) 36 | 37 | The V3 version supports USDT symbols. Users need to request the configs_v3() interface to obtain the configuration of symbols. 38 | ```python 39 | client = HttpPublic(APEX_OMNI_HTTP_MAIN) 40 | print(client.configs_v3()) 41 | print(client.klines_v3(symbol="ETHUSDT",interval=5,start=1718358480, end=1718950620, limit=5)) 42 | print(client.depth_v3(symbol="BTCUSDT")) 43 | print(client.trades_v3(symbol="BTCUSDT")) 44 | print(client.klines_v3(symbol="BTCUSDT",interval="15")) 45 | print(client.ticker_v3(symbol="BTCUSDT")) 46 | print(client.history_funding_v3(symbol="BTC-USDT")) 47 | ``` 48 | 49 | ### Register OMNI method V3 50 | - You can get zkKeys from client.derive_zk_key(), for regiter-user, create-order or transfer and withdraw. 51 | - If the user wants to trade OMNI's symbols, needs to call the register_user_v3() interface to register a OMNI account. 52 | - If the user has previously registered a pro apex account using the v1 or v2 interface, you also need to use the register_user_v3() 53 | interface to register again. 54 | - You need to call client.configs_v3() after init client. 55 | - You can get apiKey and accountId for private Api 56 | - After call register_user_v3(), the user must call change_pub_key_v3() to complete register v3 account. 57 | - Since the register_user_v3 is a non-blocking process, you need to sleep for 10 sec before call the change_pub_key_v3() action. 58 | Please refer to [demo_register_v3](https://github.com/ApeX-Protocol/apexpro-openapi/blob/main/tests/demo_register_v3.py) 59 | 60 | 61 | ```python 62 | from apexpro.constants import APEX_OMNI_HTTP_MAIN, NETWORKID_OMNI_MAIN_ARB, NETWORKID_MAIN 63 | 64 | print("Hello, Apex Omni") 65 | priKey = "your eth private key" 66 | 67 | client = HttpPrivate_v3(APEX_OMNI_HTTP_MAIN, network_id=NETWORKID_MAIN, eth_private_key=priKey) 68 | configs = client.configs_v3() 69 | 70 | zkKeys = client.derive_zk_key(client.default_address) 71 | print(zkKeys) 72 | 73 | nonceRes = client.generate_nonce_v3(refresh="false", l2Key=zkKeys['l2Key'],ethAddress=client.default_address, chainId=NETWORKID_OMNI_MAIN_ARB) 74 | 75 | regRes = client.register_user_v3(nonce=nonceRes['data']['nonce'],l2Key=zkKeys['l2Key'], seeds=zkKeys['seeds'],ethereum_address=client.default_address) 76 | 77 | 78 | key = regRes['data']['apiKey']['key'] 79 | secret = regRes['data']['apiKey']['secret'] 80 | passphrase = regRes['data']['apiKey']['passphrase'] 81 | 82 | time.sleep(10) 83 | accountRes = client.get_account_v3() 84 | print(accountRes) 85 | 86 | 87 | #back zkKeys, apiKey,and accountId for private Api or create-oreder transfer or withdraw 88 | 89 | print(regRes['data']['account']['id']) 90 | print(regRes['data']['apiKey']) 91 | 92 | changeRes = client.change_pub_key_v3(chainId=NETWORKID_OMNI_MAIN_ARB, seeds=zkKeys.get('seeds'), ethPrivateKey=priKey, zkAccountId = accountRes.get('spotAccount').get('zkAccountId'), subAccountId = accountRes.get('spotAccount').get('defaultSubAccountId'), 93 | newPkHash = zkKeys.get('pubKeyHash'), nonce= accountRes.get('spotAccount').get('nonce'), l2Key= zkKeys.get('l2Key')) 94 | print(changeRes) 95 | 96 | time.sleep(10) 97 | accountRes = client.get_account_v3() 98 | print(accountRes) 99 | ``` 100 | 101 | ### Private endpoints V3 102 | Users need to request the configs_v3() and get_account_v3() interface to obtain the configuration of Account. 103 | 104 | some authentication information is required to access private endpoints. 105 | Please refer to [demo_private_v3](https://github.com/ApeX-Protocol/apexpro-openapi/blob/main/tests/demo_private_v3.py) 106 | 107 | ```python 108 | from apexpro.constants import APEX_OMNI_HTTP_MAIN, \ 109 | NETWORKID_OMNI_MAIN_ARB 110 | 111 | print("Hello, Apex Omni") 112 | # need api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase} for private api 113 | 114 | key = 'your apiKey-key from register V3' 115 | secret = 'your apiKey-secret from register V3' 116 | passphrase = 'your apiKey-passphrase from register V3' 117 | 118 | client = HttpPrivate_v3(APEX_OMNI_HTTP_MAIN, network_id=NETWORKID_OMNI_MAIN_ARB, api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase}) 119 | configs = client.configs_v3() 120 | 121 | userRes = client.get_user_v3() 122 | print(userRes) 123 | 124 | 125 | accountRes = client.get_account_v3() 126 | print(accountRes) 127 | 128 | accountBalanceRes = client.get_account_balance_v3() 129 | print(accountBalanceRes) 130 | 131 | fillsRes = client.fills_v3(limit=100,page=0,symbol="BTC-USDT",side="BUY",token="USDT") 132 | print(fillsRes) 133 | 134 | transfersRes = client.transfers_v3(limit=100) 135 | print(transfersRes) 136 | 137 | transferRes = client.transfer_v3(ids='586213648326721628') 138 | print(transferRes) 139 | 140 | transfersRes = client.contract_transfers_v3(limit=100) 141 | print(transfersRes) 142 | 143 | transferRes = client.contract_transfer_v3(ids='588301879870489180') 144 | print(transferRes) 145 | 146 | #deleteOrderRes = client.delete_order_v3(id="588302655921587036") 147 | #print(deleteOrderRes) 148 | 149 | #deleteOrderRes = client.delete_order_by_client_order_id_v3(id="123456") 150 | #print(deleteOrderRes) 151 | 152 | openOrdersRes = client.open_orders_v3() 153 | print(openOrdersRes) 154 | 155 | deleteOrdersRes = client.delete_open_orders_v3(symbol="BTC-USDT",) 156 | print(deleteOrdersRes) 157 | 158 | historyOrdersRes = client.history_orders_v3(token='USDT') 159 | print(historyOrdersRes) 160 | 161 | getOrderRes = client.get_order_v3(id="123456") 162 | print(getOrderRes) 163 | 164 | getOrderRes = client.get_order_by_client_order_id_v3(id="123456") 165 | print(getOrderRes) 166 | 167 | fundingRes = client.funding_v3(limit=100) 168 | print(fundingRes) 169 | 170 | historicalPnlRes = client.historical_pnl_v3(limit=100) 171 | print(historicalPnlRes) 172 | 173 | yesterdayPnlRes = client.yesterday_pnl_v3() 174 | print(yesterdayPnlRes) 175 | 176 | historyValueRes = client.history_value_v3() 177 | print(historyValueRes) 178 | 179 | setInitialMarginRateRes = client.set_initial_margin_rate_v3(symbol="BTC-USDT",initialMarginRate="0.05") 180 | print(setInitialMarginRateRes) 181 | 182 | ``` 183 | 184 | ### zkKey sign withdraw or transfer method 185 | Users need to request the configs_v3() and get_account_v3() interface to obtain the configuration of Account. 186 | 187 | Several endpoints require a seeds and l2Key signature authentication, namely as following: 188 | - create_withdrawal_v3() to withdraw or fast withdraw 189 | - create_transfer_out_v3() to transfer asset from spot account to contract account 190 | - create_contract_transfer_out_v3() to transfer asset from contract account to spot account 191 | 192 | Please refer to [demo_transfer_v3](https://github.com/ApeX-Protocol/apexpro-openapi/blob/main/tests/demo_transfer_v3.py) 193 | 194 | ```python 195 | key = 'your apiKey-key from register' 196 | secret = 'your apiKey-secret from register' 197 | passphrase = 'your apiKey-passphrase from register' 198 | 199 | seeds = 'your zk seeds from register' 200 | l2Key = 'your l2Key seeds from register' 201 | 202 | client = HttpPrivateSign(APEX_OMNI_HTTP_TEST, network_id=NETWORKID_TEST, 203 | zk_seeds=seeds,zk_l2Key=l2Key, 204 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 205 | configs = client.configs_v3() 206 | accountData = client.get_account_v3() 207 | 208 | #smple1 withdraw 209 | #createWithdrawRes = client.create_withdrawal_v3(amount='3',asset='USDT', toChainId=3) 210 | #print(createWithdrawRes) 211 | 212 | #smple2 fast withdraw 213 | #withdraw_feeRes = client.withdraw_fee_v3(amount="3",chainIds="3",tokenId='140') 214 | #print(withdraw_feeRes) 215 | #createWithdrawRes = client.create_withdrawal_v3(amount='3',asset='USDT', toChainId=3, fee=withdraw_feeRes.get('data').get('withdrawFeeAndPoolBalances')[0].get('fee'), isFastWithdraw=True) 216 | #print(createWithdrawRes) 217 | 218 | #smple3 transfer_out 219 | #createTransferRes = client.create_transfer_out_v3(amount='3.4359738368',asset='USDT') 220 | #print(createTransferRes) 221 | 222 | #smple4 contract transfer_out 223 | createContractTransferRes = client.create_contract_transfer_out_v3(amount='3.4359738368',asset='USDT') 224 | print(createContractTransferRes) 225 | 226 | ``` 227 | 228 | 229 | ### zkKey sign create order method 230 | Users need to request the configs_v3() and get_account_v3() interface to obtain the configuration of Account. 231 | Several endpoints require a seeds and l2Key signature authentication, namely as following: 232 | - create_order_v3() to create order 233 | 234 | ```python 235 | from apexpro.constants import NETWORKID_TEST, APEX_OMNI_HTTP_TEST 236 | 237 | print("Hello, Apex omni") 238 | 239 | key = 'your apiKey-key from register' 240 | secret = 'your apiKey-secret from register' 241 | passphrase = 'your apiKey-passphrase from register' 242 | 243 | seeds = 'your zk seeds from register' 244 | l2Key = 'your l2Key seeds from register' 245 | 246 | 247 | client = HttpPrivateSign(APEX_OMNI_HTTP_TEST, network_id=NETWORKID_TEST, 248 | zk_seeds=seeds,zk_l2Key=l2Key, 249 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 250 | configs = client.configs_v3() 251 | accountData = client.get_account_v3() 252 | 253 | 254 | currentTime = time.time() 255 | createOrderRes = client.create_order_v3(symbol="BTC-USDT", side="SELL", 256 | type="MARKET", size="0.001", timestampSeconds= currentTime, 257 | price="60000") 258 | print(createOrderRes) 259 | 260 | # sample6 261 | # Create a TP/SL order 262 | # first, Set a slippage to get an acceptable slPrice or tpPrice 263 | #slippage is recommended to be greater than 0.1 264 | # when buying, the price = price*(1 + slippage). when selling, the price = price*(1 - slippage) 265 | slippage = decimal.Decimal("-0.1") 266 | slPrice = decimal.Decimal("58000") * (decimal.Decimal("1") + slippage) 267 | tpPrice = decimal.Decimal("79000") * (decimal.Decimal("1") - slippage) 268 | 269 | createOrderRes = client.create_order_v3(symbol="BTC-USDT", side="BUY", 270 | type="LIMIT", size="0.01", 271 | price="65000", 272 | isOpenTpslOrder=True, 273 | isSetOpenSl=True, 274 | slPrice=slPrice, 275 | slSide="SELL", 276 | slSize="0.01", 277 | slTriggerPrice="58000", 278 | isSetOpenTp=True, 279 | tpPrice=tpPrice, 280 | tpSide="SELL", 281 | tpSize="0.01", 282 | tpTriggerPrice="79000", 283 | ) 284 | print(createOrderRes) 285 | 286 | print("end, Apexpro") 287 | 288 | ``` 289 | 290 | ### WebSocket 291 | To see comprehensive examples of how to subscribe topics from websockets. 292 | Please refer to [demo_ws_v3](https://github.com/ApeX-Protocol/apexpro-openapi/blob/main/tests/demo_ws_v3.py) 293 | 294 | 295 | ```python 296 | from time import sleep 297 | 298 | from apexpro.constants import APEX_OMNI_WS_MAIN 299 | from apexpro.websocket_api import WebSocket 300 | 301 | key = 'your apiKey-key from register V3' 302 | secret = 'your apiKey-secret from register V3' 303 | passphrase = 'your apiKey-passphrase from register V3' 304 | 305 | 306 | # Connect with authentication! 307 | # APEX_OMNI_WS_MAIN for mainnet, APEX_OMNI_WS_TEST for testnet 308 | ws_client = WebSocket( 309 | endpoint=APEX_OMNI_WS_MAIN, 310 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}, 311 | ) 312 | 313 | def handle_account(message): 314 | print(message) 315 | contents_data = message["contents"] 316 | print(len(contents_data)) 317 | 318 | def h1(message): 319 | print(1, message) 320 | def h2(message): 321 | print(2, message) 322 | def h3(message): 323 | print(3, message) 324 | def h4(message): 325 | print(4, message) 326 | 327 | #ws_client.depth_stream(h1,'BTCUSDT',25) 328 | #ws_client.ticker_stream(h2,'BTCUSDT') 329 | ws_client.trade_stream(h3,'BTCUSDT') 330 | ws_client.klines_stream(h4,'BTCUSDT',1) 331 | ws_client.account_info_stream_v3(handle_account) 332 | 333 | 334 | while True: 335 | # Run your main trading logic here. 336 | sleep(1) 337 | 338 | -------------------------------------------------------------------------------- /README_V3.md: -------------------------------------------------------------------------------- 1 | # apex omni 2 | 3 | Official Python3 API connector for Apex omni's HTTP and WebSockets APIs. 4 | You can get Api information from 5 | [OpenApi-SDK](https://api-docs.pro.apex.exchange/#introduction) 6 | 7 | ## About 8 | Put simply, `apex omni` is the official lightweight one-stop-shop module for the Apex omni HTTP and WebSocket APIs. 9 | 10 | ## Development 11 | - `apex omni` is being actively developed, and new API changes should arrive on `apex omni` very quickly. `apex omni` uses `requests` and `websocket` for its methods, alongside other built-in modules. Anyone is welcome to branch/fork the repository and add their own upgrades. If you think you've made substantial improvements to the module, submit a pull request and we'll gladly take a look. 12 | - If the user's computer using arm chip, change (libzklink_sdk-arm.dylib and zklink_sdk-arm.py) to (libzklink_sdk.dylib, zklink_sdk.py) and replace old (libzklink_sdk.dylib and zklink_sdk.py) in the directory ./apexpro/ and ./test/ 13 | - If the user's computer using x86 chip, change (libzklink_sdk-x86.dylib and zklink_sdk-x86.py) to (libzklink_sdk.dylib, zklink_sdk.py) and replace old (libzklink_sdk.dylib and zklink_sdk.py) in the directory ./apexpro/ and ./test/ 14 | - If the user's computer OS is windows, copy zklink_sdk.dll in the directory ./apexpro/ and ./test/. Change zklink_sdk-pc.py to zklink_sdk.py and replace and zklink_sdk.py in the directory ./apexpro/ and ./test/ 15 | - If the user's computer OS is Linux and x86 chip, copy zklink_sdk.so in the directory ./apexpro/ and ./test/. Change zklink_sdk-pc.py to zklink_sdk.py and replace and zklink_sdk.py in the directory ./apexpro/ and ./test/ 16 | 17 | ## Installation 18 | `apex omni` supports Python versions from 3.6 to 3.12. The module can be installed manually or via [apexomni-arm](https://pypi.org/project/apexomni-arm/) or [apexomni-x86](https://pypi.org/project/apexomni-x86/) with `pip`: 19 | ``` 20 | pip3 install apexomni-arm 21 | pip3 install apexomni-x86-mac 22 | pip3 install apexomni-x86-windows-linux 23 | ``` 24 | ## New Basic Usage V3 25 | You can create an HTTP session for Inverse on APEX_OMNI_HTTP_TEST or APEX_OMNI_HTTP_MAIN: 26 | ```python 27 | from apexpro.constants import APEX_OMNI_HTTP_TEST 28 | from apexpro.http_public import HttpPublic 29 | 30 | client = HttpPublic(APEX_OMNI_HTTP_TEST) 31 | ``` 32 | ### Public endpoints V3 33 | 34 | You can get no authentication information from public endpoints. 35 | Please refer to [demo_public_v3](https://github.com/ApeX-Protocol/apexpro-openapi/blob/main/tests/demo_public_v3.py) 36 | 37 | The V3 version supports USDT symbols. Users need to request the configs_v3() interface to obtain the configuration of symbols. 38 | ```python 39 | client = HttpPublic(APEX_OMNI_HTTP_MAIN) 40 | print(client.configs_v3()) 41 | print(client.klines_v3(symbol="ETHUSDT",interval=5,start=1718358480, end=1718950620, limit=5)) 42 | print(client.depth_v3(symbol="BTCUSDT")) 43 | print(client.trades_v3(symbol="BTCUSDT")) 44 | print(client.klines_v3(symbol="BTCUSDT",interval="15")) 45 | print(client.ticker_v3(symbol="BTCUSDT")) 46 | print(client.history_funding_v3(symbol="BTC-USDT")) 47 | ``` 48 | 49 | ### Register OMNI method V3 50 | - You can get zkKeys from client.derive_zk_key(), for regiter-user, create-order or transfer and withdraw. 51 | - If the user wants to trade OMNI's symbols, needs to call the register_user_v3() interface to register a OMNI account. 52 | - If the user has previously registered a pro apex account using the v1 or v2 interface, you also need to use the register_user_v3() 53 | interface to register again. 54 | - You need to call client.configs_v3() after init client. 55 | - You can get apiKey and accountId for private Api 56 | - After call register_user_v3(), the user must call change_pub_key_v3() to complete register v3 account. 57 | - Since the register_user_v3 is a non-blocking process, you need to sleep for 10 sec before call the change_pub_key_v3() action. 58 | Please refer to [demo_register_v3](https://github.com/ApeX-Protocol/apexpro-openapi/blob/main/tests/demo_register_v3.py) 59 | 60 | 61 | ```python 62 | from apexpro.constants import APEX_OMNI_HTTP_MAIN, NETWORKID_OMNI_MAIN_ARB, NETWORKID_MAIN 63 | 64 | print("Hello, Apex Omni") 65 | priKey = "your eth private key" 66 | 67 | client = HttpPrivate_v3(APEX_OMNI_HTTP_MAIN, network_id=NETWORKID_MAIN, eth_private_key=priKey) 68 | configs = client.configs_v3() 69 | 70 | zkKeys = client.derive_zk_key(client.default_address) 71 | print(zkKeys) 72 | 73 | nonceRes = client.generate_nonce_v3(refresh="false", l2Key=zkKeys['l2Key'],ethAddress=client.default_address, chainId=NETWORKID_OMNI_MAIN_ARB) 74 | 75 | regRes = client.register_user_v3(nonce=nonceRes['data']['nonce'],l2Key=zkKeys['l2Key'], seeds=zkKeys['seeds'],ethereum_address=client.default_address) 76 | 77 | 78 | key = regRes['data']['apiKey']['key'] 79 | secret = regRes['data']['apiKey']['secret'] 80 | passphrase = regRes['data']['apiKey']['passphrase'] 81 | 82 | time.sleep(10) 83 | accountRes = client.get_account_v3() 84 | print(accountRes) 85 | 86 | 87 | #back zkKeys, apiKey,and accountId for private Api or create-oreder transfer or withdraw 88 | 89 | print(regRes['data']['account']['id']) 90 | print(regRes['data']['apiKey']) 91 | 92 | changeRes = client.change_pub_key_v3(chainId=NETWORKID_OMNI_MAIN_ARB, seeds=zkKeys.get('seeds'), ethPrivateKey=priKey, zkAccountId = accountRes.get('spotAccount').get('zkAccountId'), subAccountId = accountRes.get('spotAccount').get('defaultSubAccountId'), 93 | newPkHash = zkKeys.get('pubKeyHash'), nonce= accountRes.get('spotAccount').get('nonce'), l2Key= zkKeys.get('l2Key')) 94 | print(changeRes) 95 | 96 | time.sleep(10) 97 | accountRes = client.get_account_v3() 98 | print(accountRes) 99 | ``` 100 | 101 | ### Private endpoints V3 102 | Users need to request the configs_v3() and get_account_v3() interface to obtain the configuration of Account. 103 | 104 | some authentication information is required to access private endpoints. 105 | Please refer to [demo_private_v3](https://github.com/ApeX-Protocol/apexpro-openapi/blob/main/tests/demo_private_v3.py) 106 | 107 | ```python 108 | from apexpro.constants import APEX_OMNI_HTTP_MAIN, \ 109 | NETWORKID_OMNI_MAIN_ARB 110 | 111 | print("Hello, Apex Omni") 112 | # need api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase} for private api 113 | 114 | key = 'your apiKey-key from register V3' 115 | secret = 'your apiKey-secret from register V3' 116 | passphrase = 'your apiKey-passphrase from register V3' 117 | 118 | client = HttpPrivate_v3(APEX_OMNI_HTTP_MAIN, network_id=NETWORKID_OMNI_MAIN_ARB, api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase}) 119 | configs = client.configs_v3() 120 | 121 | userRes = client.get_user_v3() 122 | print(userRes) 123 | 124 | 125 | accountRes = client.get_account_v3() 126 | print(accountRes) 127 | 128 | accountBalanceRes = client.get_account_balance_v3() 129 | print(accountBalanceRes) 130 | 131 | fillsRes = client.fills_v3(limit=100,page=0,symbol="BTC-USDT",side="BUY",token="USDT") 132 | print(fillsRes) 133 | 134 | transfersRes = client.transfers_v3(limit=100) 135 | print(transfersRes) 136 | 137 | transferRes = client.transfer_v3(ids='586213648326721628') 138 | print(transferRes) 139 | 140 | transfersRes = client.contract_transfers_v3(limit=100) 141 | print(transfersRes) 142 | 143 | transferRes = client.contract_transfer_v3(ids='588301879870489180') 144 | print(transferRes) 145 | 146 | #deleteOrderRes = client.delete_order_v3(id="588302655921587036") 147 | #print(deleteOrderRes) 148 | 149 | #deleteOrderRes = client.delete_order_by_client_order_id_v3(id="123456") 150 | #print(deleteOrderRes) 151 | 152 | openOrdersRes = client.open_orders_v3() 153 | print(openOrdersRes) 154 | 155 | deleteOrdersRes = client.delete_open_orders_v3(symbol="BTC-USDT",) 156 | print(deleteOrdersRes) 157 | 158 | historyOrdersRes = client.history_orders_v3(token='USDT') 159 | print(historyOrdersRes) 160 | 161 | getOrderRes = client.get_order_v3(id="123456") 162 | print(getOrderRes) 163 | 164 | getOrderRes = client.get_order_by_client_order_id_v3(id="123456") 165 | print(getOrderRes) 166 | 167 | fundingRes = client.funding_v3(limit=100) 168 | print(fundingRes) 169 | 170 | historicalPnlRes = client.historical_pnl_v3(limit=100) 171 | print(historicalPnlRes) 172 | 173 | yesterdayPnlRes = client.yesterday_pnl_v3() 174 | print(yesterdayPnlRes) 175 | 176 | historyValueRes = client.history_value_v3() 177 | print(historyValueRes) 178 | 179 | setInitialMarginRateRes = client.set_initial_margin_rate_v3(symbol="BTC-USDT",initialMarginRate="0.05") 180 | print(setInitialMarginRateRes) 181 | 182 | ``` 183 | 184 | ### zkKey sign withdraw or transfer method 185 | Users need to request the configs_v3() and get_account_v3() interface to obtain the configuration of Account. 186 | 187 | Several endpoints require a seeds and l2Key signature authentication, namely as following: 188 | - create_withdrawal_v3() to withdraw or fast withdraw 189 | - create_transfer_out_v3() to transfer asset from spot account to contract account 190 | - create_contract_transfer_out_v3() to transfer asset from contract account to spot account 191 | 192 | Please refer to [demo_transfer_v3](https://github.com/ApeX-Protocol/apexpro-openapi/blob/main/tests/demo_transfer_v3.py) 193 | 194 | ```python 195 | key = 'your apiKey-key from register' 196 | secret = 'your apiKey-secret from register' 197 | passphrase = 'your apiKey-passphrase from register' 198 | 199 | seeds = 'your zk seeds from register' 200 | l2Key = 'your l2Key seeds from register' 201 | 202 | client = HttpPrivateSign(APEX_OMNI_HTTP_TEST, network_id=NETWORKID_TEST, 203 | zk_seeds=seeds,zk_l2Key=l2Key, 204 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 205 | configs = client.configs_v3() 206 | accountData = client.get_account_v3() 207 | 208 | #smple1 withdraw 209 | #createWithdrawRes = client.create_withdrawal_v3(amount='3',asset='USDT', toChainId=3) 210 | #print(createWithdrawRes) 211 | 212 | #smple2 fast withdraw 213 | #withdraw_feeRes = client.withdraw_fee_v3(amount="3",chainIds="3",tokenId='140') 214 | #print(withdraw_feeRes) 215 | #createWithdrawRes = client.create_withdrawal_v3(amount='3',asset='USDT', toChainId=3, fee=withdraw_feeRes.get('data').get('withdrawFeeAndPoolBalances')[0].get('fee'), isFastWithdraw=True) 216 | #print(createWithdrawRes) 217 | 218 | #smple3 transfer_out 219 | #createTransferRes = client.create_transfer_out_v3(amount='3.4359738368',asset='USDT') 220 | #print(createTransferRes) 221 | 222 | #smple4 contract transfer_out 223 | createContractTransferRes = client.create_contract_transfer_out_v3(amount='3.4359738368',asset='USDT') 224 | print(createContractTransferRes) 225 | 226 | ``` 227 | 228 | 229 | ### zkKey sign create order method 230 | Users need to request the configs_v3() and get_account_v3() interface to obtain the configuration of Account. 231 | Several endpoints require a seeds and l2Key signature authentication, namely as following: 232 | - create_order_v3() to create order 233 | 234 | ```python 235 | from apexpro.constants import NETWORKID_TEST, APEX_OMNI_HTTP_TEST 236 | 237 | print("Hello, Apex omni") 238 | 239 | key = 'your apiKey-key from register' 240 | secret = 'your apiKey-secret from register' 241 | passphrase = 'your apiKey-passphrase from register' 242 | 243 | seeds = 'your zk seeds from register' 244 | l2Key = 'your l2Key seeds from register' 245 | 246 | 247 | client = HttpPrivateSign(APEX_OMNI_HTTP_TEST, network_id=NETWORKID_TEST, 248 | zk_seeds=seeds,zk_l2Key=l2Key, 249 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 250 | configs = client.configs_v3() 251 | accountData = client.get_account_v3() 252 | 253 | 254 | currentTime = time.time() 255 | createOrderRes = client.create_order_v3(symbol="BTC-USDT", side="SELL", 256 | type="MARKET", size="0.001", timestampSeconds= currentTime, 257 | price="60000") 258 | print(createOrderRes) 259 | 260 | # sample6 261 | # Create a TP/SL order 262 | # first, Set a slippage to get an acceptable slPrice or tpPrice 263 | #slippage is recommended to be greater than 0.1 264 | # when buying, the price = price*(1 + slippage). when selling, the price = price*(1 - slippage) 265 | slippage = decimal.Decimal("-0.1") 266 | slPrice = decimal.Decimal("58000") * (decimal.Decimal("1") + slippage) 267 | tpPrice = decimal.Decimal("79000") * (decimal.Decimal("1") - slippage) 268 | 269 | createOrderRes = client.create_order_v3(symbol="BTC-USDT", side="BUY", 270 | type="LIMIT", size="0.01", 271 | price="65000", 272 | isOpenTpslOrder=True, 273 | isSetOpenSl=True, 274 | slPrice=slPrice, 275 | slSide="SELL", 276 | slSize="0.01", 277 | slTriggerPrice="58000", 278 | isSetOpenTp=True, 279 | tpPrice=tpPrice, 280 | tpSide="SELL", 281 | tpSize="0.01", 282 | tpTriggerPrice="79000", 283 | ) 284 | print(createOrderRes) 285 | 286 | print("end, Apexpro") 287 | 288 | ``` 289 | 290 | ### WebSocket 291 | To see comprehensive examples of how to subscribe topics from websockets. 292 | Please refer to [demo_ws_v3](https://github.com/ApeX-Protocol/apexpro-openapi/blob/main/tests/demo_ws_v3.py) 293 | 294 | 295 | ```python 296 | from time import sleep 297 | 298 | from apexpro.constants import APEX_OMNI_WS_MAIN 299 | from apexpro.websocket_api import WebSocket 300 | 301 | key = 'your apiKey-key from register V3' 302 | secret = 'your apiKey-secret from register V3' 303 | passphrase = 'your apiKey-passphrase from register V3' 304 | 305 | 306 | # Connect with authentication! 307 | # APEX_OMNI_WS_MAIN for mainnet, APEX_OMNI_WS_TEST for testnet 308 | ws_client = WebSocket( 309 | endpoint=APEX_OMNI_WS_MAIN, 310 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}, 311 | ) 312 | 313 | def handle_account(message): 314 | print(message) 315 | contents_data = message["contents"] 316 | print(len(contents_data)) 317 | 318 | def h1(message): 319 | print(1, message) 320 | def h2(message): 321 | print(2, message) 322 | def h3(message): 323 | print(3, message) 324 | def h4(message): 325 | print(4, message) 326 | 327 | #ws_client.depth_stream(h1,'BTCUSDT',25) 328 | #ws_client.ticker_stream(h2,'BTCUSDT') 329 | ws_client.trade_stream(h3,'BTCUSDT') 330 | ws_client.klines_stream(h4,'BTCUSDT',1) 331 | ws_client.account_info_stream_v3(handle_account) 332 | 333 | 334 | while True: 335 | # Run your main trading logic here. 336 | sleep(1) 337 | 338 | -------------------------------------------------------------------------------- /apexpro/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/__pycache__/constants.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/__pycache__/constants.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/__pycache__/errors.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/__pycache__/errors.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/__pycache__/eth.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/__pycache__/eth.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/__pycache__/exceptions.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/__pycache__/exceptions.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/__pycache__/http_private.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/__pycache__/http_private.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/__pycache__/http_public.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/__pycache__/http_public.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/__pycache__/models.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/__pycache__/models.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/_websocket_stream.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import hashlib 3 | 4 | import websocket 5 | import threading 6 | import time 7 | import json 8 | import hmac 9 | import logging 10 | import re 11 | import copy 12 | from . import HTTP, APEX_HTTP_MAIN 13 | from .constants import APEX_WS_MAIN 14 | from .helpers.request_helpers import generate_now 15 | 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | PRIVATE_REQUEST_PATH = '/ws/accounts' 20 | PRIVATE_WSS = "/realtime_private?v=2" 21 | PUBLIC_WSS = "/realtime_public?v=2" 22 | 23 | class _WebSocketManager: 24 | def __init__(self, callback_function, endpoint="", api_key_credentials=None, 25 | ping_interval=15, ping_timeout=None, 26 | restart_on_error=True, trace_logging=False): 27 | 28 | if endpoint.endswith('/'): 29 | self.endpoint = endpoint[:-1] 30 | # Set the endpoint. 31 | if endpoint is None: 32 | self.endpoint = APEX_WS_MAIN 33 | else: 34 | self.endpoint = endpoint 35 | 36 | self.api_key_credentials = api_key_credentials 37 | 38 | self.callback = callback_function 39 | 40 | # Setup the callback directory following the format: 41 | # { 42 | # "topic_name": function 43 | # } 44 | self.callback_directory = {} 45 | 46 | # Set ping settings. 47 | self.ping_interval = ping_interval 48 | self.ping_timeout = ping_timeout 49 | 50 | # Other optional data handling settings. 51 | self.handle_error = restart_on_error 52 | self.timer = None 53 | self.r_timer = None 54 | 55 | # Enable websocket-client's trace logging for extra debug information 56 | # on the websocket connection, including the raw sent & recv messages 57 | websocket.enableTrace(trace_logging) 58 | 59 | # Set initial state, initialize dictionary and connect. 60 | self._reset() 61 | 62 | def sign( 63 | self, 64 | request_path, 65 | method, 66 | iso_timestamp, 67 | ): 68 | 69 | message_string = ( 70 | iso_timestamp + 71 | method + 72 | request_path 73 | ) 74 | hashed = hmac.new( 75 | base64.standard_b64encode( 76 | (self.api_key_credentials['secret']).encode(encoding='utf-8'), 77 | ), 78 | msg=message_string.encode(encoding='utf-8'), 79 | digestmod=hashlib.sha256, 80 | ) 81 | return base64.standard_b64encode(hashed.digest()).decode() 82 | 83 | def runTimer(self): 84 | time_stamp = generate_now() 85 | ping = json.dumps({ 86 | "op": "ping", 87 | "args": [str(time_stamp)] 88 | }) 89 | self.ws.send(ping) 90 | #print("send ping:" + ping) 91 | 92 | def _on_open(self): 93 | """ 94 | Log WS open. 95 | """ 96 | 97 | logger.debug(f"WebSocket opened.") 98 | 99 | def _on_message(self, message): 100 | """ 101 | Parse incoming messages. 102 | """ 103 | self.callback(json.loads(message)) 104 | 105 | def _connect(self, url): 106 | """ 107 | Open websocket in a thread. 108 | """ 109 | self.private_websocket = True if url.__contains__("private") else False 110 | 111 | time_stamp = generate_now() 112 | self.ws = websocket.WebSocketApp( 113 | url=url + '×tamp=' + str(time_stamp), 114 | on_message=lambda ws, msg: self._on_message(msg), 115 | on_close=self._on_close(), 116 | on_open=self._on_open(), 117 | on_error=lambda ws, err: self._on_error(err) 118 | ) 119 | 120 | # Setup the thread running WebSocketApp. 121 | self.wst = threading.Thread(target=lambda: self.ws.run_forever( 122 | ping_interval=self.ping_interval, 123 | ping_timeout=self.ping_timeout 124 | )) 125 | 126 | # Configure as daemon; start. 127 | self.wst.daemon = True 128 | self.wst.start() 129 | 130 | # Attempt to connect for X seconds. 131 | retries = 10 132 | while retries > 0 and (not self.ws.sock or not self.ws.sock.connected): 133 | retries -= 1 134 | time.sleep(1) 135 | 136 | # If connection was not successful, raise error. 137 | if retries <= 0: 138 | self.exit() 139 | raise websocket.WebSocketTimeoutException("Connection failed.") 140 | 141 | # If given an api_key, authenticate. 142 | if self.api_key_credentials: 143 | self._auth(time_stamp) 144 | 145 | def _auth(self, time_stamp): 146 | """ 147 | Authorize websocket connection. 148 | """ 149 | 150 | signature = self.sign( 151 | request_path=PRIVATE_REQUEST_PATH, 152 | method='GET', 153 | iso_timestamp=str(time_stamp), 154 | ) 155 | 156 | 157 | req = { 158 | 'type': 'login', 159 | 'topics': ['ws_notify_v1', 'ws_accounts_v2', 'ws_zk_accounts_v3','ws_accounts_v1'], 160 | 'httpMethod': 'GET', 161 | 'requestPath': PRIVATE_REQUEST_PATH, 162 | 'apiKey': self.api_key_credentials['key'], 163 | 'passphrase': self.api_key_credentials['passphrase'], 164 | 'timestamp': time_stamp, 165 | 'signature': signature, 166 | } 167 | sendStr = \ 168 | { 169 | "op": "login", 170 | "args": [json.dumps(req)] 171 | } 172 | 173 | # Authenticate with API. 174 | self.ws.send( 175 | json.dumps(sendStr) 176 | ) 177 | 178 | def _on_error(self, error): 179 | """ 180 | Exit on errors and raise exception, or attempt reconnect. 181 | """ 182 | 183 | if not self.exited: 184 | logger.error(f"WebSocket encountered error: {error}.") 185 | self.exit() 186 | 187 | # Reconnect. 188 | if self.handle_error: 189 | self._reset() 190 | if self.private_websocket: 191 | self._connect(self.endpoint + PRIVATE_WSS) 192 | else: 193 | self._connect(self.endpoint + PUBLIC_WSS) 194 | 195 | def _on_close(self): 196 | """ 197 | Log WS close. 198 | """ 199 | logger.debug(f"WebSocket closed.") 200 | 201 | def _reset(self): 202 | """ 203 | Set state booleans and initialize dictionary. 204 | """ 205 | self.exited = False 206 | self.auth = False 207 | self.data = {} 208 | 209 | def exit(self): 210 | """ 211 | Closes the websocket connection. 212 | """ 213 | 214 | self.ws.close() 215 | while self.ws.sock: 216 | continue 217 | self.exited = True 218 | 219 | 220 | class _ApexWebSocketManager(_WebSocketManager): 221 | def __init__(self, **kwargs): 222 | super().__init__(self._handle_incoming_message, **kwargs) 223 | 224 | def subscribe(self, sendStr, topic, callback): 225 | """ 226 | Formats and sends the subscription message, given a topic. Saves the 227 | provided callback function, to be called by incoming messages. 228 | """ 229 | 230 | if self.private_websocket: 231 | # Spot private topics don't need a subscription message 232 | self._set_callback(topic, callback) 233 | return 234 | 235 | self.ws.send(sendStr) 236 | self._set_callback(topic, callback) 237 | 238 | def _handle_incoming_message(self, message): 239 | #print(message) 240 | def is_ping_message(): 241 | if type(message) == dict and message.get("op") == "ping": 242 | return True 243 | else: 244 | return False 245 | 246 | def is_auth_message(): 247 | if type(message) == dict and \ 248 | message.get("request") is not None and message.get("request").get("op") is not None and message.get("request").get("op") == "login": 249 | return True 250 | else: 251 | return False 252 | 253 | def is_subscription_message(): 254 | if type(message) == dict and \ 255 | message.get("request") is not None and message.get("request").get("op") is not None and message.get("request").get("op") == "subscribe": 256 | return True 257 | else: 258 | return False 259 | 260 | if is_ping_message(): 261 | time_stamp = generate_now() 262 | pong = json.dumps({ 263 | "op": "pong", 264 | "args": [str(time_stamp)] 265 | }) 266 | self.ws.send(pong) 267 | #print("send pong:" + pong) 268 | return 269 | 270 | # Check auth 271 | if is_auth_message(): 272 | # If we get successful spot auth, notify user 273 | if message.get("success") is not None and message.get("success") == "true": 274 | logger.debug(f"Authorization successful.") 275 | self.auth = True 276 | 277 | # Check subscription 278 | elif is_subscription_message(): 279 | # If we get successful spot subscription, notify user 280 | if message.get("success") is not None and message.get("success") == "true": 281 | logger.debug(f"Subscription successful.") 282 | 283 | 284 | else: # Standard topic push 285 | if message.get('topic') is not None and self.callback_directory.get(message.get('topic')) is not None: 286 | callback_function = self.callback_directory[message['topic'] ] 287 | callback_function(message) 288 | 289 | def _set_callback(self, topic, callback_function): 290 | self.callback_directory[topic] = callback_function 291 | 292 | def _get_callback(self, topic): 293 | return self.callback_directory[topic] 294 | 295 | def _pop_callback(self, topic): 296 | self.callback_directory.pop(topic) 297 | 298 | def _check_callback_directory(self, topics): 299 | for topic in topics: 300 | if topic in self.callback_directory: 301 | raise Exception(f"You have already subscribed to this topic: " 302 | f"{topic}") 303 | 304 | 305 | def _identify_ws_method(input_wss_url, wss_dictionary): 306 | """ 307 | This method matches the input_wss_url with a particular WSS method. This 308 | helps ensure that, when subscribing to a custom topic, the topic 309 | subscription message is sent down the correct WSS connection. 310 | """ 311 | path = re.compile("(wss://)?([^/\s]+)(.*)") 312 | input_wss_url_path = path.match(input_wss_url).group(3) 313 | for wss_url, function_call in wss_dictionary.items(): 314 | wss_url_path = path.match(wss_url).group(3) 315 | if input_wss_url_path == wss_url_path: 316 | return function_call 317 | 318 | 319 | def _find_index(source, target, key): 320 | """ 321 | Find the index in source list of the targeted ID. 322 | """ 323 | return next(i for i, j in enumerate(source) if j[key] == target[key]) 324 | 325 | 326 | def _make_public_kwargs(private_kwargs): 327 | public_kwargs = copy.deepcopy(private_kwargs) 328 | public_kwargs.pop("api_key_credentials", "") 329 | return public_kwargs 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | -------------------------------------------------------------------------------- /apexpro/abi/erc20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "_spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "_value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "_from", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "decimals", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "uint8" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": true, 96 | "inputs": [ 97 | { 98 | "name": "_owner", 99 | "type": "address" 100 | } 101 | ], 102 | "name": "balanceOf", 103 | "outputs": [ 104 | { 105 | "name": "balance", 106 | "type": "uint256" 107 | } 108 | ], 109 | "payable": false, 110 | "stateMutability": "view", 111 | "type": "function" 112 | }, 113 | { 114 | "constant": true, 115 | "inputs": [], 116 | "name": "symbol", 117 | "outputs": [ 118 | { 119 | "name": "", 120 | "type": "string" 121 | } 122 | ], 123 | "payable": false, 124 | "stateMutability": "view", 125 | "type": "function" 126 | }, 127 | { 128 | "constant": false, 129 | "inputs": [ 130 | { 131 | "name": "_to", 132 | "type": "address" 133 | }, 134 | { 135 | "name": "_value", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "transfer", 140 | "outputs": [ 141 | { 142 | "name": "", 143 | "type": "bool" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "nonpayable", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": true, 152 | "inputs": [ 153 | { 154 | "name": "_owner", 155 | "type": "address" 156 | }, 157 | { 158 | "name": "_spender", 159 | "type": "address" 160 | } 161 | ], 162 | "name": "allowance", 163 | "outputs": [ 164 | { 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "payable": false, 170 | "stateMutability": "view", 171 | "type": "function" 172 | }, 173 | { 174 | "payable": true, 175 | "stateMutability": "payable", 176 | "type": "fallback" 177 | }, 178 | { 179 | "anonymous": false, 180 | "inputs": [ 181 | { 182 | "indexed": true, 183 | "name": "owner", 184 | "type": "address" 185 | }, 186 | { 187 | "indexed": true, 188 | "name": "spender", 189 | "type": "address" 190 | }, 191 | { 192 | "indexed": false, 193 | "name": "value", 194 | "type": "uint256" 195 | } 196 | ], 197 | "name": "Approval", 198 | "type": "event" 199 | }, 200 | { 201 | "anonymous": false, 202 | "inputs": [ 203 | { 204 | "indexed": true, 205 | "name": "from", 206 | "type": "address" 207 | }, 208 | { 209 | "indexed": true, 210 | "name": "to", 211 | "type": "address" 212 | }, 213 | { 214 | "indexed": false, 215 | "name": "value", 216 | "type": "uint256" 217 | } 218 | ], 219 | "name": "Transfer", 220 | "type": "event" 221 | } 222 | ] 223 | -------------------------------------------------------------------------------- /apexpro/constants.py: -------------------------------------------------------------------------------- 1 | # ------------ API URLs ------------ 2 | APEX_HTTP_MAIN = 'https://pro.apex.exchange' 3 | APEX_HTTP_TEST = 'https://testnet.pro.apex.exchange' 4 | APEX_WS_MAIN = 'wss://quote.pro.apex.exchange' 5 | APEX_WS_TEST = 'wss://quote-qa.pro.apex.exchange' 6 | 7 | APEX_OMNI_HTTP_MAIN = 'https://omni.apex.exchange' 8 | APEX_OMNI_HTTP_TEST = 'https://qa.omni.apex.exchange' 9 | 10 | APEX_OMNI_WS_MAIN = 'wss://quote.omni.apex.exchange' 11 | APEX_OMNI_WS_TEST = 'wss://qa-quote.omni.apex.exchange' 12 | 13 | URL_SUFFIX = "/api" 14 | 15 | # ------------ register_env_id ------------ 16 | REGISTER_ENVID_MAIN = 1 17 | REGISTER_ENVID_TEST = 5 18 | 19 | # ------------ network_id ------------ 20 | NETWORKID_MAIN = 1 21 | NETWORKID_TEST = 11155111 22 | 23 | NETWORKID_OMNI_MAIN_ARB = 9 24 | NETWORKID_OMNI_TEST_BNB = 3 25 | NETWORKID_OMNI_TEST_BASE = 11 26 | 27 | # ------------ Signature Types ------------ 28 | SIGNATURE_TYPE_NO_PREPEND = 0 29 | SIGNATURE_TYPE_DECIMAL = 1 30 | SIGNATURE_TYPE_HEXADECIMAL = 2 31 | SIGNATURE_TYPE_PERSONAL = 3 32 | 33 | 34 | # ------------ Order Side ------------ 35 | ORDER_SIDE_BUY = 'BUY' 36 | ORDER_SIDE_SELL = 'SELL' 37 | 38 | 39 | # ------------ Assets ------------ 40 | ASSET_USDC = 'USDC' 41 | ASSET_BTC = 'BTC' 42 | ASSET_ETH = 'ETH' 43 | ASSET_LINK = 'LINK' 44 | ASSET_AAVE = 'AAVE' 45 | ASSET_DOGE = 'DOGE' 46 | ASSET_MATIC = 'MATIC' 47 | COLLATERAL_ASSET = ASSET_USDC 48 | 49 | ASSET_RESOLUTION = { 50 | ASSET_USDC: '1e6', 51 | } 52 | 53 | # ------------ Ethereum Transactions ------------ 54 | DEFAULT_GAS_AMOUNT = 250000 55 | DEFAULT_GAS_MULTIPLIER = 1.5 56 | DEFAULT_GAS_PRICE = 4000000000 57 | DEFAULT_GAS_PRICE_ADDITION = 3 58 | MAX_SOLIDITY_UINT = 115792089237316195423570985008687907853269984665640564039457584007913129639935 # noqa: E501 59 | 60 | 61 | COLLATERAL_TOKEN_DECIMALS = 6 62 | 63 | # ------------ Off-Chain Ethereum-Signed Actions ------------ 64 | OFF_CHAIN_ONBOARDING_ACTION = 'ApeX Onboarding' # action:ApeX Onboarding onlySignOn:https://pro.apex.exchange nonce:1188491033265307648 65 | OFF_CHAIN_KEY_DERIVATION_ACTION = 'L2 Key' #{"name": "ApeX","version": "1.0","envId": 1,"action": "L2 Key","onlySignOn": "https://pro.apex.exchange"} 66 | 67 | 68 | -------------------------------------------------------------------------------- /apexpro/errors.py: -------------------------------------------------------------------------------- 1 | class ApexproError(Exception): 2 | """Base error class for all exceptions raised in this library. 3 | Will never be raised naked; more specific subclasses of this exception will 4 | be raised when appropriate.""" 5 | 6 | 7 | class ApexproApiError(ApexproError): 8 | 9 | def __init__(self, response): 10 | self.status_code = response.status_code 11 | try: 12 | self.msg = response.json() 13 | except ValueError: 14 | self.msg = response.text 15 | self.response = response 16 | self.request = getattr(response, 'request', None) 17 | 18 | def __str__(self): 19 | return self.__repr__() 20 | 21 | def __repr__(self): 22 | return 'ApexproApiError(status_code={}, response={})'.format( 23 | self.status_code, 24 | self.msg, 25 | ) 26 | 27 | 28 | class TransactionReverted(ApexproError): 29 | 30 | def __init__(self, tx_receipt): 31 | self.tx_receipt = tx_receipt 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /apexpro/eth_signing/__init__.py: -------------------------------------------------------------------------------- 1 | from apexpro.eth_signing.eth_prive_action import SignEthPrivateAction 2 | from apexpro.eth_signing.onboarding_action import SignOnboardingAction 3 | from apexpro.eth_signing.signers import SignWithKey 4 | from apexpro.eth_signing.signers import SignWithWeb3 5 | -------------------------------------------------------------------------------- /apexpro/eth_signing/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/eth_signing/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/eth_signing/__pycache__/eth_prive_action.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/eth_signing/__pycache__/eth_prive_action.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/eth_signing/__pycache__/onboarding_action.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/eth_signing/__pycache__/onboarding_action.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/eth_signing/__pycache__/sign_off_chain_action.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/eth_signing/__pycache__/sign_off_chain_action.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/eth_signing/__pycache__/signers.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/eth_signing/__pycache__/signers.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/eth_signing/__pycache__/util.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/eth_signing/__pycache__/util.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/eth_signing/eth_prive_action.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | from apexpro.eth_signing import util 4 | from apexpro.eth_signing.sign_off_chain_action import SignOffChainAction 5 | 6 | EIP712_ETH_PRIVATE_ACTION_STRUCT_STRING = [ 7 | {'type': 'string', 'name': 'method'}, 8 | {'type': 'string', 'name': 'requestPath'}, 9 | {'type': 'string', 'name': 'body'}, 10 | {'type': 'string', 'name': 'timestamp'}, 11 | ] 12 | EIP712_ETH_PRIVATE_ACTION_STRUCT_STRING_STRING = ( 13 | 'apex(' + 14 | 'string method,' + 15 | 'string requestPath,' + 16 | 'string body,' + 17 | 'string timestamp' + 18 | ')' 19 | ) 20 | EIP712_STRUCT_NAME = 'apex' 21 | 22 | 23 | class SignEthPrivateAction(SignOffChainAction): 24 | 25 | def get_eip712_struct(self): 26 | return EIP712_ETH_PRIVATE_ACTION_STRUCT_STRING 27 | 28 | def get_eip712_struct_name(self): 29 | return EIP712_STRUCT_NAME 30 | 31 | def get_eip712_message( 32 | self, 33 | method, 34 | request_path, 35 | body, 36 | timestamp, 37 | ): 38 | return super(SignEthPrivateAction, self).get_eip712_message( 39 | method=method, 40 | requestPath=request_path, 41 | body=body, 42 | timestamp=timestamp, 43 | ) 44 | 45 | def get_hash( 46 | self, 47 | method, 48 | request_path, 49 | body, 50 | timestamp, 51 | ): 52 | data = [ 53 | [ 54 | 'bytes32', 55 | 'bytes32', 56 | 'bytes32', 57 | 'bytes32', 58 | 'bytes32', 59 | ], 60 | [ 61 | util.hash_string( 62 | EIP712_ETH_PRIVATE_ACTION_STRUCT_STRING_STRING, 63 | ), 64 | util.hash_string(method), 65 | util.hash_string(request_path), 66 | util.hash_string(body), 67 | util.hash_string(timestamp), 68 | ], 69 | ] 70 | struct_hash = Web3.solidityKeccak(*data) 71 | return self.get_eip712_hash(struct_hash) 72 | -------------------------------------------------------------------------------- /apexpro/eth_signing/onboarding_action.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | from apexpro.constants import REGISTER_ENVID_MAIN 4 | from apexpro.eth_signing import util 5 | from apexpro.eth_signing.sign_off_chain_action import SignOffChainAction 6 | 7 | # On mainnet, include an extra onlySignOn parameter. 8 | EIP712_ONBOARDING_ACTION_STRUCT = [ 9 | {'type': 'string', 'name': 'action'}, 10 | {'type': 'string', 'name': 'onlySignOn'}, 11 | {'type': 'string', 'name': 'nonce'}, 12 | ] 13 | EIP712_ONBOARDING_ACTION_STRUCT_STRING = ( 14 | 'ApeX(' + 15 | 'string action,' + 16 | 'string onlySignOn,' + 17 | 'string nonce' + 18 | ')' 19 | ) 20 | EIP712_ONBOARDING_ACTION_STRUCT_TESTNET = [ 21 | {'type': 'string', 'name': 'action'}, 22 | ] 23 | EIP712_ONBOARDING_ACTION_STRUCT_STRING_TESTNET = ( 24 | 'apex(' + 25 | 'string action' + 26 | ')' 27 | ) 28 | EIP712_STRUCT_NAME = 'ApeX' 29 | 30 | ONLY_SIGN_ON_DOMAIN_MAINNET = 'https://pro.apex.exchange' 31 | 32 | 33 | class SignOnboardingAction(SignOffChainAction): 34 | 35 | def get_eip712_struct(self): 36 | # On mainnet, include an extra onlySignOn parameter. 37 | #if self.network_id == REGISTER_ENVID_MAIN: 38 | return EIP712_ONBOARDING_ACTION_STRUCT 39 | #else: 40 | # return EIP712_ONBOARDING_ACTION_STRUCT_TESTNET 41 | 42 | def get_eip712_struct_name(self): 43 | return EIP712_STRUCT_NAME 44 | 45 | def get_eip712_message( 46 | self, 47 | **message, 48 | ): 49 | eip712_message = super(SignOnboardingAction, self).get_eip712_message( 50 | **message, 51 | ) 52 | 53 | # On mainnet, include an extra onlySignOn parameter. 54 | #if self.network_id == REGISTER_ENVID_MAIN: 55 | eip712_message['message']['onlySignOn'] = ( 56 | 'https://pro.apex.exchange' 57 | ) 58 | 59 | return eip712_message 60 | 61 | def get_hash( 62 | self, 63 | action, 64 | nonce 65 | ): 66 | # On mainnet, include an extra onlySignOn parameter. 67 | #if self.network_id == REGISTER_ENVID_MAIN: 68 | eip712_struct_str = EIP712_ONBOARDING_ACTION_STRUCT_STRING 69 | #else: 70 | # eip712_struct_str = EIP712_ONBOARDING_ACTION_STRUCT_STRING_TESTNET 71 | 72 | data = [ 73 | [ 74 | 'bytes32', 75 | 'bytes32', 76 | ], 77 | [ 78 | util.hash_string(eip712_struct_str), 79 | util.hash_string(action) 80 | ], 81 | ] 82 | 83 | # On mainnet, include an extra onlySignOn parameter. 84 | #if self.network_id == REGISTER_ENVID_MAIN: 85 | data[0].append('bytes32') 86 | data[1].append(util.hash_string(ONLY_SIGN_ON_DOMAIN_MAINNET)) 87 | 88 | data[0].append('bytes32') 89 | data[1].append(util.hash_string(nonce)) 90 | 91 | struct_hash = Web3.solidityKeccak(*data) 92 | return self.get_eip712_hash(struct_hash) 93 | -------------------------------------------------------------------------------- /apexpro/eth_signing/sign_off_chain_action.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | from apexpro.eth_signing import util 4 | 5 | DOMAIN = 'ApeX' 6 | VERSION = '1.0' 7 | EIP712_DOMAIN_STRING_NO_CONTRACT = ( 8 | 'EIP712Domain(' + 9 | 'string name,' + 10 | 'string version,' + 11 | 'uint256 chainId' + 12 | ')' 13 | ) 14 | 15 | 16 | class SignOffChainAction(object): 17 | 18 | def __init__(self, signer, network_id): 19 | self.signer = signer 20 | self.network_id = network_id 21 | 22 | def get_hash(self, **message): 23 | raise NotImplementedError 24 | 25 | def get_eip712_struct(self): 26 | raise NotImplementedError 27 | 28 | def get_eip712_struct_name(self): 29 | raise NotImplementedError 30 | 31 | def sign( 32 | self, 33 | signer_address, 34 | **message, 35 | ): 36 | eip712_message = self.get_eip712_message(**message) 37 | message_hash = self.get_hash(**message) 38 | typed_signature = self.signer.sign( 39 | eip712_message, 40 | message_hash, 41 | signer_address, 42 | ) 43 | return typed_signature 44 | 45 | def sign_message( 46 | self, 47 | signer_address, 48 | **message, 49 | ): 50 | eip712_message = self.get_person_message(**message) 51 | #eip712_message = '{"name": "apex","version": "1.0","envId": 5,"action": "L2 Key","onlySignOn": "https://trade.apex.exchange"}' 52 | #msgStr = json_msg_stringify(eip712_message) 53 | msgStr ='\n'.join('{key}: {value}'.format( 54 | key=x[0], value=x[1]) for x in eip712_message.items()) 55 | 56 | message_hash = util.hash_person(msgStr) 57 | typed_signature = self.signer.sign_person( 58 | eip712_message, 59 | message_hash, 60 | signer_address, 61 | ) 62 | return typed_signature 63 | 64 | def sign_zk_message( 65 | self, 66 | signer_address, 67 | msgHeader, 68 | ): 69 | eip712_message = self.get_zk_person_message(signer_address) 70 | #eip712_message = '{"name": "apex","version": "1.0","envId": 5,"action": "L2 Key","onlySignOn": "https://trade.apex.exchange"}' 71 | 72 | msgStr ='\n'.join('{key}: {value}'.format( 73 | key=x[0], value=x[1]) for x in eip712_message.items()) 74 | 75 | message_hash = util.hash_person(msgHeader+ '\n' + msgStr) 76 | signature = self.signer.sign_zk_person( 77 | message_hash, 78 | signer_address, 79 | ) 80 | return signature 81 | 82 | 83 | def verify( 84 | self, 85 | typed_signature, 86 | expected_signer_address, 87 | **message, 88 | ): 89 | message_hash = self.get_hash(**message) 90 | signer = util.ec_recover_typed_signature(message_hash, typed_signature) 91 | return util.addresses_are_equal(signer, expected_signer_address) 92 | 93 | def get_eip712_message( 94 | self, 95 | **message, 96 | ): 97 | struct_name = self.get_eip712_struct_name() 98 | return { 99 | 'types': { 100 | 'EIP712Domain': [ 101 | { 102 | 'name': 'name', 103 | 'type': 'string', 104 | }, 105 | { 106 | 'name': 'version', 107 | 'type': 'string', 108 | }, 109 | { 110 | 'name': 'chainId', 111 | 'type': 'uint256', 112 | }, 113 | ], 114 | struct_name: self.get_eip712_struct(), 115 | }, 116 | 'domain': { 117 | 'name': DOMAIN, 118 | 'version': VERSION, 119 | 'chainId': self.network_id, 120 | }, 121 | 'primaryType': struct_name, 122 | 'message': message, 123 | } 124 | 125 | def get_person_message( 126 | self, 127 | **message, 128 | ): 129 | return { 130 | 'name': DOMAIN, 131 | 'version': VERSION, 132 | 'envId': self.network_id, 133 | 'action': "L2 Key", 134 | 'onlySignOn': 'https://pro.apex.exchange', 135 | } 136 | 137 | def get_zk_person_message( 138 | self, 139 | address, 140 | ): 141 | return { 142 | 'Address': address, 143 | 'Action': 'ApeX Omni Onboarding', 144 | } 145 | 146 | def get_eip712_hash(self, struct_hash): 147 | return Web3.solidityKeccak( 148 | [ 149 | 'bytes2', 150 | 'bytes32', 151 | 'bytes32', 152 | ], 153 | [ 154 | '0x1901', 155 | self.get_domain_hash(), 156 | struct_hash, 157 | ] 158 | ) 159 | 160 | def get_domain_hash(self): 161 | return Web3.solidityKeccak( 162 | [ 163 | 'bytes32', 164 | 'bytes32', 165 | 'bytes32', 166 | 'uint256', 167 | ], 168 | [ 169 | util.hash_string(EIP712_DOMAIN_STRING_NO_CONTRACT), 170 | util.hash_string(DOMAIN), 171 | util.hash_string(VERSION), 172 | self.network_id, 173 | ], 174 | ) 175 | -------------------------------------------------------------------------------- /apexpro/eth_signing/signers.py: -------------------------------------------------------------------------------- 1 | import eth_account 2 | 3 | from apexpro.constants import SIGNATURE_TYPE_NO_PREPEND, SIGNATURE_TYPE_PERSONAL 4 | from apexpro.eth_signing import util 5 | 6 | 7 | class Signer(object): 8 | 9 | def sign( 10 | self, 11 | eip712_message, 12 | message_hash, 13 | opt_signer_address, 14 | ): 15 | ''' 16 | Sign an EIP-712 message. 17 | 18 | Returns a “typed signature” whose last byte indicates whether the hash 19 | was prepended before being signed. 20 | 21 | :param eip712_message: required 22 | :type eip712_message: dict 23 | 24 | :param message_hash: required 25 | :type message_hash: HexBytes 26 | 27 | :param opt_signer_address: optional 28 | :type opt_signer_address: str 29 | 30 | :returns: str 31 | ''' 32 | raise NotImplementedError() 33 | 34 | 35 | class SignWithWeb3(Signer): 36 | 37 | def __init__(self, web3): 38 | self.web3 = web3 39 | 40 | def sign( 41 | self, 42 | eip712_message, 43 | message_hash, # Ignored. 44 | opt_signer_address, 45 | ): 46 | signer_address = opt_signer_address or self.web3.eth.defaultAccount 47 | if not signer_address: 48 | raise ValueError( 49 | 'Must set ethereum_address or web3.eth.defaultAccount', 50 | ) 51 | raw_signature = self.web3.eth.signTypedData( 52 | signer_address, 53 | eip712_message, 54 | ) 55 | typed_signature = util.create_typed_signature( 56 | raw_signature.hex(), 57 | SIGNATURE_TYPE_NO_PREPEND, 58 | ) 59 | return typed_signature 60 | 61 | 62 | class SignWithKey(Signer): 63 | 64 | def __init__(self, private_key): 65 | self.address = eth_account.Account.from_key(private_key).address 66 | self._private_key = private_key 67 | 68 | def sign( 69 | self, 70 | eip712_message, # Ignored. 71 | message_hash, 72 | opt_signer_address, 73 | ): 74 | if ( 75 | opt_signer_address is not None and 76 | opt_signer_address != self.address 77 | ): 78 | raise ValueError( 79 | 'signer_address is {} but Ethereum key (eth_private_key / ' 80 | 'web3_account) corresponds to address {}'.format( 81 | opt_signer_address, 82 | self.address, 83 | ), 84 | ) 85 | signed_message = eth_account.Account._sign_hash( 86 | message_hash.hex(), 87 | self._private_key, 88 | ) 89 | typed_signature = util.create_typed_signature( 90 | signed_message.signature.hex(), 91 | SIGNATURE_TYPE_NO_PREPEND, 92 | ) 93 | return typed_signature 94 | 95 | def sign_person( 96 | self, 97 | eip712_message, # Ignored. 98 | message_hash, 99 | opt_signer_address, 100 | ): 101 | if ( 102 | opt_signer_address is not None and 103 | opt_signer_address != self.address 104 | ): 105 | raise ValueError( 106 | 'signer_address is {} but Ethereum key (eth_private_key / ' 107 | 'web3_account) corresponds to address {}'.format( 108 | opt_signer_address, 109 | self.address, 110 | ), 111 | ) 112 | 113 | signed_message = eth_account.Account._sign_hash( 114 | message_hash.hex(), 115 | self._private_key, 116 | ) 117 | typed_signature = util.create_typed_signature( 118 | signed_message.signature.hex(), 119 | SIGNATURE_TYPE_PERSONAL, 120 | ) 121 | return typed_signature 122 | 123 | def sign_zk_person( 124 | self, 125 | message_hash, 126 | opt_signer_address, 127 | ): 128 | if ( 129 | opt_signer_address is not None and 130 | opt_signer_address != self.address 131 | ): 132 | raise ValueError( 133 | 'signer_address is {} but Ethereum key (eth_private_key / ' 134 | 'web3_account) corresponds to address {}'.format( 135 | opt_signer_address, 136 | self.address, 137 | ), 138 | ) 139 | 140 | signed_message = eth_account.Account._sign_hash( 141 | message_hash.hex(), 142 | self._private_key, 143 | ) 144 | signature = util.fix_raw_signature( 145 | signed_message.signature.hex() 146 | ) 147 | return signature 148 | -------------------------------------------------------------------------------- /apexpro/eth_signing/util.py: -------------------------------------------------------------------------------- 1 | import web3 2 | from web3 import Web3 3 | from web3.auto import w3 4 | from apexpro import constants 5 | 6 | PREPEND_DEC = '\x19Ethereum Signed Message:\n32' 7 | PREPEND_HEX = '\x19Ethereum Signed Message:\n\x20' 8 | 9 | 10 | def is_valid_sig_type( 11 | sig_type, 12 | ): 13 | return sig_type in [ 14 | constants.SIGNATURE_TYPE_DECIMAL, 15 | constants.SIGNATURE_TYPE_HEXADECIMAL, 16 | constants.SIGNATURE_TYPE_NO_PREPEND, 17 | constants.SIGNATURE_TYPE_PERSONAL, 18 | ] 19 | 20 | 21 | def ec_recover_typed_signature( 22 | hashVal, 23 | typed_signature, 24 | ): 25 | if len(strip_hex_prefix(typed_signature)) != 66 * 2: 26 | raise Exception('Unable to ecrecover signature: ' + typed_signature) 27 | 28 | sig_type = int(typed_signature[-2:], 16) 29 | prepended_hash = '' 30 | if sig_type == constants.SIGNATURE_TYPE_NO_PREPEND: 31 | prepended_hash = hashVal 32 | elif sig_type == constants.SIGNATURE_TYPE_DECIMAL: 33 | prepended_hash = Web3.solidityKeccak( 34 | ['string', 'bytes32'], 35 | [PREPEND_DEC, hashVal], 36 | ) 37 | elif sig_type == constants.SIGNATURE_TYPE_HEXADECIMAL: 38 | prepended_hash = Web3.solidityKeccak( 39 | ['string', 'bytes32'], 40 | [PREPEND_HEX, hashVal], 41 | ) 42 | else: 43 | raise Exception('Invalid signature type: ' + sig_type) 44 | 45 | if not prepended_hash: 46 | raise Exception('Invalid hash: ' + hashVal) 47 | 48 | signature = typed_signature[:-2] 49 | 50 | address = w3.eth.account.recoverHash(prepended_hash, signature=signature) 51 | return address 52 | 53 | 54 | def create_typed_signature( 55 | signature, 56 | sig_type, 57 | ): 58 | if not is_valid_sig_type(sig_type): 59 | raise Exception('Invalid signature type: ' + sig_type) 60 | 61 | return fix_raw_signature(signature) + '0' + str(sig_type) 62 | 63 | 64 | def fix_raw_signature( 65 | signature, 66 | ): 67 | stripped = strip_hex_prefix(signature) 68 | 69 | if len(stripped) != 130: 70 | raise Exception('Invalid raw signature: ' + signature) 71 | 72 | rs = stripped[:128] 73 | v = stripped[128: 130] 74 | 75 | if v == '00': 76 | return '0x' + rs + '1b' 77 | if v == '01': 78 | return '0x' + rs + '1c' 79 | if v in ['1b', '1c']: 80 | return '0x' + stripped 81 | 82 | raise Exception('Invalid v value: ' + v) 83 | 84 | # ============ Byte Helpers ============ 85 | 86 | 87 | def strip_hex_prefix(input): 88 | if input.startswith('0x'): 89 | return input[2:] 90 | 91 | return input 92 | 93 | 94 | def addresses_are_equal( 95 | address_one, 96 | address_two, 97 | ): 98 | if not address_one or not address_two: 99 | return False 100 | 101 | return ( 102 | strip_hex_prefix( 103 | address_one 104 | ).lower() == strip_hex_prefix(address_two).lower() 105 | ) 106 | 107 | 108 | def hash_string(input): 109 | return Web3.solidity_keccak(['string'], [input]) 110 | 111 | def hash_person(input): 112 | msg = "\x19Ethereum Signed Message:\n%d%s" %( len(input), input) 113 | return Web3.solidity_keccak(['string'], [msg]) 114 | -------------------------------------------------------------------------------- /apexpro/exceptions.py: -------------------------------------------------------------------------------- 1 | class FailedRequestError(Exception): 2 | """ 3 | Exception raised for failed requests. 4 | 5 | Attributes: 6 | request -- The original request that caused the error. 7 | message -- Explanation of the error. 8 | status_code -- The code number returned. 9 | time -- The time of the error. 10 | """ 11 | def __init__(self, request, message, status_code, time): 12 | self.request = request 13 | self.message = message 14 | self.status_code = status_code 15 | self.time = time 16 | super().__init__( 17 | f'{message.capitalize()} (ErrCode: {status_code}) (ErrTime: {time})' 18 | f'.\nRequest → {request}.' 19 | ) 20 | 21 | 22 | class InvalidRequestError(Exception): 23 | """ 24 | Exception raised for returned Apex pro errors. 25 | 26 | Attributes: 27 | request -- The original request that caused the error. 28 | message -- Explanation of the error. 29 | status_code -- The code number returned. 30 | time -- The time of the error. 31 | """ 32 | def __init__(self, request, message, status_code, time): 33 | self.request = request 34 | self.message = message 35 | self.status_code = status_code 36 | self.time = time 37 | super().__init__( 38 | f'{message.capitalize()} (ErrCode: {status_code}) (ErrTime: {time})' 39 | f'.\nRequest → {request}.' 40 | ) 41 | -------------------------------------------------------------------------------- /apexpro/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/helpers/__init__.py -------------------------------------------------------------------------------- /apexpro/helpers/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/helpers/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/helpers/__pycache__/request_helpers.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/helpers/__pycache__/request_helpers.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/helpers/db.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | NAMESPACE = uuid.UUID('0f9da948-a6fb-4c45-9edc-4685c3f3317d') 4 | 5 | 6 | def get_user_id(address): 7 | return str(uuid.uuid5(NAMESPACE, address)) 8 | 9 | 10 | def get_account_id( 11 | address, 12 | accountNumber=0, 13 | ): 14 | return str( 15 | uuid.uuid5( 16 | NAMESPACE, 17 | get_user_id(address.lower()) + str(accountNumber), 18 | ), 19 | ) 20 | -------------------------------------------------------------------------------- /apexpro/helpers/request_helpers.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | import time 4 | from datetime import datetime 5 | 6 | import dateutil.parser as dp 7 | from web3 import Web3 8 | 9 | from apexpro.eth_signing.util import strip_hex_prefix 10 | from apexpro.starkex.helpers import serialize_signature, deserialize_signature, int_to_hex_32 11 | from apexpro.starkex.starkex_resources.proxy import sign, verify, get_hash 12 | from apexpro.starkex.starkex_resources.python_signature import inv_mod_curve_size 13 | 14 | 15 | def generate_query_path(url, params): 16 | entries = params.items() 17 | if not entries: 18 | return url 19 | 20 | paramsString = '&'.join('{key}={value}'.format( 21 | key=x[0], value=x[1]) for x in entries if x[1] is not None) 22 | if paramsString: 23 | return url + '?' + paramsString 24 | 25 | return url 26 | 27 | 28 | def json_stringify(data): 29 | return json.dumps(data, separators=(',', ':')) 30 | 31 | def json_msg_stringify(data): 32 | return json.dumps(data, separators=(',', ': '), indent=2) 33 | 34 | 35 | def random_client_id(): 36 | return str(int(float(str(random.random())[2:]))) 37 | 38 | 39 | def generate_now_iso(): 40 | return datetime.utcnow().strftime( 41 | '%Y-%m-%dT%H:%M:%S.%f', 42 | )[:-3] + 'Z' 43 | 44 | 45 | def generate_now(): 46 | now = time.time() 47 | return int(round(now * 1000)) 48 | 49 | 50 | def iso_to_epoch_seconds(iso): 51 | return dp.parse(iso).timestamp() 52 | 53 | 54 | def epoch_seconds_to_iso(epoch): 55 | return datetime.utcfromtimestamp(epoch).strftime( 56 | '%Y-%m-%dT%H:%M:%S.%f', 57 | )[:-3] + 'Z' 58 | 59 | 60 | def remove_nones(original): 61 | return {k: v for k, v in original.items() if v is not None} 62 | 63 | 64 | def calc_bind_owner_key_sig_hash(star_key_hex, owner_key): 65 | data_bytes = "UserRegistration:" 66 | owner_key_bytes = bytes.fromhex(strip_hex_prefix(owner_key)) 67 | data = Web3.solidityKeccak( 68 | [ 69 | 'string', 70 | 'bytes20', 71 | 'uint256', 72 | ], 73 | [ 74 | data_bytes, 75 | owner_key_bytes, 76 | int(star_key_hex, 16), 77 | ] 78 | ) 79 | print(data.hex()) 80 | return data 81 | 82 | 83 | def starkex_sign(hash, private_key_hex): 84 | """Sign the hash of the object using the given private key.""" 85 | EC_ORDER = 3618502788666131213697322783095070105526743751716087489154079457884512865583 86 | hash_mod = int(hash.hex(), 16) % EC_ORDER 87 | print("hash_mod:" + int_to_hex_32(hash_mod)) 88 | r, s = sign(hash_mod, int(private_key_hex, 16)) 89 | return serialize_signature(r, s) 90 | 91 | def starkex_verify(hash, sign, pub_key): 92 | EC_ORDER = 3618502788666131213697322783095070105526743751716087489154079457884512865583 93 | hash_mod = int(hash.hex(), 16) % EC_ORDER 94 | r, s = deserialize_signature(sign) 95 | return verify(hash_mod, r, s, int(pub_key, 16)) 96 | -------------------------------------------------------------------------------- /apexpro/helpers/requests.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import requests 4 | 5 | from apexpro.errors import ApexproApiError 6 | from apexpro.helpers.request_helpers import remove_nones 7 | 8 | # TODO: Use a separate session per client instance. 9 | session = requests.session() 10 | session.headers.update({ 11 | 'User-Agent': 'apexpro-python-sdk-1.0.0' , 12 | 'Content-Type': 'application/x-www-urlencoded', 13 | 'Accept': 'application/json', 14 | }) 15 | 16 | 17 | class Response(object): 18 | def __init__(self, data={}, headers=None): 19 | self.data = data 20 | self.headers = headers 21 | 22 | 23 | def request(uri, method, headers=None, data_values={}): 24 | response = send_request( 25 | uri, 26 | method, 27 | headers, 28 | data=json.dumps( 29 | remove_nones(data_values) 30 | ) 31 | ) 32 | if not str(response.status_code).startswith('2'): 33 | raise ApexproApiError(response) 34 | 35 | if response.content: 36 | return Response(response.json(), response.headers) 37 | else: 38 | return Response('{}', response.headers) 39 | 40 | 41 | def send_request(uri, method, headers=None, **kwargs): 42 | return getattr(session, method)(uri, headers=headers, **kwargs) 43 | -------------------------------------------------------------------------------- /apexpro/helpers/util.py: -------------------------------------------------------------------------------- 1 | import decimal 2 | import time 3 | 4 | 5 | class TimedOutWaitingForCondition(Exception): 6 | 7 | def __init__(self, last_value, expected_value): 8 | self.last_value = last_value 9 | self.expected_value = expected_value 10 | 11 | 12 | def wait_for_condition(fn, expected_value, timeout_s, interval_s=1): 13 | start = time.time() 14 | result = fn() 15 | while result != expected_value: 16 | if time.time() - start > timeout_s: 17 | raise TimedOutWaitingForCondition(result, expected_value) 18 | time.sleep(interval_s) 19 | if time.time() - start > timeout_s: 20 | raise TimedOutWaitingForCondition(result, expected_value) 21 | result = fn() 22 | return result 23 | 24 | def round_size(size, ticker_size): 25 | sizeNumber = decimal.Decimal(size) / decimal.Decimal(ticker_size) 26 | return decimal.Decimal(int(sizeNumber)) * decimal.Decimal(ticker_size) 27 | 28 | -------------------------------------------------------------------------------- /apexpro/http_public.py: -------------------------------------------------------------------------------- 1 | from apexpro import HTTP 2 | from apexpro.constants import URL_SUFFIX 3 | 4 | 5 | class HttpPublic(HTTP): 6 | def server_time(self, **kwargs): 7 | """" 8 | GET Retrieve System Time. 9 | :param kwargs: See 10 | https://api-docs.pro.apex.exchange/#publicapi-get-apex-server-time 11 | :returns: Request results as dictionary. 12 | """ 13 | 14 | suffix = URL_SUFFIX + "/v1/time" 15 | return self._submit_request( 16 | method="GET", 17 | path=self.endpoint + suffix 18 | ) 19 | 20 | def depth(self, **kwargs): 21 | """" 22 | GET Retrieve Market Depth. 23 | :param kwargs: See 24 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-market-depth 25 | :returns: Request results as dictionary. 26 | """ 27 | suffix = URL_SUFFIX + "/v1/depth" 28 | if kwargs.get('symbol') is not None: 29 | kwargs['symbol'] = kwargs['symbol'].replace('-', '') 30 | return self._submit_request( 31 | method='GET', 32 | path=self.endpoint + suffix, 33 | query=kwargs 34 | ) 35 | 36 | def trades(self, **kwargs): 37 | """" 38 | GET Retrieve Newest Trading Data. 39 | :param kwargs: See 40 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-newest-trading-data 41 | :returns: Request results as dictionary. 42 | """ 43 | suffix = URL_SUFFIX + "/v1/trades" 44 | if kwargs.get('symbol') is not None: 45 | kwargs['symbol'] = kwargs['symbol'].replace('-', '') 46 | return self._submit_request( 47 | method='GET', 48 | path=self.endpoint + suffix, 49 | query=kwargs 50 | ) 51 | 52 | def klines(self, **kwargs): 53 | """" 54 | GET Retrieve Candlestick Chart Data. 55 | :param kwargs: See 56 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-candlestick-chart-data 57 | :returns: Request results as dictionary. 58 | """ 59 | suffix = URL_SUFFIX + "/v1/klines" 60 | if kwargs.get('symbol') is not None: 61 | kwargs['symbol'] = kwargs['symbol'].replace('-', '') 62 | return self._submit_request( 63 | method='GET', 64 | path=self.endpoint + suffix, 65 | query=kwargs 66 | ) 67 | 68 | def ticker(self, **kwargs): 69 | """" 70 | GET Retrieve Ticker Data. 71 | :param kwargs: See 72 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-ticker-data 73 | :returns: Request results as dictionary. 74 | """ 75 | suffix = URL_SUFFIX + "/v1/ticker" 76 | if kwargs.get('symbol') is not None: 77 | kwargs['symbol'] = kwargs['symbol'].replace('-', '') 78 | return self._submit_request( 79 | method='GET', 80 | path=self.endpoint + suffix, 81 | query=kwargs 82 | ) 83 | 84 | def history_funding(self, **kwargs): 85 | """" 86 | GET Retrieve Funding Rate History. 87 | :param kwargs: See 88 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-funding-rate-history 89 | :returns: Request results as dictionary. 90 | """ 91 | suffix = URL_SUFFIX + "/v1/history-funding" 92 | #if kwargs['symbol'] is not None: 93 | # kwargs['symbol'] = kwargs['symbol'].replace('-', '') 94 | return self._submit_request( 95 | method='GET', 96 | path=self.endpoint + suffix, 97 | query=kwargs 98 | ) 99 | 100 | def history_funding_v2(self, **kwargs): 101 | """" 102 | GET Retrieve Funding Rate History. 103 | :param kwargs: See 104 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-funding-rate-history 105 | :returns: Request results as dictionary. 106 | """ 107 | suffix = URL_SUFFIX + "/v2/history-funding" 108 | #if kwargs.get('symbol') is not None: 109 | # kwargs['symbol'] = kwargs['symbol'].replace('-', '') 110 | return self._submit_request( 111 | method='GET', 112 | path=self.endpoint + suffix, 113 | query=kwargs 114 | ) 115 | 116 | def test_ticker(self, **kwargs): 117 | suffix = URL_SUFFIX + "/v1/test-ticker" 118 | if kwargs.get('symbol') is not None: 119 | kwargs['symbol'] = kwargs['symbol'].replace('-', '') 120 | return self._submit_request( 121 | method='GET', 122 | path=self.endpoint + suffix, 123 | query=kwargs 124 | ) 125 | 126 | def depth_v3(self, **kwargs): 127 | """" 128 | GET Retrieve Market Depth. 129 | :param kwargs: See 130 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-market-depth 131 | :returns: Request results as dictionary. 132 | """ 133 | suffix = URL_SUFFIX + "/v3/depth" 134 | if kwargs.get('symbol') is not None: 135 | kwargs['symbol'] = kwargs['symbol'].replace('-', '') 136 | return self._submit_request( 137 | method='GET', 138 | path=self.endpoint + suffix, 139 | query=kwargs 140 | ) 141 | 142 | def trades_v3(self, **kwargs): 143 | """" 144 | GET Retrieve Newest Trading Data. 145 | :param kwargs: See 146 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-newest-trading-data 147 | :returns: Request results as dictionary. 148 | """ 149 | suffix = URL_SUFFIX + "/v3/trades" 150 | if kwargs.get('symbol') is not None: 151 | kwargs['symbol'] = kwargs['symbol'].replace('-', '') 152 | return self._submit_request( 153 | method='GET', 154 | path=self.endpoint + suffix, 155 | query=kwargs 156 | ) 157 | 158 | def klines_v3(self, **kwargs): 159 | """" 160 | GET Retrieve Candlestick Chart Data. 161 | :param kwargs: See 162 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-candlestick-chart-data 163 | :returns: Request results as dictionary. 164 | """ 165 | suffix = URL_SUFFIX + "/v3/klines" 166 | if kwargs.get('symbol') is not None: 167 | kwargs['symbol'] = kwargs['symbol'].replace('-', '') 168 | return self._submit_request( 169 | method='GET', 170 | path=self.endpoint + suffix, 171 | query=kwargs 172 | ) 173 | 174 | def ticker_v3(self, **kwargs): 175 | """" 176 | GET Retrieve Ticker Data. 177 | :param kwargs: See 178 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-ticker-data 179 | :returns: Request results as dictionary. 180 | """ 181 | suffix = URL_SUFFIX + "/v3/ticker" 182 | if kwargs.get('symbol') is not None: 183 | kwargs['symbol'] = kwargs['symbol'].replace('-', '') 184 | return self._submit_request( 185 | method='GET', 186 | path=self.endpoint + suffix, 187 | query=kwargs 188 | ) 189 | 190 | def history_funding_v3(self, **kwargs): 191 | """" 192 | GET Retrieve Funding Rate History. 193 | :param kwargs: See 194 | https://api-docs.pro.apex.exchange/#publicapi-get-retrieve-funding-rate-history 195 | :returns: Request results as dictionary. 196 | """ 197 | suffix = URL_SUFFIX + "/v3/history-funding" 198 | return self._submit_request( 199 | method='GET', 200 | path=self.endpoint + suffix, 201 | query=kwargs 202 | ) 203 | -------------------------------------------------------------------------------- /apexpro/libzklink_sdk-arm.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/libzklink_sdk-arm.dylib -------------------------------------------------------------------------------- /apexpro/libzklink_sdk-x86.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/libzklink_sdk-x86.dylib -------------------------------------------------------------------------------- /apexpro/libzklink_sdk.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/libzklink_sdk.dylib -------------------------------------------------------------------------------- /apexpro/libzklink_sdk.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/libzklink_sdk.so -------------------------------------------------------------------------------- /apexpro/models.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | 4 | def configDecoder(configs): 5 | return namedtuple('X', configs.keys())(*configs.values()) 6 | -------------------------------------------------------------------------------- /apexpro/starkex/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/starkex/__init__.py -------------------------------------------------------------------------------- /apexpro/starkex/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/starkex/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/starkex/__pycache__/constants.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/starkex/__pycache__/constants.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/starkex/__pycache__/helpers.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/starkex/__pycache__/helpers.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/starkex/conditional_transfer.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | import math 3 | 4 | from apexpro.constants import COLLATERAL_ASSET, ASSET_RESOLUTION 5 | from apexpro.starkex.constants import CONDITIONAL_TRANSFER_FEE_ASSET_ID, ORDER_SIGNATURE_EXPIRATION_BUFFER_HOURS 6 | from apexpro.starkex.constants import CONDITIONAL_TRANSFER_FIELD_BIT_LENGTHS 7 | from apexpro.starkex.constants import CONDITIONAL_TRANSFER_MAX_AMOUNT_FEE 8 | from apexpro.starkex.constants import CONDITIONAL_TRANSFER_PADDING_BITS 9 | from apexpro.starkex.constants import CONDITIONAL_TRANSFER_PREFIX 10 | from apexpro.starkex.constants import ONE_HOUR_IN_SECONDS 11 | from apexpro.starkex.helpers import fact_to_condition 12 | from apexpro.starkex.helpers import nonce_from_client_id 13 | from apexpro.starkex.helpers import to_quantums_exact 14 | from apexpro.starkex.signable import Signable 15 | from apexpro.starkex.starkex_resources.proxy import get_hash 16 | 17 | StarkwareConditionalTransfer = namedtuple( 18 | 'StarkwareConditionalTransfer', 19 | [ 20 | 'sender_position_id', 21 | 'receiver_position_id', 22 | 'receiver_public_key', 23 | 'condition', 24 | 'quantums_amount', 25 | 'nonce', 26 | 'expiration_epoch_hours', 27 | ], 28 | ) 29 | 30 | 31 | class SignableConditionalTransfer(Signable): 32 | 33 | def __init__( 34 | self, 35 | network_id, 36 | sender_position_id, 37 | receiver_position_id, 38 | receiver_public_key, 39 | fact_registry_address, 40 | fact, 41 | human_amount, 42 | client_id, 43 | expiration_epoch_seconds, 44 | collateral_id 45 | ): 46 | self.collateral_asset_id = int( 47 | collateral_id, 48 | 16, 49 | ) 50 | receiver_public_key = ( 51 | receiver_public_key 52 | if isinstance(receiver_public_key, int) 53 | else int(receiver_public_key, 16) 54 | ) 55 | quantums_amount = to_quantums_exact(human_amount, ASSET_RESOLUTION[COLLATERAL_ASSET]) 56 | expiration_epoch_hours = math.ceil( 57 | float(expiration_epoch_seconds) / ONE_HOUR_IN_SECONDS, 58 | ) + ORDER_SIGNATURE_EXPIRATION_BUFFER_HOURS 59 | message = StarkwareConditionalTransfer( 60 | sender_position_id=int(sender_position_id), 61 | receiver_position_id=int(receiver_position_id), 62 | receiver_public_key=receiver_public_key, 63 | condition=fact_to_condition(fact_registry_address, fact), 64 | quantums_amount=quantums_amount, 65 | nonce=nonce_from_client_id(client_id), 66 | expiration_epoch_hours=expiration_epoch_hours, 67 | ) 68 | super(SignableConditionalTransfer, self).__init__( 69 | message, 70 | ) 71 | 72 | def to_starkware(self): 73 | return self._message 74 | 75 | def _calculate_hash(self): 76 | """Calculate the hash of the Starkware order.""" 77 | 78 | # TODO: Check values are in bounds 79 | 80 | # The transfer asset and fee asset are always the collateral asset. 81 | # Fees are not supported for conditional transfers. 82 | asset_ids = get_hash( 83 | self.collateral_asset_id, 84 | CONDITIONAL_TRANSFER_FEE_ASSET_ID, 85 | ) 86 | 87 | part_1 = get_hash( 88 | get_hash( 89 | asset_ids, 90 | self._message.receiver_public_key, 91 | ), 92 | self._message.condition, 93 | ) 94 | 95 | part_2 = self._message.sender_position_id 96 | part_2 <<= CONDITIONAL_TRANSFER_FIELD_BIT_LENGTHS['position_id'] 97 | part_2 += self._message.receiver_position_id 98 | part_2 <<= CONDITIONAL_TRANSFER_FIELD_BIT_LENGTHS['position_id'] 99 | part_2 += self._message.sender_position_id 100 | part_2 <<= CONDITIONAL_TRANSFER_FIELD_BIT_LENGTHS['nonce'] 101 | part_2 += self._message.nonce 102 | 103 | part_3 = CONDITIONAL_TRANSFER_PREFIX 104 | part_3 <<= CONDITIONAL_TRANSFER_FIELD_BIT_LENGTHS['quantums_amount'] 105 | part_3 += self._message.quantums_amount 106 | part_3 <<= CONDITIONAL_TRANSFER_FIELD_BIT_LENGTHS['quantums_amount'] 107 | part_3 += CONDITIONAL_TRANSFER_MAX_AMOUNT_FEE 108 | part_3 <<= CONDITIONAL_TRANSFER_FIELD_BIT_LENGTHS[ 109 | 'expiration_epoch_hours' 110 | ] 111 | part_3 += self._message.expiration_epoch_hours 112 | part_3 <<= CONDITIONAL_TRANSFER_PADDING_BITS 113 | 114 | return get_hash( 115 | get_hash( 116 | part_1, 117 | part_2, 118 | ), 119 | part_3, 120 | ) 121 | -------------------------------------------------------------------------------- /apexpro/starkex/constants.py: -------------------------------------------------------------------------------- 1 | """Constants related to creating hashes of Starkware objects.""" 2 | 3 | ONE_HOUR_IN_SECONDS = 60 * 60 4 | ORDER_SIGNATURE_EXPIRATION_BUFFER_HOURS = 24 * 7 *4 # 28 days. 5 | 6 | TRANSFER_PREFIX = 4 7 | TRANSFER_PADDING_BITS = 81 8 | CONDITIONAL_TRANSFER_PADDING_BITS = 81 9 | CONDITIONAL_TRANSFER_PREFIX = 5 10 | ORDER_PREFIX = 3 11 | ORDER_PADDING_BITS = 17 12 | WITHDRAWAL_PADDING_BITS = 49 13 | WITHDRAWAL_PREFIX = 6 14 | WITHDRAWAL_TO_ADDRESS_PREFIX = 7 15 | 16 | # Note: Fees are not supported for conditional transfers or transfers. 17 | TRANSFER_FEE_ASSET_ID = 0 18 | TRANSFER_MAX_AMOUNT_FEE = 0 19 | 20 | CONDITIONAL_TRANSFER_FEE_ASSET_ID = 0 21 | CONDITIONAL_TRANSFER_MAX_AMOUNT_FEE = 0 22 | 23 | TRANSFER_FIELD_BIT_LENGTHS = { 24 | "asset_id": 250, 25 | "receiver_public_key": 251, 26 | "position_id": 64, 27 | "quantums_amount": 64, 28 | "nonce": 32, 29 | "expiration_epoch_hours": 32, 30 | } 31 | 32 | CONDITIONAL_TRANSFER_FIELD_BIT_LENGTHS = { 33 | "asset_id": 250, 34 | "receiver_public_key": 251, 35 | "position_id": 64, 36 | "condition": 251, 37 | "quantums_amount": 64, 38 | "nonce": 32, 39 | "expiration_epoch_hours": 32, 40 | } 41 | 42 | ORDER_FIELD_BIT_LENGTHS = { 43 | "asset_id_synthetic": 128, 44 | "asset_id_collateral": 250, 45 | "asset_id_fee": 250, 46 | "quantums_amount": 64, 47 | "nonce": 32, 48 | "position_id": 64, 49 | "expiration_epoch_hours": 32, 50 | } 51 | 52 | WITHDRAWAL_FIELD_BIT_LENGTHS = { 53 | "asset_id": 250, 54 | "position_id": 64, 55 | "nonce": 32, 56 | "quantums_amount": 64, 57 | "expiration_epoch_hours": 32, 58 | "eth_address": 160, 59 | } 60 | 61 | ORACLE_PRICE_FIELD_BIT_LENGTHS = { 62 | "asset_name": 128, 63 | "oracle_name": 40, 64 | "price": 120, 65 | "timestamp_epoch_seconds": 32, 66 | } 67 | -------------------------------------------------------------------------------- /apexpro/starkex/helpers.py: -------------------------------------------------------------------------------- 1 | import decimal 2 | import hashlib 3 | 4 | from web3 import Web3 5 | 6 | from apexpro.constants import ASSET_RESOLUTION 7 | from apexpro.eth_signing.util import strip_hex_prefix 8 | from apexpro.starkex.constants import ORDER_FIELD_BIT_LENGTHS 9 | from apexpro.starkex.starkex_resources.python_signature import ( 10 | get_random_private_key 11 | ) 12 | from apexpro.starkex.starkex_resources.python_signature import ( 13 | private_key_to_ec_point_on_stark_curve, 14 | ) 15 | from apexpro.starkex.starkex_resources.python_signature import ( 16 | private_to_stark_key 17 | ) 18 | 19 | BIT_MASK_250 = (2 ** 250) - 1 20 | NONCE_UPPER_BOUND_EXCLUSIVE = 1 << ORDER_FIELD_BIT_LENGTHS['nonce'] 21 | DECIMAL_CTX_ROUND_DOWN = decimal.Context(rounding=decimal.ROUND_DOWN) 22 | DECIMAL_CTX_ROUND_UP = decimal.Context(rounding=decimal.ROUND_UP) 23 | DECIMAL_CTX_EXACT = decimal.Context( 24 | traps=[ 25 | decimal.Inexact, 26 | decimal.DivisionByZero, 27 | decimal.InvalidOperation, 28 | decimal.Overflow, 29 | ], 30 | ) 31 | 32 | 33 | def bytes_to_int(x): 34 | """Convert a bytestring to an int.""" 35 | return int(x.hex(), 16) 36 | 37 | 38 | def int_to_hex_32(x): 39 | """Normalize to a 32-byte hex string without 0x prefix.""" 40 | padded_hex = hex(x)[2:].rjust(64, '0') 41 | if len(padded_hex) != 64: 42 | raise ValueError('Input does not fit in 32 bytes') 43 | return padded_hex 44 | 45 | 46 | def serialize_signature(r, s): 47 | """Convert a signature from an r, s pair to a 32-byte hex string.""" 48 | return int_to_hex_32(r) + int_to_hex_32(s) 49 | 50 | 51 | def deserialize_signature(signature): 52 | """Convert a signature from a 32-byte hex string to an r, s pair.""" 53 | if len(signature) != 128: 54 | raise ValueError( 55 | 'Invalid serialized signature, expected hex string of length 128', 56 | ) 57 | return int(signature[:64], 16), int(signature[64:], 16) 58 | 59 | 60 | def to_quantums_exact(human_amount, asset_resolution): 61 | """Convert a human-readable amount to an integer amount of quantums. 62 | 63 | If the provided human_amount is not a multiple of the quantum size, 64 | an exception will be raised. 65 | """ 66 | return _to_quantums_helper(human_amount, asset_resolution, DECIMAL_CTX_EXACT) 67 | 68 | 69 | def to_quantums_round_down(human_amount, asset_resolution): 70 | """Convert a human-readable amount to an integer amount of quantums. 71 | 72 | If the provided human_amount is not a multiple of the quantum size, 73 | the result will be rounded down to the nearest integer. 74 | """ 75 | return _to_quantums_helper(human_amount, asset_resolution, DECIMAL_CTX_ROUND_DOWN) 76 | 77 | 78 | def to_quantums_round_up(human_amount, asset_resolution): 79 | """Convert a human-readable amount to an integer amount of quantums. 80 | 81 | If the provided human_amount is not a multiple of the quantum size, 82 | the result will be rounded up to the nearest integer. 83 | """ 84 | return _to_quantums_helper(human_amount, asset_resolution, DECIMAL_CTX_ROUND_UP) 85 | 86 | 87 | def _to_quantums_helper(human_amount, asset_resolution, ctx): 88 | try: 89 | amount_dec = ctx.create_decimal(human_amount) 90 | resolution_dec = ctx.create_decimal(asset_resolution) 91 | quantums = (amount_dec * resolution_dec).to_integral_exact(context=ctx) 92 | except decimal.Inexact: 93 | raise ValueError( 94 | 'Amount {} is not a multiple of the quantum size {}'.format( 95 | human_amount, 96 | 1 / float(asset_resolution), 97 | ), 98 | ) 99 | return int(quantums) 100 | 101 | 102 | def nonce_from_client_id(client_id): 103 | """Generate a nonce deterministically from an arbitrary string.""" 104 | message = hashlib.sha256() 105 | message.update(client_id.encode()) # Encode as UTF-8. 106 | return int(message.digest().hex()[0:8], 16) 107 | #return int(message.digest().hex(), 16) % NONCE_UPPER_BOUND_EXCLUSIVE 108 | 109 | 110 | def get_transfer_erc20_fact( 111 | recipient, 112 | token_decimals, 113 | human_amount, 114 | token_address, 115 | salt, 116 | ): 117 | token_amount = float(human_amount) * (10 ** token_decimals) 118 | if not token_amount.is_integer(): 119 | raise ValueError( 120 | 'Amount {} has more precision than token decimals {}'.format( 121 | human_amount, 122 | token_decimals, 123 | ) 124 | ) 125 | hex_bytes = Web3.solidityKeccak( 126 | [ 127 | 'address', 128 | 'uint256', 129 | 'address', 130 | 'uint256', 131 | ], 132 | [ 133 | recipient, 134 | int(token_amount), 135 | token_address, 136 | salt, 137 | ], 138 | ) 139 | return bytes(hex_bytes) 140 | 141 | 142 | def fact_to_condition(fact_registry_address, fact): 143 | """Generate the condition, signed as part of a conditional transfer.""" 144 | if not isinstance(fact, bytes): 145 | raise ValueError('fact must be a byte-string') 146 | data = bytes.fromhex(strip_hex_prefix(fact_registry_address)) + fact 147 | return int(Web3.keccak(data).hex(), 16) & BIT_MASK_250 148 | 149 | 150 | def message_to_hash(message_string): 151 | """Generate a hash deterministically from an arbitrary string.""" 152 | message = hashlib.sha256() 153 | message.update(message_string.encode()) # Encode as UTF-8. 154 | return int(message.digest().hex(), 16) >> 5 155 | 156 | 157 | def generate_private_key_hex_unsafe(): 158 | """Generate a STARK key using the Python builtin random module.""" 159 | return hex(get_random_private_key()) 160 | 161 | 162 | def private_key_from_bytes(data): 163 | """Generate a STARK key deterministically from binary data.""" 164 | if not isinstance(data, bytes): 165 | raise ValueError('Input must be a byte-string') 166 | return hex(int(Web3.keccak(data).hex(), 16) >> 5) 167 | 168 | 169 | def private_key_to_public_hex(private_key_hex): 170 | """Given private key as hex string, return the public key as hex string.""" 171 | private_key_int = int(private_key_hex, 16) 172 | return hex(private_to_stark_key(private_key_int)) 173 | 174 | 175 | def private_key_to_public_key_pair_hex(private_key_hex): 176 | """Given private key as hex string, return the public x, y pair as hex.""" 177 | private_key_int = int(private_key_hex, 16) 178 | x, y = private_key_to_ec_point_on_stark_curve(private_key_int) 179 | return [hex(x), hex(y)] 180 | -------------------------------------------------------------------------------- /apexpro/starkex/order.py: -------------------------------------------------------------------------------- 1 | import decimal 2 | import math 3 | from collections import namedtuple 4 | 5 | from apexpro.constants import COLLATERAL_ASSET, ASSET_RESOLUTION 6 | from apexpro.constants import ORDER_SIDE_BUY 7 | from apexpro.starkex.constants import ONE_HOUR_IN_SECONDS 8 | from apexpro.starkex.constants import ORDER_FIELD_BIT_LENGTHS 9 | from apexpro.starkex.constants import ORDER_PADDING_BITS 10 | from apexpro.starkex.constants import ORDER_PREFIX 11 | from apexpro.starkex.constants import ORDER_SIGNATURE_EXPIRATION_BUFFER_HOURS 12 | from apexpro.starkex.helpers import nonce_from_client_id 13 | from apexpro.starkex.helpers import to_quantums_exact 14 | from apexpro.starkex.helpers import to_quantums_round_down 15 | from apexpro.starkex.helpers import to_quantums_round_up 16 | from apexpro.starkex.signable import Signable 17 | from apexpro.starkex.starkex_resources.proxy import get_hash 18 | 19 | DECIMAL_CONTEXT_ROUND_DOWN = decimal.Context(rounding=decimal.ROUND_DOWN) 20 | DECIMAL_CONTEXT_ROUND_UP = decimal.Context(rounding=decimal.ROUND_UP) 21 | 22 | StarkwareOrder = namedtuple( 23 | 'StarkwareOrder', 24 | [ 25 | 'order_type', 26 | 'asset_id_synthetic', 27 | 'asset_id_collateral', 28 | 'asset_id_fee', 29 | 'quantums_amount_synthetic', 30 | 'quantums_amount_collateral', 31 | 'quantums_amount_fee', 32 | 'is_buying_synthetic', 33 | 'position_id', 34 | 'nonce', 35 | 'expiration_epoch_hours', 36 | ], 37 | ) 38 | 39 | 40 | class SignableOrder(Signable): 41 | 42 | def __init__( 43 | self, 44 | market, 45 | side, 46 | position_id, 47 | human_size, 48 | human_price, 49 | limit_fee, 50 | client_id, 51 | expiration_epoch_seconds, 52 | synthetic_resolution, 53 | synthetic_id, 54 | collateral_id, 55 | ): 56 | synthetic_asset_id = int( 57 | synthetic_id, 58 | 16, 59 | ) 60 | collateral_asset_id = int( 61 | collateral_id, 62 | 16, 63 | ) 64 | # collateral_asset_id = int(collateral_asset_id, 16), 65 | is_buying_synthetic = side == ORDER_SIDE_BUY 66 | quantums_amount_synthetic = to_quantums_exact( 67 | human_size, 68 | synthetic_resolution, 69 | ) 70 | 71 | # Note: By creating the decimals outside the context and then 72 | # multiplying within the context, we ensure rounding does not occur 73 | # until after the multiplication is computed with full precision. 74 | if is_buying_synthetic: 75 | human_cost = DECIMAL_CONTEXT_ROUND_UP.multiply( 76 | decimal.Decimal(human_size), 77 | decimal.Decimal(human_price) 78 | ) 79 | quantums_amount_collateral = to_quantums_round_up( 80 | human_cost, 81 | ASSET_RESOLUTION[COLLATERAL_ASSET], 82 | ) 83 | else: 84 | human_cost = DECIMAL_CONTEXT_ROUND_DOWN.multiply( 85 | decimal.Decimal(human_size), 86 | decimal.Decimal(human_price) 87 | ) 88 | quantums_amount_collateral = to_quantums_round_down( 89 | human_cost, 90 | ASSET_RESOLUTION[COLLATERAL_ASSET], 91 | ) 92 | 93 | # The limitFee is a fraction, e.g. 0.01 is a 1 % fee. 94 | # It is always paid in the collateral asset. 95 | # Constrain the limit fee to six decimals of precision. 96 | # The final fee amount must be rounded up. 97 | limit_fee_rounded = DECIMAL_CONTEXT_ROUND_DOWN.quantize( 98 | decimal.Decimal(limit_fee), 99 | decimal.Decimal('0.000001'), 100 | ) 101 | quantums_amount_fee_decimal = DECIMAL_CONTEXT_ROUND_UP.multiply( 102 | limit_fee_rounded, 103 | quantums_amount_collateral, 104 | ).to_integral_value(context=DECIMAL_CONTEXT_ROUND_UP) 105 | 106 | # Orders may have a short time-to-live on the orderbook, but we need 107 | # to ensure their signatures are valid by the time they reach the 108 | # blockchain. Therefore, we enforce that the signed expiration includes 109 | # a buffer relative to the expiration timestamp sent to the dYdX API. 110 | expiration_epoch_hours = math.ceil( 111 | float(expiration_epoch_seconds) / ONE_HOUR_IN_SECONDS, 112 | ) + ORDER_SIGNATURE_EXPIRATION_BUFFER_HOURS 113 | 114 | message = StarkwareOrder( 115 | order_type='LIMIT_ORDER_WITH_FEES', 116 | asset_id_synthetic=synthetic_asset_id, 117 | asset_id_collateral=collateral_asset_id, 118 | asset_id_fee=collateral_asset_id, 119 | quantums_amount_synthetic=quantums_amount_synthetic, 120 | quantums_amount_collateral=quantums_amount_collateral, 121 | quantums_amount_fee=int(quantums_amount_fee_decimal), 122 | is_buying_synthetic=is_buying_synthetic, 123 | position_id=int(position_id), 124 | nonce=nonce_from_client_id(client_id), 125 | expiration_epoch_hours=expiration_epoch_hours, 126 | ) 127 | super(SignableOrder, self).__init__(message) 128 | 129 | def to_starkware(self): 130 | return self._message 131 | 132 | def _calculate_hash(self): 133 | """Calculate the hash of the Starkware order.""" 134 | 135 | # TODO: Check values are in bounds 136 | 137 | if self._message.is_buying_synthetic: 138 | asset_id_sell = self._message.asset_id_collateral 139 | asset_id_buy = self._message.asset_id_synthetic 140 | quantums_amount_sell = self._message.quantums_amount_collateral 141 | quantums_amount_buy = self._message.quantums_amount_synthetic 142 | else: 143 | asset_id_sell = self._message.asset_id_synthetic 144 | asset_id_buy = self._message.asset_id_collateral 145 | quantums_amount_sell = self._message.quantums_amount_synthetic 146 | quantums_amount_buy = self._message.quantums_amount_collateral 147 | 148 | part_1 = quantums_amount_sell 149 | part_1 <<= ORDER_FIELD_BIT_LENGTHS['quantums_amount'] 150 | part_1 += quantums_amount_buy 151 | part_1 <<= ORDER_FIELD_BIT_LENGTHS['quantums_amount'] 152 | part_1 += self._message.quantums_amount_fee 153 | part_1 <<= ORDER_FIELD_BIT_LENGTHS['nonce'] 154 | part_1 += self._message.nonce 155 | 156 | part_2 = ORDER_PREFIX 157 | for _ in range(3): 158 | part_2 <<= ORDER_FIELD_BIT_LENGTHS['position_id'] 159 | part_2 += self._message.position_id 160 | part_2 <<= ORDER_FIELD_BIT_LENGTHS['expiration_epoch_hours'] 161 | part_2 += self._message.expiration_epoch_hours 162 | part_2 <<= ORDER_PADDING_BITS 163 | 164 | assets_hash = get_hash( 165 | get_hash( 166 | asset_id_sell, 167 | asset_id_buy, 168 | ), 169 | self._message.asset_id_fee, 170 | ) 171 | return get_hash( 172 | get_hash( 173 | assets_hash, 174 | part_1, 175 | ), 176 | part_2, 177 | ) 178 | -------------------------------------------------------------------------------- /apexpro/starkex/signable.py: -------------------------------------------------------------------------------- 1 | 2 | from apexpro.starkex.helpers import deserialize_signature 3 | from apexpro.starkex.helpers import serialize_signature 4 | from apexpro.starkex.starkex_resources.proxy import sign 5 | from apexpro.starkex.starkex_resources.proxy import verify 6 | 7 | 8 | class Signable(object): 9 | """Base class for an object signable with a STARK key.""" 10 | 11 | def __init__(self, message): 12 | self._message = message 13 | self._hash = None 14 | 15 | @property 16 | def hash(self): 17 | """Get the hash of the object.""" 18 | if self._hash is None: 19 | self._hash = self._calculate_hash() 20 | return self._hash 21 | 22 | def sign(self, private_key_hex): 23 | """Sign the hash of the object using the given private key.""" 24 | r, s = sign(self.hash, int(private_key_hex, 16)) 25 | return serialize_signature(r, s) 26 | 27 | def verify_signature(self, signature_hex, public_key_hex): 28 | """Return True if the signature is valid for the given public key.""" 29 | r, s = deserialize_signature(signature_hex) 30 | return verify(self.hash, r, s, int(public_key_hex, 16)) 31 | 32 | def _calculate_hash(self): 33 | raise NotImplementedError 34 | -------------------------------------------------------------------------------- /apexpro/starkex/starkex_resources/__init__.py: -------------------------------------------------------------------------------- 1 | """Cryptographic functions to sign and verify signatures with STARK keys. 2 | 3 | The code in this module is copied from 4 | https://github.com/starkware-libs/starkex-resources 5 | and is used in accordance with the Apache License, Version 2.0. 6 | 7 | https://www.starkware.co/open-source-license/ 8 | """ 9 | -------------------------------------------------------------------------------- /apexpro/starkex/starkex_resources/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/starkex/starkex_resources/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/starkex/starkex_resources/__pycache__/cpp_signature.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/starkex/starkex_resources/__pycache__/cpp_signature.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/starkex/starkex_resources/__pycache__/math_utils.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/starkex/starkex_resources/__pycache__/math_utils.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/starkex/starkex_resources/__pycache__/proxy.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/starkex/starkex_resources/__pycache__/proxy.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/starkex/starkex_resources/__pycache__/python_signature.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/starkex/starkex_resources/__pycache__/python_signature.cpython-39.pyc -------------------------------------------------------------------------------- /apexpro/starkex/starkex_resources/cpp_signature.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import secrets 3 | import os 4 | from typing import Optional, Tuple 5 | import json 6 | import math 7 | from apexpro.starkex.starkex_resources.python_signature import ( 8 | inv_mod_curve_size, 9 | ) 10 | 11 | PEDERSEN_HASH_POINT_FILENAME = os.path.join( 12 | os.path.dirname(__file__), 'pedersen_params.json') 13 | PEDERSEN_PARAMS = json.load(open(PEDERSEN_HASH_POINT_FILENAME)) 14 | 15 | EC_ORDER = PEDERSEN_PARAMS['EC_ORDER'] 16 | 17 | FIELD_PRIME = PEDERSEN_PARAMS['FIELD_PRIME'] 18 | 19 | N_ELEMENT_BITS_ECDSA = math.floor(math.log(FIELD_PRIME, 2)) 20 | assert N_ELEMENT_BITS_ECDSA == 251 21 | 22 | 23 | CPP_LIB_PATH = None 24 | OUT_BUFFER_SIZE = 251 25 | 26 | def get_cpp_lib(crypto_c_exports_path): 27 | global CPP_LIB_PATH 28 | CPP_LIB_PATH = ctypes.cdll.LoadLibrary(os.path.abspath(crypto_c_exports_path)) 29 | # Configure argument and return types. 30 | CPP_LIB_PATH.Hash.argtypes = [ 31 | ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] 32 | CPP_LIB_PATH.Verify.argtypes = [ 33 | ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] 34 | CPP_LIB_PATH.Verify.restype = bool 35 | CPP_LIB_PATH.Sign.argtypes = [ 36 | ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] 37 | 38 | def check_cpp_lib_path() -> bool: 39 | return CPP_LIB_PATH is not None 40 | 41 | ######### 42 | # ECDSA # 43 | ######### 44 | 45 | # A type for the digital signature. 46 | ECSignature = Tuple[int, int] 47 | 48 | ################# 49 | # CPP WRAPPERS # 50 | ################# 51 | 52 | def cpp_hash(left, right) -> int: 53 | res = ctypes.create_string_buffer(OUT_BUFFER_SIZE) 54 | if CPP_LIB_PATH.Hash( 55 | left.to_bytes(32, 'little', signed=False), 56 | right.to_bytes(32, 'little', signed=False), 57 | res) != 0: 58 | raise ValueError(res.raw.rstrip(b'\00')) 59 | return int.from_bytes(res.raw[:32], 'little', signed=False) 60 | 61 | 62 | def cpp_sign(msg_hash, priv_key, seed: Optional[int] = 32) -> ECSignature: 63 | """ 64 | Note that this uses the secrets module to generate cryptographically strong random numbers. 65 | Note that the same seed will give a different signature compared with the sign function in 66 | signature.py. 67 | """ 68 | res = ctypes.create_string_buffer(OUT_BUFFER_SIZE) 69 | random_bytes = secrets.token_bytes(seed) 70 | if CPP_LIB_PATH.Sign( 71 | priv_key.to_bytes(32, 'little', signed=False), 72 | msg_hash.to_bytes(32, 'little', signed=False), 73 | random_bytes, res) != 0: 74 | raise ValueError(res.raw.rstrip(b'\00')) 75 | w = int.from_bytes(res.raw[32:64], 'little', signed=False) 76 | s = inv_mod_curve_size(w) 77 | return (int.from_bytes(res.raw[:32], 'little', signed=False), s) 78 | 79 | 80 | def cpp_verify(msg_hash, r, s, stark_key) -> bool: 81 | w =inv_mod_curve_size(s) 82 | assert 1 <= stark_key < 2**N_ELEMENT_BITS_ECDSA, 'stark_key = %s' % stark_key 83 | assert 1 <= msg_hash < 2**N_ELEMENT_BITS_ECDSA, 'msg_hash = %s' % msg_hash 84 | assert 1 <= r < 2**N_ELEMENT_BITS_ECDSA, 'r = %s' % r 85 | assert 1 <= w < EC_ORDER, 'w = %s' % w 86 | return CPP_LIB_PATH.Verify( 87 | stark_key.to_bytes(32, 'little', signed=False), 88 | msg_hash.to_bytes(32, 'little', signed=False), 89 | r.to_bytes(32, 'little', signed=False), 90 | w.to_bytes(32, 'little', signed=False)) 91 | -------------------------------------------------------------------------------- /apexpro/starkex/starkex_resources/math_utils.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright 2019 StarkWare Industries Ltd. # 3 | # # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). # 5 | # You may not use this file except in compliance with the License. # 6 | # You may obtain a copy of the License at # 7 | # # 8 | # https://www.starkware.co/open-source-license/ # 9 | # # 10 | # Unless required by applicable law or agreed to in writing, # 11 | # software distributed under the License is distributed on an "AS IS" BASIS, # 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 13 | # See the License for the specific language governing permissions # 14 | # and limitations under the License. # 15 | ############################################################################### 16 | 17 | 18 | from typing import Tuple 19 | 20 | import mpmath 21 | import sympy 22 | from sympy.core.numbers import igcdex 23 | 24 | # A type that represents a point (x,y) on an elliptic curve. 25 | ECPoint = Tuple[int, int] 26 | 27 | 28 | def pi_as_string(digits: int) -> str: 29 | """ 30 | Returns pi as a string of decimal digits without the decimal point ("314..."). 31 | """ 32 | mpmath.mp.dps = digits # Set number of digits. 33 | return '3' + str(mpmath.mp.pi)[2:] 34 | 35 | 36 | def is_quad_residue(n: int, p: int) -> bool: 37 | """ 38 | Returns True if n is a quadratic residue mod p. 39 | """ 40 | return sympy.is_quad_residue(n, p) 41 | 42 | 43 | def sqrt_mod(n: int, p: int) -> int: 44 | """ 45 | Finds the minimum positive integer m such that (m*m) % p == n 46 | """ 47 | return min(sympy.sqrt_mod(n, p, all_roots=True)) 48 | 49 | 50 | def div_mod(n: int, m: int, p: int) -> int: 51 | """ 52 | Finds a nonnegative integer 0 <= x < p such that (m * x) % p == n 53 | """ 54 | a, b, c = igcdex(m, p) 55 | assert c == 1 56 | return (n * a) % p 57 | 58 | 59 | def ec_add(point1: ECPoint, point2: ECPoint, p: int) -> ECPoint: 60 | """ 61 | Gets two points on an elliptic curve mod p and returns their sum. 62 | Assumes the points are given in affine form (x, y) and have different x coordinates. 63 | """ 64 | assert (point1[0] - point2[0]) % p != 0 65 | m = div_mod(point1[1] - point2[1], point1[0] - point2[0], p) 66 | x = (m * m - point1[0] - point2[0]) % p 67 | y = (m * (point1[0] - x) - point1[1]) % p 68 | return x, y 69 | 70 | 71 | def ec_neg(point: ECPoint, p: int) -> ECPoint: 72 | """ 73 | Given a point (x,y) return (x, -y) 74 | """ 75 | x, y = point 76 | return (x, (-y) % p) 77 | 78 | 79 | def ec_double(point: ECPoint, alpha: int, p: int) -> ECPoint: 80 | """ 81 | Doubles a point on an elliptic curve with the equation y^2 = x^3 + alpha*x + beta mod p. 82 | Assumes the point is given in affine form (x, y) and has y != 0. 83 | """ 84 | assert point[1] % p != 0 85 | m = div_mod(3 * point[0] * point[0] + alpha, 2 * point[1], p) 86 | x = (m * m - 2 * point[0]) % p 87 | y = (m * (point[0] - x) - point[1]) % p 88 | return x, y 89 | 90 | 91 | def ec_mult(m: int, point: ECPoint, alpha: int, p: int) -> ECPoint: 92 | """ 93 | Multiplies by m a point on the elliptic curve with equation y^2 = x^3 + alpha*x + beta mod p. 94 | Assumes the point is given in affine form (x, y) and that 0 < m < order(point). 95 | """ 96 | if m == 1: 97 | return point 98 | if m % 2 == 0: 99 | return ec_mult(m // 2, ec_double(point, alpha, p), alpha, p) 100 | return ec_add(ec_mult(m - 1, point, alpha, p), point, p) 101 | -------------------------------------------------------------------------------- /apexpro/starkex/starkex_resources/proxy.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | 3 | from apexpro.starkex.starkex_resources.cpp_signature import check_cpp_lib_path 4 | from apexpro.starkex.starkex_resources.cpp_signature import cpp_hash 5 | from apexpro.starkex.starkex_resources.cpp_signature import cpp_verify 6 | from apexpro.starkex.starkex_resources.python_signature import ECPoint 7 | from apexpro.starkex.starkex_resources.python_signature import ECSignature 8 | from apexpro.starkex.starkex_resources.python_signature import py_pedersen_hash 9 | from apexpro.starkex.starkex_resources.python_signature import py_sign 10 | from apexpro.starkex.starkex_resources.python_signature import py_verify 11 | 12 | 13 | def sign( 14 | msg_hash: int, 15 | priv_key: int, 16 | seed: Optional[int] = None, 17 | ) -> ECSignature: 18 | # Note: cpp_sign() is not optimized and is currently slower than py_sign(). 19 | # So always use py_sign() for now. 20 | return py_sign(msg_hash=msg_hash, priv_key=priv_key, seed=seed) 21 | 22 | 23 | def verify( 24 | msg_hash: int, 25 | r: int, 26 | s: int, 27 | public_key: Union[int, ECPoint], 28 | ) -> bool: 29 | if check_cpp_lib_path(): 30 | return cpp_verify(msg_hash=msg_hash, r=r, s=s, stark_key=public_key) 31 | 32 | return py_verify(msg_hash=msg_hash, r=r, s=s, public_key=public_key) 33 | 34 | 35 | def get_hash(*elements: int) -> int: 36 | if check_cpp_lib_path(): 37 | return cpp_hash(*elements) 38 | 39 | return py_pedersen_hash(*elements) 40 | -------------------------------------------------------------------------------- /apexpro/starkex/starkex_resources/python_signature.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright 2019 StarkWare Industries Ltd. # 3 | # # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). # 5 | # You may not use this file except in compliance with the License. # 6 | # You may obtain a copy of the License at # 7 | # # 8 | # https://www.starkware.co/open-source-license/ # 9 | # # 10 | # Unless required by applicable law or agreed to in writing, # 11 | # software distributed under the License is distributed on an "AS IS" BASIS, # 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 13 | # See the License for the specific language governing permissions # 14 | # and limitations under the License. # 15 | ############################################################################### 16 | 17 | import hashlib 18 | import json 19 | import math 20 | import os 21 | import random 22 | from typing import Optional, Tuple, Union 23 | 24 | from ecdsa.rfc6979 import generate_k 25 | 26 | from .math_utils import ECPoint, div_mod, ec_add, ec_double, ec_mult, is_quad_residue, sqrt_mod 27 | 28 | PEDERSEN_HASH_POINT_FILENAME = os.path.join( 29 | os.path.dirname(__file__), 'pedersen_params.json') 30 | PEDERSEN_PARAMS = json.load(open(PEDERSEN_HASH_POINT_FILENAME)) 31 | 32 | FIELD_PRIME = PEDERSEN_PARAMS['FIELD_PRIME'] 33 | FIELD_GEN = PEDERSEN_PARAMS['FIELD_GEN'] 34 | ALPHA = PEDERSEN_PARAMS['ALPHA'] 35 | BETA = PEDERSEN_PARAMS['BETA'] 36 | EC_ORDER = PEDERSEN_PARAMS['EC_ORDER'] 37 | CONSTANT_POINTS = PEDERSEN_PARAMS['CONSTANT_POINTS'] 38 | 39 | N_ELEMENT_BITS_ECDSA = math.floor(math.log(FIELD_PRIME, 2)) 40 | assert N_ELEMENT_BITS_ECDSA == 251 41 | 42 | N_ELEMENT_BITS_HASH = FIELD_PRIME.bit_length() 43 | assert N_ELEMENT_BITS_HASH == 252 44 | 45 | # Elliptic curve parameters. 46 | assert 2**N_ELEMENT_BITS_ECDSA < EC_ORDER < FIELD_PRIME 47 | 48 | SHIFT_POINT = CONSTANT_POINTS[0] 49 | MINUS_SHIFT_POINT = (SHIFT_POINT[0], FIELD_PRIME - SHIFT_POINT[1]) 50 | EC_GEN = CONSTANT_POINTS[1] 51 | 52 | assert SHIFT_POINT == [0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804, 53 | 0x3ca0cfe4b3bc6ddf346d49d06ea0ed34e621062c0e056c1d0405d266e10268a] 54 | assert EC_GEN == [0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca, 55 | 0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f] 56 | 57 | 58 | ######### 59 | # ECDSA # 60 | ######### 61 | 62 | # A type for the digital signature. 63 | ECSignature = Tuple[int, int] 64 | 65 | 66 | class InvalidPublicKeyError(Exception): 67 | def __init__(self): 68 | super().__init__('Given x coordinate does not represent any point on the elliptic curve.') 69 | 70 | 71 | def get_y_coordinate(stark_key_x_coordinate: int) -> int: 72 | """ 73 | Given the x coordinate of a stark_key, returns a possible y coordinate such that together the 74 | point (x,y) is on the curve. 75 | Note that the real y coordinate is either y or -y. 76 | If x is invalid stark_key it throws an error. 77 | """ 78 | 79 | x = stark_key_x_coordinate 80 | y_squared = (x * x * x + ALPHA * x + BETA) % FIELD_PRIME 81 | if not is_quad_residue(y_squared, FIELD_PRIME): 82 | raise InvalidPublicKeyError() 83 | return sqrt_mod(y_squared, FIELD_PRIME) 84 | 85 | 86 | def get_random_private_key() -> int: 87 | # NOTE: It is IMPORTANT to use a strong random function here. 88 | return random.randint(1, EC_ORDER - 1) 89 | 90 | 91 | def private_key_to_ec_point_on_stark_curve(priv_key: int) -> ECPoint: 92 | assert 0 < priv_key < EC_ORDER 93 | return ec_mult(priv_key, EC_GEN, ALPHA, FIELD_PRIME) 94 | 95 | 96 | def private_to_stark_key(priv_key: int) -> int: 97 | return private_key_to_ec_point_on_stark_curve(priv_key)[0] 98 | 99 | 100 | def inv_mod_curve_size(x: int) -> int: 101 | return div_mod(1, x, EC_ORDER) 102 | 103 | 104 | def generate_k_rfc6979(msg_hash: int, priv_key: int, seed: Optional[int] = None) -> int: 105 | # Pad the message hash, for consistency with the elliptic.js library. 106 | if 1 <= msg_hash.bit_length() % 8 <= 4 and msg_hash.bit_length() >= 248: 107 | # Only if we are one-nibble short: 108 | msg_hash *= 16 109 | 110 | if seed is None: 111 | extra_entropy = b'' 112 | else: 113 | extra_entropy = seed.to_bytes(math.ceil(seed.bit_length() / 8), 'big') 114 | 115 | return generate_k(EC_ORDER, priv_key, hashlib.sha256, 116 | msg_hash.to_bytes(math.ceil(msg_hash.bit_length() / 8), 'big'), 117 | extra_entropy=extra_entropy) 118 | 119 | 120 | # Starkware crypto functions implemented in Python. 121 | # 122 | # Copied from: 123 | # https://github.com/starkware-libs/starkex-resources/blob/0f08e6c55ad88c93499f71f2af4a2e7ae0185cdf/crypto/starkware/crypto/signature/signature.py 124 | # 125 | # Changes made by dYdX to function name only. 126 | 127 | def py_sign(msg_hash: int, priv_key: int, seed: Optional[int] = None) -> ECSignature: 128 | # Note: msg_hash must be smaller than 2**N_ELEMENT_BITS_ECDSA. 129 | # Message whose hash is >= 2**N_ELEMENT_BITS_ECDSA cannot be signed. 130 | # This happens with a very small probability. 131 | assert 0 <= msg_hash < 2**N_ELEMENT_BITS_ECDSA, 'Message not signable.' 132 | 133 | # Choose a valid k. In our version of ECDSA not every k value is valid, 134 | # and there is a negligible probability a drawn k cannot be used for signing. 135 | # This is why we have this loop. 136 | while True: 137 | k = generate_k_rfc6979(msg_hash, priv_key, seed) 138 | # Update seed for next iteration in case the value of k is bad. 139 | if seed is None: 140 | seed = 1 141 | else: 142 | seed += 1 143 | 144 | # Cannot fail because 0 < k < EC_ORDER and EC_ORDER is prime. 145 | x = ec_mult(k, EC_GEN, ALPHA, FIELD_PRIME)[0] 146 | 147 | # DIFF: in classic ECDSA, we take int(x) % n. 148 | r = int(x) 149 | if not (1 <= r < 2**N_ELEMENT_BITS_ECDSA): 150 | # Bad value. This fails with negligible probability. 151 | continue 152 | 153 | if (msg_hash + r * priv_key) % EC_ORDER == 0: 154 | # Bad value. This fails with negligible probability. 155 | continue 156 | 157 | w = div_mod(k, msg_hash + r * priv_key, EC_ORDER) 158 | if not (1 <= w < 2**N_ELEMENT_BITS_ECDSA): 159 | # Bad value. This fails with negligible probability. 160 | continue 161 | 162 | s = inv_mod_curve_size(w) 163 | return r, s 164 | 165 | 166 | def mimic_ec_mult_air(m: int, point: ECPoint, shift_point: ECPoint) -> ECPoint: 167 | """ 168 | Computes m * point + shift_point using the same steps like the AIR and throws an exception if 169 | and only if the AIR errors. 170 | """ 171 | assert 0 < m < 2**N_ELEMENT_BITS_ECDSA 172 | partial_sum = shift_point 173 | for _ in range(N_ELEMENT_BITS_ECDSA): 174 | assert partial_sum[0] != point[0] 175 | if m & 1: 176 | partial_sum = ec_add(partial_sum, point, FIELD_PRIME) 177 | point = ec_double(point, ALPHA, FIELD_PRIME) 178 | m >>= 1 179 | assert m == 0 180 | return partial_sum 181 | 182 | 183 | # Starkware crypto functions implemented in Python. 184 | # 185 | # Copied from: 186 | # https://github.com/starkware-libs/starkex-resources/blob/0f08e6c55ad88c93499f71f2af4a2e7ae0185cdf/crypto/starkware/crypto/signature/signature.py 187 | # 188 | # Changes made by dYdX to function name only. 189 | 190 | def py_verify(msg_hash: int, r: int, s: int, public_key: Union[int, ECPoint]) -> bool: 191 | # Compute w = s^-1 (mod EC_ORDER). 192 | assert 1 <= s < EC_ORDER, 's = %s' % s 193 | w = inv_mod_curve_size(s) 194 | 195 | # Preassumptions: 196 | # DIFF: in classic ECDSA, we assert 1 <= r, w <= EC_ORDER-1. 197 | # Since r, w < 2**N_ELEMENT_BITS_ECDSA < EC_ORDER, we only need to verify r, w != 0. 198 | assert 1 <= r < 2**N_ELEMENT_BITS_ECDSA, 'r = %s' % r 199 | assert 1 <= w < 2**N_ELEMENT_BITS_ECDSA, 'w = %s' % w 200 | assert 0 <= msg_hash < 2**N_ELEMENT_BITS_ECDSA, 'msg_hash = %s' % msg_hash 201 | 202 | if isinstance(public_key, int): 203 | # Only the x coordinate of the point is given, check the two possibilities for the y 204 | # coordinate. 205 | try: 206 | y = get_y_coordinate(public_key) 207 | except InvalidPublicKeyError: 208 | return False 209 | assert pow(y, 2, FIELD_PRIME) == ( 210 | pow(public_key, 3, FIELD_PRIME) + ALPHA * public_key + BETA) % FIELD_PRIME 211 | return py_verify(msg_hash, r, s, (public_key, y)) or \ 212 | py_verify(msg_hash, r, s, (public_key, (-y) % FIELD_PRIME)) 213 | else: 214 | # The public key is provided as a point. 215 | # Verify it is on the curve. 216 | assert (public_key[1]**2 - (public_key[0]**3 + ALPHA * 217 | public_key[0] + BETA)) % FIELD_PRIME == 0 218 | 219 | # Signature validation. 220 | # DIFF: original formula is: 221 | # x = (w*msg_hash)*EC_GEN + (w*r)*public_key 222 | # While what we implement is: 223 | # x = w*(msg_hash*EC_GEN + r*public_key). 224 | # While both mathematically equivalent, one might error while the other doesn't, 225 | # given the current implementation. 226 | # This formula ensures that if the verification errors in our AIR, it errors here as well. 227 | try: 228 | zG = mimic_ec_mult_air(msg_hash, EC_GEN, MINUS_SHIFT_POINT) 229 | rQ = mimic_ec_mult_air(r, public_key, SHIFT_POINT) 230 | wB = mimic_ec_mult_air(w, ec_add(zG, rQ, FIELD_PRIME), SHIFT_POINT) 231 | x = ec_add(wB, MINUS_SHIFT_POINT, FIELD_PRIME)[0] 232 | except AssertionError: 233 | return False 234 | 235 | # DIFF: Here we drop the mod n from classic ECDSA. 236 | return r == x 237 | 238 | 239 | ################# 240 | # Pedersen hash # 241 | ################# 242 | 243 | # Starkware crypto functions implemented in Python. 244 | # 245 | # Copied from: 246 | # https://github.com/starkware-libs/starkex-resources/blob/0f08e6c55ad88c93499f71f2af4a2e7ae0185cdf/crypto/starkware/crypto/signature/signature.py 247 | # 248 | # Changes made by dYdX to function name only. 249 | 250 | def py_pedersen_hash(*elements: int) -> int: 251 | return pedersen_hash_as_point(*elements)[0] 252 | 253 | 254 | def pedersen_hash_as_point(*elements: int) -> ECPoint: 255 | """ 256 | Similar to pedersen_hash but also returns the y coordinate of the resulting EC point. 257 | This function is used for testing. 258 | """ 259 | point = SHIFT_POINT 260 | for i, x in enumerate(elements): 261 | assert 0 <= x < FIELD_PRIME 262 | point_list = CONSTANT_POINTS[2 + i * N_ELEMENT_BITS_HASH:2 + (i + 1) * N_ELEMENT_BITS_HASH] 263 | assert len(point_list) == N_ELEMENT_BITS_HASH 264 | for pt in point_list: 265 | assert point[0] != pt[0], 'Unhashable input.' 266 | if x & 1: 267 | point = ec_add(point, pt, FIELD_PRIME) 268 | x >>= 1 269 | assert x == 0 270 | return point 271 | -------------------------------------------------------------------------------- /apexpro/starkex/transfer.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | import math 3 | 4 | from apexpro.constants import COLLATERAL_ASSET, ASSET_RESOLUTION 5 | from apexpro.starkex.constants import ONE_HOUR_IN_SECONDS, ORDER_SIGNATURE_EXPIRATION_BUFFER_HOURS 6 | from apexpro.starkex.constants import TRANSFER_FIELD_BIT_LENGTHS 7 | from apexpro.starkex.constants import TRANSFER_PADDING_BITS 8 | from apexpro.starkex.constants import TRANSFER_PREFIX 9 | from apexpro.starkex.constants import TRANSFER_FEE_ASSET_ID 10 | from apexpro.starkex.constants import TRANSFER_MAX_AMOUNT_FEE 11 | from apexpro.starkex.helpers import nonce_from_client_id 12 | from apexpro.starkex.helpers import to_quantums_exact 13 | from apexpro.starkex.signable import Signable 14 | from apexpro.starkex.starkex_resources.proxy import get_hash 15 | 16 | StarkwareTransfer = namedtuple( 17 | 'StarkwareTransfer', 18 | [ 19 | 'sender_position_id', 20 | 'receiver_position_id', 21 | 'receiver_public_key', 22 | 'quantums_amount', 23 | 'nounce', 24 | 'expiration_epoch_hours', 25 | ], 26 | ) 27 | 28 | 29 | class SignableTransfer(Signable): 30 | """ 31 | Wrapper object to convert a transfer, and hash, sign, and verify its 32 | signature. 33 | """ 34 | 35 | def __init__( 36 | self, 37 | sender_position_id, 38 | receiver_position_id, 39 | receiver_public_key, 40 | human_amount, 41 | client_id, 42 | expiration_epoch_seconds, 43 | network_id, 44 | collateral_id, 45 | ): 46 | nounce = nonce_from_client_id(client_id) 47 | 48 | self.collateral_asset_id = int( 49 | collateral_id, 50 | 16, 51 | ) 52 | # The transfer asset is always the collateral asset. 53 | quantums_amount = to_quantums_exact( 54 | human_amount, 55 | ASSET_RESOLUTION[COLLATERAL_ASSET], 56 | ) 57 | 58 | # Convert to a Unix timestamp (in hours). 59 | expiration_epoch_hours = math.ceil( 60 | float(expiration_epoch_seconds) / ONE_HOUR_IN_SECONDS, 61 | ) + ORDER_SIGNATURE_EXPIRATION_BUFFER_HOURS 62 | 63 | receiver_public_key = ( 64 | receiver_public_key 65 | if isinstance(receiver_public_key, int) 66 | else int(receiver_public_key, 16) 67 | ) 68 | 69 | message = StarkwareTransfer( 70 | sender_position_id=int(sender_position_id), 71 | receiver_position_id=int(receiver_position_id), 72 | receiver_public_key=receiver_public_key, 73 | quantums_amount=quantums_amount, 74 | nounce=nounce, 75 | expiration_epoch_hours=expiration_epoch_hours 76 | ) 77 | 78 | super(SignableTransfer, self).__init__(message) 79 | 80 | def to_starkware(self): 81 | return self._message 82 | 83 | def _calculate_hash(self): 84 | """Calculate the hash of the Starkware order.""" 85 | # TODO: Check values are in bounds 86 | 87 | asset_ids = get_hash( 88 | self.collateral_asset_id, 89 | TRANSFER_FEE_ASSET_ID, 90 | ) 91 | 92 | part1 = get_hash( 93 | asset_ids, 94 | self._message.receiver_public_key, 95 | ) 96 | 97 | part2 = self._message.sender_position_id 98 | part2 <<= TRANSFER_FIELD_BIT_LENGTHS['position_id'] 99 | part2 += self._message.receiver_position_id 100 | part2 <<= TRANSFER_FIELD_BIT_LENGTHS['position_id'] 101 | part2 += self._message.sender_position_id 102 | part2 <<= TRANSFER_FIELD_BIT_LENGTHS['nonce'] 103 | part2 += self._message.nounce 104 | 105 | part3 = TRANSFER_PREFIX 106 | part3 <<= TRANSFER_FIELD_BIT_LENGTHS['quantums_amount'] 107 | part3 += self._message.quantums_amount 108 | part3 <<= TRANSFER_FIELD_BIT_LENGTHS['quantums_amount'] 109 | part3 += TRANSFER_MAX_AMOUNT_FEE 110 | part3 <<= TRANSFER_FIELD_BIT_LENGTHS['expiration_epoch_hours'] 111 | part3 += self._message.expiration_epoch_hours 112 | part3 <<= TRANSFER_PADDING_BITS 113 | 114 | return get_hash( 115 | get_hash( 116 | part1, 117 | part2, 118 | ), 119 | part3, 120 | ) 121 | -------------------------------------------------------------------------------- /apexpro/starkex/withdrawal.py: -------------------------------------------------------------------------------- 1 | import math 2 | from collections import namedtuple 3 | 4 | from apexpro.constants import COLLATERAL_ASSET, ASSET_RESOLUTION 5 | from apexpro.starkex.constants import ONE_HOUR_IN_SECONDS, WITHDRAWAL_TO_ADDRESS_PREFIX, \ 6 | ORDER_SIGNATURE_EXPIRATION_BUFFER_HOURS 7 | from apexpro.starkex.constants import WITHDRAWAL_FIELD_BIT_LENGTHS 8 | from apexpro.starkex.constants import WITHDRAWAL_PADDING_BITS 9 | from apexpro.starkex.helpers import nonce_from_client_id 10 | from apexpro.starkex.helpers import to_quantums_exact 11 | from apexpro.starkex.signable import Signable 12 | from apexpro.starkex.starkex_resources.proxy import get_hash 13 | 14 | StarkwareWithdrawal = namedtuple( 15 | 'StarkwareWithdrawal', 16 | [ 17 | 'quantums_amount', 18 | 'position_id', 19 | 'nonce', 20 | 'expiration_epoch_hours', 21 | 'eth_address', 22 | ], 23 | ) 24 | 25 | 26 | class SignableWithdrawal(Signable): 27 | 28 | def __init__( 29 | self, 30 | network_id, 31 | position_id, 32 | human_amount, 33 | client_id, 34 | expiration_epoch_seconds, 35 | eth_address, 36 | collateral_id, 37 | ): 38 | self.collateral_asset_id = int( 39 | collateral_id, 40 | 16, 41 | ) 42 | quantums_amount = to_quantums_exact(human_amount, ASSET_RESOLUTION[COLLATERAL_ASSET]) 43 | expiration_epoch_hours = math.ceil( 44 | float(expiration_epoch_seconds) / ONE_HOUR_IN_SECONDS, 45 | ) + ORDER_SIGNATURE_EXPIRATION_BUFFER_HOURS 46 | message = StarkwareWithdrawal( 47 | quantums_amount=quantums_amount, 48 | position_id=int(position_id), 49 | nonce=nonce_from_client_id(client_id), 50 | expiration_epoch_hours=expiration_epoch_hours, 51 | eth_address=eth_address, 52 | ) 53 | super(SignableWithdrawal, self).__init__(message) 54 | 55 | def to_starkware(self): 56 | return self._message 57 | 58 | def _calculate_hash(self): 59 | """Calculate the hash of the Starkware order.""" 60 | 61 | # TODO: Check values are in bounds 62 | 63 | packed = WITHDRAWAL_TO_ADDRESS_PREFIX 64 | packed <<= WITHDRAWAL_FIELD_BIT_LENGTHS['position_id'] 65 | packed += self._message.position_id 66 | packed <<= WITHDRAWAL_FIELD_BIT_LENGTHS['nonce'] 67 | packed += self._message.nonce 68 | packed <<= WITHDRAWAL_FIELD_BIT_LENGTHS['quantums_amount'] 69 | packed += self._message.quantums_amount 70 | packed <<= WITHDRAWAL_FIELD_BIT_LENGTHS['expiration_epoch_hours'] 71 | packed += self._message.expiration_epoch_hours 72 | packed <<= WITHDRAWAL_PADDING_BITS 73 | 74 | 75 | return get_hash( 76 | get_hash( 77 | self.collateral_asset_id, 78 | int(self._message.eth_address, 16) 79 | ), 80 | packed, 81 | ) 82 | -------------------------------------------------------------------------------- /apexpro/websocket_api.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from ._websocket_stream import _identify_ws_method, _make_public_kwargs, _WebSocketManager, \ 4 | _ApexWebSocketManager, PUBLIC_WSS, PRIVATE_WSS 5 | 6 | from concurrent.futures import ThreadPoolExecutor 7 | 8 | 9 | 10 | class WebSocket(_ApexWebSocketManager): 11 | def __init__(self, **kwargs): 12 | super().__init__(**kwargs) 13 | 14 | self.ws_public = None 15 | self.ws_private = None 16 | self.kwargs = kwargs 17 | self.public_kwargs = _make_public_kwargs(self.kwargs) 18 | 19 | def _ws_public_subscribe(self, sendStr, topic, callback): 20 | if not self.ws_public: 21 | self.ws_public = _ApexWebSocketManager( 22 | **self.public_kwargs) 23 | self.ws_public._connect(self.endpoint + PUBLIC_WSS) 24 | self.ws_public.subscribe(sendStr, topic, callback) 25 | 26 | def _ws_private_subscribe(self,topic, callback): 27 | if not self.ws_private: 28 | self.ws_private = _ApexWebSocketManager( 29 | **self.kwargs) 30 | self.ws_private._connect(self.endpoint + PRIVATE_WSS) 31 | self.ws_private.subscribe("", topic, callback) 32 | 33 | def custom_topic_stream(self, topic, callback, wss_url): 34 | subscribe = _identify_ws_method( 35 | wss_url, 36 | { 37 | PUBLIC_WSS: self._ws_public_subscribe, 38 | PRIVATE_WSS: self._ws_private_subscribe 39 | }) 40 | subscribe(topic, callback) 41 | 42 | def depth_stream(self, callback, symbol, limit): 43 | """ 44 | https://api-docs.pro.apex.exchange/#public-websocket-depth 45 | """ 46 | arg = "orderBook" + str(limit) + ".H." + symbol 47 | topic = \ 48 | { 49 | "op": "subscribe", 50 | "args": [arg] 51 | } 52 | topicStr = json.dumps(topic, sort_keys=True, separators=(",", ":")) 53 | self._ws_public_subscribe(topicStr, arg, callback) 54 | def unsub_depth_topic_stream(self, callback, arg): 55 | """ 56 | https://api-docs.pro.apex.exchange/#public-websocket-depth 57 | """ 58 | topic = \ 59 | { 60 | "op": "unsubscribe", 61 | "args": [arg] 62 | } 63 | topicStr = json.dumps(topic, sort_keys=True, separators=(",", ":")) 64 | self._ws_public_subscribe(topicStr, arg, callback) 65 | def depth_topic_stream(self, callback, arg): 66 | """ 67 | https://api-docs.pro.apex.exchange/#public-websocket-depth 68 | """ 69 | topic = \ 70 | { 71 | "op": "subscribe", 72 | "args": [arg] 73 | } 74 | topicStr = json.dumps(topic, sort_keys=True, separators=(",", ":")) 75 | self._ws_public_subscribe(topicStr, arg, callback) 76 | def ticker_stream(self, callback, symbol): 77 | """ 78 | https://api-docs.pro.apex.exchange/#public-websocket-ticker 79 | """ 80 | arg = "instrumentInfo" + ".H." + symbol 81 | topic = \ 82 | { 83 | "op": "subscribe", 84 | "args": [arg] 85 | } 86 | topicStr = json.dumps(topic, sort_keys=True, separators=(",", ":")) 87 | self._ws_public_subscribe(topicStr, arg, callback) 88 | 89 | def all_ticker_stream(self, callback): 90 | """ 91 | https://api-docs.pro.apex.exchange/#public-websocket-ticker 92 | """ 93 | arg = "instrumentInfo.all" 94 | topic = \ 95 | { 96 | "op": "subscribe", 97 | "args": [arg] 98 | } 99 | topicStr = json.dumps(topic, sort_keys=True, separators=(",", ":")) 100 | self._ws_public_subscribe(topicStr, arg, callback) 101 | 102 | def klines_stream(self, callback, symbol, interval): 103 | """ 104 | https://api-docs.pro.apex.exchange/#public-websocket-candlestick-chart 105 | """ 106 | arg = "candle" + "." + str(interval) + "." + symbol 107 | topic = \ 108 | { 109 | "op": "subscribe", 110 | "args": [arg] 111 | } 112 | topicStr = json.dumps(topic, sort_keys=True, separators=(",", ":")) 113 | self._ws_public_subscribe(topicStr, arg, callback) 114 | 115 | def trade_stream(self, callback, symbol): 116 | """ 117 | https://api-docs.pro.apex.exchange/#public-websocket-trade 118 | """ 119 | arg = "recentlyTrade" + ".H." + symbol 120 | topic = \ 121 | { 122 | "op": "subscribe", 123 | "args": [arg] 124 | } 125 | topicStr = json.dumps(topic, sort_keys=True, separators=(",", ":")) 126 | self._ws_public_subscribe(topicStr, arg, callback) 127 | 128 | 129 | def account_info_stream(self, callback): 130 | """ 131 | https://api-docs.pro.apex.exchange/#private-websocket 132 | """ 133 | topic = "ws_accounts_v1" 134 | self._ws_private_subscribe(topic=topic, callback=callback) 135 | 136 | def account_info_stream_v2(self, callback): 137 | """ 138 | https://api-docs.pro.apex.exchange/#private-websocket 139 | """ 140 | topic = "ws_accounts_v2" 141 | self._ws_private_subscribe(topic=topic, callback=callback) 142 | def account_info_stream_v3(self, callback): 143 | """ 144 | https://api-docs.pro.apex.exchange/#private-websocket 145 | """ 146 | topic = "ws_zk_accounts_v3" 147 | self._ws_private_subscribe(topic=topic, callback=callback) 148 | -------------------------------------------------------------------------------- /apexpro/zklink_sdk.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/apexpro/zklink_sdk.dll -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "apexomni-x86-mac" 7 | version = "3.1.0" 8 | description = "Python3 Apexpro omni HTTP/WebSocket API Connector" 9 | authors = [ 10 | {name = "Dexter Dickinson", email = "xxx@apexomni.com"} 11 | ] 12 | readme = "README.md" 13 | requires-python = ">=3.6" 14 | classifiers = [ 15 | "Development Status :: 4 - Beta", 16 | "Intended Audience :: Developers", 17 | "Topic :: Software Development :: Libraries :: Python Modules", 18 | "License :: OSI Approved :: MIT License", 19 | "Programming Language :: Python :: 3.6", 20 | "Programming Language :: Python :: 3.7", 21 | "Programming Language :: Python :: 3.8", 22 | "Programming Language :: Python :: 3.9", 23 | "Programming Language :: Python :: 3.10", 24 | "Programming Language :: Python :: 3.11", 25 | "Programming Language :: Python :: 3.12", 26 | ] 27 | 28 | [tool.pytest.ini_options] 29 | testpaths = ["tests"] 30 | python_files = ["test_*.py"] 31 | addopts = "-ra -q" 32 | 33 | [tool.black] 34 | line-length = 88 35 | target-version = ['py36', 'py37', 'py38', 'py39', 'py310', 'py311', 'py312'] 36 | include = '\.pyi?$' 37 | 38 | [tool.isort] 39 | profile = "black" 40 | multi_line_output = 3 41 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_files = LICENSE 3 | 4 | [options] 5 | zip_safe = False 6 | 7 | [bdist_wheel] 8 | universal = 1 9 | 10 | [egg_info] 11 | tag_build = 12 | tag_date = 0 13 | 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from os import path 3 | 4 | here = path.abspath(path.dirname(__file__)) 5 | 6 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 7 | long_description = f.read() 8 | 9 | 10 | 11 | setup( 12 | name='apexomni-x86-mac', 13 | version='3.1.0', 14 | packages=find_packages(), 15 | description='Python3 Apexpro omni HTTP/WebSocket API Connector', 16 | long_description=long_description, 17 | long_description_content_type='text/markdown', 18 | url='https://github.com/ApeX-Protocol/apexpro-openapi', 19 | license='MIT License', 20 | author='Dexter Dickinson', 21 | author_email='xxx@apexomni.com', 22 | classifiers=[ 23 | 'Development Status :: 4 - Beta', 24 | 'Intended Audience :: Developers', 25 | 'Topic :: Software Development :: Libraries :: Python Modules', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Programming Language :: Python :: 3.6', 28 | 'Programming Language :: Python :: 3.7', 29 | 'Programming Language :: Python :: 3.8', 30 | 'Programming Language :: Python :: 3.9', 31 | 'Programming Language :: Python :: 3.10', 32 | 'Programming Language :: Python :: 3.11', 33 | 'Programming Language :: Python :: 3.12', 34 | ], 35 | keywords='apexomni apexpro api connector', 36 | package_data={'': ['*.json','*.dylib','*.dll','*.so']}, 37 | data_files=[('apexpro', ['apexpro/starkex/starkex_resources/pedersen_params.json', 'apexpro/libzklink_sdk.dylib'])], 38 | # packages=['apexpro'], 39 | python_requires='>=3.6', 40 | install_requires=[ 41 | 'requests', 42 | 'websocket-client', 43 | 'websockets', 44 | 'dateparser==1.0.0', 45 | 'ecdsa==0.16.0', 46 | 'eth_keys', 47 | 'eth-account==0.13.7', 48 | 'mpmath==1.0.0', 49 | 'pytest>=4.4.0,<5.0.0', 50 | 'requests-mock==1.6.0', 51 | 'requests>=2.32.3,<3.0.0', 52 | 'setuptools>=50.3.2', 53 | 'sympy==1.6', 54 | 'tox==3.13.2', 55 | 'web3==6.0.0', 56 | ], 57 | ) 58 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ApeX-Protocol/apexpro-openapi/376ce1933eeace12744af625821aa82790decf40/tests/__init__.py -------------------------------------------------------------------------------- /tests/account_value.py: -------------------------------------------------------------------------------- 1 | import decimal 2 | import time 3 | from threading import Timer 4 | 5 | from apexpro.constants import APEX_WS_TEST, APEX_HTTP_TEST, NETWORKID_TEST 6 | from apexpro.http_private import HttpPrivate 7 | from apexpro.http_public import HttpPublic 8 | from apexpro.websocket_api import WebSocket 9 | 10 | key = 'your apiKey-key from register' 11 | secret = 'your apiKey-secret from register' 12 | passphrase = 'your apiKey-passphrase from register' 13 | 14 | ticker_time = 10 15 | 16 | # Connect with authentication! 17 | ws_client = WebSocket( 18 | endpoint=APEX_WS_TEST 19 | ) 20 | client = HttpPrivate(APEX_HTTP_TEST, network_id=NETWORKID_TEST, 21 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 22 | 23 | symbol_list = client.configs().get("data").get("perpetualContract") 24 | print("symbol_list:", symbol_list) 25 | 26 | 27 | wallets = {} 28 | openPositions = {} 29 | orders = {} 30 | 31 | allTickerPrice = [] 32 | 33 | def all_ticker(message): 34 | global allTickerPrice 35 | print("all_ticker: ", message) 36 | newAllTickerPrice = message.get("data") 37 | 38 | if len(wallets) == 0: 39 | allTickerPrice = newAllTickerPrice 40 | handle_account() 41 | return 42 | 43 | if newAllTickerPrice is not None: 44 | for k, position in enumerate(openPositions): 45 | oldPrice = decimal.Decimal(get_symbol_price(position.get('symbol'))) 46 | symbolData = get_symbol_config(position.get('symbol')) 47 | for k, v in enumerate(newAllTickerPrice): 48 | if v['s'] == symbolData.get('symbol') or v['s'] == symbolData.get('crossSymbolName') or v['s'] == symbolData.get( 49 | 'symbolDisplayName'): 50 | if v.get("op") is not None: 51 | newPrice = decimal.Decimal(v.get("op")) 52 | if oldPrice != newPrice: 53 | print(v['s'] + " oldPrice: " + str(oldPrice) + " newPrice: " + str(newPrice)) 54 | handle_account() 55 | allTickerPrice = newAllTickerPrice 56 | return 57 | allTickerPrice = newAllTickerPrice 58 | 59 | 60 | ws_client.all_ticker_stream(all_ticker) 61 | 62 | 63 | def handle_account(): 64 | print("current time:", time.time()) 65 | global client, wallets, openPositions, orders 66 | account = client.get_account() 67 | if account.get("data") is not None and account.get("data").get("wallets") is not None: 68 | wallets = account.get("data").get("wallets") 69 | if account.get("data") is not None and account.get("data").get("openPositions") is not None: 70 | openPositions = account.get("data").get("openPositions") 71 | 72 | get_orders = client.open_orders() 73 | if get_orders.get("data") is not None: 74 | orders = get_orders.get("data") 75 | 76 | totalAccountValue = decimal.Decimal('0.0') 77 | for k, wallet in enumerate(wallets): 78 | if wallet.get("asset") == "USDC": 79 | totalAccountValue = decimal.Decimal(wallet.get('balance')) - decimal.Decimal(wallet.get('pendingWithdrawAmount')) - decimal.Decimal(wallet.get('pendingTransferOutAmount')) 80 | for k, position in enumerate(openPositions): 81 | positionValue = decimal.Decimal(position.get('size')) * decimal.Decimal(get_symbol_price(position.get('symbol'))) 82 | if position.get('side') == "SHORT": 83 | positionValue = positionValue * decimal.Decimal(-1) 84 | totalAccountValue = totalAccountValue + positionValue 85 | 86 | print("totalAccountValue is :" + str(totalAccountValue)) 87 | 88 | totalInitialMarginRequirement = decimal.Decimal('0.0') 89 | for k, position in enumerate(openPositions): 90 | totalInitialMarginRequirement = totalInitialMarginRequirement + decimal.Decimal(position.get('entryPrice')) * decimal.Decimal(position.get('size')) * decimal.Decimal(get_symbol_config(position.get('symbol')).get('initialMarginRate')) 91 | 92 | orderTotalInitialMarginRequirement = decimal.Decimal('0.0') 93 | for k, order in enumerate(orders): 94 | orderTotalInitialMarginRequirement = orderTotalInitialMarginRequirement + decimal.Decimal(order.get('price')) * decimal.Decimal(order.get('size')) * decimal.Decimal(get_symbol_config(position.get('symbol')).get('initialMarginRate')) 95 | orderTotalInitialMarginRequirement = orderTotalInitialMarginRequirement + decimal.Decimal(order.get('price')) * decimal.Decimal(order.get('size')) * decimal.Decimal(account.get("data").get('takerFeeRate')) 96 | 97 | 98 | availableValue = totalAccountValue - totalInitialMarginRequirement - orderTotalInitialMarginRequirement 99 | 100 | print("availableValue is :" + str(availableValue)) 101 | 102 | 103 | totalMaintenanceMarginRequirement = decimal.Decimal('0.0') 104 | for k, position in enumerate(openPositions): 105 | totalMaintenanceMarginRequirement = totalMaintenanceMarginRequirement + decimal.Decimal(position.get('size')) * decimal.Decimal(get_symbol_price(position.get('symbol'))) * decimal.Decimal(get_symbol_config(position.get('symbol')).get('maintenanceMarginRate')) 106 | 107 | 108 | print("totalMaintenanceMarginRequirement is :" + str(totalMaintenanceMarginRequirement)) 109 | 110 | 111 | 112 | def get_symbol_config(symbol): 113 | for k, v in enumerate(symbol_list): 114 | if v.get('symbol') == symbol or v.get('crossSymbolName') == symbol or v.get('symbolDisplayName') == symbol: 115 | return v 116 | 117 | 118 | def get_symbol_price(symbol): 119 | global allTickerPrice 120 | symbolData = get_symbol_config(symbol) 121 | for k, v in enumerate(allTickerPrice): 122 | if v['s'] == symbolData.get('symbol') or v['s'] == symbolData.get('crossSymbolName') or v['s'] == symbolData.get( 123 | 'symbolDisplayName'): 124 | return v.get("op") 125 | 126 | 127 | while True: 128 | # Run your main trading logic here. 129 | time.sleep(2) 130 | timer = Timer(ticker_time, handle_account) 131 | timer.start() 132 | timer.join() 133 | -------------------------------------------------------------------------------- /tests/account_value_ws.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from apexpro.constants import APEX_WS_TEST, APEX_HTTP_TEST 4 | from apexpro.http_public import HttpPublic 5 | from apexpro.websocket_api import WebSocket 6 | 7 | key = 'your apiKey-key from register' 8 | secret = 'your apiKey-secret from register' 9 | passphrase = 'your apiKey-passphrase from register' 10 | 11 | # Connect with authentication! 12 | ws_client = WebSocket( 13 | endpoint=APEX_WS_TEST, 14 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}, 15 | ) 16 | client = HttpPublic(APEX_HTTP_TEST) 17 | symbol_list = client.configs().get("data").get("perpetualContract") 18 | print("symbol_list:", symbol_list) 19 | 20 | positions = {} 21 | wallets = {} 22 | orders = {} 23 | 24 | def handle_account(message): 25 | print("all account: ", message) 26 | if message.get("contents") is not None and message.get("contents").get("positions") is not None: 27 | positions = message.get("contents").get("positions") 28 | print("positions: ", positions) 29 | 30 | def h2(message): 31 | print("ticker: ", message) 32 | 33 | #ws_client.depth_stream(h1,'BTCUSDC',25) 34 | ws_client.ticker_stream(h2,'All') 35 | #ws_client.trade_stream(h3,'BTCUSDC') 36 | #ws_client.klines_stream(h4,'BTCUSDC',1) 37 | ws_client.account_info_stream(handle_account) 38 | 39 | 40 | 41 | while True: 42 | # Run your main trading logic here. 43 | sleep(1) 44 | -------------------------------------------------------------------------------- /tests/constants.py: -------------------------------------------------------------------------------- 1 | # ------------ Constants for Testing ------------ 2 | DEFAULT_HOST = 'http://localhost:8080' 3 | DEFAULT_NETWORK_ID = 1001 4 | SEVEN_DAYS_S = 7 * 24 * 60 * 60 5 | -------------------------------------------------------------------------------- /tests/demo_create_order_v3.py: -------------------------------------------------------------------------------- 1 | import decimal 2 | import os 3 | import sys 4 | import time 5 | 6 | from apexpro.helpers.util import round_size 7 | 8 | from apexpro.http_private_sign import HttpPrivateSign 9 | import os 10 | import sys 11 | import time 12 | 13 | from apexpro.http_private_sign import HttpPrivateSign 14 | 15 | root_path = os.path.abspath(__file__) 16 | root_path = '/'.join(root_path.split('/')[:-2]) 17 | sys.path.append(root_path) 18 | 19 | from apexpro.constants import NETWORKID_TEST, APEX_OMNI_HTTP_TEST 20 | 21 | print("Hello, Apex omni") 22 | 23 | key = 'your apiKey-key from register' 24 | secret = 'your apiKey-secret from register' 25 | passphrase = 'your apiKey-passphrase from register' 26 | 27 | seeds = 'your zk seeds from register' 28 | l2Key = 'your l2Key seeds from register' 29 | 30 | 31 | client = HttpPrivateSign(APEX_OMNI_HTTP_TEST, network_id=NETWORKID_TEST, 32 | zk_seeds=seeds,zk_l2Key=l2Key, 33 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 34 | configs = client.configs_v3() 35 | accountData = client.get_account_v3() 36 | 37 | 38 | currentTime = time.time() 39 | createOrderRes = client.create_order_v3(symbol="BTC-USDT", side="SELL", 40 | type="MARKET", size="0.001", timestampSeconds= currentTime, 41 | price="60000") 42 | print(createOrderRes) 43 | 44 | # sample6 45 | # Create a TP/SL order 46 | # first, Set a slippage to get an acceptable slPrice or tpPrice 47 | #slippage is recommended to be greater than 0.1 48 | # when buying, the price = price*(1 + slippage). when selling, the price = price*(1 - slippage) 49 | slippage = decimal.Decimal("-0.1") 50 | slPrice = decimal.Decimal("58000") * (decimal.Decimal("1") + slippage) 51 | tpPrice = decimal.Decimal("79000") * (decimal.Decimal("1") - slippage) 52 | 53 | createOrderRes = client.create_order_v3(symbol="BTC-USDT", side="BUY", 54 | type="LIMIT", size="0.01", 55 | price="65000", 56 | isOpenTpslOrder=True, 57 | isSetOpenSl=True, 58 | slPrice=slPrice, 59 | slSide="SELL", 60 | slSize="0.01", 61 | slTriggerPrice="58000", 62 | isSetOpenTp=True, 63 | tpPrice=tpPrice, 64 | tpSide="SELL", 65 | tpSize="0.01", 66 | tpTriggerPrice="79000", 67 | ) 68 | print(createOrderRes) 69 | 70 | 71 | print("end, Apex Omni") 72 | 73 | 74 | -------------------------------------------------------------------------------- /tests/demo_deposit.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | from apexpro.helpers.util import wait_for_condition 6 | from apexpro.starkex.helpers import nonce_from_client_id 7 | 8 | root_path = os.path.abspath(__file__) 9 | root_path = '/'.join(root_path.split('/')[:-2]) 10 | sys.path.append(root_path) 11 | 12 | from apexpro.http_private import HttpPrivate 13 | from apexpro.constants import APEX_HTTP_TEST, NETWORKID_TEST, APEX_HTTP_MAIN, NETWORKID_MAIN 14 | 15 | print("Hello, Apexpro") 16 | priKey = "your eth private key" 17 | 18 | key = 'your apiKey-key from register' 19 | secret = 'your apiKey-secret from register' 20 | passphrase = 'your apiKey-passphrase from register' 21 | 22 | public_key = 'your stark_public_key from register' 23 | public_key_y_coordinate = 'your stark_public_key_y_coordinate from register' 24 | private_key = 'your stark_private_key from register' 25 | 26 | 27 | client = HttpPrivate(APEX_HTTP_MAIN, network_id=NETWORKID_MAIN, eth_private_key=priKey, 28 | stark_public_key=public_key, 29 | stark_private_key=private_key, 30 | stark_public_key_y_coordinate=public_key_y_coordinate, 31 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 32 | configs = client.configs() 33 | 34 | account = client.get_account() 35 | 36 | # If you have not approve usdc on eth, please approve first 37 | # Set allowance on the Starkware perpetual contract, for the deposit. 38 | #approve_tx_hash = client.eth.set_token_max_allowance( 39 | # client.eth.get_exchange_contract().address, 40 | #) 41 | print('Waiting for allowance...') 42 | # Don't worry if you encounter a timeout request while waiting. Execution on the chain takes a certain time 43 | #client.eth.wait_for_tx(approve_tx_hash) 44 | print('...done.') 45 | 46 | # Send an on-chain deposit. 47 | deposit_tx_hash = client.eth.deposit_to_exchange( 48 | client.account['positionId'], 49 | 0.1, 50 | ) 51 | print('Waiting for deposit...') 52 | # Don't worry if you encounter a timeout request while waiting. Execution on the chain takes a certain time 53 | 54 | client.eth.wait_for_tx(deposit_tx_hash) 55 | print('...done.') 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /tests/demo_private.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | root_path = os.path.abspath(__file__) 6 | root_path = '/'.join(root_path.split('/')[:-2]) 7 | sys.path.append(root_path) 8 | 9 | from apexpro.http_private import HttpPrivate 10 | 11 | from apexpro.constants import APEX_HTTP_TEST, NETWORKID_TEST 12 | 13 | print("Hello, Apexpro") 14 | # need api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase} for private api 15 | 16 | key = 'your apiKey-key from register' 17 | secret = 'your apiKey-secret from register' 18 | passphrase = 'your apiKey-passphrase from register' 19 | 20 | client = HttpPrivate(APEX_HTTP_TEST, network_id=NETWORKID_TEST, api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase}) 21 | configs = client.configs() 22 | 23 | userRes = client.get_user() 24 | print(userRes) 25 | 26 | modifyUserRes = client.modify_user(username="pythonTest",email="11@aa.com",emailNotifyGeneralEnable="false") 27 | print(modifyUserRes) 28 | 29 | accountRes = client.get_account() 30 | print(accountRes) 31 | 32 | 33 | transfersRes = client.transfers(limit=100,page=0,currencyId="USDC",chainIds="1,5,13") 34 | print(transfersRes) 35 | 36 | withdrawListRes = client.withdraw_list(limit=100,page=0,beginTimeInclusive=1651406864000,endTimeExclusive=1657105971171) 37 | print(withdrawListRes) 38 | 39 | uncommon_withdraw_feeRes = client.uncommon_withdraw_fee(amount="101000.1",chainId=5) 40 | print(uncommon_withdraw_feeRes) 41 | 42 | transfer_limitRes = client.transfer_limit(currencyId="USDC") 43 | print(transfer_limitRes) 44 | 45 | fillsRes = client.fills(limit=100,page=0,symbol="BTC-USDC",side="BUY") 46 | print(fillsRes) 47 | 48 | deleteOrderRes = client.delete_order(id="123456") 49 | print(deleteOrderRes) 50 | 51 | deleteOrderRes = client.delete_order_by_client_order_id(id="123456") 52 | print(deleteOrderRes) 53 | 54 | openOrdersRes = client.open_orders() 55 | print(openOrdersRes) 56 | 57 | deleteOrdersRes = client.delete_open_orders(symbol="BTC-USDC,ETH-USDC") 58 | print(deleteOrdersRes) 59 | 60 | historyOrdersRes = client.history_orders() 61 | print(historyOrdersRes) 62 | 63 | getOrderRes = client.get_order(id="123456") 64 | print(getOrderRes) 65 | 66 | getOrderRes = client.get_order_by_client_order_id(id="123456") 67 | print(getOrderRes) 68 | 69 | fundingRes = client.funding(limit=100,page=0,symbol="BTC-USDC",side="BUY") 70 | print(fundingRes) 71 | 72 | notifyListRes = client.notify_list(limit=100,page=0,unreadOnly="true",notifyCategory="1") 73 | print(notifyListRes) 74 | 75 | markNotifyReadRes = client.mark_notify_read(ids="113123,123123123") 76 | print(markNotifyReadRes) 77 | 78 | historicalPnlRes = client.historical_pnl(limit=100,page=0,beginTimeInclusive=1651406864000,endTimeExclusive=1657105971171,symbol="BTC-USDC") 79 | print(historicalPnlRes) 80 | 81 | yesterdayPnlRes = client.yesterday_pnl() 82 | print(yesterdayPnlRes) 83 | 84 | historyValueRes = client.history_value() 85 | print(historyValueRes) 86 | 87 | markAllNotifyReadRes = client.mark_all_notify_read() 88 | print(markAllNotifyReadRes) 89 | 90 | setInitialMarginRateRes = client.set_initial_margin_rate(symbol="BTC-USDC",initialMarginRate="0.1") 91 | print(setInitialMarginRateRes) 92 | 93 | 94 | print("end, Apexpro") 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /tests/demo_private_apikeys_v3.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from apexpro.http_private_sign import HttpPrivateSign 5 | 6 | root_path = os.path.abspath(__file__) 7 | root_path = '/'.join(root_path.split('/')[:-2]) 8 | sys.path.append(root_path) 9 | 10 | from apexpro.constants import NETWORKID_TEST, APEX_OMNI_HTTP_TEST 11 | 12 | print("Hello, Apex Omni") 13 | # need api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase} for private api 14 | 15 | key = 'your apiKey-key from register V3' 16 | secret = 'your apiKey-secret from register V3' 17 | passphrase = 'your apiKey-passphrase from register V3' 18 | 19 | seeds = 'your zk seeds from register' 20 | l2Key = 'your l2Key seeds from register' 21 | 22 | client = HttpPrivateSign(APEX_OMNI_HTTP_TEST, network_id=NETWORKID_TEST, 23 | zk_seeds=seeds,zk_l2Key=l2Key, 24 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 25 | 26 | configs = client.configs_v3() 27 | 28 | userRes = client.get_user_v3() 29 | print(userRes) 30 | 31 | accountRes = client.get_account_v3() 32 | print(accountRes) 33 | 34 | allApikeys = client.all_apikeys_v3() 35 | print(allApikeys) 36 | 37 | nonceRes = client.generate_nonce_v3(refresh="true", l2Key=l2Key,ethAddress=client.default_address, chainId=NETWORKID_TEST) 38 | 39 | 40 | generateRes = client.generate_api_key_v3(remark="test3",ips="127.0.0.1,127.0.0.2",nonce=nonceRes['data']['nonce']) 41 | print(generateRes) 42 | 43 | allApikeys = client.all_apikeys_v3() 44 | print(allApikeys) 45 | 46 | print("end, Apex Omni") 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /tests/demo_private_v2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | root_path = os.path.abspath(__file__) 6 | root_path = '/'.join(root_path.split('/')[:-2]) 7 | sys.path.append(root_path) 8 | 9 | from apexpro.http_private import HttpPrivate 10 | 11 | from apexpro.constants import APEX_HTTP_TEST, NETWORKID_TEST 12 | 13 | print("Hello, Apexpro") 14 | # need api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase} for private api 15 | 16 | key = 'your apiKey-key from register' 17 | secret = 'your apiKey-secret from register' 18 | passphrase = 'your apiKey-passphrase from register' 19 | 20 | 21 | client = HttpPrivate(APEX_HTTP_TEST, network_id=NETWORKID_TEST, api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase}) 22 | configs = client.configs_v2() 23 | 24 | #userRes = client.get_user() 25 | #print(userRes) 26 | 27 | 28 | #modifyUserRes = client.modify_user(username="pythonTest",email="11@aa.com",emailNotifyGeneralEnable="false") 29 | #print(modifyUserRes) 30 | 31 | accountRes = client.get_account_v2() 32 | print(accountRes) 33 | 34 | fillsRes = client.fills_v2(limit=100,page=0,symbol="BTC-USDC",side="BUY",token="USDC") 35 | print(fillsRes) 36 | 37 | transfersRes = client.transfers_v2(limit=100,page=0,currencyId="USDC",chainIds="1,5,13,97") 38 | print(transfersRes) 39 | 40 | withdrawListRes = client.withdraw_list_v2(limit=100,page=0,beginTimeInclusive=1651406864000,endTimeExclusive=1657105971171) 41 | print(withdrawListRes) 42 | 43 | uncommon_withdraw_feeRes = client.uncommon_withdraw_fee_v2(amount="101000.1",token="USDC", chainId=5) 44 | print(uncommon_withdraw_feeRes) 45 | 46 | transfer_limitRes = client.transfer_limit_v2(currencyId="USDC") 47 | print(transfer_limitRes) 48 | 49 | fillsRes = client.fills_v2(limit=100,page=0,symbol="BTC-USDC",side="BUY",token="USDC") 50 | print(fillsRes) 51 | 52 | deleteOrderRes = client.delete_order_v2(id="123456") 53 | print(deleteOrderRes) 54 | 55 | deleteOrderRes = client.delete_order_by_client_order_id_v2(id="123456") 56 | print(deleteOrderRes) 57 | 58 | openOrdersRes = client.open_orders_v2(token='USDC') 59 | print(openOrdersRes) 60 | 61 | deleteOrdersRes = client.delete_open_orders(symbol="BTC-USDC,ETH-USDC", token='USDC') 62 | print(deleteOrdersRes) 63 | 64 | historyOrdersRes = client.history_orders_v2(token='USDC') 65 | print(historyOrdersRes) 66 | 67 | getOrderRes = client.get_order_v2(id="123456") 68 | print(getOrderRes) 69 | 70 | getOrderRes = client.get_order_by_client_order_id_v2(id="123456") 71 | print(getOrderRes) 72 | 73 | fundingRes = client.funding_v2(limit=100,page=0,symbol="BTC-USDC",side="BUY",token='USDC') 74 | print(fundingRes) 75 | 76 | notifyListRes = client.notify_list(limit=100,page=0,unreadOnly="true",notifyCategory="1") 77 | print(notifyListRes) 78 | 79 | markNotifyReadRes = client.mark_notify_read(ids="113123,123123123") 80 | print(markNotifyReadRes) 81 | 82 | historicalPnlRes = client.historical_pnl_v2(limit=100,page=0,beginTimeInclusive=1651406864000,endTimeExclusive=1657105971171,symbol="BTC-USDC") 83 | print(historicalPnlRes) 84 | 85 | yesterdayPnlRes = client.yesterday_pnl_v2(token='USDC') 86 | print(yesterdayPnlRes) 87 | 88 | historyValueRes = client.history_value_v2(token='USDC') 89 | print(historyValueRes) 90 | 91 | markAllNotifyReadRes = client.mark_all_notify_read() 92 | print(markAllNotifyReadRes) 93 | 94 | setInitialMarginRateRes = client.set_initial_margin_rate_v2(symbol="BTC-USDC",initialMarginRate="0.1",token='USDC') 95 | print(setInitialMarginRateRes) 96 | 97 | 98 | print("end, Apexpro") 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /tests/demo_private_v3.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from datetime import time 4 | 5 | from apexpro.http_private_v3 import HttpPrivate_v3 6 | 7 | root_path = os.path.abspath(__file__) 8 | root_path = '/'.join(root_path.split('/')[:-2]) 9 | sys.path.append(root_path) 10 | 11 | from apexpro.constants import APEX_OMNI_HTTP_MAIN, \ 12 | NETWORKID_OMNI_MAIN_ARB 13 | 14 | print("Hello, Apex Omni") 15 | # need api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase} for private api 16 | 17 | key = 'your apiKey-key from register V3' 18 | secret = 'your apiKey-secret from register V3' 19 | passphrase = 'your apiKey-passphrase from register V3' 20 | 21 | client = HttpPrivate_v3(APEX_OMNI_HTTP_MAIN, network_id=NETWORKID_OMNI_MAIN_ARB, api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase}) 22 | configs = client.configs_v3() 23 | 24 | userRes = client.get_user_v3() 25 | print(userRes) 26 | 27 | 28 | accountRes = client.get_account_v3() 29 | print(accountRes) 30 | 31 | currentTime = time.time()*1000 32 | withdrawRes = client.withdraws_by_time_and_status_v3(startTime=int(currentTime), status='success') 33 | 34 | accountBalanceRes = client.get_account_balance_v3() 35 | print(accountBalanceRes) 36 | 37 | fillsRes = client.fills_v3(limit=100,page=0,symbol="BTC-USDT",side="BUY",token="USDT") 38 | print(fillsRes) 39 | 40 | transfersRes = client.transfers_v3(limit=100) 41 | print(transfersRes) 42 | 43 | transferRes = client.transfer_v3(ids='586213648326721628') 44 | print(transferRes) 45 | 46 | transfersRes = client.contract_transfers_v3(limit=100) 47 | print(transfersRes) 48 | 49 | transferRes = client.contract_transfer_v3(ids='588301879870489180') 50 | print(transferRes) 51 | 52 | #deleteOrderRes = client.delete_order_v3(id="588302655921587036") 53 | #print(deleteOrderRes) 54 | 55 | #deleteOrderRes = client.delete_order_by_client_order_id_v3(id="123456") 56 | #print(deleteOrderRes) 57 | 58 | openOrdersRes = client.open_orders_v3() 59 | print(openOrdersRes) 60 | 61 | deleteOrdersRes = client.delete_open_orders_v3(symbol="BTC-USDT",) 62 | print(deleteOrdersRes) 63 | 64 | historyOrdersRes = client.history_orders_v3(token='USDT') 65 | print(historyOrdersRes) 66 | 67 | getOrderRes = client.get_order_v3(id="123456") 68 | print(getOrderRes) 69 | 70 | getOrderRes = client.get_order_by_client_order_id_v3(id="123456") 71 | print(getOrderRes) 72 | 73 | fundingRes = client.funding_v3(limit=100) 74 | print(fundingRes) 75 | 76 | historicalPnlRes = client.historical_pnl_v3(limit=100) 77 | print(historicalPnlRes) 78 | 79 | yesterdayPnlRes = client.yesterday_pnl_v3() 80 | print(yesterdayPnlRes) 81 | 82 | historyValueRes = client.history_value_v3() 83 | print(historyValueRes) 84 | 85 | setInitialMarginRateRes = client.set_initial_margin_rate_v3(symbol="BTC-USDT",initialMarginRate="0.05") 86 | print(setInitialMarginRateRes) 87 | 88 | 89 | print("end, Apex Omni") 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /tests/demo_public.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | root_path = os.path.abspath(__file__) 5 | root_path = '/'.join(root_path.split('/')[:-2]) 6 | sys.path.append(root_path) 7 | 8 | 9 | from apexpro.constants import APEX_HTTP_TEST, APEX_HTTP_MAIN 10 | from apexpro.http_public import HttpPublic 11 | 12 | print("Hello, Apexpro") 13 | 14 | client = HttpPublic(APEX_HTTP_MAIN) 15 | #print(client.history_funding(symbol="BTC-USDC")) 16 | print(client.klines(symbol="BTCUSDC",interval=60,start=1718683200, end=1718683200, limit=1)) 17 | print(client.server_time()) 18 | print(client.configs()) 19 | print(client.depth(symbol="BTC-USDC")) 20 | print(client.trades(symbol="BTC-USDC")) 21 | print(client.klines(symbol="BTC-USDC",interval="15")) 22 | print(client.ticker(symbol="BTC-USDC")) 23 | print(client.history_funding(symbol="BTC-USDC")) 24 | 25 | print(client.depth(symbol="ETH-USDC",limit=50)) 26 | print(client.trades(symbol="ETH-USDC",limit=50)) 27 | print(client.klines(symbol="ETH-USDC",interval="15")) 28 | print(client.history_funding(symbol="ETH-USDC",limit=100,page=0,beginTimeInclusive=1662348573000,endTimeExclusive=1662434973000)) 29 | 30 | -------------------------------------------------------------------------------- /tests/demo_public_v2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | root_path = os.path.abspath(__file__) 5 | root_path = '/'.join(root_path.split('/')[:-2]) 6 | sys.path.append(root_path) 7 | 8 | 9 | from apexpro.constants import APEX_HTTP_TEST, APEX_HTTP_MAIN 10 | from apexpro.http_public import HttpPublic 11 | 12 | print("Hello, Apexpro") 13 | 14 | client = HttpPublic(APEX_HTTP_MAIN) 15 | print(client.history_funding_v2(symbol="BTC-USDT")) 16 | print(client.klines(symbol="ETHUSDT",interval=5,start=1681463600, end=1681563600, limit=5)) 17 | print(client.server_time()) 18 | print(client.configs_v2()) 19 | print(client.depth(symbol="BTC-USDC")) 20 | print(client.trades(symbol="BTC-USDC")) 21 | print(client.klines(symbol="BTC-USDT",interval="15")) 22 | print(client.ticker(symbol="BTC-USDT")) 23 | print(client.history_funding(symbol="BTC-USDT")) 24 | 25 | print(client.depth(symbol="ETH-USDT",limit=50)) 26 | print(client.trades(symbol="ETH-USDT",limit=50)) 27 | print(client.klines(symbol="ETH-USDT",interval="15")) 28 | print(client.history_funding_v2(symbol="ETH-USDT",limit=100,page=0,beginTimeInclusive=1662348573000,endTimeExclusive=1662434973000)) 29 | 30 | -------------------------------------------------------------------------------- /tests/demo_public_v3.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | root_path = os.path.abspath(__file__) 5 | root_path = '/'.join(root_path.split('/')[:-2]) 6 | sys.path.append(root_path) 7 | 8 | 9 | from apexpro.constants import APEX_OMNI_HTTP_MAIN 10 | from apexpro.http_public import HttpPublic 11 | 12 | print("Hello, ApexOmni") 13 | 14 | # APEX_OMNI_HTTP_MAIN for mainnet, APEX_OMNI_HTTP_TEST for testnet 15 | client = HttpPublic(APEX_OMNI_HTTP_MAIN) 16 | print(client.klines_v3(symbol="ETHUSDT",interval=5,start=1718358480, end=1718950620, limit=5)) 17 | print(client.configs_v3()) 18 | print(client.depth_v3(symbol="BTCUSDT")) 19 | print(client.trades_v3(symbol="BTCUSDT")) 20 | print(client.klines_v3(symbol="BTCUSDT",interval="15")) 21 | print(client.ticker_v3(symbol="BTCUSDT")) 22 | print(client.history_funding_v3(symbol="BTC-USDT")) 23 | 24 | -------------------------------------------------------------------------------- /tests/demo_register.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | root_path = os.path.abspath(__file__) 5 | root_path = '/'.join(root_path.split('/')[:-2]) 6 | sys.path.append(root_path) 7 | 8 | from apexpro.http_private import HttpPrivate 9 | from apexpro.constants import APEX_HTTP_TEST, NETWORKID_TEST, APEX_HTTP_MAIN, NETWORKID_MAIN 10 | 11 | print("Hello, Apexpro") 12 | priKey = "your eth private key" 13 | 14 | client = HttpPrivate(APEX_HTTP_MAIN, network_id=NETWORKID_MAIN, eth_private_key=priKey) 15 | configs = client.configs() 16 | 17 | stark_key_pair_with_y_coordinate = client.derive_stark_key(client.default_address) 18 | 19 | nonceRes = client.generate_nonce(starkKey=stark_key_pair_with_y_coordinate['public_key'],ethAddress=client.default_address,chainId=NETWORKID_MAIN) 20 | 21 | #api_key = client.recover_api_key_credentials(nonce=nonceRes['data']['nonce'], ethereum_address=client.default_address) 22 | #print(api_key) 23 | regRes = client.register_user(nonce=nonceRes['data']['nonce'],starkKey=stark_key_pair_with_y_coordinate['public_key'],stark_public_key_y_coordinate=stark_key_pair_with_y_coordinate['public_key_y_coordinate'],ethereum_address=client.default_address) 24 | key = regRes['data']['apiKey']['key'] 25 | secret = regRes['data']['apiKey']['secret'] 26 | passphrase = regRes['data']['apiKey']['passphrase'] 27 | 28 | #back stark_key_pair, apiKey,and accountId for private Api or create-oreder or withdraw 29 | print(stark_key_pair_with_y_coordinate) 30 | print(regRes['data']['account']['positionId']) 31 | print(regRes['data']['apiKey']) 32 | print("end, Apexpro") 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/demo_register_mul_address.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | root_path = os.path.abspath(__file__) 5 | root_path = '/'.join(root_path.split('/')[:-2]) 6 | sys.path.append(root_path) 7 | 8 | from apexpro.http_private import HttpPrivate 9 | from apexpro.constants import APEX_HTTP_TEST, NETWORKID_TEST, APEX_HTTP_MAIN, NETWORKID_MAIN 10 | 11 | print("Hello, Apexpro") 12 | priKey = "your eth private key" 13 | 14 | client = HttpPrivate(APEX_HTTP_MAIN, network_id=NETWORKID_MAIN, eth_private_key=priKey) 15 | configs = client.configs() 16 | 17 | stark_key_pair_with_y_coordinate = client.derive_stark_key(client.default_address) 18 | 19 | nonceRes = client.generate_nonce(starkKey=stark_key_pair_with_y_coordinate['public_key'],ethAddress=client.default_address,chainId=NETWORKID_MAIN) 20 | 21 | #api_key = client.recover_api_key_credentials(nonce=nonceRes['data']['nonce'], ethereum_address=client.default_address) 22 | #print(api_key) 23 | 24 | regRes = client.register_user(nonce=nonceRes['data']['nonce'],starkKey=stark_key_pair_with_y_coordinate['public_key'],stark_public_key_y_coordinate=stark_key_pair_with_y_coordinate['public_key_y_coordinate'],ethereum_address=client.default_address, 25 | eth_mul_address="your mul eth address") 26 | key = regRes['data']['apiKey']['key'] 27 | secret = regRes['data']['apiKey']['secret'] 28 | passphrase = regRes['data']['apiKey']['passphrase'] 29 | 30 | #back stark_key_pair, apiKey,and accountId for private Api or create-oreder or withdraw 31 | print(stark_key_pair_with_y_coordinate) 32 | print(regRes['data']['account']['positionId']) 33 | print(regRes['data']['apiKey']) 34 | print("end, Apexpro") 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/demo_register_mul_address_v3_step1.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | from apexpro.http_private_v3 import HttpPrivate_v3 6 | 7 | root_path = os.path.abspath(__file__) 8 | root_path = '/'.join(root_path.split('/')[:-2]) 9 | sys.path.append(root_path) 10 | 11 | from apexpro.constants import APEX_OMNI_HTTP_MAIN, NETWORKID_OMNI_MAIN_ARB 12 | 13 | print("Hello, Apex Omni") 14 | priKey = "your eth private key" 15 | 16 | client = HttpPrivate_v3(APEX_OMNI_HTTP_MAIN, network_id=NETWORKID_OMNI_MAIN_ARB, eth_private_key=priKey) 17 | configs = client.configs_v3() 18 | 19 | zkKeys = client.derive_zk_key(client.default_address) 20 | 21 | nonceRes = client.generate_nonce_v3(refresh="false", l2Key=zkKeys['l2Key'],ethAddress=client.default_address, chainId=NETWORKID_OMNI_MAIN_ARB) 22 | 23 | regRes = client.register_user_v3(nonce=nonceRes['data']['nonce'],l2Key=zkKeys['l2Key'], seeds=zkKeys['seeds'],ethereum_address=client.default_address, 24 | eth_mul_address="your mul eth address", isLpAccount=True) 25 | print(regRes) 26 | key = regRes['data']['apiKey']['key'] 27 | secret = regRes['data']['apiKey']['secret'] 28 | passphrase = regRes['data']['apiKey']['passphrase'] 29 | 30 | time.sleep(10) 31 | accountRes = client.get_account_v3() 32 | print(accountRes) 33 | 34 | #back stark_key_pair, apiKey,and accountId for private Api or create-oreder or withdraw 35 | 36 | seeds = zkKeys.get('seeds') 37 | l2Key = zkKeys.get('l2Key') 38 | pubKeyHash = zkKeys.get('pubKeyHash') 39 | 40 | #back zkKeys, seeds, l2Key and pubKeyHash for register_step2 41 | print(zkKeys) 42 | print(regRes['data']['account']['id']) 43 | print(regRes['data']['apiKey']) 44 | 45 | print("end, Apex Omni") 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/demo_register_mul_address_v3_step2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | from threading import Timer 5 | 6 | from apexpro.http_private_v3 import HttpPrivate_v3 7 | 8 | root_path = os.path.abspath(__file__) 9 | root_path = '/'.join(root_path.split('/')[:-2]) 10 | sys.path.append(root_path) 11 | 12 | from apexpro.http_private import HttpPrivate 13 | from apexpro.constants import APEX_HTTP_TEST, NETWORKID_TEST, APEX_HTTP_MAIN, NETWORKID_MAIN, APEX_OMNI_HTTP_TEST, \ 14 | APEX_OMNI_HTTP_MAIN, NETWORKID_OMNI_MAIN_ARB 15 | 16 | print("Hello, Apex Omni") 17 | 18 | key = 'your apiKey-key from register' 19 | secret = 'your apiKey-secret from register' 20 | passphrase = 'your apiKey-passphrase from register' 21 | 22 | seeds = 'your zk seeds from register' 23 | l2Key = 'your l2Key seeds from register' 24 | pubKeyHash = 'your l2Key seeds from pubKeyHash' 25 | 26 | 27 | client = HttpPrivate_v3(APEX_OMNI_HTTP_MAIN, network_id=NETWORKID_OMNI_MAIN_ARB, api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase}) 28 | configs = client.configs_v3() 29 | 30 | accountRes = client.get_account_v3() 31 | print(accountRes) 32 | 33 | changeRes = client.change_pub_key_v3(chainId=NETWORKID_OMNI_MAIN_ARB, seeds= seeds, zkAccountId = accountRes.get('spotAccount').get('zkAccountId'), subAccountId = accountRes.get('spotAccount').get('defaultSubAccountId'), 34 | newPkHash=pubKeyHash, nonce=accountRes.get('spotAccount').get('nonce'), l2Key= l2Key, ethSignatureType='Onchain') 35 | print(changeRes) 36 | 37 | time.sleep(10) 38 | accountRes = client.get_account_v3() 39 | print(accountRes) 40 | print("end, Apex Omni") 41 | 42 | -------------------------------------------------------------------------------- /tests/demo_register_v2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | root_path = os.path.abspath(__file__) 5 | root_path = '/'.join(root_path.split('/')[:-2]) 6 | sys.path.append(root_path) 7 | 8 | from apexpro.http_private import HttpPrivate 9 | from apexpro.constants import APEX_HTTP_TEST, NETWORKID_TEST, APEX_HTTP_MAIN, NETWORKID_MAIN 10 | 11 | print("Hello, Apexpro") 12 | priKey = "your eth private key" 13 | 14 | client = HttpPrivate(APEX_HTTP_TEST, network_id=NETWORKID_TEST, eth_private_key=priKey) 15 | configs = client.configs_v2() 16 | 17 | stark_key_pair_with_y_coordinate = client.derive_stark_key(client.default_address) 18 | 19 | nonceRes = client.generate_nonce(refresh="false", starkKey=stark_key_pair_with_y_coordinate['public_key'],ethAddress=client.default_address, chainId=NETWORKID_TEST) 20 | 21 | #api_key = client.recover_api_key_credentials(nonce=nonceRes['data']['nonce'], ethereum_address=client.default_address) 22 | #print(api_key) 23 | regRes = client.register_user_v2(token='USDT', nonce=nonceRes['data']['nonce'],starkKey=stark_key_pair_with_y_coordinate['public_key'],stark_public_key_y_coordinate=stark_key_pair_with_y_coordinate['public_key_y_coordinate'],ethereum_address=client.default_address) 24 | key = regRes['data']['apiKey']['key'] 25 | secret = regRes['data']['apiKey']['secret'] 26 | passphrase = regRes['data']['apiKey']['passphrase'] 27 | 28 | #back stark_key_pair, apiKey,and accountId for private Api or create-oreder or withdraw 29 | print(stark_key_pair_with_y_coordinate) 30 | print(regRes['data']['account']['positionId']) 31 | print(regRes['data']['apiKey']) 32 | print("end, Apexpro") 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/demo_register_v3.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | from apexpro.http_private_v3 import HttpPrivate_v3 6 | 7 | root_path = os.path.abspath(__file__) 8 | root_path = '/'.join(root_path.split('/')[:-2]) 9 | sys.path.append(root_path) 10 | 11 | from apexpro.constants import APEX_OMNI_HTTP_MAIN, NETWORKID_OMNI_MAIN_ARB, NETWORKID_MAIN 12 | 13 | print("Hello, Apex Omni") 14 | priKey = "your eth private key" 15 | 16 | client = HttpPrivate_v3(APEX_OMNI_HTTP_MAIN, network_id=NETWORKID_MAIN, eth_private_key=priKey) 17 | configs = client.configs_v3() 18 | 19 | zkKeys = client.derive_zk_key(client.default_address) 20 | print(zkKeys) 21 | print(zkKeys['seeds']) 22 | print(zkKeys['l2Key']) 23 | print(zkKeys['pubKeyHash']) 24 | 25 | nonceRes = client.generate_nonce_v3(refresh="false", l2Key=zkKeys['l2Key'],ethAddress=client.default_address, chainId=NETWORKID_OMNI_MAIN_ARB) 26 | 27 | regRes = client.register_user_v3(nonce=nonceRes['data']['nonce'],l2Key=zkKeys['l2Key'], seeds=zkKeys['seeds'],ethereum_address=client.default_address) 28 | 29 | print(regRes['data']['apiKey']['key']) 30 | print(regRes['data']['apiKey']['secret']) 31 | print(regRes['data']['apiKey']['passphrase']) 32 | 33 | time.sleep(10) 34 | accountRes = client.get_account_v3() 35 | print(accountRes) 36 | 37 | #back zkKeys, apiKey,and accountId for private Api or create-order transfer or withdraw 38 | 39 | print(regRes['data']['apiKey']) 40 | 41 | changeRes = client.change_pub_key_v3(chainId=NETWORKID_OMNI_MAIN_ARB, seeds=zkKeys.get('seeds'), ethPrivateKey=priKey, zkAccountId = accountRes.get('spotAccount').get('zkAccountId'), subAccountId = accountRes.get('spotAccount').get('defaultSubAccountId'), 42 | newPkHash = zkKeys.get('pubKeyHash'), nonce= accountRes.get('spotAccount').get('nonce'), l2Key= zkKeys.get('l2Key')) 43 | print(changeRes) 44 | 45 | time.sleep(10) 46 | accountRes = client.get_account_v3() 47 | print(accountRes) 48 | print("end, Apexpro") 49 | 50 | -------------------------------------------------------------------------------- /tests/demo_sign.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from apexpro.helpers.request_helpers import calc_bind_owner_key_sig_hash, starkex_sign, starkex_verify 5 | 6 | root_path = os.path.abspath(__file__) 7 | root_path = '/'.join(root_path.split('/')[:-2]) 8 | sys.path.append(root_path) 9 | 10 | 11 | print("Hello, Apexpro") 12 | 13 | hash = calc_bind_owner_key_sig_hash('your stark_key_pair pubic key', "your eth address") 14 | print(hash.hex()) 15 | 16 | signature = starkex_sign(hash, 'your stark_key_pair private key') 17 | print("signature:" + signature) 18 | 19 | bVerify = starkex_verify(hash, signature, 'your stark_key_pair pubic key') 20 | print("Verify:" +str( bVerify)) 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/demo_stark_key_sign.py: -------------------------------------------------------------------------------- 1 | import decimal 2 | import os 3 | import sys 4 | import time 5 | 6 | from apexpro.helpers.util import round_size 7 | from apexpro.http_private_stark_key_sign import HttpPrivateStark 8 | 9 | root_path = os.path.abspath(__file__) 10 | root_path = '/'.join(root_path.split('/')[:-2]) 11 | sys.path.append(root_path) 12 | 13 | from apexpro.constants import APEX_HTTP_TEST, NETWORKID_TEST, APEX_HTTP_MAIN, NETWORKID_MAIN 14 | 15 | print("Hello, Apexpro") 16 | 17 | 18 | # need api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase} for private api 19 | # need starkey for withdraw and createOrder 20 | 21 | key = 'your apiKey-key from register' 22 | secret = 'your apiKey-secret from register' 23 | passphrase = 'your apiKey-passphrase from register' 24 | 25 | public_key = 'your stark_public_key from register' 26 | public_key_y_coordinate = 'your stark_public_key_y_coordinate from register' 27 | private_key = 'your stark_private_key from register' 28 | 29 | 30 | client = HttpPrivateStark(APEX_HTTP_TEST, network_id=NETWORKID_TEST, 31 | stark_public_key=public_key, 32 | stark_private_key=private_key, 33 | stark_public_key_y_coordinate=public_key_y_coordinate, 34 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 35 | configs = client.configs() 36 | client.get_user() 37 | print(client.get_account()) 38 | 39 | # sample1 40 | # When create an order, optimize the size of the order according to the stepSize of the currency symbol, 41 | # and optimize the price of the order according to the tickSize 42 | symbolData = {} 43 | for k, v in enumerate(configs.get('data').get('perpetualContract')): 44 | if v.get('symbol') == "BTC-USDC": 45 | symbolData = v 46 | 47 | print(round_size("0.0116", symbolData.get('stepSize'))) 48 | print(round_size("25555.8", symbolData.get('tickSize'))) 49 | 50 | # sample2 51 | # Create a limit order 52 | currentTime = time.time() 53 | limitFeeRate = client.account['takerFeeRate'] 54 | 55 | size = round_size("0.01", symbolData.get('stepSize')) 56 | price = round_size("28888.5", symbolData.get('tickSize')) 57 | createOrderRes = client.create_order(symbol="BTC-USDC", side="BUY", 58 | type="LIMIT", size=size, expirationEpochSeconds= currentTime, 59 | price=price, limitFeeRate=limitFeeRate) 60 | print(createOrderRes) 61 | 62 | # sample3 63 | # Create a conditional order 64 | 65 | createOrderRes = client.create_order(symbol="ETH-USDC", side="BUY", 66 | type="STOP_LIMIT", size="0.01",expirationEpochSeconds= currentTime, 67 | price="1811.5", limitFeeRate=limitFeeRate, triggerPriceType="INDEX", triggerPrice="1811") 68 | print(createOrderRes) 69 | 70 | # sample4 71 | # Create a market order 72 | # first, get a worstPrice from server.( market order price must not none) 73 | 74 | worstPrice = client.get_worst_price(symbol="BTC-USDC", side="SELL", size="0.1") 75 | price = worstPrice['data']['worstPrice'] 76 | 77 | createOrderRes = client.create_order(symbol="BTC-USDC", side="SELL", 78 | type="MARKET", size="1", price=price, limitFeeRate=limitFeeRate, 79 | expirationEpochSeconds= currentTime ) 80 | print(createOrderRes) 81 | 82 | # sample5 83 | # Create a Position TP/SL order 84 | # first, Set a slippage to get an acceptable price 85 | # if timeInForce="GOOD_TIL_CANCEL" or "POST_ONLY", slippage is recommended to be greater than 0.1 86 | # if timeInForce="FILL_OR_KILL" or "IMMEDIATE_OR_CANCEL", slippage is recommended to be greater than 0.2 87 | # when buying, the price = price*(1 + slippage). when selling, the price = price*(1 - slippage) 88 | 89 | slippage = decimal.Decimal("0.1") 90 | price = round_size(decimal.Decimal("28888") * (decimal.Decimal("1") + slippage), symbolData.get('tickSize')) 91 | 92 | createOrderRes = client.create_order(symbol="BTC-USDC", side="BUY", isPositionTpsl = True, reduceOnly= True, 93 | type="TAKE_PROFIT_MARKET", size="0.1", price=price, limitFeeRate=limitFeeRate, 94 | expirationEpochSeconds= currentTime, triggerPriceType="INDEX", triggerPrice="28888" ) 95 | print(createOrderRes) 96 | 97 | # sample6 98 | # Create a TP/SL order 99 | # first, Set a slippage to get an acceptable slPrice or tpPrice 100 | #slippage is recommended to be greater than 0.1 101 | # when buying, the price = price*(1 + slippage). when selling, the price = price*(1 - slippage) 102 | slippage = decimal.Decimal("-0.1") 103 | slPrice = round_size(decimal.Decimal("38000") * (decimal.Decimal("1") + slippage), symbolData.get('tickSize')) 104 | tpPrice = round_size(decimal.Decimal("49000") * (decimal.Decimal("1") + slippage), symbolData.get('tickSize')) 105 | 106 | createOrderRes = client.create_order(symbol="BTC-USDC", side="BUY", 107 | type="LIMIT", size="0.01", expirationEpochSeconds= currentTime, 108 | price="41000", limitFeeRate=limitFeeRate, 109 | isOpenTpslOrder=True, 110 | isSetOpenSl=True, 111 | slPrice=slPrice, 112 | slSide="SELL", 113 | slSize="0.01", 114 | slTriggerPrice="38000", 115 | isSetOpenTp=True, 116 | tpPrice=tpPrice, 117 | tpSide="SELL", 118 | tpSize="0.01", 119 | tpTriggerPrice="49000", 120 | ) 121 | print(createOrderRes) 122 | 123 | 124 | #createWithdrawRes = client.create_withdrawal(amount='1001',expirationEpochSeconds= currentTime,asset='USDC') 125 | #print(createWithdrawRes) 126 | 127 | #feeRes = client.uncommon_withdraw_fee(amount='1002',chainId='5') 128 | #print(feeRes) 129 | #fastWithdrawRes = client.fast_withdrawal(amount='1002',expirationEpochSeconds= currentTime,asset='USDC',fee=feeRes['data']['fee']) 130 | #print(fastWithdrawRes) 131 | 132 | deleteOrderRes = client.delete_open_orders(symbol="BTC-USDC") 133 | print(deleteOrderRes) 134 | 135 | deleteOrderRes = client.delete_open_orders() 136 | print(deleteOrderRes) 137 | 138 | 139 | feeRes = client.uncommon_withdraw_fee(amount='1003',chainId='97') 140 | print(feeRes) 141 | crossWithdrawRes = client.cross_chain_withdraw(amount='1003',expirationEpochSeconds= currentTime,asset='USDC',fee=feeRes['data']['fee'],chainId='97') 142 | print(crossWithdrawRes) 143 | 144 | print("end, Apexpro") 145 | 146 | 147 | -------------------------------------------------------------------------------- /tests/demo_stark_key_sign_v2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | from apexpro.http_private_stark_key_sign import HttpPrivateStark 6 | 7 | root_path = os.path.abspath(__file__) 8 | root_path = '/'.join(root_path.split('/')[:-2]) 9 | sys.path.append(root_path) 10 | 11 | from apexpro.constants import APEX_HTTP_TEST, NETWORKID_TEST, APEX_HTTP_MAIN, NETWORKID_MAIN 12 | 13 | print("Hello, Apexpro") 14 | 15 | # need api_key_credentials={'key': key,'secret': secret, 'passphrase': passphrase} for private api 16 | # need starkey for withdraw and createOrder 17 | 18 | 19 | key = 'your apiKey-key from register' 20 | secret = 'your apiKey-secret from register' 21 | passphrase = 'your apiKey-passphrase from register' 22 | 23 | public_key = 'your stark_public_key from register' 24 | public_key_y_coordinate = 'your stark_public_key_y_coordinate from register' 25 | private_key = 'your stark_private_key from register' 26 | 27 | 28 | client = HttpPrivateStark(APEX_HTTP_TEST, network_id=NETWORKID_TEST, 29 | stark_public_key=public_key, 30 | stark_private_key=private_key, 31 | stark_public_key_y_coordinate=public_key_y_coordinate, 32 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 33 | configs = client.configs_v2() 34 | client.get_user() 35 | print(client.get_account_v2()) 36 | 37 | print(client.get_account_balance_v2()) 38 | 39 | currentTime = time.time() 40 | 41 | feeRes = client.uncommon_withdraw_fee_v2(amount='2',chainId='1', token='USDC',) 42 | print(feeRes) 43 | fastWithdrawRes = client.fast_withdrawal_v2(amount='2',expirationEpochSeconds= currentTime,asset='USDC',fee=feeRes['data']['fee']) 44 | 45 | createWithdrawRes = client.create_withdrawal_v2(amount='1',expirationEpochSeconds= currentTime,asset='USDC') 46 | print(createWithdrawRes) 47 | 48 | historyOrdersRes = client.history_orders_v2(token="USDC") 49 | print(historyOrdersRes) 50 | 51 | orderFills = client.order_fills_v2(orderId='498441108374684453') 52 | print(orderFills) 53 | 54 | limitFeeRate = '0.0005' 55 | 56 | deleteOrdersRes = client.delete_open_orders_v2(token="USDC") 57 | print(deleteOrdersRes) 58 | createOrderRes = client.create_order_v2(symbol="BTC-USDC", side="SELL", 59 | type="LIMIT", size="0.01",expirationEpochSeconds= currentTime, 60 | price="36890", limitFeeRate=limitFeeRate) 61 | 62 | 63 | #print(createOrderRes) 64 | 65 | fillsRes = client.fills_v2(limit=100,page=0,symbol="BTC-USDC",token="USDC") 66 | print(fillsRes) 67 | 68 | 69 | openOrderRes = client.open_orders_v2(token='USDC') 70 | print(openOrderRes) 71 | 72 | 73 | deleteOrdersRes = client.delete_open_orders_v2(token="USDC") 74 | print(deleteOrdersRes) 75 | 76 | historyOrdersRes = client.history_orders_v2(token="USDC") 77 | print(historyOrdersRes) 78 | 79 | openOrderRes = client.open_orders_v2(token='USDC') 80 | print(openOrderRes) 81 | 82 | feeRes = client.uncommon_withdraw_fee_v2(amount='2',chainId='97', token='USDC') 83 | print(feeRes) 84 | crossWithdrawRes = client.cross_chain_withdraw_v2(amount='2',expirationEpochSeconds= currentTime,asset='USDC',fee=feeRes['data']['fee'],chainId='97') 85 | print(crossWithdrawRes) 86 | 87 | print("end, Apexpro") 88 | -------------------------------------------------------------------------------- /tests/demo_transfer_v3.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import urllib.parse 4 | 5 | from apexpro.helpers.request_helpers import random_client_id 6 | 7 | from apexpro.http_private_sign import HttpPrivateSign 8 | import os 9 | import sys 10 | 11 | from apexpro.http_private_sign import HttpPrivateSign 12 | 13 | root_path = os.path.abspath(__file__) 14 | root_path = '/'.join(root_path.split('/')[:-2]) 15 | sys.path.append(root_path) 16 | 17 | from apexpro.constants import NETWORKID_TEST, APEX_OMNI_HTTP_TEST 18 | 19 | print("Hello, Apex Omni") 20 | 21 | key = 'your apiKey-key from register' 22 | secret = 'your apiKey-secret from register' 23 | passphrase = 'your apiKey-passphrase from register' 24 | 25 | seeds = 'your zk seeds from register' 26 | l2Key = 'your l2Key seeds from register' 27 | 28 | 29 | client = HttpPrivateSign(APEX_OMNI_HTTP_TEST, network_id=NETWORKID_TEST, 30 | zk_seeds=seeds,zk_l2Key=l2Key, 31 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}) 32 | configs = client.configs_v3() 33 | accountData = client.get_account_v3() 34 | 35 | #smple1 withdraw 36 | #createWithdrawRes = client.create_withdrawal_v3(amount='3',asset='USDT', toChainId=3) 37 | #print(createWithdrawRes) 38 | 39 | #smple2 fast withdraw 40 | #withdraw_feeRes = client.withdraw_fee_v3(amount="3",chainIds="3",tokenId='140') 41 | #print(withdraw_feeRes) 42 | #createWithdrawRes = client.create_withdrawal_v3(amount='3',asset='USDT', toChainId=3, fee=withdraw_feeRes.get('data').get('withdrawFeeAndPoolBalances')[0].get('fee'), isFastWithdraw=True) 43 | #print(createWithdrawRes) 44 | 45 | #smple3 transfer_out, from fund account to contract account 46 | #createTransferRes = client.create_transfer_out_v3(amount='3.4359738368',asset='USDT') 47 | #print(createTransferRes) 48 | 49 | #createTransferRes = client.create_transfer_out_v3(amount='0.01',asset='ETH') 50 | #print(createTransferRes) 51 | 52 | #smple4 contract transfer_out, from contract account to fund account 53 | #createContractTransferRes = client.create_contract_transfer_out_v3(amount='0.005',asset='ETH') 54 | #print(createContractTransferRes) 55 | 56 | #smple5 contract contract_transfer_to_address, from one contract account to another contract account 57 | #createContractTransferRes = client.create_contract_transfer_to_address_v3(amount='1.1',asset='USDT',receiverAddress='0xfab6256aeef3be7805d3138be8fe1369f716ebc5',receiverAccountId='585750146675900485',receiverL2Key='0x04a234f299958150707451f649208fd085680bf3e1be432acb533eb2cc06082a') 58 | #print(createContractTransferRes) 59 | 60 | #smple6 manual-create-repayment 61 | 62 | #clientId = random_client_id() 63 | #repaymentPriceRes = client.get_repayment_price_v3(repaymentPriceTokens='ETH|0.001', clientId=clientId) 64 | #print(repaymentPriceRes) 65 | # the clientId for create_manual_repayment_v3 is the same as get_repayment_price_v3. 66 | #repaymentTokens = repaymentPriceRes.get('data').get('repaymentTokens')[0].get('token')+'|'+repaymentPriceRes.get('data').get('repaymentTokens')[0].get('price')+'|'+repaymentPriceRes.get('data').get('repaymentTokens')[0].get('size') 67 | #repaymentRes = client.create_manual_repayment_v3(repaymentTokens=repaymentTokens, poolRepaymentTokens=repaymentTokens, clientId=clientId) 68 | #print(repaymentRes) 69 | 70 | print("end, Apexpro") 71 | 72 | 73 | -------------------------------------------------------------------------------- /tests/demo_ws.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from apexpro.constants import APEX_WS_TEST 4 | from apexpro.websocket_api import WebSocket 5 | 6 | key = 'your apiKey-key from register' 7 | secret = 'your apiKey-secret from register' 8 | passphrase = 'your apiKey-passphrase from register' 9 | 10 | # Connect with authentication! 11 | ws_client = WebSocket( 12 | endpoint=APEX_WS_TEST, 13 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}, 14 | ) 15 | 16 | def handle_account(message): 17 | print(message) 18 | contents_data = message["contents"] 19 | print(len(contents_data)) 20 | 21 | def h1(message): 22 | print(1, message) 23 | def h2(message): 24 | print(2, message) 25 | def h3(message): 26 | print(3, message) 27 | def h4(message): 28 | print(4, message) 29 | 30 | #ws_client.depth_stream(h1,'BTCUSDC',25) 31 | #ws_client.ticker_stream(h2,'BTCUSDC') 32 | #ws_client.trade_stream(h3,'BTCUSDC') 33 | #ws_client.klines_stream(h4,'BTCUSDC',1) 34 | ws_client.account_info_stream(handle_account) 35 | 36 | 37 | while True: 38 | # Run your main trading logic here. 39 | sleep(1) 40 | -------------------------------------------------------------------------------- /tests/demo_ws_depthdata.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from apexpro.constants import APEX_WS_TEST 4 | from apexpro.websocket_api import WebSocket 5 | 6 | 7 | # Connect with authentication! 8 | ws_client = WebSocket( 9 | endpoint=APEX_WS_TEST, 10 | ) 11 | 12 | def depth_data(message): 13 | showDepthdata(message) 14 | 15 | 16 | lastDepthData = {} 17 | 18 | def _find_index(source, key): 19 | """ 20 | Find the index in source list of the targeted ID. 21 | """ 22 | for item in source: 23 | if item[0] == key: 24 | return source.index(item) 25 | return -1 26 | 27 | def showDepthdata(message): 28 | global lastDepthData 29 | topic = '' 30 | 31 | if message.get('topic') is not None: 32 | topic = message.get('topic') 33 | 34 | print("new:", message) 35 | if message.get('type') == 'snapshot': 36 | lastDepthData = message 37 | print('lastDepthData: ', lastDepthData) 38 | elif message.get('type') == 'delta': 39 | if message.get('data').get('u') != lastDepthData.get('data').get('u') + 1: 40 | ws_client.unsub_depth_topic_stream(depth_data,topic) 41 | ws_client.depth_topic_stream(depth_data,topic) 42 | else: 43 | lastDepthData['u'] = message.get('u') 44 | if lastDepthData.get('data') is not None and message.get('data') is not None and message.get('data').get('b') is not None: 45 | for entry in message.get('data').get('b'): 46 | index = _find_index(lastDepthData['data']['b'], entry[0]) 47 | if index == -1: 48 | lastDepthData['data']['b'].append(entry) 49 | else: 50 | lastDepthData['data']['b'][index] = entry 51 | if entry[1] == '0': 52 | lastDepthData['data']['b'].pop(index) 53 | 54 | 55 | items=lastDepthData['data']['b'] 56 | #items.sort() 57 | sorted(items,key=lambda x:x[0],reverse=False) 58 | if lastDepthData.get('data') is not None and message.get('data') is not None and message.get('data').get('a') is not None: 59 | for entry in message.get('data').get('a'): 60 | index = _find_index(lastDepthData['data']['a'], entry[0]) 61 | if index == -1: 62 | lastDepthData['data']['a'].append(entry) 63 | else: 64 | lastDepthData['data']['a'][index] = entry 65 | if entry[1] == '0': 66 | lastDepthData['data']['a'].pop(index) 67 | 68 | 69 | items=lastDepthData['data']['a'] 70 | #items.sort() 71 | sorted(items,key=lambda x:x[0],reverse=False) 72 | 73 | print('lastDepthData: ', lastDepthData) 74 | else: 75 | ws_client.depth_topic_stream(depth_data,topic) 76 | 77 | 78 | ws_client.depth_stream(depth_data,'BTCUSDC',25) 79 | 80 | 81 | while True: 82 | # Run your main trading logic here. 83 | sleep(1) 84 | -------------------------------------------------------------------------------- /tests/demo_ws_v3.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from apexpro.constants import APEX_OMNI_WS_MAIN 4 | from apexpro.websocket_api import WebSocket 5 | 6 | key = 'your apiKey-key from register V3' 7 | secret = 'your apiKey-secret from register V3' 8 | passphrase = 'your apiKey-passphrase from register V3' 9 | 10 | 11 | # Connect with authentication! 12 | # APEX_OMNI_WS_MAIN for mainnet, APEX_OMNI_WS_TEST for testnet 13 | ws_client = WebSocket( 14 | endpoint=APEX_OMNI_WS_MAIN, 15 | api_key_credentials={'key': key, 'secret': secret, 'passphrase': passphrase}, 16 | ) 17 | 18 | def handle_account(message): 19 | print(message) 20 | contents_data = message["contents"] 21 | print(len(contents_data)) 22 | 23 | def h1(message): 24 | print(1, message) 25 | def h2(message): 26 | print(2, message) 27 | def h3(message): 28 | print(3, message) 29 | def h4(message): 30 | print(4, message) 31 | 32 | #ws_client.depth_stream(h1,'BTCUSDT',25) 33 | #ws_client.ticker_stream(h2,'BTCUSDT') 34 | ws_client.trade_stream(h3,'BTCUSDT') 35 | ws_client.klines_stream(h4,'BTCUSDT',1) 36 | ws_client.account_info_stream_v3(handle_account) 37 | 38 | 39 | while True: 40 | # Run your main trading logic here. 41 | sleep(1) 42 | -------------------------------------------------------------------------------- /tests/test_onboarding.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | from apexpro import HTTP 4 | from apexpro.constants import REGISTER_ENVID_MAIN 5 | from tests.constants import DEFAULT_HOST 6 | 7 | GANACHE_PRIVATE_KEY = ( 8 | '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d' 9 | ) 10 | 11 | EXPECTED_API_KEY_CREDENTIALS_MAINNET = { 12 | 'key': '50fdcaa0-62b8-e827-02e8-a9520d46cb9f', 13 | 'secret': 'rdHdKDAOCa0B_Mq-Q9kh8Fz6rK3ocZNOhKB4QsR9', 14 | 'passphrase': '12_1LuuJMZUxcj3kGBWc', 15 | } 16 | EXPECTED_STARK_KEY_PAIR_WITH_Y_COORDINATE_MAINNET = { 17 | 'public_key': 18 | '0x39d88860b99b1809a63add01f7dfa59676ae006bbcdf38ff30b6a69dcf55ed3', 19 | 'public_key_y_coordinate': 20 | '0x2bdd58a2c2acb241070bc5d55659a85bba65211890a8c47019a33902aba8400', 21 | 'private_key': 22 | '0x170d807cafe3d8b5758f3f698331d292bf5aeb71f6fd282f0831dee094ee891', 23 | } 24 | EXPECTED_API_KEY_CREDENTIALS_ROPSTEN = { 25 | 'key': '9c1d91a5-0a30-1ed4-2d3d-b840a479b965', 26 | 'secret': 'hHYEswFe5MHMm8gFb81Jas9b7iLQUicsVv5YBRMY', 27 | 'passphrase': '9z5Ew7m2DLQd87Xlk7Hd', 28 | } 29 | EXPECTED_STARK_KEY_PAIR_WITH_Y_COORDINATE_ROPSTEN = { 30 | 'public_key': 31 | '0x35e23a936e596969a6b3131cfccbd18b71779f28276d30e8215cd0d3e9252c2', 32 | 'public_key_y_coordinate': 33 | '0x557d1a1be389d9921b9d16415eac12bd276b05e2564c4b30a7730ace13a0e19', 34 | 'private_key': 35 | '0x50505654b282eb3debadddeddfa1bc76545a6837dcd59d7d41f6a282a4bbccc' 36 | } 37 | 38 | 39 | class TestOnboarding(): 40 | 41 | def test_derive_stark_key_on_mainnet_from_web3(self): 42 | web3 = Web3() # Connect to a local Ethereum node. 43 | client = HTTP( 44 | host=DEFAULT_HOST, 45 | network_id=REGISTER_ENVID_MAIN, 46 | web3=web3, 47 | ) 48 | signer_address = web3.eth.accounts[0] 49 | stark_key_pair_with_y_coordinate = client.onboarding.derive_stark_key( 50 | signer_address, 51 | ) 52 | assert stark_key_pair_with_y_coordinate == \ 53 | EXPECTED_STARK_KEY_PAIR_WITH_Y_COORDINATE_MAINNET 54 | 55 | def test_recover_default_api_key_credentials_on_mainnet_from_web3(self): 56 | web3 = Web3() # Connect to a local Ethereum node. 57 | client = HTTP( 58 | host=DEFAULT_HOST, 59 | network_id=REGISTER_ENVID_MAIN, 60 | web3=web3, 61 | ) 62 | signer_address = web3.eth.accounts[0] 63 | api_key_credentials = ( 64 | client.onboarding.recover_default_api_key_credentials( 65 | signer_address, 66 | ) 67 | ) 68 | assert api_key_credentials == EXPECTED_API_KEY_CREDENTIALS_MAINNET 69 | 70 | def test_derive_stark_key_on_ropsten_from_web3(self): 71 | web3 = Web3() # Connect to a local Ethereum node. 72 | client = HTTP( 73 | host=DEFAULT_HOST, 74 | network_id=REGISTER_ENVID_MAIN, 75 | web3=web3, 76 | ) 77 | signer_address = web3.eth.accounts[0] 78 | stark_key_pair_with_y_coordinate = client.onboarding.derive_stark_key( 79 | signer_address, 80 | ) 81 | assert stark_key_pair_with_y_coordinate == \ 82 | EXPECTED_STARK_KEY_PAIR_WITH_Y_COORDINATE_ROPSTEN 83 | 84 | def test_recover_default_api_key_credentials_on_ropsten_from_web3(self): 85 | web3 = Web3() # Connect to a local Ethereum node. 86 | client = HTTP( 87 | host=DEFAULT_HOST, 88 | network_id=REGISTER_ENVID_MAIN, 89 | web3=web3, 90 | ) 91 | signer_address = web3.eth.accounts[0] 92 | api_key_credentials = ( 93 | client.onboarding.recover_default_api_key_credentials( 94 | signer_address, 95 | ) 96 | ) 97 | assert api_key_credentials == EXPECTED_API_KEY_CREDENTIALS_ROPSTEN 98 | 99 | def test_derive_stark_key_on_mainnet_from_priv(self): 100 | client = HTTP( 101 | host=DEFAULT_HOST, 102 | network_id=REGISTER_ENVID_MAIN, 103 | eth_private_key=GANACHE_PRIVATE_KEY, 104 | api_key_credentials={'key': 'value'}, 105 | ) 106 | signer_address = client.default_address 107 | stark_key_pair_with_y_coordinate = client.onboarding.derive_stark_key( 108 | signer_address, 109 | ) 110 | assert stark_key_pair_with_y_coordinate == \ 111 | EXPECTED_STARK_KEY_PAIR_WITH_Y_COORDINATE_MAINNET 112 | 113 | def test_recover_default_api_key_credentials_on_mainnet_from_priv(self): 114 | client = HTTP( 115 | host=DEFAULT_HOST, 116 | network_id=REGISTER_ENVID_MAIN, 117 | eth_private_key=GANACHE_PRIVATE_KEY, 118 | ) 119 | signer_address = client.default_address 120 | api_key_credentials = ( 121 | client.onboarding.recover_default_api_key_credentials( 122 | signer_address, 123 | ) 124 | ) 125 | assert api_key_credentials == EXPECTED_API_KEY_CREDENTIALS_MAINNET 126 | 127 | def test_derive_stark_key_on_ropsten_from_priv(self): 128 | client = HTTP( 129 | host=DEFAULT_HOST, 130 | network_id=REGISTER_ENVID_MAIN, 131 | eth_private_key=GANACHE_PRIVATE_KEY, 132 | ) 133 | signer_address = client.default_address 134 | stark_key_pair_with_y_coordinate = client.onboarding.derive_stark_key( 135 | signer_address, 136 | ) 137 | assert stark_key_pair_with_y_coordinate == \ 138 | EXPECTED_STARK_KEY_PAIR_WITH_Y_COORDINATE_ROPSTEN 139 | 140 | def test_recover_default_api_key_credentials_on_ropsten_from_priv(self): 141 | client = HTTP( 142 | host=DEFAULT_HOST, 143 | network_id=REGISTER_ENVID_MAIN, 144 | eth_private_key=GANACHE_PRIVATE_KEY, 145 | ) 146 | signer_address = client.default_address 147 | api_key_credentials = ( 148 | client.onboarding.recover_default_api_key_credentials( 149 | signer_address, 150 | ) 151 | ) 152 | assert api_key_credentials == EXPECTED_API_KEY_CREDENTIALS_ROPSTEN 153 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{36,37,38,39,310,311,312} 3 | skipsdist = True 4 | skip_missing_interpreters = False 5 | ignore_basepython_conflict = True 6 | 7 | [testenv] 8 | basepython = python3 9 | sitepackages = True 10 | setenv = 11 | PYTHONPATH = {toxinidir}:{toxinidir} 12 | deps = 13 | pip 14 | setuptools 15 | wheel 16 | aiohttp 17 | cryptography 18 | requests 19 | commands = 20 | pytest 21 | --------------------------------------------------------------------------------