def _from_json_data_object(self, data): # decode public key if 'pubkey' in data: self._public_key = PublicKey.from_json(data['pubkey']) else: self._public_key = None # decode signature if 'signature' in data: self._signature = ED25519Signature.from_json(data['signature']) else: self._signature = None # decode registration fee if 'regfee' in data: self.registration_fee = Currency.from_json(data['regfee']) else: self.registration_fee = None # decode transaction fee if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None # decode coin inputs self._coin_inputs = [ CoinInput.from_json(ci) for ci in data.get('coininputs', []) or [] ] # decode refund coin output (if it exists) if 'refundcoinoutput' in data: self._refund_coin_output = CoinOutput.from_json( data['refundcoinoutput']) else: self._refund_coin_output = None
def _from_json_data_object(self, data): # decode address if 'address' in data: self._address = UnlockHash.from_json(data['address']) else: self._address = None # decode value if 'value' in data: self._value = Currency.from_json(data['value']) else: self._value = None # decode transaction fee if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None # decode blockid if 'blockid' in data: self._blockid = ERC20Hash.from_json(data['blockid']) else: self._blockid = None # decode transactionid if 'txid' in data: self._transactionid = ERC20Hash.from_json(data['txid']) else: self._transactionid = None
def _from_json_data_object(self, data): # decode address if 'address' in data: self._address = ERC20Address.from_json(data['address']) else: self._address = None # decode value if 'value' in data: self._value = Currency.from_json(data['value']) else: self._value = None # decode transaction fee if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None # decode coin inputs self._coin_inputs = [ CoinInput.from_json(ci) for ci in data.get('coininputs', []) or [] ] # decode refund coin output (if it exists) if 'refundcoinoutput' in data: self._refund_coin_output = CoinOutput.from_json( data['refundcoinoutput']) else: self._refund_coin_output = None
class BotTransactionBaseClass(TransactionBaseClass, SignatureCallbackBase): BOT_FEE_NETWORK_ADDRESS_UPDATE = Currency(value='20 TFT') BOT_FEE_ADDITIONAL_NAME = Currency(value='50 TFT') BOT_FEE_REGISTRATION = Currency(value='90 TFT') BOT_FEE_MONTHLY = Currency(value='10 TFT') MAX_NAMES_PER_BOT = 5 MAX_ADDRESSES_PER_BOT = 10 SPECIFIER_SENDER = BinaryData(value=b'sender', fixed_size=0) SPECIFIER_RECEIVER = BinaryData(value=b'receiver', fixed_size=0) @staticmethod def compute_monthly_bot_fees(months): """ computes the total monthly fees required for the given months, using the given oneCoin value as the currency's unit value. """ fees = BotTransactionBaseClass.BOT_FEE_MONTHLY * months if months < 12: return fees if months < 24: return fees * 0.7 return fees * 0.5 @property @abstractmethod def required_bot_fees(self): """ The bot fees required to pay for this Bot Transaction. """ pass
def registration_fee(self, txfee): if txfee is not None: fee = Currency(value=txfee) if fee != self._registration_fee: raise ValueError( "registration fee is hardcoded at {}, cannot be set to {}". format(fee.str(with_unit=True), self._registration_fee.str(with_unit=True)))
def __init__(self): self._public_key = None self._signature = None self._registration_fee = Currency( value=TransactionV210.HARDCODED_REGISTRATION_FEE) self._transaction_fee = None self._coin_inputs = None self._refund_coin_output = None super().__init__()
def unconfirmed_locked(self): """ Total unconfirmed coins that are locked, and thus not available for spending. """ if self.chain_time > 0 and self.chain_height > 0: return Currency.sum(*[ co.value for co in jsobj.dict_values(self._outputs_unconfirmed) if co.condition.lock.locked_check(time=self.chain_time, height=self.chain_height) ]) or Currency() else: return Currency( ) # impossible to know for sure without a complete context
def unconfirmed(self): """ Total unconfirmed coins, available for spending. """ if self.chain_time > 0 and self.chain_height > 0: return Currency.sum(*[ co.value for co in jsobj.dict_values(self._outputs_unconfirmed) if not co.condition.lock.locked_check(time=self.chain_time, height=self.chain_height) ]) or Currency() else: return Currency.sum(*[ co.value for co in jsobj.dict_values(self._outputs_unconfirmed) ])
def _fund_individual(self, amount, addresses): outputs_available = [ co for co in self.outputs_available if co.condition.unlockhash.__str__() in addresses ] def sort_output_by_value(a, b): if a.value.less_than(b.value): return -1 if a.value.greater_than(b.value): return 1 return 0 outputs_available = jsarr.sort(outputs_available, sort_output_by_value) collected = Currency() outputs = [] # try to fund only with confirmed outputs, if possible for co in outputs_available: if co.value.greater_than_or_equal_to(amount): return [co], co.value collected = collected.plus(co.value) outputs.append(co) if len(outputs) > _MAX_RIVINE_TRANSACTION_INPUTS: # to not reach the input limit collected = collected.minus(jsarr.pop(outputs, 0).value) if collected.greater_than_or_equal_to(amount): return outputs, collected if collected.greater_than_or_equal_to(amount): # if we already have sufficient, we stop now return outputs, collected # use unconfirmed balance, not ideal, but acceptable outputs_available = [ co for co in self.outputs_unconfirmed_available if co.condition.unlockhash.__str__() in addresses ] outputs_available = jsarr.sort(outputs_available, sort_output_by_value, reverse=True) for co in outputs_available: if co.value.greater_than_or_equal_to(amount): return [co], co.value collected = collected.plus(co.value) outputs.append(co) if len(outputs) > _MAX_RIVINE_TRANSACTION_INPUTS: # to not reach the input limit collected = collected.minus(outputs.pop(0).value) if collected.greater_than_or_equal_to(amount): return outputs, collected # we return whatever we have collected, no matter if it is sufficient return outputs, collected
def fund(self, amount, source=None): """ Fund the specified amount with the available outputs of this wallet's balance. """ # collect addresses and multisig addresses addresses = set() refund = None if source == None: for co in self.outputs_available: addresses.add(co.condition.unlockhash.__str__()) for co in self.outputs_unconfirmed_available: addresses.add(co.condition.unlockhash.__str__()) else: # if only one address is given, transform it into an acceptable list if not isinstance(source, list) and not jsobj.is_js_arr(source): if isinstance(source, str): source = UnlockHash.from_json(source) elif not isinstance(source, UnlockHash): raise TypeError( "cannot add source address from type {}".format( type(source))) source = [source] # add one or multiple personal/multisig addresses for value in source: if isinstance(value, str): value = UnlockHash.from_json(value) elif not isinstance(value, UnlockHash): raise TypeError( "cannot add source address from type {}".format( type(value))) elif value.uhtype.__eq__(UnlockHashType.PUBLIC_KEY): addresses.add(value) else: raise TypeError( "cannot add source address with unsupported UnlockHashType {}" .format(value.uhtype)) if len(source) == 1: if source[0].uhtype.__eq__(UnlockHashType.PUBLIC_KEY): refund = ConditionTypes.unlockhash_new( unlockhash=source[0]) # ensure at least one address is defined if len(addresses) == 0: raise tferrors.InsufficientFunds( "insufficient funds in this wallet") # if personal addresses are given, try to use these first # as these are the easiest kind to deal with if len(addresses) == 0: outputs, collected = ([], Currency()) # start with nothing else: outputs, collected = self._fund_individual(amount, addresses) if collected.greater_than_or_equal_to(amount): # if we already have sufficient, we stop now return ([CoinInput.from_coin_output(co) for co in outputs], collected.minus(amount), refund) raise tferrors.InsufficientFunds( "not enough funds available in the wallet to fund the requested amount" )
def _from_json_data_object(self, data): self._addresses = [ NetworkAddress.from_json(address) for address in data.get('addresses', []) or [] ] self._names = [ BotName.from_json(name) for name in data.get('names', []) or [] ] self._number_of_months = int(data.get('nrofmonths', 0) or 0) if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None self._coin_inputs = [ CoinInput.from_json(ci) for ci in data.get('coininputs', []) or [] ] if 'refundcoinoutput' in data: self._refund_coin_output = CoinOutput.from_json( data['refundcoinoutput']) else: self._refund_coin_output = None if 'identification' not in data or data['identification'] in (None, {}): self._public_key = None self._signature = None else: identification = data['identification'] self._public_key = PublicKey.from_json(identification['publickey']) self._signature = ED25519Signature.from_json( identification['signature'], as_array=True)
def _from_json_data_object(self, data): self._botid = int(data.get('id', 0) or 0) addresses = data.get('addresses', {}) or {} self._addresses_to_add = [ NetworkAddress.from_json(address) for address in addresses.get('add', []) or [] ] self._addresses_to_remove = [ NetworkAddress.from_json(address) for address in addresses.get('remove', []) or [] ] names = data.get('names', {}) or {} self._names_to_add = [ BotName.from_json(name) for name in names.get('add', []) or [] ] self._names_to_remove = [ BotName.from_json(name) for name in names.get('remove', []) or [] ] self._number_of_months = int(data.get('nrofmonths', 0) or 0) if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None self._coin_inputs = [ CoinInput.from_json(ci) for ci in data.get('coininputs', []) or [] ] if 'refundcoinoutput' in data: self._refund_coin_output = CoinOutput.from_json( data['refundcoinoutput']) else: self._refund_coin_output = None self._signature = ED25519Signature.from_json( data.get('signature', None) or None)
def _from_json_data_object(self, data): # decode sender info if 'sender' in data: bot_data = data['sender'] self._sender_botid = int(bot_data.get('id', 0) or 0) self._sender_signature = ED25519Signature.from_json(bot_data.get('signature', None) or None) else: self._sender_botid = None self._sender_signature = None # decode receiver info if 'receiver' in data: bot_data = data['receiver'] self._receiver_botid = int(bot_data.get('id', 0) or 0) self._receiver_signature = ED25519Signature.from_json(bot_data.get('signature', None) or None) else: self._receiver_botid = None self._receiver_signature = None # decode names self._names = [BotName.from_json(name) for name in data.get('names', []) or []] # decode transaction fee if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None # decode coin inputs self._coin_inputs = [CoinInput.from_json(ci) for ci in data.get('coininputs', []) or []] # decode refund coin output if 'refundcoinoutput' in data: self._refund_coin_output = CoinOutput.from_json(data['refundcoinoutput']) else: self._refund_coin_output = None
def _from_json_data_object(self, data): self._nonce = BinaryData.from_json(data.get('nonce', ''), strencoding='base64') self._mint_condition = conditions.from_json( data.get('mintcondition', {})) self._mint_fulfillment = fulfillments.from_json( data.get('mintfulfillment', {})) self._miner_fees = [ Currency.from_json(fee) for fee in data.get('minerfees', []) or [] ] self._data = BinaryData.from_json(data.get('arbitrarydata', None) or '', strencoding='base64')
def _from_json_data_object(self, data): self._coin_inputs = [CoinInput.from_json( ci) for ci in data.get('coininputs', []) or []] self._coin_outputs = [CoinOutput.from_json( co) for co in data.get('coinoutputs', []) or []] self._blockstake_inputs = [BlockstakeInput.from_json( bsi) for bsi in data.get('blockstakeinputs', []) or []] self._blockstake_outputs = [BlockstakeOutput.from_json( bso) for bso in data.get('blockstakeoutputs', []) or []] self._miner_fees = [Currency.from_json( fee) for fee in data.get('minerfees', []) or []] self._data = BinaryData.from_json( data.get('arbitrarydata', None) or '', strencoding='base64')
def _block_get_parse_cb(self, result): endpoint, block = result try: # parse the transactions transactions = [] for etxn in block['transactions']: # parse the explorer transaction transaction = self._transaction_from_explorer_transaction( etxn, endpoint=endpoint, resp=block) # append the transaction to the list of transactions transactions.append(transaction) rawblock = block['rawblock'] # parse the parent id parentid = Hash.from_json(obj=rawblock['parentid']) # parse the miner payouts miner_payouts = [] minerpayoutids = block.get_or('minerpayoutids', None) or [] eminerpayouts = rawblock.get_or('minerpayouts', None) or [] if len(eminerpayouts) != len(minerpayoutids): raise tferrors.ExplorerInvalidResponse( "amount of miner payouts and payout ids are not matching: {} != {}" .format(len(eminerpayouts), len(minerpayoutids)), endpoint, block) for idx, mp in enumerate(eminerpayouts): id = Hash.from_json(minerpayoutids[idx]) value = Currency.from_json(mp['value']) unlockhash = UnlockHash.from_json(mp['unlockhash']) miner_payouts.append( ExplorerMinerPayout(id=id, value=value, unlockhash=unlockhash)) # get the timestamp and height height = int(block['height']) timestamp = int(rawblock['timestamp']) # get the block's identifier blockid = Hash.from_json(block['blockid']) # for all transactions assign these properties for transaction in transactions: _assign_block_properties_to_transacton(transaction, block) transaction.height = height transaction.blockid = blockid # return the block, as reported by the explorer return ExplorerBlock(id=blockid, parentid=parentid, height=height, timestamp=timestamp, transactions=transactions, miner_payouts=miner_payouts) except KeyError as exc: raise tferrors.ExplorerInvalidResponse(str(exc), endpoint, block) from exc
def coin_outputs(self): """ Coin outputs of this Transaction, funded by the Transaction's coin inputs. """ outputs = [] if self.fee_payout_address != None and len(self.miner_fees) > 0: amount = Currency.sum(*self.miner_fees) condition = ConditionTypes.from_recipient(self.fee_payout_address) outputs.append( CoinOutput(value=amount, condition=condition, id=self._fee_payout_id, is_fee=True)) return jsarr.concat(outputs, self._custom_coin_outputs_getter())
def _from_json_data_object(self, data): self._nonce = BinaryData.from_json(data.get('nonce', ''), strencoding='base64') self._mint_fulfillment = FulfillmentTypes.from_json( data.get('mintfulfillment', {})) self._coin_outputs = [ CoinOutput.from_json(co) for co in data.get('coinoutputs', []) or [] ] self._miner_fees = [ Currency.from_json(fee) for fee in data.get('minerfees', []) or [] ] self._data = BinaryData.from_json(data.get('arbitrarydata', None) or '', strencoding='base64')
def required_bot_fees(self): """ The fees required to pay for this 3Bot Record Update Transaction. """ fees = Currency(value=0) # all months have to be paid if self._number_of_months > 0: fees += BotTransactionBaseClass.compute_monthly_bot_fees(self._number_of_months) # if addresses have been modified, this has to be paid if len(self._addresses_to_add) > 0 or len(self._addresses_to_remove) > 0: fees += BotTransactionBaseClass.BOT_FEE_NETWORK_ADDRESS_UPDATE # each additional that is added, has to be paid as well lnames = len(self._names_to_add) if lnames > 0: fees += BotTransactionBaseClass.BOT_FEE_ADDITIONAL_NAME * lnames # return the total fees return fees
def value(self, value): if isinstance(value, Currency): self._value = value return self._value = Currency(value=value)
def from_json(cls, obj): return cls( value=Currency.from_json(obj['value']), condition=ConditionTypes.from_json(obj['condition']))
class CoinOutput(BaseDataTypeClass): """ CoinOutput class """ def __init__(self, value=None, condition=None, id=None, is_fee=False): self._value = None self.value = value self._condition = None self.condition = condition # property that can be set if known, but which is not part of the actual CoinOutput self._id = None self.id = id self._is_fee = False self.is_fee = is_fee @classmethod def from_json(cls, obj): return cls( value=Currency.from_json(obj['value']), condition=ConditionTypes.from_json(obj['condition'])) @property def value(self): return self._value @value.setter def value(self, value): if isinstance(value, Currency): self._value = value return self._value = Currency(value=value) @property def condition(self): return self._condition @condition.setter def condition(self, value): if value == None: self._condition = ConditionNil() return if not isinstance(value, ConditionBaseClass): raise TypeError( "cannot assign value of type {} as a CoinOutput's condition (expected: ConditionBaseClass subtype)".format(type(value))) self._condition = value @property def id(self): return self._id @id.setter def id(self, value): if isinstance(value, Hash): self._id = Hash(value=value.value) return self._id = Hash(value=value) @property def is_fee(self): return self._is_fee @is_fee.setter def is_fee(self, value): if not isinstance(value, bool): raise TypeError("is fee is supposed to be a bool, cannot be {} ({})".format(value, type(value))) self._is_fee = value def json(self): return { 'value': self._value.json(), 'condition': self._condition.json() } def sia_binary_encode(self, encoder): """ Encode this CoinOutput according to the Sia Binary Encoding format. """ encoder.add_all(self._value, self._condition) def rivine_binary_encode(self, encoder): """ Encode this CoinOutput according to the Rivine Binary Encoding format. """ encoder.add_all(self._value, self._condition)
def transaction_fee(self, txfee): if txfee is None: self._transaction_fee = None else: self._transaction_fee = Currency(value=txfee)
def transaction_fee(self): if self._transaction_fee is None: return Currency() return self._transaction_fee
def minimum_miner_fee(self): if self == NetworkType.DEVNET: return Currency('1.0') return Currency('0.1')
class TransactionV210(TransactionBaseClass, SignatureCallbackBase): _SPECIFIER = b'erc20 addrreg tx' HARDCODED_REGISTRATION_FEE = '10 TFT' SPECIFIER_REGISTRATION_SIGNATURE = BinaryData(value=b'registration', fixed_size=0) def __init__(self): self._public_key = None self._signature = None self._registration_fee = Currency( value=TransactionV210.HARDCODED_REGISTRATION_FEE) self._transaction_fee = None self._coin_inputs = None self._refund_coin_output = None super().__init__() @property def version(self): return TransactionVersion.ERC20_ADDRESS_REGISTRATION @property def public_key(self): if self._public_key is None: return PublicKey() return self._public_key @public_key.setter def public_key(self, value): if value is None: self._public_key = None return if not isinstance(value, PublicKey): raise TypeError( "cannot assign value of type {} as BotRegistration's public key (expected type: PublicKey)" .format(type(value))) self._public_key = PublicKey(specifier=value.specifier, hash=value.hash) @property def signature(self): if self._signature is None: return ED25519Signature(as_array=True) return self._signature @signature.setter def signature(self, value): if value is None: self._signature = None return self._signature = ED25519Signature(value=value, as_array=True) @property def coin_inputs(self): """ Coin inputs of this Transaction, used as funding for coin outputs, fees and any other kind of coin output. """ return self._coin_inputs @coin_inputs.setter def coin_inputs(self, value): self._coin_inputs = [] if not value: return for ci in value: self.coin_input_add(ci.parentid, ci.fulfillment, parent_output=ci.parent_output) @property def refund_coin_output(self): if self._refund_coin_output is None: return CoinOutput() return self._refund_coin_output @property def coin_outputs(self): """ Empty list, or a singleton with the refund coin output if that one exists. """ if self._refund_coin_output is None: return [] return [self._refund_coin_output] @coin_outputs.setter def coin_outputs(self, value): if isinstance(value, list): lvalue = len(value) if lvalue == 0: value = None elif lvalue == 1: value = value[0] else: raise ValueError( "ThreeBot only can have one coin output, a refund coin output" ) if value is None: self._refund_coin_output = None elif isinstance(value, CoinOutput): self._refund_coin_output = CoinOutput(value=value.value, condition=value.condition) self._refund_coin_output.id = value.id else: raise TypeError( "cannot assign a value of type {} to coin outputs".format( type(value))) def coin_input_add(self, parentid, fulfillment, parent_output=None): ci = CoinInput(parentid=parentid, fulfillment=fulfillment) ci.parent_output = parent_output self._coin_inputs.append(ci) def refund_coin_output_set(self, value, condition, id=None): co = CoinOutput(value=value, condition=condition) co.id = id self._refund_coin_output = co @property def registration_fee(self): return self._registration_fee @registration_fee.setter def registration_fee(self, txfee): if txfee is not None: fee = Currency(value=txfee) if fee != self._registration_fee: raise ValueError( "registration fee is hardcoded at {}, cannot be set to {}". format(fee.str(with_unit=True), self._registration_fee.str(with_unit=True))) @property def transaction_fee(self): if self._transaction_fee is None: return Currency() return self._transaction_fee @transaction_fee.setter def transaction_fee(self, txfee): if txfee is None: self._transaction_fee = None else: self._transaction_fee = Currency(value=txfee) @property def miner_fees(self): if self._transaction_fee is None: return [] return [self._transaction_fee] def signature_add(self, public_key, signature): """ Implements SignatureCallbackBase. """ if self._public_key.unlockhash != public_key.unlockhash: raise ValueError( "given public key ({}) does not equal public key ({})".format( str(self._public_key.unlockhash), str(public_key.unlockhash))) self.signature = signature def _signature_hash_input_get(self, *extra_objects): e = encoder_rivine_get() # encode the transaction version e.add_int8(self.version) # encode the specifier e.add_array(TransactionV210._SPECIFIER) # encode the public key e.add_all(self.public_key) # extra objects if any if extra_objects: e.add_all(*extra_objects) # encode coin inputs e.add(len(self.coin_inputs)) for ci in self.coin_inputs: e.add(ci.parentid) # encode registration and transaction fee e.add_all(self.registration_fee, self.transaction_fee) # encode refund coin output if self._refund_coin_output is None: e.add_int8(0) else: e.add_int8(1) e.add(self._refund_coin_output) # return data return e.data def _id_input_compute(self): return bytearray( TransactionV210._SPECIFIER) + self._binary_encode_data() def _binary_encode_data(self): e = encoder_rivine_get() # encode all easy properties e.add_all(self.public_key, self.signature, self.registration_fee, self.transaction_fee, self.coin_inputs) # encode the only "pointer" property if self._refund_coin_output is None: e.add_int8(0) else: e.add_int8(1) e.add(self._refund_coin_output) # return encoded data return e.data def _from_json_data_object(self, data): # decode public key if 'pubkey' in data: self._public_key = PublicKey.from_json(data['pubkey']) else: self._public_key = None # decode signature if 'signature' in data: self._signature = ED25519Signature.from_json(data['signature']) else: self._signature = None # decode registration fee if 'regfee' in data: self.registration_fee = Currency.from_json(data['regfee']) else: self.registration_fee = None # decode transaction fee if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None # decode coin inputs self._coin_inputs = [ CoinInput.from_json(ci) for ci in data.get('coininputs', []) or [] ] # decode refund coin output (if it exists) if 'refundcoinoutput' in data: self._refund_coin_output = CoinOutput.from_json( data['refundcoinoutput']) else: self._refund_coin_output = None def _json_data_object(self): tftaddress = self.public_key.unlockhash erc20address = ERC20Address.from_unlockhash(tftaddress) output = { 'pubkey': self.public_key.json(), 'tftaddress': tftaddress.json(), 'erc20address': erc20address.json(), 'signature': self.signature.json(), 'regfee': self.registration_fee.json(), 'txfee': self.transaction_fee.json(), 'coininputs': [ci.json() for ci in self.coin_inputs], } if self._refund_coin_output is not None: output['refundcoinoutput'] = self._refund_coin_output.json() return output def _extra_signature_requests_new(self): if self._public_key is None: # if no parent public key is defined, cannot do anything return [] if self._signature is not None: return [] # nothing to do # generate the input hash func input_hash_func = InputSignatureHashFactory( self, TransactionV210.SPECIFIER_REGISTRATION_SIGNATURE ).signature_hash_new # define the input_hash_new generator function, # used to create the input hash for creating the signature unlockhash = self._public_key.unlockhash def input_hash_gen(public_key): return input_hash_func() # create the only signature request return [ SignatureRequest(unlockhash=unlockhash, input_hash_gen=input_hash_gen, callback=self) ] def _extra_is_fulfilled(self): return self._signature is not None
def value(self): if self._value is None: return Currency() return self._value
def value(self, value): if value is None: self._value = None else: self._value = Currency(value=value)
def miner_fee_add(self, value): self._miner_fees.append(Currency(value=value))
def available(self): """ Total available coins. """ return Currency.sum(*[co.value for co in self.outputs_available])