Files
chia-blockchain/chia/wallet/cat_wallet/dao_cat_wallet.py
T
6c90a76b56 Tighten up ruff ignore list (#18837)
* Tighten up ruff ignore list

* Okay that fix was indeed unsafe

* enable type-name-incorrect-variance

* enable literal-membership

* enable non-augmented-assignment

* enable useless-return

* enable global-variable-not-assigned

* -

* fixup

* Clean up roff.toml from annotations

* use ignore instead of explicit re-export

* use more descriptive names

---------

Co-authored-by: Kyle Altendorf <sda@fstab.net>
2024-11-12 12:19:31 -07:00

670 lines
28 KiB
Python

from __future__ import annotations
import logging
import time
from typing import TYPE_CHECKING, Any, ClassVar, Optional, cast
from chia_rs import G1Element
from chia.server.ws_connection import WSChiaConnection
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.byte_types import hexstr_to_bytes
from chia.util.ints import uint32, uint64, uint128
from chia.wallet.cat_wallet.cat_utils import (
CAT_MOD,
SpendableCAT,
construct_cat_puzzle,
unsigned_spend_bundle_for_spendable_cats,
)
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.wallet.cat_wallet.dao_cat_info import DAOCATInfo, LockedCoinInfo
from chia.wallet.cat_wallet.lineage_store import CATLineageStore
from chia.wallet.conditions import Condition, CreatePuzzleAnnouncement, parse_timelock_info
from chia.wallet.dao_wallet.dao_utils import (
add_proposal_to_active_list,
get_active_votes_from_lockup_puzzle,
get_finished_state_inner_puzzle,
get_innerpuz_from_lockup_puzzle,
get_lockup_puzzle,
)
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.payment import Payment
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.util.curry_and_treehash import calculate_hash_of_quoted_mod_hash
from chia.wallet.util.transaction_type import TransactionType
from chia.wallet.util.tx_config import TXConfig
from chia.wallet.util.wallet_sync_utils import fetch_coin_spend
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.wallet import Wallet
from chia.wallet.wallet_action_scope import WalletActionScope
from chia.wallet.wallet_coin_record import WalletCoinRecord
from chia.wallet.wallet_info import WalletInfo
from chia.wallet.wallet_spend_bundle import WalletSpendBundle
if TYPE_CHECKING:
from chia.wallet.wallet_state_manager import WalletStateManager
CAT_MOD_HASH = CAT_MOD.get_tree_hash()
CAT_MOD_HASH_HASH = Program.to(CAT_MOD_HASH).get_tree_hash()
QUOTED_MOD_HASH = calculate_hash_of_quoted_mod_hash(CAT_MOD_HASH)
class DAOCATWallet:
if TYPE_CHECKING:
from chia.wallet.wallet_protocol import WalletProtocol
_protocol_check: ClassVar[WalletProtocol[DAOCATInfo]] = cast("DAOCATWallet", None)
wallet_state_manager: Any
log: logging.Logger
wallet_info: WalletInfo
dao_cat_info: DAOCATInfo
standard_wallet: Wallet
cost_of_single_tx: Optional[int]
lineage_store: CATLineageStore
@classmethod
def type(cls) -> WalletType:
return WalletType.DAO_CAT
@staticmethod
async def create(
wallet_state_manager: WalletStateManager,
wallet: Wallet,
wallet_info: WalletInfo,
) -> DAOCATWallet:
self = DAOCATWallet()
self.log = logging.getLogger(__name__)
self.cost_of_single_tx = None
self.wallet_state_manager = wallet_state_manager
self.wallet_info = wallet_info
self.standard_wallet = wallet
try:
self.dao_cat_info = DAOCATInfo.from_bytes(hexstr_to_bytes(self.wallet_info.data))
self.lineage_store = await CATLineageStore.create(self.wallet_state_manager.db_wrapper, self.get_asset_id())
except AssertionError as e: # pragma: no cover
self.log.error(f"Error creating DAO CAT wallet: {e}")
return self
@staticmethod
async def get_or_create_wallet_for_cat(
wallet_state_manager: Any,
wallet: Wallet,
limitations_program_hash_hex: str,
name: Optional[str] = None,
) -> DAOCATWallet:
self = DAOCATWallet()
self.cost_of_single_tx = None
self.standard_wallet = wallet
self.log = logging.getLogger(__name__)
limitations_program_hash_hex = bytes32.from_hexstr(limitations_program_hash_hex).hex() # Normalize the format
dao_wallet_id = None
free_cat_wallet_id = None
for id, w in wallet_state_manager.wallets.items():
if w.type() == DAOCATWallet.type():
assert isinstance(w, DAOCATWallet)
if w.get_asset_id() == limitations_program_hash_hex:
self.log.warning("Not creating wallet for already existing DAO CAT wallet")
return w
elif w.type() == CATWallet.type():
assert isinstance(w, CATWallet)
if w.get_asset_id() == limitations_program_hash_hex:
free_cat_wallet_id = w.id()
assert free_cat_wallet_id is not None
for id, w in wallet_state_manager.wallets.items():
if w.type() == WalletType.DAO:
self.log.info(f"FOUND DAO WALLET: {w}")
self.log.info(f"ALL WALLETS: {wallet_state_manager.wallets}")
if w.get_cat_wallet_id() == free_cat_wallet_id:
dao_wallet_id = w.id()
assert dao_wallet_id is not None
self.wallet_state_manager = wallet_state_manager
if name is None:
name = CATWallet.default_wallet_name_for_unknown_cat(limitations_program_hash_hex)
limitations_program_hash = bytes32.from_hexstr(limitations_program_hash_hex)
self.dao_cat_info = DAOCATInfo(
dao_wallet_id,
uint64(free_cat_wallet_id),
limitations_program_hash,
None,
[],
)
info_as_string = bytes(self.dao_cat_info).hex()
self.wallet_info = await wallet_state_manager.user_store.create_wallet(name, WalletType.DAO_CAT, info_as_string)
self.lineage_store = await CATLineageStore.create(self.wallet_state_manager.db_wrapper, self.get_asset_id())
await self.wallet_state_manager.add_new_wallet(self)
return self
async def coin_added(self, coin: Coin, height: uint32, peer: WSChiaConnection, coin_data: Optional[Any]) -> None:
"""Notification from wallet state manager that wallet has been received."""
self.log.info(f"DAO CAT wallet has been notified that {coin} was added")
wallet_node: Any = self.wallet_state_manager.wallet_node
parent_coin = (await wallet_node.get_coin_state([coin.parent_coin_info], peer, height))[0]
parent_spend = await fetch_coin_spend(height, parent_coin.coin, peer)
uncurried = parent_spend.puzzle_reveal.uncurry()
cat_inner = uncurried[1].at("rrf")
active_votes_list: list[Optional[bytes32]] = []
record = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(coin.puzzle_hash)
if record:
inner_puzzle: Optional[Program] = self.standard_wallet.puzzle_for_pk(record.pubkey)
else:
inner_puzzle = get_innerpuz_from_lockup_puzzle(cat_inner)
assert isinstance(inner_puzzle, Program)
active_votes_list_prg = get_active_votes_from_lockup_puzzle(cat_inner)
active_votes_list = [bytes32(x.as_atom()) for x in active_votes_list_prg.as_iter()]
if parent_spend.coin.puzzle_hash == coin.puzzle_hash:
# shortcut, works for change
lockup_puz = cat_inner
else:
solution = parent_spend.solution.to_program().first()
if solution.first() == Program.to(0):
# No vote is being added so inner puz stays the same
try:
removals = solution.at("rrrf")
if removals != Program.to(0):
for removal in removals.as_iter():
active_votes_list.remove(bytes32(removal.as_atom()))
except Exception:
pass
else:
new_vote = solution.at("rrrf")
active_votes_list.insert(0, bytes32(new_vote.as_atom()))
lockup_puz = get_lockup_puzzle(
self.dao_cat_info.limitations_program_hash,
active_votes_list,
inner_puzzle,
)
new_cat_puzhash = construct_cat_puzzle(
CAT_MOD, self.dao_cat_info.limitations_program_hash, lockup_puz
).get_tree_hash()
if new_cat_puzhash != coin.puzzle_hash: # pragma: no cover
raise ValueError(f"Cannot add coin - incorrect lockup puzzle: {coin}")
lineage_proof = LineageProof(coin.parent_coin_info, lockup_puz.get_tree_hash(), uint64(coin.amount))
await self.add_lineage(coin.name(), lineage_proof)
# add the new coin to the list of locked coins and remove the spent coin
locked_coins = [x for x in self.dao_cat_info.locked_coins if x.coin != parent_spend.coin]
new_info = LockedCoinInfo(coin, lockup_puz, active_votes_list)
if new_info not in locked_coins:
locked_coins.append(LockedCoinInfo(coin, lockup_puz, active_votes_list))
dao_cat_info: DAOCATInfo = DAOCATInfo(
self.dao_cat_info.dao_wallet_id,
self.dao_cat_info.free_cat_wallet_id,
self.dao_cat_info.limitations_program_hash,
self.dao_cat_info.my_tail,
locked_coins,
)
await self.save_info(dao_cat_info)
async def add_lineage(self, name: bytes32, lineage: Optional[LineageProof]) -> None:
"""
Lineage proofs are stored as a list of parent coins and the lineage proof you will need if they are the
parent of the coin you are trying to spend. 'If I'm your parent, here's the info you need to spend yourself'
"""
self.log.info(f"Adding parent {name.hex()}: {lineage}")
if lineage is not None:
await self.lineage_store.add_lineage_proof(name, lineage)
async def get_lineage_proof_for_coin(self, coin: Coin) -> Optional[LineageProof]:
return await self.lineage_store.get_lineage_proof(coin.parent_coin_info)
async def remove_lineage(self, name: bytes32) -> None: # pragma: no cover
self.log.info(f"Removing parent {name} (probably had a non-CAT parent)")
await self.lineage_store.remove_lineage_proof(name)
async def advanced_select_coins(self, amount: uint64, proposal_id: bytes32) -> list[LockedCoinInfo]:
coins = []
s = 0
for coin in self.dao_cat_info.locked_coins:
compatible = True
for active_vote in coin.active_votes:
if active_vote == proposal_id: # pragma: no cover
compatible = False
break
if compatible:
coins.append(coin)
s += coin.coin.amount
if s >= amount:
break
if s < amount: # pragma: no cover
raise ValueError(
"We do not have enough CATs in Voting Mode right now. "
"Please convert some more or try again with permission to convert."
)
return coins
def id(self) -> uint32:
return self.wallet_info.id
async def create_vote_spend(
self,
amount: uint64,
proposal_id: bytes32,
is_yes_vote: bool,
proposal_puzzle: Optional[Program] = None,
) -> WalletSpendBundle:
coins: list[LockedCoinInfo] = await self.advanced_select_coins(amount, proposal_id)
running_sum = 0 # this will be used for change calculation
change = sum(c.coin.amount for c in coins) - amount
extra_delta, limitations_solution = 0, Program.to([])
limitations_program_reveal = Program.to([])
spendable_cat_list = []
dao_wallet = self.wallet_state_manager.wallets[self.dao_cat_info.dao_wallet_id]
if proposal_puzzle is None: # pragma: no cover
proposal_puzzle = dao_wallet.get_proposal_puzzle(proposal_id)
assert proposal_puzzle is not None
for lci in coins:
coin = lci.coin
vote_info = 0
new_innerpuzzle = add_proposal_to_active_list(lci.inner_puzzle, proposal_id)
assert new_innerpuzzle is not None
standard_inner_puz = get_innerpuz_from_lockup_puzzle(new_innerpuzzle)
assert isinstance(standard_inner_puz, Program)
# add_proposal_to_active_list also verifies that the lci.inner_puzzle is accurate
# We must create either: one coin with the new puzzle and all our value
# OR
# a coin with the new puzzle and part of our amount AND a coin with our current puzzle and the change
# We must also create a puzzle announcement which announces the following:
# message = (sha256tree (list new_proposal_vote_id_or_removal_id vote_amount vote_info my_id))
message = Program.to([proposal_id, amount, is_yes_vote, coin.name()]).get_tree_hash()
vote_amounts_list = []
voting_coin_id_list = []
previous_votes_list = []
lockup_innerpuz_list = []
if running_sum + coin.amount <= amount:
vote_amount = coin.amount
running_sum += coin.amount
primaries = [
Payment(
new_innerpuzzle.get_tree_hash(),
vote_amount,
[standard_inner_puz.get_tree_hash()],
)
]
message = Program.to([proposal_id, vote_amount, is_yes_vote, coin.name()]).get_tree_hash()
inner_solution = self.standard_wallet.make_solution(
primaries=primaries,
conditions=(CreatePuzzleAnnouncement(message),),
)
else:
vote_amount = uint64(amount - running_sum)
running_sum += coin.amount
primaries = [
Payment(
new_innerpuzzle.get_tree_hash(),
vote_amount,
[standard_inner_puz.get_tree_hash()],
),
]
if change > 0:
primaries.append(
Payment(
lci.inner_puzzle.get_tree_hash(),
uint64(change),
[lci.inner_puzzle.get_tree_hash()],
)
)
message = Program.to([proposal_id, vote_amount, is_yes_vote, coin.name()]).get_tree_hash()
inner_solution = self.standard_wallet.make_solution(
primaries=primaries,
conditions=(CreatePuzzleAnnouncement(message),),
)
if is_yes_vote:
vote_info = 1
vote_amounts_list.append(vote_amount)
voting_coin_id_list.append(coin.name())
previous_votes_list.append(get_active_votes_from_lockup_puzzle(lci.inner_puzzle))
lockup_innerpuz_list.append(get_innerpuz_from_lockup_puzzle(lci.inner_puzzle))
solution = Program.to(
[
coin.name(),
inner_solution,
coin.amount,
proposal_id,
proposal_puzzle.get_tree_hash(),
vote_info,
vote_amount,
lci.inner_puzzle.get_tree_hash(),
0,
]
)
lineage_proof = await self.get_lineage_proof_for_coin(coin)
assert lineage_proof is not None
new_spendable_cat = SpendableCAT(
coin,
self.dao_cat_info.limitations_program_hash,
lci.inner_puzzle,
solution,
limitations_solution=limitations_solution,
extra_delta=extra_delta,
lineage_proof=lineage_proof,
limitations_program_reveal=limitations_program_reveal,
)
spendable_cat_list.append(new_spendable_cat)
cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list)
return cat_spend_bundle
async def enter_dao_cat_voting_mode(
self,
amount: uint64,
action_scope: WalletActionScope,
fee: uint64 = uint64(0),
extra_conditions: tuple[Condition, ...] = tuple(),
) -> list[TransactionRecord]:
"""
Enter existing CATs for the DAO into voting mode
"""
# check there are enough cats to convert
cat_wallet = self.wallet_state_manager.wallets[self.dao_cat_info.free_cat_wallet_id]
cat_balance = await cat_wallet.get_spendable_balance()
if cat_balance < amount: # pragma: no cover
raise ValueError(f"Insufficient CAT balance. Requested: {amount} Available: {cat_balance}")
# get the lockup puzzle hash
lockup_puzzle = await self.get_new_puzzle()
# create the cat spend
txs: list[TransactionRecord] = await cat_wallet.generate_signed_transaction(
[amount],
[lockup_puzzle.get_tree_hash()],
action_scope,
fee=fee,
extra_conditions=extra_conditions,
)
cat_puzzle_hash: bytes32 = construct_cat_puzzle(
CAT_MOD, self.dao_cat_info.limitations_program_hash, lockup_puzzle
).get_tree_hash()
await self.wallet_state_manager.add_interested_puzzle_hashes([cat_puzzle_hash], [self.id()])
return txs
async def exit_vote_state(
self,
coins: list[LockedCoinInfo],
action_scope: WalletActionScope,
fee: uint64 = uint64(0),
extra_conditions: tuple[Condition, ...] = tuple(),
) -> None:
extra_delta, limitations_solution = 0, Program.to([])
limitations_program_reveal = Program.to([])
spendable_cat_list = []
total_amt = 0
spent_coins = []
for lci in coins:
coin = lci.coin
if action_scope.config.tx_config.reuse_puzhash: # pragma: no cover
new_inner_puzhash = await self.standard_wallet.get_puzzle_hash(new=False)
else:
new_inner_puzhash = await self.standard_wallet.get_puzzle_hash(new=True)
# CREATE_COIN new_puzzle coin.amount
primaries = [
Payment(
new_inner_puzhash,
uint64(coin.amount),
[new_inner_puzhash],
),
]
total_amt += coin.amount
inner_solution = self.standard_wallet.make_solution(
primaries=primaries,
)
# Create the solution using only the values needed for exiting the lockup mode (my_id = 0)
solution = Program.to(
[
0, # my_id
inner_solution,
coin.amount,
0, # new_proposal_vote_id_or_removal_id
0, # proposal_innerpuzhash
0, # vote_info
0, # vote_amount
0, # my_inner_puzhash
]
)
lineage_proof = await self.get_lineage_proof_for_coin(coin)
assert lineage_proof is not None
new_spendable_cat = SpendableCAT(
coin,
self.dao_cat_info.limitations_program_hash,
lci.inner_puzzle,
solution,
limitations_solution=limitations_solution,
extra_delta=extra_delta,
lineage_proof=lineage_proof,
limitations_program_reveal=limitations_program_reveal,
)
spendable_cat_list.append(new_spendable_cat)
spent_coins.append(coin)
spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list)
if fee > 0: # pragma: no cover
await self.standard_wallet.create_tandem_xch_tx(
fee,
action_scope,
)
record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=new_inner_puzhash,
amount=uint64(total_amt),
fee_amount=fee,
confirmed=False,
sent=uint32(10),
spend_bundle=spend_bundle,
additions=spend_bundle.additions(),
removals=spend_bundle.removals(),
wallet_id=self.id(),
sent_to=[],
trade_id=None,
type=uint32(TransactionType.INCOMING_TX.value),
name=spend_bundle.name(),
memos=[],
valid_times=parse_timelock_info(extra_conditions),
)
# TODO: Hack to just drop coins from locked list. Need to catch this event in WSM to
# check if we're adding CATs from our DAO CAT wallet and update the locked coin list
# accordingly
new_locked_coins = [x for x in self.dao_cat_info.locked_coins if x.coin not in spent_coins]
dao_cat_info: DAOCATInfo = DAOCATInfo(
self.dao_cat_info.dao_wallet_id,
self.dao_cat_info.free_cat_wallet_id,
self.dao_cat_info.limitations_program_hash,
self.dao_cat_info.my_tail,
new_locked_coins,
)
await self.save_info(dao_cat_info)
async with action_scope.use() as interface:
interface.side_effects.transactions.append(record)
async def remove_active_proposal(
self,
proposal_id_list: list[bytes32],
action_scope: WalletActionScope,
fee: uint64 = uint64(0),
) -> WalletSpendBundle:
locked_coins: list[tuple[LockedCoinInfo, list[bytes32]]] = []
for lci in self.dao_cat_info.locked_coins:
my_finished_proposals = []
for active_vote in lci.active_votes:
if active_vote in proposal_id_list:
my_finished_proposals.append(active_vote)
if my_finished_proposals:
locked_coins.append((lci, my_finished_proposals))
extra_delta, limitations_solution = 0, Program.to([])
limitations_program_reveal = Program.to([])
spendable_cat_list = []
for lci_proposals_tuple in locked_coins:
proposal_innerpuzhashes = []
coin = lci_proposals_tuple[0].coin
lci = lci_proposals_tuple[0]
proposals = lci_proposals_tuple[1]
for proposal_id in proposals:
INNERPUZ = get_finished_state_inner_puzzle(proposal_id)
proposal_innerpuzhashes.append(INNERPUZ)
# new_innerpuzzle = await cat_wallet.get_new_inner_puzzle()
# my_id ; if my_id is 0 we do the return to return_address (exit voting mode) spend case
# inner_solution
# my_amount
# new_proposal_vote_id_or_removal_id ; if we're exiting fully, set this to 0
# proposal_curry_vals
# vote_info
# vote_amount
# my_puzhash
solution = Program.to(
[
0,
0,
coin.amount,
proposals,
0,
0,
0,
0,
0,
]
)
lineage_proof = await self.get_lineage_proof_for_coin(coin)
assert lineage_proof is not None
new_spendable_cat = SpendableCAT(
coin,
self.dao_cat_info.limitations_program_hash,
lci.inner_puzzle,
solution,
limitations_solution=limitations_solution,
extra_delta=extra_delta,
lineage_proof=lineage_proof,
limitations_program_reveal=limitations_program_reveal,
)
spendable_cat_list.append(new_spendable_cat)
spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list)
if fee > 0: # pragma: no cover
await self.standard_wallet.create_tandem_xch_tx(fee, action_scope=action_scope)
return spend_bundle
def get_asset_id(self) -> str:
return bytes(self.dao_cat_info.limitations_program_hash).hex()
async def get_new_inner_hash(self, tx_config: TXConfig) -> bytes32:
puzzle = await self.get_new_inner_puzzle(tx_config)
return puzzle.get_tree_hash()
async def get_new_inner_puzzle(self, tx_config: TXConfig) -> Program:
return await self.standard_wallet.get_puzzle(new=not tx_config.reuse_puzhash)
async def get_new_puzzle(self) -> Program:
record = await self.wallet_state_manager.get_unused_derivation_record(self.id())
inner_puzzle = self.standard_wallet.puzzle_for_pk(record.pubkey)
puzzle = get_lockup_puzzle(
self.dao_cat_info.limitations_program_hash,
[],
inner_puzzle,
)
cat_puzzle: Program = construct_cat_puzzle(CAT_MOD, self.dao_cat_info.limitations_program_hash, puzzle)
await self.wallet_state_manager.add_interested_puzzle_hashes([puzzle.get_tree_hash()], [self.id()])
await self.wallet_state_manager.add_interested_puzzle_hashes([cat_puzzle.get_tree_hash()], [self.id()])
return puzzle
async def get_new_puzzlehash(self) -> bytes32:
puzzle = await self.get_new_puzzle()
return puzzle.get_tree_hash()
def puzzle_for_pk(self, pubkey: G1Element) -> Program:
inner_puzzle = self.standard_wallet.puzzle_for_pk(pubkey)
puzzle = get_lockup_puzzle(
self.dao_cat_info.limitations_program_hash,
[],
inner_puzzle,
)
cat_puzzle: Program = construct_cat_puzzle(CAT_MOD, self.dao_cat_info.limitations_program_hash, puzzle)
return cat_puzzle
def puzzle_hash_for_pk(self, pubkey: G1Element) -> bytes32:
puzzle = self.puzzle_for_pk(pubkey)
return puzzle.get_tree_hash()
def require_derivation_paths(self) -> bool:
return True
async def match_hinted_coin(self, coin: Coin, hint: bytes32) -> bool:
raise NotImplementedError("Method not implemented for DAO CAT Wallet") # pragma: no cover
async def get_spendable_balance(self, records: Optional[set[WalletCoinRecord]] = None) -> uint128:
return uint128(0)
async def get_confirmed_balance(self, record_list: Optional[set[WalletCoinRecord]] = None) -> uint128:
amount = 0
for coin in self.dao_cat_info.locked_coins:
amount += coin.coin.amount
return uint128(amount)
async def get_unconfirmed_balance(self, unspent_records: Optional[set[WalletCoinRecord]] = None) -> uint128:
return uint128(0)
async def get_pending_change_balance(self) -> uint64:
return uint64(0)
async def select_coins(
self,
amount: uint64,
action_scope: WalletActionScope,
) -> set[Coin]:
return set()
async def get_max_send_amount(self, unspent_records: Optional[set[WalletCoinRecord]] = None) -> uint128:
return uint128(0)
async def get_votable_balance(
self,
proposal_id: Optional[bytes32] = None,
include_free_cats: bool = True,
) -> uint64:
balance = 0
for coin in self.dao_cat_info.locked_coins:
if proposal_id is not None:
compatible = True
for active_vote in coin.active_votes:
if active_vote == proposal_id:
compatible = False
break
if compatible:
balance += coin.coin.amount
else:
balance += coin.coin.amount
if include_free_cats:
cat_wallet = self.wallet_state_manager.wallets[self.dao_cat_info.free_cat_wallet_id]
cat_balance = await cat_wallet.get_spendable_balance()
balance += cat_balance
return uint64(balance)
async def save_info(self, dao_cat_info: DAOCATInfo) -> None:
self.dao_cat_info = dao_cat_info
current_info = self.wallet_info
data_str = bytes(dao_cat_info).hex()
wallet_info = WalletInfo(current_info.id, current_info.name, current_info.type, data_str)
self.wallet_info = wallet_info
await self.wallet_state_manager.user_store.update_wallet(wallet_info)
def get_name(self) -> str:
return self.wallet_info.name