├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE.txt ├── README.md ├── binancechain ├── __init__.py ├── crypto.py ├── enums.py ├── exceptions.py ├── httpclient.py ├── noderpc.py ├── ratelimit.py ├── transaction.proto ├── transaction.py ├── transaction_base.py ├── transaction_pb2.py ├── wallet.py └── websocket.py ├── cli-requirements.txt ├── examples ├── cli.py ├── http_examples.py ├── noderpc_examples.py ├── transaction_examples.py ├── wallet_examples.py ├── websocket_decorator.py └── websocket_examples.py ├── requirements.txt ├── setup.py ├── test-requirements.txt ├── test ├── test_httpclient.py ├── test_noderpc.py ├── test_ratelimit.py ├── test_transaction.py ├── test_wallet.py └── test_websocket.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | py*env 2 | *.py[co] 3 | .*.sw* 4 | build 5 | .ipynb_checkpoints 6 | .mypy_cache 7 | *.ipynb 8 | py36 9 | .tox 10 | *.egg* 11 | .coverage 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial # required for Python >= 3.7 2 | language: python 3 | python: 4 | - "3.6" 5 | - "3.7" 6 | - "3.7-dev" # 3.7 development branch 7 | - "3.8-dev" # 3.8 development branch 8 | - "nightly" # nightly build 9 | install: 10 | - python setup.py develop && pip install -r requirements.txt -r test-requirements.txt 11 | script: 12 | - pytest 13 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Kim Bui 2 | Luke Macken 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of 2 | this software and associated documentation files (the “Software”), to deal in 3 | the Software without restriction, including without limitation the rights to 4 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 5 | of the Software, and to permit persons to whom the Software is furnished to do 6 | so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # binance-chain-python 2 | 3 | [![Build Status](https://travis-ci.org/lmacken/binance-chain-python.svg?branch=master&color=green)](https://travis-ci.org/lmacken/binance-chain-python) 4 | [![Coverage Status](https://coveralls.io/repos/github/lmacken/binance-chain-python/badge.svg)](https://coveralls.io/github/lmacken/binance-chain-python) 5 | ![GitHub](https://img.shields.io/github/license/lmacken/binance-chain-python.svg) 6 | ![PyPI](https://img.shields.io/pypi/v/binancechain.svg) 7 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/binancechain.svg) 8 | 9 | An unofficial asyncio Python API for the Binance Chain. 10 | 11 | ## Installation 12 | 13 | pip install binancechain 14 | 15 | ## Implementation Details 16 | 17 | - [Extensive test suite](https://github.com/lmacken/binance-chain-python/tree/master/test) 18 | - Optional rate limiter with the `HTTPClient(rate_limit=True)`, which uses a token-bucket style queue for each endpoint. 19 | - [aiohttp](https://aiohttp.readthedocs.io) for all HTTP requests, which automatically performs connection-pooling 20 | - [SPDX license identifiers](https://spdx.org/) 21 | - Python [type hints](https://docs.python.org/3/library/typing.html) for ease of development 22 | - Python3.6+ f-strings for faster string interpolation 23 | - Exception-chaining with [`raise from`](https://docs.python.org/3/library/exceptions.html#built-in-exceptions) 24 | - Clean and consistent syntax formatting with [Black](https://github.com/ambv/black) 25 | - Example [CLI tool](https://github.com/lmacken/binance-chain-python/blob/master/examples/cli.py) that just outputs raw JSON responses 26 | - Event-driven WebSocket using [pyee](https://github.com/jfhbrook/pyee) 27 | - Automatically sends `keepAlive` WebSocket messages every 30 minutes 28 | - Utilizes [orjson](https://github.com/ijl/orjson), the fastest JSON library in Python. 29 | 30 | ### Utilizes popular crypto libraries 31 | - [bitcoinlib](https://github.com/1200wd/bitcoinlib) 32 | - [bech32](https://github.com/sipa/bech32) 33 | - [secp256k1](https://github.com/ludbb/secp256k1-py) 34 | - [eth_keyfile](https://github.com/ethereum/eth-keyfile) 35 | 36 | ## API SECTIONS 37 | 38 | - [WEBSOCKET](https://github.com/lmacken/binance-chain-python#websocket) 39 | - [REST API](https://github.com/lmacken/binance-chain-python#rest-api) 40 | - [NODE RPC](https://github.com/lmacken/binance-chain-python#node-rpc) 41 | - [WALLET](https://github.com/lmacken/binance-chain-python#binance-chain-wallet) 42 | - [TRANSACTION](https://github.com/lmacken/binance-chain-python#binance-chain-transaction) 43 | 44 | ### NAMESPACE 45 | 46 | ```python 47 | from binancechain import HTTPClient, NodeRPC, WebSocket, Wallet, Transaction, BinanceChainException 48 | ``` 49 | 50 | ------------------ 51 | 52 | ## WEBSOCKET 53 | 54 | ### Decorator API 55 | 56 | ```python 57 | dex = WebSocket(address, testnet=True) 58 | 59 | @dex.on("open") 60 | async def on_open(): … 61 | 62 | @dex.on("allTickers", symbols=["$all"]) 63 | async def on_ticker(msg): … 64 | 65 | @dex.on("kline_1m", symbols=["000-0E1_BNB"]) 66 | async def on_kline(kline): … 67 | 68 | @dex.on("orders") 69 | async def user_orders(msg): … 70 | 71 | @dex.on("accounts") 72 | async def user_accounts(msg): … 73 | 74 | @dex.on("transfers") 75 | async def user_transfers(msg): … 76 | 77 | @dex.on("error") 78 | async def on_error(msg): … 79 | 80 | dex.start() # or dex.start_async() coroutine 81 | ``` 82 | ### Callback API 83 | ```python 84 | dex = WebSocket(address, testnet=True) 85 | 86 | def on_open(): 87 | dex.subscribe_user_orders(callback=user_orders) 88 | dex.subscribe_user_accounts(callback=user_accounts) 89 | dex.subscribe_user_transfers(callback=user_transfers) 90 | dex.subscribe_trades(callback=callback, symbols=symbols) 91 | dex.subscribe_market_depth(callback=callback, symbols=symbols) 92 | dex.subscribe_market_diff(callback=callback, symbols=symbols) 93 | dex.subscribe_klines(callback=callback, symbols=symbols) 94 | dex.subscribe_ticker(callback=callback, symbols=symbols) 95 | dex.subscribe_all_tickers(callback=callback) 96 | dex.subscribe_mini_ticker(callback=callback, symbols=symbols) 97 | dex.subscribe_all_mini_tickers(callback=callback) 98 | dex.subscribe_blockheight(callback=callback) 99 | 100 | dex.start(on_open, on_error) 101 | ``` 102 | 103 | See the WebSocket [examples](https://github.com/lmacken/binance-chain-python/tree/master/examples) for more information. 104 | 105 | ---------------- 106 | 107 | ## REST API 108 | 109 | ### Query information 110 | ```python 111 | 112 | client = HTTPClient(testnet=True) 113 | 114 | server_time = await client.get_time() 115 | 116 | node_info = await client.get_node_info() 117 | 118 | validators = await client.get_validators() 119 | 120 | peers = await client.get_peers() 121 | 122 | account_info = await client.get_account_info(address) 123 | 124 | sequence_info = await client.get_account_sequence(address) 125 | 126 | transaction = await client.get_transaction(hash) 127 | 128 | token_list = await client.get_token_list() 129 | 130 | markets = await client.get_markets(limit=500, offset=0) 131 | 132 | fees = await client.get_fees() 133 | 134 | depth = await client.get_depth(symbol, limit=100) 135 | 136 | klines = await client.get_klines(symbol, interval, limit=300, start=None, end=None) 137 | 138 | closed_orders = await client.get_closed_orders( 139 | address, 140 | end=None, 141 | limit=None, 142 | offset=None, 143 | side=None, 144 | start=None, 145 | status=None, 146 | symbol=None, 147 | total=None, 148 | ) 149 | 150 | open_orders = await client.get_open_orders( 151 | self, address, limit=None, offset=None, symbol=None, total=None 152 | ) 153 | 154 | order = await client.get_order(id) 155 | 156 | ticker = await client.get_ticker(symbol) 157 | 158 | trades = await client.get_trades( 159 | address=None, 160 | buyerOrderId=None, 161 | height=None, 162 | limit=None, 163 | offset=None, 164 | quoteAsset=None, 165 | sellerOrderId=None, 166 | side=None, 167 | start=None, 168 | end=None, 169 | total=None, 170 | symbol=None, 171 | ) 172 | 173 | block_fee = await client.block_exchange_fee( 174 | address=None, end=None, limit=None, offset=None, start=None, total=None 175 | ) 176 | 177 | transactions = await client.get_transactions( 178 | address, 179 | height=None, 180 | end=None, 181 | limit=None, 182 | offset=None, 183 | side=None, 184 | start=None, 185 | tx_asset=None, 186 | tx_type=None, 187 | ) 188 | ``` 189 | ### Broadcast transaction 190 | ```python 191 | broadcast_info = await client.broadcast(hash) 192 | 193 | ``` 194 | 195 | ------------------ 196 | 197 | ## NODE RPC 198 | ### Query Information 199 | ```python 200 | noderpc = binancechain.NodeRPC(testnet=True) 201 | 202 | abic_info = await noderpc.get_abci_info(path, data=None, height="0", prove=False) 203 | 204 | concensus_state = await noderpc.get_consensus_state() 205 | 206 | dump_concensus_state = await noderpc.get_dump_consensus_state() 207 | 208 | genesis = await noderpc.get_genesis() 209 | 210 | health = await noderpc.get_health() 211 | 212 | net_info = await noderpc.get_net_info() 213 | 214 | status = await noderpc.get_status() 215 | 216 | query = await noderpc.abci_query("/param/fees") 217 | 218 | block = await noderpc.block(height=None) 219 | 220 | block_hash = await noderpc.block_by_hash(hash) 221 | 222 | block_results = await noderpc.block_results(height=None) # ABCIResults 223 | 224 | blockchain = await noderpc.blockchain(min_height, max_height) 225 | 226 | concensus_params = await noderpc.consensus_params("1") 227 | 228 | validators = await noderpc.validators(height=None) 229 | 230 | transaction = await noderpc.tx(txid, prove=False) 231 | 232 | tx_search = await noderpc.tx_search(query, prove=False, page=1, per_page=30) 233 | 234 | pending_transactions = await noderpc.unconfirmed_txs(limit=None) 235 | 236 | pendings_number = await noderpc.get_num_unconfirmed_txs() 237 | ``` 238 | 239 | ### NodeRPC WebSocket 240 | 241 | ```python 242 | query = "tm.event = 'Tx'" 243 | 244 | def on_open(): 245 | noderpc.subscribe(query) 246 | 247 | def on_msg(msg): … 248 | 249 | noderpc.start(on_open=on_open, on_msg=on_msg, on_error=on_error) 250 | 251 | noderpc.unsubscribe(query=query) 252 | noderpc.unsubscribe_all() 253 | ``` 254 | 255 | See list of all possible events here 256 | https://godoc.org/github.com/tendermint/tendermint/types#pkg-constants 257 | 258 | For complete query syntax, check out https://godoc.org/github.com/tendermint/tendermint/libs/pubsub/query. 259 | 260 | ### Broadcast transaction 261 | ```python 262 | tx_hash = await noderpc.broadcast_tx_async(hash) 263 | 264 | tx_onchain = await noderpc.broadcast_tx_sync(hash) 265 | 266 | tx_confirmed = await noderpc.commit(height=None) 267 | ``` 268 | ------------------- 269 | 270 | ## BINANCE CHAIN WALLET 271 | 272 | ### Create or recover wallet and keystore 273 | ```python 274 | wallet = Wallet.create_wallet(password=None, testnet=False) 275 | 276 | wallet = Wallet.create_wallet_mnemonic(language="english", password=None, testnet=False) 277 | 278 | keystore = Wallet.create_keystore(password=None) 279 | 280 | wallet = Wallet(key="HDKEY object", testnet=False) 281 | 282 | wallet = Wallet.wallet_from_keystore(keystore=keystore, password=None, testnet=False) 283 | 284 | wallet = Wallet.wallet_from_mnemonic(words="mnemonic words", password=None, testnet=False) 285 | 286 | wallet = Wallet.wallet_from_privatekey(privatekey="private_key", password=None, testnet=False) 287 | ``` 288 | 289 | ### Using the wallet 290 | ```python 291 | address = wallet.get_adress() 292 | 293 | priv = wallet.get_privatekey() 294 | 295 | pub = wallet.get_publickey() 296 | 297 | pub, signature = wallet.sign(msg) 298 | 299 | is_valid = wallet.verify_signature(msg, signature) 300 | ``` 301 | 302 | ------------------- 303 | 304 | ## BINANCE CHAIN TRANSACTION 305 | 306 | ### Using Transaction with wallet and client, handle signing and broadcast internally 307 | ```python 308 | from binancechain.enums import Ordertype, Side, Timeinforce, Votes 309 | 310 | #if client is passed in , testnet arg will be ignored 311 | transaction = Transaction(wallet=wallet, client=client) 312 | 313 | transfer = await transaction.transfer( 314 | to_address=wallet_two.get_address(), symbol="BNB", amount=0.1 315 | ) 316 | 317 | multi_transfer = await transaction.multi_transfer( 318 | to_address, 319 | transfers=[{"symbol": "BTC", "amount": 0.1}, {"symbol": "BNB", "amount": 0.1}], 320 | ) 321 | 322 | new_order_txid = await transaction.create_new_order( 323 | symbol="binance_pair", 324 | side=Side.BUY, 325 | ordertype=Ordertype.LIMIT, 326 | price=1, 327 | quantity=1, 328 | timeInForce=Timeinforce.GTE, 329 | ) 330 | 331 | cancel_order_txid = await transaction.cancel_order(symbol="BTC-531_BNB", refid="") 332 | 333 | freeze_token_txid = await transaction.freeze_token(symbol="BNB", amount=1) 334 | 335 | unfreeze_token_txid = await transaction.unfreeze_token(symbol="BNB", amount=1) 336 | 337 | vote_txid = await transaction.vote(proposal_id, option=Votes.YES) # only validator can vote 338 | 339 | issue_token_txid = await transaction.issue_token(symbol, name, supply, mintable) 340 | 341 | mint_token_txid = await transaction.mint_token(symbol, amount) 342 | 343 | burn_token_txid = await transaction.burn_token(symbol, amount) 344 | ``` 345 | ### Create Transaction Message. This message can be signed and broadcast somewhere else 346 | 347 | ```python 348 | transfer_transaction = await Transaction.transfer_transaction( 349 | from_address, to_address, symbol, amount 350 | ) 351 | 352 | multi_transfer_transaction = await Transaction.multi_transfer_transaction( 353 | from_address, 354 | to_address, 355 | transfers=[{"symbol": "BTC", "amount": 0.1}, {"symbol": "BNB", "amount": 0.1}], 356 | ) 357 | 358 | limit_buy_transaction = await Transaction.new_order_transaction( 359 | address="owner address", 360 | symbol="pair", 361 | side=Side.BUY, 362 | ordertype=Ordertype.LIMIT, 363 | price=1, 364 | quantity=1, 365 | timeInForce=Timeinforce.GTE, 366 | testnet=True, 367 | client=None, 368 | ) 369 | 370 | limit_sell_transaction = await Transaction.new_order_transaction( 371 | address="owner address", 372 | symbol="pair", 373 | side=Side.BUY, 374 | ordertype=Ordertype.LIMIT, 375 | price=1, 376 | quantity=1, 377 | timeInForce=Timeinforce.GTE, 378 | testnet=True, 379 | client=None, 380 | ) 381 | 382 | cancel_order_transaction = await Transaction.cancel_order( 383 | address="owner_address", symbol="pair", refid="", testnet=True, client=None 384 | ) 385 | 386 | freeze_token_transaction = await Transaction.freeze_token( 387 | address="ownder_address", symbol="BNB", amount=1, testnet=True, client=None 388 | ) 389 | 390 | unfreeze_token_tranasaction = await Transaction.unfreeze_token_transaction( 391 | address="ownder_address", symbol="BNB", amount=1, testnet=True, client=None 392 | ) 393 | 394 | vote_transaction = await Transaction.vote_transaction( 395 | voter, proposal_id, option=Votes.YES, client=None, testnet=True 396 | ) 397 | 398 | issue_token_transaction = await Transaction.issue_token_transaction( 399 | owner, name, symbol, sypply, mintable, client=None, testnet=True 400 | ) 401 | 402 | mint_token_transaction = await Transaction.mint_token_transaction( 403 | owner, symbol, amount, client=None, testnet=True 404 | ) 405 | 406 | burn_token_transaction = Transaction.burn_token_transaction( 407 | owner, symbol, amount, client=None, testnet=True 408 | ) 409 | 410 | """ Get transaction message to sign""" 411 | 412 | sign_message_bytes_format = Limit_Buy_Transaction.get_sign_message() 413 | ``` 414 | - Example transaction message : 415 | 416 | ``` 417 | b'{"account_number":"668107","chain_id":"Binance-Chain-Nile","data":null,"memo":"","msgs":[{"inputs":[{"address":"tbnb1r5jc35v338tlphnjx65wy7tecm6vm82tftfkt7","coins":[{"amount":10000000,"denom":"BNB"}]}],"outputs":[{"address":"tbnb1nhvpuq0u5pgpry0x2ap2hqv9n5jfkj90eps6qx","coins":[{"amount":10000000,"denom":"BNB"}]}]}],"sequence":"35","source":"1"}' 418 | ``` 419 | 420 | ---------------------- 421 | 422 | ## Running the test suite 423 | 424 | ```bash 425 | git clone https://github.com/lmacken/binance-chain-python.git 426 | cd binance-chain-python 427 | pip install -r test-requirements.txt -r requirements.txt 428 | python setup.py develop 429 | pytest 430 | ``` 431 | 432 | ---------------------- 433 | 434 | ## Contributors 435 | 436 | [@lmacken](https://github.com/lmacken) 437 | [@propulsor](https://github.com/propulsor) 438 | 439 | ## Donate 440 | 441 | BNB: `bnb1qx8u39hqcykjy5puv582gvqy5520dsz7fdh9ak` 442 | 443 | BTC: `39n2J2hWY5FHCnGwNgRSZTe4TdFKcQea9v` 444 | -------------------------------------------------------------------------------- /binancechain/__init__.py: -------------------------------------------------------------------------------- 1 | from .enums import Ordertype, Side, Votes, Timeinforce 2 | from .httpclient import HTTPClient 3 | from .noderpc import NodeRPC 4 | from .transaction import Transaction 5 | from .wallet import Wallet 6 | from .websocket import WebSocket 7 | from .exceptions import BinanceChainException 8 | -------------------------------------------------------------------------------- /binancechain/crypto.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | 4 | from bitcoinlib import encoding 5 | import bech32 6 | 7 | 8 | def from_path(root_key, path): 9 | if isinstance(path, str): 10 | p = path.rstrip("/").split("/") 11 | elif isinstance(path, bytes): 12 | p = path.decode("utf-8").rstrip("/").split("/") 13 | else: 14 | p = list(path) 15 | key = root_key 16 | for i in p: 17 | if isinstance(i, str): 18 | hardened = i[-1] == "'" 19 | index = int(i[:-1], 0) | 0x80000000 if hardened else int(i, 0) 20 | else: 21 | index = i 22 | key = key.child_private(index, hardened=hardened) 23 | return key 24 | 25 | 26 | def get_address(prefix, key): 27 | convert = encoding.convertbits(encoding.hash160(key.public_compressed_byte), 8, 5) 28 | address = bech32.bech32_encode(prefix, convert) 29 | return address 30 | 31 | 32 | def address_decode(address): 33 | prefix, words = bech32.bech32_decode(address) 34 | convert = encoding.convertbits(words, 5, 8) 35 | return bytes(convert) 36 | 37 | 38 | def generate_signature(key, data): 39 | pass 40 | 41 | 42 | def verify_signature(data, signed_data, pubkey): 43 | pass 44 | 45 | 46 | def generate_id(address, sequence): 47 | prefix, words = bech32.bech32_decode(address) 48 | convert_w = encoding.convertbits(words, 5, 8) 49 | decodedAddress = encoding.to_bytearray(convert_w).hex() 50 | return f"{decodedAddress.upper()}-{sequence+1}" 51 | -------------------------------------------------------------------------------- /binancechain/enums.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | 4 | from enum import Enum 5 | 6 | 7 | class Votes(Enum): 8 | YES = 1 9 | NO = 2 10 | ABSTAIN = 3 11 | NOWITHVETO = 4 12 | 13 | 14 | class Ordertype(Enum): 15 | LIMIT = 2 16 | 17 | 18 | class Side(Enum): 19 | BUY = 1 20 | SELL = 2 21 | 22 | 23 | class Timeinforce(Enum): 24 | GTE = 1 25 | IOC = 3 26 | -------------------------------------------------------------------------------- /binancechain/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | 4 | import aiohttp 5 | 6 | from typing import Optional 7 | 8 | 9 | class BinanceChainException(Exception): 10 | def __init__(self, response: Optional[aiohttp.ClientResponse] = None): 11 | self.response = response 12 | 13 | def __repr__(self): 14 | return f"<{self.__class__.__name__} {self.response}>" 15 | -------------------------------------------------------------------------------- /binancechain/httpclient.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | """ 4 | Binance Chain HTTP API 5 | 6 | https://docs.binance.org/api-reference/dex-api/paths.html 7 | """ 8 | import logging 9 | import warnings 10 | from typing import Any, Dict, List, Optional, Tuple, Union 11 | 12 | import aiohttp 13 | import orjson 14 | 15 | from .exceptions import BinanceChainException 16 | from .ratelimit import RateLimiter 17 | 18 | log = logging.getLogger(__name__) 19 | 20 | MAINNET_URL = "https://dex.binance.org" 21 | TESTNET_URL = "https://testnet-dex.binance.org" 22 | 23 | 24 | class HTTPClient: 25 | """ Binance Chain HTTP API Client """ 26 | 27 | def __init__( 28 | self, 29 | testnet: bool = True, 30 | api_version: str = "v1", 31 | url=None, 32 | rate_limit: bool = False, 33 | ): 34 | """ 35 | :param testnet: Use testnet instead of mainnet 36 | :param api_version: The API version to use 37 | :param session: An optional HTTP session to use 38 | :param rate_limit: Enable automatic rate-limiting 39 | """ 40 | if not url: 41 | url = TESTNET_URL if testnet else MAINNET_URL 42 | self._server = f"{url}/api/{api_version}/" 43 | self._session: Optional[aiohttp.ClientSession] = None 44 | self._testnet = testnet 45 | self._rate_limiter: Optional[RateLimiter] = None 46 | if rate_limit: 47 | self._rate_limiter = RateLimiter() 48 | 49 | def __del__(self): 50 | if self._session: # pragma: nocover 51 | warnings.warn(f"{repr(self)}.close() was never awaited") 52 | 53 | async def close(self): 54 | """ Clean up our connections """ 55 | if self._session: 56 | await self._session.close() 57 | self._session = None 58 | if self._rate_limiter: 59 | self._rate_limiter.close() 60 | 61 | async def _request(self, method: str, path: str, rps: int = 1, **kwargs): 62 | """ 63 | :param method: `get` or `post` 64 | :param path: the remote endpoint to call 65 | :param rps: requests per second, used if the rate limiter is enabled 66 | :param kwargs: Extra arguments to pass to the request, like `params` or `data`. 67 | :raises: `BinanceChainException`, which has a `response` attribute. 68 | """ 69 | if not self._session: 70 | self._session = aiohttp.ClientSession() 71 | if self._rate_limiter: 72 | await self._rate_limiter.limit(path.split("/")[0], rps) 73 | try: 74 | resp = None 75 | async with getattr(self._session, method)( 76 | self._server + path, **kwargs 77 | ) as resp: 78 | return await resp.json(loads=orjson.loads) 79 | except Exception as e: 80 | log.exception(f"Request error: {method} {path} {kwargs}") 81 | raise BinanceChainException(resp) from e 82 | 83 | async def get_request(self, path: str, params: dict = None, rps: int = 1) -> Any: 84 | """Perform a GET request""" 85 | return await self._request("get", path, params=params, rps=rps) 86 | 87 | async def post_request( 88 | self, 89 | path: str, 90 | data: Optional[str] = None, 91 | headers: Optional[dict] = None, 92 | rps: int = 1, 93 | ) -> Any: 94 | """Perform a POST request""" 95 | return await self._request("post", path, data=data, headers=headers, rps=rps) 96 | 97 | async def get_time(self) -> dict: 98 | """Get the block time. 99 | 100 | Gets the latest block time and the current time according to the HTTP 101 | service. 102 | 103 | Destination: Validator node. 104 | Rate Limit: 1 request per IP per second. 105 | """ 106 | return await self.get_request("time") 107 | 108 | async def get_node_info(self) -> dict: 109 | """Get node information. 110 | 111 | Gets runtime information about the node. 112 | 113 | Destination: Validator node. 114 | Rate Limit: 1 request per IP per second. 115 | """ 116 | return await self.get_request("node-info") 117 | 118 | async def get_validators(self) -> dict: 119 | """Get validators. 120 | 121 | Gets the list of validators used in consensus. 122 | 123 | Destination: Witness node. 124 | Rate Limit: 10 requests per IP per second. 125 | """ 126 | return await self.get_request("validators", rps=10) 127 | 128 | async def get_peers(self) -> List[dict]: 129 | """Get network peers. 130 | 131 | Gets the list of network peers. 132 | 133 | Destination: Witness node. 134 | Rate Limit: 1 request per IP per second. 135 | """ 136 | return await self.get_request("peers") 137 | 138 | async def get_account(self, address: str) -> dict: 139 | """Get an account. 140 | 141 | Gets account metadata for an address. 142 | 143 | Destination: Witness node. 144 | Rate Limit: 5 requests per IP per second. 145 | 146 | :param address: The account address to query 147 | """ 148 | return await self.get_request(f"account/{address}", rps=5) 149 | 150 | async def get_account_sequence(self, address: str) -> dict: 151 | """Get an account sequence. 152 | 153 | Gets an account sequence for an address. 154 | 155 | Destination: Validator node. 156 | Rate Limit: 5 requests per IP per second. 157 | Parameters: 158 | 159 | :param address: The account address to query. 160 | """ 161 | return await self.get_request(f"account/{address}/sequence", rps=5) 162 | 163 | async def get_transaction(self, hash: str) -> dict: 164 | """Get a transaction. 165 | 166 | Gets transaction metadata by transaction ID. By default, transactions 167 | are returned in a raw format. 168 | 169 | Destination: Seed node. 170 | Rate Limit: 10 requests per IP per second. 171 | 172 | :param hash: The transaction hash to query 173 | """ 174 | return await self.get_request(f"tx/{hash}", rps=10) 175 | 176 | async def get_token_list(self) -> List[dict]: 177 | """Get tokens list. 178 | 179 | Gets a list of tokens that have been issued. 180 | 181 | Destination: Witness node. 182 | Rate Limit: 1 request per IP per second. 183 | """ 184 | return await self.get_request("tokens") 185 | 186 | async def get_markets(self, limit: int = 500, offset: int = 0) -> List[dict]: 187 | """Get market pairs. 188 | 189 | Gets the list of market pairs that have been listed. 190 | 191 | Destination: Witness node. 192 | Rate Limit: 1 request per IP per second. 193 | 194 | :param limit: default 500; max 1000. 195 | :param offset: start with 0; default 0. 196 | """ 197 | return await self.get_request( 198 | "markets", params={"limit": limit, "offset": offset} 199 | ) 200 | 201 | async def get_fees(self) -> List[dict]: 202 | """Obtain trading fees information. 203 | 204 | Gets the current trading fees settings. 205 | 206 | Destination: Witness node. 207 | Rate Limit: 1 request per IP per second. 208 | """ 209 | return await self.get_request("fees") 210 | 211 | async def get_depth(self, symbol: str, limit: int = 100) -> Dict[str, list]: 212 | """Get the order book. 213 | 214 | Gets the order book depth data for a given pair symbol. 215 | The given limit must be one of the allowed limits below. 216 | 217 | Destination: Validator node. 218 | Rate Limit: 10 requests per IP per second. 219 | 220 | :param symbol: Market pair symbol, e.g. NNB-0AD_BNB 221 | :param limit: The limit of results. Allowed limits: [5, 10, 20, 50, 100, 500, 1000] 222 | """ 223 | return await self.get_request( 224 | "depth", params={"symbol": symbol, "limit": limit}, rps=10 225 | ) 226 | 227 | async def broadcast(self, body: str, sync: bool = None) -> List[dict]: 228 | """Broadcast a transaction. 229 | 230 | Broadcasts a signed transaction. A single transaction must be sent 231 | hex-encoded with a content-type of text/plain. 232 | 233 | Destination: Witness node. 234 | Rate Limit: 5 requests per IP per second. 235 | 236 | :param sync: Synchronous broadcast (wait for DeliverTx)? 237 | :param body: Hex-encoded transaction 238 | """ 239 | return await self.post_request( 240 | "broadcast", data=body, headers={"Content-Type": "text/plain"}, rps=5 241 | ) 242 | 243 | async def get_klines( 244 | self, 245 | symbol: str, 246 | interval: str, 247 | limit: int = 300, 248 | start: int = None, 249 | end: int = None, 250 | ) -> List[Tuple[int, str, str, str, str, str, int, str, int]]: 251 | """Get candlestick bars. 252 | 253 | Gets candlestick/kline bars for a symbol. Bars are uniquely identified 254 | by their open time. 255 | 256 | If the time window is larger than limits, only the first n klines will 257 | return. In this case, please either shrink the window or increase the 258 | limit to get proper amount of klines. 259 | 260 | Rate Limit: 10 requests per IP per second. 261 | 262 | :param symbol: symbol 263 | :param interval: interval. Allowed value: [1m, 3m, 5m, 15m, 30m, 1h, 264 | 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M] 265 | :param limit: default 300; max 1000. 266 | :param start: start time in Milliseconds 267 | :param end: end time in Milliseconds 268 | """ 269 | params = {"symbol": symbol, "interval": interval, "limit": limit} 270 | if start is not None: 271 | params["startTime"] = int(start) 272 | if end is not None: 273 | params["endTime"] = int(end) 274 | return await self.get_request("klines", params=params, rps=10) 275 | 276 | async def get_closed_orders( 277 | self, 278 | address: str, 279 | end: int = None, 280 | limit: int = None, 281 | offset: int = None, 282 | side: int = None, 283 | start: int = None, 284 | status: str = None, 285 | symbol: str = None, 286 | total: int = None, 287 | ) -> List[dict]: 288 | """Get closed orders. 289 | 290 | Gets closed (filled and cancelled) orders for a given address. 291 | 292 | Query Window: Default query window is latest 7 days; The maximum 293 | start - end query window is 3 months. 294 | Rate Limit: 5 requests per IP per second. 295 | 296 | :param address: the owner address 297 | :param end: end time in Milliseconds 298 | :param limit: default 500; max 1000. 299 | :param offset: start with 0; default 0. 300 | :param side: order side. 1 for buy and 2 for sell. 301 | :param start: start time in Milliseconds 302 | :param status: order status list. Allowed value: [Ack, PartialFill, 303 | IocNoFill, FullyFill, Canceled, Expired, FailedBlocking, 304 | FailedMatching] 305 | :param symbol: symbol 306 | :param total: total number required, 0 for not required and 1 for 307 | required; default not required, return total=-1 in response 308 | """ 309 | params: Dict[str, Union[str, int]] = {"address": address} 310 | if end is not None: 311 | params["end"] = int(end) 312 | if limit is not None: 313 | params["limit"] = limit 314 | if offset is not None: 315 | params["offset"] = offset 316 | if side is not None: 317 | params["side"] = side 318 | if start is not None: 319 | params["start"] = int(start) 320 | if status is not None: 321 | params["status"] = status 322 | if symbol is not None: 323 | params["symbol"] = symbol 324 | if total is not None: 325 | params["total"] = total 326 | return await self.get_request("orders/closed", params=params, rps=5) 327 | 328 | async def get_open_orders( 329 | self, 330 | address: str, 331 | limit: int = None, 332 | offset: int = None, 333 | symbol: str = None, 334 | total: int = None, 335 | ): 336 | """Get open orders. 337 | 338 | Gets open orders for a given address. 339 | 340 | Rate Limit: 5 requests per IP per second. 341 | 342 | :param address: the owner address 343 | :param limit: default 500; max 1000. 344 | :param offset: start with 0; default 0. 345 | :param symbol: symbol 346 | :param total: total number required, 0 for not required and 1 for 347 | required; default not required, return total=-1 in response 348 | """ 349 | params: Dict[str, Union[str, int]] = {"address": address} 350 | if limit is not None: 351 | params["limit"] = limit 352 | if offset is not None: 353 | params["offset"] = offset 354 | if symbol is not None: 355 | params["symbol"] = symbol 356 | if total is not None: 357 | params["total"] = total 358 | return await self.get_request("orders/open", params=params, rps=5) 359 | 360 | async def get_order(self, id: str) -> dict: 361 | """Get an order. 362 | 363 | Gets metadata for an individual order by its ID. 364 | 365 | Rate Limit: 5 requests per IP per second. 366 | 367 | :param id: order id 368 | """ 369 | return await self.get_request(f"orders/{id}", rps=5) 370 | 371 | async def get_ticker(self, symbol: str = None) -> List[dict]: 372 | """Get a market ticker. 373 | 374 | Gets 24 hour price change statistics for a market pair symbol. 375 | 376 | Rate Limit: 5 requests per IP per second. 377 | 378 | :param symbol: symbol 379 | """ 380 | params = {} 381 | if symbol: 382 | params["symbol"] = symbol 383 | return await self.get_request("ticker/24hr", params=params, rps=5) 384 | 385 | async def get_trades( 386 | self, 387 | address: str = None, 388 | buyerOrderId: str = None, 389 | height: int = None, 390 | limit: int = None, 391 | offset: int = None, 392 | quoteAsset: str = None, 393 | sellerOrderId: str = None, 394 | side: int = None, 395 | start: int = None, 396 | end: int = None, 397 | total: int = None, 398 | symbol: str = None, 399 | ) -> List[dict]: 400 | """Get market trades. 401 | 402 | Gets a list of historical trades. 403 | 404 | Query Window: Default query window is latest 7 days; The maximum 405 | start - end query window is 3 months. 406 | 407 | Rate Limit: 5 requests per IP per second. 408 | 409 | :param address: the buyer/seller address 410 | :param buyerOrderId: buyer order id 411 | :param end: end time in Milliseconds 412 | :param height: block height 413 | :param limit: default 500; max 1000. 414 | :param start: start with 0; default 0. 415 | :param quoteAsset: quote asset 416 | :param sellerOrderId: seller order id 417 | :param side: order side. 1 for buy and 2 for sell. 418 | :param start: start time in Milliseconds 419 | :param symbol: symbol 420 | :param total: total number required, 0 for not required and 1 for 421 | required; default not required, return total=-1 in response 422 | """ 423 | params: Dict[Any, Any] = {} 424 | if address is not None: 425 | params["address"] = address 426 | if buyerOrderId is not None: 427 | params["buyerOrderId"] = buyerOrderId 428 | if height is not None: 429 | params["height"] = height 430 | if limit is not None: 431 | params["limit"] = limit 432 | if offset is not None: 433 | params["offset"] = offset 434 | if sellerOrderId is not None: 435 | params["sellerOrderId"] = sellerOrderId 436 | if side is not None: 437 | params["side"] = side 438 | if start is not None: 439 | params["start"] = start 440 | if end is not None: 441 | params["end"] = end 442 | if symbol is not None: 443 | params["symbol"] = symbol 444 | if total is not None: 445 | params["total"] = total 446 | return await self.get_request("trades", params=params, rps=5) 447 | 448 | async def get_block_exchange_fee( 449 | self, 450 | address: str = None, 451 | end: int = None, 452 | limit: int = None, 453 | offset: int = None, 454 | start: int = None, 455 | total: int = None, 456 | ) -> List[dict]: 457 | """Trading fee of the address grouped by block 458 | 459 | Get historical trading fees of the address, including fees of 460 | trade/canceled order/expired order. Transfer and other transaction fees 461 | are not included. Order by block height DESC. 462 | 463 | Query Window: Default query window is latest 7 days; The maximum 464 | start - end query window is 3 months. 465 | 466 | Rate Limit: 5 requests per IP per second. 467 | 468 | :param address: the buyer/seller address 469 | :param buyerOrderId: buyer order id 470 | :param end: end time in Milliseconds 471 | :param limit: default 500; max 1000. 472 | :param offset: start with 0; default 0. 473 | :param start: start with 0; default 0. 474 | :param total: total number required, 0 for not required and 1 for 475 | required; default not required, return total=-1 in response 476 | """ 477 | params: Dict[Any, Any] = {} 478 | if address is not None: 479 | params["address"] = address 480 | if end is not None: 481 | params["end"] = end 482 | if limit is not None: 483 | params["limit"] = limit 484 | if offset is not None: 485 | params["offset"] = offset 486 | if start is not None: 487 | params["start"] = start 488 | if total is not None: 489 | params["total"] = total 490 | return await self.get_request("block-exchange-fee", params=params, rps=5) 491 | 492 | async def get_transactions( 493 | self, 494 | address: str, 495 | height: int = None, 496 | end: int = None, 497 | limit: int = None, 498 | offset: int = None, 499 | side: str = None, 500 | start: int = None, 501 | tx_asset: str = None, 502 | tx_type: str = None, 503 | ) -> List[dict]: 504 | """Get transactions. 505 | 506 | Gets a list of transactions. Multisend transaction is not available in 507 | this API. 508 | 509 | Query Window: Default query window is latest 24 hours; The maximum 510 | start - end query window is 3 months. 511 | 512 | Rate Limit: 60 requests per IP per minute. 513 | 514 | :param address: address 515 | :param height: block height 516 | :param end: end time in milliseconds 517 | :param limit: limit 518 | :param offset: offset 519 | :param side: transaction side. Allowed value: [ RECEIVE, SEND] 520 | :param start: start time in milliseconds 521 | :param tx_asset: txAsset 522 | :param tx_type: transaction type. Allowed value: [ 523 | NEW_ORDER,ISSUE_TOKEN,BURN_TOKEN,LIST_TOKEN,CANCEL_ORDER,FREEZE_TOKEN, 524 | UN_FREEZE_TOKEN,TRANSFER,PROPOSAL,VOTE,MINT,DEPOSIT] 525 | """ 526 | params: Dict[Any, Any] = {"address": address} 527 | if height is not None: 528 | params["blockHeight"] = height 529 | if start is not None: 530 | params["startTime"] = start 531 | if end is not None: 532 | params["endTime"] = end 533 | if limit is not None: 534 | params["limit"] = limit 535 | if offset is not None: 536 | params["offset"] = offset 537 | if side is not None: 538 | params["side"] = side 539 | if tx_asset is not None: 540 | params["txAsset"] = tx_asset 541 | if tx_type is not None: 542 | params["txType"] = tx_type 543 | return await self.get_request("transactions", params=params) 544 | -------------------------------------------------------------------------------- /binancechain/noderpc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | """ 4 | Binance Chain Node RPC HTTP API 5 | 6 | https://docs.binance.org/api-reference/node-rpc.html#node-rpc 7 | 8 | Endpoints that require arguments: 9 | 10 | /subscribe?query=_ 11 | /unsubscribe?query=_ 12 | /unsubscribe_all? 13 | """ 14 | import itertools 15 | import warnings 16 | import logging 17 | from typing import Any, Optional, Callable 18 | 19 | import asyncio 20 | import aiohttp 21 | import orjson 22 | 23 | from .exceptions import BinanceChainException 24 | 25 | log = logging.getLogger(__name__) 26 | 27 | MAINNET_URL = "https://seed1.longevito.io:443/" 28 | TESTNET_URL = "https://seed-pre-s3.binance.org/" 29 | 30 | 31 | class NodeRPC: 32 | """ Binance Chain Node RPC HTTP API Client """ 33 | 34 | def __init__(self, url: str = None, testnet: bool = True): 35 | """ 36 | :param: url: binance chain node URL 37 | :param testnet: A boolean to enable testnet 38 | :param session: An optional HTTP session to use 39 | """ 40 | if not url: 41 | self.url = TESTNET_URL if testnet else MAINNET_URL 42 | self._id = itertools.count() 43 | self._session: Optional[aiohttp.ClientSession] = None 44 | self._testnet = testnet 45 | self._keepalive_task: Optional[asyncio.Future] = None 46 | 47 | def __del__(self): 48 | if self._session and not self._session.closed: 49 | warnings.warn(f"{repr(self)}.close() was never awaited") 50 | 51 | async def _request(self, method: str, path: str, **kwargs): 52 | """ 53 | :method: `get` or `post` 54 | :path: the remote endpoint to call 55 | :kwargs: Extra arguments to pass to the request, like `params` or `data`. 56 | """ 57 | if not self._session: 58 | self._session = aiohttp.ClientSession() 59 | try: 60 | resp = None 61 | async with getattr(self._session, method)( 62 | self.url + path, **kwargs 63 | ) as resp: 64 | return await resp.json(loads=orjson.loads) 65 | except Exception as e: 66 | raise BinanceChainException(resp) from e 67 | 68 | async def get_request(self, path: str, params: dict = None) -> Any: 69 | return await self._request("get", path, params=params) 70 | 71 | async def post_request(self, method: str, *params) -> dict: 72 | payload = { 73 | "method": method, 74 | "params": params, 75 | "jsonrpc": "2.0", 76 | "id": str(next(self._id)), 77 | } 78 | if not self._session: 79 | self._session = aiohttp.ClientSession() 80 | try: 81 | async with self._session.post(self.url, json=payload) as resp: 82 | return await resp.json(loads=orjson.loads) 83 | except Exception as e: 84 | raise BinanceChainException(resp) from e 85 | 86 | async def get_abci_info(self) -> dict: 87 | """Get some info about the application.""" 88 | return await self.get_request("abci_info") 89 | 90 | async def get_consensus_state(self) -> dict: 91 | """ConsensusState returns a concise summary of the consensus state. 92 | This is just a snapshot of consensus state, and it will not persist. 93 | """ 94 | return await self.get_request("consensus_state") 95 | 96 | async def get_dump_consensus_state(self) -> dict: 97 | """ConsensusState returns a concise summary of the consensus state. 98 | This is just a snapshot of consensus state, and it will not persist. 99 | """ 100 | return await self.get_request("dump_consensus_state") 101 | 102 | async def get_genesis(self) -> dict: 103 | """Get genesis file.""" 104 | return await self.get_request("genesis") 105 | 106 | async def get_health(self) -> dict: 107 | """Get node health. 108 | Returns empty result on success, no response - in case of an error. 109 | """ 110 | return await self.get_request("health") 111 | 112 | async def get_net_info(self) -> dict: 113 | """Get network info.""" 114 | return await self.get_request("net_info") 115 | 116 | async def get_num_unconfirmed_txs(self) -> dict: 117 | """Get number of unconfirmed transactions.""" 118 | return await self.get_request("num_unconfirmed_txs") 119 | 120 | async def get_status(self) -> dict: 121 | """Get Tendermint status including node info, pubkey, latest block 122 | hash, app hash, block height and time. 123 | """ 124 | return await self.get_request("status") 125 | 126 | async def abci_query( 127 | self, path: str, data: str = None, height: str = "0", prove: bool = False 128 | ) -> dict: 129 | """Query the application for some information. 130 | 131 | Available Query Paths: 132 | 133 | /store/acc/key 134 | /tokens/info 135 | /tokens/list 136 | /dex/pairs 137 | /dex/orderbook 138 | /param/fees 139 | """ 140 | return await self.post_request("abci_query", path, data, height, prove) 141 | 142 | async def block(self, height: Optional[int] = None) -> dict: 143 | """Query the block at a given height. 144 | 145 | :param height: height of blockchain 146 | """ 147 | return await self.post_request("block", height) 148 | 149 | async def block_by_hash(self, hash: str) -> dict: 150 | """Query a block by it's hash. 151 | :param hash: the block hash 152 | """ 153 | return await self.post_request("block_by_hash", hash) 154 | 155 | async def block_results(self, height: Optional[str] = None) -> dict: 156 | """Gets ABCIResults at a given height.""" 157 | return await self.post_request("block_results", height) 158 | 159 | async def blockchain(self, min_height: str, max_height: str) -> dict: 160 | """Get block headers for minHeight <= height <= maxHeight. 161 | Block headers are returned in descending order (highest first). 162 | """ 163 | return await self.post_request("blockchain", min_height, max_height) 164 | 165 | async def broadcast_tx_async(self, tx: str) -> dict: 166 | """ 167 | This method just returns the transaction hash right away and there is no 168 | return from CheckTx or DeliverTx. 169 | """ 170 | return await self.post_request("broadcast_tx_async", tx) 171 | 172 | async def broadcast_tx_sync(self, tx: str) -> dict: 173 | """ 174 | The transaction will be broadcasted and returns with the response from CheckTx. 175 | """ 176 | return await self.post_request("broadcast_tx_sync", tx) 177 | 178 | async def broadcast_tx_commit(self, tx: str) -> dict: 179 | """ 180 | The transaction will be broadcasted and returns with the response from 181 | CheckTx and DeliverTx. 182 | 183 | If CheckTx or DeliverTx fail, no error will be returned, but the 184 | returned result will contain a non-OK ABCI code. 185 | """ 186 | return await self.post_request("broadcast_tx_commit", tx) 187 | 188 | async def commit(self, height: Optional[str] = None) -> dict: 189 | """Get block commit at a given height. 190 | If no height is provided, it will fetch the commit for the latest block. 191 | """ 192 | return await self.post_request("commit", height) 193 | 194 | async def consensus_params(self, height: Optional[str] = None) -> dict: 195 | """Get consensus params at a given height.""" 196 | return await self.post_request("consensus_params", height) 197 | 198 | async def tx(self, hash: str, prove: bool = False) -> dict: 199 | """Allows you to query the transaction results.""" 200 | return await self.post_request("tx", hash, prove) 201 | 202 | async def tx_search( 203 | self, query: str, prove: bool = False, page: int = 1, per_page: int = 30 204 | ) -> dict: 205 | """Allows you to query for multiple transactions results. 206 | You could search transaction by its index. It returns a list of 207 | transactions (maximum ?per_page entries) and the total count. 208 | """ 209 | return await self.post_request( 210 | "tx_search", query, prove, str(page), str(per_page) 211 | ) 212 | 213 | async def unconfirmed_txs(self, limit: int = None) -> dict: 214 | """Get unconfirmed transactions.""" 215 | return await self.post_request("unconfirmed_txs", limit) 216 | 217 | async def validators(self, height: str = None) -> dict: 218 | """Get information on the validators""" 219 | return await self.post_request("validators", height) 220 | 221 | def subscribe( 222 | self, query: str, callback: Optional[Callable[[dict], None]] = None 223 | ) -> None: 224 | """Subscribe to events via WebSocket. 225 | 226 | See list of all possible events here: 227 | https://godoc.org/github.com/tendermint/tendermint/types#pkg-constants 228 | For complete query syntax, check out: 229 | https://godoc.org/github.com/tendermint/tendermint/libs/pubsub/query. 230 | """ 231 | payload = { 232 | "method": "subscribe", 233 | "params": [query], 234 | "jsonrpc": "2.0", 235 | "id": str(next(self._id)), 236 | } 237 | asyncio.ensure_future(self.send(payload)) 238 | 239 | def unsubscribe(self, query: str) -> None: 240 | """Unubscribe from events via WebSocket.""" 241 | payload = { 242 | "method": "unsubscribe", 243 | "params": [query], 244 | "jsonrpc": "2.0", 245 | "id": str(next(self._id)), 246 | } 247 | asyncio.ensure_future(self.send(payload)) 248 | 249 | def unsubscribe_all(self) -> None: 250 | """Unubscribe from all events via WebSocket.""" 251 | payload = { 252 | "method": "unsubscribe_all", 253 | "params": [], 254 | "jsonrpc": "2.0", 255 | "id": str(next(self._id)), 256 | } 257 | asyncio.ensure_future(self.send(payload)) 258 | 259 | def start( 260 | self, 261 | on_open: Optional[Callable[[], None]] = None, 262 | on_msg: Callable[[dict], None] = None, 263 | on_error: Optional[Callable[[dict], None]] = None, 264 | loop: asyncio.AbstractEventLoop = None, 265 | keepalive: bool = True, 266 | ) -> None: 267 | """The main blocking call to start the WebSocket connection.""" 268 | loop = loop or asyncio.get_event_loop() 269 | return loop.run_until_complete( 270 | self.start_async(on_open, on_msg, on_error, keepalive) 271 | ) 272 | 273 | async def start_async( 274 | self, 275 | on_open: Optional[Callable[[], None]] = None, 276 | on_msg: Callable[[dict], None] = None, 277 | on_error: Optional[Callable[[Any], None]] = None, 278 | keepalive: bool = True, 279 | ) -> None: 280 | """Processes all websocket messages. 281 | 282 | :param callback: The single callback to use for all websocket messages 283 | :param keepalive: Run a background keepAlive coroutine 284 | """ 285 | if not self._session: 286 | self._session = aiohttp.ClientSession() 287 | ws_url = f"{self.url}/websocket" 288 | async with self._session.ws_connect(ws_url) as ws: 289 | self._ws = ws 290 | if on_open: 291 | on_open() 292 | 293 | # Schedule keepalive calls every 30 minutes 294 | if keepalive: 295 | self._keepalive_task = asyncio.ensure_future(self._auto_keepalive()) 296 | 297 | async for msg in ws: 298 | if msg.type == aiohttp.WSMsgType.TEXT: 299 | try: 300 | data = msg.json(loads=orjson.loads) 301 | except Exception as e: 302 | log.error(f"Unable to decode msg: {msg}") 303 | continue 304 | if data: 305 | if on_msg: 306 | on_msg(data) 307 | elif msg.type == aiohttp.WSMsgType.ERROR: 308 | log.error(msg) 309 | if on_error: 310 | on_error(msg) 311 | break 312 | 313 | async def send(self, data: dict) -> None: 314 | """Send data to the WebSocket""" 315 | if not self._ws: 316 | log.error("Error: Cannot send to uninitialized websocket") 317 | return 318 | await self._ws.send_bytes(orjson.dumps(data)) 319 | 320 | async def _auto_keepalive(self): 321 | while True: 322 | await asyncio.sleep(30 * 60) 323 | self.keepalive() 324 | 325 | def close(self) -> None: 326 | """Close the websocket session""" 327 | if self._session and not self._session.closed: 328 | asyncio.ensure_future(self._session.close()) 329 | if self._keepalive_task: 330 | self._keepalive_task.cancel() 331 | -------------------------------------------------------------------------------- /binancechain/ratelimit.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | 4 | import asyncio 5 | 6 | from typing import Dict, Tuple, Optional 7 | 8 | 9 | class RateLimiter: 10 | """A rate-limiter that manages a token bucket for each namespace""" 11 | 12 | def __init__(self, period: int = 1): 13 | """ 14 | :param period: How often this rate limiter will wake up to fill 15 | the token buckets. Defaults to once a second. 16 | """ 17 | self.buckets: Dict[str, Tuple[asyncio.Queue, int]] = {} 18 | self.period = period 19 | self.task: Optional[asyncio.Future] = None 20 | 21 | def close(self): 22 | if self.task: 23 | self.task.cancel() 24 | 25 | async def token_manager(self): 26 | """Fills each of the token buckets at `self.period` intervals.""" 27 | while True: 28 | await asyncio.sleep(self.period) 29 | for queue, num in self.buckets.values(): 30 | for i in range(num - queue.qsize()): 31 | queue.put_nowait(1) 32 | 33 | async def limit(self, namespace: str, num: int): 34 | """Blocks for a given `namespace`, rate-limiting appropriately""" 35 | if namespace not in self.buckets: 36 | queue: asyncio.Queue = asyncio.Queue(num) 37 | self.buckets[namespace] = (queue, num) 38 | for _ in range(num): 39 | queue.put_nowait(1) 40 | if not self.task: 41 | self.task = asyncio.ensure_future(self.token_manager()) 42 | # Let the manager begin it's sleep cycle 43 | await asyncio.sleep(0.001) 44 | else: 45 | queue = self.buckets[namespace][0] 46 | await queue.get() 47 | -------------------------------------------------------------------------------- /binancechain/transaction.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package transaction; 4 | 5 | 6 | message StdTx { 7 | repeated bytes msgs = 1; 8 | repeated bytes signatures = 2; 9 | string memo = 3; 10 | int64 source = 4; 11 | bytes data = 5; 12 | } 13 | message StdSignature{ 14 | bytes pub_key = 1; 15 | bytes signature = 2; 16 | int64 account_number = 3; 17 | int64 sequence = 4; 18 | } 19 | message Msg{ 20 | 21 | } 22 | 23 | message NewOrder { 24 | bytes sender = 1; 25 | string id = 2; 26 | string symbol = 3; 27 | int64 ordertype = 4; 28 | int64 side = 5; 29 | int64 price = 6; 30 | int64 quantity = 7; 31 | int64 timeinforce = 8; 32 | 33 | } 34 | message CancelOrder{ 35 | bytes sender = 1; 36 | string symbol = 2; 37 | string refid = 3; 38 | } 39 | message Send{ 40 | repeated Input inputs = 1; 41 | repeated Output outputs = 2; 42 | } 43 | message Token { 44 | string denom = 1; 45 | int64 amount = 2; 46 | } 47 | message Input { 48 | bytes address = 1; 49 | repeated Token coins = 2; 50 | } 51 | message Output { 52 | bytes address = 1; 53 | repeated Token coins = 2; 54 | } 55 | message Freeze{ 56 | bytes from = 1; 57 | string symbol = 2; 58 | int64 amount = 3; 59 | } 60 | message Unfreeze{ 61 | bytes from = 1; 62 | string symbol = 2; 63 | int64 amount = 3; 64 | } 65 | message Vote{ 66 | int64 proposal_id = 1; 67 | bytes voter = 2; 68 | VoteOption option = 3; 69 | } 70 | 71 | enum VoteOption { 72 | Unknown = 0; 73 | Yes = 1; 74 | Abstain = 2; 75 | No = 3; 76 | NoWithVeto = 4; 77 | } 78 | 79 | message Issue{ 80 | bytes from = 1; 81 | string name = 2; 82 | string symbol = 3; 83 | int64 total_supply = 4; 84 | bool mintable = 5; 85 | } 86 | 87 | message Mint{ 88 | bytes from = 1; 89 | string symbol = 2; 90 | int64 amount = 3; 91 | } 92 | 93 | message Burn{ 94 | bytes from = 1; 95 | string symbol = 2; 96 | int64 amount = 3; 97 | } 98 | -------------------------------------------------------------------------------- /binancechain/transaction.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | """ 4 | Create and manage Transactions 5 | """ 6 | from typing import Union, Any, Tuple, Optional, List, Dict 7 | from decimal import Decimal 8 | from .httpclient import HTTPClient 9 | from .enums import Ordertype, Side, Timeinforce, Votes 10 | from .transaction_base import TransactionBase 11 | 12 | TESTNET_CHAIN_ID = "Binance-Chain-Nile" 13 | MAINNET_CHAIN_ID = "Binance-Chain-Tigris" 14 | 15 | number_type = Union[str, float, int, Decimal] 16 | 17 | 18 | class Transaction: 19 | @staticmethod 20 | async def new_order_transaction( 21 | address: str, 22 | symbol: str, 23 | side: Side, 24 | price: number_type, 25 | quantity: number_type, 26 | ordertype: Ordertype = Ordertype.LIMIT, 27 | timeInForce: Timeinforce = Timeinforce.GTE, 28 | testnet: bool = False, 29 | account_number: int = None, 30 | sequence: int = None, 31 | client: Any = None, 32 | ) -> TransactionBase: 33 | """ Create New Order TransactionBase object""" 34 | transaction = await Transaction.prepare_transaction( 35 | address=address, 36 | client=client, 37 | testnet=testnet, 38 | account_number=account_number, 39 | sequence=sequence, 40 | ) 41 | transaction.get_new_order_msg( 42 | symbol=symbol, 43 | side=side, 44 | ordertype=ordertype, 45 | price=price, 46 | quantity=quantity, 47 | timeInForce=timeInForce, 48 | ) 49 | return transaction 50 | 51 | @staticmethod 52 | async def cancel_order_transaction( 53 | address: str, 54 | symbol: str, 55 | refid: str, 56 | testnet: bool = False, 57 | account_number: int = None, 58 | sequence: int = None, 59 | client: Any = None, 60 | ) -> TransactionBase: 61 | """ 62 | Create Cancel order TransactionBase object 63 | """ 64 | transaction = await Transaction.prepare_transaction( 65 | address=address, 66 | client=client, 67 | testnet=testnet, 68 | account_number=account_number, 69 | sequence=sequence, 70 | ) 71 | transaction.get_cancel_order_msg(symbol=symbol, refid=refid) 72 | return transaction 73 | 74 | @staticmethod 75 | async def transfer_transaction( 76 | from_address: str, 77 | to_address: str, 78 | symbol: str, 79 | amount: number_type, 80 | testnet: bool = False, 81 | account_number: int = None, 82 | sequence: int = None, 83 | client: Any = None, 84 | ) -> TransactionBase: 85 | """ 86 | Create transfer Transaction Base object 87 | """ 88 | transaction = await Transaction.prepare_transaction( 89 | address=from_address, 90 | client=client, 91 | testnet=testnet, 92 | account_number=account_number, 93 | sequence=sequence, 94 | ) 95 | transaction.get_transfer_msg( 96 | to_address=to_address, symbol=symbol, amount=amount 97 | ) 98 | return transaction 99 | 100 | @staticmethod 101 | async def multi_transfer_transaction( 102 | from_address: str, 103 | to_address: str, 104 | transfers: List[Dict[str, number_type]], 105 | testnet: bool = False, 106 | account_number: int = None, 107 | sequence: int = None, 108 | client: Any = None, 109 | ) -> TransactionBase: 110 | """ 111 | Create transfer Transaction Base object 112 | """ 113 | transaction = await Transaction.prepare_transaction( 114 | address=from_address, 115 | client=client, 116 | testnet=testnet, 117 | account_number=account_number, 118 | sequence=sequence, 119 | ) 120 | transaction.get_multi_transfer_msg(to_address=to_address, transfers=transfers) 121 | return transaction 122 | 123 | @staticmethod 124 | async def freeze_token_transaction( 125 | address: str, 126 | symbol: str, 127 | amount: number_type, 128 | testnet: bool = False, 129 | account_number: int = None, 130 | sequence: int = None, 131 | client: Any = None, 132 | ) -> TransactionBase: 133 | """ 134 | Create free_token TransactionBase object 135 | """ 136 | transaction = await Transaction.prepare_transaction( 137 | address=address, 138 | client=client, 139 | testnet=testnet, 140 | account_number=account_number, 141 | sequence=sequence, 142 | ) 143 | transaction.get_freeze_token_msg(symbol=symbol, amount=amount) 144 | return transaction 145 | 146 | @staticmethod 147 | async def unfreeze_token_transaction( 148 | address: str, 149 | symbol: str, 150 | amount: number_type, 151 | testnet: bool = False, 152 | account_number: int = None, 153 | sequence: int = None, 154 | client: Any = None, 155 | ) -> TransactionBase: 156 | """ 157 | Create unfreeze token TransactionBase object 158 | """ 159 | transaction = await Transaction.prepare_transaction( 160 | address=address, 161 | client=client, 162 | testnet=testnet, 163 | account_number=account_number, 164 | sequence=sequence, 165 | ) 166 | transaction.get_unfreeze_token_msg(symbol=symbol, amount=amount) 167 | return transaction 168 | 169 | @staticmethod 170 | async def vote_transaction( 171 | voter: str, 172 | proposal_id: Union[int, str], 173 | option: Votes, 174 | testnet: bool = False, 175 | account_number: int = None, 176 | sequence: int = None, 177 | client: Any = None, 178 | ) -> TransactionBase: 179 | """ 180 | Create vote TransactionBase object 181 | """ 182 | transaction = await Transaction.prepare_transaction( 183 | address=voter, 184 | client=client, 185 | testnet=testnet, 186 | account_number=account_number, 187 | sequence=sequence, 188 | ) 189 | transaction.get_vote_msg(proposal_id=proposal_id, option=option) 190 | return transaction 191 | 192 | @staticmethod 193 | async def issue_token_transaction( 194 | owner: str, 195 | name: str, 196 | symbol: str, 197 | supply: int, 198 | mintable: bool, 199 | client: Any = None, 200 | testnet: bool = False, 201 | account_number: int = None, 202 | sequence: int = None, 203 | ): 204 | transaction = await Transaction.prepare_transaction( 205 | address=owner, 206 | client=client, 207 | testnet=testnet, 208 | account_number=account_number, 209 | sequence=sequence, 210 | ) 211 | transaction.get_issue_msg( 212 | name=name, symbol=symbol, supply=supply, mintable=mintable 213 | ) 214 | return transaction 215 | 216 | @staticmethod 217 | async def mint_token_transaction( 218 | owner: str, 219 | symbol: str, 220 | amount: number_type, 221 | client: Any = None, 222 | testnet: bool = False, 223 | account_number: int = None, 224 | sequence: int = None, 225 | ): 226 | transaction = await Transaction.prepare_transaction( 227 | address=owner, 228 | client=client, 229 | testnet=testnet, 230 | account_number=account_number, 231 | sequence=sequence, 232 | ) 233 | transaction.get_mint_msg(symbol=symbol, amount=amount) 234 | return transaction 235 | 236 | @staticmethod 237 | async def burn_token_transaction( 238 | owner: str, 239 | symbol: str, 240 | amount: number_type, 241 | client: Any = None, 242 | testnet: bool = False, 243 | account_number: int = None, 244 | sequence: int = None, 245 | ): 246 | transaction = await Transaction.prepare_transaction( 247 | address=owner, 248 | client=client, 249 | testnet=testnet, 250 | account_number=account_number, 251 | sequence=sequence, 252 | ) 253 | transaction.get_burn_msg(symbol=symbol, amount=amount) 254 | return transaction 255 | 256 | @staticmethod 257 | async def prepare_transaction( 258 | address: str, 259 | client: Any = None, 260 | testnet: bool = False, 261 | account_number: int = None, 262 | sequence: int = None, 263 | ): 264 | if not client: 265 | client = HTTPClient(testnet=testnet) 266 | chain_id = TESTNET_CHAIN_ID if testnet else MAINNET_CHAIN_ID 267 | else: 268 | chain_id = TESTNET_CHAIN_ID if client._testnet else MAINNET_CHAIN_ID 269 | account_info = await client.get_account(address) 270 | if not account_info: 271 | account_number = account_number if account_number else 0 272 | sequence = sequence if sequence else 0 273 | else: 274 | account_number = account_info["account_number"] 275 | sequence = account_info["sequence"] 276 | transaction = TransactionBase( 277 | address=address, 278 | account_number=account_number, 279 | sequence=sequence, 280 | chainid=chain_id, 281 | ) 282 | return transaction 283 | 284 | def __init__(self, wallet: Any, client: Any = None, testnet: bool = False): 285 | self.wallet = wallet 286 | self.address = wallet.get_address() 287 | if not client: 288 | self.client = HTTPClient(testnet=testnet) 289 | else: 290 | self.client = client 291 | 292 | async def get_account_info(self) -> Tuple[int, int]: 293 | """Get account number and current valid sequence number""" 294 | account_info = await self.client.get_account(self.address) 295 | account_number = account_info["account_number"] 296 | sequence = account_info["sequence"] 297 | return account_number, sequence 298 | 299 | async def create_new_order( 300 | self, 301 | symbol: str, 302 | side: Side, 303 | ordertype: Ordertype, 304 | price: number_type, 305 | quantity: number_type, 306 | timeInForce: Timeinforce, 307 | ) -> Any: 308 | """ 309 | Create,sign and broadcast new_order tranasction 310 | """ 311 | account_number, sequence = await self.get_account_info() 312 | transaction = await Transaction.new_order_transaction( 313 | address=self.address, 314 | client=self.client, 315 | account_number=account_number, 316 | sequence=sequence, 317 | symbol=symbol, 318 | side=side, 319 | ordertype=ordertype, 320 | price=price, 321 | quantity=quantity, 322 | timeInForce=timeInForce, 323 | ) 324 | return await self.sign_and_broadcast(transaction) 325 | 326 | async def cancel_order(self, symbol: str, refid: str) -> Any: 327 | """Create, sign and broadcast cancel_order transaction""" 328 | account_number, sequence = await self.get_account_info() 329 | transaction = await Transaction.cancel_order_transaction( 330 | address=self.address, 331 | client=self.client, 332 | account_number=account_number, 333 | sequence=sequence, 334 | symbol=symbol, 335 | refid=refid, 336 | ) 337 | return await self.sign_and_broadcast(transaction) 338 | 339 | async def transfer(self, to_address: str, symbol: str, amount: number_type) -> Any: 340 | """Create, sign and broadcast transfer transaction""" 341 | account_number, sequence = await self.get_account_info() 342 | transaction = await Transaction.transfer_transaction( 343 | from_address=self.address, 344 | client=self.client, 345 | account_number=account_number, 346 | sequence=sequence, 347 | to_address=to_address, 348 | symbol=symbol, 349 | amount=amount, 350 | ) 351 | return await self.sign_and_broadcast(transaction) 352 | 353 | async def multi_transfer( 354 | self, to_address: str, transfers: List[Dict[str, number_type]] 355 | ) -> Any: 356 | """Create, sign and broadcast transfer transaction""" 357 | account_number, sequence = await self.get_account_info() 358 | transaction = await Transaction.multi_transfer_transaction( 359 | from_address=self.address, 360 | client=self.client, 361 | account_number=account_number, 362 | sequence=sequence, 363 | to_address=to_address, 364 | transfers=transfers, 365 | ) 366 | return await self.sign_and_broadcast(transaction) 367 | 368 | async def freeze_token(self, symbol: str, amount: number_type) -> Any: 369 | """Create, sign and broadcast free_token transaction""" 370 | account_number, sequence = await self.get_account_info() 371 | transaction = await Transaction.freeze_token_transaction( 372 | address=self.address, 373 | client=self.client, 374 | account_number=account_number, 375 | sequence=sequence, 376 | symbol=symbol, 377 | amount=amount, 378 | ) 379 | return await self.sign_and_broadcast(transaction) 380 | 381 | async def unfreeze_token(self, symbol: str, amount: number_type) -> Any: 382 | """Create, sign and broadcast unfreeze_token transaction""" 383 | account_number, sequence = await self.get_account_info() 384 | transaction = await Transaction.unfreeze_token_transaction( 385 | address=self.address, 386 | client=self.client, 387 | account_number=account_number, 388 | sequence=sequence, 389 | symbol=symbol, 390 | amount=amount, 391 | ) 392 | return await self.sign_and_broadcast(transaction) 393 | 394 | async def vote(self, proposal_id: str, option: Votes): 395 | account_number, sequence = await self.get_account_info() 396 | transaction = await Transaction.vote_transaction( 397 | voter=self.address, 398 | client=self.client, 399 | account_number=account_number, 400 | sequence=sequence, 401 | proposal_id=proposal_id, 402 | option=option, 403 | ) 404 | return await self.sign_and_broadcast(transaction) 405 | 406 | async def issue_token(self, name: str, symbol: str, supply: int, mintable: bool): 407 | account_number, sequence = await self.get_account_info() 408 | transaction = await Transaction.issue_token_transaction( 409 | client=self.client, 410 | owner=self.address, 411 | name=name, 412 | symbol=symbol, 413 | supply=supply, 414 | mintable=mintable, 415 | ) 416 | return await self.sign_and_broadcast(transaction) 417 | 418 | async def mint_token(self, symbol: str, amount: number_type): 419 | account_number, sequence = await self.get_account_info() 420 | transaction = await Transaction.mint_token_transaction( 421 | client=self.client, owner=self.address, symbol=symbol, amount=amount 422 | ) 423 | return await self.sign_and_broadcast(transaction) 424 | 425 | async def burn_token(self, symbol: str, amount: number_type): 426 | account_number, sequence = await self.get_account_info() 427 | transaction = await Transaction.burn_token_transaction( 428 | client=self.client, owner=self.address, symbol=symbol, amount=amount 429 | ) 430 | return await self.sign_and_broadcast(transaction) 431 | 432 | async def sign_and_broadcast(self, transaction: TransactionBase) -> Any: 433 | """Sign and broadcast an TransactionBase object""" 434 | pub, sig = self.wallet.sign(transaction.get_sign_message()) 435 | hex_data = transaction.update_signature(pub, sig) 436 | broadcast_info = await self.client.broadcast(hex_data) 437 | return broadcast_info 438 | -------------------------------------------------------------------------------- /binancechain/transaction_base.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | from typing import Union, Any, Tuple, Optional, List, Dict 3 | import json 4 | from bitcoinlib import encoding 5 | from varint import encode 6 | from decimal import Decimal 7 | from .crypto import generate_id, address_decode 8 | from .enums import Ordertype, Side, Timeinforce, Votes 9 | from .transaction_pb2 import ( 10 | CancelOrder, 11 | Freeze, 12 | NewOrder, 13 | Send, 14 | StdSignature, 15 | Unfreeze, 16 | Vote, 17 | StdTx, 18 | Input, 19 | Output, 20 | Token, 21 | Issue, 22 | Mint, 23 | Burn, 24 | ) 25 | 26 | SOURCE = "1" 27 | TYPE_PREFIX = { 28 | "CancelOrder": b"166E681B", 29 | "TokenFreeze": b"E774B32D", 30 | "TokenUnfreeze": b"6515FF0D", 31 | "NewOrder": b"CE6DC043", 32 | "Send": b"2A2C87FA", 33 | "PubKey": b"EB5AE987", 34 | "StdTx": b"F0625DEE", 35 | "Vote": b"A1CADD36", 36 | "Issue": b"17EFAB80", 37 | "Mint": b"467E0829", 38 | "Burn": b"7ED2D2A0", 39 | } 40 | 41 | BASE = 100000000 42 | number_type = Union[str, float, int, Decimal] 43 | 44 | 45 | class TransactionBase: 46 | def __init__( 47 | self, 48 | address: str, 49 | account_number, 50 | sequence, 51 | chainid: str, 52 | memo: str = "", 53 | data: str = "", 54 | ): 55 | self.account_number = account_number 56 | self.data = data.encode() 57 | self.memo = memo 58 | self.address = address 59 | self.sequence = sequence 60 | self.StdSignMsg = { 61 | "memo": self.memo, 62 | "msgs": [dict], 63 | "account_number": str(self.account_number), 64 | "sequence": str(self.sequence), 65 | "chain_id": chainid, 66 | "source": str(SOURCE), 67 | "data": None, 68 | } 69 | print("CHAIN ID ", chainid) 70 | 71 | def get_new_order_msg( 72 | self, 73 | symbol: str, 74 | side: Side, 75 | price: number_type, 76 | quantity: number_type, 77 | sequence=None, 78 | ordertype: Ordertype = Ordertype.LIMIT, 79 | timeInForce: Timeinforce = Timeinforce.GTE, 80 | ): 81 | """Create new_order protobuf attributes, SignMessage of the transaction""" 82 | id = generate_id(self.address, self.sequence) 83 | price = int(Decimal(price) * BASE) 84 | quantity = int(Decimal(quantity) * Decimal(100000000)) 85 | self.msg = { 86 | "symbol": symbol, 87 | "sender": self.address, 88 | "id": id, 89 | "side": side.value, 90 | "ordertype": ordertype.value, 91 | "timeinforce": timeInForce.value, 92 | "price": price, 93 | "quantity": quantity, 94 | } 95 | self.StdSignMsg["msgs"] = [self.msg] 96 | self.SignMessage = json.dumps( 97 | self.StdSignMsg, sort_keys=True, separators=(",", ":") 98 | ).encode() 99 | self.stdMsg = self.generate_stdNewOrderMsg(self.msg) 100 | return self.SignMessage 101 | 102 | def generate_stdNewOrderMsg(self, msg: dict) -> bytes: 103 | """Generate StdMsg part of StdTx""" 104 | std = NewOrder() 105 | std.sender = address_decode(self.address) 106 | std.id = generate_id(self.address, self.sequence) 107 | std.ordertype = msg[ 108 | "ordertype" 109 | ] # currently only 1 type : limit =2, will change in the future 110 | std.symbol = msg["symbol"].encode() 111 | std.side = msg["side"] 112 | std.price = msg["price"] 113 | std.quantity = msg["quantity"] 114 | std.timeinforce = msg["timeinforce"] 115 | proto_bytes = std.SerializeToString() 116 | type_bytes = encoding.to_bytes(TYPE_PREFIX["NewOrder"]) 117 | return type_bytes + proto_bytes 118 | 119 | def get_cancel_order_msg(self, symbol: str, refid: str): 120 | """Generate cancel_order StdMsg for StdTx and SignMessage for current transaction""" 121 | self.msg = {"sender": self.address, "symbol": symbol, "refid": refid} 122 | self.StdSignMsg["msgs"] = [self.msg] 123 | self.SignMessage = json.dumps( 124 | self.StdSignMsg, sort_keys=True, separators=(",", ":") 125 | ).encode() 126 | std = CancelOrder() 127 | std.symbol = symbol 128 | std.sender = address_decode(self.address) 129 | std.refid = refid 130 | self.stdMsg = ( 131 | encoding.to_bytes(TYPE_PREFIX["CancelOrder"]) + std.SerializeToString() 132 | ) 133 | return self.SignMessage 134 | 135 | def get_transfer_msg(self, to_address: str, symbol: str, amount: number_type): 136 | """Generate transfer StdMsg for StdTx and SignMessage for current transaction""" 137 | amount = int(Decimal(amount) * BASE) 138 | self.msg = { 139 | "inputs": [ 140 | { 141 | "address": self.address, 142 | "coins": [{"denom": symbol, "amount": amount}], 143 | } 144 | ], 145 | "outputs": [ 146 | {"address": to_address, "coins": [{"denom": symbol, "amount": amount}]} 147 | ], 148 | } 149 | self.StdSignMsg["msgs"] = [self.msg] 150 | 151 | self.SignMessage = json.dumps( 152 | self.StdSignMsg, sort_keys=True, separators=(",", ":") 153 | ).encode() 154 | std = Send() 155 | input = Input() 156 | output = Output() 157 | token_proto = Token() 158 | token_proto.amount = amount 159 | token_proto.denom = symbol.encode() 160 | input.address = address_decode(self.address) 161 | input.coins.extend([token_proto]) 162 | output.address = address_decode(to_address) 163 | output.coins.extend([token_proto]) 164 | std.inputs.extend([input]) 165 | std.outputs.extend([output]) 166 | self.stdMsg = encoding.to_bytes(TYPE_PREFIX["Send"]) + std.SerializeToString() 167 | return self.SignMessage 168 | 169 | def get_multi_transfer_msg( 170 | self, to_address: str, transfers: List[Dict[str, number_type]] 171 | ): 172 | """ Generate StdMsg and SignMessage for multiple tokens send in one transaction""" 173 | coins = [] 174 | input = Input() 175 | output = Output() 176 | for transfer in transfers: 177 | coin = {} 178 | token = Token() 179 | amount = transfer["amount"] 180 | coin["denom"] = transfer["symbol"] 181 | coin["amount"] = token.amount = int(Decimal(amount) * BASE) 182 | token.denom = str(transfer["symbol"]).encode() 183 | coins.append(coin) 184 | input.coins.extend([token]) 185 | output.coins.extend([token]) 186 | self.msg = { 187 | "inputs": [{"address": self.address, "coins": coins}], 188 | "outputs": [{"address": to_address, "coins": coins}], 189 | } 190 | self.StdSignMsg["msgs"] = [self.msg] 191 | self.SignMessage = json.dumps( 192 | self.StdSignMsg, sort_keys=True, separators=(",", ":") 193 | ).encode() 194 | std = Send() 195 | input.address = address_decode(self.address) 196 | output.address = address_decode(to_address) 197 | std.inputs.extend([input]) 198 | std.outputs.extend([output]) 199 | self.stdMsg = encoding.to_bytes(TYPE_PREFIX["Send"]) + std.SerializeToString() 200 | return self.SignMessage 201 | 202 | def get_freeze_token_msg(self, symbol: str, amount: number_type): 203 | """Generate freeze_token StdMsg for StdTx and SignMessage for current transaction""" 204 | amount = int(Decimal(amount) * BASE) 205 | self.msg = {"from": self.address, "symbol": symbol, "amount": amount} 206 | self.StdSignMsg["msgs"] = [self.msg] 207 | self.SignMessage = json.dumps( 208 | self.StdSignMsg, sort_keys=True, separators=(",", ":") 209 | ).encode() 210 | std = Freeze() 211 | setattr(std, "from", address_decode(self.address)) 212 | std.symbol = symbol 213 | std.amount = amount 214 | self.stdMsg = ( 215 | encoding.to_bytes(TYPE_PREFIX["TokenFreeze"]) + std.SerializeToString() 216 | ) 217 | return self.SignMessage 218 | 219 | def get_unfreeze_token_msg(self, symbol: str, amount: number_type): 220 | """Generate unfreeze_token StdMsg for StdTx and SignMessage for current transaction""" 221 | amount = int(Decimal(amount) * BASE) 222 | self.msg = {"from": self.address, "symbol": symbol, "amount": amount} 223 | self.StdSignMsg["msgs"] = [self.msg] 224 | self.SignMessage = json.dumps( 225 | self.StdSignMsg, sort_keys=True, separators=(",", ":") 226 | ).encode() 227 | std = Freeze() 228 | setattr(std, "from", address_decode(self.address)) 229 | std.symbol = symbol 230 | std.amount = amount 231 | self.stdMsg = ( 232 | encoding.to_bytes(TYPE_PREFIX["TokenUnfreeze"]) + std.SerializeToString() 233 | ) 234 | return self.SignMessage 235 | 236 | def get_vote_msg(self, proposal_id: Union[str, int], option: Votes): 237 | """Generate cancel_order StdMsg for StdTx and SignMessage for current transaction""" 238 | self.msg = { 239 | "proposal_id": proposal_id, 240 | "voter": self.address, 241 | "option": option.value, 242 | } 243 | self.StdSignMsg["msgs"] = [self.msg] 244 | self.SignMessage = json.dumps( 245 | self.StdSignMsg, sort_keys=True, separators=(",", ":") 246 | ).encode() 247 | std = Vote() 248 | std.voter = address_decode(self.address) 249 | std.proposal_id = proposal_id 250 | std.option = option.value 251 | self.stdMsg = encoding.to_bytes(TYPE_PREFIX["Vote"]) + std.SerializeToString() 252 | return self.SignMessage 253 | 254 | def get_issue_msg(self, name: str, symbol: str, supply: int, mintable): 255 | """ Generate issue_token StdMsg and SignMessage""" 256 | self.msg = { 257 | "from": self.address, 258 | "name": name, 259 | "symbol": symbol, 260 | "total_supply": supply, 261 | "mintable": mintable, 262 | } 263 | self.StdSignMsg["msgs"] = [self.msg] 264 | self.SignMessage = json.dumps( 265 | self.StdSignMsg, sort_keys=True, separators=(",", ":") 266 | ).encode() 267 | std = Issue() 268 | setattr(std, "from", address_decode(self.address)) 269 | std.name = name 270 | std.symbol = symbol 271 | std.total_supply = int(supply) 272 | std.mintable = mintable 273 | self.stdMsg = encoding.to_bytes(TYPE_PREFIX["Issue"]) + std.SerializeToString() 274 | return self.SignMessage 275 | 276 | def get_mint_msg(self, symbol: str, amount: number_type): 277 | """ Generate mint_token StdMsg and SignMessage""" 278 | amount = int(Decimal(amount) * BASE) 279 | self.msg = {"from": self.address, "symbol": symbol, "amount": amount} 280 | self.StdSignMsg["msgs"] = [self.msg] 281 | self.SignMessage = json.dumps( 282 | self.StdSignMsg, sort_keys=True, separators=(",", ":") 283 | ).encode() 284 | std = Mint() 285 | setattr(std, "from", address_decode(self.address)) 286 | std.symbol = symbol 287 | std.amount = amount 288 | self.stdMsg = encoding.to_bytes(TYPE_PREFIX["Mint"]) + std.SerializeToString() 289 | return self.SignMessage 290 | 291 | def get_burn_msg(self, symbol: str, amount: number_type): 292 | """ Generate burn_token StdMsg and SignMessage""" 293 | amount = int(Decimal(amount) * BASE) 294 | self.msg = {"from": self.address, "symbol": symbol, "amount": amount} 295 | self.StdSignMsg["msgs"] = [self.msg] 296 | self.SignMessage = json.dumps( 297 | self.StdSignMsg, sort_keys=True, separators=(",", ":") 298 | ).encode() 299 | std = Burn() 300 | setattr(std, "from", address_decode(self.address)) 301 | std.symbol = symbol 302 | std.amount = amount 303 | self.stdMsg = encoding.to_bytes(TYPE_PREFIX["Burn"]) + std.SerializeToString() 304 | return self.SignMessage 305 | 306 | def get_sign_message(self): 307 | return self.SignMessage 308 | 309 | def update_signature(self, pubkey: str, signature: str): 310 | """ 311 | :Update current transaction with pubkey and signature from self.address 312 | :Create StdTx proto 313 | :Return data hex ready to be broadcast 314 | """ 315 | pubkey_bytes = self.pubkey_to_msg(pubkey) 316 | self.stdSignature = self.generate_stdSignatureMsg(pubkey_bytes, signature) 317 | self.stdTx = self.generate_StdTxMsg() 318 | return binascii.hexlify(self.stdTx) 319 | 320 | def pubkey_to_msg(self, pubkey: str): 321 | key_bytes = encoding.to_bytes(pubkey) 322 | return ( 323 | encoding.to_bytes(TYPE_PREFIX["PubKey"]) 324 | + encode(len(key_bytes)) 325 | + key_bytes 326 | ) 327 | 328 | def generate_stdSignatureMsg(self, pubkey_bytes: bytes, signature: str): 329 | """Generate StdSignature for StdTx""" 330 | std = StdSignature() 331 | std.pub_key = pubkey_bytes 332 | std.signature = encoding.to_bytes(signature) 333 | std.account_number = self.account_number 334 | std.sequence = self.sequence 335 | proto_bytes = std.SerializeToString() 336 | return proto_bytes 337 | 338 | def generate_StdTxMsg(self): 339 | """Geneate StdTx""" 340 | std = StdTx() 341 | std.msgs.extend([self.stdMsg]) 342 | std.signatures.extend([self.stdSignature]) 343 | std.memo = self.memo 344 | std.source = 1 345 | std.data = self.data 346 | proto_bytes = std.SerializeToString() 347 | type_bytes = encoding.to_bytes(TYPE_PREFIX["StdTx"]) 348 | return encode(len(proto_bytes) + len(type_bytes)) + type_bytes + proto_bytes 349 | 350 | def ___repr__(self): 351 | return "test string transaction" 352 | -------------------------------------------------------------------------------- /binancechain/transaction_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: transaction.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf.internal import enum_type_wrapper 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf import descriptor_pb2 12 | # @@protoc_insertion_point(imports) 13 | 14 | _sym_db = _symbol_database.Default() 15 | 16 | 17 | 18 | 19 | DESCRIPTOR = _descriptor.FileDescriptor( 20 | name='transaction.proto', 21 | package='transaction', 22 | syntax='proto3', 23 | serialized_pb=_b('\n\x11transaction.proto\x12\x0btransaction\"U\n\x05StdTx\x12\x0c\n\x04msgs\x18\x01 \x03(\x0c\x12\x12\n\nsignatures\x18\x02 \x03(\x0c\x12\x0c\n\x04memo\x18\x03 \x01(\t\x12\x0e\n\x06source\x18\x04 \x01(\x03\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\"\\\n\x0cStdSignature\x12\x0f\n\x07pub_key\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x03 \x01(\x03\x12\x10\n\x08sequence\x18\x04 \x01(\x03\"\x05\n\x03Msg\"\x8d\x01\n\x08NewOrder\x12\x0e\n\x06sender\x18\x01 \x01(\x0c\x12\n\n\x02id\x18\x02 \x01(\t\x12\x0e\n\x06symbol\x18\x03 \x01(\t\x12\x11\n\tordertype\x18\x04 \x01(\x03\x12\x0c\n\x04side\x18\x05 \x01(\x03\x12\r\n\x05price\x18\x06 \x01(\x03\x12\x10\n\x08quantity\x18\x07 \x01(\x03\x12\x13\n\x0btimeinforce\x18\x08 \x01(\x03\"<\n\x0b\x43\x61ncelOrder\x12\x0e\n\x06sender\x18\x01 \x01(\x0c\x12\x0e\n\x06symbol\x18\x02 \x01(\t\x12\r\n\x05refid\x18\x03 \x01(\t\"P\n\x04Send\x12\"\n\x06inputs\x18\x01 \x03(\x0b\x32\x12.transaction.Input\x12$\n\x07outputs\x18\x02 \x03(\x0b\x32\x13.transaction.Output\"&\n\x05Token\x12\r\n\x05\x64\x65nom\x18\x01 \x01(\t\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x03\";\n\x05Input\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12!\n\x05\x63oins\x18\x02 \x03(\x0b\x32\x12.transaction.Token\"<\n\x06Output\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12!\n\x05\x63oins\x18\x02 \x03(\x0b\x32\x12.transaction.Token\"6\n\x06\x46reeze\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x0c\x12\x0e\n\x06symbol\x18\x02 \x01(\t\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x03\"8\n\x08Unfreeze\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x0c\x12\x0e\n\x06symbol\x18\x02 \x01(\t\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x03\"S\n\x04Vote\x12\x13\n\x0bproposal_id\x18\x01 \x01(\x03\x12\r\n\x05voter\x18\x02 \x01(\x0c\x12\'\n\x06option\x18\x03 \x01(\x0e\x32\x17.transaction.VoteOption\"[\n\x05Issue\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06symbol\x18\x03 \x01(\t\x12\x14\n\x0ctotal_supply\x18\x04 \x01(\x03\x12\x10\n\x08mintable\x18\x05 \x01(\x08\"4\n\x04Mint\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x0c\x12\x0e\n\x06symbol\x18\x02 \x01(\t\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x03\"4\n\x04\x42urn\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x0c\x12\x0e\n\x06symbol\x18\x02 \x01(\t\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x03*G\n\nVoteOption\x12\x0b\n\x07Unknown\x10\x00\x12\x07\n\x03Yes\x10\x01\x12\x0b\n\x07\x41\x62stain\x10\x02\x12\x06\n\x02No\x10\x03\x12\x0e\n\nNoWithVeto\x10\x04\x62\x06proto3') 24 | ) 25 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 26 | 27 | _VOTEOPTION = _descriptor.EnumDescriptor( 28 | name='VoteOption', 29 | full_name='transaction.VoteOption', 30 | filename=None, 31 | file=DESCRIPTOR, 32 | values=[ 33 | _descriptor.EnumValueDescriptor( 34 | name='Unknown', index=0, number=0, 35 | options=None, 36 | type=None), 37 | _descriptor.EnumValueDescriptor( 38 | name='Yes', index=1, number=1, 39 | options=None, 40 | type=None), 41 | _descriptor.EnumValueDescriptor( 42 | name='Abstain', index=2, number=2, 43 | options=None, 44 | type=None), 45 | _descriptor.EnumValueDescriptor( 46 | name='No', index=3, number=3, 47 | options=None, 48 | type=None), 49 | _descriptor.EnumValueDescriptor( 50 | name='NoWithVeto', index=4, number=4, 51 | options=None, 52 | type=None), 53 | ], 54 | containing_type=None, 55 | options=None, 56 | serialized_start=1073, 57 | serialized_end=1144, 58 | ) 59 | _sym_db.RegisterEnumDescriptor(_VOTEOPTION) 60 | 61 | VoteOption = enum_type_wrapper.EnumTypeWrapper(_VOTEOPTION) 62 | Unknown = 0 63 | Yes = 1 64 | Abstain = 2 65 | No = 3 66 | NoWithVeto = 4 67 | 68 | 69 | 70 | _STDTX = _descriptor.Descriptor( 71 | name='StdTx', 72 | full_name='transaction.StdTx', 73 | filename=None, 74 | file=DESCRIPTOR, 75 | containing_type=None, 76 | fields=[ 77 | _descriptor.FieldDescriptor( 78 | name='msgs', full_name='transaction.StdTx.msgs', index=0, 79 | number=1, type=12, cpp_type=9, label=3, 80 | has_default_value=False, default_value=[], 81 | message_type=None, enum_type=None, containing_type=None, 82 | is_extension=False, extension_scope=None, 83 | options=None), 84 | _descriptor.FieldDescriptor( 85 | name='signatures', full_name='transaction.StdTx.signatures', index=1, 86 | number=2, type=12, cpp_type=9, label=3, 87 | has_default_value=False, default_value=[], 88 | message_type=None, enum_type=None, containing_type=None, 89 | is_extension=False, extension_scope=None, 90 | options=None), 91 | _descriptor.FieldDescriptor( 92 | name='memo', full_name='transaction.StdTx.memo', index=2, 93 | number=3, type=9, cpp_type=9, label=1, 94 | has_default_value=False, default_value=_b("").decode('utf-8'), 95 | message_type=None, enum_type=None, containing_type=None, 96 | is_extension=False, extension_scope=None, 97 | options=None), 98 | _descriptor.FieldDescriptor( 99 | name='source', full_name='transaction.StdTx.source', index=3, 100 | number=4, type=3, cpp_type=2, label=1, 101 | has_default_value=False, default_value=0, 102 | message_type=None, enum_type=None, containing_type=None, 103 | is_extension=False, extension_scope=None, 104 | options=None), 105 | _descriptor.FieldDescriptor( 106 | name='data', full_name='transaction.StdTx.data', index=4, 107 | number=5, type=12, cpp_type=9, label=1, 108 | has_default_value=False, default_value=_b(""), 109 | message_type=None, enum_type=None, containing_type=None, 110 | is_extension=False, extension_scope=None, 111 | options=None), 112 | ], 113 | extensions=[ 114 | ], 115 | nested_types=[], 116 | enum_types=[ 117 | ], 118 | options=None, 119 | is_extendable=False, 120 | syntax='proto3', 121 | extension_ranges=[], 122 | oneofs=[ 123 | ], 124 | serialized_start=34, 125 | serialized_end=119, 126 | ) 127 | 128 | 129 | _STDSIGNATURE = _descriptor.Descriptor( 130 | name='StdSignature', 131 | full_name='transaction.StdSignature', 132 | filename=None, 133 | file=DESCRIPTOR, 134 | containing_type=None, 135 | fields=[ 136 | _descriptor.FieldDescriptor( 137 | name='pub_key', full_name='transaction.StdSignature.pub_key', index=0, 138 | number=1, type=12, cpp_type=9, label=1, 139 | has_default_value=False, default_value=_b(""), 140 | message_type=None, enum_type=None, containing_type=None, 141 | is_extension=False, extension_scope=None, 142 | options=None), 143 | _descriptor.FieldDescriptor( 144 | name='signature', full_name='transaction.StdSignature.signature', index=1, 145 | number=2, type=12, cpp_type=9, label=1, 146 | has_default_value=False, default_value=_b(""), 147 | message_type=None, enum_type=None, containing_type=None, 148 | is_extension=False, extension_scope=None, 149 | options=None), 150 | _descriptor.FieldDescriptor( 151 | name='account_number', full_name='transaction.StdSignature.account_number', index=2, 152 | number=3, type=3, cpp_type=2, label=1, 153 | has_default_value=False, default_value=0, 154 | message_type=None, enum_type=None, containing_type=None, 155 | is_extension=False, extension_scope=None, 156 | options=None), 157 | _descriptor.FieldDescriptor( 158 | name='sequence', full_name='transaction.StdSignature.sequence', index=3, 159 | number=4, type=3, cpp_type=2, label=1, 160 | has_default_value=False, default_value=0, 161 | message_type=None, enum_type=None, containing_type=None, 162 | is_extension=False, extension_scope=None, 163 | options=None), 164 | ], 165 | extensions=[ 166 | ], 167 | nested_types=[], 168 | enum_types=[ 169 | ], 170 | options=None, 171 | is_extendable=False, 172 | syntax='proto3', 173 | extension_ranges=[], 174 | oneofs=[ 175 | ], 176 | serialized_start=121, 177 | serialized_end=213, 178 | ) 179 | 180 | 181 | _MSG = _descriptor.Descriptor( 182 | name='Msg', 183 | full_name='transaction.Msg', 184 | filename=None, 185 | file=DESCRIPTOR, 186 | containing_type=None, 187 | fields=[ 188 | ], 189 | extensions=[ 190 | ], 191 | nested_types=[], 192 | enum_types=[ 193 | ], 194 | options=None, 195 | is_extendable=False, 196 | syntax='proto3', 197 | extension_ranges=[], 198 | oneofs=[ 199 | ], 200 | serialized_start=215, 201 | serialized_end=220, 202 | ) 203 | 204 | 205 | _NEWORDER = _descriptor.Descriptor( 206 | name='NewOrder', 207 | full_name='transaction.NewOrder', 208 | filename=None, 209 | file=DESCRIPTOR, 210 | containing_type=None, 211 | fields=[ 212 | _descriptor.FieldDescriptor( 213 | name='sender', full_name='transaction.NewOrder.sender', index=0, 214 | number=1, type=12, cpp_type=9, label=1, 215 | has_default_value=False, default_value=_b(""), 216 | message_type=None, enum_type=None, containing_type=None, 217 | is_extension=False, extension_scope=None, 218 | options=None), 219 | _descriptor.FieldDescriptor( 220 | name='id', full_name='transaction.NewOrder.id', index=1, 221 | number=2, type=9, cpp_type=9, label=1, 222 | has_default_value=False, default_value=_b("").decode('utf-8'), 223 | message_type=None, enum_type=None, containing_type=None, 224 | is_extension=False, extension_scope=None, 225 | options=None), 226 | _descriptor.FieldDescriptor( 227 | name='symbol', full_name='transaction.NewOrder.symbol', index=2, 228 | number=3, type=9, cpp_type=9, label=1, 229 | has_default_value=False, default_value=_b("").decode('utf-8'), 230 | message_type=None, enum_type=None, containing_type=None, 231 | is_extension=False, extension_scope=None, 232 | options=None), 233 | _descriptor.FieldDescriptor( 234 | name='ordertype', full_name='transaction.NewOrder.ordertype', index=3, 235 | number=4, type=3, cpp_type=2, label=1, 236 | has_default_value=False, default_value=0, 237 | message_type=None, enum_type=None, containing_type=None, 238 | is_extension=False, extension_scope=None, 239 | options=None), 240 | _descriptor.FieldDescriptor( 241 | name='side', full_name='transaction.NewOrder.side', index=4, 242 | number=5, type=3, cpp_type=2, label=1, 243 | has_default_value=False, default_value=0, 244 | message_type=None, enum_type=None, containing_type=None, 245 | is_extension=False, extension_scope=None, 246 | options=None), 247 | _descriptor.FieldDescriptor( 248 | name='price', full_name='transaction.NewOrder.price', index=5, 249 | number=6, type=3, cpp_type=2, label=1, 250 | has_default_value=False, default_value=0, 251 | message_type=None, enum_type=None, containing_type=None, 252 | is_extension=False, extension_scope=None, 253 | options=None), 254 | _descriptor.FieldDescriptor( 255 | name='quantity', full_name='transaction.NewOrder.quantity', index=6, 256 | number=7, type=3, cpp_type=2, label=1, 257 | has_default_value=False, default_value=0, 258 | message_type=None, enum_type=None, containing_type=None, 259 | is_extension=False, extension_scope=None, 260 | options=None), 261 | _descriptor.FieldDescriptor( 262 | name='timeinforce', full_name='transaction.NewOrder.timeinforce', index=7, 263 | number=8, type=3, cpp_type=2, label=1, 264 | has_default_value=False, default_value=0, 265 | message_type=None, enum_type=None, containing_type=None, 266 | is_extension=False, extension_scope=None, 267 | options=None), 268 | ], 269 | extensions=[ 270 | ], 271 | nested_types=[], 272 | enum_types=[ 273 | ], 274 | options=None, 275 | is_extendable=False, 276 | syntax='proto3', 277 | extension_ranges=[], 278 | oneofs=[ 279 | ], 280 | serialized_start=223, 281 | serialized_end=364, 282 | ) 283 | 284 | 285 | _CANCELORDER = _descriptor.Descriptor( 286 | name='CancelOrder', 287 | full_name='transaction.CancelOrder', 288 | filename=None, 289 | file=DESCRIPTOR, 290 | containing_type=None, 291 | fields=[ 292 | _descriptor.FieldDescriptor( 293 | name='sender', full_name='transaction.CancelOrder.sender', index=0, 294 | number=1, type=12, cpp_type=9, label=1, 295 | has_default_value=False, default_value=_b(""), 296 | message_type=None, enum_type=None, containing_type=None, 297 | is_extension=False, extension_scope=None, 298 | options=None), 299 | _descriptor.FieldDescriptor( 300 | name='symbol', full_name='transaction.CancelOrder.symbol', index=1, 301 | number=2, type=9, cpp_type=9, label=1, 302 | has_default_value=False, default_value=_b("").decode('utf-8'), 303 | message_type=None, enum_type=None, containing_type=None, 304 | is_extension=False, extension_scope=None, 305 | options=None), 306 | _descriptor.FieldDescriptor( 307 | name='refid', full_name='transaction.CancelOrder.refid', index=2, 308 | number=3, type=9, cpp_type=9, label=1, 309 | has_default_value=False, default_value=_b("").decode('utf-8'), 310 | message_type=None, enum_type=None, containing_type=None, 311 | is_extension=False, extension_scope=None, 312 | options=None), 313 | ], 314 | extensions=[ 315 | ], 316 | nested_types=[], 317 | enum_types=[ 318 | ], 319 | options=None, 320 | is_extendable=False, 321 | syntax='proto3', 322 | extension_ranges=[], 323 | oneofs=[ 324 | ], 325 | serialized_start=366, 326 | serialized_end=426, 327 | ) 328 | 329 | 330 | _SEND = _descriptor.Descriptor( 331 | name='Send', 332 | full_name='transaction.Send', 333 | filename=None, 334 | file=DESCRIPTOR, 335 | containing_type=None, 336 | fields=[ 337 | _descriptor.FieldDescriptor( 338 | name='inputs', full_name='transaction.Send.inputs', index=0, 339 | number=1, type=11, cpp_type=10, label=3, 340 | has_default_value=False, default_value=[], 341 | message_type=None, enum_type=None, containing_type=None, 342 | is_extension=False, extension_scope=None, 343 | options=None), 344 | _descriptor.FieldDescriptor( 345 | name='outputs', full_name='transaction.Send.outputs', index=1, 346 | number=2, type=11, cpp_type=10, label=3, 347 | has_default_value=False, default_value=[], 348 | message_type=None, enum_type=None, containing_type=None, 349 | is_extension=False, extension_scope=None, 350 | options=None), 351 | ], 352 | extensions=[ 353 | ], 354 | nested_types=[], 355 | enum_types=[ 356 | ], 357 | options=None, 358 | is_extendable=False, 359 | syntax='proto3', 360 | extension_ranges=[], 361 | oneofs=[ 362 | ], 363 | serialized_start=428, 364 | serialized_end=508, 365 | ) 366 | 367 | 368 | _TOKEN = _descriptor.Descriptor( 369 | name='Token', 370 | full_name='transaction.Token', 371 | filename=None, 372 | file=DESCRIPTOR, 373 | containing_type=None, 374 | fields=[ 375 | _descriptor.FieldDescriptor( 376 | name='denom', full_name='transaction.Token.denom', index=0, 377 | number=1, type=9, cpp_type=9, label=1, 378 | has_default_value=False, default_value=_b("").decode('utf-8'), 379 | message_type=None, enum_type=None, containing_type=None, 380 | is_extension=False, extension_scope=None, 381 | options=None), 382 | _descriptor.FieldDescriptor( 383 | name='amount', full_name='transaction.Token.amount', index=1, 384 | number=2, type=3, cpp_type=2, label=1, 385 | has_default_value=False, default_value=0, 386 | message_type=None, enum_type=None, containing_type=None, 387 | is_extension=False, extension_scope=None, 388 | options=None), 389 | ], 390 | extensions=[ 391 | ], 392 | nested_types=[], 393 | enum_types=[ 394 | ], 395 | options=None, 396 | is_extendable=False, 397 | syntax='proto3', 398 | extension_ranges=[], 399 | oneofs=[ 400 | ], 401 | serialized_start=510, 402 | serialized_end=548, 403 | ) 404 | 405 | 406 | _INPUT = _descriptor.Descriptor( 407 | name='Input', 408 | full_name='transaction.Input', 409 | filename=None, 410 | file=DESCRIPTOR, 411 | containing_type=None, 412 | fields=[ 413 | _descriptor.FieldDescriptor( 414 | name='address', full_name='transaction.Input.address', index=0, 415 | number=1, type=12, cpp_type=9, label=1, 416 | has_default_value=False, default_value=_b(""), 417 | message_type=None, enum_type=None, containing_type=None, 418 | is_extension=False, extension_scope=None, 419 | options=None), 420 | _descriptor.FieldDescriptor( 421 | name='coins', full_name='transaction.Input.coins', index=1, 422 | number=2, type=11, cpp_type=10, label=3, 423 | has_default_value=False, default_value=[], 424 | message_type=None, enum_type=None, containing_type=None, 425 | is_extension=False, extension_scope=None, 426 | options=None), 427 | ], 428 | extensions=[ 429 | ], 430 | nested_types=[], 431 | enum_types=[ 432 | ], 433 | options=None, 434 | is_extendable=False, 435 | syntax='proto3', 436 | extension_ranges=[], 437 | oneofs=[ 438 | ], 439 | serialized_start=550, 440 | serialized_end=609, 441 | ) 442 | 443 | 444 | _OUTPUT = _descriptor.Descriptor( 445 | name='Output', 446 | full_name='transaction.Output', 447 | filename=None, 448 | file=DESCRIPTOR, 449 | containing_type=None, 450 | fields=[ 451 | _descriptor.FieldDescriptor( 452 | name='address', full_name='transaction.Output.address', index=0, 453 | number=1, type=12, cpp_type=9, label=1, 454 | has_default_value=False, default_value=_b(""), 455 | message_type=None, enum_type=None, containing_type=None, 456 | is_extension=False, extension_scope=None, 457 | options=None), 458 | _descriptor.FieldDescriptor( 459 | name='coins', full_name='transaction.Output.coins', index=1, 460 | number=2, type=11, cpp_type=10, label=3, 461 | has_default_value=False, default_value=[], 462 | message_type=None, enum_type=None, containing_type=None, 463 | is_extension=False, extension_scope=None, 464 | options=None), 465 | ], 466 | extensions=[ 467 | ], 468 | nested_types=[], 469 | enum_types=[ 470 | ], 471 | options=None, 472 | is_extendable=False, 473 | syntax='proto3', 474 | extension_ranges=[], 475 | oneofs=[ 476 | ], 477 | serialized_start=611, 478 | serialized_end=671, 479 | ) 480 | 481 | 482 | _FREEZE = _descriptor.Descriptor( 483 | name='Freeze', 484 | full_name='transaction.Freeze', 485 | filename=None, 486 | file=DESCRIPTOR, 487 | containing_type=None, 488 | fields=[ 489 | _descriptor.FieldDescriptor( 490 | name='from', full_name='transaction.Freeze.from', index=0, 491 | number=1, type=12, cpp_type=9, label=1, 492 | has_default_value=False, default_value=_b(""), 493 | message_type=None, enum_type=None, containing_type=None, 494 | is_extension=False, extension_scope=None, 495 | options=None), 496 | _descriptor.FieldDescriptor( 497 | name='symbol', full_name='transaction.Freeze.symbol', index=1, 498 | number=2, type=9, cpp_type=9, label=1, 499 | has_default_value=False, default_value=_b("").decode('utf-8'), 500 | message_type=None, enum_type=None, containing_type=None, 501 | is_extension=False, extension_scope=None, 502 | options=None), 503 | _descriptor.FieldDescriptor( 504 | name='amount', full_name='transaction.Freeze.amount', index=2, 505 | number=3, type=3, cpp_type=2, label=1, 506 | has_default_value=False, default_value=0, 507 | message_type=None, enum_type=None, containing_type=None, 508 | is_extension=False, extension_scope=None, 509 | options=None), 510 | ], 511 | extensions=[ 512 | ], 513 | nested_types=[], 514 | enum_types=[ 515 | ], 516 | options=None, 517 | is_extendable=False, 518 | syntax='proto3', 519 | extension_ranges=[], 520 | oneofs=[ 521 | ], 522 | serialized_start=673, 523 | serialized_end=727, 524 | ) 525 | 526 | 527 | _UNFREEZE = _descriptor.Descriptor( 528 | name='Unfreeze', 529 | full_name='transaction.Unfreeze', 530 | filename=None, 531 | file=DESCRIPTOR, 532 | containing_type=None, 533 | fields=[ 534 | _descriptor.FieldDescriptor( 535 | name='from', full_name='transaction.Unfreeze.from', index=0, 536 | number=1, type=12, cpp_type=9, label=1, 537 | has_default_value=False, default_value=_b(""), 538 | message_type=None, enum_type=None, containing_type=None, 539 | is_extension=False, extension_scope=None, 540 | options=None), 541 | _descriptor.FieldDescriptor( 542 | name='symbol', full_name='transaction.Unfreeze.symbol', index=1, 543 | number=2, type=9, cpp_type=9, label=1, 544 | has_default_value=False, default_value=_b("").decode('utf-8'), 545 | message_type=None, enum_type=None, containing_type=None, 546 | is_extension=False, extension_scope=None, 547 | options=None), 548 | _descriptor.FieldDescriptor( 549 | name='amount', full_name='transaction.Unfreeze.amount', index=2, 550 | number=3, type=3, cpp_type=2, label=1, 551 | has_default_value=False, default_value=0, 552 | message_type=None, enum_type=None, containing_type=None, 553 | is_extension=False, extension_scope=None, 554 | options=None), 555 | ], 556 | extensions=[ 557 | ], 558 | nested_types=[], 559 | enum_types=[ 560 | ], 561 | options=None, 562 | is_extendable=False, 563 | syntax='proto3', 564 | extension_ranges=[], 565 | oneofs=[ 566 | ], 567 | serialized_start=729, 568 | serialized_end=785, 569 | ) 570 | 571 | 572 | _VOTE = _descriptor.Descriptor( 573 | name='Vote', 574 | full_name='transaction.Vote', 575 | filename=None, 576 | file=DESCRIPTOR, 577 | containing_type=None, 578 | fields=[ 579 | _descriptor.FieldDescriptor( 580 | name='proposal_id', full_name='transaction.Vote.proposal_id', index=0, 581 | number=1, type=3, cpp_type=2, label=1, 582 | has_default_value=False, default_value=0, 583 | message_type=None, enum_type=None, containing_type=None, 584 | is_extension=False, extension_scope=None, 585 | options=None), 586 | _descriptor.FieldDescriptor( 587 | name='voter', full_name='transaction.Vote.voter', index=1, 588 | number=2, type=12, cpp_type=9, label=1, 589 | has_default_value=False, default_value=_b(""), 590 | message_type=None, enum_type=None, containing_type=None, 591 | is_extension=False, extension_scope=None, 592 | options=None), 593 | _descriptor.FieldDescriptor( 594 | name='option', full_name='transaction.Vote.option', index=2, 595 | number=3, type=14, cpp_type=8, label=1, 596 | has_default_value=False, default_value=0, 597 | message_type=None, enum_type=None, containing_type=None, 598 | is_extension=False, extension_scope=None, 599 | options=None), 600 | ], 601 | extensions=[ 602 | ], 603 | nested_types=[], 604 | enum_types=[ 605 | ], 606 | options=None, 607 | is_extendable=False, 608 | syntax='proto3', 609 | extension_ranges=[], 610 | oneofs=[ 611 | ], 612 | serialized_start=787, 613 | serialized_end=870, 614 | ) 615 | 616 | 617 | _ISSUE = _descriptor.Descriptor( 618 | name='Issue', 619 | full_name='transaction.Issue', 620 | filename=None, 621 | file=DESCRIPTOR, 622 | containing_type=None, 623 | fields=[ 624 | _descriptor.FieldDescriptor( 625 | name='from', full_name='transaction.Issue.from', index=0, 626 | number=1, type=12, cpp_type=9, label=1, 627 | has_default_value=False, default_value=_b(""), 628 | message_type=None, enum_type=None, containing_type=None, 629 | is_extension=False, extension_scope=None, 630 | options=None), 631 | _descriptor.FieldDescriptor( 632 | name='name', full_name='transaction.Issue.name', index=1, 633 | number=2, type=9, cpp_type=9, label=1, 634 | has_default_value=False, default_value=_b("").decode('utf-8'), 635 | message_type=None, enum_type=None, containing_type=None, 636 | is_extension=False, extension_scope=None, 637 | options=None), 638 | _descriptor.FieldDescriptor( 639 | name='symbol', full_name='transaction.Issue.symbol', index=2, 640 | number=3, type=9, cpp_type=9, label=1, 641 | has_default_value=False, default_value=_b("").decode('utf-8'), 642 | message_type=None, enum_type=None, containing_type=None, 643 | is_extension=False, extension_scope=None, 644 | options=None), 645 | _descriptor.FieldDescriptor( 646 | name='total_supply', full_name='transaction.Issue.total_supply', index=3, 647 | number=4, type=3, cpp_type=2, label=1, 648 | has_default_value=False, default_value=0, 649 | message_type=None, enum_type=None, containing_type=None, 650 | is_extension=False, extension_scope=None, 651 | options=None), 652 | _descriptor.FieldDescriptor( 653 | name='mintable', full_name='transaction.Issue.mintable', index=4, 654 | number=5, type=8, cpp_type=7, label=1, 655 | has_default_value=False, default_value=False, 656 | message_type=None, enum_type=None, containing_type=None, 657 | is_extension=False, extension_scope=None, 658 | options=None), 659 | ], 660 | extensions=[ 661 | ], 662 | nested_types=[], 663 | enum_types=[ 664 | ], 665 | options=None, 666 | is_extendable=False, 667 | syntax='proto3', 668 | extension_ranges=[], 669 | oneofs=[ 670 | ], 671 | serialized_start=872, 672 | serialized_end=963, 673 | ) 674 | 675 | 676 | _MINT = _descriptor.Descriptor( 677 | name='Mint', 678 | full_name='transaction.Mint', 679 | filename=None, 680 | file=DESCRIPTOR, 681 | containing_type=None, 682 | fields=[ 683 | _descriptor.FieldDescriptor( 684 | name='from', full_name='transaction.Mint.from', index=0, 685 | number=1, type=12, cpp_type=9, label=1, 686 | has_default_value=False, default_value=_b(""), 687 | message_type=None, enum_type=None, containing_type=None, 688 | is_extension=False, extension_scope=None, 689 | options=None), 690 | _descriptor.FieldDescriptor( 691 | name='symbol', full_name='transaction.Mint.symbol', index=1, 692 | number=2, type=9, cpp_type=9, label=1, 693 | has_default_value=False, default_value=_b("").decode('utf-8'), 694 | message_type=None, enum_type=None, containing_type=None, 695 | is_extension=False, extension_scope=None, 696 | options=None), 697 | _descriptor.FieldDescriptor( 698 | name='amount', full_name='transaction.Mint.amount', index=2, 699 | number=3, type=3, cpp_type=2, label=1, 700 | has_default_value=False, default_value=0, 701 | message_type=None, enum_type=None, containing_type=None, 702 | is_extension=False, extension_scope=None, 703 | options=None), 704 | ], 705 | extensions=[ 706 | ], 707 | nested_types=[], 708 | enum_types=[ 709 | ], 710 | options=None, 711 | is_extendable=False, 712 | syntax='proto3', 713 | extension_ranges=[], 714 | oneofs=[ 715 | ], 716 | serialized_start=965, 717 | serialized_end=1017, 718 | ) 719 | 720 | 721 | _BURN = _descriptor.Descriptor( 722 | name='Burn', 723 | full_name='transaction.Burn', 724 | filename=None, 725 | file=DESCRIPTOR, 726 | containing_type=None, 727 | fields=[ 728 | _descriptor.FieldDescriptor( 729 | name='from', full_name='transaction.Burn.from', index=0, 730 | number=1, type=12, cpp_type=9, label=1, 731 | has_default_value=False, default_value=_b(""), 732 | message_type=None, enum_type=None, containing_type=None, 733 | is_extension=False, extension_scope=None, 734 | options=None), 735 | _descriptor.FieldDescriptor( 736 | name='symbol', full_name='transaction.Burn.symbol', index=1, 737 | number=2, type=9, cpp_type=9, label=1, 738 | has_default_value=False, default_value=_b("").decode('utf-8'), 739 | message_type=None, enum_type=None, containing_type=None, 740 | is_extension=False, extension_scope=None, 741 | options=None), 742 | _descriptor.FieldDescriptor( 743 | name='amount', full_name='transaction.Burn.amount', index=2, 744 | number=3, type=3, cpp_type=2, label=1, 745 | has_default_value=False, default_value=0, 746 | message_type=None, enum_type=None, containing_type=None, 747 | is_extension=False, extension_scope=None, 748 | options=None), 749 | ], 750 | extensions=[ 751 | ], 752 | nested_types=[], 753 | enum_types=[ 754 | ], 755 | options=None, 756 | is_extendable=False, 757 | syntax='proto3', 758 | extension_ranges=[], 759 | oneofs=[ 760 | ], 761 | serialized_start=1019, 762 | serialized_end=1071, 763 | ) 764 | 765 | _SEND.fields_by_name['inputs'].message_type = _INPUT 766 | _SEND.fields_by_name['outputs'].message_type = _OUTPUT 767 | _INPUT.fields_by_name['coins'].message_type = _TOKEN 768 | _OUTPUT.fields_by_name['coins'].message_type = _TOKEN 769 | _VOTE.fields_by_name['option'].enum_type = _VOTEOPTION 770 | DESCRIPTOR.message_types_by_name['StdTx'] = _STDTX 771 | DESCRIPTOR.message_types_by_name['StdSignature'] = _STDSIGNATURE 772 | DESCRIPTOR.message_types_by_name['Msg'] = _MSG 773 | DESCRIPTOR.message_types_by_name['NewOrder'] = _NEWORDER 774 | DESCRIPTOR.message_types_by_name['CancelOrder'] = _CANCELORDER 775 | DESCRIPTOR.message_types_by_name['Send'] = _SEND 776 | DESCRIPTOR.message_types_by_name['Token'] = _TOKEN 777 | DESCRIPTOR.message_types_by_name['Input'] = _INPUT 778 | DESCRIPTOR.message_types_by_name['Output'] = _OUTPUT 779 | DESCRIPTOR.message_types_by_name['Freeze'] = _FREEZE 780 | DESCRIPTOR.message_types_by_name['Unfreeze'] = _UNFREEZE 781 | DESCRIPTOR.message_types_by_name['Vote'] = _VOTE 782 | DESCRIPTOR.message_types_by_name['Issue'] = _ISSUE 783 | DESCRIPTOR.message_types_by_name['Mint'] = _MINT 784 | DESCRIPTOR.message_types_by_name['Burn'] = _BURN 785 | DESCRIPTOR.enum_types_by_name['VoteOption'] = _VOTEOPTION 786 | 787 | StdTx = _reflection.GeneratedProtocolMessageType('StdTx', (_message.Message,), dict( 788 | DESCRIPTOR = _STDTX, 789 | __module__ = 'transaction_pb2' 790 | # @@protoc_insertion_point(class_scope:transaction.StdTx) 791 | )) 792 | _sym_db.RegisterMessage(StdTx) 793 | 794 | StdSignature = _reflection.GeneratedProtocolMessageType('StdSignature', (_message.Message,), dict( 795 | DESCRIPTOR = _STDSIGNATURE, 796 | __module__ = 'transaction_pb2' 797 | # @@protoc_insertion_point(class_scope:transaction.StdSignature) 798 | )) 799 | _sym_db.RegisterMessage(StdSignature) 800 | 801 | Msg = _reflection.GeneratedProtocolMessageType('Msg', (_message.Message,), dict( 802 | DESCRIPTOR = _MSG, 803 | __module__ = 'transaction_pb2' 804 | # @@protoc_insertion_point(class_scope:transaction.Msg) 805 | )) 806 | _sym_db.RegisterMessage(Msg) 807 | 808 | NewOrder = _reflection.GeneratedProtocolMessageType('NewOrder', (_message.Message,), dict( 809 | DESCRIPTOR = _NEWORDER, 810 | __module__ = 'transaction_pb2' 811 | # @@protoc_insertion_point(class_scope:transaction.NewOrder) 812 | )) 813 | _sym_db.RegisterMessage(NewOrder) 814 | 815 | CancelOrder = _reflection.GeneratedProtocolMessageType('CancelOrder', (_message.Message,), dict( 816 | DESCRIPTOR = _CANCELORDER, 817 | __module__ = 'transaction_pb2' 818 | # @@protoc_insertion_point(class_scope:transaction.CancelOrder) 819 | )) 820 | _sym_db.RegisterMessage(CancelOrder) 821 | 822 | Send = _reflection.GeneratedProtocolMessageType('Send', (_message.Message,), dict( 823 | DESCRIPTOR = _SEND, 824 | __module__ = 'transaction_pb2' 825 | # @@protoc_insertion_point(class_scope:transaction.Send) 826 | )) 827 | _sym_db.RegisterMessage(Send) 828 | 829 | Token = _reflection.GeneratedProtocolMessageType('Token', (_message.Message,), dict( 830 | DESCRIPTOR = _TOKEN, 831 | __module__ = 'transaction_pb2' 832 | # @@protoc_insertion_point(class_scope:transaction.Token) 833 | )) 834 | _sym_db.RegisterMessage(Token) 835 | 836 | Input = _reflection.GeneratedProtocolMessageType('Input', (_message.Message,), dict( 837 | DESCRIPTOR = _INPUT, 838 | __module__ = 'transaction_pb2' 839 | # @@protoc_insertion_point(class_scope:transaction.Input) 840 | )) 841 | _sym_db.RegisterMessage(Input) 842 | 843 | Output = _reflection.GeneratedProtocolMessageType('Output', (_message.Message,), dict( 844 | DESCRIPTOR = _OUTPUT, 845 | __module__ = 'transaction_pb2' 846 | # @@protoc_insertion_point(class_scope:transaction.Output) 847 | )) 848 | _sym_db.RegisterMessage(Output) 849 | 850 | Freeze = _reflection.GeneratedProtocolMessageType('Freeze', (_message.Message,), dict( 851 | DESCRIPTOR = _FREEZE, 852 | __module__ = 'transaction_pb2' 853 | # @@protoc_insertion_point(class_scope:transaction.Freeze) 854 | )) 855 | _sym_db.RegisterMessage(Freeze) 856 | 857 | Unfreeze = _reflection.GeneratedProtocolMessageType('Unfreeze', (_message.Message,), dict( 858 | DESCRIPTOR = _UNFREEZE, 859 | __module__ = 'transaction_pb2' 860 | # @@protoc_insertion_point(class_scope:transaction.Unfreeze) 861 | )) 862 | _sym_db.RegisterMessage(Unfreeze) 863 | 864 | Vote = _reflection.GeneratedProtocolMessageType('Vote', (_message.Message,), dict( 865 | DESCRIPTOR = _VOTE, 866 | __module__ = 'transaction_pb2' 867 | # @@protoc_insertion_point(class_scope:transaction.Vote) 868 | )) 869 | _sym_db.RegisterMessage(Vote) 870 | 871 | Issue = _reflection.GeneratedProtocolMessageType('Issue', (_message.Message,), dict( 872 | DESCRIPTOR = _ISSUE, 873 | __module__ = 'transaction_pb2' 874 | # @@protoc_insertion_point(class_scope:transaction.Issue) 875 | )) 876 | _sym_db.RegisterMessage(Issue) 877 | 878 | Mint = _reflection.GeneratedProtocolMessageType('Mint', (_message.Message,), dict( 879 | DESCRIPTOR = _MINT, 880 | __module__ = 'transaction_pb2' 881 | # @@protoc_insertion_point(class_scope:transaction.Mint) 882 | )) 883 | _sym_db.RegisterMessage(Mint) 884 | 885 | Burn = _reflection.GeneratedProtocolMessageType('Burn', (_message.Message,), dict( 886 | DESCRIPTOR = _BURN, 887 | __module__ = 'transaction_pb2' 888 | # @@protoc_insertion_point(class_scope:transaction.Burn) 889 | )) 890 | _sym_db.RegisterMessage(Burn) 891 | 892 | 893 | # @@protoc_insertion_point(module_scope) 894 | -------------------------------------------------------------------------------- /binancechain/wallet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | """ 4 | Intergrate with different methods of signing 5 | """ 6 | 7 | from eth_keyfile import decode_keyfile_json, create_keyfile_json 8 | from bitcoinlib import keys, mnemonic, encoding 9 | from .crypto import from_path, get_address 10 | from secp256k1 import PrivateKey, PublicKey 11 | 12 | 13 | HDPATH = "44'/714'/0'/0/0" 14 | TESTNET_PREFIX = "tbnb" 15 | MAINET_PREFIX = "bnb" 16 | 17 | 18 | class Wallet: 19 | @staticmethod 20 | def create_wallet(password: str = "", testnet: bool = False): 21 | """ 22 | Create brand new wallet 23 | """ 24 | root_key = keys.HDKey(passphrase=password) 25 | key = from_path(root_key=root_key, path=HDPATH) 26 | return Wallet(key=key, testnet=testnet) 27 | 28 | @staticmethod 29 | def create_keystore(password: str = "") -> dict: 30 | """ 31 | Create Keystore object 32 | """ 33 | m = mnemonic.Mnemonic() 34 | mnem = m.generate(256) 35 | root_key = keys.HDKey.from_seed(m.to_seed(mnem, password=password)) 36 | key = from_path(root_key=root_key, path=HDPATH) 37 | return create_keyfile_json( 38 | private_key=key.private_byte, password=bytes(password, "utf-8") 39 | ) 40 | 41 | @staticmethod 42 | def create_wallet_mnemonic( 43 | language: str = "english", password: str = "", testnet: bool = False 44 | ): 45 | """ 46 | Create wallet with mnemonic in language 47 | """ 48 | m = mnemonic.Mnemonic(language) 49 | mnem = m.generate(256) 50 | root_key = keys.HDKey.from_seed(m.to_seed(mnem, password=password)) 51 | key = from_path(root_key=root_key, path=HDPATH) 52 | return Wallet(key=key, testnet=testnet, mnemonic=mnem) 53 | 54 | @staticmethod 55 | def wallet_from_keystore(keystore: dict, password: str = "", testnet: bool = False): 56 | "Recover Binance wallet from keystore" 57 | private_key = decode_keyfile_json( 58 | keystore, password=encoding.to_bytes(password) 59 | ) 60 | key = keys.HDKey(private_key) 61 | return Wallet(key=key, testnet=testnet) 62 | 63 | @staticmethod 64 | def wallet_from_privatekey( 65 | privatekey: str, password: str = "", testnet: bool = False 66 | ): 67 | """Recover Binance Wallet from privatekey""" 68 | key = keys.HDKey(import_key=privatekey, passphrase=password) 69 | return Wallet(key=key, testnet=testnet) 70 | 71 | @staticmethod 72 | def wallet_from_mnemonic(words: str, password: str = "", testnet: bool = False): 73 | "Recover wallet from mnemonic" 74 | m = mnemonic.Mnemonic(language="english") 75 | root_key = keys.HDKey.from_seed(m.to_seed(words=words, password=password)) 76 | key = from_path(root_key=root_key, path=HDPATH) 77 | return Wallet(key=key, testnet=testnet, mnemonic=words) 78 | 79 | # @staticmethod 80 | # def wallet_from_seed(seed, testnet=False): 81 | # root_key = keys.HDKey.from_seed(seed) 82 | # key = from_path(root_key=root_key, path=HDPATH) 83 | # return Wallet(key=key, testnet=testnet, mnemonic=words) 84 | 85 | def __init__(self, key: str, testnet: bool = False, mnemonic: str = None): 86 | self.testnet = testnet 87 | self.prefix = TESTNET_PREFIX if testnet else MAINET_PREFIX 88 | self.key = key 89 | self.address = get_address(prefix=self.prefix, key=key) 90 | if mnemonic: 91 | self.mnemonic = mnemonic 92 | 93 | def get_address(self) -> str: 94 | """Return wallet's address""" 95 | return self.address 96 | 97 | def get_privatekey(self): 98 | """Return wallet's private key""" 99 | return self.key.private_hex 100 | 101 | def get_publickey(self): 102 | """Return wallet's public key""" 103 | return self.key.public_hex 104 | 105 | def get_mnemonic(self): 106 | if not self.mnemonic: 107 | raise Exception("No mnemonic available in this wallet") 108 | return self.mnemonic 109 | 110 | def sign(self, msg): 111 | """ 112 | Sign a message with private key, Return signature 113 | """ 114 | priv = PrivateKey(self.key.private_byte, raw=True) 115 | sig = priv.ecdsa_sign(msg) 116 | h = priv.ecdsa_serialize_compact(sig) 117 | return self.key.public_hex, encoding.to_hexstring(h) 118 | 119 | def verify_signature(self, msg, signature): 120 | """Verify message and signature if its from this wallet""" 121 | pub = PublicKey(self.key.public_byte, raw=True) 122 | sig = pub.ecdsa_deserialize_compact(encoding.to_bytes(signature)) 123 | valid = pub.ecdsa_verify(msg, sig) 124 | return valid 125 | -------------------------------------------------------------------------------- /binancechain/websocket.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | """ 4 | Binance DEX WebSockets 5 | 6 | https://docs.binance.org/api-reference/dex-api/ws-streams.html#websocket-streams 7 | """ 8 | import asyncio 9 | import logging 10 | from typing import Any, Callable, Dict, List, Optional, Tuple 11 | 12 | import aiohttp 13 | import orjson 14 | from pyee import AsyncIOEventEmitter 15 | 16 | log = logging.getLogger(__name__) 17 | 18 | MAINNET_URL = "wss://dex.binance.org/api/ws" 19 | TESTNET_URL = "wss://testnet-dex.binance.org/api/ws" 20 | 21 | 22 | class WebSocket: 23 | """The Binance DEX WebSocket Manager.""" 24 | 25 | def __init__( 26 | self, 27 | address: str = None, 28 | testnet: bool = False, 29 | keepalive: bool = True, 30 | loop: asyncio.AbstractEventLoop = None, 31 | url: str = None, 32 | ) -> None: 33 | if not url: 34 | self.url = TESTNET_URL if testnet else MAINNET_URL 35 | else: 36 | self.url = url 37 | self.address = address 38 | self._session = aiohttp.ClientSession() 39 | self._ws: Optional[aiohttp.ClientWebSocketResponse] = None 40 | self._loop = loop or asyncio.get_event_loop() 41 | self._events = AsyncIOEventEmitter(loop=self._loop) 42 | self._sub_queue: List[Tuple[str, dict]] = [] 43 | self._keepalive = keepalive 44 | self._keepalive_task: Optional[asyncio.Future] = None 45 | self._open = False 46 | self._testnet = testnet 47 | 48 | def on(self, event: str, func: Optional[Callable] = None, **kwargs): 49 | """Register an event, and optional handler. 50 | 51 | This can be used as a decorator or as a normal method. 52 | See `examples/websockets_decorator.py` for usage. 53 | """ 54 | # Queue up most events from startup-time decorators until after we are open 55 | if not self._open and event not in ("open", "error", "new_listener"): 56 | self._sub_queue.append((event, kwargs)) 57 | if func: 58 | self._events.on(event, func) 59 | return None 60 | else: 61 | return self._events.on(event) 62 | 63 | def start( 64 | self, 65 | on_open: Optional[Callable[[], None]] = None, 66 | on_error: Optional[Callable[[dict], None]] = None, 67 | loop: asyncio.AbstractEventLoop = None, 68 | ) -> None: 69 | """The main blocking call to start the WebSocket connection.""" 70 | loop = loop or asyncio.get_event_loop() 71 | return loop.run_until_complete(self.start_async(on_open, on_error)) 72 | 73 | async def start_async( 74 | self, 75 | on_open: Optional[Callable[[], None]] = None, 76 | on_error: Optional[Callable[[dict], None]] = None, 77 | ) -> None: 78 | """Processes all websocket messages.""" 79 | if self.address: # address-specific socket 80 | url = f"{self.url}/{self.address}" 81 | else: 82 | url = self.url 83 | 84 | async with self._session.ws_connect(url) as ws: 85 | self._ws = ws 86 | self._events.emit("open") 87 | while self._sub_queue: 88 | event, kwargs = self._sub_queue.pop() 89 | self.subscribe(event, **kwargs) 90 | if on_open: 91 | on_open() 92 | 93 | # Schedule keepalive calls every 30 minutes 94 | if self._keepalive: 95 | self._keepalive_task = asyncio.ensure_future(self._auto_keepalive()) 96 | 97 | async for msg in ws: 98 | if msg.type == aiohttp.WSMsgType.TEXT: 99 | try: 100 | data = msg.json(loads=orjson.loads) 101 | except Exception as e: 102 | log.error(f"Unable to decode msg: {msg}") 103 | continue 104 | if not data: 105 | log.error(f"Got empty msg: {msg}") 106 | continue 107 | if "error" in data: 108 | self._events.emit("error", data) 109 | if on_error: 110 | on_error(data) 111 | else: 112 | log.error(f"Unhandled error msg: {data}") 113 | continue 114 | if "stream" not in data: 115 | log.error(f"Got msg without stream: {data}") 116 | continue 117 | if "data" not in data: 118 | log.error(f"Got msg without data: {data}") 119 | continue 120 | 121 | self._events.emit(data["stream"], data) 122 | 123 | elif msg.type == aiohttp.WSMsgType.ERROR: 124 | log.error(msg) 125 | self._events.emit("error", msg) 126 | break 127 | 128 | async def send(self, data: dict) -> None: 129 | """Send data to the WebSocket""" 130 | if not self._ws: 131 | log.error("Error: Cannot send to uninitialized websocket") 132 | return 133 | await self._ws.send_bytes(orjson.dumps(data)) 134 | 135 | def subscribe( 136 | self, 137 | stream: str, 138 | symbols: Optional[List[str]] = None, 139 | address: Optional[str] = None, 140 | callback: Optional[Callable[[dict], None]] = None, 141 | ): 142 | """Subscribe to a WebSocket stream. 143 | 144 | See the documentation for more details on the available streams 145 | https://docs.binance.org/api-reference/dex-api/ws-streams.html 146 | """ 147 | payload: Dict[Any, Any] = {"method": "subscribe", "topic": stream} 148 | if symbols: 149 | payload["symbols"] = symbols 150 | if address: 151 | payload["address"] = address 152 | elif self.address: 153 | payload["address"] = self.address 154 | self._events.on(stream, callback) 155 | asyncio.ensure_future(self.send(payload)) 156 | 157 | def unsubscribe(self, stream, symbols=None) -> None: 158 | payload = {"method": "unsubscribe", "topic": stream} 159 | if symbols: 160 | payload["symbols"] = symbols 161 | asyncio.ensure_future(self.send(payload)) 162 | 163 | def subscribe_user_orders( 164 | self, callback: Callable[[dict], None], address: Optional[str] = None 165 | ) -> None: 166 | """Subscribe to individual order updates.""" 167 | self.subscribe("orders", address=address, callback=callback) 168 | 169 | def subscribe_user_accounts( 170 | self, callback: Callable[[dict], None], address: Optional[str] = None 171 | ) -> None: 172 | """Subscribe to account updates.""" 173 | self.subscribe("accounts", address=address, callback=callback) 174 | 175 | def subscribe_user_transfers( 176 | self, callback: Callable[[dict], None], address: Optional[str] = None 177 | ) -> None: 178 | """ 179 | Subscribe to transfer updates if `address` is involved (as sender or 180 | receiver) in a transfer. Multisend is also covered. 181 | """ 182 | self.subscribe("transfers", address=address, callback=callback) 183 | 184 | def subscribe_trades( 185 | self, symbols: List[str], callback: Callable[[dict], None] 186 | ) -> None: 187 | """Subscribe to individual trade updates.""" 188 | self.subscribe("trades", symbols=symbols, callback=callback) 189 | 190 | def subscribe_market_diff( 191 | self, symbols: List[str], callback: Callable[[dict], None] 192 | ) -> None: 193 | "Order book price and quantity depth updates used to locally keep an order book." "" 194 | self.subscribe("marketDiff", symbols=symbols, callback=callback) 195 | 196 | def subscribe_market_depth( 197 | self, symbols: List[str], callback: Callable[[dict], None] 198 | ) -> None: 199 | """Top 20 levels of bids and asks.""" 200 | self.subscribe("marketDepth", symbols=symbols, callback=callback) 201 | 202 | def subscribe_kline( 203 | self, interval: str, symbols: List[str], callback: Callable[[dict], None] 204 | ) -> None: 205 | """ 206 | The kline/candlestick stream pushes updates to the current 207 | klines/candlestick every second. 208 | 209 | Kline/Candlestick chart intervals: 210 | m -> minutes; h -> hours; d -> days; w -> weeks; M -> months 211 | 1m 3m 5m 15m 30m 1h 2h 4h 6h 8h 12h 1d 3d 1w 1M 212 | """ 213 | self.subscribe(f"kline_{interval}", symbols=symbols, callback=callback) 214 | 215 | def subscribe_ticker( 216 | self, symbols: List[str], callback: Callable[[dict], None] 217 | ) -> None: 218 | """24hr Ticker statistics for a single symbol are pushed every second.""" 219 | self.subscribe("ticker", symbols=symbols, callback=callback) 220 | 221 | def subscribe_all_tickers(self, callback: Callable[[dict], None]) -> None: 222 | """24hr Ticker statistics for a all symbols are pushed every second.""" 223 | self.subscribe("allTickers", symbols=["$all"], callback=callback) 224 | 225 | def subscribe_mini_ticker( 226 | self, symbols: List[str], callback: Callable[[dict], None] 227 | ) -> None: 228 | """A ticker for a single symbol is pushed every second.""" 229 | self.subscribe("miniTicker", symbols=symbols, callback=callback) 230 | 231 | def subscribe_all_mini_tickers(self, callback: Callable[[dict], None]) -> None: 232 | """Array of 24hr Mini Ticker statistics for a all symbols pushed every second.""" 233 | self.subscribe("allMiniTickers", symbols=["$all"], callback=callback) 234 | 235 | def subscribe_blockheight(self, callback: Callable[[dict], None]) -> None: 236 | """Streams the latest block height.""" 237 | self.subscribe("blockheight", symbols=["$all"], callback=callback) 238 | 239 | def keepalive(self) -> None: 240 | """Extend the connection time by another 30 minutes""" 241 | asyncio.ensure_future(self.send({"method": "keepAlive"})) 242 | 243 | async def _auto_keepalive(self): 244 | while True: 245 | await asyncio.sleep(30 * 60) 246 | self.keepalive() 247 | 248 | def close(self) -> None: 249 | """Close the websocket session""" 250 | asyncio.ensure_future(self.send({"method": "close"})) 251 | if self._session: 252 | asyncio.ensure_future(self._session.close()) 253 | if self._keepalive_task: 254 | self._keepalive_task.cancel() 255 | -------------------------------------------------------------------------------- /cli-requirements.txt: -------------------------------------------------------------------------------- 1 | click 2 | -------------------------------------------------------------------------------- /examples/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 3 | # SPDX-License-Identifier: MIT 4 | """ 5 | Binance Chain CLI 6 | """ 7 | import asyncio 8 | import logging 9 | from pprint import pprint 10 | 11 | import click 12 | from .httpclient import HTTPClient 13 | 14 | log = logging.getLogger(__name__) 15 | 16 | 17 | def run(coro): 18 | """ Run an async coroutine """ 19 | loop = asyncio.get_event_loop() 20 | return loop.run_until_complete(coro) 21 | 22 | 23 | def dex_run(method, **kwargs): 24 | """ Run a method on the Binance DEX client """ 25 | dex = HTTPClient() 26 | result = run(getattr(dex, method)(**kwargs)) 27 | pprint(result) 28 | run(dex.close()) 29 | return result 30 | 31 | 32 | @click.group() 33 | def main(): 34 | pass 35 | 36 | 37 | @main.command() 38 | def time(): 39 | dex_run("get_time") 40 | 41 | 42 | @main.command() 43 | def node_info(): 44 | dex_run("get_node_info") 45 | 46 | 47 | @main.command() 48 | def fees(): 49 | dex_run("get_fees") 50 | 51 | 52 | @main.command() 53 | def validators(): 54 | dex_run("get_validators") 55 | 56 | 57 | @main.command() 58 | def peers(): 59 | dex_run("get_peers") 60 | 61 | 62 | @main.command() 63 | def tokens(): 64 | dex_run("get_token_list") 65 | 66 | 67 | @main.command() 68 | def markets(): 69 | dex_run("get_markets") 70 | 71 | 72 | @main.command() 73 | @click.argument("symbol") 74 | def depth(**kwargs): 75 | dex_run("get_depth", **kwargs) 76 | 77 | 78 | @main.command() 79 | @click.argument("hash") 80 | def broadcast(**kwargs): 81 | dex_run("broadcast", **kwargs) 82 | 83 | 84 | @main.command() 85 | @click.argument("symbol") 86 | @click.argument("interval") 87 | def klines(**kwargs): 88 | dex_run("get_klines", **kwargs) 89 | 90 | 91 | @main.command() 92 | @click.option("--address", help="the seller/buyer address", type=str) 93 | @click.option("--end", help="end time in milliseconds", type=int) 94 | @click.option("--limit", help="default 50; max 1000.", type=int) 95 | @click.option("--offset", help="start with 0; default 0.", type=int) 96 | @click.option("--side", help="order side. 1 for buy and 2 for sell.", type=int) 97 | @click.option("--start", help="start time in milliseconds", type=int) 98 | @click.option( 99 | "--status", 100 | help="order status list. Allowed value: [Ack, PartialFill, IocNoFill, FullyFill, Canceled, Expired, FailedBlocking, FailedMatching]", 101 | type=str, 102 | ) 103 | @click.option("--symbol", help="symbol", type=str) 104 | @click.option( 105 | "--total", 106 | help="total number required, 0 for not required and 1 for required; default not required, return total=-1 in response", 107 | type=int, 108 | ) 109 | def closed_orders(**kwargs): 110 | dex_run("get_closed_orders", **kwargs) 111 | 112 | 113 | @main.command() 114 | @click.option("--address", help="the seller/buyer address", type=str) 115 | @click.option("--limit", help="default 50; max 1000.", type=int) 116 | @click.option("--offset", help="start with 0; default 0.", type=int) 117 | @click.option("--symbol", help="symbol", type=str) 118 | @click.option( 119 | "--total", 120 | help="total number required, 0 for not required and 1 for required; default not required, return total=-1 in response", 121 | type=int, 122 | ) 123 | def open_orders(**kwargs): 124 | dex_run("get_open_orders", **kwargs) 125 | 126 | 127 | @main.command() 128 | @click.argument("symbol", default="") 129 | def ticker(**kwargs): 130 | dex_run("get_ticker", **kwargs) 131 | 132 | 133 | @main.command() 134 | @click.option("--address", help="the seller/buyer address") 135 | @click.option("--end", help="end time", type=int) 136 | @click.option("--limit", help="default 50; max 1000.", type=int) 137 | @click.option("--offset", help="start with 0; default 0.", type=int) 138 | @click.option("--start", help="start time", type=int) 139 | @click.option( 140 | "--total", 141 | help="total number required, 0 for not required and 1 for required; default not required, return total=-1 in response", 142 | type=int, 143 | ) 144 | def trades(**kwargs): 145 | dex_run("get_trades", **kwargs) 146 | 147 | 148 | @main.command() 149 | @click.option("--address", help="the seller/buyer address") 150 | @click.option("--end", help="end time", type=int) 151 | @click.option("--limit", help="default 50; max 1000.", type=int) 152 | @click.option("--offset", help="start with 0; default 0.", type=int) 153 | @click.option("--start", help="start time", type=int) 154 | @click.option( 155 | "--total", 156 | help="total number required, 0 for not required and 1 for required; default not required, return total=-1 in response", 157 | type=int, 158 | ) 159 | def block_exchange_fee(**kwargs): 160 | dex_run("get_block_exchange_fee", **kwargs) 161 | 162 | 163 | @main.command() 164 | @click.argument("address") 165 | @click.option("--height", help="block height", type=int) 166 | @click.option("--end", help="end in milliseconds", type=int) 167 | @click.option("--limit", help="default 50; max 1000.", type=int) 168 | @click.option("--offset", help="start with 0; default 0.", type=int) 169 | @click.option( 170 | "--side", help="transaction side. Allowed value: [ RECEIVE, SEND]", type=str 171 | ) 172 | @click.option("--start", help="start time in milliseconds", type=int) 173 | @click.option("--tx-asset", help="txAsset", type=str) 174 | @click.option( 175 | "--tx-type", 176 | help="transaction type. Allowed value: [NEW_ORDER,ISSUE_TOKEN,BURN_TOKEN,LIST_TOKEN,CANCEL_ORDER,FREEZE_TOKEN,UN_FREEZE_TOKEN,TRANSFER,PROPOSAL,VOTE,MINT,DEPOSIT]", 177 | type=str, 178 | ) 179 | def transactions(**kwargs): 180 | dex_run("get_transactions", **kwargs) 181 | 182 | 183 | if __name__ == "__main__": 184 | main() 185 | -------------------------------------------------------------------------------- /examples/http_examples.py: -------------------------------------------------------------------------------- 1 | from binancechain import HTTPClient 2 | 3 | 4 | async def http_examples(): 5 | client = HTTPClient(testnet=True) 6 | 7 | server_time = await client.get_time() 8 | 9 | node_info = await client.get_node_info() 10 | 11 | validators = await client.get_validators() 12 | 13 | peers = await client.get_peers() 14 | 15 | account_info = await client.get_account_info(address) 16 | 17 | sequence_info = await client.get_account_sequence(address) 18 | 19 | transaction = await client.get_transaction(hash) 20 | 21 | token_list = await client.get_token_list() 22 | 23 | markets = await client.get_markets(limit=500, offset=0) 24 | 25 | fees = await client.get_fees() 26 | 27 | depth = await client.get_depth(symbol, limit=100) 28 | 29 | klines = await client.get_klines(symbol, interval, limit=300, start=None, end=None) 30 | 31 | closed_orders = await client.get_closed_orders( 32 | address, 33 | end=None, 34 | limit=None, 35 | offset=None, 36 | side=None, 37 | start=None, 38 | status=None, 39 | symbol=None, 40 | total=None, 41 | ) 42 | 43 | open_orders = await client.get_open_orders( 44 | self, address, limit=None, offset=None, symbol=None, total=None 45 | ) 46 | 47 | order = await client.get_order(id) 48 | 49 | ticker = await client.get_ticker(symbol) 50 | 51 | trades = await client.get_trades( 52 | address=None, 53 | buyerOrderId=None, 54 | height=None, 55 | limit=None, 56 | offset=None, 57 | quoteAsset=None, 58 | sellerOrderId=None, 59 | side=None, 60 | start=None, 61 | end=None, 62 | total=None, 63 | symbol=None, 64 | ) 65 | 66 | block_fee = await client.block_exchange_fee( 67 | address=None, end=None, limit=None, offset=None, start=None, total=None 68 | ) 69 | 70 | transactions = await client.get_transactions( 71 | address, 72 | height=None, 73 | end=None, 74 | limit=None, 75 | offset=None, 76 | side=None, 77 | start=None, 78 | tx_asset=None, 79 | tx_type=None, 80 | ) 81 | 82 | """ POST REQUEST""" 83 | broadcast_info = await client.broadcast(data) 84 | -------------------------------------------------------------------------------- /examples/noderpc_examples.py: -------------------------------------------------------------------------------- 1 | from binancechain import NodeRPC 2 | 3 | 4 | async def nodeRPC_examples(): 5 | noderpc = NodeRPC(testnet=True) 6 | 7 | abic_info = await noderpc.get_abci_info(path, data=None, height="0", prove=False) 8 | 9 | concensus_state = await noderpc.get_consensus_state() 10 | 11 | dump_concensus_state = await noderpc.get_dump_consensus_state() 12 | 13 | genesis = await noderpc.get_genesis() 14 | 15 | health = await noderpc.get_health() 16 | 17 | net_info = await noderpc.get_net_info() 18 | 19 | status = await noderpc.get_status() 20 | 21 | query = await noderpc.abci_query("/param/fees") 22 | 23 | block = await noderpc.block(height=None) 24 | 25 | block_hash = await noderpc.block_by_hash(hash) 26 | 27 | block_results = await noderpc.block_results(height=None) # ABCIResults 28 | 29 | blockchain = await noderpc.blockchain(min_height, max_height) 30 | 31 | concensus_params = await noderpc.consensus_params("1") 32 | 33 | validators = await noderpc.validators(height=None) 34 | 35 | transaction = await noderpc.tx(txid, prove=False) 36 | 37 | tx_search = await noderpc.tx_search(query, prove=False, page=1, per_page=30) 38 | 39 | pending_transactions = await noderpc.unconfirmed_txs(limit=None) 40 | 41 | pendings_number = await noderpc.get_num_unconfirmed_txs() 42 | 43 | tx_hash = await noderpc.broadcast_tx_async(hash) 44 | 45 | tx_onchain = await noderpc.broadcast_tx_sync(hash) 46 | 47 | tx_confirmed = await noderpc.commit(height=None) 48 | -------------------------------------------------------------------------------- /examples/transaction_examples.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example to use BinanceTransction for transaction creation 3 | """ 4 | 5 | from binancechain import Transaction 6 | from binancechain.enums import Side, Ordertype, Timeinforce 7 | 8 | 9 | async def transaction_example(): 10 | transaction = Transaction(wallet=wallet, client=client) 11 | 12 | transfer = await transaction.transfer(to_address, symbol="BNB", amount=0.1) 13 | 14 | multi_transfer = await transaction.multi_transfer( 15 | to_address, 16 | transfers=[{"symbol": "BTC", "amount": 0.1}, {"symbol": "BNB", "amount": 0.1}], 17 | ) 18 | 19 | new_order_txid = await transaction.create_new_order( 20 | symbol="binance_pair", 21 | side=Side.BUY, 22 | ordertype=Ordertype.LIMIT, 23 | price=1, 24 | quantity=1, 25 | timeInForce=Timeinforce.GTE, 26 | ) 27 | 28 | cancel_order_txid = await transaction.cancel_order(symbol="pair", refid="") 29 | 30 | freeze_token_txid = await transaction.freeze_token(symbol="token", amount=1) 31 | 32 | unfreeze_token_txid = await transaction.unfreeze_token(symbol="token", amount=1) 33 | 34 | vote_txid = await transaction.vote( 35 | proposal_id="", option=Votes.YES 36 | ) # only validator can vote 37 | 38 | issue_token_txid = await transaction.issue_token(symbol, name, supply, mintable) 39 | 40 | mint_token_txid = await transaction.mint_token(symbol, amount) 41 | 42 | burn_token_txid = await transaction.burn_token(symbol, amount) 43 | """ 44 | Create Unsigned Transaction, return transaction with message to sign and broadcast somewhere else 45 | """ 46 | """ 47 | Using default client if no client is passed in 48 | """ 49 | 50 | transfer_transaction = await Transaction.transfer_transaction( 51 | from_address, to_address, symbol, amount 52 | ) 53 | 54 | multi_transfer_transaction = await Transaction.multi_transfer_transaction( 55 | from_address, 56 | to_address, 57 | transfers=[{"symbol": "BTC", "amount": 0.1}, {"symbol": "BNB", "amount": 0.1}], 58 | ) 59 | 60 | limit_buy_transaction = await Transaction.new_order_transaction( 61 | address="owner address", 62 | symbol="pair", 63 | side=Side.BUY, 64 | ordertype=Ordertype.LIMIT, 65 | price=1, 66 | quantity=1, 67 | timeInForce=Timeinforce.GTE, 68 | testnet=True, 69 | client=None, 70 | ) 71 | 72 | limit_sell_transaction = await Transaction.new_order_transaction( 73 | address="owner address", 74 | symbol="pair", 75 | side=Side.BUY, 76 | ordertype=Ordertype.LIMIT, 77 | price=1, 78 | quantity=1, 79 | timeInForce=Timeinforce.GTE, 80 | testnet=True, 81 | client=None, 82 | ) 83 | 84 | cancel_order_transaction = await Transaction.cancel_order( 85 | address="owner_address", symbol="pair", refid="", testnet=True, client=None 86 | ) 87 | 88 | freeze_token_transaction = await Transaction.freeze_token( 89 | address="ownder_address", symbol="BNB", amount=1, testnet=True, client=None 90 | ) 91 | 92 | unfreeze_token_tranasaction = await Transaction.unfreeze_token_transaction( 93 | address="ownder_address", symbol="BNB", amount=1, testnet=True, client=None 94 | ) 95 | 96 | vote_transaction = await Transaction.vote_transaction( 97 | voter, proposal_id, option=Votes.YES, client=None, testnet=True 98 | ) 99 | 100 | issue_token_transaction = await Transaction.issue_token_transaction( 101 | owner, name, symbol, sypply, mintable, client=None, testnet=True 102 | ) 103 | 104 | mint_token_transaction = await Transaction.mint_token_transaction( 105 | owner, symbol, amount, client=None, testnet=True 106 | ) 107 | 108 | burn_token_transaction = Transaction.burn_token_transaction( 109 | owner, symbol, amount, client=None, testnet=True 110 | ) 111 | 112 | """ 113 | Get Sign Message to sign with wallet 114 | """ 115 | sign_message_bytes_format = limit_buy_transaction.get_sign_message() 116 | -------------------------------------------------------------------------------- /examples/wallet_examples.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example to use BinanceWallet for keys, keystore, address management 3 | """ 4 | 5 | from binancechain import BinanceWallet 6 | 7 | """ 8 | Create wallet 9 | """ 10 | wallet = BinanceWallet.create_wallet(password="", testnet=False) 11 | 12 | wallet = BinanceWallet.create_wallet_mnemonic( 13 | language="english", password="", testnet=False 14 | ) 15 | 16 | """ 17 | Create Keystore 18 | """ 19 | keystore = BinanceWallet.create_keystore(password=None) 20 | 21 | 22 | """ 23 | Initiate wallet 24 | """ 25 | wallet = BinanceWallet(key="HDKEY object", testnet=False) 26 | """ 27 | Get wallet Recover from key 28 | """ 29 | 30 | wallet = BinanceWallet.wallet_from_keystore( 31 | keystore=keystore, password="", testnet=False 32 | ) 33 | 34 | wallat = BinanceWallet.wallet_from_mnemonic( 35 | words="mnemonic words", password="", testnet=False 36 | ) 37 | 38 | wallet = BinanceWallet.wallet_from_privatekey( 39 | privatekey="private_key", password="", testnet=False 40 | ) 41 | 42 | wallet = BinanceWallet.wallet_from_seed(seed="seed", testnet=False) 43 | 44 | """ 45 | Use wallet to get address, sign, verify message 46 | """ 47 | 48 | address = wallet.get_address() 49 | 50 | private_key = wallet.get_privatekey() 51 | 52 | public_key = wallet.get_publickey() 53 | 54 | pubkey, signature = wallet.sign("message") 55 | 56 | isValid = wallet.verify_signature("message", signature) 57 | -------------------------------------------------------------------------------- /examples/websocket_decorator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | An example of the event-driven decorator WebSocket API 4 | """ 5 | 6 | import binancechain 7 | 8 | ADDRESS = "tbnb18d6rnpmkxzqv3767xaaev5zzn06p42nya8zu79" 9 | 10 | dex = binancechain.WebSocket(ADDRESS, testnet=True) 11 | 12 | 13 | @dex.on("open") 14 | async def on_open(): 15 | print("Binance Chain WebSocket open!") 16 | 17 | 18 | @dex.on("allTickers", symbols=["$all"]) 19 | async def on_ticker(msg): 20 | print(f"tickers: {str(msg)[:75]}") 21 | 22 | 23 | @dex.on("kline_1m", symbols=["000-0E1_BNB"]) 24 | async def on_kline(kline): 25 | print(f"kline: {str(kline)[:75]}") 26 | 27 | 28 | @dex.on("orders") 29 | async def user_orders(msg): 30 | print(msg) 31 | 32 | 33 | @dex.on("accounts") 34 | async def user_accounts(msg): 35 | print(msg) 36 | 37 | 38 | @dex.on("transfers") 39 | async def user_transfers(msg): 40 | print(msg) 41 | 42 | 43 | @dex.on("error") 44 | async def on_error(msg): 45 | print(msg) 46 | 47 | 48 | if __name__ == "__main__": 49 | try: 50 | dex.start() 51 | except KeyboardInterrupt: 52 | pass 53 | finally: 54 | dex.close() 55 | -------------------------------------------------------------------------------- /examples/websocket_examples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pprint import pprint 4 | 5 | import binancechain 6 | 7 | address = "tbnb18d6rnpmkxzqv3767xaaev5zzn06p42nya8zu79" 8 | 9 | dex = binancechain.WebSocket(address, testnet=True) 10 | 11 | 12 | def callback(msg): 13 | print(msg) 14 | 15 | 16 | def on_open(): 17 | symbols = ["BNB_BTC.B-918"] 18 | dex.subscribe_user_orders(callback=callback) 19 | dex.subscribe_user_accounts(callback=callback) 20 | dex.subscribe_user_transfers(callback=callback) 21 | dex.subscribe_trades(callback=callback, symbols=symbols) 22 | dex.subscribe_market_depth(callback=callback, symbols=symbols) 23 | dex.subscribe_market_diff(callback=callback, symbols=symbols) 24 | dex.subscribe_klines(callback=callback, symbols=symbols) 25 | dex.subscribe_ticker(callback=callback, symbols=symbols) 26 | dex.subscribe_all_tickers(callback=callback) 27 | dex.subscribe_mini_ticker(callback=callback, symbols=symbols) 28 | dex.subscribe_all_mini_tickers(callback=callback) 29 | dex.subscribe_blockheight(callback=callback) 30 | 31 | 32 | def user_orders(msg): 33 | pprint(msg) 34 | 35 | 36 | def user_accounts(msg): 37 | pprint(msg) 38 | 39 | 40 | def user_transfers(msg): 41 | pprint(msg) 42 | 43 | 44 | def on_error(msg): 45 | pprint(msg) 46 | 47 | 48 | if __name__ == '__main__': 49 | try: 50 | dex.start(on_open, on_error) 51 | except KeyboardInterrupt: 52 | pass 53 | finally: 54 | dex.close() 55 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | . 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import setuptools 4 | from distutils.core import setup 5 | 6 | setup( 7 | name="binancechain", 8 | version="0.1.6", 9 | description="Unofficial Binance Chain SDK", 10 | author="Luke Macken & Kim Bui", 11 | author_email="", 12 | url="https://github.com/lmacken/binance-chain-python", 13 | packages=["binancechain"], 14 | classifiers=[ 15 | "Development Status :: 4 - Beta", 16 | "Framework :: AsyncIO", 17 | "Programming Language :: Python :: 3 :: Only", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3.6", 20 | "Programming Language :: Python :: 3.7", 21 | "License :: OSI Approved :: MIT License", 22 | "Operating System :: OS Independent", 23 | "Intended Audience :: Developers", 24 | ], 25 | install_requires=[ 26 | "wheel", 27 | "bech32", 28 | "aiohttp", 29 | "bitcoinlib", 30 | "eth_keyfile", 31 | "secp256k1", 32 | "pyee", 33 | "varint", 34 | "protobuf", 35 | "orjson", 36 | ], 37 | ) 38 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | tox 2 | 3 | pytest-asyncio 4 | pytest-cov 5 | coveralls 6 | 7 | # for the static typechecker 8 | mypy 9 | 10 | -------------------------------------------------------------------------------- /test/test_httpclient.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | """ 4 | Binance DEX SDK Test Suite 5 | """ 6 | from pprint import pprint 7 | from datetime import datetime, timedelta 8 | 9 | import aiohttp 10 | import pytest 11 | 12 | from binancechain import HTTPClient, BinanceChainException 13 | 14 | 15 | @pytest.fixture 16 | async def client(): 17 | client = HTTPClient(testnet=True) 18 | yield client 19 | await client.close() 20 | 21 | 22 | @pytest.mark.asyncio 23 | async def test_time(client): 24 | time = await client.get_time() 25 | pprint(time) 26 | for key in ("ap_time", "block_time"): 27 | assert key in time 28 | 29 | 30 | @pytest.mark.asyncio 31 | async def test_node_info(client): 32 | info = await client.get_node_info() 33 | pprint(info) 34 | for key in ("node_info", "sync_info", "validator_info"): 35 | assert key in info 36 | 37 | 38 | @pytest.mark.asyncio 39 | async def test_get_validators(client): 40 | validators = await client.get_validators() 41 | pprint(validators) 42 | for key in ("block_height", "validators"): 43 | assert key in validators 44 | 45 | 46 | @pytest.mark.asyncio 47 | async def test_get_peers(client): 48 | peers = await client.get_peers() 49 | pprint(peers) 50 | assert isinstance(peers, list) 51 | assert len(peers) 52 | for peer in peers: 53 | for key in ("version", "network", "moniker", "id", "capabilities"): 54 | assert key in peer 55 | 56 | 57 | @pytest.mark.asyncio 58 | async def test_token_list(client): 59 | tokens = await client.get_token_list() 60 | pprint(tokens) 61 | for token in tokens: 62 | for key in ( 63 | "mintable", 64 | "name", 65 | "original_symbol", 66 | "owner", 67 | "symbol", 68 | "total_supply", 69 | ): 70 | assert key in token 71 | 72 | 73 | @pytest.mark.asyncio 74 | async def test_get_markets(client): 75 | markets = await client.get_markets() 76 | pprint(markets) 77 | for market in markets: 78 | for key in ( 79 | "base_asset_symbol", 80 | "list_price", 81 | "lot_size", 82 | "quote_asset_symbol", 83 | "tick_size", 84 | ): 85 | assert key in market 86 | 87 | 88 | @pytest.mark.asyncio 89 | async def test_get_fees(client): 90 | fees = await client.get_fees() 91 | assert fees 92 | assert "fee" in fees[0] 93 | 94 | 95 | @pytest.mark.asyncio 96 | async def test_get_transaction(client): 97 | h = "F9016F01A1098BF8024C28C8400AE010FC32DC8A393ADB26E56F37BC8B0C5D66" 98 | tx = await client.get_transaction(h) 99 | for key in ("ok", "log", "hash", "data"): 100 | assert key in tx 101 | assert tx["ok"] 102 | 103 | 104 | @pytest.mark.asyncio 105 | async def test_get_account(client): 106 | address = "tbnb18d6rnpmkxzqv3767xaaev5zzn06p42nya8zu79" 107 | account = await client.get_account(address) 108 | assert "address" in account 109 | 110 | 111 | @pytest.mark.asyncio 112 | async def test_get_account_sequence(client): 113 | address = "tbnb18d6rnpmkxzqv3767xaaev5zzn06p42nya8zu79" 114 | account = await client.get_account_sequence(address) 115 | assert "sequence" in account 116 | 117 | 118 | @pytest.mark.asyncio 119 | async def test_get_closed_orders(client): 120 | address = "tbnb18d6rnpmkxzqv3767xaaev5zzn06p42nya8zu79" 121 | orders = await client.get_closed_orders(address, limit=1) 122 | assert "order" in orders 123 | 124 | start = (datetime.utcnow() - timedelta(days=1)).timestamp() 125 | end = datetime.utcnow().timestamp() 126 | markets = await client.get_markets() 127 | market = markets[0] 128 | symbol = "{base_asset_symbol}_{quote_asset_symbol}".format_map(market) 129 | orders = await client.get_closed_orders( 130 | address, 131 | start=start, 132 | end=end, 133 | offset=1, 134 | side=1, 135 | status="Ack", 136 | symbol=symbol, 137 | total=1, 138 | ) 139 | assert "order" in orders 140 | 141 | 142 | @pytest.mark.asyncio 143 | async def test_get_open_orders(client): 144 | address = "tbnb18d6rnpmkxzqv3767xaaev5zzn06p42nya8zu79" 145 | markets = await client.get_markets() 146 | market = markets[0] 147 | symbol = "{base_asset_symbol}_{quote_asset_symbol}".format_map(market) 148 | orders = await client.get_open_orders( 149 | address, limit=1, offset=0, total=1, symbol=symbol 150 | ) 151 | pprint(orders) 152 | assert "order" in orders 153 | 154 | 155 | @pytest.mark.asyncio 156 | async def test_get_transactions(client): 157 | address = "tbnb18d6rnpmkxzqv3767xaaev5zzn06p42nya8zu79" 158 | transactions = await client.get_transactions(address=address) 159 | assert "tx" in transactions 160 | 161 | transactions = await client.get_transactions( 162 | address=address, 163 | height=999, 164 | start=11, 165 | end=999999, 166 | limit=1, 167 | offset=0, 168 | side="RECEIVE", 169 | tx_asset="test", 170 | tx_type="NEW_ORDER", 171 | ) 172 | assert "tx" in transactions 173 | 174 | 175 | @pytest.mark.asyncio 176 | async def test_get_order(client): 177 | address = "tbnb18d6rnpmkxzqv3767xaaev5zzn06p42nya8zu79" 178 | orders = await client.get_closed_orders(address) 179 | closed_order = orders["order"][0] 180 | order_id = closed_order["orderId"] 181 | order = await client.get_order(order_id) 182 | assert order == closed_order 183 | 184 | 185 | """ 186 | @pytest.mark.asyncio 187 | async def test_broadcast(client): 188 | body = ... 189 | tx = await client.broadcast(body) 190 | pprint(tx) 191 | 192 | """ 193 | 194 | 195 | @pytest.mark.asyncio 196 | async def test_get_klines(client): 197 | markets = await client.get_markets() 198 | market = markets[0] 199 | symbol = "{base_asset_symbol}_{quote_asset_symbol}".format_map(market) 200 | interval = "1m" 201 | pprint(market) 202 | start = (datetime.utcnow() - timedelta(days=7)).timestamp() 203 | end = datetime.utcnow().timestamp() 204 | klines = await client.get_klines( 205 | symbol=symbol, interval=interval, start=start, end=end 206 | ) 207 | pprint(klines) 208 | for kline in klines: 209 | assert len(kline) == 9 210 | 211 | 212 | @pytest.mark.asyncio 213 | async def test_get_ticker(client): 214 | tickers = await client.get_ticker() 215 | pprint(tickers) 216 | assert tickers 217 | for ticker in tickers: 218 | for key in ("askPrice", "askQuantity", "bidPrice", "bidQuantity"): 219 | assert key in ticker 220 | 221 | markets = await client.get_markets() 222 | market = markets[0] 223 | symbol = f"{market['base_asset_symbol']}_{market['quote_asset_symbol']}" 224 | tickers = await client.get_ticker(symbol=symbol) 225 | assert tickers 226 | for ticker in tickers: 227 | for key in ("askPrice", "askQuantity", "bidPrice", "bidQuantity"): 228 | assert key in ticker 229 | 230 | 231 | @pytest.mark.asyncio 232 | async def test_trades(client): 233 | trades = await client.get_trades() 234 | pprint(trades) 235 | assert "total" in trades 236 | assert "trade" in trades 237 | for trade in trades["trade"]: 238 | for key in ( 239 | "baseAsset", 240 | "blockHeight", 241 | "buyFee", 242 | "buyerId", 243 | "buyerOrderId", 244 | "price", 245 | "quantity", 246 | "quoteAsset", 247 | "sellFee", 248 | "sellerId", 249 | "sellerOrderId", 250 | "symbol", 251 | "time", 252 | "tradeId", 253 | ): 254 | assert key in trade 255 | 256 | 257 | @pytest.mark.asyncio 258 | async def test_get_trades_for_address(client): 259 | address = "tbnb18d6rnpmkxzqv3767xaaev5zzn06p42nya8zu79" 260 | trades = await client.get_trades(address=address) 261 | pprint(trades) 262 | assert "total" in trades 263 | assert "trade" in trades 264 | for trade in trades["trade"]: 265 | for key in ( 266 | "baseAsset", 267 | "blockHeight", 268 | "buyFee", 269 | "buyerId", 270 | "buyerOrderId", 271 | "price", 272 | "quantity", 273 | "quoteAsset", 274 | "sellFee", 275 | "sellerId", 276 | "sellerOrderId", 277 | "symbol", 278 | "time", 279 | "tradeId", 280 | ): 281 | assert key in trade 282 | 283 | 284 | @pytest.mark.asyncio 285 | async def test_get_trades_other_params(client): 286 | trades = await client.get_trades( 287 | buyerOrderId="abc", 288 | height=999999, 289 | limit=1, 290 | offset=0, 291 | sellerOrderId="zyx", 292 | side=1, 293 | start=0, 294 | end=99999999, 295 | total=1, 296 | symbol="xyz", 297 | ) 298 | assert trades 299 | 300 | 301 | @pytest.mark.asyncio 302 | async def test_get_block_exchange_fee(client): 303 | address = "tbnb18d6rnpmkxzqv3767xaaev5zzn06p42nya8zu79" 304 | fees = await client.get_block_exchange_fee(address) 305 | assert "blockExchangeFee" in fees 306 | fees = await client.get_block_exchange_fee( 307 | address, start=111111, end=99999999, offset=0, limit=1, total=1 308 | ) 309 | assert "blockExchangeFee" in fees 310 | 311 | 312 | @pytest.mark.asyncio 313 | async def test_get_depth(client): 314 | markets = await client.get_markets() 315 | market = markets[0] 316 | symbol = f"{market['base_asset_symbol']}_{market['quote_asset_symbol']}" 317 | depth = await client.get_depth(symbol) 318 | assert "asks" in depth and "bids" in depth 319 | 320 | 321 | @pytest.mark.asyncio 322 | async def test_invalid_request(client): 323 | client._server = "https://binance.com/" 324 | try: 325 | resp = await client.post_request("INVALID") 326 | assert False, resp 327 | except BinanceChainException as e: 328 | assert e.response.status == 404 329 | assert isinstance(e.__cause__, aiohttp.ContentTypeError) 330 | 331 | 332 | @pytest.mark.asyncio 333 | async def test_del_without_close_warning(): 334 | client = HTTPClient(testnet=True) 335 | await client.get_time() 336 | with pytest.warns(UserWarning): 337 | del (client) 338 | -------------------------------------------------------------------------------- /test/test_noderpc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | """ 4 | Binance Chain Node RPC Test Suite 5 | """ 6 | import pytest 7 | import aiohttp 8 | 9 | from binancechain import NodeRPC, BinanceChainException 10 | 11 | 12 | @pytest.fixture 13 | async def noderpc(): 14 | noderpc = NodeRPC(testnet=True) 15 | yield noderpc 16 | noderpc.close() 17 | 18 | 19 | def on_error(msg): 20 | print(f'Error: {msg}') 21 | 22 | 23 | @pytest.mark.asyncio 24 | async def test_abci_info(noderpc): 25 | resp = await noderpc.get_abci_info() 26 | assert "result" in resp 27 | 28 | 29 | @pytest.mark.asyncio 30 | async def test_consensus_state(noderpc): 31 | resp = await noderpc.get_consensus_state() 32 | assert "result" in resp 33 | 34 | 35 | @pytest.mark.asyncio 36 | async def test_get_dump_consensus_state(noderpc): 37 | resp = await noderpc.get_dump_consensus_state() 38 | assert "result" in resp 39 | 40 | 41 | @pytest.mark.asyncio 42 | async def test_get_genesis(noderpc): 43 | resp = await noderpc.get_genesis() 44 | assert "result" in resp 45 | 46 | 47 | @pytest.mark.asyncio 48 | async def test_get_health(noderpc): 49 | resp = await noderpc.get_health() 50 | assert "result" in resp 51 | 52 | 53 | @pytest.mark.asyncio 54 | async def test_net_info(noderpc): 55 | resp = await noderpc.get_net_info() 56 | assert "result" in resp 57 | 58 | 59 | @pytest.mark.asyncio 60 | async def test_get_num_unconfirmed_txs(noderpc): 61 | resp = await noderpc.get_num_unconfirmed_txs() 62 | assert "result" in resp 63 | 64 | 65 | @pytest.mark.asyncio 66 | async def test_get_status(noderpc): 67 | resp = await noderpc.get_status() 68 | assert "result" in resp 69 | 70 | 71 | @pytest.mark.asyncio 72 | async def test_abci_query(noderpc): 73 | resp = await noderpc.abci_query("/param/fees") 74 | assert "result" in resp 75 | 76 | 77 | @pytest.mark.asyncio 78 | async def test_block(noderpc): 79 | resp = await noderpc.block() 80 | assert "result" in resp 81 | 82 | 83 | @pytest.mark.asyncio 84 | async def test_block_by_hash(noderpc): 85 | resp = await noderpc.block_by_hash( 86 | "8B43B22699803E8F8E51E7D9294C14EDAD34A264ECFB7F776AF9422714B93C06" 87 | ) 88 | assert "error" in resp 89 | 90 | 91 | @pytest.mark.asyncio 92 | async def test_block_results(noderpc): 93 | resp = await noderpc.block_results() 94 | assert "result" in resp 95 | 96 | 97 | @pytest.mark.asyncio 98 | async def test_blockchain(noderpc): 99 | resp = await noderpc.blockchain("0", "1") 100 | assert "result" in resp 101 | 102 | 103 | @pytest.mark.asyncio 104 | async def test_broadcast_tx_async(noderpc): 105 | resp = await noderpc.broadcast_tx_async( 106 | "0xdb01f0625dee0a63ce6dc0430a14813e4939f1567b219704ffc2ad4df58bde010879122b383133453439333946313536374232313937303446464332414434444635384244453031303837392d34341a0d5a454252412d3136445f424e422002280130c0843d38904e400112700a26eb5ae9872102139bdd95de72c22ac2a2b0f87853b1cca2e8adf9c58a4a689c75d3263013441a124015e99f7a686529c76ccc2d70b404af82ca88dfee27c363439b91ea0280571b2731c03b902193d6a5793baf64b54bcdf3f85e0d7cf657e1a1077f88143a5a65f518d2e518202b" 107 | ) 108 | assert "result" in resp 109 | 110 | 111 | @pytest.mark.asyncio 112 | async def test_broadcast_tx_commit(noderpc): 113 | resp = await noderpc.broadcast_tx_commit( 114 | "0xdb01f0625dee0a63ce6dc0430a14813e4939f1567b219704ffc2ad4df58bde010879122b383133453439333946313536374232313937303446464332414434444635384244453031303837392d34341a0d5a454252412d3136445f424e422002280130c0843d38904e400112700a26eb5ae9872102139bdd95de72c22ac2a2b0f87853b1cca2e8adf9c58a4a689c75d3263013441a124015e99f7a686529c76ccc2d70b404af82ca88dfee27c363439b91ea0280571b2731c03b902193d6a5793baf64b54bcdf3f85e0d7cf657e1a1077f88143a5a65f518d2e518202b" 115 | ) 116 | assert "result" in resp 117 | 118 | 119 | @pytest.mark.asyncio 120 | async def test_broadcast_tx_sync(noderpc): 121 | resp = await noderpc.broadcast_tx_sync( 122 | "0xdb01f0625dee0a63ce6dc0430a14813e4939f1567b219704ffc2ad4df58bde010879122b383133453439333946313536374232313937303446464332414434444635384244453031303837392d34341a0d5a454252412d3136445f424e422002280130c0843d38904e400112700a26eb5ae9872102139bdd95de72c22ac2a2b0f87853b1cca2e8adf9c58a4a689c75d3263013441a124015e99f7a686529c76ccc2d70b404af82ca88dfee27c363439b91ea0280571b2731c03b902193d6a5793baf64b54bcdf3f85e0d7cf657e1a1077f88143a5a65f518d2e518202b" 123 | ) 124 | assert "result" in resp 125 | 126 | 127 | @pytest.mark.asyncio 128 | async def test_commit(noderpc): 129 | resp = await noderpc.commit("1") 130 | assert "result" in resp 131 | 132 | 133 | @pytest.mark.asyncio 134 | async def test_consensus_params(noderpc): 135 | resp = await noderpc.consensus_params("1") 136 | assert "result" in resp 137 | 138 | 139 | @pytest.mark.asyncio 140 | async def test_tx(noderpc): 141 | resp = await noderpc.tx("36F0945A22CD6921FF9F85F64080334B4887DE4021B2EA5EE7B1D182C3FFEE01") 142 | assert "error" in resp 143 | 144 | 145 | @pytest.mark.asyncio 146 | async def test_tx_search(noderpc): 147 | resp = await noderpc.tx_search("tx.height=1000", per_page=1) 148 | assert "result" in resp 149 | 150 | 151 | @pytest.mark.asyncio 152 | async def test_unconfirmed_txs(noderpc): 153 | resp = await noderpc.unconfirmed_txs() 154 | assert "result" in resp 155 | 156 | 157 | @pytest.mark.asyncio 158 | async def test_validators(noderpc): 159 | resp = await noderpc.validators() 160 | assert "result" in resp 161 | 162 | 163 | @pytest.mark.asyncio 164 | async def test_invalid_post_request(noderpc): 165 | noderpc.url = "https://binance.org/invalid" 166 | try: 167 | resp = await noderpc.post_request('INVALID') 168 | assert False, resp 169 | except BinanceChainException as e: 170 | assert e.response.status == 403 171 | assert isinstance(e.__cause__, aiohttp.ContentTypeError) 172 | 173 | 174 | @pytest.mark.asyncio 175 | async def test_invalid_get_request(noderpc): 176 | try: 177 | resp = await noderpc.get_request('INVALID') 178 | assert False, resp 179 | except BinanceChainException as e: 180 | assert e.response.status == 404 181 | assert isinstance(e.__cause__, aiohttp.ContentTypeError) 182 | 183 | 184 | @pytest.mark.asyncio 185 | async def test_del_without_close_warning(): 186 | noderpc = NodeRPC(testnet=True) 187 | await noderpc.get_status() 188 | with pytest.warns(UserWarning): 189 | del(noderpc) 190 | 191 | 192 | @pytest.mark.asyncio 193 | async def test_ws_open_close(noderpc): 194 | """"Open then immediately close""" 195 | def on_open(): 196 | print('opened') 197 | noderpc.close() 198 | 199 | await noderpc.start_async(on_open=on_open, on_error=on_error) 200 | print('closed') 201 | 202 | 203 | @pytest.mark.asyncio 204 | async def test_ws_subscribe(noderpc): 205 | """"Open then immediately close with the decorator API""" 206 | 207 | results = [] 208 | 209 | def on_msg(msg): 210 | results.append(msg) 211 | noderpc.close() 212 | 213 | def on_open(): 214 | noderpc.subscribe("tm.event = 'Tx'") 215 | 216 | await noderpc.start_async(on_open=on_open, on_error=on_error, on_msg=on_msg) 217 | assert results 218 | result_ids = [r['id'] for r in results] 219 | assert result_ids == ['0'] 220 | for result in results: 221 | assert result['result'] == {} 222 | 223 | 224 | @pytest.mark.asyncio 225 | async def test_ws_unsubscribe(noderpc): 226 | """"Open then immediately close with the decorator API""" 227 | 228 | results = [] 229 | 230 | def on_msg(msg): 231 | print('on_msg', msg) 232 | results.append(msg) 233 | if len(results) == 1: 234 | noderpc.unsubscribe("tm.event = 'Tx'") 235 | if len(results) == 2: 236 | noderpc.close() 237 | 238 | def on_open(): 239 | noderpc.subscribe("tm.event = 'Tx'") 240 | 241 | await noderpc.start_async(on_open=on_open, on_error=on_error, on_msg=on_msg) 242 | assert results 243 | result_ids = [r['id'] for r in results] 244 | assert result_ids == ['0', '1'] 245 | for result in results: 246 | assert result['result'] == {} 247 | 248 | 249 | @pytest.mark.asyncio 250 | async def test_ws_unsubscribe_all(noderpc): 251 | """"Open then immediately close with the decorator API""" 252 | 253 | results = [] 254 | 255 | def on_msg(msg): 256 | print('on_msg', msg) 257 | results.append(msg) 258 | if len(results) == 1: 259 | noderpc.unsubscribe_all() 260 | if len(results) == 2: 261 | noderpc.close() 262 | 263 | def on_open(): 264 | noderpc.subscribe("tm.event = 'Tx'") 265 | 266 | await noderpc.start_async(on_open=on_open, on_error=on_error, on_msg=on_msg) 267 | assert results 268 | result_ids = [r['id'] for r in results] 269 | assert result_ids == ['0', '1'] 270 | for result in results: 271 | assert result['result'] == {} 272 | -------------------------------------------------------------------------------- /test/test_ratelimit.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pytest 3 | 4 | from binancechain import HTTPClient 5 | 6 | 7 | @pytest.mark.asyncio 8 | async def test_ratelimiter(): 9 | """ 10 | Make 10 `time` requests concurrently and ensure they are not rate-limited 11 | """ 12 | client = HTTPClient(testnet=True, rate_limit=True) 13 | futures = [] 14 | for _ in range(10): 15 | futures.append(asyncio.ensure_future(client.get_time())) 16 | 17 | results = await asyncio.gather(*futures) 18 | print(results) 19 | assert results 20 | for result in results: 21 | print(result) 22 | if 'message' in result: 23 | assert result['message'] != 'API rate limit exceeded' 24 | assert 'block_time' in result 25 | 26 | await client.close() 27 | -------------------------------------------------------------------------------- /test/test_transaction.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | """ 4 | Binance DEX SDK Test Suite for Transaction builder 5 | """ 6 | from pprint import pprint 7 | 8 | import pytest 9 | import asyncio 10 | import json 11 | from decimal import Decimal 12 | 13 | import binascii 14 | 15 | from binancechain import Transaction, Wallet, HTTPClient, BinanceChainException 16 | from binancechain.enums import Side, Votes, Ordertype, Timeinforce 17 | 18 | MNEMONIC_2 = "tennis utility midnight pattern that foot security tent punch glance still night virus loop trade velvet rent glare ramp cushion defy grass section cage" 19 | MNEMONIC = "apart conduct congress bless remember picnic aerobic nothing dinner guilt catch brain sunny vocal advice castle horror shift reject valley evoke fork syrup code" 20 | PAIR = "IBB-8DE_BNB" 21 | PROPOSAL_ID = 370 22 | 23 | 24 | @pytest.fixture 25 | async def client(): 26 | client = HTTPClient(testnet=True) 27 | yield client 28 | await client.close() 29 | 30 | 31 | @pytest.fixture 32 | async def wallet(): 33 | wallet = Wallet.wallet_from_mnemonic(words=MNEMONIC, testnet=True) 34 | yield wallet 35 | 36 | 37 | @pytest.fixture 38 | async def wallet_two(): 39 | wallet_two = Wallet.wallet_from_mnemonic(words=MNEMONIC_2, testnet=True) 40 | yield wallet_two 41 | 42 | 43 | @pytest.mark.asyncio 44 | async def test_new_order(wallet, client): 45 | address = wallet.get_address() 46 | transaction = await Transaction.new_order_transaction( 47 | address=address, 48 | symbol=PAIR, 49 | side=Side.BUY, 50 | timeInForce=Timeinforce.GTE, 51 | price=0.01, 52 | quantity=1, 53 | ordertype=Ordertype.LIMIT, 54 | client=client, 55 | ) 56 | pubkey, signature = wallet.sign(transaction.get_sign_message()) 57 | hex_data = transaction.update_signature(pubkey, signature) 58 | broadcast = await client.broadcast(hex_data) 59 | txid = broadcast[0]["hash"] 60 | assert txid 61 | await asyncio.sleep(2) 62 | tx = await client.get_transaction(txid) 63 | assert tx["data"], "transaction not found" 64 | print(tx) 65 | 66 | 67 | @pytest.mark.asyncio 68 | async def test_cancel_order(wallet, client): 69 | address = wallet.get_address() 70 | open_orders = await client.get_open_orders(address) 71 | order = open_orders["order"][0] 72 | refid = order["orderId"] 73 | symbol = order["symbol"] 74 | transaction = await Transaction.cancel_order_transaction( 75 | address=address, symbol=symbol, refid=refid, client=client 76 | ) 77 | pubkey, signature = wallet.sign(transaction.get_sign_message()) 78 | hex_data = transaction.update_signature(pubkey, signature) 79 | broadcast = await client.broadcast(hex_data) 80 | txid = broadcast[0]["hash"] 81 | await asyncio.sleep(2) 82 | tx = await client.get_transaction(txid) 83 | assert tx["data"], "transaction not found" 84 | 85 | 86 | @pytest.mark.asyncio 87 | async def test_transfer_order(wallet, wallet_two, client): 88 | address_1 = wallet.get_address() 89 | address_2 = wallet_two.get_address() 90 | account_1 = await client.get_account(address_1) 91 | account_2 = await client.get_account(address_2) 92 | balances_1 = account_1["balances"] 93 | balances_2 = account_2["balances"] 94 | TOKEN = "BNB" 95 | for balance in balances_1: 96 | if balance["symbol"] == TOKEN: 97 | balance_1 = Decimal(balance["free"]) 98 | break 99 | for balance in balances_2: 100 | if balance["symbol"] == TOKEN: 101 | balance_2 = Decimal(balance["free"]) 102 | break 103 | assert balance_1 and balance_1 > 0.1, "No Token balance to test" 104 | transaction = await Transaction.transfer_transaction( 105 | from_address=address_1, 106 | to_address=address_2, 107 | symbol=TOKEN, 108 | amount=0.1, 109 | client=client, 110 | ) 111 | pubkey, signature = wallet.sign(transaction.get_sign_message()) 112 | hex_data = transaction.update_signature(pubkey, signature) 113 | broadcast = await client.broadcast(hex_data) 114 | assert broadcast, "Fail to broadcast" 115 | assert "hash" in broadcast[0], "No txid" 116 | txid = broadcast[0]["hash"] 117 | await asyncio.sleep(1) 118 | tx = await client.get_transaction(txid) 119 | assert tx, "No transaction on client" 120 | assert "data" in tx, "tx is not on chain" 121 | print(tx) 122 | # TODO check balance again 123 | 124 | 125 | @pytest.mark.asyncio 126 | async def test_multi_transfer_order(wallet, wallet_two, client): 127 | address_1 = wallet.get_address() 128 | address_2 = wallet_two.get_address() 129 | account_1 = await client.get_account(address_1) 130 | account_2 = await client.get_account(address_2) 131 | TOKEN_1 = "BNB" 132 | TOKEN_2 = "BTC-531" 133 | AMOUNT = 0.001 134 | transfers = [ 135 | {"symbol": TOKEN_1, "amount": AMOUNT}, 136 | {"symbol": TOKEN_2, "amount": AMOUNT}, 137 | ] 138 | transaction = await Transaction.multi_transfer_transaction( 139 | from_address=address_2, to_address=address_1, transfers=transfers, client=client 140 | ) 141 | pubkey, signature = wallet_two.sign(transaction.get_sign_message()) 142 | hex_data = transaction.update_signature(pubkey, signature) 143 | broadcast = await client.broadcast(hex_data) 144 | assert broadcast, "Fail to broadcast" 145 | assert "hash" in broadcast[0], "No txid" 146 | txid = broadcast[0]["hash"] 147 | await asyncio.sleep(1) 148 | tx = await client.get_transaction(txid) 149 | assert tx, "No transaction on client" 150 | assert "data" in tx, "tx is not on chain" 151 | print(tx) 152 | 153 | 154 | @pytest.mark.asyncio 155 | async def test_freeze_token(wallet, client): 156 | address = wallet.get_address() 157 | transaction = await Transaction.freeze_token_transaction( 158 | address=address, symbol="BNB", amount=0.0001, client=client 159 | ) 160 | pubkey, signature = wallet.sign(transaction.get_sign_message()) 161 | hex_data = transaction.update_signature(pubkey, signature) 162 | broadcast = await client.broadcast(hex_data) 163 | txid = broadcast[0]["hash"] 164 | await asyncio.sleep(1) 165 | tx = await client.get_transaction(txid) 166 | print(tx) 167 | 168 | 169 | @pytest.mark.asyncio 170 | async def test_unfreeze_token(wallet, client): 171 | address = wallet.get_address() 172 | transaction = await Transaction.unfreeze_token_transaction( 173 | address=address, symbol="BNB", amount=0.0001, client=client 174 | ) 175 | pubkey, signature = wallet.sign(transaction.get_sign_message()) 176 | hex_data = transaction.update_signature(pubkey, signature) 177 | broadcast = await client.broadcast(hex_data) 178 | txid = broadcast[0]["hash"] 179 | await asyncio.sleep(1) 180 | tx = await client.get_transaction(txid) 181 | 182 | 183 | @pytest.mark.asyncio 184 | async def test_transaction_object_new_order(wallet, client): 185 | address = wallet.get_address() 186 | transaction = Transaction(wallet=wallet, client=client) 187 | new_order = await transaction.create_new_order( 188 | symbol=PAIR, 189 | side=Side.BUY, 190 | timeInForce=Timeinforce.GTE, 191 | price=0.01, 192 | quantity=1, 193 | ordertype=Ordertype.LIMIT, 194 | ) 195 | assert new_order, "No result of new_order found" 196 | assert new_order[0]["hash"], "No txid found" 197 | await asyncio.sleep(1) 198 | tx = await client.get_transaction(new_order[0]["hash"]) 199 | assert tx, "Transaction not on chain" 200 | 201 | 202 | @pytest.mark.asyncio 203 | async def test_transaction_object_cancel(wallet, client): 204 | address = wallet.get_address() 205 | open_orders = await client.get_open_orders(address) 206 | order = open_orders["order"][0] 207 | refid = order["orderId"] 208 | symbol = order["symbol"] 209 | transaction = Transaction(wallet=wallet, client=client) 210 | cancel_order = await transaction.cancel_order(symbol=symbol, refid=refid) 211 | print(cancel_order) 212 | assert cancel_order, "No result of cancel found" 213 | assert cancel_order[0]["hash"], "No txid found" 214 | await asyncio.sleep(1) 215 | tx = await client.get_transaction(cancel_order[0]["hash"]) 216 | assert tx, "Transaction not on chain" 217 | 218 | 219 | @pytest.mark.asyncio 220 | async def test_transaction_object_transfer(wallet, wallet_two, client): 221 | transaction = Transaction(wallet=wallet, client=client) 222 | transfer = await transaction.transfer( 223 | to_address=wallet_two.get_address(), symbol="BNB", amount=0.1 224 | ) 225 | assert transfer, "No result of transfer found" 226 | assert transfer[0]["hash"], "No txid found" 227 | await asyncio.sleep(1) 228 | tx = await client.get_transaction(transfer[0]["hash"]) 229 | assert tx, "Transaction not on chain" 230 | 231 | 232 | @pytest.mark.asyncio 233 | async def test_multi_transaction_object_transfer(wallet, wallet_two, client): 234 | transaction = Transaction(wallet=wallet_two, client=client) 235 | TOKEN_1 = "BNB" 236 | TOKEN_2 = "BTC-531" 237 | AMOUNT = 0.002 238 | transfers = [ 239 | {"symbol": TOKEN_1, "amount": AMOUNT}, 240 | {"symbol": TOKEN_2, "amount": AMOUNT}, 241 | ] 242 | transfer = await transaction.multi_transfer( 243 | to_address=wallet.get_address(), transfers=transfers 244 | ) 245 | assert transfer, "No result of transfer found" 246 | assert transfer[0]["hash"], "No txid found" 247 | await asyncio.sleep(1) 248 | tx = await client.get_transaction(transfer[0]["hash"]) 249 | assert tx, "Transaction not on chain" 250 | 251 | 252 | @pytest.mark.asyncio 253 | async def test_transaction_object_freeze(wallet, client): 254 | transaction = Transaction(wallet=wallet, client=client) 255 | freeze = await transaction.freeze_token(symbol="BNB", amount=0.1) 256 | print(freeze) 257 | assert freeze, "No result of freeze found" 258 | assert freeze[0]["hash"], "No txid found" 259 | await asyncio.sleep(1) 260 | tx = await client.get_transaction(freeze[0]["hash"]) 261 | assert tx, "Transaction not on chain" 262 | 263 | 264 | @pytest.mark.asyncio 265 | async def test_transaction_object_unfreeze(wallet, client): 266 | transaction = Transaction(wallet=wallet, client=client) 267 | unfreeze = await transaction.unfreeze_token(symbol="BNB", amount=0.1) 268 | print(unfreeze) 269 | assert unfreeze, "No result of unfreeze found" 270 | assert unfreeze[0]["hash"], "No txid found" 271 | await asyncio.sleep(1) 272 | tx = await client.get_transaction(unfreeze[0]["hash"]) 273 | assert tx, "Transaction not on chain" 274 | 275 | 276 | @pytest.mark.asyncio 277 | async def test_transaction_object_burn(wallet_two, client): 278 | transaction = Transaction(wallet=wallet_two, client=client) 279 | burn = await transaction.burn_token(symbol="BTC-531", amount=1) 280 | print(burn) 281 | assert burn, "No result of unfreeze found" 282 | assert burn[0]["hash"], "No txid found" 283 | await asyncio.sleep(1) 284 | tx = await client.get_transaction(burn[0]["hash"]) 285 | assert tx, "Transaction not on chain" 286 | print(tx) 287 | 288 | 289 | @pytest.mark.asyncio 290 | async def test_issue_token(wallet_two, client): 291 | address = wallet_two.get_address() 292 | transaction = await Transaction.issue_token_transaction( 293 | owner=address, 294 | name="Bitcoin", 295 | symbol="BTC", 296 | supply=1000000000000000, 297 | mintable=True, 298 | client=client, 299 | ) 300 | pubkey, signature = wallet_two.sign(transaction.get_sign_message()) 301 | hex_data = transaction.update_signature(pubkey, signature) 302 | print(binascii.hexlify(transaction.stdTx)) 303 | broadcast = await client.broadcast(hex_data) 304 | assert broadcast, "No result of issue found" 305 | assert broadcast[0]["hash"], "No txid found" 306 | # TODO need 400 BNB on testnet to issue 307 | # txid = broadcast[0]["hash"] 308 | # await asyncio.sleep(1) 309 | # tx = await client.get_transaction(txid) 310 | # print(tx) 311 | 312 | 313 | @pytest.mark.asyncio 314 | async def test_mint_token(wallet_two, client): 315 | address = wallet_two.get_address() 316 | transaction = await Transaction.mint_token_transaction( 317 | owner=address, symbol="BTC-531", amount=10000, client=client 318 | ) 319 | pubkey, signature = wallet_two.sign(transaction.get_sign_message()) 320 | hex_data = transaction.update_signature(pubkey, signature) 321 | print(binascii.hexlify(transaction.stdTx)) 322 | broadcast = await client.broadcast(hex_data) 323 | assert broadcast, "No result of mint found" 324 | assert broadcast[0]["hash"], "No txid found" 325 | # TODO need 200 BNB on testnet to mint 326 | # txid = broadcast[0]["hash"] 327 | # await asyncio.sleep(1) 328 | # tx = await client.get_transaction(txid) 329 | # print(tx) 330 | 331 | 332 | @pytest.mark.asyncio 333 | async def test_burn_token(wallet_two, client): 334 | address = wallet_two.get_address() 335 | print(address) 336 | transaction = await Transaction.burn_token_transaction( 337 | owner=address, symbol="BTC-531", amount=1, client=client 338 | ) 339 | pubkey, signature = wallet_two.sign(transaction.get_sign_message()) 340 | hex_data = transaction.update_signature(pubkey, signature) 341 | print(binascii.hexlify(transaction.stdTx)) 342 | broadcast = await client.broadcast(hex_data) 343 | txid = broadcast[0]["hash"] 344 | await asyncio.sleep(1) 345 | tx = await client.get_transaction(txid) 346 | print(tx) 347 | 348 | 349 | @pytest.mark.asyncio 350 | async def test_vote_token(wallet, client): 351 | address = wallet.get_address() 352 | transaction = await Transaction.vote_transaction( 353 | voter=address, proposal_id=PROPOSAL_ID, option=Votes.YES, client=client 354 | ) 355 | pubkey, signature = wallet.sign(transaction.get_sign_message()) 356 | hex_data = transaction.update_signature(pubkey, signature) 357 | broadcast = await client.broadcast(hex_data) 358 | assert broadcast, "No result of vote found" 359 | assert broadcast[0]["hash"], "No txid found" 360 | # TODO need to be validator to vote 361 | # txid = broadcast[0]["hash"] 362 | # await asyncio.sleep(1) 363 | # tx = await client.get_transaction(txid) 364 | # print(tx) 365 | -------------------------------------------------------------------------------- /test/test_wallet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | """ 4 | Binance DEX SDK Test Suite for Wallet builder 5 | """ 6 | from pprint import pprint 7 | 8 | import pytest 9 | import asyncio 10 | import json 11 | from decimal import Decimal 12 | from binancechain import Wallet, HTTPClient 13 | 14 | MNEMONIC_2 = "tennis utility midnight pattern that foot security tent punch glance still night virus loop trade velvet rent glare ramp cushion defy grass section cage" 15 | MNEMONIC = "apart conduct congress bless remember picnic aerobic nothing dinner guilt catch brain sunny vocal advice castle horror shift reject valley evoke fork syrup code" 16 | PASSWORD = "Alkseui12p,d" 17 | KEYSTORE = { 18 | "address": "309ac34e39715899735bfaed1677ff82b5bf35e0", 19 | "crypto": { 20 | "cipher": "aes-128-ctr", 21 | "cipherparams": {"iv": "bab8a3ace08205c897a8a69ddb9bf056"}, 22 | "ciphertext": "6b273686d7858feac0708d64acdd9bdb353191d4f735e8c04a11ee3a31f08f0f", 23 | "kdf": "pbkdf2", 24 | "kdfparams": { 25 | "c": 1000000, 26 | "dklen": 32, 27 | "prf": "hmac-sha256", 28 | "salt": "ed2e6c05ec3e5e49c3519a07d0572cf0", 29 | }, 30 | "mac": "f11eb5068e3e697c1eeeccd4c5f167c6fc0956f40b3f0cb1588ccd4f0b9d7a13", 31 | }, 32 | "id": "a0c93b32-6a63-4346-aafa-afdc2b5637dd", 33 | "version": 3, 34 | } 35 | 36 | PRIVATEKEY = "14ffce9d61a219f3b24c0a093a24837368e2317b1e96d77f80505df49c2224c3" 37 | ADDRESS = "bnb1r5jc35v338tlphnjx65wy7tecm6vm82t87qjt0" 38 | TRANSACTION_MSG = b'{"account_number":"668107","chain_id":"Binance-Chain-Nile","data":null,"memo":"","msgs":[{"inputs":[{"address":"tbnb1r5jc35v338tlphnjx65wy7tecm6vm82tftfkt7","coins":[{"amount":10000000,"denom":"BNB"}]}],"outputs":[{"address":"tbnb1nhvpuq0u5pgpry0x2ap2hqv9n5jfkj90eps6qx","coins":[{"amount":10000000,"denom":"BNB"}]}]}],"sequence":"35","source":"1"}' 39 | 40 | 41 | @pytest.fixture 42 | async def chain(): 43 | chain = HTTPClient(testnet=True) 44 | yield chain 45 | await chain.close() 46 | 47 | 48 | @pytest.fixture 49 | async def wallet(): 50 | wallet = Wallet.wallet_from_privatekey(privatekey=PRIVATEKEY, password="") 51 | yield wallet 52 | 53 | 54 | @pytest.mark.asyncio 55 | async def test_create_wallet(chain): 56 | wallet = Wallet.create_wallet() 57 | address = wallet.get_address() 58 | print(wallet) 59 | assert address, "Failed to get address from wallet" 60 | 61 | 62 | @pytest.mark.asyncio 63 | async def test_create_wallet_mnemonic(chain): 64 | wallet = Wallet.create_wallet_mnemonic(password=PASSWORD) 65 | address = wallet.get_address() 66 | assert address, "Failed to get address from wallet" 67 | 68 | 69 | @pytest.mark.asyncio 70 | async def test_create_keystore(chain): 71 | keystore = Wallet.create_keystore(password=PASSWORD) 72 | assert keystore, "Failed to create keystore" 73 | 74 | 75 | @pytest.mark.asyncio 76 | async def test_wallet_from_keystore(chain): 77 | keystore = Wallet.create_keystore(password=PASSWORD) 78 | assert keystore, "Failed to create keystore" 79 | wallet = Wallet.wallet_from_keystore(keystore=keystore, password=PASSWORD) 80 | address = wallet.get_address() 81 | assert address, "Failed to get address from recovered wallet" 82 | 83 | 84 | @pytest.mark.asyncio 85 | async def test_not_recover_wallet_keystore_with_wrong_password(chain): 86 | keystore = Wallet.create_keystore(password=PASSWORD) 87 | assert keystore, "Failed to create keystore" 88 | try: 89 | wallet = Wallet.wallet_from_keystore(keystore=keystore, password="") 90 | assert False 91 | except ValueError as e: 92 | print(e) 93 | assert True 94 | 95 | 96 | @pytest.mark.asyncio 97 | async def test_wallet_from_mnemonic(chain): 98 | wallet = Wallet.wallet_from_mnemonic(MNEMONIC, password="") 99 | address = wallet.get_address() 100 | privatekey = wallet.get_privatekey() 101 | assert address, "Failed to get address from wallet" 102 | assert privatekey, "Failed to get private key from wallet" 103 | assert privatekey == PRIVATEKEY, "Failed to get private key from wallet" 104 | assert address == ADDRESS, "Not getting the same address from recover" 105 | 106 | 107 | @pytest.mark.asyncio 108 | async def test_wallet_from_privatekey(chain): 109 | wallet = Wallet.wallet_from_privatekey(privatekey=PRIVATEKEY, password="") 110 | address = wallet.get_address() 111 | assert address, "Failed to get address from wallet" 112 | assert address == ADDRESS, "Not getting the same address from recover" 113 | 114 | 115 | @pytest.mark.asyncio 116 | async def test_signing_msg(wallet, chain): 117 | pub, sig = wallet.sign(TRANSACTION_MSG) 118 | assert pub, "No public key return" 119 | assert sig, "No signature return" 120 | 121 | 122 | @pytest.mark.asyncio 123 | async def test_verify_signature(chain, wallet): 124 | pub, sig = wallet.sign(TRANSACTION_MSG) 125 | valid = wallet.verify_signature(TRANSACTION_MSG, signature=sig) 126 | assert pub, "No public key return" 127 | assert sig, "No signature return" 128 | assert valid, "Invalid signature" 129 | -------------------------------------------------------------------------------- /test/test_websocket.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Luke Macken, Kim Bui, and the binance-chain-python contributors 2 | # SPDX-License-Identifier: MIT 3 | """ 4 | Binance DEX WebSocket Test Suite 5 | """ 6 | import asyncio 7 | 8 | import pytest 9 | 10 | from binancechain import HTTPClient, WebSocket 11 | 12 | 13 | def on_error(msg): 14 | print(f'Error: {msg}') 15 | 16 | 17 | @pytest.fixture 18 | async def client(): 19 | # If we create fresh websockets too fast it may error? 20 | await asyncio.sleep(1) 21 | client = WebSocket(testnet=True) 22 | yield client 23 | client.close() 24 | 25 | 26 | @pytest.fixture 27 | async def symbols(): 28 | symbols = [] 29 | rest = HTTPClient(testnet=True) 30 | markets = await rest.get_markets() 31 | for market in markets: 32 | symbol = f"{market['base_asset_symbol']}_{market['quote_asset_symbol']}" 33 | symbols.append(symbol) 34 | yield symbols 35 | await rest.close() 36 | 37 | 38 | @pytest.mark.asyncio 39 | async def test_open_close(client): 40 | """"Open then immediately close""" 41 | def on_open(): 42 | print('opened') 43 | client.close() 44 | 45 | await client.start_async(on_open=on_open, on_error=on_error) 46 | print('closed') 47 | 48 | 49 | @pytest.mark.asyncio 50 | async def test_trades(client, symbols): 51 | print(symbols) 52 | results = [] 53 | 54 | def callback(msg): 55 | results.append(msg) 56 | client.close() 57 | 58 | def on_open(): 59 | client.subscribe_trades(symbols=symbols, callback=callback) 60 | 61 | await client.start_async(on_open=on_open, on_error=on_error) 62 | 63 | result = results[0] 64 | assert result['stream'] == 'trades' 65 | 66 | 67 | @pytest.mark.asyncio 68 | async def test_market_diff(client, symbols): 69 | results = [] 70 | 71 | def callback(msg): 72 | results.append(msg) 73 | client.close() 74 | 75 | def on_open(): 76 | client.subscribe_market_diff(symbols=symbols, callback=callback) 77 | 78 | await client.start_async(on_open=on_open, on_error=on_error) 79 | 80 | result = results[0] 81 | assert result['stream'] == 'marketDiff' 82 | 83 | 84 | @pytest.mark.asyncio 85 | async def test_market_depth(client, symbols): 86 | results = [] 87 | 88 | def callback(msg): 89 | results.append(msg) 90 | client.close() 91 | 92 | def on_open(): 93 | client.subscribe_market_depth(symbols=symbols, callback=callback) 94 | 95 | await client.start_async(on_open=on_open, on_error=on_error) 96 | 97 | result = results[0] 98 | assert result['stream'] == 'marketDepth' 99 | 100 | 101 | @pytest.mark.asyncio 102 | async def test_kline(client, symbols): 103 | results = [] 104 | 105 | def callback(msg): 106 | results.append(msg) 107 | client.close() 108 | 109 | def on_open(): 110 | client.subscribe_kline(interval='1m', symbols=symbols, callback=callback) 111 | 112 | await client.start_async(on_open=on_open, on_error=on_error) 113 | 114 | result = results[0] 115 | assert result['stream'] == 'kline_1m' 116 | 117 | 118 | @pytest.mark.asyncio 119 | async def test_tickers(client, symbols): 120 | results = [] 121 | 122 | def callback(msg): 123 | results.append(msg) 124 | client.close() 125 | 126 | def on_open(): 127 | client.subscribe_ticker(symbols=symbols, callback=callback) 128 | 129 | await client.start_async(on_open=on_open, on_error=on_error) 130 | 131 | result = results[0] 132 | assert result['stream'] == 'ticker' 133 | 134 | 135 | @pytest.mark.asyncio 136 | async def test_all_tickers(client): 137 | results = [] 138 | 139 | def callback(msg): 140 | results.append(msg) 141 | client.close() 142 | 143 | def on_open(): 144 | client.subscribe_all_tickers(callback=callback) 145 | 146 | await client.start_async(on_open=on_open, on_error=on_error) 147 | 148 | result = results[0] 149 | assert result['stream'] == 'allTickers' 150 | 151 | 152 | @pytest.mark.asyncio 153 | async def test_mini_ticker(client, symbols): 154 | results = [] 155 | 156 | def callback(msg): 157 | results.append(msg) 158 | client.close() 159 | 160 | def on_open(): 161 | client.subscribe_mini_ticker(symbols=symbols, callback=callback) 162 | 163 | await client.start_async(on_open=on_open, on_error=on_error) 164 | 165 | result = results[0] 166 | assert result['stream'] == 'miniTicker' 167 | 168 | 169 | @pytest.mark.asyncio 170 | async def test_all_mini_ticker(client, symbols): 171 | results = [] 172 | 173 | def callback(msg): 174 | results.append(msg) 175 | client.close() 176 | 177 | def on_open(): 178 | client.subscribe_all_mini_tickers(callback=callback) 179 | 180 | await client.start_async(on_open=on_open, on_error=on_error) 181 | 182 | result = results[0] 183 | assert result['stream'] == 'allMiniTickers' 184 | 185 | 186 | @pytest.mark.asyncio 187 | async def test_blockheight(client): 188 | results = [] 189 | 190 | def callback(msg): 191 | results.append(msg) 192 | client.close() 193 | 194 | def on_open(): 195 | client.subscribe_blockheight(callback=callback) 196 | 197 | await client.start_async(on_open=on_open, on_error=on_error) 198 | 199 | result = results[0] 200 | assert 'stream' in result 201 | 202 | 203 | @pytest.mark.asyncio 204 | async def test_keepalive(client): 205 | def on_open(): 206 | client.keepalive() 207 | client.close() 208 | await client.start_async(on_open=on_open, on_error=on_error) 209 | 210 | 211 | @pytest.mark.asyncio 212 | async def test_unsubscribe(client): 213 | results = [] 214 | 215 | def callback(msg): 216 | results.append(msg) 217 | client.unsubscribe("blockheight") 218 | client.close() 219 | 220 | def on_open(): 221 | client.subscribe_blockheight(callback=callback) 222 | 223 | await client.start_async(on_open=on_open, on_error=on_error) 224 | assert results 225 | 226 | 227 | @pytest.mark.asyncio 228 | async def test_decorator(client): 229 | @client.on('open') 230 | def callback(): 231 | client.close() 232 | await client.start_async() 233 | 234 | 235 | @pytest.mark.asyncio 236 | async def test_decorator_async(client): 237 | @client.on('open') 238 | async def callback(): 239 | client.close() 240 | await client.start_async() 241 | 242 | 243 | @pytest.mark.asyncio 244 | async def test_decorator_sub_queue(client): 245 | results = [] 246 | 247 | @client.on("allTickers", symbols=["$all"]) 248 | async def callback(msg): 249 | results.append(msg) 250 | client.close() 251 | 252 | await client.start_async() 253 | assert results 254 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36,py37 3 | 4 | [testenv] 5 | deps = 6 | pytest-asyncio 7 | pytest-cov 8 | aiohttp 9 | commands = pytest 10 | --------------------------------------------------------------------------------