2020-04-28 04:42:08 +03:00
|
|
|
import time
|
2020-09-17 00:25:10 +03:00
|
|
|
from typing import Dict, Optional, List, Set, Any
|
2020-02-12 00:00:41 +03:00
|
|
|
import logging
|
2020-09-17 01:53:22 +03:00
|
|
|
from blspy import G1Element, AugSchemeMPL, PrivateKey
|
2020-03-30 20:27:22 +03:00
|
|
|
from src.types.coin import Coin
|
|
|
|
from src.types.coin_solution import CoinSolution
|
|
|
|
from src.types.program import Program
|
|
|
|
from src.types.spend_bundle import SpendBundle
|
2020-02-12 04:01:39 +03:00
|
|
|
from src.types.sized_bytes import bytes32
|
2020-02-13 22:57:40 +03:00
|
|
|
from src.util.condition_tools import (
|
2020-04-21 16:55:59 +03:00
|
|
|
conditions_dict_for_solution,
|
2020-07-13 19:40:32 +03:00
|
|
|
pkm_pairs_for_conditions_dict,
|
2020-02-13 22:57:40 +03:00
|
|
|
)
|
2020-04-28 04:42:08 +03:00
|
|
|
from src.util.ints import uint64, uint32
|
2020-06-04 05:26:27 +03:00
|
|
|
from src.wallet.abstract_wallet import AbstractWallet
|
2020-02-12 04:01:39 +03:00
|
|
|
from src.wallet.puzzles.p2_conditions import puzzle_for_conditions
|
|
|
|
from src.wallet.puzzles.p2_delegated_puzzle import puzzle_for_pk
|
2020-02-13 22:57:40 +03:00
|
|
|
from src.wallet.puzzles.puzzle_utils import (
|
|
|
|
make_assert_my_coin_id_condition,
|
|
|
|
make_assert_time_exceeds_condition,
|
|
|
|
make_assert_coin_consumed_condition,
|
|
|
|
make_create_coin_condition,
|
2020-04-20 08:10:19 +03:00
|
|
|
make_assert_fee_condition,
|
|
|
|
)
|
2020-04-28 04:42:08 +03:00
|
|
|
from src.wallet.transaction_record import TransactionRecord
|
2020-03-19 11:26:51 +03:00
|
|
|
from src.wallet.wallet_coin_record import WalletCoinRecord
|
|
|
|
from src.wallet.wallet_info import WalletInfo
|
2020-02-24 20:16:47 +03:00
|
|
|
|
2020-02-12 00:00:41 +03:00
|
|
|
|
2020-06-04 05:26:27 +03:00
|
|
|
class Wallet(AbstractWallet):
|
2020-04-07 12:17:44 +03:00
|
|
|
wallet_state_manager: Any
|
2020-02-13 22:57:40 +03:00
|
|
|
log: logging.Logger
|
2020-03-19 11:26:51 +03:00
|
|
|
wallet_info: WalletInfo
|
2020-09-17 00:25:10 +03:00
|
|
|
_pk2sk: Dict[G1Element, PrivateKey]
|
2020-02-13 23:59:03 +03:00
|
|
|
|
2020-02-13 22:57:40 +03:00
|
|
|
@staticmethod
|
2020-02-24 21:41:54 +03:00
|
|
|
async def create(
|
2020-09-15 19:56:04 +03:00
|
|
|
wallet_state_manager: Any,
|
|
|
|
info: WalletInfo,
|
|
|
|
name: str = None,
|
2020-02-24 21:41:54 +03:00
|
|
|
):
|
2020-02-13 22:57:40 +03:00
|
|
|
self = Wallet()
|
2020-02-12 00:00:41 +03:00
|
|
|
if name:
|
|
|
|
self.log = logging.getLogger(name)
|
|
|
|
else:
|
|
|
|
self.log = logging.getLogger(__name__)
|
2020-02-24 20:16:47 +03:00
|
|
|
self.wallet_state_manager = wallet_state_manager
|
2020-03-19 11:26:51 +03:00
|
|
|
self.wallet_info = info
|
2020-09-17 00:25:10 +03:00
|
|
|
|
|
|
|
# HACK
|
|
|
|
self._pk2sk = {}
|
|
|
|
|
2020-02-13 22:57:40 +03:00
|
|
|
return self
|
2020-02-12 04:01:39 +03:00
|
|
|
|
2020-02-13 23:59:03 +03:00
|
|
|
async def get_confirmed_balance(self) -> uint64:
|
2020-03-19 22:11:58 +03:00
|
|
|
return await self.wallet_state_manager.get_confirmed_balance_for_wallet(
|
|
|
|
self.wallet_info.id
|
|
|
|
)
|
2020-02-13 22:57:40 +03:00
|
|
|
|
2020-02-13 23:59:03 +03:00
|
|
|
async def get_unconfirmed_balance(self) -> uint64:
|
2020-03-19 22:11:58 +03:00
|
|
|
return await self.wallet_state_manager.get_unconfirmed_balance(
|
|
|
|
self.wallet_info.id
|
|
|
|
)
|
2020-02-13 23:59:03 +03:00
|
|
|
|
2020-05-20 10:41:10 +03:00
|
|
|
async def get_frozen_amount(self) -> uint64:
|
2020-05-29 05:41:33 +03:00
|
|
|
return await self.wallet_state_manager.get_frozen_balance(self.wallet_info.id)
|
2020-05-20 10:41:10 +03:00
|
|
|
|
|
|
|
async def get_spendable_balance(self) -> uint64:
|
2020-09-15 19:56:04 +03:00
|
|
|
spendable_am = (
|
|
|
|
await self.wallet_state_manager.get_confirmed_spendable_balance_for_wallet(
|
|
|
|
self.wallet_info.id
|
|
|
|
)
|
2020-05-20 10:41:10 +03:00
|
|
|
)
|
|
|
|
return spendable_am
|
|
|
|
|
|
|
|
async def get_pending_change_balance(self) -> uint64:
|
2020-09-15 19:56:04 +03:00
|
|
|
unconfirmed_tx = (
|
|
|
|
await self.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
|
|
|
|
self.wallet_info.id
|
|
|
|
)
|
2020-05-20 10:41:10 +03:00
|
|
|
)
|
|
|
|
addition_amount = 0
|
|
|
|
|
|
|
|
for record in unconfirmed_tx:
|
|
|
|
our_spend = False
|
|
|
|
for coin in record.removals:
|
|
|
|
if await self.wallet_state_manager.does_coin_belong_to_wallet(
|
|
|
|
coin, self.wallet_info.id
|
|
|
|
):
|
|
|
|
our_spend = True
|
|
|
|
break
|
|
|
|
|
|
|
|
if our_spend is not True:
|
|
|
|
continue
|
|
|
|
|
|
|
|
for coin in record.additions:
|
|
|
|
if await self.wallet_state_manager.does_coin_belong_to_wallet(
|
|
|
|
coin, self.wallet_info.id
|
|
|
|
):
|
|
|
|
addition_amount += coin.amount
|
|
|
|
|
|
|
|
return uint64(addition_amount)
|
|
|
|
|
2020-03-27 17:30:07 +03:00
|
|
|
def puzzle_for_pk(self, pubkey: bytes) -> Program:
|
2020-02-12 04:01:39 +03:00
|
|
|
return puzzle_for_pk(pubkey)
|
|
|
|
|
2020-09-17 01:53:22 +03:00
|
|
|
async def hack_populate_secret_key_for_puzzle_hash(
|
|
|
|
self, puzzle_hash: bytes32
|
|
|
|
) -> G1Element:
|
2020-09-17 01:49:18 +03:00
|
|
|
maybe = await self.wallet_state_manager.get_keys(puzzle_hash)
|
|
|
|
if maybe is None:
|
|
|
|
error_msg = f"Wallet couldn't find keys for puzzle_hash {puzzle_hash}"
|
|
|
|
self.log.error(error_msg)
|
|
|
|
raise ValueError(error_msg)
|
|
|
|
|
|
|
|
# Get puzzle for pubkey
|
|
|
|
public_key, secret_key = maybe
|
|
|
|
|
|
|
|
# HACK
|
|
|
|
self._pk2sk[bytes(public_key)] = secret_key
|
2020-09-17 01:53:22 +03:00
|
|
|
return public_key
|
2020-09-17 01:49:18 +03:00
|
|
|
|
2020-09-17 01:53:22 +03:00
|
|
|
async def puzzle_for_puzzle_hash(self, puzzle_hash: bytes32) -> Program:
|
|
|
|
public_key = await self.hack_populate_secret_key_for_puzzle_hash(puzzle_hash)
|
2020-09-17 01:49:18 +03:00
|
|
|
return puzzle_for_pk(bytes(public_key))
|
|
|
|
|
2020-04-10 17:53:31 +03:00
|
|
|
async def get_new_puzzle(self) -> Program:
|
2020-08-13 03:08:05 +03:00
|
|
|
dr = await self.wallet_state_manager.get_unused_derivation_record(
|
|
|
|
self.wallet_info.id
|
2020-04-10 17:53:31 +03:00
|
|
|
)
|
2020-08-13 03:08:05 +03:00
|
|
|
return puzzle_for_pk(bytes(dr.pubkey))
|
2020-04-10 17:53:31 +03:00
|
|
|
|
2020-02-27 23:37:32 +03:00
|
|
|
async def get_new_puzzlehash(self) -> bytes32:
|
2020-04-07 12:17:44 +03:00
|
|
|
return (
|
|
|
|
await self.wallet_state_manager.get_unused_derivation_record(
|
|
|
|
self.wallet_info.id
|
|
|
|
)
|
|
|
|
).puzzle_hash
|
2020-02-12 04:01:39 +03:00
|
|
|
|
2020-09-17 01:53:22 +03:00
|
|
|
def make_solution(self, primaries=None, min_time=0, me=None, consumed=None, fee=0):
|
2020-09-16 03:09:17 +03:00
|
|
|
assert fee >= 0
|
2020-03-06 02:53:36 +03:00
|
|
|
condition_list = []
|
2020-02-13 22:57:40 +03:00
|
|
|
if primaries:
|
|
|
|
for primary in primaries:
|
2020-03-06 02:53:36 +03:00
|
|
|
condition_list.append(
|
2020-02-13 22:57:40 +03:00
|
|
|
make_create_coin_condition(primary["puzzlehash"], primary["amount"])
|
|
|
|
)
|
|
|
|
if consumed:
|
|
|
|
for coin in consumed:
|
2020-03-06 02:53:36 +03:00
|
|
|
condition_list.append(make_assert_coin_consumed_condition(coin))
|
2020-02-12 04:01:39 +03:00
|
|
|
if min_time > 0:
|
2020-03-06 02:53:36 +03:00
|
|
|
condition_list.append(make_assert_time_exceeds_condition(min_time))
|
2020-02-12 04:01:39 +03:00
|
|
|
if me:
|
2020-03-06 02:53:36 +03:00
|
|
|
condition_list.append(make_assert_my_coin_id_condition(me["id"]))
|
2020-04-20 08:08:16 +03:00
|
|
|
if fee:
|
|
|
|
condition_list.append(make_assert_fee_condition(fee))
|
2020-07-09 00:57:41 +03:00
|
|
|
return Program.to([puzzle_for_conditions(condition_list), []])
|
2020-02-12 04:01:39 +03:00
|
|
|
|
2020-09-14 12:29:03 +03:00
|
|
|
async def select_coins(self, amount, exclude: List[Coin] = None) -> Set[Coin]:
|
2020-03-19 11:26:51 +03:00
|
|
|
""" Returns a set of coins that can be used for generating a new transaction. """
|
2020-04-08 09:29:34 +03:00
|
|
|
async with self.wallet_state_manager.lock:
|
2020-04-22 08:59:48 +03:00
|
|
|
if exclude is None:
|
|
|
|
exclude = []
|
|
|
|
|
2020-06-11 07:30:33 +03:00
|
|
|
spendable_amount = await self.get_spendable_balance()
|
2020-03-19 11:26:51 +03:00
|
|
|
|
2020-06-11 07:30:33 +03:00
|
|
|
if amount > spendable_amount:
|
2020-09-14 12:29:03 +03:00
|
|
|
error_msg = (
|
2020-09-14 15:06:41 +03:00
|
|
|
f"Can't select amount higher than our spendable balance. Amount: {amount}, spendable: "
|
2020-09-14 12:29:03 +03:00
|
|
|
f" {spendable_amount}"
|
2020-04-08 09:29:34 +03:00
|
|
|
)
|
2020-09-14 12:29:03 +03:00
|
|
|
self.log.warning(error_msg)
|
|
|
|
raise ValueError(error_msg)
|
2020-03-19 11:26:51 +03:00
|
|
|
|
2020-04-08 09:29:34 +03:00
|
|
|
self.log.info(f"About to select coins for amount {amount}")
|
|
|
|
unspent: List[WalletCoinRecord] = list(
|
|
|
|
await self.wallet_state_manager.get_spendable_coins_for_wallet(
|
|
|
|
self.wallet_info.id
|
|
|
|
)
|
2020-04-02 20:33:38 +03:00
|
|
|
)
|
2020-06-17 02:46:51 +03:00
|
|
|
sum_value = 0
|
2020-04-08 09:29:34 +03:00
|
|
|
used_coins: Set = set()
|
|
|
|
|
|
|
|
# Use older coins first
|
|
|
|
unspent.sort(key=lambda r: r.confirmed_block_index)
|
|
|
|
|
|
|
|
# Try to use coins from the store, if there isn't enough of "unused"
|
|
|
|
# coins use change coins that are not confirmed yet
|
|
|
|
unconfirmed_removals: Dict[
|
|
|
|
bytes32, Coin
|
|
|
|
] = await self.wallet_state_manager.unconfirmed_removals_for_wallet(
|
2020-03-28 00:03:48 +03:00
|
|
|
self.wallet_info.id
|
|
|
|
)
|
2020-04-08 09:29:34 +03:00
|
|
|
for coinrecord in unspent:
|
2020-06-17 02:46:51 +03:00
|
|
|
if sum_value >= amount and len(used_coins) > 0:
|
2020-03-19 11:26:51 +03:00
|
|
|
break
|
2020-04-08 09:29:34 +03:00
|
|
|
if coinrecord.coin.name() in unconfirmed_removals:
|
2020-03-19 11:26:51 +03:00
|
|
|
continue
|
2020-04-22 08:59:48 +03:00
|
|
|
if coinrecord.coin in exclude:
|
|
|
|
continue
|
2020-06-17 02:46:51 +03:00
|
|
|
sum_value += coinrecord.coin.amount
|
2020-04-08 09:29:34 +03:00
|
|
|
used_coins.add(coinrecord.coin)
|
|
|
|
self.log.info(
|
|
|
|
f"Selected coin: {coinrecord.coin.name()} at height {coinrecord.confirmed_block_index}!"
|
|
|
|
)
|
2020-03-28 00:03:48 +03:00
|
|
|
|
2020-04-08 09:29:34 +03:00
|
|
|
# This happens when we couldn't use one of the coins because it's already used
|
|
|
|
# but unconfirmed, and we are waiting for the change. (unconfirmed_additions)
|
2020-06-17 02:46:51 +03:00
|
|
|
if sum_value < amount:
|
2020-04-08 09:29:34 +03:00
|
|
|
raise ValueError(
|
|
|
|
"Can't make this transaction at the moment. Waiting for the change from the previous transaction."
|
|
|
|
)
|
2020-06-17 02:46:51 +03:00
|
|
|
# TODO(straya): remove this
|
|
|
|
# unconfirmed_additions = await self.wallet_state_manager.unconfirmed_additions_for_wallet(
|
|
|
|
# self.wallet_info.id
|
|
|
|
# )
|
|
|
|
# for coin in unconfirmed_additions.values():
|
|
|
|
# if sum_value > amount:
|
|
|
|
# break
|
|
|
|
# if coin.name() in unconfirmed_removals:
|
|
|
|
# continue
|
|
|
|
|
|
|
|
# sum_value += coin.amount
|
|
|
|
# used_coins.add(coin)
|
|
|
|
# self.log.info(f"Selected used coin: {coin.name()}")
|
|
|
|
|
|
|
|
self.log.info(f"Successfully selected coins: {used_coins}")
|
|
|
|
return used_coins
|
2020-03-19 11:26:51 +03:00
|
|
|
|
2020-02-13 22:57:40 +03:00
|
|
|
async def generate_unsigned_transaction(
|
2020-03-23 22:59:53 +03:00
|
|
|
self,
|
2020-03-27 17:30:07 +03:00
|
|
|
amount: uint64,
|
2020-03-23 22:59:53 +03:00
|
|
|
newpuzzlehash: bytes32,
|
2020-03-27 17:30:07 +03:00
|
|
|
fee: uint64 = uint64(0),
|
2020-03-23 22:59:53 +03:00
|
|
|
origin_id: bytes32 = None,
|
|
|
|
coins: Set[Coin] = None,
|
2020-09-17 00:25:10 +03:00
|
|
|
) -> List[CoinSolution]:
|
2020-03-06 02:53:36 +03:00
|
|
|
"""
|
|
|
|
Generates a unsigned transaction in form of List(Puzzle, Solutions)
|
|
|
|
"""
|
2020-03-23 22:46:17 +03:00
|
|
|
if coins is None:
|
|
|
|
coins = await self.select_coins(amount + fee)
|
2020-09-14 12:29:03 +03:00
|
|
|
assert len(coins) > 0
|
2020-03-06 02:53:36 +03:00
|
|
|
|
2020-04-01 04:59:14 +03:00
|
|
|
self.log.info(f"coins is not None {coins}")
|
2020-03-23 22:46:17 +03:00
|
|
|
spend_value = sum([coin.amount for coin in coins])
|
2020-02-12 04:01:39 +03:00
|
|
|
change = spend_value - amount - fee
|
2020-03-06 02:53:36 +03:00
|
|
|
|
2020-09-17 00:25:10 +03:00
|
|
|
spends: List[CoinSolution] = []
|
2020-03-06 02:53:36 +03:00
|
|
|
output_created = False
|
|
|
|
|
2020-03-23 22:46:17 +03:00
|
|
|
for coin in coins:
|
2020-04-01 04:59:14 +03:00
|
|
|
self.log.info(f"coin from coins {coin}")
|
2020-09-17 01:49:18 +03:00
|
|
|
puzzle: Program = await self.puzzle_for_puzzle_hash(coin.puzzle_hash)
|
2020-03-06 02:53:36 +03:00
|
|
|
|
|
|
|
# Only one coin creates outputs
|
2020-09-17 00:25:10 +03:00
|
|
|
if not output_created and origin_id in (None, coin.name()):
|
2020-02-13 22:57:40 +03:00
|
|
|
primaries = [{"puzzlehash": newpuzzlehash, "amount": amount}]
|
2020-02-12 04:01:39 +03:00
|
|
|
if change > 0:
|
2020-02-27 23:37:32 +03:00
|
|
|
changepuzzlehash = await self.get_new_puzzlehash()
|
2020-02-13 22:57:40 +03:00
|
|
|
primaries.append({"puzzlehash": changepuzzlehash, "amount": change})
|
2020-02-24 20:16:47 +03:00
|
|
|
|
2020-09-16 03:09:17 +03:00
|
|
|
solution = self.make_solution(primaries=primaries, fee=fee)
|
2020-02-12 04:01:39 +03:00
|
|
|
output_created = True
|
|
|
|
else:
|
2020-04-20 08:08:16 +03:00
|
|
|
solution = self.make_solution()
|
2020-03-06 02:53:36 +03:00
|
|
|
|
2020-09-17 00:25:10 +03:00
|
|
|
puzzle_solution_pair = Program.to([puzzle, solution])
|
|
|
|
spends.append(CoinSolution(coin, puzzle_solution_pair))
|
2020-04-01 04:59:14 +03:00
|
|
|
|
|
|
|
self.log.info(f"Spends is {spends}")
|
2020-02-12 04:01:39 +03:00
|
|
|
return spends
|
|
|
|
|
2020-09-17 00:25:10 +03:00
|
|
|
def secret_key_for_public_key(self, public_key: G1Element) -> Optional[PrivateKey]:
|
|
|
|
return self._pk2sk.get(bytes(public_key))
|
|
|
|
|
2020-03-27 17:30:07 +03:00
|
|
|
async def sign_transaction(
|
2020-09-17 00:25:10 +03:00
|
|
|
self, coin_solutions: List[CoinSolution]
|
2020-09-14 12:29:03 +03:00
|
|
|
) -> SpendBundle:
|
2020-03-06 02:53:36 +03:00
|
|
|
signatures = []
|
2020-09-17 00:25:10 +03:00
|
|
|
|
|
|
|
for coin_solution in coin_solutions:
|
2020-09-17 01:53:22 +03:00
|
|
|
await self.hack_populate_secret_key_for_puzzle_hash(
|
|
|
|
coin_solution.coin.puzzle_hash
|
|
|
|
)
|
|
|
|
|
2020-03-06 02:53:36 +03:00
|
|
|
# Get AGGSIG conditions
|
2020-09-17 01:53:22 +03:00
|
|
|
err, conditions_dict, cost = conditions_dict_for_solution(
|
|
|
|
coin_solution.solution
|
|
|
|
)
|
|
|
|
if err or conditions_dict is None:
|
|
|
|
error_msg = f"Sign transaction failed, con:{conditions_dict}, error: {err}"
|
2020-09-14 12:29:03 +03:00
|
|
|
self.log.error(error_msg)
|
|
|
|
raise ValueError(error_msg)
|
2020-04-01 04:59:14 +03:00
|
|
|
|
2020-03-06 02:53:36 +03:00
|
|
|
# Create signature
|
2020-07-13 19:40:32 +03:00
|
|
|
for _, msg in pkm_pairs_for_conditions_dict(
|
2020-09-17 00:25:10 +03:00
|
|
|
conditions_dict, bytes(coin_solution.coin)
|
2020-03-26 00:36:32 +03:00
|
|
|
):
|
2020-09-17 00:25:10 +03:00
|
|
|
secret_key = self.secret_key_for_public_key(_)
|
|
|
|
if secret_key is None:
|
2020-09-17 01:53:22 +03:00
|
|
|
self.log.error(f"no secret key for {_}")
|
2020-09-17 00:25:10 +03:00
|
|
|
return None
|
2020-09-17 01:49:18 +03:00
|
|
|
signature = AugSchemeMPL.sign(secret_key, msg)
|
2020-03-06 02:53:36 +03:00
|
|
|
signatures.append(signature)
|
|
|
|
|
|
|
|
# Aggregate signatures
|
2020-07-13 19:40:32 +03:00
|
|
|
aggsig = AugSchemeMPL.aggregate(signatures)
|
2020-09-17 00:25:10 +03:00
|
|
|
return SpendBundle(coin_solutions, aggsig)
|
|
|
|
|
2020-02-13 22:57:40 +03:00
|
|
|
async def generate_signed_transaction(
|
2020-03-23 22:59:53 +03:00
|
|
|
self,
|
2020-03-27 17:30:07 +03:00
|
|
|
amount: uint64,
|
|
|
|
puzzle_hash: bytes32,
|
|
|
|
fee: uint64 = uint64(0),
|
2020-03-23 22:59:53 +03:00
|
|
|
origin_id: bytes32 = None,
|
|
|
|
coins: Set[Coin] = None,
|
2020-09-14 12:29:03 +03:00
|
|
|
) -> TransactionRecord:
|
2020-02-25 04:19:50 +03:00
|
|
|
""" Use this to generate transaction. """
|
2020-03-19 11:26:51 +03:00
|
|
|
|
2020-03-23 22:59:53 +03:00
|
|
|
transaction = await self.generate_unsigned_transaction(
|
|
|
|
amount, puzzle_hash, fee, origin_id, coins
|
|
|
|
)
|
2020-09-14 12:29:03 +03:00
|
|
|
assert len(transaction) > 0
|
2020-04-01 04:59:14 +03:00
|
|
|
|
2020-04-01 07:00:49 +03:00
|
|
|
self.log.info("About to sign a transaction")
|
2020-09-14 12:29:03 +03:00
|
|
|
spend_bundle: SpendBundle = await self.sign_transaction(transaction)
|
2020-02-13 22:57:40 +03:00
|
|
|
|
2020-04-28 04:42:08 +03:00
|
|
|
now = uint64(int(time.time()))
|
2020-09-17 00:25:10 +03:00
|
|
|
add_list: List[Coin] = list(spend_bundle.additions())
|
|
|
|
rem_list: List[Coin] = list(spend_bundle.removals())
|
2020-04-28 04:42:08 +03:00
|
|
|
|
2020-09-14 12:29:03 +03:00
|
|
|
return TransactionRecord(
|
2020-04-28 04:42:08 +03:00
|
|
|
confirmed_at_index=uint32(0),
|
|
|
|
created_at_time=now,
|
|
|
|
to_puzzle_hash=puzzle_hash,
|
|
|
|
amount=uint64(amount),
|
|
|
|
fee_amount=uint64(fee),
|
|
|
|
incoming=False,
|
|
|
|
confirmed=False,
|
|
|
|
sent=uint32(0),
|
|
|
|
spend_bundle=spend_bundle,
|
|
|
|
additions=add_list,
|
|
|
|
removals=rem_list,
|
|
|
|
wallet_id=self.wallet_info.id,
|
|
|
|
sent_to=[],
|
2020-06-12 02:24:10 +03:00
|
|
|
trade_id=None,
|
2020-03-19 22:11:58 +03:00
|
|
|
)
|
2020-04-21 16:55:59 +03:00
|
|
|
|
2020-04-28 04:42:08 +03:00
|
|
|
async def push_transaction(self, tx: TransactionRecord) -> None:
|
|
|
|
""" Use this API to send transactions. """
|
|
|
|
await self.wallet_state_manager.add_pending_transaction(tx)
|
|
|
|
|
2020-04-21 16:55:59 +03:00
|
|
|
# This is to be aggregated together with a coloured coin offer to ensure that the trade happens
|
2020-04-24 08:30:37 +03:00
|
|
|
async def create_spend_bundle_relative_chia(
|
|
|
|
self, chia_amount: int, exclude: List[Coin]
|
2020-09-14 12:29:03 +03:00
|
|
|
) -> SpendBundle:
|
2020-04-21 16:55:59 +03:00
|
|
|
list_of_solutions = []
|
|
|
|
utxos = None
|
|
|
|
|
|
|
|
# If we're losing value then get coins with at least that much value
|
|
|
|
# If we're gaining value then our amount doesn't matter
|
|
|
|
if chia_amount < 0:
|
2020-04-24 05:54:10 +03:00
|
|
|
utxos = await self.select_coins(abs(chia_amount), exclude)
|
2020-04-21 16:55:59 +03:00
|
|
|
else:
|
2020-04-24 05:54:10 +03:00
|
|
|
utxos = await self.select_coins(0, exclude)
|
2020-04-21 16:55:59 +03:00
|
|
|
|
2020-09-14 12:29:03 +03:00
|
|
|
assert len(utxos) > 0
|
2020-04-21 16:55:59 +03:00
|
|
|
|
|
|
|
# Calculate output amount given sum of utxos
|
|
|
|
spend_value = sum([coin.amount for coin in utxos])
|
|
|
|
chia_amount = spend_value + chia_amount
|
|
|
|
|
|
|
|
# Create coin solutions for each utxo
|
|
|
|
output_created = None
|
|
|
|
for coin in utxos:
|
2020-09-17 01:49:18 +03:00
|
|
|
puzzle = await self.puzzle_for_puzzle_hash(coin.puzzle_hash)
|
2020-04-21 16:55:59 +03:00
|
|
|
if output_created is None:
|
|
|
|
newpuzhash = await self.get_new_puzzlehash()
|
|
|
|
primaries = [{"puzzlehash": newpuzhash, "amount": chia_amount}]
|
|
|
|
solution = self.make_solution(primaries=primaries)
|
|
|
|
output_created = coin
|
|
|
|
else:
|
|
|
|
solution = self.make_solution(consumed=[output_created.name()])
|
2020-07-14 06:59:17 +03:00
|
|
|
list_of_solutions.append(CoinSolution(coin, Program.to([puzzle, solution])))
|
2020-04-21 16:55:59 +03:00
|
|
|
|
2020-09-17 01:53:22 +03:00
|
|
|
spend_bundle = await self.sign_transaction(list_of_solutions)
|
|
|
|
return spend_bundle
|