diff --git a/chia/cmds/vault.py b/chia/cmds/vault.py index fcd1b4f98363..a6fcde48996e 100644 --- a/chia/cmds/vault.py +++ b/chia/cmds/vault.py @@ -2,7 +2,7 @@ import asyncio from decimal import Decimal -from typing import Optional +from typing import Optional, Sequence import click @@ -66,6 +66,34 @@ def vault_cmd(ctx: click.Context) -> None: callback=validate_fee, ) @click.option("-n", "--name", help="Set the vault name", type=str) +@click.option( + "-ma", + "--min-coin-amount", + help="Ignore coins worth less then this much XCH or CAT units", + type=str, + required=False, + default="0", +) +@click.option( + "-l", + "--max-coin-amount", + help="Ignore coins worth more then this much XCH or CAT units", + type=str, + required=False, + default=None, +) +@click.option( + "--exclude-coin", + "coins_to_exclude", + multiple=True, + help="Exclude this coin from being spent.", +) +@click.option( + "--reuse", + help="Reuse existing address for the change.", + is_flag=True, + default=False, +) def vault_create_cmd( wallet_rpc_port: Optional[int], fingerprint: int, @@ -75,6 +103,10 @@ def vault_create_cmd( hidden_puzzle_index: Optional[int], fee: str, name: Optional[str], + min_coin_amount: str, + max_coin_amount: Optional[str], + coins_to_exclude: Sequence[str], + reuse: bool, ) -> None: from .vault_funcs import create_vault @@ -91,6 +123,10 @@ def vault_create_cmd( hidden_puzzle_index, Decimal(fee), name, + min_coin_amount=min_coin_amount, + max_coin_amount=max_coin_amount, + excluded_coin_ids=coins_to_exclude, + reuse_puzhash=True if reuse else None, ) ) @@ -105,6 +141,37 @@ def vault_create_cmd( ) @options.create_fingerprint() @click.option("-i", "--wallet-id", help="Vault Wallet ID", type=int, required=True, default=1) +@click.option( + "-pk", + "--public-key", + help="SECP public key", + type=str, + required=True, +) +@click.option( + "-i", + "--hidden-puzzle-index", + help="Starting index for hidden puzzle", + type=int, + required=False, + default=0, +) +@click.option( + "-rk", + "--recovery-public-key", + help="BLS public key for vault recovery", + type=str, + required=False, + default=None, +) +@click.option( + "-rt", + "--recovery-timelock", + help="Timelock for vault recovery (in seconds)", + type=int, + required=False, + default=None, +) @click.option( "-ri", "--recovery-initiate-file", @@ -121,13 +188,65 @@ def vault_create_cmd( required=True, default="finish_recovery.json", ) +@click.option( + "-ma", + "--min-coin-amount", + help="Ignore coins worth less then this much XCH or CAT units", + type=str, + required=False, + default="0", +) +@click.option( + "-l", + "--max-coin-amount", + help="Ignore coins worth more then this much XCH or CAT units", + type=str, + required=False, + default=None, +) +@click.option( + "--exclude-coin", + "coins_to_exclude", + multiple=True, + help="Exclude this coin from being spent.", +) +@click.option( + "--reuse", + help="Reuse existing address for the change.", + is_flag=True, + default=False, +) def vault_recover_cmd( wallet_rpc_port: Optional[int], fingerprint: int, wallet_id: int, + public_key: str, + hidden_puzzle_index: int, + recovery_public_key: Optional[str], + recovery_timelock: Optional[int], recovery_initiate_file: str, recovery_finish_file: str, + min_coin_amount: str, + max_coin_amount: Optional[str], + coins_to_exclude: Sequence[str], + reuse: bool, ) -> None: from .vault_funcs import recover_vault - asyncio.run(recover_vault(wallet_rpc_port, fingerprint, wallet_id, recovery_initiate_file, recovery_finish_file)) + asyncio.run( + recover_vault( + wallet_rpc_port, + fingerprint, + wallet_id, + public_key, + hidden_puzzle_index, + recovery_public_key, + recovery_timelock, + recovery_initiate_file, + recovery_finish_file, + min_coin_amount=min_coin_amount, + max_coin_amount=max_coin_amount, + excluded_coin_ids=coins_to_exclude, + reuse_puzhash=True if reuse else None, + ) + ) diff --git a/chia/cmds/vault_funcs.py b/chia/cmds/vault_funcs.py index 956184902b20..ea1e191215ab 100644 --- a/chia/cmds/vault_funcs.py +++ b/chia/cmds/vault_funcs.py @@ -2,9 +2,9 @@ import json from decimal import Decimal -from typing import Optional +from typing import Optional, Sequence -from chia.cmds.cmds_util import get_wallet_client +from chia.cmds.cmds_util import CMDTXConfigLoader, get_wallet_client from chia.cmds.units import units from chia.util.ints import uint32, uint64 @@ -18,17 +18,27 @@ async def create_vault( hidden_puzzle_index: int, d_fee: Decimal, name: Optional[str], + min_coin_amount: Optional[str], + max_coin_amount: Optional[str], + excluded_coin_ids: Sequence[str], + reuse_puzhash: Optional[bool], ) -> None: async with get_wallet_client(wallet_rpc_port, fingerprint) as (wallet_client, fingerprint, config): fee: int = int(d_fee * units["chia"]) assert hidden_puzzle_index >= 0 + tx_config = CMDTXConfigLoader( + min_coin_amount=min_coin_amount, + max_coin_amount=max_coin_amount, + excluded_coin_ids=list(excluded_coin_ids), + reuse_puzhash=reuse_puzhash, + ).to_tx_config(units["chia"], config, fingerprint) if timelock is not None: assert timelock > 0 try: await wallet_client.vault_create( bytes.fromhex(public_key), uint32(hidden_puzzle_index), - config, + tx_config, bytes.fromhex(recovery_public_key) if recovery_public_key else None, uint64(timelock) if timelock else None, uint64(fee), @@ -40,11 +50,39 @@ async def create_vault( async def recover_vault( - wallet_rpc_port: Optional[int], fingerprint: Optional[int], wallet_id: int, initiate_file: str, finish_file: str + wallet_rpc_port: Optional[int], + fingerprint: Optional[int], + wallet_id: int, + public_key: str, + hidden_puzzle_index: int, + recovery_public_key: Optional[str], + timelock: Optional[int], + initiate_file: str, + finish_file: str, + min_coin_amount: Optional[str], + max_coin_amount: Optional[str], + excluded_coin_ids: Sequence[str], + reuse_puzhash: Optional[bool], ) -> None: async with get_wallet_client(wallet_rpc_port, fingerprint) as (wallet_client, fingerprint, config): + assert hidden_puzzle_index >= 0 + if timelock is not None: + assert timelock > 0 + tx_config = CMDTXConfigLoader( + min_coin_amount=min_coin_amount, + max_coin_amount=max_coin_amount, + excluded_coin_ids=list(excluded_coin_ids), + reuse_puzhash=reuse_puzhash, + ).to_tx_config(units["chia"], config, fingerprint) try: - response = await wallet_client.vault_recovery(uint32(wallet_id)) + response = await wallet_client.vault_recovery( + uint32(wallet_id), + bytes.fromhex(public_key), + uint32(hidden_puzzle_index), + tx_config, + bytes.fromhex(recovery_public_key) if recovery_public_key else None, + uint64(timelock) if timelock else None, + ) with open(initiate_file, "w") as f: json.dump(response[0].to_json_dict(), f, indent=4) print(f"Initiate Recovery transaction written to: {initiate_file}") diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 64d127fd3ece..16026037349c 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -4620,11 +4620,21 @@ async def vault_create( "transactions": [vault_record.to_json_dict_convenience(self.service.config)], } - async def vault_recovery(self, request: Dict[str, Any]) -> EndpointResult: + async def vault_recovery(self, request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG) -> EndpointResult: """ Initiate Vault Recovery """ wallet_id = uint32(request["wallet_id"]) wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=Vault) - recovery_txs = await wallet.create_recovery_spends() + secp_pk = bytes.fromhex(str(request.get("secp_pk"))) + hp_index = request.get("hp_index", 0) + hidden_puzzle_hash = get_vault_hidden_puzzle_with_index(hp_index).get_tree_hash() + bls_str = request.get("bls_pk") + bls_pk = G1Element.from_bytes(bytes.fromhex(str(bls_str))) if bls_str else None + timelock_int = request.get("timelock") + timelock = uint64(timelock_int) if timelock_int else None + genesis_challenge = DEFAULT_CONSTANTS.GENESIS_CHALLENGE + recovery_txs = await wallet.create_recovery_spends( + secp_pk, hidden_puzzle_hash, genesis_challenge, tx_config, bls_pk=bls_pk, timelock=timelock + ) return {"transactions": [tx.to_json_dict_convenience(self.service.config) for tx in recovery_txs]} diff --git a/chia/rpc/wallet_rpc_client.py b/chia/rpc/wallet_rpc_client.py index df1bc025f1ab..7540cafbe50d 100644 --- a/chia/rpc/wallet_rpc_client.py +++ b/chia/rpc/wallet_rpc_client.py @@ -1674,9 +1674,23 @@ async def vault_create( ) return [TransactionRecord.from_json_dict_convenience(tx) for tx in response["transactions"]] - async def vault_recovery(self, wallet_id: uint32) -> List[TransactionRecord]: + async def vault_recovery( + self, + wallet_id: uint32, + secp_pk: bytes, + hp_index: uint32, + tx_config: TXConfig, + bls_pk: Optional[bytes] = None, + timelock: Optional[uint64] = None, + ) -> List[TransactionRecord]: response = await self.fetch( "vault_recovery", - {"wallet_id": wallet_id}, + { + "wallet_id": wallet_id, + "secp_pk": secp_pk.hex(), + "hp_index": hp_index, + "bls_pk": bls_pk.hex() if bls_pk else None, + "timelock": timelock, + }, ) return [TransactionRecord.from_json_dict_convenience(tx) for tx in response["transactions"]] diff --git a/tests/cmds/wallet/test_vault.py b/tests/cmds/wallet/test_vault.py index 92cadf121249..9bc06792ac5a 100644 --- a/tests/cmds/wallet/test_vault.py +++ b/tests/cmds/wallet/test_vault.py @@ -3,7 +3,7 @@ from pathlib import Path from typing import List, Optional, Tuple -from chia_rs import Coin, G2Element +from chia_rs import Coin, G1Element, G2Element from chia.types.spend_bundle import SpendBundle from chia.util.ints import uint8, uint32, uint64 @@ -55,9 +55,9 @@ async def vault_create( test_rpc_clients.wallet_rpc_client = inst_rpc_client pk = get_bytes32(0).hex() recovery_pk = get_bytes32(1).hex() - timelock = 100 - hidden_puzzle_index = 10 - fee = 0.1 + timelock = "100" + hidden_puzzle_index = "10" + fee = "0.1" command_args = [ "vault", "create", @@ -84,6 +84,11 @@ class CreateVaultRpcClient(TestWalletRpcClient): async def vault_recovery( self, wallet_id: uint32, + secp_pk: bytes, + hp_index: uint32, + tx_config: TXConfig, + bls_pk: Optional[G1Element] = None, + timelock: Optional[uint64] = None, ) -> List[TransactionRecord]: tx_rec = TransactionRecord( confirmed_at_height=uint32(1), @@ -108,9 +113,21 @@ async def vault_recovery( inst_rpc_client = CreateVaultRpcClient() # pylint: disable=no-value-for-parameter test_rpc_clients.wallet_rpc_client = inst_rpc_client + pk = get_bytes32(0).hex() + recovery_pk = get_bytes32(1).hex() + timelock = "100" + hidden_puzzle_index = "10" command_args = [ "vault", "recover", + "-pk", + pk, + "-rk", + recovery_pk, + "-rt", + timelock, + "-i", + hidden_puzzle_index, "-ri", "recovery_init.json", "-rf",