chia-blockchain/src/wallet/wallet.py

444 lines
17 KiB
Python
Raw Normal View History

2020-04-28 04:42:08 +03:00
import time
2020-09-14 12:29:03 +03:00
from typing import Dict, List, Tuple, Set, Any
2020-02-12 00:00:41 +03:00
import logging
from blspy import G2Element, AugSchemeMPL
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-08-07 22:13:22 +03:00
from src.util.chech32 import decode_puzzle_hash
2020-02-13 22:57:40 +03:00
from src.util.condition_tools import (
conditions_for_solution,
2020-04-21 16:55:59 +03:00
conditions_dict_for_solution,
2020-02-13 22:57:40 +03:00
conditions_by_opcode,
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):
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-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(
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-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
Electron react (#226) * clean react * add material ui * add word list * mnemonic v0 * jeepney backup * keychain usage * wallet api * mnemonic ui * mnemonics redux state * handle exceptions correctly * dashboard * wallets * get puzzle hash * tx history * sidebar * start stop wallet node * use existing mnemonics * status info * create cc wallet * theme should be outside of switch * create offer * dbus alternative for linux * key migration * don't autocomplete, don't reset simulator db * reset mnemonics * Refactor keychain, and key migration * Implement UI for changing keys * Removing keys and mnemonic button * Start making farmer and harvester RPCs * start rpx for simulator * Harvester and farmer websocket, and basic UI * Plot display and deletion * launch daemon on start * State changes from full node, harvester, farmer, and full node ui improvements * split balances in react * pending change balance * plotter * dev config * maintain connection / retry * Remove electron-ui, and style fixes * Better farmer and full node control * Remove electron ui references * Uncomment out starting the dameon * Remove timelord tab, and change full node style * Clean up new wallet login * Refactor RPCs * Now that the GH runner uses python 3.7.7 - use for windows installer * add balance split to coloured coin wallet * spendable balance fix * Import private key from UI fix * mac build/installer Co-authored-by: Mariano Sorgente <sorgente711@gmail.com> Co-authored-by: Lipa Long <lipa@chia.net> Co-authored-by: Gene Hoffman <hoffmang@hoffmang.com>
2020-05-20 10:41:10 +03:00
async def get_frozen_amount(self) -> uint64:
return await self.wallet_state_manager.get_frozen_balance(self.wallet_info.id)
Electron react (#226) * clean react * add material ui * add word list * mnemonic v0 * jeepney backup * keychain usage * wallet api * mnemonic ui * mnemonics redux state * handle exceptions correctly * dashboard * wallets * get puzzle hash * tx history * sidebar * start stop wallet node * use existing mnemonics * status info * create cc wallet * theme should be outside of switch * create offer * dbus alternative for linux * key migration * don't autocomplete, don't reset simulator db * reset mnemonics * Refactor keychain, and key migration * Implement UI for changing keys * Removing keys and mnemonic button * Start making farmer and harvester RPCs * start rpx for simulator * Harvester and farmer websocket, and basic UI * Plot display and deletion * launch daemon on start * State changes from full node, harvester, farmer, and full node ui improvements * split balances in react * pending change balance * plotter * dev config * maintain connection / retry * Remove electron-ui, and style fixes * Better farmer and full node control * Remove electron ui references * Uncomment out starting the dameon * Remove timelord tab, and change full node style * Clean up new wallet login * Refactor RPCs * Now that the GH runner uses python 3.7.7 - use for windows installer * add balance split to coloured coin wallet * spendable balance fix * Import private key from UI fix * mac build/installer Co-authored-by: Mariano Sorgente <sorgente711@gmail.com> Co-authored-by: Lipa Long <lipa@chia.net> Co-authored-by: Gene Hoffman <hoffmang@hoffmang.com>
2020-05-20 10:41:10 +03:00
async def get_spendable_balance(self) -> uint64:
spendable_am = (
await self.wallet_state_manager.get_confirmed_spendable_balance_for_wallet(
self.wallet_info.id
)
Electron react (#226) * clean react * add material ui * add word list * mnemonic v0 * jeepney backup * keychain usage * wallet api * mnemonic ui * mnemonics redux state * handle exceptions correctly * dashboard * wallets * get puzzle hash * tx history * sidebar * start stop wallet node * use existing mnemonics * status info * create cc wallet * theme should be outside of switch * create offer * dbus alternative for linux * key migration * don't autocomplete, don't reset simulator db * reset mnemonics * Refactor keychain, and key migration * Implement UI for changing keys * Removing keys and mnemonic button * Start making farmer and harvester RPCs * start rpx for simulator * Harvester and farmer websocket, and basic UI * Plot display and deletion * launch daemon on start * State changes from full node, harvester, farmer, and full node ui improvements * split balances in react * pending change balance * plotter * dev config * maintain connection / retry * Remove electron-ui, and style fixes * Better farmer and full node control * Remove electron ui references * Uncomment out starting the dameon * Remove timelord tab, and change full node style * Clean up new wallet login * Refactor RPCs * Now that the GH runner uses python 3.7.7 - use for windows installer * add balance split to coloured coin wallet * spendable balance fix * Import private key from UI fix * mac build/installer Co-authored-by: Mariano Sorgente <sorgente711@gmail.com> Co-authored-by: Lipa Long <lipa@chia.net> Co-authored-by: Gene Hoffman <hoffmang@hoffmang.com>
2020-05-20 10:41:10 +03:00
)
return spendable_am
async def get_pending_change_balance(self) -> uint64:
unconfirmed_tx = (
await self.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
self.wallet_info.id
)
Electron react (#226) * clean react * add material ui * add word list * mnemonic v0 * jeepney backup * keychain usage * wallet api * mnemonic ui * mnemonics redux state * handle exceptions correctly * dashboard * wallets * get puzzle hash * tx history * sidebar * start stop wallet node * use existing mnemonics * status info * create cc wallet * theme should be outside of switch * create offer * dbus alternative for linux * key migration * don't autocomplete, don't reset simulator db * reset mnemonics * Refactor keychain, and key migration * Implement UI for changing keys * Removing keys and mnemonic button * Start making farmer and harvester RPCs * start rpx for simulator * Harvester and farmer websocket, and basic UI * Plot display and deletion * launch daemon on start * State changes from full node, harvester, farmer, and full node ui improvements * split balances in react * pending change balance * plotter * dev config * maintain connection / retry * Remove electron-ui, and style fixes * Better farmer and full node control * Remove electron ui references * Uncomment out starting the dameon * Remove timelord tab, and change full node style * Clean up new wallet login * Refactor RPCs * Now that the GH runner uses python 3.7.7 - use for windows installer * add balance split to coloured coin wallet * spendable balance fix * Import private key from UI fix * mac build/installer Co-authored-by: Mariano Sorgente <sorgente711@gmail.com> Co-authored-by: Lipa Long <lipa@chia.net> Co-authored-by: Gene Hoffman <hoffmang@hoffmang.com>
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)
def puzzle_for_pk(self, pubkey: bytes) -> Program:
2020-02-12 04:01:39 +03:00
return puzzle_for_pk(pubkey)
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
async def get_new_puzzlehash(self) -> bytes32:
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-04-20 08:10:19 +03:00
def make_solution(
self, primaries=None, min_time=0, me=None, consumed=None, fee=None
):
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))
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. """
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 = (
f"Can't select amount higher than our spendable balance {amount}, spendable"
f" {spendable_amount}"
)
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
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
)
sum_value = 0
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
)
for coinrecord in unspent:
if sum_value >= amount and len(used_coins) > 0:
2020-03-19 11:26:51 +03:00
break
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
sum_value += coinrecord.coin.amount
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
# 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)
if sum_value < amount:
raise ValueError(
"Can't make this transaction at the moment. Waiting for the change from the previous transaction."
)
# 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,
amount: uint64,
2020-03-23 22:59:53 +03:00
newpuzzlehash: bytes32,
fee: uint64 = uint64(0),
2020-03-23 22:59:53 +03:00
origin_id: bytes32 = None,
coins: Set[Coin] = None,
2020-02-13 22:57:40 +03:00
) -> List[Tuple[Program, CoinSolution]]:
2020-03-06 02:53:36 +03:00
"""
Generates a unsigned transaction in form of List(Puzzle, Solutions)
"""
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}")
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
spends: List[Tuple[Program, CoinSolution]] = []
output_created = False
for coin in coins:
2020-04-01 04:59:14 +03:00
self.log.info(f"coin from coins {coin}")
2020-03-06 02:53:36 +03:00
# Get keys for puzzle_hash
2020-02-12 04:01:39 +03:00
puzzle_hash = coin.puzzle_hash
2020-04-15 03:44:17 +03:00
maybe = await self.wallet_state_manager.get_keys(puzzle_hash)
2020-02-13 22:57:40 +03:00
if not maybe:
2020-09-14 12:29:03 +03:00
error_msg = f"Wallet couldn't find keys for puzzle_hash {puzzle_hash}"
self.log.error(error_msg)
raise ValueError(error_msg)
2020-03-06 02:53:36 +03:00
# Get puzzle for pubkey
2020-02-13 22:57:40 +03:00
pubkey, secretkey = maybe
puzzle: Program = puzzle_for_pk(bytes(pubkey))
2020-03-06 02:53:36 +03:00
# Only one coin creates outputs
if output_created is False and origin_id is None:
primaries = [{"puzzlehash": newpuzzlehash, "amount": amount}]
if change > 0:
changepuzzlehash = await self.get_new_puzzlehash()
primaries.append({"puzzlehash": changepuzzlehash, "amount": change})
2020-04-20 08:08:16 +03:00
if fee > 0:
solution = self.make_solution(primaries=primaries, fee=fee)
else:
solution = self.make_solution(primaries=primaries)
output_created = True
elif output_created is False and origin_id == 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:
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-04-20 08:08:16 +03:00
if fee > 0:
solution = self.make_solution(primaries=primaries, fee=fee)
else:
solution = self.make_solution(primaries=primaries)
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-02-12 04:01:39 +03:00
spends.append((puzzle, CoinSolution(coin, solution)))
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
async def sign_transaction(
self, spends: List[Tuple[Program, CoinSolution]]
2020-09-14 12:29:03 +03:00
) -> SpendBundle:
2020-03-06 02:53:36 +03:00
signatures = []
2020-02-13 22:57:40 +03:00
for puzzle, solution in spends:
2020-03-06 02:53:36 +03:00
# Get keys
2020-04-15 09:40:36 +03:00
keys = await self.wallet_state_manager.get_keys(solution.coin.puzzle_hash)
2020-02-13 22:57:40 +03:00
if not keys:
2020-09-14 12:29:03 +03:00
error_msg = f"Sign transaction failed, No Keys for puzzlehash {solution.coin.puzzle_hash}"
self.log.error(error_msg)
raise ValueError(error_msg)
2020-04-01 04:59:14 +03:00
2020-02-13 22:57:40 +03:00
pubkey, secretkey = keys
code_ = [puzzle, solution.solution]
sexp = Program.to(code_)
2020-03-06 02:53:36 +03:00
# Get AGGSIG conditions
2020-02-27 04:33:25 +03:00
err, con, cost = conditions_for_solution(sexp)
2020-02-13 22:57:40 +03:00
if err or not con:
2020-09-14 12:29:03 +03:00
error_msg = f"Sign transaction failed, con:{con}, error: {err}"
self.log.error(error_msg)
raise ValueError(error_msg)
2020-04-01 04:59:14 +03:00
2020-02-13 22:57:40 +03:00
conditions_dict = conditions_by_opcode(con)
2020-03-06 02:53:36 +03:00
# Create signature
for _, msg in pkm_pairs_for_conditions_dict(
2020-03-26 00:36:32 +03:00
conditions_dict, bytes(solution.coin)
):
signature = AugSchemeMPL.sign(secretkey, msg)
2020-03-06 02:53:36 +03:00
signatures.append(signature)
# Aggregate signatures
aggsig = AugSchemeMPL.aggregate(signatures)
2020-02-13 22:57:40 +03:00
solution_list: List[CoinSolution] = [
CoinSolution(
coin_solution.coin, Program.to([puzzle, coin_solution.solution])
2020-02-13 22:57:40 +03:00
)
for (puzzle, coin_solution) in spends
]
2020-09-14 12:29:03 +03:00
return SpendBundle(solution_list, aggsig)
2020-02-13 22:57:40 +03:00
2020-03-19 11:26:51 +03:00
async def generate_signed_transaction_dict(
self, data: Dict[str, Any]
2020-09-14 12:29:03 +03:00
) -> TransactionRecord:
2020-03-19 11:26:51 +03:00
""" Use this to generate transaction. """
# Check that both are integers
if not isinstance(data["amount"], int) or not isinstance(data["amount"], int):
raise ValueError("An integer amount or fee is required (too many decimals)")
amount = uint64(data["amount"])
2020-03-19 11:26:51 +03:00
if "fee" in data:
fee = uint64(data["fee"])
2020-03-19 11:26:51 +03:00
else:
fee = uint64(0)
2020-03-19 11:26:51 +03:00
2020-08-07 22:13:22 +03:00
puzzle_hash = decode_puzzle_hash(data["puzzle_hash"])
2020-03-19 11:26:51 +03:00
return await self.generate_signed_transaction(amount, puzzle_hash, fee)
2020-02-13 22:57:40 +03:00
async def generate_signed_transaction(
2020-03-23 22:59:53 +03:00
self,
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()))
add_list: List[Coin] = []
rem_list: List[Coin] = []
for add in spend_bundle.additions():
add_list.append(add)
for rem in spend_bundle.removals():
rem_list.append(rem)
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 also defined in CCWallet as get_sigs()
# I think this should be a the default way the wallet gets signatures in sign_transaction()
2020-04-22 09:56:48 +03:00
async def get_sigs_for_innerpuz_with_innersol(
self, innerpuz: Program, innersol: Program
) -> List[G2Element]:
2020-04-21 16:55:59 +03:00
puzzle_hash = innerpuz.get_tree_hash()
pubkey, private = await self.wallet_state_manager.get_keys(puzzle_hash)
sigs: List[G2Element] = []
2020-04-21 16:55:59 +03:00
code_ = [innerpuz, innersol]
sexp = Program.to(code_)
error, conditions, cost = conditions_dict_for_solution(sexp)
if conditions is not None:
for _, msg in pkm_pairs_for_conditions_dict(conditions):
signature = AugSchemeMPL.sign(private, msg)
2020-04-21 16:55:59 +03:00
sigs.append(signature)
return sigs
# Create an offer spend bundle for chia given an amount of relative change (i.e -400 or 1000)
2020-04-22 09:56:48 +03:00
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:
utxos = await self.select_coins(abs(chia_amount), exclude)
2020-04-21 16:55:59 +03:00
else:
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
sigs: List[G2Element] = []
2020-04-21 16:55:59 +03:00
for coin in utxos:
2020-04-22 09:56:48 +03:00
pubkey, secretkey = await self.wallet_state_manager.get_keys(
coin.puzzle_hash
)
2020-04-21 16:55:59 +03:00
puzzle = self.puzzle_for_pk(bytes(pubkey))
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()])
list_of_solutions.append(CoinSolution(coin, Program.to([puzzle, solution])))
2020-04-21 16:55:59 +03:00
new_sigs = await self.get_sigs_for_innerpuz_with_innersol(puzzle, solution)
sigs = sigs + new_sigs
aggsig = AugSchemeMPL.aggregate(sigs)
2020-09-14 12:29:03 +03:00
return SpendBundle(list_of_solutions, aggsig)