async def get_all_transactions(self, wallet_id: int, type: int = None ) -> List[TransactionRecord]: """ Returns all stored transactions. """ if type is None: cursor = await self.db_connection.execute( "SELECT * from transaction_record where wallet_id=?", (wallet_id, )) else: cursor = await self.db_connection.execute( "SELECT * from transaction_record where wallet_id=? and type=?", ( wallet_id, type, ), ) rows = await cursor.fetchall() await cursor.close() records = [] cache_set = set() for row in rows: record = TransactionRecord.from_bytes(row[0]) records.append(record) cache_set.add(record.name) if wallet_id not in self.tx_wallet_cache: self.tx_wallet_cache[wallet_id] = {} self.tx_wallet_cache[wallet_id][type] = cache_set return records
async def get_transactions( self, wallet_id: str, start: int = None, end: int = None, sort_key: SortKey = None, reverse: bool = False, ) -> List[TransactionRecord]: request: Dict[str, Any] = {"wallet_id": wallet_id} if start is not None: request["start"] = start if end is not None: request["end"] = end if sort_key is not None: request["sort_key"] = sort_key.name request["reverse"] = reverse res = await self.fetch( "get_transactions", request, ) reverted_tx: List[TransactionRecord] = [] for modified_tx in res["transactions"]: # Server returns address instead of ph, but TransactionRecord requires ph modified_tx["to_puzzle_hash"] = decode_puzzle_hash( modified_tx["to_address"]).hex() del modified_tx["to_address"] reverted_tx.append(TransactionRecord.from_json_dict(modified_tx)) return reverted_tx
async def get_not_sent(self) -> List[TransactionRecord]: """ Returns the list of transaction that have not been received by full node yet. """ current_time = int(time.time()) cursor = await self.db_connection.execute( "SELECT * from transaction_record WHERE confirmed=?", (0, ), ) rows = await cursor.fetchall() await cursor.close() records = [] for row in rows: record = TransactionRecord.from_bytes(row[0]) if record.name in self.tx_submitted: time_submitted, count = self.tx_submitted[record.name] if time_submitted < current_time - (60 * 10): records.append(record) self.tx_submitted[record.name] = current_time, 1 else: if count < 5: records.append(record) self.tx_submitted[record.name] = time_submitted, ( count + 1) else: records.append(record) self.tx_submitted[record.name] = current_time, 1 return records
async def aggregate_this_coin(self, coin: Coin): spend_bundle = await self.rl_generate_signed_aggregation_transaction( self.rl_info, coin, await self._get_rl_parent(), await self._get_rl_coin()) rl_coin = await self._get_rl_coin() puzzle_hash = rl_coin.puzzle_hash if rl_coin is not None else None # TODO: address hint error and remove ignore # error: Argument "to_puzzle_hash" to "TransactionRecord" has incompatible type "Optional[bytes32]"; # expected "bytes32" [arg-type] tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=puzzle_hash, # type: ignore[arg-type] amount=uint64(0), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), ) asyncio.create_task(self.push_transaction(tx_record))
async def get_unconfirmed_for_wallet( self, wallet_id: int) -> List[TransactionRecord]: """ Returns the list of transaction that have not yet been confirmed. """ if wallet_id in self.unconfirmed_for_wallet: return list(self.unconfirmed_for_wallet[wallet_id].values()) cursor = await self.db_connection.execute( "SELECT * from transaction_record WHERE confirmed=? and wallet_id=?", ( 0, wallet_id, ), ) rows = await cursor.fetchall() await cursor.close() records = [] dict = {} for row in rows: record = TransactionRecord.from_bytes(row[0]) records.append(record) dict[record.name] = record self.unconfirmed_for_wallet[wallet_id] = dict return records
async def aggregate_this_coin(self, coin: Coin): spend_bundle = await self.rl_generate_signed_aggregation_transaction( self.rl_info, coin, await self._get_rl_parent(), await self._get_rl_coin()) rl_coin = await self._get_rl_coin() puzzle_hash = rl_coin.puzzle_hash if rl_coin is not None else None tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=puzzle_hash, amount=uint64(0), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), ) asyncio.create_task(self.push_transaction(tx_record))
async def generate_signed_transaction( self, amount, to_puzzle_hash, fee: uint64 = uint64(0)) -> TransactionRecord: self.rl_coin_record = await self._get_rl_coin_record() if not self.rl_coin_record: raise ValueError("No unspent coin (zero balance)") if amount > self.rl_coin_record.coin.amount: raise ValueError( f"Coin value not sufficient: {amount} > {self.rl_coin_record.coin.amount}" ) transaction = await self.rl_generate_unsigned_transaction( to_puzzle_hash, amount, fee) spend_bundle = await self.rl_sign_transaction(transaction) return TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=to_puzzle_hash, amount=uint64(amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), )
async def tx_reorged(self, tx_id: bytes32): """ Updates transaction sent count to 0 and resets confirmation data """ current: Optional[ TransactionRecord] = await self.get_transaction_record(tx_id) if current is None: return tx: TransactionRecord = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=current.created_at_time, to_puzzle_hash=current.to_puzzle_hash, amount=current.amount, fee_amount=current.fee_amount, confirmed=False, sent=uint32(0), spend_bundle=current.spend_bundle, additions=current.additions, removals=current.removals, wallet_id=current.wallet_id, sent_to=[], trade_id=None, type=current.type, name=current.name, ) await self.add_transaction_record(tx)
async def pw_self_pool(self, wallet_id: str, fee: uint64) -> TransactionRecord: return TransactionRecord.from_json_dict( (await self.fetch("pw_self_pool", { "wallet_id": wallet_id, "fee": fee }))["transaction"])
async def pw_absorb_rewards( self, wallet_id: str, fee: uint64 = uint64(0)) -> TransactionRecord: return TransactionRecord.from_json_dict( (await self.fetch("pw_absorb_rewards", { "wallet_id": wallet_id, "fee": fee }))["transaction"])
async def create_new_pool_wallet( self, target_puzzlehash: Optional[bytes32], pool_url: Optional[str], relative_lock_height: uint32, backup_host: str, mode: str, state: str, p2_singleton_delay_time: Optional[uint64] = None, p2_singleton_delayed_ph: Optional[bytes32] = None, ) -> TransactionRecord: request: Dict[str, Any] = { "wallet_type": "pool_wallet", "mode": mode, "host": backup_host, "initial_target_state": { "target_puzzle_hash": target_puzzlehash.hex() if target_puzzlehash else None, "relative_lock_height": relative_lock_height, "pool_url": pool_url, "state": state, }, } if p2_singleton_delay_time is not None: request["p2_singleton_delay_time"] = p2_singleton_delay_time if p2_singleton_delayed_ph is not None: request["p2_singleton_delayed_ph"] = p2_singleton_delayed_ph.hex() res = await self.fetch("create_new_wallet", request) return TransactionRecord.from_json_dict(res["transaction"])
async def send_transaction_multi( self, wallet_id: str, additions: List[Dict], coins: List[Coin] = None, fee: uint64 = uint64(0)) -> TransactionRecord: # Converts bytes to hex for puzzle hashes additions_hex = [] for ad in additions: additions_hex.append({ "amount": ad["amount"], "puzzle_hash": ad["puzzle_hash"].hex() }) if "memos" in ad: additions_hex[-1]["memos"] = ad["memos"] if coins is not None and len(coins) > 0: coins_json = [c.to_json_dict() for c in coins] response: Dict = await self.fetch( "send_transaction_multi", { "wallet_id": wallet_id, "additions": additions_hex, "coins": coins_json, "fee": fee }, ) else: response = await self.fetch("send_transaction_multi", { "wallet_id": wallet_id, "additions": additions_hex, "fee": fee }) return TransactionRecord.from_json_dict_convenience( response["transaction"])
async def get_transactions( self, wallet_id: str, start: int = None, end: int = None, sort_key: SortKey = None, reverse: bool = False, ) -> List[TransactionRecord]: request: Dict[str, Any] = {"wallet_id": wallet_id} if start is not None: request["start"] = start if end is not None: request["end"] = end if sort_key is not None: request["sort_key"] = sort_key.name request["reverse"] = reverse res = await self.fetch( "get_transactions", request, ) return [ TransactionRecord.from_json_dict_convenience(tx) for tx in res["transactions"] ]
async def set_confirmed(self, tx_id: bytes32, height: uint32): """ Updates transaction to be confirmed. """ current: Optional[ TransactionRecord] = await self.get_transaction_record(tx_id) if current is None: return tx: TransactionRecord = TransactionRecord( confirmed_at_height=height, created_at_time=current.created_at_time, to_puzzle_hash=current.to_puzzle_hash, amount=current.amount, fee_amount=current.fee_amount, confirmed=True, sent=current.sent, spend_bundle=current.spend_bundle, additions=current.additions, removals=current.removals, wallet_id=current.wallet_id, sent_to=current.sent_to, trade_id=None, type=current.type, name=current.name, ) await self.add_transaction_record(tx)
async def create_spend(self, puzhash: bytes32): assert self.did_info.current_inner is not None coins = await self.select_coins(1) assert coins is not None coin = coins.pop() # innerpuz solution is (mode amount new_puz identity my_puz) innersol: Program = Program.to([0, coin.amount, puzhash, coin.name(), coin.puzzle_hash]) # full solution is (corehash parent_info my_amount innerpuz_reveal solution) innerpuz: Program = self.did_info.current_inner full_puzzle: Program = did_wallet_puzzles.create_fullpuz( innerpuz, self.did_info.my_did, ) parent_info = await self.get_parent_for_coin(coin) assert parent_info is not None fullsol = Program.to( [ [ parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ], coin.amount, innersol, ] ) list_of_solutions = [CoinSolution(coin, full_puzzle, fullsol)] # sign for AGG_SIG_ME message = bytes(puzhash) + bytes(coin.name()) pubkey = did_wallet_puzzles.get_pubkey_from_innerpuz(innerpuz) index = await self.wallet_state_manager.puzzle_store.index_for_pubkey(pubkey) private = master_sk_to_wallet_sk(self.wallet_state_manager.private_key, index) signature = AugSchemeMPL.sign(private, message) # assert signature.validate([signature.PkMessagePair(pubkey, message)]) sigs = [signature] aggsig = AugSchemeMPL.aggregate(sigs) spend_bundle = SpendBundle(list_of_solutions, aggsig) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=puzhash, amount=uint64(coin.amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.wallet_info.id, sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=token_bytes(), ) await self.standard_wallet.push_transaction(did_record) return spend_bundle
async def generate_signed_transaction( self, amount: uint64, puzzle_hash: bytes32, fee: uint64 = uint64(0), origin_id: bytes32 = None, coins: Set[Coin] = None, primaries: Optional[List[AmountWithPuzzlehash]] = None, ignore_max_send_amount: bool = False, announcements_to_consume: Set[bytes32] = None, ) -> TransactionRecord: """ Use this to generate transaction. Note: this must be called under a wallet state manager lock """ if primaries is None: non_change_amount = amount else: non_change_amount = uint64(amount + sum(p["amount"] for p in primaries)) # TODO: address hint error and remove ignore # error: Argument 8 to "_generate_unsigned_transaction" of "Wallet" has incompatible type # "Optional[Set[bytes32]]"; expected "Optional[Set[Announcement]]" [arg-type] transaction = await self._generate_unsigned_transaction( amount, puzzle_hash, fee, origin_id, coins, primaries, ignore_max_send_amount, announcements_to_consume # type: ignore[arg-type] # noqa: E501 ) assert len(transaction) > 0 self.log.info("About to sign a transaction") await self.hack_populate_secret_keys_for_coin_spends(transaction) spend_bundle: SpendBundle = await sign_coin_spends( transaction, self.secret_key_store.secret_key_for_public_key, self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA, self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, ) now = uint64(int(time.time())) add_list: List[Coin] = list(spend_bundle.additions()) rem_list: List[Coin] = list(spend_bundle.removals()) assert sum(a.amount for a in add_list) + fee == sum(r.amount for r in rem_list) return TransactionRecord( confirmed_at_height=uint32(0), created_at_time=now, to_puzzle_hash=puzzle_hash, amount=uint64(non_change_amount), fee_amount=uint64(fee), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=add_list, removals=rem_list, wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), )
async def send_transaction( self, wallet_id: str, amount: uint64, address: str, fee: uint64 = uint64(0) ) -> TransactionRecord: res = await self.fetch( "send_transaction", {"wallet_id": wallet_id, "amount": amount, "address": address, "fee": fee}, ) return TransactionRecord.from_json_dict(res["transaction"])
async def pw_join_pool( self, wallet_id: str, target_puzzlehash: bytes32, pool_url: str, relative_lock_height: uint32 ) -> TransactionRecord: request = { "wallet_id": int(wallet_id), "target_puzzlehash": target_puzzlehash.hex(), "relative_lock_height": relative_lock_height, "pool_url": pool_url, } return TransactionRecord.from_json_dict((await self.fetch("pw_join_pool", request))["transaction"])
async def get_transaction(self, wallet_id: str, transaction_id: bytes32) -> TransactionRecord: res = await self.fetch( "get_transaction", { "walled_id": wallet_id, "transaction_id": transaction_id.hex() }, ) return TransactionRecord.from_json_dict_convenience(res["transaction"])
async def is_transaction_confirmed(user_wallet_id, api, tx_id: bytes32) -> bool: try: val = await api.get_transaction({ "wallet_id": user_wallet_id, "transaction_id": tx_id.hex() }) except ValueError: return False return TransactionRecord.from_json_dict_convenience( val["transaction"]).confirmed
async def generate_signed_transaction( self, amount: uint64, puzzle_hash: bytes32, fee: uint64 = uint64(0), origin_id: bytes32 = None, coins: Set[Coin] = None, primaries: Optional[List[Dict[str, bytes32]]] = None, ignore_max_send_amount: bool = False, ) -> TransactionRecord: """ Use this to generate transaction. """ if primaries is None: non_change_amount = amount else: non_change_amount = uint64(amount + sum(p["amount"] for p in primaries)) transaction = await self.generate_unsigned_transaction( amount, puzzle_hash, fee, origin_id, coins, primaries, ignore_max_send_amount) assert len(transaction) > 0 self.log.info("About to sign a transaction") await self.hack_populate_secret_keys_for_coin_solutions(transaction) spend_bundle: SpendBundle = await sign_coin_solutions( transaction, self.secret_key_store.secret_key_for_public_key, self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA, self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, ) now = uint64(int(time.time())) add_list: List[Coin] = list(spend_bundle.additions()) rem_list: List[Coin] = list(spend_bundle.removals()) assert sum(a.amount for a in add_list) + fee == sum(r.amount for r in rem_list) return TransactionRecord( confirmed_at_height=uint32(0), created_at_time=now, to_puzzle_hash=puzzle_hash, amount=uint64(non_change_amount), fee_amount=uint64(fee), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=add_list, removals=rem_list, wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), )
async def pw_status( self, wallet_id: str) -> Tuple[PoolWalletInfo, List[TransactionRecord]]: json_dict = await self.fetch("pw_status", {"wallet_id": wallet_id}) return ( PoolWalletInfo.from_json_dict(json_dict["state"]), [ TransactionRecord.from_json_dict(tr) for tr in json_dict["unconfirmed_transactions"] ], )
async def increment_sent( self, tx_id: bytes32, name: str, send_status: MempoolInclusionStatus, err: Optional[Err], ) -> bool: """ Updates transaction sent count (Full Node has received spend_bundle and sent ack). """ current: Optional[ TransactionRecord] = await self.get_transaction_record(tx_id) if current is None: return False sent_to = current.sent_to.copy() current_peers = set() err_str = err.name if err is not None else None append_data = (name, uint8(send_status.value), err_str) for peer_id, status, error in sent_to: current_peers.add(peer_id) if name in current_peers: sent_count = uint32(current.sent) else: sent_count = uint32(current.sent + 1) sent_to.append(append_data) tx: TransactionRecord = TransactionRecord( confirmed_at_height=current.confirmed_at_height, created_at_time=current.created_at_time, to_puzzle_hash=current.to_puzzle_hash, amount=current.amount, fee_amount=current.fee_amount, confirmed=current.confirmed, sent=sent_count, spend_bundle=current.spend_bundle, additions=current.additions, removals=current.removals, wallet_id=current.wallet_id, sent_to=sent_to, trade_id=current.trade_id, type=current.type, name=current.name, memos=current.memos, ) await self.add_transaction_record(tx, False) return True
async def create_signed_transaction( self, additions: List[Dict], coins: List[Coin] = None, fee: uint64 = uint64(0), coin_announcements: Optional[List[Announcement]] = None, puzzle_announcements: Optional[List[Announcement]] = None, ) -> TransactionRecord: # Converts bytes to hex for puzzle hashes additions_hex = [] for ad in additions: additions_hex.append({ "amount": ad["amount"], "puzzle_hash": ad["puzzle_hash"].hex() }) if "memos" in ad: additions_hex[-1]["memos"] = ad["memos"] request: Dict[str, Any] = { "additions": additions_hex, "fee": fee, } if coin_announcements is not None and len(coin_announcements) > 0: request["coin_announcements"] = [{ "coin_id": ann.origin_info.hex(), "message": ann.message.hex(), "morph_bytes": ann.morph_bytes.hex() if ann.morph_bytes is not None else b"".hex(), } for ann in coin_announcements] if puzzle_announcements is not None and len(puzzle_announcements) > 0: request["puzzle_announcements"] = [{ "puzzle_hash": ann.origin_info.hex(), "message": ann.message.hex(), "morph_bytes": ann.morph_bytes.hex() if ann.morph_bytes is not None else b"".hex(), } for ann in puzzle_announcements] if coins is not None and len(coins) > 0: coins_json = [c.to_json_dict() for c in coins] request["coins"] = coins_json response: Dict = await self.fetch("create_signed_transaction", request) return TransactionRecord.from_json_dict_convenience( response["signed_tx"])
async def create_signed_transaction( self, additions: List[Dict], coins: List[Coin] = None, fee: uint64 = uint64(0) ) -> TransactionRecord: # Converts bytes to hex for puzzle hashes additions_hex = [{"amount": ad["amount"], "puzzle_hash": ad["puzzle_hash"].hex()} for ad in additions] if coins is not None and len(coins) > 0: coins_json = [c.to_json_dict() for c in coins] response: Dict = await self.fetch( "create_signed_transaction", {"additions": additions_hex, "coins": coins_json, "fee": fee} ) else: response = await self.fetch("create_signed_transaction", {"additions": additions_hex, "fee": fee}) return TransactionRecord.from_json_dict(response["signed_tx"])
async def get_transactions_by_trade_id( self, trade_id: bytes32) -> List[TransactionRecord]: cursor = await self.db_connection.execute( "SELECT * from transaction_record WHERE trade_id=?", (trade_id, )) rows = await cursor.fetchall() await cursor.close() records = [] for row in rows: record = TransactionRecord.from_bytes(row[0]) records.append(record) return records
async def get_all_transactions(self) -> List[TransactionRecord]: """ Returns all stored transactions. """ cursor = await self.db_connection.execute( "SELECT * from transaction_record") rows = await cursor.fetchall() await cursor.close() records = [] for row in rows: record = TransactionRecord.from_bytes(row[0]) records.append(record) return records
async def get_transaction_above(self, height: int) -> List[TransactionRecord]: # Can be -1 (get all tx) cursor = await self.db_connection.execute( "SELECT * from transaction_record WHERE confirmed_at_height>?", (height,) ) rows = await cursor.fetchall() await cursor.close() records = [] for row in rows: record = TransactionRecord.from_bytes(row[0]) records.append(record) return records
async def is_transaction_in_mempool(user_wallet_id, api, tx_id: bytes32) -> bool: try: val = await api.get_transaction({ "wallet_id": user_wallet_id, "transaction_id": tx_id.hex() }) except ValueError: return False for _, mis, _ in TransactionRecord.from_json_dict_convenience( val["transaction"]).sent_to: if (MempoolInclusionStatus(mis) == MempoolInclusionStatus.SUCCESS or MempoolInclusionStatus(mis) == MempoolInclusionStatus.PENDING): return True return False
async def get_transactions( self, wallet_id: str, ) -> List[TransactionRecord]: res = await self.fetch( "get_transactions", {"wallet_id": wallet_id}, ) reverted_tx: List[TransactionRecord] = [] for modified_tx in res["transactions"]: # Server returns address instead of ph, but TransactionRecord requires ph modified_tx["to_puzzle_hash"] = decode_puzzle_hash(modified_tx["to_address"]).hex() del modified_tx["to_address"] reverted_tx.append(TransactionRecord.from_json_dict(modified_tx)) return reverted_tx