git / moonbite-network / cli.py · raw

"""MyCoin — command-line interface.A thin, human-friendly wrapper over the existing modules (transaction, block,node, wallet, params). It creates fresh in-memory state per invocation; there isno persistence and this network never holds real funds.Subcommands:  newkey             generate a keypair and print its address  mine [--count N]   mine N blocks to an address, printing each block  subsidy --height H print the block subsidy at height H  info               print genesis hash, height, and total money in the UTXO set"""from __future__ import annotationsimport argparsefrom typing import Optionalfrom block import block_subsidy, genesis_blockfrom node import Nodefrom params import CENTS_PER_COINfrom transaction import generate_keypair, pubkey_hashfrom wallet import address_from_pubkey_hash, is_valid_addressdef _fresh_address() -> str:    """Generate a keypair and return its Base58Check address."""    _sk, pubkey_hex = generate_keypair()    return address_from_pubkey_hash(pubkey_hash(pubkey_hex))def _address_to_pkh(address: str) -> str:    """Recover the pubkey-hash (hex) that a coinbase output locks to."""    from wallet import pubkey_hash_from_address    return pubkey_hash_from_address(address)def _format_amount(cents: int) -> str:    """Render an integer cent amount as 'C coins (K cents)'."""    coins = cents / CENTS_PER_COIN    return f"{coins:g} coins ({cents} cents)"# --------------------------------------------------------------------------- ## Subcommand handlers# --------------------------------------------------------------------------- #def cmd_newkey(args: argparse.Namespace) -> int:    print(_fresh_address())    return 0def cmd_mine(args: argparse.Namespace) -> int:    if args.count < 1:        print("error: --count must be at least 1")        return 1    address = args.address or _fresh_address()    if not is_valid_address(address):        print(f"error: invalid address {address!r}")        return 1    miner_pkh = _address_to_pkh(address)    # coinbase_maturity=0 so the CLI can mine freely without maturity gating.    node = Node("cli-miner", coinbase_maturity=0)    print(f"mining {args.count} block(s) to {address}")    for _ in range(args.count):        block = node.mine_block(miner_pkh)        if block is None:            print("error: failed to mine a block")            return 1        total = node.chain.utxo.total_value()        print(            f"height={node.chain.height} hash={block.hash} "            f"total_utxo={_format_amount(total)}"        )    return 0def cmd_subsidy(args: argparse.Namespace) -> int:    if args.height < 0:        print("error: --height must be non-negative")        return 1    cents = block_subsidy(args.height)    coins = cents // CENTS_PER_COIN    print(f"height {args.height}: subsidy = {coins} coins ({cents} cents)")    return 0def cmd_info(args: argparse.Namespace) -> int:    node = Node("cli-info")    chain = node.chain    print(f"genesis_hash: {chain.genesis_hash}")    print(f"height:       {chain.height}")    print(f"total_money:  {_format_amount(chain.utxo.total_value())}")    return 0# --------------------------------------------------------------------------- ## Parser# --------------------------------------------------------------------------- #def build_parser() -> argparse.ArgumentParser:    parser = argparse.ArgumentParser(        prog="mycoin",        description="MyCoin command-line interface (educational; no real funds).",    )    sub = parser.add_subparsers(dest="command", metavar="command")    p_newkey = sub.add_parser("newkey", help="generate a keypair and print its address")    p_newkey.set_defaults(func=cmd_newkey)    p_mine = sub.add_parser("mine", help="mine blocks to an address")    p_mine.add_argument("--count", type=int, default=1, help="number of blocks to mine")    p_mine.add_argument(        "--address", default=None, help="destination address (default: a fresh key)"    )    p_mine.set_defaults(func=cmd_mine)    p_subsidy = sub.add_parser("subsidy", help="print the block subsidy at a height")    p_subsidy.add_argument("--height", type=int, required=True, help="block height")    p_subsidy.set_defaults(func=cmd_subsidy)    p_info = sub.add_parser("info", help="print fresh-chain info")    p_info.set_defaults(func=cmd_info)    return parserdef main(argv: Optional[list[str]] = None) -> int:    parser = build_parser()    args = parser.parse_args(argv)    if not getattr(args, "command", None):        parser.print_help()        return 0    return args.func(args)if __name__ == "__main__":    raise SystemExit(main())