├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── PythonMiddleware ├── __init__.py ├── account.py ├── aes.py ├── amount.py ├── asset.py ├── block.py ├── blockchain.py ├── committee.py ├── contract.py ├── dex.py ├── exceptions.py ├── extensions.py ├── graphene.py ├── instance.py ├── market.py ├── memo.py ├── notify.py ├── price.py ├── proposal.py ├── storage.py ├── transactionbuilder.py ├── utils.py ├── vesting.py ├── wallet.py ├── witness.py └── worker.py ├── PythonMiddlewareapi ├── __init__.py ├── exceptions.py ├── graphenenoderpc.py └── websocket.py ├── PythonMiddlewarebase ├── __init__.py ├── account.py ├── asset_permissions.py ├── base58.py ├── baseaccount.py ├── baseobjects.py ├── basesignedtransactions.py ├── bip38.py ├── chains.py ├── dictionary.py ├── memo.py ├── objects.py ├── objecttypes.py ├── operationids.py ├── operations.py ├── signedtransactions.py ├── transactions.py └── types.py ├── README.md ├── README_cn.md ├── setup.cfg ├── setup.py └── test ├── account.py ├── contract.py ├── info.py ├── nh_asset.py └── unittest ├── account_test.py ├── asset_test.py ├── balance_claim_test.py ├── committee_test.py ├── config.py ├── contract_test.py ├── crontab_test.py ├── file_test.py ├── gas_test.py ├── nh_asset_test.py ├── vesting_balance_test.py ├── witness_test.py └── worker_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | .eggs/ 2 | PythonMiddleware.egg-info/ 3 | build/ 4 | dist/ 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Fabian Schuh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md 2 | -------------------------------------------------------------------------------- /PythonMiddleware/__init__.py: -------------------------------------------------------------------------------- 1 | from .graphene import Graphene 2 | 3 | __all__ = [ 4 | "graphene", 5 | "aes", 6 | "account", 7 | "amount", 8 | "asset", 9 | "block", 10 | "blockchain", 11 | "dex", 12 | "market", 13 | "storage", 14 | "price", 15 | "utils", 16 | "wallet", 17 | "committee", 18 | "vesting", 19 | "proposal", 20 | ] 21 | -------------------------------------------------------------------------------- /PythonMiddleware/account.py: -------------------------------------------------------------------------------- 1 | from .instance import shared_graphene_instance 2 | from .exceptions import AccountDoesNotExistsException 3 | 4 | 5 | class Account(dict): 6 | """ This class allows to easily access Account data 7 | 8 | :param str account_name: Name of the account 9 | :param graphene.graphene.graphene graphene_instance: graphene instance 10 | :param bool lazy: Use lazy loading 11 | :param bool full: Obtain all account data including orders, positions, etc. 12 | :returns: Account data 13 | :rtype: dictionary 14 | :raises graphene.exceptions.AccountDoesNotExistsException: if account does not exist 15 | 16 | Instances of this class are dictionaries that come with additional 17 | methods (see below) that allow dealing with an account and it's 18 | corresponding functions. 19 | 20 | .. code-block:: python 21 | 22 | from graphene.account import Account 23 | account = Account("init0") 24 | print(account) 25 | 26 | .. note:: This class comes with its own caching function to reduce the 27 | load on the API server. Instances of this class can be 28 | refreshed with ``Account.refresh()``. 29 | 30 | """ 31 | 32 | accounts_cache = dict() 33 | 34 | def __init__( 35 | self, 36 | account, 37 | lazy=False, 38 | full=False, 39 | graphene_instance=None 40 | ): 41 | self.cached = False 42 | self.full = full 43 | self.graphene = graphene_instance or shared_graphene_instance() 44 | 45 | if isinstance(account, Account): 46 | super(Account, self).__init__(account) 47 | self.name = account["name"] 48 | elif isinstance(account, str): 49 | self.name = account.strip().lower() 50 | else: 51 | raise ValueError("Account() expects an account name, id or an instance of Account") 52 | 53 | if self.name in Account.accounts_cache and not self.full: 54 | super(Account, self).__init__(Account.accounts_cache[self.name]) 55 | self.cached = True 56 | elif not lazy and not self.cached: 57 | self.refresh() 58 | self.cached = True 59 | 60 | def refresh(self): 61 | """ Refresh/Obtain an account's data from the API server 62 | """ 63 | import re 64 | if re.match(r"^1\.2\.[0-9]*$", self.name): 65 | account = self.graphene.rpc.get_objects([self.name])[0] 66 | else: 67 | account = self.graphene.rpc.lookup_account_names([self.name])[0] 68 | if not account: 69 | raise AccountDoesNotExistsException(self.name) 70 | 71 | if self.full: 72 | account = self.graphene.rpc.get_full_accounts([account["id"]], False)[0][1] 73 | super(Account, self).__init__(account["account"]) 74 | self._cache(account["account"]) 75 | for k, v in account.items(): 76 | if k != "account": 77 | self[k] = v 78 | else: 79 | super(Account, self).__init__(account) 80 | self._cache(account) 81 | self.cached = True 82 | self.name = self["name"] 83 | 84 | def _cache(self, account): 85 | # store in cache 86 | Account.accounts_cache[account["name"]] = account 87 | 88 | def __getitem__(self, key): 89 | if not self.cached: 90 | self.refresh() 91 | return super(Account, self).__getitem__(key) 92 | 93 | def __repr__(self): 94 | return " 0 117 | ] 118 | 119 | def balance(self, symbol): 120 | """ Obtain the balance of a specific Asset. This call returns instances of 121 | :class:`graphene.amount.Amount`. 122 | """ 123 | from .amount import Amount 124 | if isinstance(symbol, dict) and "symbol" in symbol: 125 | symbol = symbol["symbol"] 126 | balances = self.balances 127 | for b in balances: 128 | if b["symbol"] == symbol: 129 | return b 130 | return Amount(0, symbol) 131 | 132 | @property 133 | def callpositions(self): 134 | """ List call positions (collateralized positions :doc:`mpa`) 135 | """ 136 | if not self.full: 137 | self.full = True 138 | self.refresh() 139 | from .dex import Dex 140 | dex = Dex(graphene_instance=self.graphene) 141 | return dex.list_debt_positions(self) 142 | 143 | @property 144 | def call_positions(self): 145 | """ Alias for :func:graphene.account.Account.callpositions 146 | """ 147 | return self.callpositions 148 | 149 | @property 150 | def openorders(self): 151 | """ Returns open Orders 152 | """ 153 | from .price import Order 154 | if not self.full: 155 | self.full = True 156 | self.refresh() 157 | return [Order(o) for o in self["limit_orders"]] 158 | 159 | def history( 160 | self, first=None, 161 | last=0, limit=1000, 162 | only_ops=[], exclude_ops=[] 163 | ): 164 | """ Returns a generator for individual account transactions. The 165 | latest operation will be first. This call can be used in a 166 | ``for`` loop. 167 | 168 | :param int first: sequence number of the first transaction to return (*optional*) 169 | :param int limit: limit number of transactions to return (*optional*) 170 | :param array only_ops: Limit generator by these operations (*optional*) 171 | :param array exclude_ops: Exclude thse operations from generator (*optional*) 172 | """ 173 | from PythonMiddlewarebase.operations import getOperationNameForId 174 | _limit = 1000 175 | cnt = 0 176 | 177 | mostrecent = self.graphene.rpc.get_account_history( 178 | self["id"], 179 | "1.11.{}".format(0), 180 | 1, 181 | "1.11.{}".format(9999999999999), 182 | api="history" 183 | ) 184 | if not mostrecent: 185 | raise StopIteration 186 | 187 | if not first: 188 | # first = int(mostrecent[0].get("id").split(".")[2]) + 1 189 | first = 9999999999 190 | 191 | while True: 192 | # RPC call 193 | txs = self.graphene.rpc.get_account_history( 194 | self["id"], 195 | "1.11.{}".format(last), 196 | _limit, 197 | "1.11.{}".format(first - 1), 198 | api="history" 199 | ) 200 | for i in txs: 201 | if exclude_ops and getOperationNameForId(i["op"][0]) in exclude_ops: 202 | continue 203 | if not only_ops or getOperationNameForId(i["op"][0]) in only_ops: 204 | cnt += 1 205 | yield i 206 | if limit >= 0 and cnt >= limit: 207 | raise StopIteration 208 | 209 | if not txs: 210 | break 211 | if len(txs) < _limit: 212 | break 213 | first = int(txs[-1]["id"].split(".")[2]) 214 | 215 | def upgrade(self): 216 | return self.graphene.upgrade_account(account=self) 217 | 218 | 219 | class AccountUpdate(dict): 220 | """ This purpose of this class is to keep track of account updates 221 | as they are pushed through by :class:`graphene.notify.Notify`. 222 | 223 | Instances of this class are dictionaries and take the following 224 | form: 225 | 226 | ... code-block: js 227 | 228 | {'id': '2.6.29', 229 | 'lifetime_fees_paid': '44261516129', 230 | 'most_recent_op': '2.9.0', 231 | 'owner': '1.2.29', 232 | 'pending_fees': 0, 233 | 'pending_vested_fees': 16310, 234 | 'total_core_in_orders': '6788845277634', 235 | 'total_ops': 0} 236 | 237 | """ 238 | 239 | def __init__( 240 | self, 241 | data, 242 | graphene_instance=None 243 | ): 244 | self.graphene = graphene_instance or shared_graphene_instance() 245 | 246 | if isinstance(data, dict): 247 | super(AccountUpdate, self).__init__(data) 248 | else: 249 | account = Account(data, graphene_instance=self.graphene) 250 | update = self.graphene.rpc.get_objects([ 251 | "2.6.%s" % (account["id"].split(".")[2]) 252 | ])[0] 253 | super(AccountUpdate, self).__init__(update) 254 | 255 | #@property 256 | def account(self): 257 | """ In oder to obtain the actual 258 | :class:`graphene.account.Account` from this class, you can 259 | use the ``account`` attribute. 260 | """ 261 | account = Account(self["owner"]) 262 | account.refresh() 263 | return account 264 | 265 | def __repr__(self): 266 | return "".format(self["owner"]) 267 | -------------------------------------------------------------------------------- /PythonMiddleware/aes.py: -------------------------------------------------------------------------------- 1 | from Crypto import Random 2 | from Crypto.Cipher import AES 3 | import hashlib 4 | import base64 5 | 6 | 7 | class AESCipher(object): 8 | """ 9 | A classical AES Cipher. Can use any size of data and any size of password thanks to padding. 10 | Also ensure the coherence and the type of the data with a unicode to byte converter. 11 | """ 12 | def __init__(self, key): 13 | self.bs = 32 14 | self.key = hashlib.sha256(AESCipher.str_to_bytes(key)).digest() 15 | 16 | @staticmethod 17 | def str_to_bytes(data): 18 | u_type = type(b''.decode('utf8')) 19 | if isinstance(data, u_type): 20 | return data.encode('utf8') 21 | return data 22 | 23 | def _pad(self, s): 24 | return s + (self.bs - len(s) % self.bs) * AESCipher.str_to_bytes(chr(self.bs - len(s) % self.bs)) 25 | 26 | @staticmethod 27 | def _unpad(s): 28 | return s[:-ord(s[len(s) - 1:])] 29 | 30 | def encrypt(self, raw): 31 | raw = self._pad(AESCipher.str_to_bytes(raw)) 32 | iv = Random.new().read(AES.block_size) 33 | cipher = AES.new(self.key, AES.MODE_CBC, iv) 34 | return base64.b64encode(iv + cipher.encrypt(raw)).decode('utf-8') 35 | 36 | def decrypt(self, enc): 37 | enc = base64.b64decode(enc) 38 | iv = enc[:AES.block_size] 39 | cipher = AES.new(self.key, AES.MODE_CBC, iv) 40 | return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8') 41 | -------------------------------------------------------------------------------- /PythonMiddleware/amount.py: -------------------------------------------------------------------------------- 1 | from .instance import shared_graphene_instance 2 | from .asset import Asset 3 | 4 | 5 | class Amount(dict): 6 | """ This class deals with Amounts of any asset to simplify dealing with the tuple:: 7 | 8 | (amount, asset) 9 | 10 | :param list args: Allows to deal with different representations of an amount 11 | :param float amount: Let's create an instance with a specific amount 12 | :param str asset: Let's you create an instance with a specific asset (symbol) 13 | :param graphene.graphene.graphene graphene_instance: graphene instance 14 | :returns: All data required to represent an Amount/Asset 15 | :rtype: dict 16 | :raises ValueError: if the data provided is not recognized 17 | 18 | Way to obtain a proper instance: 19 | 20 | * ``args`` can be a string, e.g.: "1 USD" 21 | * ``args`` can be a dictionary containing ``amount`` and ``asset_id`` 22 | * ``args`` can be a dictionary containing ``amount`` and ``asset`` 23 | * ``args`` can be a list of a ``float`` and ``str`` (symbol) 24 | * ``args`` can be a list of a ``float`` and a :class:`graphene.asset.Asset` 25 | * ``amount`` and ``asset`` are defined manually 26 | 27 | An instance is a dictionary and comes with the following keys: 28 | 29 | * ``amount`` (float) 30 | * ``symbol`` (str) 31 | * ``asset`` (instance of :class:`graphene.asset.Asset`) 32 | 33 | Instances of this class can be used in regular mathematical expressions 34 | (``+-*/%``) such as: 35 | 36 | .. code-block:: python 37 | 38 | Amount("1 USD") * 2 39 | Amount("15 GOLD") + Amount("0.5 GOLD") 40 | """ 41 | def __init__(self, *args, amount=None, asset=None, graphene_instance=None): 42 | self["asset"] = {} 43 | 44 | self.graphene = graphene_instance or shared_graphene_instance() 45 | 46 | if len(args) == 1 and isinstance(args[0], Amount): 47 | # Copy Asset object 48 | self["amount"] = args[0]["amount"] 49 | self["symbol"] = args[0]["symbol"] 50 | self["asset"] = args[0]["asset"] 51 | 52 | elif len(args) == 1 and isinstance(args[0], str): 53 | self["amount"], self["symbol"] = args[0].split(" ") 54 | self["asset"] = Asset(self["symbol"], graphene_instance=self.graphene) 55 | 56 | elif (len(args) == 1 and 57 | isinstance(args[0], dict) and 58 | "amount" in args[0] and 59 | "asset_id" in args[0]): 60 | self["asset"] = Asset(args[0]["asset_id"], graphene_instance=self.graphene) 61 | self["symbol"] = self["asset"]["symbol"] 62 | self["amount"] = int(args[0]["amount"]) / 10 ** self["asset"]["precision"] 63 | 64 | elif (len(args) == 1 and 65 | isinstance(args[0], dict) and 66 | "amount" in args[0] and 67 | "asset" in args[0]): 68 | self["asset"] = Asset(args[0]["asset"], graphene_instance=self.graphene) 69 | self["symbol"] = self["asset"]["symbol"] 70 | self["amount"] = int(args[0]["amount"]) / 10 ** self["asset"]["precision"] 71 | 72 | elif len(args) == 2 and isinstance(args[1], Asset): 73 | self["amount"] = args[0] 74 | self["symbol"] = args[1]["symbol"] 75 | self["asset"] = args[1] 76 | 77 | elif len(args) == 2 and isinstance(args[1], str): 78 | self["amount"] = args[0] 79 | self["asset"] = Asset(args[1], graphene_instance=self.graphene) 80 | self["symbol"] = self["asset"]["symbol"] 81 | 82 | elif isinstance(amount, (int, float)) and asset and isinstance(asset, Asset): 83 | self["amount"] = amount 84 | self["asset"] = asset 85 | self["symbol"] = self["asset"]["symbol"] 86 | 87 | elif isinstance(amount, (int, float)) and asset and isinstance(asset, dict): 88 | self["amount"] = amount 89 | self["asset"] = asset 90 | self["symbol"] = self["asset"]["symbol"] 91 | 92 | elif isinstance(amount, (int, float)) and asset and isinstance(asset, str): 93 | self["amount"] = amount 94 | self["asset"] = Asset(asset) 95 | self["symbol"] = asset 96 | 97 | else: 98 | raise ValueError 99 | 100 | # make sure amount is a float 101 | self["amount"] = float(self["amount"]) 102 | 103 | def copy(self): 104 | """ Copy the instance and make sure not to use a reference 105 | """ 106 | return Amount( 107 | amount=self["amount"], 108 | asset=self["asset"].copy(), 109 | graphene_instance=self.graphene) 110 | 111 | @property 112 | def amount(self): 113 | """ Returns the amount as float 114 | """ 115 | return self["amount"] 116 | 117 | @property 118 | def symbol(self): 119 | """ Returns the symbol of the asset 120 | """ 121 | return self["symbol"] 122 | 123 | @property 124 | def asset(self): 125 | """ Returns the asset as instance of :class:`graphene.asset.Asset` 126 | """ 127 | if not self["asset"]: 128 | self["asset"] = Asset(self["symbol"], graphene_instance=self.graphene) 129 | return self["asset"] 130 | 131 | def json(self): 132 | return { 133 | "amount": int(self), 134 | "asset_id": self["asset"]["id"] 135 | } 136 | 137 | def __str__(self): 138 | return "{:,.{prec}f} {}".format( 139 | self["amount"], 140 | self["symbol"], 141 | prec=self["asset"]["precision"] 142 | ) 143 | 144 | def __float__(self): 145 | return self["amount"] 146 | 147 | def __int__(self): 148 | return int(self["amount"] * 10 ** self["asset"]["precision"]) 149 | 150 | def __add__(self, other): 151 | a = self.copy() 152 | if isinstance(other, Amount): 153 | assert other["asset"] == self["asset"] 154 | a["amount"] += other["amount"] 155 | else: 156 | a["amount"] += float(other) 157 | return a 158 | 159 | def __sub__(self, other): 160 | a = self.copy() 161 | if isinstance(other, Amount): 162 | assert other["asset"] == self["asset"] 163 | a["amount"] -= other["amount"] 164 | else: 165 | a["amount"] -= float(other) 166 | return a 167 | 168 | def __mul__(self, other): 169 | a = self.copy() 170 | if isinstance(other, Amount): 171 | a["amount"] *= other["amount"] 172 | else: 173 | a["amount"] *= other 174 | return a 175 | 176 | def __floordiv__(self, other): 177 | a = self.copy() 178 | if isinstance(other, Amount): 179 | from .price import Price 180 | return Price(self, other) 181 | else: 182 | a["amount"] //= other 183 | return a 184 | 185 | def __div__(self, other): 186 | a = self.copy() 187 | if isinstance(other, Amount): 188 | from .price import Price 189 | return Price(self, other) 190 | else: 191 | a["amount"] /= other 192 | return a 193 | 194 | def __mod__(self, other): 195 | a = self.copy() 196 | if isinstance(other, Amount): 197 | a["amount"] %= other["amount"] 198 | else: 199 | a["amount"] %= other 200 | return a 201 | 202 | def __pow__(self, other): 203 | a = self.copy() 204 | if isinstance(other, Amount): 205 | a["amount"] **= other["amount"] 206 | else: 207 | a["amount"] **= other 208 | return a 209 | 210 | def __iadd__(self, other): 211 | if isinstance(other, Amount): 212 | assert other["asset"] == self["asset"] 213 | self["amount"] += other["amount"] 214 | else: 215 | self["amount"] += other 216 | return self 217 | 218 | def __isub__(self, other): 219 | if isinstance(other, Amount): 220 | assert other["asset"] == self["asset"] 221 | self["amount"] -= other["amount"] 222 | else: 223 | self["amount"] -= other 224 | return self 225 | 226 | def __imul__(self, other): 227 | if isinstance(other, Amount): 228 | self["amount"] *= other["amount"] 229 | else: 230 | self["amount"] *= other 231 | return self 232 | 233 | def __idiv__(self, other): 234 | if isinstance(other, Amount): 235 | assert other["asset"] == self["asset"] 236 | return self["amount"] / other["amount"] 237 | else: 238 | self["amount"] /= other 239 | return self 240 | 241 | def __ifloordiv__(self, other): 242 | if isinstance(other, Amount): 243 | self["amount"] //= other["amount"] 244 | else: 245 | self["amount"] //= other 246 | return self 247 | 248 | def __imod__(self, other): 249 | if isinstance(other, Amount): 250 | self["amount"] %= other["amount"] 251 | else: 252 | self["amount"] %= other 253 | return self 254 | 255 | def __ipow__(self, other): 256 | self["amount"] **= other 257 | return self 258 | 259 | def __lt__(self, other): 260 | if isinstance(other, Amount): 261 | assert other["asset"] == self["asset"] 262 | return self["amount"] < other["amount"] 263 | else: 264 | return self["amount"] < float(other or 0) 265 | 266 | def __le__(self, other): 267 | if isinstance(other, Amount): 268 | assert other["asset"] == self["asset"] 269 | return self["amount"] <= other["amount"] 270 | else: 271 | return self["amount"] <= float(other or 0) 272 | 273 | def __eq__(self, other): 274 | if isinstance(other, Amount): 275 | assert other["asset"] == self["asset"] 276 | return self["amount"] == other["amount"] 277 | else: 278 | return self["amount"] == float(other or 0) 279 | 280 | def __ne__(self, other): 281 | if isinstance(other, Amount): 282 | assert other["asset"] == self["asset"] 283 | return self["amount"] != other["amount"] 284 | else: 285 | return self["amount"] != float(other or 0) 286 | 287 | def __ge__(self, other): 288 | if isinstance(other, Amount): 289 | assert other["asset"] == self["asset"] 290 | return self["amount"] >= other["amount"] 291 | else: 292 | return self["amount"] >= float(other or 0) 293 | 294 | def __gt__(self, other): 295 | if isinstance(other, Amount): 296 | assert other["asset"] == self["asset"] 297 | return self["amount"] > other["amount"] 298 | else: 299 | return self["amount"] > float(other or 0) 300 | 301 | __repr__ = __str__ 302 | __truediv__ = __div__ 303 | __truemul__ = __mul__ 304 | -------------------------------------------------------------------------------- /PythonMiddleware/block.py: -------------------------------------------------------------------------------- 1 | from PythonMiddleware.instance import shared_graphene_instance 2 | 3 | from .exceptions import BlockDoesNotExistsException 4 | from .utils import parse_time 5 | 6 | 7 | class Block(dict): 8 | """ Read a single block from the chain 9 | 10 | :param int block: block number 11 | :param graphene.graphene.graphene graphene_instance: graphene instance 12 | :param bool lazy: Use lazy loading 13 | 14 | Instances of this class are dictionaries that come with additional 15 | methods (see below) that allow dealing with a block and it's 16 | corresponding functions. 17 | 18 | .. code-block:: python 19 | 20 | from graphene.block import Block 21 | block = Block(1) 22 | print(block) 23 | 24 | .. note:: This class comes with its own caching function to reduce the 25 | load on the API server. Instances of this class can be 26 | refreshed with ``Account.refresh()``. 27 | 28 | """ 29 | def __init__( 30 | self, 31 | block, 32 | graphene_instance=None, 33 | lazy=False 34 | ): 35 | self.graphene = graphene_instance or shared_graphene_instance() 36 | self.cached = False 37 | self.block = block 38 | 39 | if isinstance(block, Block): 40 | super(Block, self).__init__(block) 41 | self.cached = True 42 | elif not lazy: 43 | self.refresh() 44 | 45 | def refresh(self): 46 | """ Even though blocks never change, you freshly obtain its contents 47 | from an API with this method 48 | """ 49 | block = self.graphene.rpc.get_block(self.block) 50 | if not block: 51 | raise BlockDoesNotExistsException 52 | super(Block, self).__init__(block) 53 | self.cached = True 54 | 55 | def __getitem__(self, key): 56 | if not self.cached: 57 | self.refresh() 58 | return super(Block, self).__getitem__(key) 59 | 60 | def items(self): 61 | if not self.cached: 62 | self.refresh() 63 | return super(Block, self).items() 64 | 65 | def time(self): 66 | """ Return a datatime instance for the timestamp of this block 67 | """ 68 | return parse_time(self['timestamp']) 69 | -------------------------------------------------------------------------------- /PythonMiddleware/blockchain.py: -------------------------------------------------------------------------------- 1 | import time 2 | from .block import Block 3 | from PythonMiddleware.instance import shared_graphene_instance 4 | from .utils import parse_time 5 | from PythonMiddlewarebase.operationids import operations, getOperationNameForId 6 | 7 | 8 | class Blockchain(object): 9 | """ This class allows to access the blockchain and read data 10 | from it 11 | 12 | :param graphene.graphene.graphene graphene_instance: graphene instance 13 | :param str mode: (default) Irreversible block (``irreversible``) or actual head block (``head``) 14 | 15 | This class let's you deal with blockchain related data and methods. 16 | """ 17 | def __init__( 18 | self, 19 | graphene_instance=None, 20 | mode="irreversible" 21 | ): 22 | self.graphene = graphene_instance or shared_graphene_instance() 23 | 24 | if mode == "irreversible": 25 | self.mode = 'last_irreversible_block_num' 26 | elif mode == "head": 27 | self.mode = "head_block_number" 28 | else: 29 | raise ValueError("invalid value for 'mode'!") 30 | 31 | def info(self): 32 | """ This call returns the *dynamic global properties* 33 | """ 34 | return self.graphene.rpc.get_dynamic_global_properties() 35 | 36 | def chainParameters(self): 37 | """ The blockchain parameters, such as fees, and committee-controlled 38 | parameters are returned here 39 | """ 40 | return self.config()["parameters"] 41 | 42 | def get_network(self): 43 | """ Identify the network 44 | 45 | :returns: Network parameters 46 | :rtype: dict 47 | """ 48 | return self.graphene.rpc.get_network() 49 | 50 | def get_chain_properties(self): 51 | """ Return chain properties 52 | """ 53 | return self.graphene.rpc.get_chain_properties() 54 | 55 | def config(self): 56 | """ Returns object 2.0.0 57 | """ 58 | return self.graphene.rpc.get_object("2.0.0") 59 | 60 | def get_current_block_num(self): 61 | """ This call returns the current block 62 | 63 | .. note:: The block number returned depends on the ``mode`` used 64 | when instanciating from this class. 65 | """ 66 | return self.info().get(self.mode) 67 | 68 | def get_current_block(self): 69 | """ This call returns the current block 70 | 71 | .. note:: The block number returned depends on the ``mode`` used 72 | when instanciating from this class. 73 | """ 74 | return Block(self.get_current_block_num()) 75 | 76 | def block_time(self, block_num): 77 | """ Returns a datetime of the block with the given block 78 | number. 79 | 80 | :param int block_num: Block number 81 | """ 82 | return Block(block_num).time() 83 | 84 | def block_timestamp(self, block_num): 85 | """ Returns the timestamp of the block with the given block 86 | number. 87 | 88 | :param int block_num: Block number 89 | """ 90 | return int(Block(block_num).time().timestamp()) 91 | 92 | def blocks(self, start=None, stop=None): 93 | """ Yields blocks starting from ``start``. 94 | 95 | :param int start: Starting block 96 | :param int stop: Stop at this block 97 | :param str mode: We here have the choice between 98 | * "head": the last block 99 | * "irreversible": the block that is confirmed by 2/3 of all block producers and is thus irreversible! 100 | """ 101 | # Let's find out how often blocks are generated! 102 | block_interval = self.chainParameters().get("block_interval") 103 | 104 | if not start: 105 | start = self.get_current_block_num() 106 | 107 | # We are going to loop indefinitely 108 | while True: 109 | 110 | # Get chain properies to identify the 111 | head_block = self.get_current_block_num() 112 | 113 | # Blocks from start until head block 114 | for blocknum in range(start, head_block + 1): 115 | # Get full block 116 | block = self.graphene.rpc.get_block(blocknum) 117 | block.update({"block_num": blocknum}) 118 | yield block 119 | # Set new start 120 | start = head_block + 1 121 | 122 | if stop and start > stop: 123 | raise StopIteration 124 | 125 | # Sleep for one block 126 | time.sleep(block_interval) 127 | 128 | def ops(self, start=None, stop=None, **kwargs): 129 | """ Yields all operations (including virtual operations) starting from ``start``. 130 | 131 | :param int start: Starting block 132 | :param int stop: Stop at this block 133 | :param str mode: We here have the choice between 134 | * "head": the last block 135 | * "irreversible": the block that is confirmed by 2/3 of all block producers and is thus irreversible! 136 | :param bool only_virtual_ops: Only yield virtual operations 137 | 138 | This call returns a list that only carries one operation and 139 | its type! 140 | """ 141 | 142 | for block in self.blocks(start=start, stop=stop, **kwargs): 143 | for tx in block["transactions"]: 144 | for op in tx["operations"]: 145 | # Replace opid by op name 146 | op[0] = getOperationNameForId(op[0]) 147 | yield { 148 | "block_num": block["block_num"], 149 | "op": op, 150 | "timestamp": block["timestamp"] 151 | } 152 | 153 | def stream(self, opNames=[], *args, **kwargs): 154 | """ Yield specific operations (e.g. comments) only 155 | 156 | :param array opNames: List of operations to filter for 157 | :param int start: Start at this block 158 | :param int stop: Stop at this block 159 | :param str mode: We here have the choice between 160 | * "head": the last block 161 | * "irreversible": the block that is confirmed by 2/3 of all block producers and is thus irreversible! 162 | 163 | The dict output is formated such that ``type`` caries the 164 | operation type, timestamp and block_num are taken from the 165 | block the operation was stored in and the other key depend 166 | on the actualy operation. 167 | """ 168 | for op in self.ops(**kwargs): 169 | if not opNames or op["op"][0] in opNames: 170 | r = { 171 | "type": op["op"][0], 172 | "timestamp": op.get("timestamp"), 173 | "block_num": op.get("block_num"), 174 | } 175 | r.update(op["op"][1]) 176 | yield r 177 | 178 | def awaitTxConfirmation(self, transaction, limit=20): 179 | """ Returns the transaction as seen by the blockchain after being included into a block 180 | 181 | .. note:: If you want instant confirmation, you need to instantiate 182 | class:`graphene.blockchain.Blockchain` with 183 | ``mode="head"``, otherwise, the call will wait until 184 | confirmed in an irreversible block. 185 | 186 | .. note:: This method returns once the blockchain has included a 187 | transaction with the **same signature**. Even though the 188 | signature is not usually used to identify a transaction, 189 | it still cannot be forfeited and is derived from the 190 | transaction contented and thus identifies a transaction 191 | uniquely. 192 | """ 193 | # print("transaction>>>:", transaction) 194 | # counter = 0 195 | # start = self.get_current_block_num()-1 196 | # for block in self.blocks(start=start): 197 | # # print("block...", block) 198 | # counter += 1 199 | # index=0 200 | # for tx in block["transactions"]: 201 | # # print("---tx>:", tx) 202 | # if sorted(tx[1]["signatures"]) == sorted(transaction["signatures"]): 203 | # # tx['transactions']=block["transaction_ids"][index] 204 | # # tx[1]['transactions']=block["transactions"][index][0] 205 | # tx[1]['block'] = block["block_num"] 206 | # return tx 207 | # index += 1 208 | # if counter > limit: 209 | # raise Exception("The operation has not been added after 10 blocks!") 210 | # while 1: 211 | while 1: 212 | tx_info = self.graphene.rpc.get_transaction_by_id(transaction) 213 | # print("result>>>", result) 214 | if tx_info["operation_results"]: 215 | break 216 | time.sleep(0.1) 217 | block_num = self.graphene.rpc.get_transaction_in_block_info(transaction)["block_num"] 218 | tx = { 219 | "block_num": block_num, 220 | "trx_id": transaction, 221 | "transaction": tx_info 222 | } 223 | return tx 224 | 225 | 226 | 227 | def get_all_accounts(self, start='', stop='', steps=1e3, **kwargs): 228 | """ Yields account names between start and stop. 229 | 230 | :param str start: Start at this account name 231 | :param str stop: Stop at this account name 232 | :param int steps: Obtain ``steps`` ret with a single call from RPC 233 | """ 234 | lastname = start 235 | while True: 236 | ret = self.graphene.rpc.lookup_accounts(lastname, steps) 237 | for account in ret: 238 | yield account[0] 239 | if account[0] == stop: 240 | raise StopIteration 241 | if lastname == ret[-1][0]: 242 | raise StopIteration 243 | lastname = ret[-1][0] 244 | if len(ret) < steps: 245 | raise StopIteration 246 | -------------------------------------------------------------------------------- /PythonMiddleware/committee.py: -------------------------------------------------------------------------------- 1 | from .instance import shared_graphene_instance 2 | from .account import Account 3 | from .exceptions import CommitteeMemberDoesNotExistsException 4 | 5 | 6 | class Committee(dict): 7 | """ Read data about a Committee Member in the chain 8 | 9 | :param str member: Name of the Committee Member 10 | :param graphene graphene_instance: graphene() instance to use when accesing a RPC 11 | :param bool lazy: Use lazy loading 12 | 13 | """ 14 | def __init__( 15 | self, 16 | member, 17 | graphene_instance=None, 18 | lazy=False 19 | ): 20 | self.cached = False 21 | self.member = member 22 | 23 | self.graphene = graphene_instance or shared_graphene_instance() 24 | 25 | if not lazy: 26 | self.refresh() 27 | 28 | def refresh(self): 29 | account = Account(self.member) 30 | member = self.graphene.rpc.get_committee_member_by_account(account["id"]) 31 | if not member: 32 | raise CommitteeMemberDoesNotExistsException 33 | super(Committee, self).__init__(member) 34 | self.cached = True 35 | 36 | def __getitem__(self, key): 37 | if not self.cached: 38 | self.refresh() 39 | return super(Committee, self).__getitem__(key) 40 | 41 | def items(self): 42 | if not self.cached: 43 | self.refresh() 44 | return super(Committee, self).items() 45 | 46 | @property 47 | def account(self): 48 | return Account(self.member) 49 | -------------------------------------------------------------------------------- /PythonMiddleware/contract.py: -------------------------------------------------------------------------------- 1 | from .instance import shared_graphene_instance 2 | from .exceptions import ContractDoesNotExistsException 3 | 4 | 5 | class Contract(dict): 6 | """ This class allows to easily access Contract data 7 | 8 | :param str Contract_name: Name of the Contract 9 | :param graphene.graphene.graphene graphene_instance: graphene instance 10 | :param bool lazy: Use lazy loading 11 | :param bool full: Obtain all Contract data including orders, positions, etc. 12 | :returns: Contract data 13 | :rtype: dictionary 14 | :raises graphene.exceptions.ContractDoesNotExistsException: if Contract does not exist 15 | 16 | Instances of this class are dictionaries that come with additional 17 | methods (see below) that allow dealing with an Contract and it's 18 | corresponding functions. 19 | 20 | .. code-block:: python 21 | 22 | from graphene.Contract import Contract 23 | Contract = Contract("init0") 24 | print(Contract) 25 | 26 | .. note:: This class comes with its own caching function to reduce the 27 | load on the API server. Instances of this class can be 28 | refreshed with ``Contract.refresh()``. 29 | 30 | """ 31 | 32 | contracts_cache = dict() 33 | 34 | def __init__( 35 | self, 36 | contract, 37 | lazy=False, 38 | graphene_instance=None 39 | ): 40 | self.cached = False 41 | self.graphene = graphene_instance or shared_graphene_instance() 42 | 43 | if isinstance(contract, Contract): 44 | # print("1:",contract) 45 | super(Contract, self).__init__(contract) 46 | self.name = contract["name"] 47 | elif isinstance(contract, str): 48 | # print("2:", contract) 49 | self.name = contract.strip() 50 | else: 51 | raise ValueError("Contract() expects a contract name, id or an instance of Contract") 52 | 53 | if not lazy: 54 | self.refresh() 55 | 56 | # if self.name in Contract.contracts_cache and not self.full: 57 | # super(Contract, self).__init__(Contract.contracts_cache[self.name]) 58 | # self.cached = True 59 | # elif not lazy and not self.cached: 60 | # self.refresh() 61 | # self.cached = True 62 | 63 | def refresh(self): 64 | """ Refresh/Obtain an contract's data from the API server 65 | """ 66 | # import re 67 | # if re.match(r"^1\.16\.[0-9]*$", self.name): 68 | # contract = self.graphene.rpc.get_objects([self.name])[0] 69 | # else: 70 | # contract = self.graphene.rpc.lookup_account_names([self.name])[0] 71 | try: 72 | contract = self.graphene.rpc.get_contract(self.name) 73 | except: 74 | raise ContractDoesNotExistsException(self.name) 75 | # if not contract: 76 | # raise AccountDoesNotExistsException(self.name) 77 | 78 | super(Contract, self).__init__(contract) 79 | self._cache(contract) 80 | self.cached = True 81 | self.name = self["name"] 82 | 83 | def _cache(self, contract): 84 | # store in cache 85 | Contract.contracts_cache[contract["name"]] = contract 86 | 87 | def __getitem__(self, key): 88 | if not self.cached: 89 | self.refresh() 90 | return super(Contract, self).__getitem__(key) 91 | 92 | def __repr__(self): 93 | return " new_collateral_ratio: 176 | raise ValueError("Collateral Ratio has to be higher than %5.2f" % maintenance_col_ratio) 177 | 178 | # Derive Amount of Collateral 179 | collateral_asset = Asset(backing_asset_id) 180 | settlement_price = Price(bitasset["current_feed"]["settlement_price"]) 181 | 182 | if symbol in current_debts: 183 | amount_of_collateral = ( 184 | float(current_debts[symbol]["debt"]) + float(delta["amount"]) 185 | ) * new_collateral_ratio / float(settlement_price) 186 | amount_of_collateral -= float(current_debts[symbol]["collateral"]) 187 | else: 188 | amount_of_collateral = new_collateral_ratio 189 | 190 | # Verify that enough funds are available 191 | fundsNeeded = amount_of_collateral + float(self.returnFees()["call_order_update"]["fee"]) 192 | fundsHave = account.balance(collateral_asset["symbol"]) or 0 193 | if fundsHave <= fundsNeeded: 194 | raise ValueError("Not enough funds available. Need %f %s, but only %f %s are available" % 195 | (fundsNeeded, collateral_asset["symbol"], fundsHave, collateral_asset["symbol"])) 196 | 197 | op = operations.Call_order_update(**{ 198 | 'fee': {'amount': 0, 'asset_id': '1.3.0'}, 199 | 'delta_debt': { 200 | 'amount': int(float(delta) * 10 ** asset["precision"]), 201 | 'asset_id': asset["id"]}, 202 | 'delta_collateral': { 203 | 'amount': int(float(amount_of_collateral) * 10 ** collateral_asset["precision"]), 204 | 'asset_id': collateral_asset["id"]}, 205 | 'funding_account': account["id"], 206 | 'extensions': [] 207 | }) 208 | return self.graphene.finalizeOp(op, account["name"], "active") 209 | 210 | def adjust_collateral_ratio(self, symbol, target_collateral_ratio, account=None): 211 | """ Adjust the collataral ratio of a debt position 212 | 213 | :param Asset amount: Amount to borrow (denoted in 'asset') 214 | :param float target_collateral_ratio: desired collateral ratio 215 | :raises ValueError: if symbol is not a bitasset 216 | :raises ValueError: if collateral ratio is smaller than maintenance collateral ratio 217 | :raises ValueError: if required amounts of collateral are not available 218 | """ 219 | if not account: 220 | if "default_account" in self.graphene.config: 221 | account = self.graphene.config["default_account"] 222 | if not account: 223 | raise ValueError("You need to provide an account") 224 | account = Account(account, full=True, graphene_instance=self.graphene) 225 | current_debts = self.list_debt_positions(account) 226 | if symbol not in current_debts: 227 | raise ValueError("No Call position available to adjust! Please borrow first!") 228 | return self.adjust_debt(Amount(0, symbol), target_collateral_ratio, account) 229 | 230 | def borrow(self, amount, collateral_ratio=None, account=None): 231 | """ Borrow bitassets/smartcoins from the network by putting up 232 | collateral in a CFD at a given collateral ratio. 233 | 234 | :param float amount: Amount to borrow (denoted in 'asset') 235 | :param float collateral_ratio: Collateral ratio to borrow at 236 | :raises ValueError: if symbol is not a bitasset 237 | :raises ValueError: if collateral ratio is smaller than maintenance collateral ratio 238 | :raises ValueError: if required amounts of collateral are not available 239 | 240 | """ 241 | return self.adjust_debt(amount, collateral_ratio, account) 242 | -------------------------------------------------------------------------------- /PythonMiddleware/exceptions.py: -------------------------------------------------------------------------------- 1 | class WalletExists(Exception): 2 | """ A wallet has already been created and requires a password to be 3 | unlocked by means of :func:`graphene.wallet.unlock`. 4 | """ 5 | pass 6 | 7 | 8 | class AccountExistsException(Exception): 9 | """ The requested account already exists 10 | """ 11 | pass 12 | 13 | 14 | class AccountDoesNotExistsException(Exception): 15 | """ The account does not exist 16 | """ 17 | pass 18 | 19 | 20 | class AssetDoesNotExistsException(Exception): 21 | """ The asset does not exist 22 | """ 23 | pass 24 | 25 | 26 | class BlockDoesNotExistsException(Exception): 27 | """ The block does not exist 28 | """ 29 | pass 30 | 31 | 32 | class WitnessDoesNotExistsException(Exception): 33 | """ The witness does not exist 34 | """ 35 | pass 36 | 37 | 38 | class CommitteeMemberDoesNotExistsException(Exception): 39 | """ Committee Member does not exist 40 | """ 41 | pass 42 | 43 | 44 | class VestingBalanceDoesNotExistsException(Exception): 45 | """ Vesting Balance does not exist 46 | """ 47 | pass 48 | 49 | 50 | class ProposalDoesNotExistException(Exception): 51 | """ The proposal does not exist 52 | """ 53 | pass 54 | 55 | 56 | class InsufficientAuthorityError(Exception): 57 | """ The transaction requires signature of a higher authority 58 | """ 59 | pass 60 | 61 | 62 | class MissingKeyError(Exception): 63 | """ A required key couldn't be found in the wallet 64 | """ 65 | pass 66 | 67 | 68 | class InvalidWifError(Exception): 69 | """ The provided private Key has an invalid format 70 | """ 71 | pass 72 | 73 | 74 | class NoWalletException(Exception): 75 | """ No Wallet could be found, please use :func:`graphene.wallet.create` to 76 | create a new wallet 77 | """ 78 | pass 79 | 80 | 81 | class WrongMasterPasswordException(Exception): 82 | """ The password provided could not properly unlock the wallet 83 | """ 84 | pass 85 | 86 | 87 | class WorkerDoesNotExistsException(Exception): 88 | """ Worker does not exist 89 | """ 90 | pass 91 | 92 | 93 | class ContractDoesNotExistsException(Exception): 94 | """ Contract does not exist 95 | """ 96 | pass 97 | -------------------------------------------------------------------------------- /PythonMiddleware/extensions.py: -------------------------------------------------------------------------------- 1 | def getExtensionObjectFromString(strExtension): 2 | try: 3 | assetID,tempData=strExtension.split("$") 4 | itemVER, tempData=tempData.split("@") 5 | itemID,tempData=tempData.split(";") 6 | return extensions(assetID,itemVER,itemID,tempData) 7 | except: return None 8 | 9 | class extensions: 10 | def __init__(self, assetID, itemVER, itemID, data): 11 | if assetID and itemVER and itemID: 12 | self.assetID = assetID 13 | self.itemVER = itemVER 14 | self.itemID=itemID 15 | self.data= "%s$%s@%s;%s" % (self.assetID,self.itemVER,self.itemID, data) 16 | 17 | def string(self): 18 | return self.data 19 | 20 | def compareWithId(self,itemid): 21 | try: 22 | if(self.itemID==itemid): 23 | return True 24 | else: 25 | return False 26 | except: 27 | return False 28 | def compareWithVER(self,ver): 29 | try: 30 | if(self.itemVER==ver): 31 | return True 32 | except: 33 | return False 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /PythonMiddleware/instance.py: -------------------------------------------------------------------------------- 1 | import PythonMiddleware as gph 2 | 3 | _shared_graphene_instance = None 4 | 5 | def shared_graphene_instance(): 6 | """ This method will initialize ``_shared_graphene_instance`` and return it. 7 | The purpose of this method is to have offer single default 8 | graphene instance that can be reused by multiple classes. 9 | """ 10 | global _shared_graphene_instance 11 | if not _shared_graphene_instance: 12 | _shared_graphene_instance = gph.Graphene() 13 | return _shared_graphene_instance 14 | #print("dasd") 15 | 16 | 17 | def set_shared_graphene_instance(graphene_instance): 18 | """ This method allows us to override default graphene instance for all users of 19 | ``_shared_graphene_instance``. 20 | 21 | :param graphene.Graphene graphene_instance: Graphene instance 22 | """ 23 | global _shared_graphene_instance 24 | _shared_graphene_instance = graphene_instance 25 | -------------------------------------------------------------------------------- /PythonMiddleware/memo.py: -------------------------------------------------------------------------------- 1 | from .instance import shared_graphene_instance 2 | import random 3 | from PythonMiddlewarebase import memo as BtsMemo 4 | from PythonMiddlewarebase.account import PrivateKey, PublicKey 5 | from .account import Account 6 | from .exceptions import MissingKeyError 7 | 8 | 9 | class Memo(object): 10 | """ Deals with Memos that are attached to a transfer 11 | 12 | :param graphene.account.Account from_account: Account that has sent the memo 13 | :param graphene.account.Account to_account: Account that has received the memo 14 | :param graphene.graphene.graphene graphene_instance: graphene instance 15 | 16 | A memo is encrypted with a shared secret derived from a private key of 17 | the sender and a public key of the receiver. Due to the underlying 18 | mathematics, the same shared secret can be derived by the private key 19 | of the receiver and the public key of the sender. The encrypted message 20 | is perturbed by a nonce that is part of the transmitted message. 21 | 22 | .. code-block:: python 23 | 24 | from graphene.memo import Memo 25 | m = Memo("grapheneeu", "wallet.xeroc") 26 | enc = (m.encrypt("foobar")) 27 | print(enc) 28 | >> {'nonce': '17329630356955254641', 'message': '8563e2bb2976e0217806d642901a2855'} 29 | print(m.decrypt(enc)) 30 | >> foobar 31 | 32 | """ 33 | def __init__( 34 | self, 35 | from_account, 36 | to_account, 37 | graphene_instance=None 38 | ): 39 | 40 | self.graphene = graphene_instance or shared_graphene_instance() 41 | 42 | self.to_account = Account(to_account, graphene_instance=self.graphene) 43 | self.from_account = Account(from_account, graphene_instance=self.graphene) 44 | 45 | def encrypt(self, memo): 46 | """ Encrypt a memo 47 | 48 | :param str memo: clear text memo message 49 | :returns: encrypted memo 50 | :rtype: str 51 | """ 52 | if not memo: 53 | return None 54 | 55 | nonce = str(random.getrandbits(64)) 56 | memo_wif = self.graphene.wallet.getPrivateKeyForPublicKey( 57 | self.from_account["options"]["memo_key"] 58 | ) 59 | if not memo_wif: 60 | raise MissingKeyError("Memo key for %s missing!" % self.from_account["name"]) 61 | 62 | enc = BtsMemo.encode_memo( 63 | PrivateKey(memo_wif), 64 | PublicKey( 65 | self.to_account["options"]["memo_key"], 66 | prefix=self.graphene.rpc.chain_params["prefix"] 67 | ), 68 | nonce, 69 | memo 70 | ) 71 | 72 | return { 73 | "message": enc, 74 | "nonce": nonce, 75 | "from": self.from_account["options"]["memo_key"], 76 | "to": self.to_account["options"]["memo_key"] 77 | } 78 | 79 | def decrypt(self, memo): 80 | """ Decrypt a memo 81 | 82 | :param str memo: encrypted memo message 83 | :returns: encrypted memo 84 | :rtype: str 85 | """ 86 | if not memo: 87 | return None 88 | 89 | memo_wif = self.graphene.wallet.getPrivateKeyForPublicKey( 90 | self.to_account["options"]["memo_key"] 91 | ) 92 | if not memo_wif: 93 | raise MissingKeyError("Memo key for %s missing!" % self.to_account["name"]) 94 | 95 | # TODO: Use pubkeys of the message, not pubkeys of account! 96 | return BtsMemo.decode_memo( 97 | PrivateKey(memo_wif), 98 | PublicKey( 99 | self.from_account["options"]["memo_key"], 100 | prefix=self.graphene.rpc.chain_params["prefix"] 101 | ), 102 | memo.get("nonce"), 103 | memo.get("message") 104 | ) 105 | -------------------------------------------------------------------------------- /PythonMiddleware/notify.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from events import Events 3 | from PythonMiddlewareapi.websocket import GrapheneWebsocket 4 | from PythonMiddleware.instance import shared_graphene_instance 5 | from PythonMiddleware.market import Market 6 | from PythonMiddleware.price import Order, FilledOrder, UpdateCallOrder 7 | from PythonMiddleware.account import Account, AccountUpdate 8 | from threading import Thread 9 | log = logging.getLogger(__name__) 10 | # logging.basicConfig(level=logging.DEBUG) 11 | 12 | 13 | class Notify(Events): 14 | """ Notifications on Blockchain events. 15 | 16 | :param list accounts: Account names/ids to be notified about when changing 17 | :param list markets: Instances of :class:`graphene.market.Market` that identify markets to be monitored 18 | :param list objects: Object ids to be notified about when changed 19 | :param fnt on_tx: Callback that will be called for each transaction received 20 | :param fnt on_block: Callback that will be called for each block received 21 | :param fnt on_account: Callback that will be called for changes of the listed accounts 22 | :param fnt on_market: Callback that will be called for changes of the listed markets 23 | :param graphene.graphene.graphene graphene_instance: graphene instance 24 | 25 | **Example** 26 | 27 | .. code-block:: python 28 | 29 | from pprint import pprint 30 | from graphene.notify import Notify 31 | from graphene.market import Market 32 | 33 | notify = Notify( 34 | markets=["TEST:GOLD"], 35 | accounts=["xeroc"], 36 | on_market=print, 37 | on_account=print, 38 | on_block=print, 39 | on_tx=print 40 | ) 41 | notify.listen() 42 | 43 | 44 | """ 45 | 46 | __events__ = [ 47 | 'on_tx', 48 | 'on_object', 49 | 'on_block', 50 | 'on_account', 51 | 'on_market', 52 | ] 53 | 54 | def __init__( 55 | self, 56 | accounts=[], 57 | markets=[], 58 | objects=[], 59 | on_tx=None, 60 | on_object=None, 61 | on_block=None, 62 | on_account=None, 63 | on_market=None, 64 | graphene_instance=None, 65 | ): 66 | # Events 67 | super(Notify, self).__init__() 68 | self.events = Events() 69 | 70 | # graphene instance 71 | self.graphene = graphene_instance or shared_graphene_instance() 72 | 73 | # Markets 74 | market_ids = [] 75 | for market_name in markets: 76 | market = Market( 77 | market_name, 78 | graphene_instance=self.graphene 79 | ) 80 | market_ids.append([ 81 | market["base"]["id"], 82 | market["quote"]["id"], 83 | ]) 84 | 85 | # Callbacks 86 | if on_tx: 87 | self.on_tx += on_tx 88 | if on_object: 89 | self.on_object += on_object 90 | if on_block: 91 | self.on_block += on_block 92 | if on_account: 93 | self.on_account += on_account 94 | if on_market: 95 | self.on_market += on_market 96 | 97 | # Open the websocket 98 | self.websocket = GrapheneWebsocket( 99 | urls=self.graphene.rpc.urls, 100 | user=self.graphene.rpc.user, 101 | password=self.graphene.rpc.password, 102 | accounts=accounts, 103 | markets=market_ids, 104 | objects=objects, 105 | on_tx=on_tx, 106 | on_object=on_object, 107 | on_block=on_block, 108 | on_account=self.process_account, 109 | on_market=self.process_market, 110 | ) 111 | 112 | def process_market(self, data): 113 | """ This method is used for post processing of market 114 | notifications. It will return instances of either 115 | 116 | * :class:`graphene.price.Order` or 117 | * :class:`graphene.price.FilledOrder` or 118 | * :class:`graphene.price.UpdateCallOrder` 119 | 120 | Also possible are limit order updates (margin calls) 121 | 122 | """ 123 | for d in data: 124 | if not d: 125 | continue 126 | if isinstance(d, str): 127 | # Single order has been placed 128 | log.debug("Calling on_market with Order()") 129 | self.on_market(Order(d)) 130 | continue 131 | elif isinstance(d, dict): 132 | d = [d] 133 | 134 | # Orders have been matched 135 | for p in d: 136 | if not isinstance(p, list): 137 | p = [p] 138 | for i in p: 139 | if isinstance(i, dict): 140 | if "pays" in i and "receives" in i: 141 | self.on_market(FilledOrder(i)) 142 | elif "for_sale" in i and "sell_price" in i: 143 | self.on_market(Order(i)) 144 | elif "collateral" in i and "call_price" in i: 145 | self.on_market(UpdateCallOrder(i)) 146 | else: 147 | if i: 148 | log.error( 149 | "Unknown market update type: %s" % i 150 | ) 151 | 152 | def process_account(self, message): 153 | """ This is used for processing of account Updates. It will 154 | return instances of :class:graphene.account.AccountUpdate` 155 | """ 156 | self.on_account(AccountUpdate(message)) 157 | 158 | def listen(self): 159 | """ This call initiates the listening/notification process. It 160 | behaves similar to ``run_forever()``. 161 | """ 162 | Thread( 163 | target=self.websocket.run_forever, 164 | args=() 165 | ).start() 166 | -------------------------------------------------------------------------------- /PythonMiddleware/proposal.py: -------------------------------------------------------------------------------- 1 | from .instance import shared_graphene_instance 2 | from .account import Account 3 | from .exceptions import ProposalDoesNotExistException 4 | import logging 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | class Proposal(dict): 9 | """ Read data about a Proposal Balance in the chain 10 | 11 | :param str id: Id of the proposal 12 | :param graphene graphene_instance: graphene() instance to use when accesing a RPC 13 | 14 | """ 15 | def __init__( 16 | self, 17 | id, 18 | graphene_instance=None, 19 | ): 20 | self.graphene = graphene_instance or shared_graphene_instance() 21 | 22 | if isinstance(id, str): 23 | self.id = id 24 | self.refresh() 25 | elif isinstance(id, dict) and "id" in id: 26 | self.id = id["id"] 27 | super(Proposal, self).__init__(id) 28 | 29 | def refresh(self): 30 | a, b, c = self.id.split(".") 31 | assert int(a) == 1 and int(b) == 10, "Valid proposal ids are 1.10.x" 32 | proposal = self.graphene.rpc.get_objects([self.id]) 33 | if not any(proposal): 34 | raise ProposalDoesNotExistException 35 | super(Proposal, self).__init__(proposal[0]) 36 | 37 | def __repr__(self): 38 | return "" % str(self.id) 39 | 40 | 41 | class Proposals(list): 42 | """ Obtain a list of pending proposals for an account 43 | 44 | :param str account: Account name 45 | :param graphene graphene_instance: graphene() instance to use when accesing a RPC 46 | """ 47 | def __init__(self, account, graphene_instance=None): 48 | self.graphene = graphene_instance or shared_graphene_instance() 49 | 50 | account = Account(account) 51 | proposals = self.graphene.rpc.get_proposed_transactions(account["id"]) 52 | 53 | super(Proposals, self).__init__( 54 | [ 55 | Proposal(x, graphene_instance=self.graphene) 56 | for x in proposals 57 | ] 58 | ) 59 | -------------------------------------------------------------------------------- /PythonMiddleware/transactionbuilder.py: -------------------------------------------------------------------------------- 1 | from .account import Account 2 | from .blockchain import Blockchain 3 | from PythonMiddlewarebase.objects import Operation 4 | from PythonMiddlewarebase.account import PrivateKey, PublicKey 5 | from PythonMiddlewarebase.signedtransactions import Signed_Transaction 6 | from PythonMiddlewarebase import transactions, operations 7 | from .exceptions import ( 8 | InsufficientAuthorityError, 9 | MissingKeyError, 10 | InvalidWifError 11 | ) 12 | from PythonMiddleware.instance import shared_graphene_instance 13 | import logging 14 | from pprint import pprint 15 | log = logging.getLogger(__name__) 16 | 17 | 18 | class TransactionBuilder(dict): 19 | """ This class simplifies the creation of transactions by adding 20 | operations and signers. 21 | """ 22 | 23 | def __init__(self, tx={}, graphene_instance=None): 24 | self.graphene = graphene_instance or shared_graphene_instance() 25 | self.clear() 26 | # print("tx>>", tx) 27 | if not isinstance(tx, dict): 28 | raise ValueError("Invalid TransactionBuilder Format") 29 | super(TransactionBuilder, self).__init__(tx) 30 | 31 | def appendOps(self, ops): 32 | """ Append op(s) to the transaction builder 33 | 34 | :param list ops: One or a list of operations 35 | """ 36 | if isinstance(ops, list): 37 | self.ops.extend(ops) 38 | else: 39 | self.ops.append(ops) 40 | 41 | def appendSigner(self, account, permission): 42 | """ Try to obtain the wif key from the wallet by telling which account 43 | and permission is supposed to sign the transaction 44 | """ 45 | def fetchkeys(account, perm, level=0): 46 | if level > 2: 47 | return [] 48 | r = [] 49 | # print("key_auths>>>:", account[perm]["key_auths"]) 50 | for authority in account[perm]["key_auths"]: 51 | # print("authority>>>:" ,authority) 52 | wif = self.graphene.wallet.getPrivateKeyForPublicKey(authority[0]) 53 | # print("wif>>>:", wif) 54 | if wif: 55 | r.append([wif, authority[1]]) 56 | 57 | if sum([x[1] for x in r]) < required_treshold: 58 | # go one level deeper 59 | # print("3") 60 | for authority in account[perm]["account_auths"]: 61 | auth_account = Account(authority[0], graphene_instance=self.graphene) 62 | r.extend(fetchkeys(auth_account, perm, level + 1)) 63 | 64 | # print("r>>>:", r) 65 | return r 66 | 67 | assert permission in ["active", "owner"], "Invalid permission" 68 | 69 | # print("account>>>:", account) 70 | # print("available_signers>>>:", self.available_signers) 71 | # print("permission>>>:", permission) 72 | 73 | if account not in self.available_signers: 74 | # is the account an instance of public key? 75 | if isinstance(account, PublicKey): 76 | self.wifs.append( 77 | self.graphene.wallet.getPrivateKeyForPublicKey( 78 | str(account) 79 | ) 80 | ) 81 | # print("1") 82 | else: 83 | account = Account(account, graphene_instance=self.graphene) 84 | required_treshold = account[permission]["weight_threshold"] 85 | keys = fetchkeys(account, permission) 86 | if permission != "owner": 87 | keys.extend(fetchkeys(account, "owner")) 88 | self.wifs.extend([x[0] for x in keys]) 89 | # print("wifs>>>:", self.wifs) 90 | # print("2") 91 | # print("keys:>>>", keys) 92 | 93 | self.available_signers.append(account) 94 | # print("available_signers>>>:", self.available_signers) 95 | 96 | def appendWif(self, wif): 97 | """ Add a wif that should be used for signing of the transaction. 98 | """ 99 | if wif: 100 | try: 101 | PrivateKey(wif) 102 | self.wifs.append(wif) 103 | except: 104 | raise InvalidWifError 105 | 106 | def constructTx(self): 107 | """ Construct the actual transaction and store it in the class's dict 108 | store 109 | """ 110 | if self.graphene.proposer: 111 | ops = [operations.Op_wrapper(op=o) for o in list(self.ops)] 112 | proposer = Account( 113 | self.graphene.proposer, 114 | graphene_instance=self.graphene 115 | ) 116 | ops = operations.Proposal_create(**{ 117 | "fee_paying_account": proposer["id"], 118 | "expiration_time": transactions.formatTimeFromNow( 119 | self.graphene.proposal_expiration), 120 | "proposed_ops": [o.json() for o in ops], 121 | "review_period_seconds": self.graphene.proposal_review, 122 | "extensions": [] 123 | }) 124 | ops = [Operation(ops)] 125 | # print("hahahaha") 126 | elif self.graphene.crontaber: 127 | ops = [operations.Op_wrapper(op=o) for o in list(self.ops)] 128 | crontab_creator = Account(self.graphene.crontaber, graphene_instance=self.graphene) 129 | ops = operations.Crontab_create(**{ 130 | "crontab_creator": crontab_creator["id"], 131 | "crontab_ops": [o.json() for o in ops], 132 | "start_time": self.graphene.crontab_start_time, 133 | "execute_interval": self.graphene.crontab_execute_interval, 134 | "scheduled_execute_times": self.graphene.crontab_scheduled_execute_times, 135 | "extensions": {} 136 | }) 137 | ops = [Operation(ops)] 138 | else: 139 | ops = [Operation(o) for o in list(self.ops)] 140 | # for i in ops: 141 | # print("ops>>>:", i) 142 | # print("ws:", self.graphene.rpc.get_required_fees([i.json() for i in ops], "1.3.0")) 143 | # ops = transactions.addRequiredFees(self.graphene.rpc, ops) 144 | # print("ops>>>:", ops) 145 | expiration = transactions.formatTimeFromNow(self.graphene.expiration) 146 | ref_block_num, ref_block_prefix = transactions.getBlockParams(self.graphene.rpc) 147 | 148 | tx = Signed_Transaction( 149 | ref_block_num=ref_block_num, 150 | ref_block_prefix=ref_block_prefix, 151 | expiration=expiration, 152 | operations=ops 153 | ) 154 | # pprint(tx) 155 | super(TransactionBuilder, self).__init__(tx.json()) 156 | 157 | def sign(self): 158 | """ Sign a provided transaction witht he provided key(s) 159 | 160 | :param dict tx: The transaction to be signed and returned 161 | :param string wifs: One or many wif keys to use for signing 162 | a transaction. If not present, the keys will be loaded 163 | from the wallet as defined in "missing_signatures" key 164 | of the transactions. 165 | """ 166 | self.constructTx() 167 | 168 | # If we are doing a proposal, obtain the account from the proposer_id 169 | if self.graphene.proposer: 170 | proposer = Account( 171 | self.graphene.proposer, 172 | graphene_instance=self.graphene) 173 | self.wifs = [] 174 | self.appendSigner(proposer["id"], "active") 175 | 176 | # We need to set the default prefix, otherwise pubkeys are 177 | # presented wrongly! 178 | if self.graphene.rpc: 179 | operations.default_prefix = self.graphene.rpc.chain_params["prefix"] 180 | elif "blockchain" in self: 181 | operations.default_prefix = self["blockchain"]["prefix"] 182 | # print("prefix>>>:", operations.default_prefix) 183 | try: 184 | signedtx = Signed_Transaction(**self.json()) 185 | except: 186 | raise ValueError("Invalid TransactionBuilder Format") 187 | # print("self.wifs>>>>:", self.wifs) 188 | # print("signedtx>>>:", signedtx) 189 | 190 | if not any(self.wifs): 191 | raise MissingKeyError 192 | signedtx.sign(self.wifs, chain=self.graphene.rpc.chain_params) 193 | self["signatures"].extend(signedtx.json().get("signatures")) 194 | # print("signatures>>>>:", self["signatures"]) 195 | 196 | def verify_authority(self): 197 | """ Verify the authority of the signed transaction 198 | """ 199 | # print("verify_authority>>>:", self.graphene.rpc.verify_authority(self.json())) 200 | try: 201 | if not self.graphene.rpc.verify_authority(self.json()): 202 | raise InsufficientAuthorityError 203 | except Exception as e: 204 | raise e 205 | 206 | def broadcast(self): 207 | """ Broadcast a transaction to the graphene network 208 | 209 | :param tx tx: Signed transaction to broadcast 210 | """ 211 | if "signatures" not in self or not self["signatures"]: 212 | self.sign() 213 | 214 | if self.graphene.nobroadcast: 215 | log.warning("Not broadcasting anything!") 216 | return self 217 | 218 | tx = self.json() 219 | # self.verify_authority() 220 | pprint(tx) 221 | # Broadcast 222 | try: 223 | tx_id = self.graphene.rpc.broadcast_transaction(tx, api="network_broadcast") 224 | # return tx_id 225 | except Exception as e: 226 | raise e 227 | 228 | self.clear() 229 | 230 | if self.graphene.blocking: 231 | chain = Blockchain( 232 | mode=("head" if self.graphene.blocking == "head" else "irreversible"), 233 | graphene_instance=self.graphene 234 | ) 235 | tx = chain.awaitTxConfirmation(tx_id) 236 | return tx 237 | 238 | return self 239 | 240 | def clear(self): 241 | """ Clear the transaction builder and start from scratch 242 | """ 243 | self.ops = [] 244 | self.wifs = [] 245 | self.pop("signatures", None) 246 | self.available_signers = [] 247 | super(TransactionBuilder, self).__init__({}) 248 | 249 | def addSigningInformation(self, account, permission): 250 | """ This is a private method that adds side information to a 251 | unsigned/partial transaction in order to simplify later 252 | signing (e.g. for multisig or coldstorage) 253 | 254 | FIXME: Does not work with owner keys! 255 | """ 256 | self.constructTx() 257 | self["blockchain"] = self.graphene.rpc.chain_params 258 | 259 | if isinstance(account, PublicKey): 260 | self["missing_signatures"] = [ 261 | str(account) 262 | ] 263 | else: 264 | accountObj = Account(account) 265 | authority = accountObj[permission] 266 | # We add a required_authorities to be able to identify 267 | # how to sign later. This is an array, because we 268 | # may later want to allow multiple operations per tx 269 | self.update({"required_authorities": { 270 | accountObj["name"]: authority 271 | }}) 272 | for account_auth in authority["account_auths"]: 273 | account_auth_account = Account(account_auth[0]) 274 | self["required_authorities"].update({ 275 | account_auth[0]: account_auth_account.get(permission) 276 | }) 277 | 278 | # Try to resolve required signatures for offline signing 279 | self["missing_signatures"] = [ 280 | x[0] for x in authority["key_auths"] 281 | ] 282 | # Add one recursion of keys from account_auths: 283 | for account_auth in authority["account_auths"]: 284 | account_auth_account = Account(account_auth[0]) 285 | self["missing_signatures"].extend( 286 | [x[0] for x in account_auth_account[permission]["key_auths"]] 287 | ) 288 | # print("self", self) 289 | 290 | def json(self): 291 | """ Show the transaction as plain json 292 | """ 293 | return dict(self) 294 | 295 | def appendMissingSignatures(self): 296 | """ Store which accounts/keys are supposed to sign the transaction 297 | 298 | This method is used for an offline-signer! 299 | """ 300 | missing_signatures = self.get("missing_signatures", []) 301 | for pub in missing_signatures: 302 | wif = self.graphene.wallet.getPrivateKeyForPublicKey(pub) 303 | if wif: 304 | self.appendWif(wif) 305 | -------------------------------------------------------------------------------- /PythonMiddleware/utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | from datetime import datetime 3 | from PythonMiddleware.account import Account, AccountUpdate 4 | from PythonMiddleware.storage import configStorage as config 5 | from PythonMiddlewarebase.account import ( 6 | PrivateKey,PublicKey 7 | ) 8 | import re 9 | import uuid 10 | 11 | 12 | timeFormat = '%Y-%m-%dT%H:%M:%S' 13 | 14 | 15 | def formatTime(t): 16 | """ Properly Format Time for permlinks 17 | """ 18 | if isinstance(t, float): 19 | return datetime.utcfromtimestamp(t).strftime(timeFormat) 20 | if isinstance(t, datetime): 21 | return t.strftime(timeFormat) 22 | 23 | 24 | def formatTimeString(t): 25 | """ Properly Format Time for permlinks 26 | """ 27 | return datetime.strptime(t, timeFormat) 28 | 29 | 30 | def formatTimeFromNow(secs=0): 31 | """ Properly Format Time that is `x` seconds in the future 32 | 33 | :param int secs: Seconds to go in the future (`x>0`) or the 34 | past (`x<0`) 35 | :return: Properly formated time for Graphene (`%Y-%m-%dT%H:%M:%S`) 36 | :rtype: str 37 | 38 | """ 39 | return datetime.utcfromtimestamp(time.time() + int(secs)).strftime(timeFormat) 40 | 41 | 42 | def parse_time(block_time): 43 | """Take a string representation of time from the blockchain, and parse it into datetime object. 44 | """ 45 | return datetime.strptime(block_time, timeFormat) 46 | 47 | 48 | 49 | 50 | def getRegItem(str, patter, index): 51 | pattern = re.compile(patter) 52 | try: 53 | return pattern.match(str).group(index) 54 | except: 55 | return ' ' 56 | 57 | 58 | def str_to_list(listr): 59 | if not listr: 60 | return '' 61 | else: 62 | pattern = '\[(.*)\]' 63 | try: 64 | res = re.match(pattern, listr).group(1) 65 | if ',' in res: 66 | return res.split(',') 67 | else: 68 | return [res] 69 | except: 70 | return '' 71 | 72 | # generate uuid 73 | def get_uuid(): 74 | salt=str(datetime.datetime.now()) 75 | u1 = str(uuid.uuid3(uuid.NAMESPACE_DNS, salt)) 76 | u2 = str(uuid.uuid3(uuid.NAMESPACE_DNS, salt)) 77 | _uuid = (u1+u2).replace('-', '')[:50] 78 | return _uuid 79 | 80 | # verify private key 81 | # def priv_verify(account): 82 | # print("hahahahaha!") 83 | # try: 84 | # tag_account = Account(account) 85 | # except: 86 | # return make_response(jsonify({'msg': 'account name error ', 'data': [], 'code': '400', })) 87 | # w_secr = request.form.get('wsrc', '') 88 | # if not w_secr: 89 | # return make_response(jsonify({'msg': 'no key!', 'code': '400'})) 90 | # else: 91 | # w_secr = w_secr[8:-6] 92 | # try: 93 | # gph.wallet.addPrivateKey(w_secr) 94 | # except: 95 | # pass 96 | # try: 97 | # from_account_public = PrivateKey(w_secr, prefix=config["default_prefix"]).pubkey.__str__() 98 | # except : 99 | # return make_response(jsonify({'msg': 'PrivateKey error !', 'code': '400', })) 100 | # find_account_auths = False 101 | # for temp_auths in tag_account["active"]["key_auths"]: 102 | # if from_account_public.lower() == temp_auths[0].lower(): 103 | # find_account_auths = True 104 | # break 105 | # if find_account_auths == False: 106 | # print("find_account_auths:",find_account_auths) 107 | # if gph.wallet.getAccount(from_account_public)["name"] != config["default_account"]: 108 | # gph.wallet.removePrivateKeyFromPublicKey(from_account_public) 109 | # return make_response(jsonify({'msg': 'Mismatch between the user and the private key:'+from_account_public, 'code': '400', })) -------------------------------------------------------------------------------- /PythonMiddleware/vesting.py: -------------------------------------------------------------------------------- 1 | from .instance import shared_graphene_instance 2 | from .account import Account 3 | from .exceptions import VestingBalanceDoesNotExistsException 4 | import logging 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | class Vesting(dict): 9 | """ Read data about a Vesting Balance in the chain 10 | 11 | :param str id: Id of the vesting balance 12 | :param bitshares bitshares_instance: BitShares() instance to use when accesing a RPC 13 | 14 | """ 15 | def __init__( 16 | self, 17 | id, 18 | graphene_instance=None, 19 | ): 20 | self.id = id 21 | 22 | self.graphene = graphene_instance or shared_graphene_instance() 23 | self.refresh() 24 | 25 | def refresh(self): 26 | a, b = self.id.split(".")[0], self.id.split(".")[1] 27 | assert int(a) == 1 and int(b) == 13, "Valid vesting balances are 1.13.x" 28 | obj = self.graphene.rpc.get_objects([self.id])[0] 29 | super(Vesting, self).__init__(obj) 30 | 31 | @property 32 | def account(self): 33 | return Account(self["owner"]) 34 | 35 | @property 36 | def claimable(self): 37 | from .amount import Amount 38 | if self["policy"][0] == 1: 39 | p = self["policy"][1] 40 | ratio = ( 41 | (float(p["coin_seconds_earned"]) / 42 | float(self["balance"]["amount"])) / 43 | float(p["vesting_seconds"]) 44 | ) if float(p["vesting_seconds"]) > 0.0 else 1 45 | return Amount(self["balance"]) * ratio 46 | else: 47 | log.warning("This policy isn't implemented yet") 48 | return 0 49 | 50 | def claim(self, amount=None): 51 | return self.graphene.vesting_balance_withdraw( 52 | self["id"], 53 | amount=amount, 54 | account=self["owner"] 55 | ) 56 | -------------------------------------------------------------------------------- /PythonMiddleware/wallet.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | from PythonMiddlewarebase import bip38 5 | from PythonMiddlewarebase.account import PrivateKey, GPHPrivateKey 6 | 7 | from .account import Account 8 | from .exceptions import ( 9 | InvalidWifError, 10 | WalletExists, 11 | WrongMasterPasswordException, 12 | NoWalletException 13 | ) 14 | 15 | log = logging.getLogger(__name__) 16 | 17 | 18 | class Wallet(): 19 | """ The wallet is meant to maintain access to private keys for 20 | your accounts. It either uses manually provided private keys 21 | or uses a SQLite database managed by storage.py. 22 | 23 | :param grapheneNodeRPC rpc: RPC connection to a graphene node 24 | :param array,dict,string keys: Predefine the wif keys to shortcut the wallet database 25 | 26 | Three wallet operation modes are possible: 27 | 28 | * **Wallet Database**: Here, pygraphene loads the keys from the 29 | locally stored wallet SQLite database (see ``storage.py``). 30 | To use this mode, simply call ``graphene()`` without the 31 | ``keys`` parameter 32 | * **Providing Keys**: Here, you can provide the keys for 33 | your accounts manually. All you need to do is add the wif 34 | keys for the accounts you want to use as a simple array 35 | using the ``keys`` parameter to ``graphene()``. 36 | * **Force keys**: This more is for advanced users and 37 | requires that you know what you are doing. Here, the 38 | ``keys`` parameter is a dictionary that overwrite the 39 | ``active``, ``owner``, ``posting`` or ``memo`` keys for 40 | any account. This mode is only used for *foreign* 41 | signatures! 42 | """ 43 | keys = [] 44 | rpc = None 45 | masterpassword = None 46 | 47 | # Keys from database 48 | configStorage = None 49 | MasterPassword = None 50 | keyStorage = None 51 | 52 | # Manually provided keys 53 | keys = {} # struct with pubkey as key and wif as value 54 | keyMap = {} # type:wif pairs to force certain keys 55 | 56 | def __init__(self, rpc, *args, **kwargs): 57 | from .storage import configStorage 58 | self.configStorage = configStorage 59 | 60 | # RPC 61 | Wallet.rpc = rpc 62 | 63 | # Prefix? 64 | if Wallet.rpc: 65 | self.prefix = Wallet.rpc.chain_params["prefix"] 66 | else: 67 | # If not connected, load prefix from config 68 | self.prefix = self.configStorage["prefix"] 69 | 70 | # Compatibility after name change from wif->keys 71 | if "wif" in kwargs and "keys" not in kwargs: 72 | kwargs["keys"] = kwargs["wif"] 73 | 74 | if "keys" in kwargs: 75 | self.setKeys(kwargs["keys"]) 76 | else: 77 | """ If no keys are provided manually we load the SQLite 78 | keyStorage 79 | """ 80 | from .storage import (keyStorage, 81 | MasterPassword) 82 | self.MasterPassword = MasterPassword 83 | self.keyStorage = keyStorage 84 | 85 | def setKeys(self, loadkeys): 86 | """ This method is strictly only for in memory keys that are 87 | passed to Wallet/graphene with the ``keys`` argument 88 | """ 89 | log.debug("Force setting of private keys. Not using the wallet database!") 90 | if isinstance(loadkeys, dict): 91 | Wallet.keyMap = loadkeys 92 | loadkeys = list(loadkeys.values()) 93 | elif not isinstance(loadkeys, list): 94 | loadkeys = [loadkeys] 95 | 96 | for wif in loadkeys: 97 | try: 98 | key = PrivateKey(wif) 99 | except: 100 | raise InvalidWifError 101 | Wallet.keys[format(key.pubkey, self.prefix)] = str(key) 102 | 103 | def unlock(self, pwd=None): 104 | """ Unlock the wallet database 105 | """ 106 | if not self.created(): 107 | raise NoWalletException 108 | 109 | if not pwd: 110 | self.tryUnlockFromEnv() 111 | else: 112 | if (self.masterpassword is None and 113 | self.configStorage[self.MasterPassword.config_key]): 114 | self.masterpwd = self.MasterPassword(pwd) 115 | self.masterpassword = self.masterpwd.decrypted_master 116 | 117 | def tryUnlockFromEnv(self): 118 | if "UNLOCK" in os.environ: 119 | log.debug("Trying to use environmental variable to unlock wallet") 120 | self.unlock(os.environ.get("UNLOCK")) 121 | else: 122 | raise WrongMasterPasswordException 123 | 124 | def lock(self): 125 | """ Lock the wallet database 126 | """ 127 | self.masterpassword = None 128 | 129 | def locked(self): 130 | """ Is the wallet database locked? 131 | """ 132 | try: 133 | self.tryUnlockFromEnv() 134 | except: 135 | pass 136 | return not bool(self.masterpassword) 137 | 138 | def changePassphrase(self, new_pwd): 139 | """ Change the passphrase for the wallet database 140 | """ 141 | assert not self.locked() 142 | self.masterpwd.changePassword(new_pwd) 143 | 144 | def created(self): 145 | """ Do we have a wallet database already? 146 | """ 147 | if len(self.getPublicKeys()): 148 | # Already keys installed 149 | return True 150 | elif self.MasterPassword.config_key in self.configStorage: 151 | # no keys but a master password 152 | return True 153 | else: 154 | return False 155 | 156 | def create(self, pwd): 157 | """ Alias for newWallet() 158 | """ 159 | self.newWallet(pwd) 160 | 161 | def newWallet(self, pwd): 162 | """ Create a new wallet database 163 | """ 164 | if self.created(): 165 | raise WalletExists("You already have created a wallet!") 166 | self.masterpwd = self.MasterPassword(pwd) 167 | self.masterpassword = self.masterpwd.decrypted_master 168 | 169 | def encrypt_wif(self, wif): 170 | """ Encrypt a wif key 171 | """ 172 | assert not self.locked() 173 | return format(bip38.encrypt(PrivateKey(wif), self.masterpassword), "encwif") 174 | 175 | def decrypt_wif(self, encwif): 176 | """ decrypt a wif key 177 | """ 178 | try: 179 | # Try to decode as wif 180 | PrivateKey(encwif) 181 | return encwif 182 | except: 183 | pass 184 | assert not self.locked() 185 | return format(bip38.decrypt(encwif, self.masterpassword), "wif") 186 | 187 | def addPrivateKey(self, wif): 188 | """ Add a private key to the wallet database 189 | """ 190 | # it could be either graphenebase or graphenebase so we can't check the type directly 191 | if isinstance(wif, PrivateKey) or isinstance(wif, GPHPrivateKey): 192 | wif = str(wif) 193 | try: 194 | pub = format(PrivateKey(wif).pubkey, self.prefix) 195 | except: 196 | raise InvalidWifError("Invalid Private Key Format. Please use WIF!") 197 | 198 | if self.keyStorage: 199 | # Test if wallet exists 200 | if not self.created(): 201 | raise NoWalletException 202 | self.keyStorage.add(self.encrypt_wif(wif), pub) 203 | 204 | def getPrivateKeyForPublicKey(self, pub): 205 | """ Obtain the private key for a given public key 206 | 207 | :param str pub: Public Key 208 | """ 209 | if(Wallet.keys): 210 | if pub in Wallet.keys: 211 | return Wallet.keys[pub] 212 | elif len(Wallet.keys) == 1: 213 | # If there is only one key in my overwrite-storage, then 214 | # use that one! Whether it will has sufficient 215 | # authorization is left to ensure by the developer 216 | return list(self.keys.values())[0] 217 | else: 218 | # Test if wallet exists 219 | if not self.created(): 220 | raise NoWalletException 221 | 222 | return self.decrypt_wif(self.keyStorage.getPrivateKeyForPublicKey(pub)) 223 | 224 | def removePrivateKeyFromPublicKey(self, pub): 225 | """ Remove a key from the wallet database 226 | """ 227 | if self.keyStorage: 228 | # Test if wallet exists 229 | if not self.created(): 230 | raise NoWalletException 231 | self.keyStorage.delete(pub) 232 | 233 | def removeAccount(self, account): 234 | """ Remove all keys associated with a given account 235 | """ 236 | accounts = self.getAccounts() 237 | for a in accounts: 238 | if a["name"] == account: 239 | self.removePrivateKeyFromPublicKey(a["pubkey"]) 240 | 241 | def getOwnerKeyForAccount(self, name): 242 | """ Obtain owner Private Key for an account from the wallet database 243 | """ 244 | if "owner" in Wallet.keyMap: 245 | return Wallet.keyMap.get("owner") 246 | else: 247 | account = self.rpc.get_account(name) 248 | if not account: 249 | return 250 | for authority in account["owner"]["key_auths"]: 251 | key = self.getPrivateKeyForPublicKey(authority[0]) 252 | if key: 253 | return key 254 | return False 255 | 256 | def getMemoKeyForAccount(self, name): 257 | """ Obtain owner Memo Key for an account from the wallet database 258 | """ 259 | if "memo" in Wallet.keyMap: 260 | return Wallet.keyMap.get("memo") 261 | else: 262 | account = self.rpc.get_account(name) 263 | if not account: 264 | return 265 | key = self.getPrivateKeyForPublicKey(account["options"]["memo_key"]) 266 | if key: 267 | return key 268 | return False 269 | 270 | def getActiveKeyForAccount(self, name): 271 | """ Obtain owner Active Key for an account from the wallet database 272 | """ 273 | if "active" in Wallet.keyMap: 274 | return Wallet.keyMap.get("active") 275 | else: 276 | account = self.rpc.get_account(name) 277 | if not account: 278 | return 279 | for authority in account["active"]["key_auths"]: 280 | key = self.getPrivateKeyForPublicKey(authority[0]) 281 | if key: 282 | return key 283 | return False 284 | 285 | def getAccountFromPrivateKey(self, wif): 286 | """ Obtain account name from private key 287 | """ 288 | pub = format(PrivateKey(wif).pubkey, self.prefix) 289 | return self.getAccountFromPublicKey(pub) 290 | 291 | def getAccountFromPublicKey(self, pub): 292 | """ Obtain account name from public key 293 | """ 294 | # FIXME, this only returns the first associated key. 295 | # If the key is used by multiple accounts, this 296 | # will surely lead to undesired behavior 297 | names = self.rpc.get_key_references([pub])[0] 298 | if not names: 299 | return None 300 | else: 301 | return names[0] 302 | 303 | def getAccount(self, pub): 304 | """ Get the account data for a public key 305 | """ 306 | name = self.getAccountFromPublicKey(pub) 307 | if not name: 308 | return {"name": None, 309 | "type": None, 310 | "pubkey": pub 311 | } 312 | else: 313 | try: 314 | account = Account(name) 315 | except: 316 | return 317 | keyType = self.getKeyType(account, pub) 318 | return {"name": account["name"], 319 | "account": account, 320 | "type": keyType, 321 | "pubkey": pub 322 | } 323 | 324 | def getKeyType(self, account, pub): 325 | """ Get key type 326 | """ 327 | for authority in ["owner", "active"]: 328 | for key in account[authority]["key_auths"]: 329 | if pub == key[0]: 330 | return authority 331 | if pub == account["options"]["memo_key"]: 332 | return "memo" 333 | return None 334 | 335 | def getAccounts(self): 336 | """ Return all accounts installed in the wallet database 337 | """ 338 | pubkeys = self.getPublicKeys() 339 | accounts = [] 340 | for pubkey in pubkeys: 341 | # Filter those keys not for our network 342 | if pubkey[:len(self.prefix)] == self.prefix: 343 | accounts.append(self.getAccount(pubkey)) 344 | return accounts 345 | 346 | def getPublicKeys(self): 347 | """ Return all installed public keys 348 | """ 349 | if self.keyStorage: 350 | return self.keyStorage.getPublicKeys() 351 | else: 352 | return list(Wallet.keys.keys()) 353 | 354 | def wipe(self): 355 | if self.keyStorage: 356 | return self.keyStorage.wipe() 357 | else: 358 | return 359 | -------------------------------------------------------------------------------- /PythonMiddleware/witness.py: -------------------------------------------------------------------------------- 1 | from PythonMiddleware.instance import shared_graphene_instance 2 | from .account import Account 3 | from .exceptions import WitnessDoesNotExistsException 4 | 5 | 6 | class Witness(dict): 7 | """ Read data about a witness in the chain 8 | 9 | :param str account_name: Name of the witness 10 | :param graphene graphene_instance: graphene() instance to use when accesing a RPC 11 | 12 | """ 13 | 14 | witness_cache = dict() 15 | 16 | def __init__( 17 | self, 18 | witness, 19 | lazy=False, 20 | graphene_instance=None, 21 | ): 22 | self.graphene = graphene_instance or shared_graphene_instance() 23 | self.cached = False 24 | 25 | if isinstance(witness, Witness): 26 | self.witness = witness["name"] 27 | super(Witness, self).__init__(witness) 28 | self.cached = True 29 | self._cache(witness) 30 | else: 31 | self.witness = witness 32 | if witness in Witness.witness_cache: 33 | super(Witness, self).__init__(Witness.witness_cache[witness]) 34 | self.cached = True 35 | elif not lazy and not self.cached: 36 | self.refresh() 37 | 38 | def _cache(self, witness): 39 | # store in cache 40 | Witness.witness_cache[witness["id"]] = witness 41 | 42 | def refresh(self): 43 | parts = self.witness.split(".") 44 | if len(parts) == 3: 45 | a, b, _ = self.witness.split(".") 46 | assert int(a) == 1 and (int(b) == 6 or int(b) == 2), "Witness id's need to be 1.6.x or 1.2.x!" 47 | if int(b) == 6: 48 | witness = self.graphene.rpc.get_object(self.witness) 49 | else: 50 | witness = self.graphene.rpc.get_witness_by_account(self.witness) 51 | else: 52 | account = Account(self.witness) 53 | witness = self.graphene.rpc.get_witness_by_account(account["id"]) 54 | if not witness: 55 | raise WitnessDoesNotExistsException 56 | super(Witness, self).__init__(witness) 57 | self._cache(witness) 58 | 59 | def __getitem__(self, key): 60 | if not self.cached: 61 | self.refresh() 62 | return super(Witness, self).__getitem__(key) 63 | 64 | def items(self): 65 | if not self.cached: 66 | self.refresh() 67 | return super(Witness, self).items() 68 | 69 | @property 70 | def account(self): 71 | return Account(self["witness_account"]) 72 | 73 | def __repr__(self): 74 | return "" % str(self.witness) 75 | 76 | 77 | class Witnesses(list): 78 | """ Obtain a list of **active** witnesses and the current schedule 79 | 80 | :param graphene graphene_instance: graphene() instance to use when accesing a RPC 81 | """ 82 | def __init__(self, graphene_instance=None): 83 | self.graphene = graphene_instance or shared_graphene_instance() 84 | self.schedule = self.graphene.rpc.get_object("2.12.0").get("current_shuffled_witnesses", []) 85 | 86 | super(Witnesses, self).__init__( 87 | [ 88 | Witness(x, lazy=True, graphene_instance=self.graphene) 89 | for x in self.schedule 90 | ] 91 | ) 92 | -------------------------------------------------------------------------------- /PythonMiddleware/worker.py: -------------------------------------------------------------------------------- 1 | from PythonMiddleware.instance import shared_graphene_instance 2 | from .account import Account 3 | from .exceptions import WorkerDoesNotExistsException 4 | 5 | 6 | class Worker(dict): 7 | """ Read data about a worker in the chain 8 | 9 | :param str id: id of the worker 10 | :param graphene graphene_instance: graphene() instance to use when accesing a RPC 11 | 12 | """ 13 | 14 | def __init__( 15 | self, 16 | worker, 17 | lazy=False, 18 | graphene_instance=None, 19 | ): 20 | self.graphene = graphene_instance or shared_graphene_instance() 21 | self.cached = False 22 | 23 | if isinstance(worker, (Worker, dict)): 24 | self.identifier = worker["id"] 25 | super(Worker, self).__init__(worker) 26 | self.cached = True 27 | else: 28 | self.identifier = worker 29 | if not lazy: 30 | self.refresh() 31 | 32 | def refresh(self): 33 | parts = self.identifier.split(".") 34 | assert len(parts) == 3, "Worker() class needs a worker id" 35 | assert int(parts[0]) == 1 and int(parts[1]) == 14, "Worker id's need to be 1.14.x!" 36 | worker = self.graphene.rpc.get_object(self.identifier) 37 | if not worker: 38 | raise WorkerDoesNotExistsException 39 | super(Worker, self).__init__(worker) 40 | self.cached = True 41 | 42 | def __getitem__(self, key): 43 | if not self.cached: 44 | self.refresh() 45 | return super(Worker, self).__getitem__(key) 46 | 47 | def items(self): 48 | if not self.cached: 49 | self.refresh() 50 | return super(Worker, self).items() 51 | 52 | @property 53 | def account(self): 54 | return Account(self["worker_account"]) 55 | 56 | def __repr__(self): 57 | return "" % str(self.identifier) 58 | 59 | 60 | class Workers(list): 61 | """ Obtain a list of workers for an account 62 | 63 | :param str account_name/id: Name/id of the account 64 | :param graphene graphene_instance: graphene() instance to use when accesing a RPC 65 | """ 66 | def __init__(self, account_name, graphene_instance=None): 67 | self.graphene = graphene_instance or shared_graphene_instance() 68 | account = Account(account_name) 69 | self.workers = self.graphene.rpc.get_workers_by_account(account["id"]) 70 | 71 | super(Workers, self).__init__( 72 | [ 73 | Worker(x, lazy=True, graphene_instance=self.graphene) 74 | for x in self.workers 75 | ] 76 | ) 77 | -------------------------------------------------------------------------------- /PythonMiddlewareapi/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "graphenenoderpc", 3 | "exceptions", 4 | "websocket", 5 | ] 6 | -------------------------------------------------------------------------------- /PythonMiddlewareapi/exceptions.py: -------------------------------------------------------------------------------- 1 | import re 2 | # from grapheneapi.graphenewsrpc import RPCError 3 | 4 | 5 | def decodeRPCErrorMsg(e): 6 | """ Helper function to decode the raised Exception and give it a 7 | python Exception class 8 | """ 9 | found = re.search( 10 | ( 11 | "(10 assert_exception: Assert Exception\n|" 12 | "3030000 tx_missing_posting_auth)" 13 | ".*: (.*)\n" 14 | ), 15 | str(e), 16 | flags=re.M) 17 | if found: 18 | return found.group(2).strip() 19 | else: 20 | return str(e) 21 | 22 | 23 | class RPCError(Exception): 24 | pass 25 | 26 | 27 | class MissingRequiredActiveAuthority(RPCError): 28 | pass 29 | 30 | 31 | class NoMethodWithName(RPCError): 32 | pass 33 | 34 | 35 | class UnhandledRPCError(RPCError): 36 | pass 37 | 38 | 39 | class NumRetriesReached(Exception): 40 | pass 41 | -------------------------------------------------------------------------------- /PythonMiddlewareapi/graphenenoderpc.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import threading 4 | import websocket 5 | import ssl 6 | import json 7 | import time 8 | from itertools import cycle 9 | # from grapheneapi.graphenewsrpc import GrapheneWebsocketRPC 10 | from .exceptions import RPCError 11 | from PythonMiddlewarebase.chains import known_chains 12 | from . import exceptions 13 | import logging 14 | log = logging.getLogger(__name__) 15 | 16 | 17 | class GrapheneWebsocketRPC(object): 18 | """ This class allows to call API methods synchronously, without 19 | callbacks. It logs in and registers to the APIs: 20 | 21 | * database 22 | * history 23 | 24 | :param str urls: Either a single Websocket URL, or a list of URLs 25 | :param str user: Username for Authentication 26 | :param str password: Password for Authentication 27 | :param Array apis: List of APIs to register to (default: ["database", "network_broadcast"]) 28 | :param int num_retries: Try x times to num_retries to a node on disconnect, -1 for indefinitely 29 | 30 | Available APIs 31 | 32 | * database 33 | * network_node 34 | * network_broadcast 35 | * history 36 | 37 | Usage: 38 | 39 | .. code-block:: python 40 | 41 | ws = GrapheneWebsocketRPC("ws://10.0.0.16:8090","","") 42 | print(ws.get_account_count()) 43 | 44 | .. note:: This class allows to call methods available via 45 | websocket. If you want to use the notification 46 | subsystem, please use ``GrapheneWebsocket`` instead. 47 | 48 | """ 49 | def __init__(self, urls, user="", password="", **kwargs): 50 | self.api_id = {} 51 | self._request_id = 0 52 | if isinstance(urls, list): 53 | self.urls = cycle(urls) 54 | else: 55 | self.urls = cycle([urls]) 56 | self.user = user 57 | self.password = password 58 | self.num_retries = kwargs.get("num_retries", -1) 59 | 60 | self.wsconnect() 61 | self.register_apis() 62 | 63 | def get_request_id(self): 64 | self._request_id += 1 65 | return self._request_id 66 | 67 | def wsconnect(self): 68 | cnt = 0 69 | while True: 70 | cnt += 1 71 | self.url = next(self.urls) 72 | log.debug("Trying to connect to node %s" % self.url) 73 | if self.url[:3] == "wss": 74 | sslopt_ca_certs = {'cert_reqs': ssl.CERT_NONE} 75 | self.ws = websocket.WebSocket(sslopt=sslopt_ca_certs) 76 | else: 77 | self.ws = websocket.WebSocket() 78 | try: 79 | self.ws.connect(self.url) 80 | break 81 | except KeyboardInterrupt: 82 | raise 83 | except: 84 | if (self.num_retries >= 0 and cnt > self.num_retries): 85 | raise NumRetriesReached() 86 | 87 | sleeptime = (cnt - 1) * 2 if cnt < 10 else 10 88 | if sleeptime: 89 | log.warning( 90 | "Lost connection to node during wsconnect(): %s (%d/%d) " 91 | % (self.url, cnt, self.num_retries) + 92 | "Retrying in %d seconds" % sleeptime 93 | ) 94 | time.sleep(sleeptime) 95 | self.login(self.user, self.password, api_id=1) 96 | 97 | def register_apis(self): 98 | self.api_id["database"] = self.database(api_id=1) 99 | self.api_id["history"] = self.history(api_id=1) 100 | self.api_id["network_broadcast"] = self.network_broadcast(api_id=1) 101 | 102 | """ RPC Calls 103 | """ 104 | def rpcexec(self, payload): 105 | """ Execute a call by sending the payload 106 | 107 | :param json payload: Payload data 108 | :raises ValueError: if the server does not respond in proper JSON format 109 | :raises RPCError: if the server returns an error 110 | """ 111 | log.debug(json.dumps(payload)) 112 | cnt = 0 113 | while True: 114 | cnt += 1 115 | 116 | try: 117 | self.ws.send(json.dumps(payload, ensure_ascii=False).encode('utf8')) 118 | reply = self.ws.recv() 119 | break 120 | except KeyboardInterrupt: 121 | raise 122 | except: 123 | if (self.num_retries > -1 and 124 | cnt > self.num_retries): 125 | raise NumRetriesReached() 126 | sleeptime = (cnt - 1) * 2 if cnt < 10 else 10 127 | if sleeptime: 128 | log.warning( 129 | "Lost connection to node during rpcexec(): %s (%d/%d) " 130 | % (self.url, cnt, self.num_retries) + 131 | "Retrying in %d seconds" % sleeptime 132 | ) 133 | time.sleep(sleeptime) 134 | 135 | # retry 136 | try: 137 | self.ws.close() 138 | time.sleep(sleeptime) 139 | self.wsconnect() 140 | self.register_apis() 141 | except: 142 | pass 143 | 144 | ret = {} 145 | try: 146 | ret = json.loads(reply, strict=False) 147 | except ValueError: 148 | raise ValueError("Client returned invalid format. Expected JSON!") 149 | 150 | log.debug(json.dumps(reply)) 151 | 152 | if 'error' in ret: 153 | if 'detail' in ret['error']: 154 | raise RPCError(ret['error']['detail']) 155 | else: 156 | raise RPCError(ret['error']['message']) 157 | else: 158 | return ret["result"] 159 | 160 | # End of Deprecated methods 161 | #################################################################### 162 | def __getattr__(self, name): 163 | """ Map all methods to RPC calls and pass through the arguments 164 | """ 165 | def method(*args, **kwargs): 166 | 167 | # Sepcify the api to talk to 168 | if "api_id" not in kwargs: 169 | if ("api" in kwargs): 170 | if (kwargs["api"] in self.api_id and 171 | self.api_id[kwargs["api"]]): 172 | api_id = self.api_id[kwargs["api"]] 173 | else: 174 | raise ValueError( 175 | "Unknown API! " 176 | "Verify that you have registered to %s" 177 | % kwargs["api"] 178 | ) 179 | else: 180 | api_id = 0 181 | else: 182 | api_id = kwargs["api_id"] 183 | 184 | # let's be able to define the num_retries per query 185 | self.num_retries = kwargs.get("num_retries", self.num_retries) 186 | 187 | query = {"method": "call", 188 | "params": [api_id, name, list(args)], 189 | "jsonrpc": "2.0", 190 | "id": self.get_request_id()} 191 | r = self.rpcexec(query) 192 | return r 193 | return method 194 | 195 | 196 | class NumRetriesReached(Exception): 197 | pass 198 | 199 | 200 | class GrapheneNodeRPC(GrapheneWebsocketRPC): 201 | 202 | def __init__(self, *args, **kwargs): 203 | super(GrapheneNodeRPC, self).__init__(*args, **kwargs) 204 | chain_params=self.get_network() 205 | print("chain_params",chain_params) 206 | self.chain_params = chain_params 207 | 208 | def register_apis(self): 209 | self.api_id["database"] = self.database(api_id=1) 210 | self.api_id["history"] = self.history(api_id=1) 211 | self.api_id["network_broadcast"] = self.network_broadcast(api_id=1) 212 | 213 | def rpcexec(self, payload): 214 | """ Execute a call by sending the payload. 215 | It makes use of the GrapheneRPC library. 216 | In here, we mostly deal with Graphene specific error handling 217 | 218 | :param json payload: Payload data 219 | :raises ValueError: if the server does not respond in proper JSON format 220 | :raises RPCError: if the server returns an error 221 | """ 222 | try: 223 | # print("payload>>>:", payload) 224 | # Forward call to GrapheneWebsocketRPC and catch+evaluate errors 225 | return super(GrapheneNodeRPC, self).rpcexec(payload) 226 | except exceptions.RPCError as e: 227 | msg = exceptions.decodeRPCErrorMsg(e).strip() 228 | if msg == "missing required active authority": 229 | raise exceptions.MissingRequiredActiveAuthority 230 | elif re.match("^no method with name.*", msg): 231 | raise exceptions.NoMethodWithName(msg) 232 | elif msg: 233 | raise exceptions.UnhandledRPCError(msg) 234 | else: 235 | raise e 236 | except Exception as e: 237 | raise e 238 | 239 | def get_account(self, name, **kwargs): 240 | """ Get full account details from account name or id 241 | 242 | :param str name: Account name or account id 243 | """ 244 | if len(name.split(".")) == 3: 245 | return self.get_objects([name])[0] 246 | else: 247 | return self.get_account_by_name(name, **kwargs) 248 | 249 | def get_asset(self, name, **kwargs): 250 | """ Get full asset from name of id 251 | 252 | :param str name: Symbol name or asset id (e.g. 1.3.0) 253 | """ 254 | if len(name.split(".")) == 3: 255 | return self.get_objects([name], **kwargs)[0] 256 | else: 257 | return self.lookup_asset_symbols([name], **kwargs)[0] 258 | 259 | def get_object(self, o, **kwargs): 260 | """ Get object with id ``o`` 261 | 262 | :param str o: Full object id 263 | """ 264 | return self.get_objects([o], **kwargs)[0] 265 | 266 | def get_network(self): 267 | """ Identify the connected network. This call returns a 268 | dictionary with keys chain_id, core_symbol and prefix 269 | """ 270 | props = self.get_chain_properties() 271 | chain_id = props["chain_id"] 272 | for k, v in known_chains.items(): 273 | if v["chain_id"] == chain_id: 274 | return v 275 | raise("Connecting to unknown network!") 276 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | 'account', 3 | 'bip38', 4 | 'chains', 5 | 'memo', 6 | 'objects', 7 | 'objecttypes', 8 | 'operationids', 9 | 'operations', 10 | 'signedtransactions', 11 | 'transactions', 12 | ] 13 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/account.py: -------------------------------------------------------------------------------- 1 | from .baseaccount import ( 2 | PasswordKey as GPHPasswordKey, 3 | BrainKey as GPHBrainKey, 4 | Address as GPHAddress, 5 | PublicKey as GPHPublicKey, 6 | PrivateKey as GPHPrivateKey, 7 | re,hashlib,hexlify,os,BrainKeyDictionary 8 | ) 9 | from .chains import default_prefix 10 | 11 | 12 | class PasswordKey(GPHPasswordKey): 13 | """ This class derives a private key given the account name, the 14 | role and a password. It leverages the technology of Brainkeys 15 | and allows people to have a secure private key by providing a 16 | passphrase only. 17 | """ 18 | 19 | def __init__(self, *args, **kwargs): 20 | super(PasswordKey, self).__init__(*args, **kwargs) 21 | 22 | 23 | '''class BrainKey(GPHBrainKey): 24 | """Brainkey implementation similar to the graphene-ui web-wallet. 25 | 26 | :param str brainkey: Brain Key 27 | :param int sequence: Sequence number for consecutive keys 28 | 29 | Keys in Graphene are derived from a seed brain key which is a string of 30 | 16 words out of a predefined dictionary with 49744 words. It is a 31 | simple single-chain key derivation scheme that is not compatible with 32 | BIP44 but easy to use. 33 | 34 | Given the brain key, a private key is derived as:: 35 | 36 | privkey = SHA256(SHA512(brainkey + " " + sequence)) 37 | 38 | Incrementing the sequence number yields a new key that can be 39 | regenerated given the brain key. 40 | """ 41 | 42 | def __init__(self, *args, **kwargs): 43 | super(BrainKey, self).__init__(*args, **kwargs) 44 | ''' 45 | 46 | class BrainKey(object): 47 | """Brainkey implementation similar to the graphene-ui web-wallet. 48 | 49 | :param str brainkey: Brain Key 50 | :param int sequence: Sequence number for consecutive keys 51 | 52 | Keys in Graphene are derived from a seed brain key which is a string of 53 | 16 words out of a predefined dictionary with 49744 words. It is a 54 | simple single-chain key derivation scheme that is not compatible with 55 | BIP44 but easy to use. 56 | 57 | Given the brain key, a private key is derived as:: 58 | 59 | privkey = SHA256(SHA512(brainkey + " " + sequence)) 60 | 61 | Incrementing the sequence number yields a new key that can be 62 | regenerated given the brain key. 63 | 64 | """ 65 | 66 | def __init__(self,prefix=default_prefix, brainkey=None, sequence=0): 67 | if not brainkey: 68 | self.brainkey = self.suggest() 69 | else: 70 | self.brainkey = self.normalize(brainkey).strip() 71 | self.sequence = sequence 72 | self.prefix = prefix 73 | 74 | def __next__(self): 75 | """ Get the next private key (sequence number increment) for 76 | iterators 77 | """ 78 | return self.next_sequence() 79 | 80 | def next_sequence(self): 81 | """ Increment the sequence number by 1 """ 82 | self.sequence += 1 83 | return self 84 | 85 | def normalize(self, brainkey): 86 | """ Correct formating with single whitespace syntax and no trailing space """ 87 | return " ".join(re.compile("[\t\n\v\f\r ]+").split(brainkey)) 88 | 89 | def get_brainkey(self): 90 | """ Return brain key of this instance """ 91 | return self.normalize(self.brainkey) 92 | 93 | def get_private(self): 94 | """ Derive private key from the brain key and the current sequence 95 | number 96 | """ 97 | encoded = "%s %d" % (self.brainkey, self.sequence) 98 | a = bytes(encoded, 'ascii') 99 | s = hashlib.sha256(hashlib.sha512(a).digest()).digest() 100 | return PrivateKey(hexlify(s).decode('ascii'),prefix=self.prefix) 101 | 102 | def get_public(self): 103 | return self.get_private().pubkey 104 | 105 | def get_private_key(self): 106 | return self.get_private() 107 | 108 | def get_public_key(self): 109 | return self.get_public() 110 | 111 | def suggest(self): 112 | """ Suggest a new random brain key. Randomness is provided by the 113 | operating system using ``os.urandom()``. 114 | """ 115 | word_count = 16 116 | brainkey = [None] * word_count 117 | dict_lines = BrainKeyDictionary.split(',') 118 | assert len(dict_lines) == 49744 119 | for j in range(0, word_count): 120 | num = int.from_bytes(os.urandom(2), byteorder="little") 121 | rndMult = num / 2 ** 16 # returns float between 0..1 (inclusive) 122 | wIdx = round(len(dict_lines) * rndMult) 123 | brainkey[j] = dict_lines[wIdx] 124 | return " ".join(brainkey).upper() 125 | 126 | 127 | class Address(GPHAddress): 128 | """ Address class 129 | 130 | This class serves as an address representation for Public Keys. 131 | 132 | :param str address: Base58 encoded address (defaults to ``None``) 133 | :param str pubkey: Base58 encoded pubkey (defaults to ``None``) 134 | :param str prefix: Network prefix (defaults to ``GPH``) 135 | 136 | Example:: 137 | 138 | Address("GPHFN9r6VYzBK8EKtMewfNbfiGCr56pHDBFi") 139 | 140 | """ 141 | def __init__(self, *args, **kwargs): 142 | if "prefix" not in kwargs: 143 | kwargs["prefix"] = default_prefix # make prefix GPH 144 | super(Address, self).__init__(*args, **kwargs) 145 | 146 | 147 | class PublicKey(GPHPublicKey): 148 | """ This class deals with Public Keys and inherits ``Address``. 149 | 150 | :param str pk: Base58 encoded public key 151 | :param str prefix: Network prefix (defaults to ``GPH``) 152 | 153 | Example::: 154 | 155 | PublicKey("GPH6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL") 156 | 157 | .. note:: By default, graphene-based networks deal with **compressed** 158 | public keys. If an **uncompressed** key is required, the 159 | method ``unCompressed`` can be used:: 160 | 161 | PublicKey("xxxxx").unCompressed() 162 | 163 | """ 164 | def __init__(self, *args, **kwargs): 165 | if "prefix" not in kwargs: 166 | kwargs["prefix"] = default_prefix # make prefix GPH 167 | super(PublicKey, self).__init__(*args, **kwargs) 168 | 169 | 170 | class PrivateKey(GPHPrivateKey): 171 | """ Derives the compressed and uncompressed public keys and 172 | constructs two instances of ``PublicKey``: 173 | 174 | :param str wif: Base58check-encoded wif key 175 | :param str prefix: Network prefix (defaults to ``GPH``) 176 | 177 | Example::: 178 | 179 | PrivateKey("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd") 180 | 181 | Compressed vs. Uncompressed: 182 | 183 | * ``PrivateKey("w-i-f").pubkey``: 184 | Instance of ``PublicKey`` using compressed key. 185 | * ``PrivateKey("w-i-f").pubkey.address``: 186 | Instance of ``Address`` using compressed key. 187 | * ``PrivateKey("w-i-f").uncompressed``: 188 | Instance of ``PublicKey`` using uncompressed key. 189 | * ``PrivateKey("w-i-f").uncompressed.address``: 190 | Instance of ``Address`` using uncompressed key. 191 | 192 | """ 193 | def __init__(self, *args, **kwargs): 194 | if "prefix" not in kwargs: 195 | kwargs["prefix"] = default_prefix 196 | super(PrivateKey, self).__init__(*args, **kwargs) 197 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/asset_permissions.py: -------------------------------------------------------------------------------- 1 | asset_permissions = {} 2 | asset_permissions["charge_market_fee"] = 0x01 3 | asset_permissions["white_list"] = 0x02 4 | asset_permissions["override_authority"] = 0x04 5 | asset_permissions["transfer_restricted"] = 0x08 6 | asset_permissions["disable_force_settle"] = 0x10 7 | asset_permissions["global_settle"] = 0x20 8 | asset_permissions["disable_issuer"] = 0x40 9 | asset_permissions["witness_fed_asset"] = 0x80 10 | asset_permissions["committee_fed_asset"] = 0x100 11 | 12 | 13 | # enum asset_issuer_permission_flags 14 | # { 15 | # charge_market_fee = 0x01, /**< an issuer-specified percentage of all market trades in this asset is paid to the issuer */ 16 | # white_list = 0x02, /**< accounts must be whitelisted in order to hold this asset */ 17 | # override_authority = 0x04, /**< issuer may transfer asset back to himself */ 18 | # transfer_restricted = 0x08, /**< require the issuer to be one party to every transfer */ 19 | # disable_force_settle = 0x10, /**< disable force settling */ 20 | # global_settle = 0x20, /**< allow the bitasset issuer to force a global settling -- this may be set in permissions, but not flags */ 21 | # disable_issuer = 0x40, /**< allow the asset to be used with confidential transactions */ 22 | # witness_fed_asset = 0x80, /**< allow the asset to be fed by witnesses */ 23 | # committee_fed_asset = 0x100 /**< allow the asset to be fed by the committee */ 24 | # }; 25 | 26 | 27 | whitelist = {} 28 | whitelist["no_listing"] = 0x0 29 | whitelist["white_listed"] = 0x1 30 | whitelist["black_listed"] = 0x2 31 | whitelist["white_and_black_listed"] = 0x1 | 0x2 32 | 33 | restricted = {} 34 | restricted["all_restricted"] = 0 35 | restricted["whitelist_authorities"] = 1 36 | restricted["blacklist_authorities"] = 2 37 | restricted["whitelist_markets"] = 3 38 | restricted["blacklist_markets"] = 4 39 | 40 | 41 | def toint(permissions): 42 | permissions_int = 0 43 | for p in permissions: 44 | if permissions[p]: 45 | permissions_int += asset_permissions[p] 46 | return permissions_int 47 | 48 | 49 | def todict(number): 50 | r = {} 51 | for k, v in asset_permissions.items(): 52 | r[k] = bool(number & v) 53 | return r 54 | 55 | 56 | def force_flag(perms, flags): 57 | for p in flags: 58 | if flags[p]: 59 | perms |= asset_permissions[p] 60 | return perms 61 | 62 | 63 | def test_permissions(perms, flags): 64 | for p in flags: 65 | if not asset_permissions[p] & perms: 66 | raise Exception( 67 | "Permissions prevent you from changing %s!" % p 68 | ) 69 | return True 70 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/base58.py: -------------------------------------------------------------------------------- 1 | from binascii import hexlify, unhexlify 2 | import hashlib 3 | import sys 4 | import string 5 | import logging 6 | log = logging.getLogger(__name__) 7 | from .chains import default_prefix 8 | 9 | """ This class and the methods require python3 """ 10 | assert sys.version_info[0] == 3, "graphenelib requires python3" 11 | 12 | """ Default Prefix """ 13 | PREFIX = default_prefix 14 | 15 | known_prefixes = [ 16 | PREFIX, 17 | "ZKC", 18 | "BTS", 19 | "MUSE", 20 | "TEST", 21 | "STM", 22 | "GLX", 23 | "GLS" 24 | ] 25 | 26 | 27 | class Base58(object): 28 | """Base58 base class 29 | 30 | This class serves as an abstraction layer to deal with base58 encoded 31 | strings and their corresponding hex and binary representation throughout the 32 | library. 33 | 34 | :param data: Data to initialize object, e.g. pubkey data, address data, ... 35 | :type data: hex, wif, bip38 encrypted wif, base58 string 36 | :param str prefix: Prefix to use for Address/PubKey strings (defaults to ``GPH``) 37 | :return: Base58 object initialized with ``data`` 38 | :rtype: Base58 39 | :raises ValueError: if data cannot be decoded 40 | 41 | * ``bytes(Base58)``: Returns the raw data 42 | * ``str(Base58)``: Returns the readable ``Base58CheckEncoded`` data. 43 | * ``repr(Base58)``: Gives the hex representation of the data. 44 | * ``format(Base58,_format)`` Formats the instance according to ``_format``: 45 | * ``"btc"``: prefixed with ``0x80``. Yields a valid btc address 46 | * ``"wif"``: prefixed with ``0x00``. Yields a valid wif key 47 | * ``"bts"``: prefixed with ``BTS`` 48 | * etc. 49 | 50 | """ 51 | def __init__(self, data, prefix=default_prefix): 52 | self._prefix = prefix 53 | # print("data>>:", data, self._prefix) 54 | if all(c in string.hexdigits for c in data): 55 | self._hex = data 56 | elif data[0] == "5" or data[0] == "6": 57 | self._hex = base58CheckDecode(data) 58 | elif data[0] == "K" or data[0] == "L": 59 | self._hex = base58CheckDecode(data)[:-2] 60 | elif data[:len(self._prefix)] == self._prefix: 61 | self._hex = gphBase58CheckDecode(data[len(self._prefix):]) 62 | else: 63 | raise ValueError("Error loading Base58 object") 64 | 65 | def __format__(self, _format): 66 | """ Format output according to argument _format (wif,btc,...) 67 | 68 | :param str _format: Format to use 69 | :return: formatted data according to _format 70 | :rtype: str 71 | 72 | """ 73 | if _format.upper() == "WIF": 74 | return base58CheckEncode(0x80, self._hex) 75 | elif _format.upper() == "ENCWIF": 76 | return base58encode(self._hex) 77 | elif _format.upper() == "BTC": 78 | return base58CheckEncode(0x00, self._hex) 79 | elif _format.upper() in known_prefixes: 80 | return _format.upper() + str(self) 81 | else: 82 | log.warn("Format %s unkown. You've been warned!\n" % _format) 83 | return _format.upper() + str(self) 84 | 85 | def __repr__(self): 86 | """ Returns hex value of object 87 | 88 | :return: Hex string of instance's data 89 | :rtype: hex string 90 | """ 91 | return self._hex 92 | 93 | def __str__(self): 94 | """ Return graphene-base58CheckEncoded string of data 95 | 96 | :return: Base58 encoded data 97 | :rtype: str 98 | """ 99 | return gphBase58CheckEncode(self._hex) 100 | 101 | def __bytes__(self): 102 | """ Return raw bytes 103 | 104 | :return: Raw bytes of instance 105 | :rtype: bytes 106 | 107 | """ 108 | return unhexlify(self._hex) 109 | 110 | 111 | # https://github.com/tochev/python3-cryptocoins/raw/master/cryptocoins/base58.py 112 | BASE58_ALPHABET = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 113 | 114 | 115 | def base58decode(base58_str): 116 | base58_text = bytes(base58_str, "ascii") 117 | n = 0 118 | leading_zeroes_count = 0 119 | for b in base58_text: 120 | n = n * 58 + BASE58_ALPHABET.find(b) 121 | if n == 0: 122 | leading_zeroes_count += 1 123 | res = bytearray() 124 | while n >= 256: 125 | div, mod = divmod(n, 256) 126 | res.insert(0, mod) 127 | n = div 128 | else: 129 | res.insert(0, n) 130 | return hexlify(bytearray(1) * leading_zeroes_count + res).decode('ascii') 131 | 132 | 133 | def base58encode(hexstring): 134 | byteseq = bytes(unhexlify(bytes(hexstring, 'ascii'))) 135 | n = 0 136 | leading_zeroes_count = 0 137 | for c in byteseq: 138 | n = n * 256 + c 139 | if n == 0: 140 | leading_zeroes_count += 1 141 | res = bytearray() 142 | while n >= 58: 143 | div, mod = divmod(n, 58) 144 | res.insert(0, BASE58_ALPHABET[mod]) 145 | n = div 146 | else: 147 | res.insert(0, BASE58_ALPHABET[n]) 148 | return (BASE58_ALPHABET[0:1] * leading_zeroes_count + res).decode('ascii') 149 | 150 | 151 | def ripemd160(s): 152 | ripemd160 = hashlib.new('ripemd160') 153 | ripemd160.update(unhexlify(s)) 154 | return ripemd160.digest() 155 | 156 | 157 | def doublesha256(s): 158 | return hashlib.sha256(hashlib.sha256(unhexlify(s)).digest()).digest() 159 | 160 | 161 | def b58encode(v): 162 | return base58encode(v) 163 | 164 | 165 | def b58decode(v): 166 | return base58decode(v) 167 | 168 | 169 | def base58CheckEncode(version, payload): 170 | s = ('%.2x' % version) + payload 171 | checksum = doublesha256(s)[:4] 172 | result = s + hexlify(checksum).decode('ascii') 173 | return base58encode(result) 174 | 175 | 176 | def base58CheckDecode(s): 177 | s = unhexlify(base58decode(s)) 178 | dec = hexlify(s[:-4]).decode('ascii') 179 | checksum = doublesha256(dec)[:4] 180 | assert(s[-4:] == checksum) 181 | return dec[2:] 182 | 183 | 184 | def gphBase58CheckEncode(s): 185 | checksum = ripemd160(s)[:4] 186 | result = s + hexlify(checksum).decode('ascii') 187 | return base58encode(result) 188 | 189 | 190 | def gphBase58CheckDecode(s): 191 | s = unhexlify(base58decode(s)) 192 | dec = hexlify(s[:-4]).decode('ascii') 193 | checksum = ripemd160(dec)[:4] 194 | assert(s[-4:] == checksum) 195 | return dec 196 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/baseobjects.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | import json 3 | from .types import ( 4 | Uint8, Int16, Uint16, Uint32, Uint64, 5 | Varint32, Int64, String, Bytes, Void, 6 | Array, PointInTime, Signature, Bool, 7 | Set, Fixed_array, Optional, Static_variant, 8 | Map, Id, VoteId, ObjectId, 9 | JsonObj 10 | ) 11 | from .chains import known_chains 12 | from .objecttypes import object_type 13 | from .account import PublicKey 14 | from .chains import default_prefix 15 | from .operationids import operations 16 | 17 | 18 | class Operation(): 19 | def __init__(self, op): 20 | if isinstance(op, list) and len(op) == 2: 21 | if isinstance(op[0], int): 22 | self.opId = op[0] 23 | name = self.getOperationNameForId(self.opId) 24 | else: 25 | self.opId = self.operations().get(op[0], None) 26 | name = op[0] 27 | if self.opId is None: 28 | raise ValueError("Unknown operation") 29 | self.name = name[0].upper() + name[1:] # klassname 30 | try: 31 | klass = self._getklass(self.name) 32 | except: 33 | raise NotImplementedError("Unimplemented Operation %s" % self.name) 34 | self.op = klass(op[1]) 35 | else: 36 | self.op = op 37 | # print("op", self.op) 38 | self.name = type(self.op).__name__.lower() # also store name nico read :: 关键字key 转化为小写用于匹配 39 | self.opId = self.operations()[self.name] 40 | 41 | def operations(self): 42 | return operations 43 | 44 | def getOperationNameForId(self, i): 45 | """ Convert an operation id into the corresponding string 46 | """ 47 | for key in operations: 48 | if int(operations[key]) is int(i): 49 | return key 50 | return "Unknown Operation ID %d" % i 51 | 52 | def _getklass(self, name): 53 | module = __import__("graphenebase.operations", fromlist=["operations"]) 54 | class_ = getattr(module, name) 55 | return class_ 56 | 57 | def __bytes__(self): 58 | return bytes(Id(self.opId)) + bytes(self.op) 59 | 60 | def __str__(self): 61 | return json.dumps([self.opId, self.op.toJson()]) 62 | 63 | 64 | class GrapheneObject(object): 65 | """ Core abstraction class 66 | 67 | This class is used for any JSON reflected object in Graphene. 68 | 69 | * ``instance.__json__()``: encodes data into json format 70 | * ``bytes(instance)``: encodes data into wire format 71 | * ``str(instances)``: dumps json object as string 72 | 73 | """ 74 | def __init__(self, data=None): 75 | self.data = data 76 | 77 | def __bytes__(self): 78 | if self.data is None: 79 | return bytes() 80 | b = b"" 81 | for name, value in self.data.items(): 82 | if isinstance(value, str): 83 | b += bytes(value, 'utf-8') 84 | else: 85 | b += bytes(value) 86 | return b 87 | 88 | def __json__(self): 89 | if self.data is None: 90 | return {} 91 | d = {} # JSON output is *not* ordered 92 | for name, value in self.data.items(): 93 | if isinstance(value, Optional) and value.isempty(): 94 | continue 95 | 96 | if isinstance(value, String): 97 | d.update({name: str(value)}) 98 | else: 99 | try: 100 | d.update({name: JsonObj(value)}) 101 | except: 102 | d.update({name: value.__str__()}) 103 | return d 104 | 105 | def __str__(self): 106 | return json.dumps(self.__json__()) 107 | 108 | def toJson(self): 109 | return self.__json__() 110 | 111 | def json(self): 112 | return self.__json__() 113 | 114 | def isArgsThisClass(self, args): 115 | return (len(args) == 1 and type(args[0]).__name__ == type(self).__name__) 116 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/bip38.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from binascii import hexlify, unhexlify 3 | import sys 4 | from .account import PrivateKey 5 | from .base58 import Base58, base58decode 6 | import logging 7 | log = logging.getLogger(__name__) 8 | 9 | try: 10 | from Crypto.Cipher import AES 11 | except ImportError: 12 | raise ImportError("Missing dependency: pycrypto") 13 | 14 | SCRYPT_MODULE = None 15 | if not SCRYPT_MODULE: 16 | try: 17 | import scrypt 18 | SCRYPT_MODULE = "scrypt" 19 | except ImportError: 20 | try: 21 | import pylibscrypt as scrypt 22 | SCRYPT_MODULE = "pylibscrypt" 23 | except ImportError: 24 | raise ImportError( 25 | "Missing dependency: scrypt or pylibscrypt" 26 | ) 27 | 28 | log.debug("Using scrypt module: %s" % SCRYPT_MODULE) 29 | 30 | """ This class and the methods require python3 """ 31 | assert sys.version_info[0] == 3, "graphenelib requires python3" 32 | 33 | 34 | class SaltException(Exception): 35 | pass 36 | 37 | 38 | def _encrypt_xor(a, b, aes): 39 | """ Returns encrypt(a ^ b). """ 40 | a = unhexlify('%0.32x' % (int((a), 16) ^ int(hexlify(b), 16))) 41 | return aes.encrypt(a) 42 | 43 | 44 | def encrypt(privkey, passphrase): 45 | """ BIP0038 non-ec-multiply encryption. Returns BIP0038 encrypted privkey. 46 | 47 | :param privkey: Private key 48 | :type privkey: Base58 49 | :param str passphrase: UTF-8 encoded passphrase for encryption 50 | :return: BIP0038 non-ec-multiply encrypted wif key 51 | :rtype: Base58 52 | 53 | """ 54 | privkeyhex = repr(privkey) # hex 55 | addr = format(privkey.uncompressed.address, "BTC") 56 | a = bytes(addr, 'ascii') 57 | salt = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4] 58 | if SCRYPT_MODULE == "scrypt": 59 | key = scrypt.hash(passphrase, salt, 16384, 8, 8) 60 | elif SCRYPT_MODULE == "pylibscrypt": 61 | key = scrypt.scrypt(bytes(passphrase, "utf-8"), salt, 16384, 8, 8) 62 | else: 63 | raise ValueError("No scrypt module loaded") 64 | (derived_half1, derived_half2) = (key[:32], key[32:]) 65 | aes = AES.new(derived_half2, AES.MODE_ECB) 66 | encrypted_half1 = _encrypt_xor(privkeyhex[:32], derived_half1[:16], aes) 67 | encrypted_half2 = _encrypt_xor(privkeyhex[32:], derived_half1[16:], aes) 68 | " flag byte is forced 0xc0 because Graphene only uses compressed keys " 69 | payload = (b'\x01' + b'\x42' + b'\xc0' + 70 | salt + encrypted_half1 + encrypted_half2) 71 | " Checksum " 72 | checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] 73 | privatkey = hexlify(payload + checksum).decode('ascii') 74 | return Base58(privatkey) 75 | 76 | 77 | def decrypt(encrypted_privkey, passphrase): 78 | """BIP0038 non-ec-multiply decryption. Returns WIF privkey. 79 | 80 | :param Base58 encrypted_privkey: Private key 81 | :param str passphrase: UTF-8 encoded passphrase for decryption 82 | :return: BIP0038 non-ec-multiply decrypted key 83 | :rtype: Base58 84 | :raises SaltException: if checksum verification failed (e.g. wrong password) 85 | 86 | """ 87 | 88 | d = unhexlify(base58decode(encrypted_privkey)) 89 | d = d[2:] # remove trailing 0x01 and 0x42 90 | flagbyte = d[0:1] # get flag byte 91 | d = d[1:] # get payload 92 | assert flagbyte == b'\xc0', "Flagbyte has to be 0xc0" 93 | salt = d[0:4] 94 | d = d[4:-4] 95 | if SCRYPT_MODULE == "scrypt": 96 | key = scrypt.hash(passphrase, salt, 16384, 8, 8) 97 | elif SCRYPT_MODULE == "pylibscrypt": 98 | key = scrypt.scrypt(bytes(passphrase, "utf-8"), salt, 16384, 8, 8) 99 | else: 100 | raise ValueError("No scrypt module loaded") 101 | derivedhalf1 = key[0:32] 102 | derivedhalf2 = key[32:64] 103 | encryptedhalf1 = d[0:16] 104 | encryptedhalf2 = d[16:32] 105 | aes = AES.new(derivedhalf2, AES.MODE_ECB) 106 | decryptedhalf2 = aes.decrypt(encryptedhalf2) 107 | decryptedhalf1 = aes.decrypt(encryptedhalf1) 108 | privraw = decryptedhalf1 + decryptedhalf2 109 | privraw = ('%064x' % (int(hexlify(privraw), 16) ^ 110 | int(hexlify(derivedhalf1), 16))) 111 | wif = Base58(privraw) 112 | """ Verify Salt """ 113 | privkey = PrivateKey(format(wif, "wif")) 114 | addr = format(privkey.uncompressed.address, "BTC") 115 | a = bytes(addr, 'ascii') 116 | saltverify = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4] 117 | if saltverify != salt: 118 | raise SaltException('checksum verification failed! Password may be incorrect.') 119 | return wif 120 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/chains.py: -------------------------------------------------------------------------------- 1 | default_prefix = "COCOS" 2 | 3 | known_chains = { 4 | "prod": { 5 | "chain_id": "6057d856c398875cac2650fe33caef3d5f6b403d184c5154abbff526ec1143c4", 6 | "core_symbol": "COCOS", 7 | "prefix": "COCOS" 8 | }, 9 | "testnet": { 10 | "chain_id": "1ae3653a3105800f5722c5bda2b55530d0e9e8654314e2f3dc6d2b010da641c5", 11 | "core_symbol": "COCOS", 12 | "prefix": "COCOS" 13 | }, 14 | "local": { 15 | "chain_id": "179db3c6a2e08d610f718f05e9cc2aabad62aff80305b9621b162b8b6f2fd79f", 16 | "core_symbol": "COCOS", 17 | "prefix": "COCOS" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/memo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import hashlib 3 | from binascii import hexlify, unhexlify 4 | try: 5 | from Crypto.Cipher import AES 6 | except ImportError: 7 | raise ImportError("Missing dependency: pycrypto") 8 | from .account import PrivateKey, PublicKey 9 | import struct 10 | 11 | " This class and the methods require python3 " 12 | assert sys.version_info[0] == 3, "this library requires python3" 13 | 14 | 15 | def get_shared_secret(priv, pub): 16 | """ Derive the share secret between ``priv`` and ``pub`` 17 | 18 | :param `Base58` priv: Private Key 19 | :param `Base58` pub: Public Key 20 | :return: Shared secret 21 | :rtype: hex 22 | 23 | The shared secret is generated such that:: 24 | 25 | Pub(Alice) * Priv(Bob) = Pub(Bob) * Priv(Alice) 26 | 27 | """ 28 | pub_point = pub.point() 29 | priv_point = int(repr(priv), 16) 30 | res = pub_point * priv_point 31 | res_hex = '%032x' % res.x() 32 | # Zero padding 33 | res_hex = '0' * (64 - len(res_hex)) + res_hex 34 | return res_hex 35 | 36 | 37 | def init_aes(shared_secret, nonce): 38 | """ Initialize AES instance 39 | 40 | :param hex shared_secret: Shared Secret to use as encryption key 41 | :param int nonce: Random nonce 42 | :return: AES instance 43 | :rtype: AES 44 | 45 | """ 46 | " Shared Secret " 47 | ss = hashlib.sha512(unhexlify(shared_secret)).digest() 48 | " Seed " 49 | seed = bytes(str(nonce), 'ascii') + hexlify(ss) 50 | seed_digest = hexlify(hashlib.sha512(seed).digest()).decode('ascii') 51 | " AES " 52 | key = unhexlify(seed_digest[0:64]) 53 | iv = unhexlify(seed_digest[64:96]) 54 | return AES.new(key, AES.MODE_CBC, iv) 55 | 56 | 57 | def _pad(s, BS): 58 | numBytes = (BS - len(s) % BS) 59 | return s + numBytes * struct.pack('B', numBytes) 60 | 61 | 62 | def _unpad(s, BS): 63 | count = int(struct.unpack('B', bytes(s[-1], 'ascii'))[0]) 64 | if bytes(s[-count::], 'ascii') == count * struct.pack('B', count): 65 | return s[:-count] 66 | return s 67 | 68 | 69 | def encode_memo(priv, pub, nonce, message): 70 | """ Encode a message with a shared secret between Alice and Bob 71 | 72 | :param PrivateKey priv: Private Key (of Alice) 73 | :param PublicKey pub: Public Key (of Bob) 74 | :param int nonce: Random nonce 75 | :param str message: Memo message 76 | :return: Encrypted message 77 | :rtype: hex 78 | 79 | """ 80 | shared_secret = get_shared_secret(priv, pub) 81 | aes = init_aes(shared_secret, nonce) 82 | " Checksum " 83 | raw = bytes(message, 'utf8') 84 | checksum = hashlib.sha256(raw).digest() 85 | raw = (checksum[0:4] + raw) 86 | " Padding " 87 | BS = 16 88 | " FIXME: this adds 16 bytes even if not required " 89 | if len(raw) % BS: 90 | raw = _pad(raw, BS) 91 | " Encryption " 92 | return hexlify(aes.encrypt(raw)).decode('ascii') 93 | 94 | 95 | def decode_memo(priv, pub, nonce, message): 96 | """ Decode a message with a shared secret between Alice and Bob 97 | 98 | :param PrivateKey priv: Private Key (of Bob) 99 | :param PublicKey pub: Public Key (of Alice) 100 | :param int nonce: Nonce used for Encryption 101 | :param bytes message: Encrypted Memo message 102 | :return: Decrypted message 103 | :rtype: str 104 | :raise ValueError: if message cannot be decoded as valid UTF-8 105 | string 106 | 107 | """ 108 | shared_secret = get_shared_secret(priv, pub) 109 | aes = init_aes(shared_secret, nonce) 110 | " Encryption " 111 | raw = bytes(message, 'ascii') 112 | cleartext = aes.decrypt(unhexlify(raw)) 113 | " TODO, verify checksum " 114 | message = cleartext[4:] 115 | try: 116 | return _unpad(message.decode('utf8'), 16) 117 | except: 118 | raise ValueError(message) 119 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/objecttypes.py: -------------------------------------------------------------------------------- 1 | #: Object types for object ids 2 | object_type = {} 3 | object_type["null"] = 0 4 | object_type["base"] = 1 5 | object_type["account"] = 2 6 | object_type["asset"] = 3 7 | object_type["force_settlement"] = 4 8 | object_type["committee_member"] = 5 9 | object_type["witness"] = 6 10 | object_type["limit_order"] = 7 11 | object_type["call_order"] = 8 12 | object_type["custom"] = 9 13 | object_type["proposal"] = 10 14 | object_type["operation_history"] = 11 15 | object_type["crontab"] = 12 16 | object_type["withdraw_permission"] = 12 17 | object_type["vesting_balance"] = 13 18 | object_type["worker"] = 14 19 | object_type["balance"] = 15 20 | object_type["contract"] = 16 21 | object_type["contract_data"] = 17 22 | object_type["file"] = 18 23 | # object_type["crontab"] = 19 24 | object_type["OBJECT_TYPE_COUNT"] = 20 25 | 26 | object_type["nh_asset_creator"] = 0 27 | object_type["world_view"] = 1 28 | object_type["nh_asset"] = 2 29 | object_type["nh_asset_order"] = 3 -------------------------------------------------------------------------------- /PythonMiddlewarebase/operationids.py: -------------------------------------------------------------------------------- 1 | #: Operation ids 2 | operations = {} 3 | operations["transfer"] = 0 #pass 4 | operations["limit_order_create"] = 1 #pass 5 | operations["limit_order_cancel"] = 2 #pass 6 | operations["call_order_update"] = 3 #pass 7 | operations["fill_order"] = 4 # virtrual 8 | operations["account_create"] = 5 #pass 9 | operations["account_update"] = 6 10 | operations["account_upgrade"] = 7 #pass 11 | operations["asset_create"] = 8 #pass 12 | operations["asset_update"] = 9 #pass 13 | operations["asset_update_restricted"] = 10 14 | operations["asset_update_bitasset"] = 11 #pass 15 | operations["asset_update_feed_producers"] = 12 #pass 16 | operations["asset_issue"] = 13 #pass 17 | operations["asset_reserve"] = 14 #pass 18 | operations["asset_settle"] = 15 #pass 19 | operations["asset_global_settle"] = 16 #pass 20 | operations["asset_publish_feed"] = 17 #pass 21 | operations["witness_create"] = 18 #pass 22 | operations["witness_update"] = 19 #pass 23 | operations["proposal_create"] = 20 #pass 24 | operations["proposal_update"] = 21 #pass 25 | operations["proposal_delete"] = 22 #pass 26 | operations["committee_member_create"] = 23 #pass 27 | operations["committee_member_update"] = 24 #pass 28 | operations["committee_member_update_global_parameters"] = 25 #defficult 29 | operations["vesting_balance_create"] = 26 #pass 30 | operations["vesting_balance_withdraw"] = 27 #pass 31 | operations["worker_create"] = 28 #pass 32 | operations["balance_claim"] = 29 #pass 33 | operations["asset_settle_cancel"] = 30 # vitrual 34 | operations["asset_claim_fees"] = 31 #pass 35 | operations["bid_collateral"] = 32 # virtural 36 | operations["execute_bid"] = 33 # vitrual 37 | operations["contract_create"] = 34 #pass 38 | operations["call_contract_function"] = 35 #pass 39 | operations["temporary_authority_chang"] = 36 40 | operations["register_nh_asset_creator"] = 37 #pass 41 | operations["create_world_view"] = 38 #pass 42 | operations["relate_world_view"] = 39 #pass 43 | operations["create_nh_asset"] = 40 #pass 44 | operations["delete_nh_asset"] = 41 #pass 45 | operations["transfer_nh_asset"] = 42 #pass 46 | operations["create_nh_asset_order"] = 43 #pass 47 | operations["cancel_nh_asset_order"] = 44 #pass 48 | operations["fill_nh_asset_order"] = 45 #pass 49 | operations["create_file"] = 46 #pass 50 | operations["add_file_relate_account"] = 47 #pass 51 | operations["file_signature"] = 48 #pass 52 | operations["relate_parent_file"] = 49 #pass 53 | operations["revise_contract"] = 50 #pass 54 | operations["crontab_create"] = 51 #pass 55 | operations["crontab_cancel"] = 52 #pass 56 | operations["crontab_recover"] = 53 #pass 57 | operations["update_collateral_for_gas"] = 54 #pass 58 | 59 | 60 | def getOperationNameForId(i): 61 | """ Convert an operation id into the corresponding string 62 | """ 63 | for key in operations: 64 | if int(operations[key]) is int(i): 65 | return key 66 | return "Unknown Operation ID %d" % i 67 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/signedtransactions.py: -------------------------------------------------------------------------------- 1 | from .basesignedtransactions import Signed_Transaction as GrapheneSigned_Transaction 2 | from .operations import Operation 3 | from .chains import known_chains, default_prefix 4 | import logging 5 | # from PythonMiddleware.storage import configStorage as config 6 | # default_prefix = config["default_prefix"] 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | class Signed_Transaction(GrapheneSigned_Transaction): 11 | """ Create a signed transaction and offer method to create the 12 | signature 13 | 14 | :param num refNum: parameter ref_block_num (see ``getBlockParams``) 15 | :param num refPrefix: parameter ref_block_prefix (see ``getBlockParams``) 16 | :param str expiration: expiration date 17 | :param Array operations: array of operations 18 | """ 19 | def __init__(self, *args, **kwargs): 20 | super(Signed_Transaction, self).__init__(*args, **kwargs) 21 | 22 | def sign(self, wifkeys, chain=default_prefix): 23 | return super(Signed_Transaction, self).sign(wifkeys, chain) 24 | 25 | def verify(self, pubkeys=[], chain=default_prefix): 26 | return super(Signed_Transaction, self).verify(pubkeys, chain) 27 | 28 | def getOperationKlass(self): 29 | return Operation 30 | 31 | def getKnownChains(self): 32 | return known_chains 33 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/transactions.py: -------------------------------------------------------------------------------- 1 | from .account import PublicKey 2 | from .chains import known_chains 3 | from .signedtransactions import Signed_Transaction 4 | from .operations import ( 5 | Transfer, 6 | Asset_publish_feed, 7 | Asset_update, 8 | Op_wrapper, 9 | Proposal_create, 10 | Proposal_update, 11 | Limit_order_create, 12 | Limit_order_cancel, 13 | Call_order_update, 14 | Asset_fund_fee_pool, 15 | Override_transfer, 16 | Account_create, 17 | ) 18 | from .objects import Asset 19 | from binascii import hexlify, unhexlify 20 | import struct 21 | from datetime import datetime 22 | import time 23 | # from graphenebase.transactions import getBlockParams, formatTimeFromNow, timeformat 24 | timeformat = '%Y-%m-%dT%H:%M:%S%Z' 25 | 26 | 27 | def getBlockParams(ws): 28 | """ Auxiliary method to obtain ``ref_block_num`` and 29 | ``ref_block_prefix``. Requires a websocket connection to a 30 | witness node! 31 | """ 32 | dynBCParams = ws.get_dynamic_global_properties() 33 | ref_block_num = dynBCParams["head_block_number"] & 0xFFFF 34 | ref_block_prefix = struct.unpack_from("0`) or the past (`x<0`) 42 | :return: Properly formated time for Graphene (`%Y-%m-%dT%H:%M:%S`) 43 | :rtype: str 44 | 45 | """ 46 | return datetime.utcfromtimestamp(time.time() + int(secs)).strftime(timeformat) 47 | 48 | def addRequiredFees(ws, ops, asset_id="1.3.0"): 49 | """ Auxiliary method to obtain the required fees for a set of 50 | operations. Requires a websocket connection to a witness node! 51 | """ 52 | fees = ws.get_required_fees([i.json() for i in ops], asset_id) 53 | # print("fees>>>:", fees) 54 | for i, d in enumerate(ops): 55 | if isinstance(fees[i], list): 56 | # Operation is a proposal 57 | ops[i].op.data["fee"] = Asset( 58 | amount=fees[i][0]["amount"], 59 | asset_id=fees[i][0]["asset_id"] 60 | ) 61 | for j, _ in enumerate(ops[i].op.data["proposed_ops"].data): 62 | ops[i].op.data["proposed_ops"].data[j].data["op"].op.data["fee"] = ( 63 | Asset( 64 | amount=fees[i][1][j]["amount"], 65 | asset_id=fees[i][1][j]["asset_id"])) 66 | else: 67 | # Operation is a regular operation 68 | ops[i].op.data["fee"] = Asset( 69 | amount=fees[i]["amount"], 70 | asset_id=fees[i]["asset_id"] 71 | ) 72 | return ops 73 | -------------------------------------------------------------------------------- /PythonMiddlewarebase/types.py: -------------------------------------------------------------------------------- 1 | import json 2 | import struct 3 | import time 4 | from calendar import timegm 5 | from datetime import datetime 6 | from binascii import hexlify, unhexlify 7 | from collections import OrderedDict 8 | from .objecttypes import object_type 9 | 10 | timeformat = '%Y-%m-%dT%H:%M:%S%Z' 11 | 12 | 13 | def varint(n): 14 | """ Varint encoding 15 | """ 16 | data = b'' 17 | while n >= 0x80: 18 | data += bytes([(n & 0x7f) | 0x80]) 19 | n >>= 7 20 | data += bytes([n]) 21 | return data 22 | 23 | 24 | def varintdecode(data): 25 | """ Varint decoding 26 | """ 27 | shift = 0 28 | result = 0 29 | for c in data: 30 | b = ord(c) 31 | result |= ((b & 0x7f) << shift) 32 | if not (b & 0x80): 33 | break 34 | shift += 7 35 | return result 36 | 37 | 38 | def variable_buffer(s): 39 | """ Encode variable length buffer 40 | """ 41 | return varint(len(s)) + s 42 | 43 | 44 | def JsonObj(data): 45 | """ Returns json object from data 46 | """ 47 | return json.loads(str(data)) 48 | 49 | 50 | class Uint8(): 51 | def __init__(self, d): 52 | self.data = int(d) 53 | 54 | def __bytes__(self): 55 | return struct.pack(" 13 and o < 32: 159 | r.append("u%04x" % o) 160 | else: 161 | r.append(s) 162 | return bytes("".join(r), "utf-8") 163 | 164 | 165 | class Bytes(): 166 | def __init__(self, d, length=None): 167 | self.data = d 168 | if length: 169 | self.length = length 170 | else: 171 | self.length = len(self.data) 172 | 173 | def __bytes__(self): 174 | # FIXME constraint data to self.length 175 | d = unhexlify(bytes(self.data, 'utf-8')) 176 | return varint(len(d)) + d 177 | 178 | def __str__(self): 179 | return str(self.data) 180 | 181 | 182 | class Void(): 183 | def __init__(self): 184 | pass 185 | 186 | def __bytes__(self): 187 | return b'' 188 | 189 | def __str__(self): 190 | return "" 191 | 192 | 193 | class Array(): 194 | def __init__(self, d): 195 | self.data = d 196 | self.length = Varint32(len(self.data)) 197 | 198 | def __bytes__(self): 199 | return bytes(self.length) + b"".join([bytes(a) for a in self.data]) 200 | 201 | def __str__(self): 202 | r = [] 203 | for a in self.data: 204 | try: 205 | r.append(JsonObj(a)) 206 | except: 207 | r.append(str(a)) 208 | return json.dumps(r) 209 | 210 | 211 | class Pair(): 212 | def __init__(self, _type, d): 213 | self._type = _type 214 | self.data = d 215 | def __bytes__(self): 216 | b = b"".join([bytes(self._type),bytes(self.data)]) 217 | return b 218 | def __str__(self): 219 | r = [] 220 | r.append(JsonObj(self._type)) 221 | r.append(JsonObj(self.data)) 222 | return json.dumps(r) 223 | 224 | 225 | class PointInTime(): 226 | def __init__(self, d): 227 | self.data = d 228 | 229 | def __bytes__(self): 230 | return struct.pack(">>: {'ref_block_num': 2565, 'ref_block_prefix': 3142243287, 'extensions': [], 'signatures': ['2075b4799df8add77b27383290af79cf4107c681ee50bfdeb58aee14cd87cc5b431603813a402e326b9c432cc2c5f1cbdad3f32ea29cb63010e8f90615082337ed'], 'operations': [[43, {'owner': '1.2.15', 'extensions': [], 'contract_authority': 'COCOS5X4bfMnAmeWhLoiHKUNrRu7D3LTXKBZQkZvWGj9YCTDBAYaSXU', 'name': 'contract.debug.hello', 'data': "function hello() chainhelper:log('Hello World!') chainhelper:log(date('%Y-%m-%dT%H:%M:%S', chainhelper:time())) end ", 'fee': {'amount': 2122070, 'asset_id': '1.3.0'}}]], 'expiration': '2019-08-23T10:15:08'} 66 | tx======>>: {'ref_block_num': 2565, 'ref_block_prefix': 3142243287, 'extensions': [], 'signatures': ['2075b4799df8add77b27383290af79cf4107c681ee50bfdeb58aee14cd87cc5b431603813a402e326b9c432cc2c5f1cbdad3f32ea29cb63010e8f90615082337ed'], 'operations': [[43, {'owner': '1.2.15', 'extensions': [], 'contract_authority': 'COCOS5X4bfMnAmeWhLoiHKUNrRu7D3LTXKBZQkZvWGj9YCTDBAYaSXU', 'name': 'contract.debug.hello', 'data': "function hello() chainhelper:log('Hello World!') chainhelper:log(date('%Y-%m-%dT%H:%M:%S', chainhelper:time())) end ", 'fee': {'amount': 2122070, 'asset_id': '1.3.0'}}]], 'expiration': '2019-08-23T10:15:08'} 67 | transaction>>>: {'ref_block_num': 2565, 'ref_block_prefix': 3142243287, 'extensions': [], 'signatures': ['2075b4799df8add77b27383290af79cf4107c681ee50bfdeb58aee14cd87cc5b431603813a402e326b9c432cc2c5f1cbdad3f32ea29cb63010e8f90615082337ed'], 'operations': [[43, {'owner': '1.2.15', 'extensions': [], 'contract_authority': 'COCOS5X4bfMnAmeWhLoiHKUNrRu7D3LTXKBZQkZvWGj9YCTDBAYaSXU', 'name': 'contract.debug.hello', 'data': "function hello() chainhelper:log('Hello World!') chainhelper:log(date('%Y-%m-%dT%H:%M:%S', chainhelper:time())) end ", 'fee': {'amount': 2122070, 'asset_id': '1.3.0'}}]], 'expiration': '2019-08-23T10:15:08'} 68 | ['21379327f71198e16d87e98ec143340f616abfb6d0d499598b236b8940df07e7', 69 | {'block': 2566, 70 | 'expiration': '2019-08-23T10:15:08', 71 | 'extensions': [], 72 | 'operation_results': [[2, {'real_running_time': 870, 'result': '1.16.1'}]], 73 | 'operations': [[43, 74 | {'contract_authority': 'COCOS5X4bfMnAmeWhLoiHKUNrRu7D3LTXKBZQkZvWGj9YCTDBAYaSXU', 75 | 'data': "function hello() chainhelper:log('Hello " 76 | "World!') " 77 | "chainhelper:log(date('%Y-%m-%dT%H:%M:%S', " 78 | 'chainhelper:time())) end ', 79 | 'extensions': [], 80 | 'fee': {'amount': 2122070, 'asset_id': '1.3.0'}, 81 | 'name': 'contract.debug.hello', 82 | 'owner': '1.2.15'}]], 83 | 'ref_block_num': 2565, 84 | 'ref_block_prefix': 3142243287, 85 | 'signatures': ['2075b4799df8add77b27383290af79cf4107c681ee50bfdeb58aee14cd87cc5b431603813a402e326b9c432cc2c5f1cbdad3f32ea29cb63010e8f90615082337ed']}] 86 | ''' 87 | 88 | #2. 调用合约 89 | ''' 90 | value_list:>>> [] 91 | value_list:>>> [] 92 | tx.buffer>>>: {'signatures': ['1f1dd6e131e3078857fed44fb6ae55e4d309fb5f9ef775a7c323d7ead60b58ca06218ae0b9d7feacb704a864c824c1997cdf8e42bf20a12776701402f53cf08884'], 'operations': [[44, {'function_name': 'hello', 'extensions': [], 'caller': '1.2.15', 'value_list': [], 'contract_id': '1.16.1', 'fee': {'amount': 2007812, 'asset_id': '1.3.0'}}]], 'expiration': '2019-08-23T10:16:50', 'extensions': [], 'ref_block_prefix': 2891025332, 'ref_block_num': 2612} 93 | tx======>>: {'signatures': ['1f1dd6e131e3078857fed44fb6ae55e4d309fb5f9ef775a7c323d7ead60b58ca06218ae0b9d7feacb704a864c824c1997cdf8e42bf20a12776701402f53cf08884'], 'operations': [[44, {'function_name': 'hello', 'extensions': [], 'caller': '1.2.15', 'value_list': [], 'contract_id': '1.16.1', 'fee': {'amount': 2007812, 'asset_id': '1.3.0'}}]], 'expiration': '2019-08-23T10:16:50', 'extensions': [], 'ref_block_prefix': 2891025332, 'ref_block_num': 2612} 94 | transaction>>>: {'signatures': ['1f1dd6e131e3078857fed44fb6ae55e4d309fb5f9ef775a7c323d7ead60b58ca06218ae0b9d7feacb704a864c824c1997cdf8e42bf20a12776701402f53cf08884'], 'operations': [[44, {'function_name': 'hello', 'extensions': [], 'caller': '1.2.15', 'value_list': [], 'contract_id': '1.16.1', 'fee': {'amount': 2007812, 'asset_id': '1.3.0'}}]], 'expiration': '2019-08-23T10:16:50', 'extensions': [], 'ref_block_prefix': 2891025332, 'ref_block_num': 2612} 95 | ['96207b09abdd65aef8355a4b2efde03268cdc3e841ccba48ee4263756a0b8603', 96 | {'block': 2613, 97 | 'expiration': '2019-08-23T10:16:50', 98 | 'extensions': [], 99 | 'operation_results': [[4, 100 | {'additional_cost': {'amount': 644109, 101 | 'asset_id': '1.3.0'}, 102 | 'contract_affecteds': [[3, 103 | {'affected_account': '1.2.15', 104 | 'message': 'Hello World!'}], 105 | [3, 106 | {'affected_account': '1.2.15', 107 | 'message': '2019-08-23T09:16:50'}]], 108 | 'contract_id': '1.16.1', 109 | 'existed_pv': False, 110 | 'process_value': '', 111 | 'real_running_time': 607}]], 112 | 'operations': [[44, 113 | {'caller': '1.2.15', 114 | 'contract_id': '1.16.1', 115 | 'extensions': [], 116 | 'fee': {'amount': 2007812, 'asset_id': '1.3.0'}, 117 | 'function_name': 'hello', 118 | 'value_list': []}]], 119 | 'ref_block_num': 2612, 120 | 'ref_block_prefix': 2891025332, 121 | 'signatures': ['1f1dd6e131e3078857fed44fb6ae55e4d309fb5f9ef775a7c323d7ead60b58ca06218ae0b9d7feacb704a864c824c1997cdf8e42bf20a12776701402f53cf08884']}] 122 | ''' 123 | 124 | #3. 修改合约内容 125 | ‘’‘ 126 | tx.buffer>>>: {'ref_block_num': 11181, 'ref_block_prefix': 1022871651, 'extensions': [], 'signatures': ['1f33824255bc213b2b3a1ced2c1dd970ffc11345f95250870179d40154a4432dc11b16a373a8527cb0cf617de49669d52d25ef5fa4a4b6826908132aa674201e4d'], 'operations': [[59, {'reviser': '1.2.15', 'fee': {'amount': 2157226, 'asset_id': '1.3.0'}, 'extensions': [], 'data': "function hello() chainhelper:log('hello revise contract test. 2019-08-20 11:13:15') chainhelper:log(date('%Y-%m-%dT%H:%M:%S', chainhelper:time())) end ", 'contract_id': '1.16.2'}]], 'expiration': '2019-09-20T04:15:42'} 127 | tx======>>: {'ref_block_num': 11181, 'ref_block_prefix': 1022871651, 'extensions': [], 'signatures': ['1f33824255bc213b2b3a1ced2c1dd970ffc11345f95250870179d40154a4432dc11b16a373a8527cb0cf617de49669d52d25ef5fa4a4b6826908132aa674201e4d'], 'operations': [[59, {'reviser': '1.2.15', 'fee': {'amount': 2157226, 'asset_id': '1.3.0'}, 'extensions': [], 'data': "function hello() chainhelper:log('hello revise contract test. 2019-08-20 11:13:15') chainhelper:log(date('%Y-%m-%dT%H:%M:%S', chainhelper:time())) end ", 'contract_id': '1.16.2'}]], 'expiration': '2019-09-20T04:15:42'} 128 | transaction>>>: {'ref_block_num': 11181, 'ref_block_prefix': 1022871651, 'extensions': [], 'signatures': ['1f33824255bc213b2b3a1ced2c1dd970ffc11345f95250870179d40154a4432dc11b16a373a8527cb0cf617de49669d52d25ef5fa4a4b6826908132aa674201e4d'], 'operations': [[59, {'reviser': '1.2.15', 'fee': {'amount': 2157226, 'asset_id': '1.3.0'}, 'extensions': [], 'data': "function hello() chainhelper:log('hello revise contract test. 2019-08-20 11:13:15') chainhelper:log(date('%Y-%m-%dT%H:%M:%S', chainhelper:time())) end ", 'contract_id': '1.16.2'}]], 'expiration': '2019-09-20T04:15:42'} 129 | ['9e46323a09989f4b302ad717f7ec1d31237c7b94468cb7a8098f5b508b813894', 130 | {'block': 76718, 131 | 'expiration': '2019-09-20T04:15:42', 132 | 'extensions': [], 133 | 'operation_results': [[5, 134 | {'message': 'e11db591ee109c7e46de186ab93335792c2b6c8cbf69a03a10a3670e603183b0', 135 | 'real_running_time': 1017}]], 136 | 'operations': [[59, 137 | {'contract_id': '1.16.2', 138 | 'data': "function hello() chainhelper:log('hello revise " 139 | "contract test. 2019-08-20 11:13:15') " 140 | "chainhelper:log(date('%Y-%m-%dT%H:%M:%S', " 141 | 'chainhelper:time())) end ', 142 | 'extensions': [], 143 | 'fee': {'amount': 2157226, 'asset_id': '1.3.0'}, 144 | 'reviser': '1.2.15'}]], 145 | 'ref_block_num': 11181, 146 | 'ref_block_prefix': 1022871651, 147 | 'signatures': ['1f33824255bc213b2b3a1ced2c1dd970ffc11345f95250870179d40154a4432dc11b16a373a8527cb0cf617de49669d52d25ef5fa4a4b6826908132aa674201e4d']}] 148 | ’‘’ 149 | 150 | -------------------------------------------------------------------------------- /test/info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from PythonMiddleware.graphene import Graphene 5 | from PythonMiddleware.instance import set_shared_graphene_instance 6 | from pprint import pprint 7 | 8 | import time 9 | 10 | #nodeAddress = "wss://api.cocosbcx.net" 11 | nodeAddress = "ws://test.cocosbcx.net" 12 | #nodeAddress = "ws://127.0.0.1:8049" 13 | gph = Graphene(node=nodeAddress) 14 | set_shared_graphene_instance(gph) 15 | 16 | while True: 17 | print('>> info') 18 | pprint(gph.info()) 19 | time.sleep(2) 20 | 21 | -------------------------------------------------------------------------------- /test/nh_asset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from PythonMiddleware.graphene import Graphene 5 | from PythonMiddleware.instance import set_shared_graphene_instance 6 | from pprint import pprint 7 | from PythonMiddleware.account import Account 8 | from PythonMiddleware.storage import configStorage as config 9 | 10 | nodeAddress = "ws://127.0.0.1:8049" 11 | gph = Graphene(node=nodeAddress, blocking=True) 12 | set_shared_graphene_instance(gph) 13 | 14 | #account info for test 15 | defaultAccount="nicotest" 16 | privateKey="5KgiWEMJPYbLpMhX6jvS9yBehhr4mWZhxX7hfxZQEo3rs8iakUQ" 17 | pub="COCOS5X4bfMnAmeWhLoiHKUNrRu7D3LTXKBZQkZvWGj9YCTDBAYaSXU" 18 | 19 | #创建钱包 20 | if gph.wallet.created() is False: 21 | gph.newWallet("123456") 22 | 23 | #钱包解锁 24 | if gph.wallet.locked() is True: 25 | gph.wallet.unlock("123456") 26 | 27 | #add key 28 | if gph.wallet.getAccountFromPublicKey(pub) is None: 29 | gph.wallet.addPrivateKey(privateKey) #账号私钥导入钱包 30 | pprint(gph.wallet.getPrivateKeyForPublicKey(pub)) 31 | 32 | #config 33 | config["default_prefix"] = gph.rpc.chain_params["prefix"] # 向钱包数据库中添加默认信息 34 | config["default_account"] = defaultAccount # 向钱包数据库中添加默认信息 35 | 36 | #account test 37 | pprint(gph.wallet.removeAccount(None)) 38 | pprint(gph.wallet.getAccounts()) 39 | 40 | #创建账号 41 | # try: 42 | # pprint(gph.create_account(account_name="test3", password="123456")) 43 | # except Exception as e: 44 | # print(repr(e)) 45 | # gph.wallet.removeAccount(None) 46 | #pprint(gph.wallet.getAccounts()) 47 | account="nicotest" 48 | #pprint(gph.register_nh_asset_creator(account)) 49 | #pprint(gph.create_world_view("snacktest", account)) 50 | 51 | #pprint(gph.create_nh_asset(account, "COCOS", "snacktest", '{"name":"test1"}', account)) 52 | #pprint(gph.delete_nh_asset("4.2.4", account)) 53 | #pprint(gph.transfer_nh_asset("test1", "4.2.5", account)) 54 | 55 | #pprint(gph.create_nh_asset_order("test1", 1, "1.3.0", "4.2.4", " sell nh asset order test ", 100, "1.3.0", account)) 56 | #pprint(gph.create_nh_asset_order("test1", 1, "1.3.0", "4.2.6", " sell nh asset order test ", 100, "1.3.0", account)) 57 | 58 | #pprint(gph.cancel_nh_asset_order("4.3.1", account)) 59 | 60 | pprint(gph.fill_nh_asset_order("4.3.2", account)) 61 | 62 | -------------------------------------------------------------------------------- /test/unittest/account_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class AccountTestCase(unittest.TestCase): 7 | def testCreateAccount(self): 8 | params = { 9 | "account_name": "testaccount7", 10 | "password": "123456" 11 | } 12 | gph = Config().gph 13 | try: 14 | print("CreateAccount:", gph.create_account(**params)) 15 | except Exception as e: 16 | print(repr(e)) 17 | gph.wallet.removeAccount(None) 18 | 19 | def testTransfer(self): 20 | params = { 21 | "to" : "testaccount7", 22 | "amount": "10000000", 23 | "asset": "1.3.0", 24 | "memo" : ["123124134",0], 25 | "account" : "1.2.16" 26 | } 27 | gph = Config().gph 28 | try: 29 | print("Transfer:", gph.transfer(**params)) 30 | except Exception as e: 31 | print(repr(e)) 32 | 33 | def testUpgradeAccount(self): 34 | params = { 35 | "account" : "testaccount7" 36 | } 37 | gph = Config().gph 38 | try: 39 | print("UpgradeAccount:", gph.upgrade_account(**params)) 40 | except Exception as e: 41 | print(repr(e)) 42 | 43 | if __name__ == "__main__": 44 | # case1 = AccountTestCase("testCreateAccount") 45 | # case1() 46 | case2 = AccountTestCase("testTransfer") 47 | case2() 48 | # case3 = AccountTestCase("testUpgradeAccount") 49 | # case3() -------------------------------------------------------------------------------- /test/unittest/asset_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class AssetTestCase(unittest.TestCase): 7 | def testCreateAsset(self): 8 | params = { 9 | "symbol": "TESTCOIN", 10 | "precision": 5, 11 | "common_options": { 12 | "max_supply": 10000000000000, 13 | "market_fee_percent": 0, 14 | "max_market_fee": 10000000000000, 15 | "issuer_permissions": 0, 16 | "flags": 0, 17 | "description": '', 18 | "extension": {} 19 | }, 20 | "account": "1.2.25" 21 | } 22 | gph = Config().gph 23 | try: 24 | print("CreateAsset:", gph.asset_create(**params)) 25 | except Exception as e: 26 | print(repr(e)) 27 | 28 | def testCreateBitAsset(self): 29 | params = { 30 | "symbol": "TESTBCOIN", 31 | "precision": 5, 32 | "common_options": { 33 | "max_supply": 10000000000000, 34 | "market_fee_percent": 0, 35 | "max_market_fee": 10000000000000, 36 | "issuer_permissions": 32, 37 | "flags": 0, 38 | "description": '', 39 | "extension": {} 40 | }, 41 | "bitasset_opts": { 42 | "feed_lifetime_sec": 60*60, 43 | "minimum_feeds": 1, 44 | "force_settlement_delay_sec": 60*60, 45 | "force_settlement_offset_percent": 0, 46 | "maximum_force_settlement_volume": 3000, 47 | "short_backing_asset": "1.3.0", 48 | "extensions": {} 49 | }, 50 | "account": "1.2.25" 51 | } 52 | gph = Config().gph 53 | try: 54 | print("CreateBitAsset:", gph.asset_create(**params)) 55 | except Exception as e: 56 | print(repr(e)) 57 | 58 | def testUpdateAsset(self): 59 | params = { 60 | "asset": "1.3.3", 61 | "new_options": { 62 | "max_supply": 10000000000000, 63 | "market_fee_percent": 0, 64 | "max_market_fee": 10000000000000, 65 | "issuer_permissions": 1, 66 | "flags": 0, 67 | "description": '', 68 | "extension": {} 69 | }, 70 | "issuer": None, 71 | "account": "1.2.25" 72 | } 73 | gph = Config().gph 74 | try: 75 | print("UpdateAsset:", gph.asset_update(**params)) 76 | except Exception as e: 77 | print(repr(e)) 78 | 79 | def testUpdateBitAsset(self): 80 | params = { 81 | "asset": "1.3.4", 82 | "new_options": { 83 | "feed_lifetime_sec": 60*60, 84 | "minimum_feeds": 1, 85 | "force_settlement_delay_sec": 60*60, 86 | "force_settlement_offset_percent": 0, 87 | "maximum_force_settlement_volume": 2000, 88 | "short_backing_asset": "1.3.0", 89 | "extensions": {} 90 | }, 91 | "account": "1.2.25" 92 | } 93 | gph = Config().gph 94 | try: 95 | print("UpdateBitAsset:", gph.asset_update_bitasset(**params)) 96 | except Exception as e: 97 | print(repr(e)) 98 | 99 | def testIssueAsset(self): 100 | params = { 101 | "amount": 10000000, 102 | "asset": "1.3.3", 103 | "issue_to_account": "1.2.25", 104 | "memo": ["",0], 105 | "account": "1.2.25" 106 | } 107 | gph = Config().gph 108 | try: 109 | print("IssueAsset:", gph.asset_issue(**params)) 110 | except Exception as e: 111 | print(repr(e)) 112 | 113 | def testReserveAsset(self): 114 | params = { 115 | "amount": 1000000, 116 | "asset": "1.3.3", 117 | "account": "1.2.25" 118 | } 119 | gph = Config().gph 120 | try: 121 | print("ReserveAsset:", gph.asset_reserve(**params)) 122 | except Exception as e: 123 | print(repr(e)) 124 | 125 | def testUpdateFeedProducers(self): 126 | params = { 127 | "asset": "1.3.4", 128 | "feed_producers": ["1.2.25"], 129 | "account": "1.2.25" 130 | } 131 | gph = Config().gph 132 | try: 133 | print("UpdateFeedProducers:", gph.asset_update_feed_producers(**params)) 134 | except Exception as e: 135 | print(repr(e)) 136 | 137 | def testPublishPriceFeed(self): 138 | params = { 139 | "symbol": "TESTBCOIN", 140 | "settlement_price": { 141 | "base": { 142 | "amount": 1, 143 | "asset_id": "1.3.4" 144 | }, 145 | "quote": { 146 | "amount": 2, 147 | "asset_id": "1.3.0" 148 | } 149 | }, 150 | "account": "1.2.25" 151 | } 152 | gph = Config().gph 153 | try: 154 | print("PublishPriceFeed:", gph.publish_price_feed(**params)) 155 | except Exception as e: 156 | print(repr(e)) 157 | 158 | def testBorrow(self): 159 | params = { 160 | "amount": 50, 161 | "asset": "1.3.0", 162 | "_amount": 10, 163 | "_asset": "1.3.4", 164 | "account": "1.2.25" 165 | } 166 | gph = Config().gph 167 | try: 168 | print("BorrowAsset:", gph.call_order_update(**params)) 169 | except Exception as e: 170 | print(repr(e)) 171 | 172 | def testSettleAsset(self): 173 | params = { 174 | "amount": 2, 175 | "asset": "1.3.4", 176 | "account": "1.2.25" 177 | } 178 | gph = Config().gph 179 | try: 180 | print("SettleAsset:", gph.asset_settle(**params)) 181 | except Exception as e: 182 | print(repr(e)) 183 | 184 | def testGlobalSettleAsset(self): 185 | params = { 186 | "asset_to_settle": "1.3.4", 187 | "settle_price": { 188 | "base": { 189 | "amount":1, 190 | "asset_id":"1.3.4" 191 | }, 192 | "quote":{ 193 | "amount":2, 194 | "asset_id":"1.3.0" 195 | } 196 | }, 197 | "account": "1.2.25" 198 | } 199 | gph = Config().gph 200 | try: 201 | print("GlobalSettleAsset:", gph.asset_global_settle(**params)) 202 | except Exception as e: 203 | print(repr(e)) 204 | 205 | def testCreateLimitOrder(self): 206 | params = { 207 | "amount": 1000, 208 | "asset": "1.3.3", 209 | "min_amount": 990, 210 | "min_amount_asset": "1.3.0" , 211 | "fill": False, 212 | "account": "1.2.25" 213 | } 214 | gph = Config().gph 215 | try: 216 | print("CreateLimitOrder:", gph.limit_order_create(**params)) 217 | except Exception as e: 218 | print(repr(e)) 219 | 220 | def testCancelLimitOrder(self): 221 | params = { 222 | "order_numbers": ["1.7.0"], 223 | "account": "1.2.25" 224 | } 225 | gph = Config().gph 226 | try: 227 | print("CancelLimitOrder:", gph.limit_order_cancel(**params)) 228 | except Exception as e: 229 | print(repr(e)) 230 | 231 | def testAssetClaimFees(self): 232 | params = { 233 | "amount": 1, 234 | "asset": "1.3.1", 235 | "account": "1.2.16" 236 | } 237 | gph = Config().gph 238 | try: 239 | print("CancelLimitOrder:", gph.asset_claim_fees(**params)) 240 | except Exception as e: 241 | print(repr(e)) 242 | 243 | if __name__ == "__main__": 244 | # case1 = AssetTestCase("testCreateAsset") 245 | # case1() 246 | # case2 = AssetTestCase("testCreateBitAsset") 247 | # case2() 248 | # case3 = AssetTestCase("testUpdateAsset") 249 | # case3() 250 | # case4 = AssetTestCase("testUpdateBitAsset") 251 | # case4() 252 | case5 = AssetTestCase("testIssueAsset") 253 | case5() 254 | # case6 = AssetTestCase("testReserveAsset") 255 | # case6() 256 | # case7 = AssetTestCase("testUpdateFeedProducers") 257 | # case7() 258 | # case8 = AssetTestCase("testPublishPriceFeed") 259 | # case8() 260 | # case9 = AssetTestCase("testBorrow") 261 | # case9() 262 | # case10 = AssetTestCase("testSettleAsset") 263 | # case10() 264 | # case11 = AssetTestCase("testGlobalSettleAsset") 265 | # case11() 266 | # case12 = AssetTestCase("testCreateLimitOrder") 267 | # case12() 268 | # case13 = AssetTestCase("testCancelLimitOrder") 269 | # case13() 270 | #case14 = AssetTestCase("testAssetClaimFees") 271 | #case14() -------------------------------------------------------------------------------- /test/unittest/balance_claim_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class BalanceClaimTestCase(unittest.TestCase): 7 | def testClaimBalance(self): 8 | params = { 9 | "balance_to_claim": "1.15.0", 10 | "balance_owner_key": "COCOS7yE9skpBAirth3eSNMRtwq1jYswEE3uSbbuAtXTz88HtbpQsZf", 11 | "amount": 5000000000000, 12 | "asset": "1.3.0", 13 | "account": "1.2.16" 14 | } 15 | gph = Config().gph 16 | try: 17 | print("ClaimBalance:", gph.balance_claim(**params)) 18 | except Exception as e: 19 | print(repr(e)) 20 | 21 | 22 | if __name__ == "__main__": 23 | case1 = BalanceClaimTestCase("testClaimBalance") 24 | case1() -------------------------------------------------------------------------------- /test/unittest/committee_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class CommitteeTestCase(unittest.TestCase): 7 | def testCreateCommittee(self): 8 | params = { 9 | "url": " ", 10 | "account": "1.2.25" 11 | } 12 | gph = Config().gph 13 | try: 14 | print("CreateCommittee:", gph.committee_member_create(**params)) 15 | except Exception as e: 16 | print(repr(e)) 17 | 18 | def testUpdateCommittee(self): 19 | params = { 20 | "work_status": True, 21 | "new_url": "www.1234.com", 22 | "account": "1.2.25" 23 | } 24 | gph = Config().gph 25 | try: 26 | print("UpdateCommittee:", gph.committee_member_update(**params)) 27 | except Exception as e: 28 | print(repr(e)) 29 | 30 | def testApproveCommittee(self): 31 | params = { 32 | "committees": ["testaccount7"], 33 | "vote_type": 0, 34 | "vote_amount": 10, 35 | "vote_asset": "1.3.0", 36 | "account": "1.2.16" 37 | } 38 | gph = Config().gph 39 | try: 40 | print("ApproveCommittee:", gph.approve_committee(**params)) 41 | except Exception as e: 42 | print(repr(e)) 43 | 44 | def testDisApproveCommittee(self): 45 | params = { 46 | "committees": ["testaccount7"], 47 | "vote_type": 0, 48 | "vote_amount": 1, 49 | "vote_asset": "1.3.0", 50 | "account": "1.2.14" 51 | } 52 | gph = Config().gph 53 | try: 54 | print("DisApproveCommittee:", gph.disapprove_committee(**params)) 55 | except Exception as e: 56 | print(repr(e)) 57 | 58 | 59 | if __name__ == "__main__": 60 | # case1 = CommitteeTestCase("testCreateCommittee") 61 | # case1() 62 | # case2 = CommitteeTestCase("testUpdateCommittee") 63 | # case2() 64 | case3 = CommitteeTestCase("testApproveCommittee") 65 | case3() 66 | # case4 = CommitteeTestCase("testDisApproveCommittee") 67 | # case4() -------------------------------------------------------------------------------- /test/unittest/config.py: -------------------------------------------------------------------------------- 1 | from PythonMiddleware.graphene import Graphene 2 | from PythonMiddleware.instance import set_shared_graphene_instance 3 | from PythonMiddleware.storage import configStorage as config 4 | 5 | ''' 6 | test1 7 | { 8 | "brain_priv_key": "WAKENER PAXIUBA UNCOWED CAMISIA REWEAVE DARDAOL SAFFLOW ADAWE SNOWL MUSCOSE RAS STYLITE VINTRY AXION TARHOOD VIBRATE", 9 | "wif_priv_key": "5JAt3WmMCqQvAqqq4Mr7ZisN8ztrrPZCTHCN7f8Vrx8j1cHY4hy", 10 | "address_info": "COCOSMHfYkwN2xezycrrhcijnXwwZn8s1zd89m", 11 | "pub_key": "COCOS8m1rD2w5q2fJB89MaNJRhnYppdrkEtWB71FLMjfL2xXhCnXAqn" 12 | } 13 | ''' 14 | 15 | class Config(): 16 | node_address = "ws://127.0.0.1:8049" 17 | blocking = True 18 | wifkey = "5JAt3WmMCqQvAqqq4Mr7ZisN8ztrrPZCTHCN7f8Vrx8j1cHY4hy" 19 | pub_key = "" 20 | wallet_pwd = "ping" 21 | config["default_account"] = "test1" 22 | def __init__(self, **kwargs): 23 | print("kwargs:", kwargs) 24 | 25 | self.gph = Graphene(node=self.node_address, blocking=self.blocking, **kwargs) 26 | set_shared_graphene_instance(self.gph) 27 | if self.gph.wallet.created() is False: 28 | self.gph.wallet.newWallet(self.wallet_pwd) 29 | self.gph.wallet.unlock(self.wallet_pwd) 30 | try: 31 | self.gph.wallet.addPrivateKey(self.wifkey) 32 | except: 33 | pass 34 | -------------------------------------------------------------------------------- /test/unittest/contract_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class ContractTestCase(unittest.TestCase): 7 | def testCreateContract(self): 8 | data = """ 9 | function hello() 10 | chainhelper:log('Hello World! Hello Python') 11 | end""" 12 | 13 | params = { 14 | "name": "contract.test02", 15 | "data": data, 16 | "con_authority": "COCOS5gnHLSysPddPg8aHmgxbdt6JoDJHKtJiW57UkL854C3sW9BXoK", 17 | "account": "1.2.26" 18 | } 19 | gph = Config().gph 20 | try: 21 | print("CreateContract:", gph.create_contract(**params)) 22 | except Exception as e: 23 | print(repr(e)) 24 | 25 | def testReviseContract(self): 26 | data = """ 27 | function hello() 28 | chainhelper:log('Hello World! Hello') 29 | end""" 30 | 31 | params = { 32 | "contract": "contract.test02", 33 | "data": data, 34 | "account": "1.2.26" 35 | } 36 | gph = Config().gph 37 | try: 38 | print("ReviseContract:", gph.revise_contract(**params)) 39 | except Exception as e: 40 | print(repr(e)) 41 | 42 | def testCallContractFunction(self): 43 | params = { 44 | "contract": "contract.test02", 45 | "function": "hello", 46 | "value_list": [], 47 | "account": "1.2.25" 48 | } 49 | gph = Config().gph 50 | try: 51 | print("CallContractFunction:", gph.call_contract_function(**params)) 52 | except Exception as e: 53 | print(repr(e)) 54 | 55 | 56 | if __name__ == "__main__": 57 | case1 = ContractTestCase("testCreateContract") 58 | case1() 59 | case2 = ContractTestCase("testReviseContract") 60 | case2() 61 | case3 = ContractTestCase("testCallContractFunction") 62 | case3() -------------------------------------------------------------------------------- /test/unittest/crontab_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class CrontabTestCase(unittest.TestCase): 7 | def testCreateCrontab(self): 8 | params = { 9 | "to" : "testaccount7", 10 | "amount": "100", 11 | "asset": "1.3.0", 12 | "memo" : "", 13 | "account" : "1.2.14" 14 | } 15 | gph = Config(crontaber="1.2.14", crontab_start_time="2019-08-08T06:52:00", crontab_execute_interval=30, crontab_scheduled_execute_times=10).gph 16 | try: 17 | print("CreateCrontab:", gph.transfer(**params)) 18 | except Exception as e: 19 | print(repr(e)) 20 | 21 | def testCancelCrontab(self): 22 | params = { 23 | "task": "1.12.3", 24 | "account": "1.2.14" 25 | } 26 | gph = Config().gph 27 | try: 28 | print("CancelCrontab", gph.crontab_cancel(**params)) 29 | except Exception as e: 30 | print(repr(e)) 31 | 32 | def testRecoverCrontab(self): 33 | params = { 34 | "crontab": "1.12.3", 35 | "restart_time": "2019-08-08T06:55:00", 36 | "account": "1.2.14" 37 | } 38 | gph = Config().gph 39 | try: 40 | print("RecoverCrontab:", gph.crontab_recover(**params)) 41 | except Exception as e: 42 | print(repr(e)) 43 | 44 | 45 | 46 | if __name__ == "__main__": 47 | # case1 = CrontabTestCase("testCreateCrontab") 48 | # case1() 49 | case2 = CrontabTestCase("testCancelCrontab") 50 | case2() 51 | # case3 = CrontabTestCase("testRecoverCrontab") 52 | # case3() -------------------------------------------------------------------------------- /test/unittest/file_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class FileTestCase(unittest.TestCase): 7 | def testCreateFile(self): 8 | params = { 9 | "filename": "hi-2", 10 | "content": "hi,2", 11 | "account": "1.2.24" 12 | } 13 | gph = Config().gph 14 | try: 15 | print("CreateFile:", gph.create_file(**params)) 16 | except Exception as e: 17 | print(repr(e)) 18 | 19 | def testAddFileRelateAccount(self): 20 | params = { 21 | "file": "hi-1", 22 | "related_account": ["1.2.25"], 23 | "account": "1.2.26" 24 | } 25 | gph = Config().gph 26 | try: 27 | print("AddFileRelateAccount", gph.add_file_relate_account(**params)) 28 | except Exception as e: 29 | print(repr(e)) 30 | 31 | def testSignatureFile(self): 32 | params = { 33 | "file": "hi-1", 34 | "signature": "ok!", 35 | "account": "1.2.25" 36 | } 37 | gph = Config().gph 38 | try: 39 | print("SignatureFile:", gph.file_signature(**params)) 40 | except Exception as e: 41 | print(repr(e)) 42 | 43 | def testRelateParentFile(self): 44 | params = { 45 | "parent_file": "hi-1", 46 | "sub_file": "hi-2" 47 | } 48 | gph = Config(proposer="1.2.24").gph 49 | try: 50 | print("RelateParentFile:", gph.relate_parent_file(**params)) 51 | except Exception as e: 52 | print(repr(e)) 53 | 54 | def testApproveProposal(self): 55 | params = { 56 | "proposal_ids": ["1.10.18"], 57 | "account": "1.2.26" 58 | } 59 | gph = Config().gph 60 | try: 61 | print("ApproveProposal:", gph.approveproposal(**params)) 62 | except Exception as e: 63 | print(repr(e)) 64 | 65 | 66 | if __name__ == "__main__": 67 | # case1 = FileTestCase("testCreateFile") 68 | # case1() 69 | # case2 = FileTestCase("testAddFileRelateAccount") 70 | # case2() 71 | # case3 = FileTestCase("testSignatureFile") 72 | # case3() 73 | # case4 = FileTestCase("testRelateParentFile") 74 | # case4() 75 | case5 = FileTestCase("testApproveProposal") 76 | case5() -------------------------------------------------------------------------------- /test/unittest/gas_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class GasTestCase(unittest.TestCase): 7 | def testUpdateCollateralForGas(self): 8 | params = { 9 | "beneficiary": "1.2.26", 10 | "collateral": 1000, 11 | "account": "1.2.25" 12 | } 13 | gph = Config().gph 14 | try: 15 | print("UpdateCollateralForGas:", gph.update_collateral_for_gas(**params)) 16 | except Exception as e: 17 | print(repr(e)) 18 | 19 | 20 | 21 | if __name__ == "__main__": 22 | case1 = GasTestCase("testUpdateCollateralForGas") 23 | case1() -------------------------------------------------------------------------------- /test/unittest/nh_asset_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class NhAssetTestCase(unittest.TestCase): 7 | def testRegisterNHCreator(self): 8 | params = { 9 | "account": "1.2.19" 10 | } 11 | gph = Config().gph 12 | try: 13 | print("RegisterNHAssetCreator:", gph.register_nh_asset_creator(**params)) 14 | except Exception as e: 15 | print(repr(e)) 16 | 17 | def testCreateWorldView(self): 18 | params = { 19 | "world_view": "SKY", 20 | "account": "1.2.19" 21 | } 22 | gph = Config().gph 23 | try: 24 | print("CreateWorldView:", gph.create_world_view(**params)) 25 | except Exception as e: 26 | print(repr(e)) 27 | 28 | def testRelateWorldView(self): 29 | params = { 30 | "world_view": "SKY", 31 | "account": "1.2.24" 32 | } 33 | gph = Config(proposer="1.2.24").gph 34 | try: 35 | print("RelateWorldView:", gph.relate_world_view(**params)) 36 | except Exception as e: 37 | print(repr(e)) 38 | 39 | def testApproveProposal(self): 40 | params = { 41 | "proposal_ids": ["1.10.15"], 42 | "account": "1.2.25" 43 | } 44 | gph = Config().gph 45 | try: 46 | print("ApproveProposal:", gph.approveproposal(**params)) 47 | except Exception as e: 48 | print(repr(e)) 49 | 50 | def testCreateNhAsset(self): 51 | params = { 52 | "owner": "1.2.19", 53 | "assetID": "COCOS", 54 | "world_view": "SKY", 55 | "describe": "bird-1", 56 | "account": "1.2.19" 57 | } 58 | gph = Config().gph 59 | try: 60 | print("CreateNHAsset:", gph.create_nh_asset(**params)) 61 | except Exception as e: 62 | print(repr(e)) 63 | 64 | def testTransferNhAsset(self): 65 | params = { 66 | "to": "1.2.25", 67 | "nh_asset_id": "4.2.2", 68 | "account": "1.2.24" 69 | } 70 | gph = Config().gph 71 | try: 72 | print("TransferNHAsset:", gph.transfer_nh_asset(**params)) 73 | except Exception as e: 74 | print(repr(e)) 75 | 76 | def testDeleteNhAsset(self): 77 | params = { 78 | "asset_id": "4.2.2", 79 | "account": "1.2.26" 80 | } 81 | gph = Config().gph 82 | try: 83 | print("DeleteNHAsset:", gph.delete_nh_asset(**params)) 84 | except Exception as e: 85 | print(repr(e)) 86 | 87 | def testCreateNhAssetOrder(self): 88 | params = { 89 | "otcaccount": "1.2.14", 90 | "pending_order_fee_amount": 5, 91 | "pending_order_fee_asset": "TESTCOIN", 92 | "nh_asset": "4.2.2", 93 | "memo": "nice toy", 94 | "price_amount": 100, 95 | "price": "COCOS", 96 | "account": "1.2.25" 97 | } 98 | gph = Config().gph 99 | try: 100 | print("CreateNHAssetOrder:", gph.create_nh_asset_order(**params)) 101 | except Exception as e: 102 | print(repr(e)) 103 | 104 | def testFillNhAssetOrder(self): 105 | params = { 106 | "order": "4.3.2", 107 | "account": "1.2.14" 108 | } 109 | gph = Config().gph 110 | try: 111 | print("FillNHAssetOrder:", gph.fill_nh_asset_order(**params)) 112 | except Exception as e: 113 | print(repr(e)) 114 | 115 | 116 | if __name__ == "__main__": 117 | # case1 = NhAssetTestCase("testRegisterNHCreator") 118 | # case1() 119 | # case2 = NhAssetTestCase("testCreateWorldView") 120 | # case2() 121 | # case3 = NhAssetTestCase("testRelateWorldView") 122 | # case3() 123 | # case4 = NhAssetTestCase("testApproveProposal") 124 | # case4() 125 | case5 = NhAssetTestCase("testCreateNhAsset") 126 | case5() 127 | # case6 = NhAssetTestCase("testTransferNhAsset") 128 | # case6() 129 | # case7 = NhAssetTestCase("testDeleteNhAsset") 130 | # case7() 131 | # case8 = NhAssetTestCase("testCreateNhAssetOrder") 132 | # case8() 133 | # case9 = NhAssetTestCase("testFillNhAssetOrder") 134 | # case9() -------------------------------------------------------------------------------- /test/unittest/vesting_balance_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class VestingBalanceTestCase(unittest.TestCase): 7 | def testCreateVestingBalance(self): 8 | params = { 9 | "owner": "testaccount6", 10 | "amount": 10000, 11 | "asset": "1.3.4", 12 | "start": "2019-08-6T10:00:00", 13 | "_type": "cdd", 14 | "account": "1.2.26" 15 | } 16 | gph = Config().gph 17 | try: 18 | print("CreateVestingBalance:", gph.vesting_balance_create(**params)) 19 | except Exception as e: 20 | print(repr(e)) 21 | 22 | def testWithdrawVestingBalance(self): 23 | params = { 24 | "vesting_id": "1.13.10", 25 | "amount": 1000, 26 | "asset": "1.3.4", 27 | "account": "1.2.25" 28 | } 29 | gph = Config().gph 30 | try: 31 | print("DisApproveWitness:", gph.vesting_balance_withdraw(**params)) 32 | except Exception as e: 33 | print(repr(e)) 34 | 35 | 36 | if __name__ == "__main__": 37 | case1 = VestingBalanceTestCase("testCreateVestingBalance") 38 | case1() 39 | case2 = VestingBalanceTestCase("testWithdrawVestingBalance") 40 | case2() -------------------------------------------------------------------------------- /test/unittest/witness_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class WitnessTestCase(unittest.TestCase): 7 | def testCreateWitness(self): 8 | params = { 9 | "account_name": "1.2.26", 10 | "url": "", 11 | "key": "COCOS8mv3AjCiVaLhp1varnw7E4ztSbykA1D7Wko3hQweKF5yMV2vN7" 12 | } 13 | gph = Config().gph 14 | try: 15 | print("CreateWitness:", gph.create_witness(**params)) 16 | except Exception as e: 17 | print(repr(e)) 18 | 19 | def testUpdateWitness(self): 20 | params = { 21 | "witness_identifier": "1.2.26", 22 | "work_status": True, 23 | "url": "www.12.com", 24 | "key": "COCOS8mv3AjCiVaLhp1varnw7E4ztSbykA1D7Wko3hQweKF5yMV2vN7" 25 | } 26 | gph = Config().gph 27 | try: 28 | print("UpdateWitness:", gph.update_witness(**params)) 29 | except Exception as e: 30 | print(repr(e)) 31 | 32 | def testApproveWitness(self): 33 | params = { 34 | "witnesses": ["testaccount7"], 35 | "vote_type": 1, 36 | "vote_amount": 10, 37 | "vote_asset": "1.3.0", 38 | "account": "1.2.14" 39 | } 40 | gph = Config().gph 41 | try: 42 | print("ApproveWitness:", gph.approve_witness(**params)) 43 | except Exception as e: 44 | print(repr(e)) 45 | 46 | def testDisApproveWitness(self): 47 | params = { 48 | "witnesses": ["testaccount7"], 49 | "vote_type": 1, 50 | "vote_amount": 10, 51 | "vote_asset": "1.3.0", 52 | "account": "1.2.14" 53 | } 54 | gph = Config().gph 55 | try: 56 | print("DisApproveWitness:", gph.disapprove_witness(**params)) 57 | except Exception as e: 58 | print(repr(e)) 59 | 60 | 61 | if __name__ == "__main__": 62 | case1 = WitnessTestCase("testCreateWitness") 63 | case1() 64 | case2 = WitnessTestCase("testUpdateWitness") 65 | case2() 66 | case3 = WitnessTestCase("testApproveWitness") 67 | case3() 68 | case4 = WitnessTestCase("testDisApproveWitness") 69 | case4() -------------------------------------------------------------------------------- /test/unittest/worker_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from config import Config 4 | 5 | 6 | class WorkerTestCase(unittest.TestCase): 7 | def testCreateWorker(self): #begin time should > expirtion_time(3600), need a half committee approve proposal --->allow_execution:True 8 | params = { 9 | "name": "worked", 10 | "daily_pay": 100, 11 | "end": "2019-08-09T04:02:00", 12 | "describe": "sdfasdf", 13 | "begin": "2019-08-08T04:02:00", 14 | "payment_type": "vesting", 15 | "account": "1.2.23" 16 | } 17 | gph = Config(proposer="1.2.23",proposal_review=1800).gph 18 | try: 19 | print("CreateWorker:", gph.create_worker(**params)) 20 | except Exception as e: 21 | print(repr(e)) 22 | 23 | def testApproveProposal(self): 24 | params = { 25 | "proposal_ids": ["1.10.14"], 26 | "account": "1.2.14" 27 | } 28 | gph = Config().gph 29 | try: 30 | print("ApproveProposal:", gph.approveproposal(**params)) 31 | except Exception as e: 32 | print(repr(e)) 33 | 34 | 35 | if __name__ == "__main__": 36 | # case1 = WorkerTestCase("testCreateWorker") 37 | # case1() 38 | case2 = WorkerTestCase("testApproveProposal") 39 | case2() --------------------------------------------------------------------------------