chia-blockchain/chia/cmds/dao_funcs.py

581 lines
25 KiB
Python

from __future__ import annotations
import asyncio
import json
import time
from decimal import Decimal
from typing import Any, Dict, Optional
from chia.cmds.cmds_util import CMDTXConfigLoader, get_wallet_client, transaction_status_msg, transaction_submitted_msg
from chia.cmds.units import units
from chia.cmds.wallet_funcs import get_mojo_per_unit, get_wallet_type
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash
from chia.util.config import selected_network_address_prefix
from chia.util.ints import uint64
from chia.wallet.util.tx_config import DEFAULT_COIN_SELECTION_CONFIG
from chia.wallet.util.wallet_types import WalletType
async def add_dao_wallet(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
treasury_id = args["treasury_id"]
filter_amount = args["filter_amount"]
name = args["name"]
print(f"Adding wallet for DAO: {treasury_id}")
print("This may take awhile.")
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
res = await wallet_client.create_new_dao_wallet(
mode="existing",
tx_config=CMDTXConfigLoader.from_json_dict({"reuse_puzhash": True}).to_tx_config(
units["chia"], config, fingerprint
),
dao_rules=None,
amount_of_cats=None,
treasury_id=treasury_id,
filter_amount=filter_amount,
name=name,
)
print("Successfully created DAO Wallet")
print("DAO Treasury ID: {treasury_id}".format(**res))
print("DAO Wallet ID: {wallet_id}".format(**res))
print("CAT Wallet ID: {cat_wallet_id}".format(**res))
print("DAOCAT Wallet ID: {dao_cat_wallet_id}".format(**res))
async def create_dao_wallet(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
proposal_minimum = uint64(int(Decimal(args["proposal_minimum_amount"]) * units["chia"]))
if proposal_minimum % 2 == 0:
proposal_minimum = uint64(1 + proposal_minimum)
print("Adding 1 mojo to proposal minimum amount")
dao_rules = {
"proposal_timelock": args["proposal_timelock"],
"soft_close_length": args["soft_close_length"],
"attendance_required": args["attendance_required"],
"pass_percentage": args["pass_percentage"],
"self_destruct_length": args["self_destruct_length"],
"oracle_spend_delay": args["oracle_spend_delay"],
"proposal_minimum_amount": proposal_minimum,
}
amount_of_cats = args["amount_of_cats"]
filter_amount = args["filter_amount"]
name = args["name"]
fee = Decimal(args["fee"])
final_fee: uint64 = uint64(int(fee * units["chia"]))
fee_for_cat = Decimal(args["fee_for_cat"])
final_fee_for_cat: uint64 = uint64(int(fee_for_cat * units["chia"]))
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
conf_coins, _, _ = await wallet_client.get_spendable_coins(
wallet_id=1, coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG
)
if len(conf_coins) < 2: # pragma: no cover
raise ValueError("DAO creation requires at least 2 xch coins in your wallet.")
res = await wallet_client.create_new_dao_wallet(
mode="new",
dao_rules=dao_rules,
amount_of_cats=amount_of_cats,
treasury_id=None,
filter_amount=filter_amount,
name=name,
fee=final_fee,
fee_for_cat=final_fee_for_cat,
tx_config=CMDTXConfigLoader.from_json_dict(
{
"min_coin_amount": args["min_coin_amount"],
"max_coin_amount": args["max_coin_amount"],
"coins_to_exclude": args["coins_to_exclude"],
"amounts_to_exclude": args["amounts_to_exclude"],
"reuse_puzhash": args["reuse_puzhash"],
}
).to_tx_config(units["chia"], config, fingerprint),
)
print("Successfully created DAO Wallet")
print("DAO Treasury ID: {treasury_id}".format(**res))
print("DAO Wallet ID: {wallet_id}".format(**res))
print("CAT Wallet ID: {cat_wallet_id}".format(**res))
print("DAOCAT Wallet ID: {dao_cat_wallet_id}".format(**res))
async def get_treasury_id(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, _, _):
res = await wallet_client.dao_get_treasury_id(wallet_id=wallet_id)
treasury_id = res["treasury_id"]
print(f"Treasury ID: {treasury_id}")
async def get_rules(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, _, _):
res = await wallet_client.dao_get_rules(wallet_id=wallet_id)
rules = res["rules"]
for rule, val in rules.items():
print(f"{rule}: {val}")
async def add_funds_to_treasury(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
funding_wallet_id = args["funding_wallet_id"]
amount = Decimal(args["amount"])
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
try:
typ = await get_wallet_type(wallet_id=funding_wallet_id, wallet_client=wallet_client)
mojo_per_unit = get_mojo_per_unit(typ)
except LookupError: # pragma: no cover
print(f"Wallet id: {wallet_id} not found.")
return
fee = Decimal(args["fee"])
final_fee: uint64 = uint64(int(fee * units["chia"]))
final_amount: uint64 = uint64(int(amount * mojo_per_unit))
res = await wallet_client.dao_add_funds_to_treasury(
wallet_id=wallet_id,
funding_wallet_id=funding_wallet_id,
amount=final_amount,
fee=final_fee,
tx_config=CMDTXConfigLoader.from_json_dict(
{
"min_coin_amount": args["min_coin_amount"],
"max_coin_amount": args["max_coin_amount"],
"coins_to_exclude": args["coins_to_exclude"],
"amounts_to_exclude": args["amounts_to_exclude"],
"reuse_puzhash": args["reuse_puzhash"],
}
).to_tx_config(units["chia"], config, fingerprint),
)
tx_id = res["tx_id"]
start = time.time()
while time.time() - start < 10:
await asyncio.sleep(0.1)
tx = await wallet_client.get_transaction(wallet_id, bytes32.from_hexstr(tx_id))
if len(tx.sent_to) > 0:
print(transaction_submitted_msg(tx))
print(transaction_status_msg(fingerprint, tx_id[2:]))
return None
print(f"Transaction not yet submitted to nodes. TX ID: {tx_id}") # pragma: no cover
async def get_treasury_balance(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, _, _):
res = await wallet_client.dao_get_treasury_balance(wallet_id=wallet_id)
balances = res["balances"]
if not balances:
print("The DAO treasury currently has no funds")
return None
xch_mojos = get_mojo_per_unit(WalletType.STANDARD_WALLET)
cat_mojos = get_mojo_per_unit(WalletType.CAT)
for asset_id, balance in balances.items():
if asset_id == "xch":
print(f"XCH: {balance / xch_mojos}")
else:
print(f"{asset_id}: {balance / cat_mojos}")
async def list_proposals(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
include_closed = args["include_closed"]
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, _, _):
res = await wallet_client.dao_get_proposals(wallet_id=wallet_id, include_closed=include_closed)
proposals = res["proposals"]
soft_close_length = res["soft_close_length"]
print("############################")
for prop in proposals:
print("Proposal ID: {proposal_id}".format(**prop))
prop_status = "CLOSED" if prop["closed"] else "OPEN"
print(f"Status: {prop_status}")
print("Votes for: {yes_votes}".format(**prop))
votes_against = prop["amount_voted"] - prop["yes_votes"]
print(f"Votes against: {votes_against}")
print("------------------------")
print(f"Proposals have {soft_close_length} blocks of soft close time.")
print("############################")
async def show_proposal(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
proposal_id = args["proposal_id"]
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, _, config):
res = await wallet_client.dao_parse_proposal(wallet_id, proposal_id)
pd = res["proposal_dictionary"]
blocks_needed = pd["state"]["blocks_needed"]
passed = pd["state"]["passed"]
closable = pd["state"]["closable"]
status = "CLOSED" if pd["state"]["closed"] else "OPEN"
votes_needed = pd["state"]["total_votes_needed"]
yes_needed = pd["state"]["yes_votes_needed"]
ptype_val = pd["proposal_type"]
if (ptype_val == "s") and ("mint_amount" in pd):
ptype = "mint"
elif ptype_val == "s":
ptype = "spend"
elif ptype_val == "u":
ptype = "update"
print("")
print(f"Details of Proposal: {proposal_id}")
print("---------------------------")
print("")
print(f"Type: {ptype.upper()}")
print(f"Status: {status}")
print(f"Passed: {passed}")
if not passed:
print(f"Yes votes needed: {yes_needed}")
if not pd["state"]["closed"]:
print(f"Closable: {closable}")
if not closable:
print(f"Total votes needed: {votes_needed}")
print(f"Blocks remaining: {blocks_needed}")
prefix = selected_network_address_prefix(config)
if ptype == "spend":
xch_conds = pd["xch_conditions"]
asset_conds = pd["asset_conditions"]
print("")
if xch_conds:
print("Proposal XCH Conditions")
for pmt in xch_conds:
puzzle_hash = encode_puzzle_hash(bytes32.from_hexstr(pmt["puzzle_hash"]), prefix)
amount = pmt["amount"]
print(f"Address: {puzzle_hash}\nAmount: {amount}\n")
if asset_conds:
print("Proposal asset Conditions")
for cond in asset_conds:
asset_id = cond["asset_id"]
print(f"Asset ID: {asset_id}")
conds = cond["conditions"]
for pmt in conds:
puzzle_hash = encode_puzzle_hash(bytes32.from_hexstr(pmt["puzzle_hash"]), prefix)
amount = pmt["amount"]
print(f"Address: {puzzle_hash}\nAmount: {amount}\n")
elif ptype == "update":
print("")
print("Proposed Rules:")
for key, val in pd["dao_rules"].items():
print(f"{key}: {val}")
elif ptype == "mint":
mint_amount = pd["mint_amount"]
address = encode_puzzle_hash(bytes32.from_hexstr(pd["new_cat_puzhash"]), prefix)
print("")
print(f"Amount of CAT to mint: {mint_amount}")
print(f"Address: {address}")
async def vote_on_proposal(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
vote_amount = args["vote_amount"]
fee = args["fee"]
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
proposal_id = args["proposal_id"]
is_yes_vote = args["is_yes_vote"]
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
res = await wallet_client.dao_vote_on_proposal(
wallet_id=wallet_id,
proposal_id=proposal_id,
vote_amount=vote_amount,
is_yes_vote=is_yes_vote,
fee=final_fee,
tx_config=CMDTXConfigLoader.from_json_dict(
{
"min_coin_amount": args["min_coin_amount"],
"max_coin_amount": args["max_coin_amount"],
"coins_to_exclude": args["coins_to_exclude"],
"amounts_to_exclude": args["amounts_to_exclude"],
"reuse_puzhash": args["reuse_puzhash"],
}
).to_tx_config(units["chia"], config, fingerprint),
)
tx_id = res["tx_id"]
start = time.time()
while time.time() - start < 10:
await asyncio.sleep(0.1)
tx = await wallet_client.get_transaction(wallet_id, bytes32.from_hexstr(tx_id))
if len(tx.sent_to) > 0:
print(transaction_submitted_msg(tx))
print(transaction_status_msg(fingerprint, tx_id[2:]))
return None
print(f"Transaction not yet submitted to nodes. TX ID: {tx_id}") # pragma: no cover
async def close_proposal(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
fee = args["fee"]
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
proposal_id = args["proposal_id"]
self_destruct = args["self_destruct"]
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
res = await wallet_client.dao_close_proposal(
wallet_id=wallet_id,
proposal_id=proposal_id,
fee=final_fee,
self_destruct=self_destruct,
tx_config=CMDTXConfigLoader.from_json_dict(
{
"min_coin_amount": args["min_coin_amount"],
"max_coin_amount": args["max_coin_amount"],
"coins_to_exclude": args["coins_to_exclude"],
"amounts_to_exclude": args["amounts_to_exclude"],
"reuse_puzhash": args["reuse_puzhash"],
}
).to_tx_config(units["chia"], config, fingerprint),
)
tx_id = res["tx_id"]
start = time.time()
while time.time() - start < 10:
await asyncio.sleep(0.1)
tx = await wallet_client.get_transaction(wallet_id, bytes32.from_hexstr(tx_id))
if len(tx.sent_to) > 0:
print(transaction_submitted_msg(tx))
print(transaction_status_msg(fingerprint, tx_id[2:]))
return None
print(f"Transaction not yet submitted to nodes. TX ID: {tx_id}") # pragma: no cover
async def lockup_coins(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
amount = args["amount"]
final_amount: uint64 = uint64(int(Decimal(amount) * units["cat"]))
fee = args["fee"]
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
res = await wallet_client.dao_send_to_lockup(
wallet_id=wallet_id,
amount=final_amount,
fee=final_fee,
tx_config=CMDTXConfigLoader.from_json_dict(
{
"min_coin_amount": args["min_coin_amount"],
"max_coin_amount": args["max_coin_amount"],
"coins_to_exclude": args["coins_to_exclude"],
"amounts_to_exclude": args["amounts_to_exclude"],
"reuse_puzhash": args["reuse_puzhash"],
}
).to_tx_config(units["chia"], config, fingerprint),
)
tx_id = res["tx_id"]
start = time.time()
while time.time() - start < 10:
await asyncio.sleep(0.1)
tx = await wallet_client.get_transaction(wallet_id, bytes32.from_hexstr(tx_id))
if len(tx.sent_to) > 0:
print(transaction_submitted_msg(tx))
print(transaction_status_msg(fingerprint, tx_id[2:]))
return None
print(f"Transaction not yet submitted to nodes. TX ID: {tx_id}") # pragma: no cover
async def release_coins(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
fee = args["fee"]
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
res = await wallet_client.dao_free_coins_from_finished_proposals(
wallet_id=wallet_id,
fee=final_fee,
tx_config=CMDTXConfigLoader.from_json_dict(
{
"min_coin_amount": args["min_coin_amount"],
"max_coin_amount": args["max_coin_amount"],
"coins_to_exclude": args["coins_to_exclude"],
"amounts_to_exclude": args["amounts_to_exclude"],
"reuse_puzhash": args["reuse_puzhash"],
}
).to_tx_config(units["chia"], config, fingerprint),
)
tx_id = res["tx_id"]
start = time.time()
while time.time() - start < 10:
await asyncio.sleep(0.1)
tx = await wallet_client.get_transaction(wallet_id, bytes32.from_hexstr(tx_id))
if len(tx.sent_to) > 0:
print(transaction_submitted_msg(tx))
print(transaction_status_msg(fingerprint, tx_id[2:]))
return None
print(f"Transaction not yet submitted to nodes. TX ID: {tx_id}") # pragma: no cover
async def exit_lockup(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
fee = args["fee"]
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
res = await wallet_client.dao_exit_lockup(
wallet_id=wallet_id,
coins=[],
fee=final_fee,
tx_config=CMDTXConfigLoader.from_json_dict(
{
"min_coin_amount": args["min_coin_amount"],
"max_coin_amount": args["max_coin_amount"],
"coins_to_exclude": args["coins_to_exclude"],
"amounts_to_exclude": args["amounts_to_exclude"],
"reuse_puzhash": args["reuse_puzhash"],
}
).to_tx_config(units["chia"], config, fingerprint),
)
tx_id = res["tx_id"]
start = time.time()
while time.time() - start < 10:
await asyncio.sleep(0.1)
tx = await wallet_client.get_transaction(wallet_id, bytes32.from_hexstr(tx_id))
if len(tx.sent_to) > 0:
print(transaction_submitted_msg(tx))
print(transaction_status_msg(fingerprint, tx_id[2:]))
return None
print(f"Transaction not yet submitted to nodes. TX ID: {tx_id}") # pragma: no cover
async def create_spend_proposal(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
fee = args["fee"]
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
asset_id = args.get("asset_id")
address = args.get("to_address")
amount = args.get("amount")
additions_file = args.get("from_json")
if additions_file is None and (address is None or amount is None):
raise ValueError("Must include a json specification or an address / amount pair.")
if additions_file: # pragma: no cover
with open(additions_file) as f:
additions_dict = json.load(f)
additions = []
for addition in additions_dict:
addition["puzzle_hash"] = decode_puzzle_hash(addition["address"]).hex()
del addition["address"]
additions.append(addition)
else:
additions = None
vote_amount = args.get("vote_amount")
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
wallet_type = await get_wallet_type(wallet_id=wallet_id, wallet_client=wallet_client)
mojo_per_unit = get_mojo_per_unit(wallet_type=wallet_type)
final_amount: Optional[uint64] = uint64(int(Decimal(amount) * mojo_per_unit)) if amount else None
res = await wallet_client.dao_create_proposal(
wallet_id=wallet_id,
proposal_type="spend",
additions=additions,
amount=final_amount,
inner_address=address,
asset_id=asset_id,
vote_amount=vote_amount,
fee=final_fee,
tx_config=CMDTXConfigLoader.from_json_dict(
{
"min_coin_amount": args["min_coin_amount"],
"max_coin_amount": args["max_coin_amount"],
"coins_to_exclude": args["coins_to_exclude"],
"amounts_to_exclude": args["amounts_to_exclude"],
"reuse_puzhash": args["reuse_puzhash"],
}
).to_tx_config(units["chia"], config, fingerprint),
)
if res["success"]:
asset_id_name = asset_id if asset_id else "XCH"
print(f"Created spend proposal for asset: {asset_id_name}")
print("Successfully created proposal.")
print("Proposal ID: {}".format(res["proposal_id"]))
else: # pragma: no cover
print("Failed to create proposal.")
async def create_update_proposal(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
fee = Decimal(args["fee"])
final_fee: uint64 = uint64(int(fee * units["chia"]))
proposal_timelock = args.get("proposal_timelock")
soft_close_length = args.get("soft_close_length")
attendance_required = args.get("attendance_required")
pass_percentage = args.get("pass_percentage")
self_destruct_length = args.get("self_destruct_length")
oracle_spend_delay = args.get("oracle_spend_delay")
vote_amount = args.get("vote_amount")
new_dao_rules = {
"proposal_timelock": proposal_timelock,
"soft_close_length": soft_close_length,
"attendance_required": attendance_required,
"pass_percentage": pass_percentage,
"self_destruct_length": self_destruct_length,
"oracle_spend_delay": oracle_spend_delay,
}
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
res = await wallet_client.dao_create_proposal(
wallet_id=wallet_id,
proposal_type="update",
new_dao_rules=new_dao_rules,
vote_amount=vote_amount,
fee=final_fee,
tx_config=CMDTXConfigLoader.from_json_dict(
{
"min_coin_amount": args["min_coin_amount"],
"max_coin_amount": args["max_coin_amount"],
"coins_to_exclude": args["coins_to_exclude"],
"amounts_to_exclude": args["amounts_to_exclude"],
"reuse_puzhash": args["reuse_puzhash"],
}
).to_tx_config(units["chia"], config, fingerprint),
)
if res["success"]:
print("Successfully created proposal.")
print("Proposal ID: {}".format(res["proposal_id"]))
else: # pragma: no cover
print("Failed to create proposal.")
async def create_mint_proposal(args: Dict[str, Any], wallet_rpc_port: Optional[int], fp: int) -> None:
wallet_id = args["wallet_id"]
fee = args["fee"]
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
cat_target_address = args["cat_target_address"]
amount = args["amount"]
vote_amount = args.get("vote_amount")
async with get_wallet_client(wallet_rpc_port, fp) as (wallet_client, fingerprint, config):
res = await wallet_client.dao_create_proposal(
wallet_id=wallet_id,
proposal_type="mint",
cat_target_address=cat_target_address,
amount=amount,
vote_amount=vote_amount,
fee=final_fee,
tx_config=CMDTXConfigLoader.from_json_dict(
{
"min_coin_amount": args["min_coin_amount"],
"max_coin_amount": args["max_coin_amount"],
"coins_to_exclude": args["coins_to_exclude"],
"amounts_to_exclude": args["amounts_to_exclude"],
"reuse_puzhash": args["reuse_puzhash"],
}
).to_tx_config(units["chia"], config, fingerprint),
)
if res["success"]:
print("Successfully created proposal.")
print("Proposal ID: {}".format(res["proposal_id"]))
else: # pragma: no cover
print("Failed to create proposal.")