async def create_signed_transaction(self, request): if "additions" not in request or len(request["additions"]) < 1: raise ValueError("Specify additions list") additions: List[Dict] = request["additions"] amount_0: uint64 = uint64(additions[0]["amount"]) assert amount_0 <= self.service.constants.MAX_COIN_AMOUNT puzzle_hash_0 = hexstr_to_bytes(additions[0]["puzzle_hash"]) if len(puzzle_hash_0) != 32: raise ValueError(f"Address must be 32 bytes. {puzzle_hash_0}") additional_outputs = [] for addition in additions[1:]: receiver_ph = hexstr_to_bytes(addition["puzzle_hash"]) if len(receiver_ph) != 32: raise ValueError(f"Address must be 32 bytes. {receiver_ph}") amount = uint64(addition["amount"]) if amount > self.service.constants.MAX_COIN_AMOUNT: raise ValueError(f"Coin amount cannot exceed {self.service.constants.MAX_COIN_AMOUNT}") additional_outputs.append({"puzzlehash": receiver_ph, "amount": amount}) fee = uint64(0) if "fee" in request: fee = uint64(request["fee"]) coins = None if "coins" in request and len(request["coins"]) > 0: coins = set([Coin.from_json_dict(coin_json) for coin_json in request["coins"]]) signed_tx = await self.service.wallet_state_manager.main_wallet.generate_signed_transaction( amount_0, puzzle_hash_0, fee, coins=coins, ignore_max_send_amount=True, primaries=additional_outputs ) return {"signed_tx": signed_tx}
async def test_wallet_make_transaction(self, two_wallet_nodes): test_rpc_port = uint16(21529) test_rpc_port_node = uint16(21530) num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] full_node_server = full_node_api.full_node.server wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet wallet_2 = wallet_node_2.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() ph_2 = await wallet_2.get_new_puzzlehash() await server_2.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) initial_funds = sum( [calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)] ) initial_funds_eventually = sum( [ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks + 1) ] ) wallet_rpc_api = WalletRpcApi(wallet_node) config = bt.config hostname = config["self_hostname"] daemon_port = config["daemon_port"] def stop_node_cb(): pass full_node_rpc_api = FullNodeRpcApi(full_node_api.full_node) rpc_cleanup_node = await start_rpc_server( full_node_rpc_api, hostname, daemon_port, test_rpc_port_node, stop_node_cb, bt.root_path, config, connect_to_daemon=False, ) rpc_cleanup = await start_rpc_server( wallet_rpc_api, hostname, daemon_port, test_rpc_port, stop_node_cb, bt.root_path, config, connect_to_daemon=False, ) await time_out_assert(5, wallet.get_confirmed_balance, initial_funds) await time_out_assert(5, wallet.get_unconfirmed_balance, initial_funds) client = await WalletRpcClient.create(self_hostname, test_rpc_port, bt.root_path, config) client_node = await FullNodeRpcClient.create(self_hostname, test_rpc_port_node, bt.root_path, config) try: addr = encode_puzzle_hash(await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash(), "xch") tx_amount = 15600000 try: await client.send_transaction("1", 100000000000000001, addr) raise Exception("Should not create high value tx") except ValueError: pass # Tests sending a basic transaction tx = await client.send_transaction("1", tx_amount, addr) transaction_id = tx.name async def tx_in_mempool(): tx = await client.get_transaction("1", transaction_id) return tx.is_in_mempool() await time_out_assert(5, tx_in_mempool, True) await time_out_assert(5, wallet.get_unconfirmed_balance, initial_funds - tx_amount) assert (await client.get_wallet_balance("1"))["unconfirmed_wallet_balance"] == initial_funds - tx_amount assert (await client.get_wallet_balance("1"))["confirmed_wallet_balance"] == initial_funds for i in range(0, 5): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_2)) async def eventual_balance(): return (await client.get_wallet_balance("1"))["confirmed_wallet_balance"] await time_out_assert(5, eventual_balance, initial_funds_eventually - tx_amount) # Tests offline signing ph_3 = await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash() ph_4 = await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash() ph_5 = await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash() # Test basic transaction to one output signed_tx_amount = 888000 tx_res = await client.create_signed_transaction([{"amount": signed_tx_amount, "puzzle_hash": ph_3}]) assert tx_res["success"] assert tx_res["signed_tx"]["fee_amount"] == 0 assert tx_res["signed_tx"]["amount"] == signed_tx_amount assert len(tx_res["signed_tx"]["additions"]) == 2 # The output and the change assert any([addition["amount"] == signed_tx_amount for addition in tx_res["signed_tx"]["additions"]]) push_res = await client_node.push_tx(SpendBundle.from_json_dict(tx_res["signed_tx"]["spend_bundle"])) assert push_res["success"] assert (await client.get_wallet_balance("1"))[ "confirmed_wallet_balance" ] == initial_funds_eventually - tx_amount for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) await time_out_assert(5, eventual_balance, initial_funds_eventually - tx_amount - signed_tx_amount) # Test transaction to two outputs, from a specified coin, with a fee coin_to_spend = None for addition in tx_res["signed_tx"]["additions"]: if addition["amount"] != signed_tx_amount: coin_to_spend = Coin.from_json_dict(addition) assert coin_to_spend is not None tx_res = await client.create_signed_transaction( [{"amount": 444, "puzzle_hash": ph_4}, {"amount": 999, "puzzle_hash": ph_5}], coins=[coin_to_spend], fee=100, ) assert tx_res["success"] assert tx_res["signed_tx"]["fee_amount"] == 100 assert tx_res["signed_tx"]["amount"] == 444 + 999 assert len(tx_res["signed_tx"]["additions"]) == 3 # The outputs and the change assert any([addition["amount"] == 444 for addition in tx_res["signed_tx"]["additions"]]) assert any([addition["amount"] == 999 for addition in tx_res["signed_tx"]["additions"]]) assert ( sum([rem["amount"] for rem in tx_res["signed_tx"]["removals"]]) - sum([ad["amount"] for ad in tx_res["signed_tx"]["additions"]]) == 100 ) push_res = await client_node.push_tx(SpendBundle.from_json_dict(tx_res["signed_tx"]["spend_bundle"])) assert push_res["success"] for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) await time_out_assert( 5, eventual_balance, initial_funds_eventually - tx_amount - signed_tx_amount - 444 - 999 - 100 ) address = await client.get_next_address("1", True) assert len(address) > 10 transactions = await client.get_transactions("1") assert len(transactions) > 1 pks = await client.get_public_keys() assert len(pks) == 1 assert (await client.get_height_info()) > 0 sk_dict = await client.get_private_key(pks[0]) assert sk_dict["fingerprint"] == pks[0] assert sk_dict["sk"] is not None assert sk_dict["pk"] is not None assert sk_dict["seed"] is not None mnemonic = await client.generate_mnemonic() assert len(mnemonic) == 24 await client.add_key(mnemonic) pks = await client.get_public_keys() assert len(pks) == 2 await client.log_in_and_skip(pks[1]) sk_dict = await client.get_private_key(pks[1]) assert sk_dict["fingerprint"] == pks[1] await client.delete_key(pks[0]) await client.log_in_and_skip(pks[1]) assert len(await client.get_public_keys()) == 1 assert not (await client.get_sync_status()) wallets = await client.get_wallets() assert len(wallets) == 1 balance = await client.get_wallet_balance(wallets[0]["id"]) assert balance["unconfirmed_wallet_balance"] == 0 test_wallet_backup_path = Path("test_wallet_backup_file") await client.create_backup(test_wallet_backup_path) assert test_wallet_backup_path.exists() test_wallet_backup_path.unlink() try: await client.send_transaction(wallets[0]["id"], 100, addr) raise Exception("Should not create tx if no balance") except ValueError: pass await client.delete_all_keys() assert len(await client.get_public_keys()) == 0 finally: # Checks that the RPC manages to stop the node client.close() client_node.close() await client.await_closed() await client_node.await_closed() await rpc_cleanup() await rpc_cleanup_node()