def compareConstructedTX(self): self.maxDiff = None self.op = operations.Call_order_update( **{ "fee": { "amount": 100, "asset_id": "1.3.0" }, "delta_debt": { "amount": 10000, "asset_id": "1.3.22" }, "delta_collateral": { "amount": 100000000, "asset_id": "1.3.0" }, "funding_account": "1.2.29", "extensions": { "target_collateral_ratio": 12345 }, }) ops = [Operation(self.op)] tx = Signed_Transaction( ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops, ) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], prefix) txWire = hexlify(bytes(tx)).decode("ascii") print("=" * 80) pprint(tx.json()) print("=" * 80) # Test against Bitshares backened self.cm = bitshares.rpc.get_transaction_hex(tx.json()) print("soll: %s" % self.cm[:-130]) print("ist: %s" % txWire[:-130]) print(txWire[:-130] == self.cm[:-130]) self.assertEqual(self.cm[:-130], txWire[:-130])
def sign(self): """ Sign a provided transaction with the provided key(s) :param dict tx: The transaction to be signed and returned :param string wifs: One or many wif keys to use for signing a transaction. If not present, the keys will be loaded from the wallet as defined in "missing_signatures" key of the transactions. """ self.constructTx() if "operations" not in self or not self["operations"]: return # Legacy compatibility! # If we are doing a proposal, obtain the account from the proposer_id if self.blockchain.proposer: proposer = Account(self.blockchain.proposer, blockchain_instance=self.blockchain) self.wifs = set() self.signing_accounts = list() self.appendSigner(proposer["id"], "active") # We need to set the default prefix, otherwise pubkeys are # presented wrongly! if self.blockchain.rpc: operations.default_prefix = ( self.blockchain.rpc.chain_params["prefix"]) elif "blockchain" in self: operations.default_prefix = self["blockchain"]["prefix"] try: signedtx = Signed_Transaction(**self.json()) except: raise ValueError("Invalid TransactionBuilder Format") if not any(self.wifs): raise MissingKeyError signedtx.sign(self.wifs, chain=self.blockchain.rpc.chain_params) self["signatures"].extend(signedtx.json().get("signatures")) return signedtx
def test_proposal_update(self): op = operations.Proposal_update( **{ 'fee_paying_account': "1.2.1", 'proposal': "1.10.90", 'active_approvals_to_add': ["1.2.5"], "fee": objects.Asset(amount=12512, asset_id="1.3.0"), }) ops = [Operation(op)] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], "BTS") txWire = hexlify(bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c804570117e03000000000000000015a01050000000" "000000001203255378db6dc19443e74421c954ad7fdcf23f4ea45fe4f" "e5a1b078a0f94fb529594819c9799d68efa5cfb5b271a9333a2f516ca" "4fb5093226275f48a42d9e8cf") self.assertEqual(compare[:-130], txWire[:-130])
def test_pricefeed(self): feed = objects.PriceFeed( **{ "settlement_price": objects.Price( base=objects.Asset(amount=214211, asset_id="1.3.0"), quote=objects.Asset(amount=1241, asset_id="1.3.14"), ), "core_exchange_rate": objects.Price( base=objects.Asset(amount=1241, asset_id="1.3.0"), quote=objects.Asset(amount=6231, asset_id="1.3.14"), ), "maximum_short_squeeze_ratio": 1100, "maintenance_collateral_ratio": 1750, }) op = operations.Asset_publish_feed(fee=objects.Asset(amount=100, asset_id="1.3.0"), publisher="1.2.0", asset_id="1.3.3", feed=feed) ops = [Operation(op)] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], "BTS") txWire = hexlify(bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c8045701136400000000000000000003c344030" "00000000000d9040000000000000ed6064c04d904000000000000" "0057180000000000000e0000012009e13f9066fedc3c8c1eb2ac3" "3b15dc67ecebf708890d0f8ab62ec8283d1636002315a189f1f5a" "a8497b41b8e6bb7c4dc66044510fae25d8f6aebb02c7cdef10") self.assertEqual(compare[:-130], txWire[:-130])
def compareConstructedTX(self): # def test_online(self): # self.maxDiff = None op = operations.Worker_create( **{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "owner": "1.2.0", "work_begin_date": "1970-01-01T00:00:00", "work_end_date": "1970-01-01T00:00:00", "daily_pay": 0, "name": "Myname", "url": "myURL", "initializer": [1, { "pay_vesting_period_days": 125 }] }) ops = [Operation(op)] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], "BTS") txWire = hexlify(bytes(tx)).decode("ascii") print("=" * 80) pprint(tx.json()) print("=" * 80) from grapheneapi.grapheneapi import GrapheneAPI rpc = GrapheneAPI("localhost", 8092) compare = rpc.serialize_transaction(tx.json()) print("soll: %s" % compare[:-130]) print("ist: %s" % txWire[:-130]) print(txWire[:-130] == compare[:-130]) self.assertEqual(compare[:-130], txWire[:-130])
def doit(self, printWire=False): ops = [Operation(self.op)] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], prefix) txWire = hexlify(bytes(tx)).decode("ascii") if printWire: print() print(txWire) print() self.assertEqual(self.cm[:-130], txWire[:-130]) if TEST_AGAINST_CLI_WALLET: from grapheneapi.grapheneapi import GrapheneAPI rpc = GrapheneAPI("localhost", 8092) self.cm = rpc.serialize_transaction(tx.json()) # print("soll: %s" % self.cm[:-130]) # print("ist: %s" % txWire[:-130]) # print(txWire[:-130] == self.cm[:-130]) self.assertEqual(self.cm[:-130], txWire[:-130])
def test_upgrade_account(self): op = operations.Account_upgrade( **{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "account_to_upgrade": "1.2.0", "upgrade_to_lifetime_member": True, "prefix": prefix, }) ops = [Operation(op)] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], "BTS") txWire = hexlify(bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c804570108000000000000000000000100000" "11f4e42562ada1d3fed8f8eb51dd58117e3a4024959c46955a0" "0d2a7e7e8b40ae7204f4617913aaaf028248d43e8c3463b8776" "0ca569007dba99a2c49de75bd69b3") self.assertEqual(compare[:-130], txWire[:-130])
def compareNewWire(self): # def test_online(self): # self.maxDiff = None from grapheneapi.grapheneapi import GrapheneAPI rpc = GrapheneAPI("localhost", 8092) tx = rpc.create_account("xeroc", "fsafaasf", "", False) pprint(tx) compare = rpc.serialize_transaction(tx) ref_block_num = tx["ref_block_num"] ref_block_prefix = tx["ref_block_prefix"] expiration = tx["expiration"] ops = [Operation(operations.Account_create(**tx["operations"][0][1]))] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], "BTS") txWire = hexlify(bytes(tx)).decode("ascii") print("\n") print(txWire[:-130]) print(compare[:-130])
def compareConstructedTX(self): # def test_online(self): # self.maxDiff = None op = operations.Vesting_balance_withdraw( **{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "vesting_balance": "1.13.0", "owner": "1.2.0", "amount": { "amount": 0, "asset_id": "1.3.0" }, "prefix": "TEST" }) ops = [Operation(op)] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], "BTS") txWire = hexlify(bytes(tx)).decode("ascii") print("=" * 80) pprint(tx.json()) print("=" * 80) from grapheneapi.grapheneapi import GrapheneAPI rpc = GrapheneAPI("localhost", 8092) compare = rpc.serialize_transaction(tx.json()) print(compare[:-130]) print(txWire[:-130]) print(txWire[:-130] == compare[:-130]) self.assertEqual(compare[:-130], txWire[:-130])
def test_limit_order_cancel(self): op = operations.Limit_order_cancel( **{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "fee_paying_account": "1.2.104", "order": "1.7.51840", "extensions": [] }) ops = [Operation(op)] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], "BTS") txWire = hexlify(bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c8045701020000000000000000006880950300000" "11f3fb754814f3910c1a8845486b86057d2b4588ae559b4c3810828" "c0d4cbec0e5b23517937cd7e0cc5ee8999d0777af7fe56d3c4b2e58" "7421bfb7400d4efdae97a") self.assertEqual(compare[:-130], txWire[:-130])
def test_create_account(self): s = { "fee": { "amount": 1467634, "asset_id": "1.3.0" }, "registrar": "1.2.33", "referrer": "1.2.27", "referrer_percent": 3, "name": "foobar-f124", "owner": { "weight_threshold": 1, "account_auths": [], 'key_auths': [['BTS6pbVDAjRFiw6fkiKYCrkz7PFeL7XNAfefrsREwg8MKpJ9VYV9x', 1], ['BTS6zLNtyFVToBsBZDsgMhgjpwysYVbsQD6YhP3kRkQhANUB4w7Qp', 1]], "address_auths": [] }, "active": { "weight_threshold": 1, "account_auths": [], 'key_auths': [['BTS6pbVDAjRFiw6fkiKYCrkz7PFeL7XNAfefrsREwg8MKpJ9VYV9x', 1], ['BTS6zLNtyFVToBsBZDsgMhgjpwysYVbsQD6YhP3kRkQhANUB4w7Qp', 1], ['BTS8CemMDjdUWSV5wKotEimhK6c4dY7p2PdzC2qM1HpAP8aLtZfE7', 1]], "address_auths": [] }, "options": { "memo_key": "BTS5TPTziKkLexhVKsQKtSpo4bAv5RnB8oXcG4sMHEwCcTf3r7dqE", "voting_account": "1.2.5", "num_witness": 0, "num_committee": 0, "votes": [], "extensions": [] }, "extensions": {} } op = operations.Account_create(**s) ops = [Operation(op)] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], "BTS") txWire = hexlify(bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c804570105f26416000000000000211b03000b666f" "6f6261722d6631323401000000000202fe8cc11cc8251de6977636b5" "5c1ab8a9d12b0b26154ac78e56e7c4257d8bcf6901000314aa202c91" "58990b3ec51a1aa49b2ab5d300c97b391df3beb34bb74f3c62699e01" "000001000000000303b453f46013fdbccb90b09ba169c388c34d8445" "4a3b9fbec68d5a7819a734fca0010002fe8cc11cc8251de6977636b5" "5c1ab8a9d12b0b26154ac78e56e7c4257d8bcf6901000314aa202c91" "58990b3ec51a1aa49b2ab5d300c97b391df3beb34bb74f3c62699e01" "0000024ab336b4b14ba6d881675d1c782912783c43dbbe31693aa710" "ac1896bd7c3d61050000000000000000011f61ad276120bc3f189296" "2bfff7db5e8ce04d5adec9309c80529e3a978a4fa1073225a6d56929" "e34c9d2a563e67a8f4a227e4fadb4a3bb6ec91bfdf4e57b80efd") self.assertEqual(compare[:-130], txWire[:-130])
def test_jsonLoading(self): data1 = { "expiration": expiration, "ref_block_num": ref_block_num, "ref_block_prefix": ref_block_prefix, "extensions": [], "operations": [[ 0, { "amount": { "amount": 1000000, "asset_id": "1.3.4" }, "extensions": [], "fee": { "amount": 0, "asset_id": "1.3.0" }, "from": "1.2.0", "memo": { "from": "BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "message": "fa5b6e83079a878e499e2e52a76a7739e9de40986a8e3bd8a68ce316cee50b21", "nonce": 5862723643998573708, "to": "BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" }, "to": "1.2.1" } ]], "signatures": [ "1f6c1e8df5faf18c3b057ce713ec92f9" + "b487c1ba58138daabc0038741b402c93" + "0d63d8d63861740215b4f65eb8ac9185" + "a3987f8239b962181237f47189e21102" + "af" ] } a = Signed_Transaction(data1.copy()) data2 = a.json() check1 = data1 check2 = data2 for key in [ "expiration", "extensions", "ref_block_num", "ref_block_prefix", "signatures" ]: self.assertEqual(check1[key], check2[key]) check1 = data1["operations"][0][1] check2 = data2["operations"][0][1] for key in ["from", "to"]: self.assertEqual(check1[key], check2[key]) check1 = data1["operations"][0][1]["memo"] check2 = data2["operations"][0][1]["memo"] for key in check1: self.assertEqual(check1[key], check2[key])
def sign_transaction(transaction, wifs): """ This methed signs a raw/unsigned transaction with the provided private keys. .. note:: The method does not guarantee that the blockchain will accept the signed transaction. This may happen in multiple cases, such as a) signed with irrelevant or redundant private keys, b) insufficient permissions to perform what is requested in the operations. .. note:: This method does **not** have a connection to the blockchain. Hence, neither the expiration, nor any of the TaPOS parameters (``ref_block_num``, nor ``ref_block_prefix``) are obtained here. The method needs to be provided with a fully valid (but unsigned transaction). An unsigned transaction takes the following form: .. code-block: js { "ref_block_num": 49506, "ref_block_prefix": 2292772274, "expiration": "2018-01-25T08:29:00", "operations": [[ 2, { "fee": { "amount": 9, "asset_id": "1.3.0"}, "fee_paying_account": "1.2.126225", "order": "1.7.49956139", "extensions": []}]], "extensions": [], "signatures": [], } The operation (here, operation type (0) is a ``transfer``) is the actual action that should take place on the blockchain. The parameters around it are required for a valid transaction and need to be obtained outside this method. For sake of completion, we here describe how the ``ref_block_num`` and ``ref_block_prefix`` are obtained from the blockchain (`github <https://github.com/xeroc/python-graphenelib/blob/master/graphenebase/transactions.py#L18-L26>`_): .. code-block:: python dynBCParams = rpc.get_dynamic_global_properties() ref_block_num = dynBCParams["head_block_number"] & 0xFFFF ref_block_prefix = struct.unpack_from("<I", unhexlify(dynBCParams["head_block_id"]), 4)[0] .. important:: The expiration **must not** be more than 1 day in the future! :param dict transaction: unsigned transaction :param list wifs: list of private keys in wif format :rtype: dict :returns: signed transaction """ # Instantiate Signed_Transaction with content assert isinstance(transaction, dict) assert isinstance(wifs, (list, tuple, set)) prefix = "BTS" if transaction["operations"][0][1].get("prefix", "BTS") != "BTS": # call transaction signer prefix = transaction["operations"][0][1]["prefix"] if transaction.get("prefix", "BTS") != "BTS": prefix = transaction["prefix"] for idx, item in enumerate(transaction["operations"]): transaction["operations"][idx][1].update( {"prefix": transaction["prefix"]}) tx = Signed_Transaction(**transaction) # call transaction signer result = tx.sign(wifs, chain=prefix).json() result["transaction_id"] = tx.id return result
with open(args.file) as f: obj = json.load(f) blockchain = BitShares(args.node) if args.tapos: txbuffer = blockchain.tx() obj['ref_block_num'] = txbuffer['ref_block_num'] obj['ref_block_prefix'] = txbuffer['ref_block_prefix'] if args.expire and int(args.expire) >= 1: expiration = datetime.strptime( txbuffer['expiration'], "%Y-%m-%dT%H:%M:%S") + timedelta( minutes=int(args.expire) - 1, seconds=30) txbuffer['expiration'] = expiration.strftime("%Y-%m-%dT%H:%M:%S%Z") obj['expiration'] = txbuffer['expiration'] tx = Signed_Transaction( ref_block_num=obj['ref_block_num'], ref_block_prefix=obj['ref_block_prefix'], expiration=obj['expiration'], operations=obj['operations'], ) if args.chain_id is None: args.chain_id = blockchain.rpc.chain_params['chain_id'] signData = encode(binascii.unhexlify(args.chain_id), tx) print(binascii.hexlify(signData).decode()) dongle = getDongle(True) offset = 0 first = True singSize = len(signData) while offset != singSize: if singSize - offset > 200: chunk = signData[offset:offset + 200] else:
def mergeHistory_before(self, iso, account): self.refreshing = True #iso._wait_online(timeout=3) # will raise exception #if not(iso.is_connected()): # raise Exception # subscribe rpc = iso.bts.rpc if not self.subscribed: rpc.get_full_accounts([account.name], True) self.subscribed = True # fetch history last_op_index = iso.store.historyStorage.getLastOperation(account.name) log.debug("Last OP INDEX for %s = %s" % (account.name, last_op_index)) # recreate account object #from bitshares.account import Account #account = Account(account.name, bitshares_instance=iso.bts) # load from the net history = list(account.history()) # load full tx from net + generate description t = 0 for h in history: if (h['id'] == last_op_index): break t += 1 #print("Get full tx for", # int(h['block_num']), int(h['trx_in_block']), # int(h['op_in_trx']), int(h["virtual_op"])) try: ftx = iso.bts.rpc.get_transaction(int(h['block_num']), int(h['trx_in_block'])) except Exception as error: #print(str(error)) ftx = {} #print(ftx) h['_fulltx_dict'] = ftx h['_fulltx_obj'] = Signed_Transaction(**ftx) if ftx else None h['_fulltx'] = json.dumps(ftx) h['_memo'] = 0 try: if h['op'][0] == 0: h['_memo'] = -1 if "memo" in h['op'][1]: data = h['op'][1]['memo'] clear = iso.getMemo(None, None, data=data) if len(clear["message"]) > 0: h['_memo'] = 1 except Exception as e: #import traceback #print(h['op'][1], "failed to metch memo-data") #traceback.print_exc() pass #print("Get description for", h) h['_details'] = iso.historyDescription(h, account) h['_icon'] = ":/op/images/op/" + h['_details']['icon'] + ".png" h['description'] = h['_details'].pop('long') h['details'] = json.dumps(h['_details']) history = history[0:t] return (history, account.name, iso)
def mergeHistory_before(self, iso, account, request_handler=None): self.refreshing = True #iso._wait_online(timeout=3) # will raise exception #if not(iso.is_connected()): # raise Exception rh = request_handler # subscribe rpc = iso.bts.rpc if not self.subscribed: rpc.get_full_accounts([account.name], True) self.subscribed = True # fetch history last_op_index = iso.store.historyStorage.getLastOperation(account.name) log.debug("Last OP INDEX for %s = %s" % (account.name, last_op_index)) # recreate account object #from bitshares.account import Account #account = Account(account.name, blockchain_instance=iso.bts) # load from the net history = list(account.history()) # load full tx from net + generate description total = len(history) t = 0 for h in history: if rh and rh.cancelled: break if (h['id'] == last_op_index): break t += 1 if rh: rh.ping(int(t / total * 100), None) #print("Get full tx for", # int(h['block_num']), int(h['trx_in_block']), # int(h['op_in_trx']), int(h["virtual_op"])) try: ftx = iso.bts.rpc.get_transaction(int(h['block_num']), int(h['trx_in_block'])) except Exception as error: log.error("Failed to get transaction %d in block %d: %s", int(h['trx_in_block']), int(h['block_num']), str(error)) #print(str(error)) ftx = { } #print(ftx) h['_fulltx_obj' ] = Signed_Transaction(**ftx) if ftx else None if not "operations" in ftx and h["op"][0] == 2: # cancel order ftx = { "operations": [ h["op"] ], "operation_results": [ h["result"] ] } h['_fulltx_dict'] = ftx h['_fulltx'] = json.dumps(ftx) h['_memo'] = 0 import bitshares.exceptions try: if h['op'][0] == 0: h['_memo'] = -1 if "memo" in h['op'][1]: data = h['op'][1]['memo'] clear = iso.getMemo(None, None, data=data) if len(clear["message"]) > 0: h['_memo'] = 1 except bitshares.exceptions.WalletLocked: h['_memo'] = 1 except Exception as e: log.error("Could not decode memo in op %s: %s", h['id'], str(e)) #print("Get description for", h) h['_details'] = iso.historyDescription(h, account) h['_icon'] = ":/op/images/op/"+h['_details']['icon']+".png" h['description'] = h['_details'].pop('long') h['details'] = json.dumps(h['_details']) history = history[0:t] return (history, account.name, iso)
def sendTip(to_name): # # `to_name` is account name (string) of recipient. # Logger.Write("Preparing to send %.1f %s to \"%s\"..." % (tip_amount, tip_asset_display_symbol, to_name)) tx_head = blockchain.new_tx() # Pull recent TaPoS try: dummy = tx_head[ 'ref_block_num'] # Somehow this triggers tx_head to populate 'expiration'... (??) except NumRetriesReached: Logger.Write( "ERROR: Can't reach API node: 'NumRetries' reached. Check network connection." ) return expiration = datetime.strptime(tx_head['expiration'], "%Y-%m-%dT%H:%M:%S") + timedelta(minutes=10) tx_head['expiration'] = expiration.strftime( "%Y-%m-%dT%H:%M:%S%Z" ) # Longer expiration to accomodate device interaction try: tx = append_transfer_tx(tx_head, to_name) except: Logger.Write("Could not construct transaction. Please try again.") return print("We have constructed the following transaction:") print(tx) tx_st = Signed_Transaction( ref_block_num=tx['ref_block_num'], ref_block_prefix=tx['ref_block_prefix'], expiration=tx['expiration'], operations=tx['operations'], ) signData = encode( binascii.unhexlify(blockchain.rpc.chain_params['chain_id']), tx_st) print("Serialized:") print(binascii.hexlify(signData).decode()) donglePath = parse_bip32_path(bip32_path) pathSize = int(len(donglePath) / 4) try: dongle = getDongle(True) except: Logger.Write("Ledger Nano not found! Is it plugged in and unlocked?") return Logger.Write( "Created transaction. Please review and confirm on Ledger Nano S...") offset = 0 first = True signSize = len(signData) while offset != signSize: if signSize - offset > 200: chunk = signData[offset:offset + 200] else: chunk = signData[offset:] if first: totalSize = len(donglePath) + 1 + len(chunk) apdu = binascii.unhexlify("B5040000" + "{:02x}".format( totalSize) + "{:02x}".format(pathSize)) + donglePath + chunk first = False else: totalSize = len(chunk) apdu = binascii.unhexlify("B5048000" + "{:02x}".format(totalSize)) + chunk offset += len(chunk) try: result = dongle.exchange(apdu) except CommException as e: dongle.close() if e.sw == 0x6e00: Logger.Write( "BitShares App not running on Nano. Please check.") else: Logger.Write( "Tx Not Broadcast. User declined - transaction not signed." ) return except: dongle.close() Logger.Write("An unknown error occured. Was device unplugged?") return print(binascii.hexlify(result).decode()) dongle.close() Logger.Write("Broadcasting transaction...") tx_sig = blockchain.new_tx(json.loads(str(tx_st))) tx_sig["signatures"].extend([binascii.hexlify(result).decode()]) try: print(blockchain.broadcast(tx=tx_sig)) Logger.Write("Success! Transaction has been sent.") except RPCError as e: Logger.Write("Could not broadcast transaction!") Logger.Write(str(e)) except NumRetriesReached: Logger.Write( "ERROR: Could not broadcast transaction: 'NumRetries' reached. Check network connection." ) except: raise