From 2c2a15007e82bec5dc6e69d4eb208f1f2eec5672 Mon Sep 17 00:00:00 2001 From: taobun Date: Mon, 14 Sep 2020 17:38:12 +0700 Subject: [PATCH 1/3] Add transaction module --- helpers/pyband/pyband/client.py | 14 ++++- helpers/pyband/pyband/data.py | 11 +++- helpers/pyband/pyband/transaction.py | 84 ++++++++++++++++++++++++++++ helpers/pyband/pyband/wallet.py | 8 +++ 4 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 helpers/pyband/pyband/transaction.py diff --git a/helpers/pyband/pyband/client.py b/helpers/pyband/pyband/client.py index d90d9dfa2b..14e10210b5 100644 --- a/helpers/pyband/pyband/client.py +++ b/helpers/pyband/pyband/client.py @@ -1,7 +1,7 @@ import requests from dacite import from_dict -from .data import DataSource, OracleScript, RequestInfo, DACITE_CONFIG +from .data import Account, DataSource, OracleScript, RequestInfo, DACITE_CONFIG class Client(object): @@ -11,13 +11,23 @@ def __init__(self, rpc_url: str) -> None: def _get(self, path, **kwargs): return requests.get(self.rpc_url + path, **kwargs).json()["result"] + def send_tx(self, data: dict) -> dict: + return requests.post(self.rpc_url + "/txs", json=data).json() + def get_chain_id(self) -> str: - genesis = self._get("/bandchain/genesis") + genesis = requests.get(self.rpc_url + "/bandchain/genesis").json() return genesis["chain_id"] def get_latest_block(self) -> dict: return self._get("/blocks/latest") + def get_account(self, address: str) -> Account: + return from_dict( + data_class=Account, + data=self._get("/auth/accounts/{}".format(address))["value"], + config=DACITE_CONFIG, + ) + def get_data_source(self, id: int) -> DataSource: return from_dict( data_class=DataSource, diff --git a/helpers/pyband/pyband/data.py b/helpers/pyband/pyband/data.py index 23d4ebeafe..5923f18ec2 100644 --- a/helpers/pyband/pyband/data.py +++ b/helpers/pyband/pyband/data.py @@ -1,6 +1,6 @@ import base64 from dataclasses import dataclass -from typing import List +from typing import List, Optional from dacite import Config @@ -89,3 +89,12 @@ class RequestInfo(object): reports: List[Report] result: Result + +@dataclass +class Account(object): + address: str + coins: List[dict] + public_key: Optional[dict] + account_number: int + sequence: int + diff --git a/helpers/pyband/pyband/transaction.py b/helpers/pyband/pyband/transaction.py new file mode 100644 index 0000000000..5c4c7ad26f --- /dev/null +++ b/helpers/pyband/pyband/transaction.py @@ -0,0 +1,84 @@ +import base64 +import json + +from .wallet import PrivateKey +from .client import Client + + +class Transaction: + def __init__(self, client: Client, privkey: PrivateKey): + self.client = client + self.privkey = privkey + self.msgs: list = [] + self.chain_id = client.get_chain_id() + + def send_tx( + self, + account_num: int, + sequence: int, + gas: int = 200000, + fee: int = 0, + memo: str = "", + mode="sync", + ) -> dict: + base64_pubkey = base64.b64encode(bytes.fromhex(self.privkey.to_pubkey().to_hex())).decode( + "utf-8" + ) + tx = { + "tx": { + "msg": self.msgs, + "fee": {"gas": str(gas), "amount": [{"denom": "uband", "amount": str(fee)}]}, + "memo": memo, + "signatures": [ + { + "signature": self._sign(account_num, sequence, gas, fee, memo), + "pub_key": {"type": "tendermint/PubKeySecp256k1", "value": base64_pubkey,}, + "account_number": str(account_num), + "sequence": str(sequence), + } + ], + }, + "mode": mode, + } + return self.client.send_tx(tx) + + def add_request( + self, oracle_script_id: int, calldata: bytes, ask_count: int, min_count: int, client_id: str + ) -> None: + msg = { + "type": "oracle/Request", + "value": { + "oracle_script_id": str(oracle_script_id), + "calldata": base64.b64encode(calldata).decode("utf-8"), + "ask_count": str(ask_count), + "min_count": str(min_count), + "client_id": client_id, + "sender": self.privkey.to_pubkey().to_address().to_acc_bech32(), + }, + } + self.msgs.append(msg) + + def _sign(self, account_num: int, sequence: int, gas: int, fee: int, memo: str) -> str: + message_str = json.dumps( + self._get_sign_message(account_num, sequence, gas, fee, memo), + separators=(",", ":"), + sort_keys=True, + ) + message_bytes = message_str.encode("utf-8") + + signature = self.privkey.sign(message_bytes) + + signature_base64_str = base64.b64encode(signature).decode("utf-8") + return signature_base64_str + + def _get_sign_message( + self, account_num: int, sequence: int, gas: int, fee: int, memo: str + ) -> dict: + return { + "chain_id": self.chain_id, + "account_number": str(account_num), + "fee": {"gas": str(gas), "amount": [{"amount": str(fee), "denom": "uband"}]}, + "memo": memo, + "sequence": str(sequence), + "msgs": self.msgs, + } diff --git a/helpers/pyband/pyband/wallet.py b/helpers/pyband/pyband/wallet.py index 56ba4bdf04..34e8b3c424 100644 --- a/helpers/pyband/pyband/wallet.py +++ b/helpers/pyband/pyband/wallet.py @@ -69,6 +69,14 @@ def from_mnemonic(cls, words: str, path="m/44'/494'/0'/0/0") -> PrivateKey: ) return self + @classmethod + def from_hex(cls, priv: str) -> PrivateKey: + self = cls(_error_do_not_use_init_directly=True) + self.signing_key = SigningKey.from_string( + bytes.fromhex(priv), curve=SECP256k1, hashfunc=hashlib.sha256, + ) + return self + def to_hex(self) -> str: """ Return a hex representation of signing key. From 676c4a95996122238d1113f498e21061932458a3 Mon Sep 17 00:00:00 2001 From: taobun Date: Mon, 14 Sep 2020 17:41:11 +0700 Subject: [PATCH 2/3] add changelog --- CHANGELOG_UNRELEASED.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG_UNRELEASED.md b/CHANGELOG_UNRELEASED.md index 1ea732d482..b7f7af4df4 100644 --- a/CHANGELOG_UNRELEASED.md +++ b/CHANGELOG_UNRELEASED.md @@ -76,6 +76,7 @@ ### Helpers +- (feat) [\#2640](https://github.com/bandprotocol/bandchain/pull/2640) pyband: Transaction module implementation - (feat) [\#2588](https://github.com/bandprotocol/bandchain/pull/2588) pyband: Auth module implementation - (impv) [\#2577](https://github.com/bandprotocol/bandchain/pull/2577) Wallet module completion From 71ef45e1d3323120359271dc253a38e8e2ae73a9 Mon Sep 17 00:00:00 2001 From: taobun Date: Mon, 14 Sep 2020 17:43:52 +0700 Subject: [PATCH 3/3] update setup.py for new release --- helpers/pyband/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/pyband/setup.py b/helpers/pyband/setup.py index 02f340db64..ba76d49f62 100644 --- a/helpers/pyband/setup.py +++ b/helpers/pyband/setup.py @@ -3,7 +3,7 @@ setup( name="pyband", packages=["pyband"], - version="0.0.3", + version="0.0.4", license="MIT", description="Python library for BandChain", author="Band Protocol",