async def do_spend( self, sim: SpendSim, sim_client: SimClient, tail: Program, coins: List[Coin], lineage_proofs: List[Program], inner_solutions: List[Program], expected_result: Tuple[MempoolInclusionStatus, Err], reveal_limitations_program: bool = True, signatures: List[G2Element] = [], extra_deltas: Optional[List[int]] = None, additional_spends: List[SpendBundle] = [], limitations_solutions: Optional[List[Program]] = None, cost_str: str = "", ): if limitations_solutions is None: limitations_solutions = [Program.to([])] * len(coins) if extra_deltas is None: extra_deltas = [0] * len(coins) spendable_cat_list: List[SpendableCAT] = [] for coin, innersol, proof, limitations_solution, extra_delta in zip( coins, inner_solutions, lineage_proofs, limitations_solutions, extra_deltas ): spendable_cat_list.append( SpendableCAT( coin, tail.get_tree_hash(), acs, innersol, limitations_solution=limitations_solution, lineage_proof=proof, extra_delta=extra_delta, limitations_program_reveal=tail if reveal_limitations_program else Program.to([]), ) ) spend_bundle: SpendBundle = unsigned_spend_bundle_for_spendable_cats( CAT_MOD, spendable_cat_list, ) agg_sig = AugSchemeMPL.aggregate(signatures) result = await sim_client.push_tx( SpendBundle.aggregate( [ *additional_spends, spend_bundle, SpendBundle([], agg_sig), # "Signing" the spend bundle ] ) ) assert result == expected_result self.cost[cost_str] = cost_of_spend_bundle(spend_bundle) await sim.farm_block()
async def generate_issuance_bundle( cls, wallet, _: Dict, amount: uint64) -> Tuple[TransactionRecord, SpendBundle]: coins = await wallet.standard_wallet.select_coins(amount) origin = coins.copy().pop() origin_id = origin.name() cc_inner: Program = await wallet.get_new_inner_puzzle() await wallet.add_lineage(origin_id, LineageProof()) genesis_coin_checker: Program = cls.construct([Program.to(origin_id)]) minted_cc_puzzle_hash: bytes32 = construct_cat_puzzle( CAT_MOD, genesis_coin_checker.get_tree_hash(), cc_inner).get_tree_hash() tx_record: TransactionRecord = await wallet.standard_wallet.generate_signed_transaction( amount, minted_cc_puzzle_hash, uint64(0), origin_id, coins) assert tx_record.spend_bundle is not None inner_solution = wallet.standard_wallet.add_condition_to_solution( Program.to([51, 0, -113, genesis_coin_checker, []]), wallet.standard_wallet.make_solution(primaries=[{ "puzzlehash": cc_inner.get_tree_hash(), "amount": amount }], ), ) eve_spend = unsigned_spend_bundle_for_spendable_cats( CAT_MOD, [ SpendableCAT( list( filter(lambda a: a.amount == amount, tx_record.additions))[0], genesis_coin_checker.get_tree_hash(), cc_inner, inner_solution, limitations_program_reveal=genesis_coin_checker, ) ], ) signed_eve_spend = await wallet.sign(eve_spend) if wallet.cat_info.my_tail is None: await wallet.save_info( CATInfo(genesis_coin_checker.get_tree_hash(), genesis_coin_checker, wallet.cat_info.lineage_proofs), False, ) return tx_record, SpendBundle.aggregate( [tx_record.spend_bundle, signed_eve_spend])
def generate_secure_bundle( self, selected_coins: List[Coin], announcements: List[Announcement], offered_amount: uint64, tail_str: Optional[str] = None, ) -> SpendBundle: announcement_assertions: List[List] = [[63, a.name()] for a in announcements] selected_coin_amount: int = sum([c.amount for c in selected_coins]) non_primaries: List[Coin] = [] if len( selected_coins) < 2 else selected_coins[1:] inner_solution: List[List] = [ [51, Offer.ph(), offered_amount], # Offered coin [51, acs_ph, uint64(selected_coin_amount - offered_amount)], # Change *announcement_assertions, ] if tail_str is None: bundle = SpendBundle( [ CoinSpend( selected_coins[0], acs, Program.to(inner_solution), ), *[ CoinSpend(c, acs, Program.to([])) for c in non_primaries ], ], G2Element(), ) else: spendable_cats: List[SpendableCAT] = [ SpendableCAT( c, str_to_tail_hash(tail_str), acs, Program.to([ [51, 0, -113, str_to_tail(tail_str), Program.to([])], # Use the TAIL rather than lineage *(inner_solution if c == selected_coins[0] else []), ]), ) for c in selected_coins ] bundle = unsigned_spend_bundle_for_spendable_cats( CAT_MOD, spendable_cats) return bundle
def to_valid_spend(self, arbitrage_ph: Optional[bytes32] = None) -> SpendBundle: if not self.is_valid(): raise ValueError("Offer is currently incomplete") completion_spends: List[CoinSpend] = [] for tail_hash, payments in self.requested_payments.items(): offered_coins: List[Coin] = self.get_offered_coins()[tail_hash] # Because of CAT supply laws, we must specify a place for the leftovers to go arbitrage_amount: int = self.arbitrage()[tail_hash] all_payments: List[NotarizedPayment] = payments.copy() if arbitrage_amount > 0: assert arbitrage_amount is not None assert arbitrage_ph is not None all_payments.append( NotarizedPayment(arbitrage_ph, uint64(arbitrage_amount), [])) for coin in offered_coins: inner_solutions = [] if coin == offered_coins[0]: nonces: List[bytes32] = [p.nonce for p in all_payments] for nonce in list(dict.fromkeys( nonces)): # dedup without messing with order nonce_payments: List[NotarizedPayment] = list( filter(lambda p: p.nonce == nonce, all_payments)) inner_solutions.append( (nonce, [np.as_condition_args() for np in nonce_payments])) if tail_hash: # CATs have a special way to be solved so we have to do some calculation before getting the solution parent_spend: CoinSpend = list( filter( lambda cs: cs.coin.name() == coin.parent_coin_info, self.bundle.coin_spends))[0] parent_coin: Coin = parent_spend.coin matched, curried_args = match_cat_puzzle( parent_spend.puzzle_reveal.to_program()) assert matched _, _, inner_puzzle = curried_args spendable_cat = SpendableCAT( coin, tail_hash, OFFER_MOD, Program.to(inner_solutions), lineage_proof=LineageProof( parent_coin.parent_coin_info, inner_puzzle.get_tree_hash(), parent_coin.amount), ) solution: Program = ( unsigned_spend_bundle_for_spendable_cats( CAT_MOD, [spendable_cat ]).coin_spends[0].solution.to_program()) else: solution = Program.to(inner_solutions) completion_spends.append( CoinSpend( coin, construct_cat_puzzle(CAT_MOD, tail_hash, OFFER_MOD) if tail_hash else OFFER_MOD, solution, )) return SpendBundle.aggregate( [SpendBundle(completion_spends, G2Element()), self.bundle])
async def generate_unsigned_spendbundle( self, payments: List[Payment], fee: uint64 = uint64(0), cat_discrepancy: Optional[Tuple[ int, Program]] = None, # (extra_delta, limitations_solution) coins: Set[Coin] = None, coin_announcements_to_consume: Optional[Set[Announcement]] = None, puzzle_announcements_to_consume: Optional[Set[Announcement]] = None, ) -> Tuple[SpendBundle, Optional[TransactionRecord]]: if coin_announcements_to_consume is not None: coin_announcements_bytes: Optional[Set[bytes32]] = { a.name() for a in coin_announcements_to_consume } else: coin_announcements_bytes = None if puzzle_announcements_to_consume is not None: puzzle_announcements_bytes: Optional[Set[bytes32]] = { a.name() for a in puzzle_announcements_to_consume } else: puzzle_announcements_bytes = None if cat_discrepancy is not None: extra_delta, limitations_solution = cat_discrepancy else: extra_delta, limitations_solution = 0, Program.to([]) payment_amount: int = sum([p.amount for p in payments]) starting_amount: int = payment_amount - extra_delta if coins is None: cat_coins = await self.select_coins(uint64(starting_amount)) else: cat_coins = coins selected_cat_amount = sum([c.amount for c in cat_coins]) assert selected_cat_amount >= starting_amount # Figure out if we need to absorb/melt some XCH as part of this regular_chia_to_claim: int = 0 if payment_amount > starting_amount: fee = uint64(fee + payment_amount - starting_amount) elif payment_amount < starting_amount: regular_chia_to_claim = payment_amount need_chia_transaction = (fee > 0 or regular_chia_to_claim > 0) and ( fee - regular_chia_to_claim != 0) # Calculate standard puzzle solutions change = selected_cat_amount - starting_amount primaries: List[AmountWithPuzzlehash] = [] for payment in payments: primaries.append({ "puzzlehash": payment.puzzle_hash, "amount": payment.amount, "memos": payment.memos }) if change > 0: changepuzzlehash = await self.get_new_inner_hash() primaries.append({ "puzzlehash": changepuzzlehash, "amount": uint64(change), "memos": [] }) limitations_program_reveal = Program.to([]) if self.cat_info.my_tail is None: assert cat_discrepancy is None elif cat_discrepancy is not None: limitations_program_reveal = self.cat_info.my_tail # Loop through the coins we've selected and gather the information we need to spend them spendable_cc_list = [] chia_tx = None first = True for coin in cat_coins: if first: first = False if need_chia_transaction: if fee > regular_chia_to_claim: announcement = Announcement(coin.name(), b"$", b"\xca") chia_tx, _ = await self.create_tandem_xch_tx( fee, uint64(regular_chia_to_claim), announcement_to_assert=announcement) innersol = self.standard_wallet.make_solution( primaries=primaries, coin_announcements={announcement.message}, coin_announcements_to_assert= coin_announcements_bytes, puzzle_announcements_to_assert= puzzle_announcements_bytes, ) elif regular_chia_to_claim > fee: chia_tx, _ = await self.create_tandem_xch_tx( fee, uint64(regular_chia_to_claim)) innersol = self.standard_wallet.make_solution( primaries=primaries, coin_announcements_to_assert={announcement.name()}) else: innersol = self.standard_wallet.make_solution( primaries=primaries, coin_announcements_to_assert=coin_announcements_bytes, puzzle_announcements_to_assert= puzzle_announcements_bytes, ) else: innersol = self.standard_wallet.make_solution(primaries=[]) inner_puzzle = await self.inner_puzzle_for_cc_puzhash( coin.puzzle_hash) lineage_proof = await self.get_lineage_proof_for_coin(coin) assert lineage_proof is not None new_spendable_cc = SpendableCAT( coin, self.cat_info.limitations_program_hash, inner_puzzle, innersol, limitations_solution=limitations_solution, extra_delta=extra_delta, lineage_proof=lineage_proof, limitations_program_reveal=limitations_program_reveal, ) spendable_cc_list.append(new_spendable_cc) cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats( CAT_MOD, spendable_cc_list) chia_spend_bundle = SpendBundle([], G2Element()) if chia_tx is not None and chia_tx.spend_bundle is not None: chia_spend_bundle = chia_tx.spend_bundle return ( SpendBundle.aggregate([ cat_spend_bundle, chia_spend_bundle, ]), chia_tx, )
async def generate_coins( self, sim, sim_client, requested_coins: Dict[Optional[str], List[uint64]], ) -> Dict[Optional[str], List[Coin]]: await sim.farm_block(acs_ph) parent_coin: Coin = [ cr.coin for cr in await ( sim_client.get_coin_records_by_puzzle_hash(acs_ph)) ][0] # We need to gather a list of initial coins to create as well as spends that do the eve spend for every CAT payments: List[Payment] = [] cat_bundles: List[SpendBundle] = [] for tail_str, amounts in requested_coins.items(): for amount in amounts: if tail_str: tail: Program = str_to_tail( tail_str) # Making a fake but unique TAIL cat_puzzle: Program = construct_cat_puzzle( CAT_MOD, tail.get_tree_hash(), acs) payments.append( Payment(cat_puzzle.get_tree_hash(), amount, [])) cat_bundles.append( unsigned_spend_bundle_for_spendable_cats( CAT_MOD, [ SpendableCAT( Coin(parent_coin.name(), cat_puzzle.get_tree_hash(), amount), tail.get_tree_hash(), acs, Program.to([[51, acs_ph, amount], [51, 0, -113, tail, []]]), ) ], )) else: payments.append(Payment(acs_ph, amount, [])) # This bundle create all of the initial coins parent_bundle = SpendBundle( [ CoinSpend( parent_coin, acs, Program.to([[51, p.puzzle_hash, p.amount] for p in payments]), ) ], G2Element(), ) # Then we aggregate it with all of the eve spends await sim_client.push_tx( SpendBundle.aggregate([parent_bundle, *cat_bundles])) await sim.farm_block() # Search for all of the coins and put them into a dictionary coin_dict: Dict[Optional[str], List[Coin]] = {} for tail_str, _ in requested_coins.items(): if tail_str: tail_hash: bytes32 = str_to_tail_hash(tail_str) cat_ph: bytes32 = construct_cat_puzzle(CAT_MOD, tail_hash, acs).get_tree_hash() coin_dict[tail_str] = [ cr.coin for cr in await ( sim_client.get_coin_records_by_puzzle_hash( cat_ph, include_spent_coins=False)) ] else: coin_dict[None] = list( filter( lambda c: c.amount < 250000000000, [ cr.coin for cr in await ( sim_client.get_coin_records_by_puzzle_hash( acs_ph, include_spent_coins=False)) ], )) return coin_dict