async def _create_offer_for_ids( self, offer_dict: Dict[Union[int, bytes32], int], fee: uint64 = uint64(0) ) -> Tuple[bool, Optional[Offer], Optional[str]]: """ Offer is dictionary of wallet ids and amount """ try: coins_to_offer: Dict[uint32, List[Coin]] = {} requested_payments: Dict[Optional[bytes32], List[Payment]] = {} for id, amount in offer_dict.items(): if amount > 0: if isinstance(id, int): wallet_id = uint32(id) wallet = self.wallet_state_manager.wallets[wallet_id] p2_ph: bytes32 = await wallet.get_new_puzzlehash() if wallet.type() == WalletType.STANDARD_WALLET: key: Optional[bytes32] = None memos: List[bytes] = [] elif wallet.type() == WalletType.CAT: key = bytes32(bytes.fromhex(wallet.get_asset_id())) memos = [p2_ph] else: raise ValueError(f"Offers are not implemented for {wallet.type()}") else: p2_ph = await self.wallet_state_manager.main_wallet.get_new_puzzlehash() key = id memos = [p2_ph] requested_payments[key] = [Payment(p2_ph, uint64(amount), memos)] elif amount < 0: assert isinstance(id, int) wallet_id = uint32(id) wallet = self.wallet_state_manager.wallets[wallet_id] balance = await wallet.get_confirmed_balance() if balance < abs(amount): raise Exception(f"insufficient funds in wallet {wallet_id}") coins_to_offer[wallet_id] = await wallet.select_coins(uint64(abs(amount))) elif amount == 0: raise ValueError("You cannot offer nor request 0 amount of something") all_coins: List[Coin] = [c for coins in coins_to_offer.values() for c in coins] notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( requested_payments, all_coins ) announcements_to_assert = Offer.calculate_announcements(notarized_payments) all_transactions: List[TransactionRecord] = [] fee_left_to_pay: uint64 = fee for wallet_id, selected_coins in coins_to_offer.items(): wallet = self.wallet_state_manager.wallets[wallet_id] # This should probably not switch on whether or not we're spending a CAT but it has to for now if wallet.type() == WalletType.CAT: txs = await wallet.generate_signed_transaction( [abs(offer_dict[int(wallet_id)])], [Offer.ph()], fee=fee_left_to_pay, coins=set(selected_coins), puzzle_announcements_to_consume=announcements_to_assert, ) all_transactions.extend(txs) else: tx = await wallet.generate_signed_transaction( abs(offer_dict[int(wallet_id)]), Offer.ph(), fee=fee_left_to_pay, coins=set(selected_coins), puzzle_announcements_to_consume=announcements_to_assert, ) all_transactions.append(tx) fee_left_to_pay = uint64(0) transaction_bundles: List[Optional[SpendBundle]] = [tx.spend_bundle for tx in all_transactions] total_spend_bundle = SpendBundle.aggregate(list(filter(lambda b: b is not None, transaction_bundles))) offer = Offer(notarized_payments, total_spend_bundle) return True, offer, None except Exception as e: tb = traceback.format_exc() self.log.error(f"Error with creating trade offer: {type(e)}{tb}") return False, None, str(e)
async def test_complex_offer(self, setup_sim): sim, sim_client = setup_sim try: coins_needed: Dict[Optional[str], List[int]] = { None: [500, 400, 300], "red": [250, 100], "blue": [3000], } all_coins: Dict[Optional[str], List[Coin]] = await self.generate_coins( sim, sim_client, coins_needed) chia_coins: List[Coin] = all_coins[None] red_coins: List[Coin] = all_coins["red"] blue_coins: List[Coin] = all_coins["blue"] # Create an XCH Offer for RED chia_requested_payments: Dict[Optional[bytes32], List[Payment]] = { str_to_tail_hash("red"): [ Payment(acs_ph, 100, [b"memo"]), Payment(acs_ph, 200, [b"memo"]), ] } chia_requested_payments: Dict[ Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( chia_requested_payments, chia_coins) chia_announcements: List[ Announcement] = Offer.calculate_announcements( chia_requested_payments) chia_secured_bundle: SpendBundle = self.generate_secure_bundle( chia_coins, chia_announcements, 1000) chia_offer = Offer(chia_requested_payments, chia_secured_bundle) assert not chia_offer.is_valid() # Create a RED Offer for XCH red_requested_payments: Dict[Optional[bytes32], List[Payment]] = { None: [ Payment(acs_ph, 300, [b"red memo"]), Payment(acs_ph, 400, [b"red memo"]), ] } red_requested_payments: Dict[ Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( red_requested_payments, red_coins) red_announcements: List[ Announcement] = Offer.calculate_announcements( red_requested_payments) red_secured_bundle: SpendBundle = self.generate_secure_bundle( red_coins, red_announcements, 350, tail_str="red") red_offer = Offer(red_requested_payments, red_secured_bundle) assert not red_offer.is_valid() # Test aggregation of offers new_offer = Offer.aggregate([chia_offer, red_offer]) assert new_offer.get_offered_amounts() == { None: 1000, str_to_tail_hash("red"): 350 } assert new_offer.get_requested_amounts() == { None: 700, str_to_tail_hash("red"): 300 } assert new_offer.is_valid() # Create yet another offer of BLUE for XCH and RED blue_requested_payments: Dict[Optional[bytes32], List[Payment]] = { None: [ Payment(acs_ph, 200, [b"blue memo"]), ], str_to_tail_hash("red"): [ Payment(acs_ph, 50, [b"blue memo"]), ], } blue_requested_payments: Dict[ Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments( blue_requested_payments, blue_coins) blue_announcements: List[ Announcement] = Offer.calculate_announcements( blue_requested_payments) blue_secured_bundle: SpendBundle = self.generate_secure_bundle( blue_coins, blue_announcements, 2000, tail_str="blue") blue_offer = Offer(blue_requested_payments, blue_secured_bundle) assert not blue_offer.is_valid() # Test a re-aggregation new_offer: Offer = Offer.aggregate([new_offer, blue_offer]) assert new_offer.get_offered_amounts() == { None: 1000, str_to_tail_hash("red"): 350, str_to_tail_hash("blue"): 2000, } assert new_offer.get_requested_amounts() == { None: 900, str_to_tail_hash("red"): 350 } assert new_offer.summary() == ( { "xch": 1000, str_to_tail_hash("red").hex(): 350, str_to_tail_hash("blue").hex(): 2000, }, { "xch": 900, str_to_tail_hash("red").hex(): 350 }, ) assert new_offer.get_pending_amounts() == { "xch": 1200, str_to_tail_hash("red").hex(): 350, str_to_tail_hash("blue").hex(): 3000, } assert new_offer.is_valid() # Test (de)serialization assert Offer.from_bytes(bytes(new_offer)) == new_offer # Test compression assert Offer.from_compressed(new_offer.compress()) == new_offer # Make sure we can actually spend the offer once it's valid arbitrage_ph: bytes32 = Program.to([3, [], [], 1]).get_tree_hash() offer_bundle: SpendBundle = new_offer.to_valid_spend(arbitrage_ph) result = await sim_client.push_tx(offer_bundle) assert result == (MempoolInclusionStatus.SUCCESS, None) self.cost["complex offer"] = cost_of_spend_bundle(offer_bundle) await sim.farm_block() finally: await sim.close()