def test_no_combine_with_fee(self): """ Verify that unused unspents do not increase fee. """ unspents_single = [Unspent(5000, 0, '', '', 0)] unspents_original = [ Unspent(5000, 0, '', '', 0), Unspent(5000, 0, '', '', 0) ] outputs_original = [(BITCOIN_ADDRESS_TEST_COMPRESSED, 1000, 'satoshi')] unspents, outputs = sanitize_tx_data(unspents_original, outputs_original, fee=1, leftover=RETURN_ADDRESS, combine=False, message=None) unspents_single, outputs_single = sanitize_tx_data( unspents_single, outputs_original, fee=1, leftover=RETURN_ADDRESS, combine=False, message=None) assert unspents == [Unspent(5000, 0, '', '', 0)] assert unspents_single == [Unspent(5000, 0, '', '', 0)] assert len(outputs) == 2 assert len(outputs_single) == 2 assert outputs[1][0] == RETURN_ADDRESS assert outputs_single[1][0] == RETURN_ADDRESS assert outputs[1][1] == outputs_single[1][1]
def test_no_combine_insufficient_funds(self): unspents_original = [Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0)] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 2500, 'satoshi')] with pytest.raises(InsufficientFunds): sanitize_tx_data( unspents_original, outputs_original, fee=50, leftover=RETURN_ADDRESS, combine=False, message=None )
def test_fee_applied(self): unspents_original = [Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0)] outputs_original = [(BITCOIN_ADDRESS_TEST_COMPRESSED, 2000, 'satoshi')] with pytest.raises(InsufficientFunds): sanitize_tx_data( unspents_original, outputs_original, fee=1, leftover=RETURN_ADDRESS, combine=True, message=None )
def test_zero_remaining(self): unspents_original = [Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0)] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 2000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS, combine=True, message=None ) assert unspents == unspents_original assert outputs == [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 2000)]
def test_message(self): unspents_original = [Unspent(10000, 0, '', '', 0), Unspent(10000, 0, '', '', 0)] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 1000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=5, leftover=RETURN_ADDRESS, combine=True, message='hello' ) assert len(outputs) == 3 assert outputs[2][0] == b'hello' assert outputs[2][1] == 0
def test_no_combine_remaining(self): unspents_original = [Unspent(7000, 0, '', '', 0), Unspent(3000, 0, '', '', 0)] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 2000, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS, combine=False, message=None ) assert unspents == [Unspent(3000, 0, '', '', 0)] assert len(outputs) == 2 assert outputs[1][0] == RETURN_ADDRESS assert outputs[1][1] == 1000
def test_message_pushdata(self): unspents_original = [Unspent(10000, 0, '', '', 0), Unspent(10000, 0, '', '', 0)] outputs_original = [(BITCOIN_CASHADDRESS_TEST_COMPRESSED, 1000, 'satoshi')] BYTES = len(b'hello').to_bytes(1, byteorder='little') + b'hello' unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=5, leftover=RETURN_ADDRESS, combine=True, message=BYTES, custom_pushdata=True ) assert len(outputs) == 3 assert outputs[2][0] == b'\x05' + b'hello' assert outputs[2][1] == 0
def test_combine_remaining_less_than_dust(self): unspents_original = [Unspent(1000, 0, '', '', 0), Unspent(1000, 0, '', '', 0)] outputs_original = [(BITCOIN_ADDRESS_TEST_COMPRESSED, 1500, 'satoshi')] unspents, outputs = sanitize_tx_data( unspents_original, outputs_original, fee=0, leftover=RETURN_ADDRESS, combine=True, message=None ) assert unspents == unspents_original # There will be no remaining uxto because it is less than the current dust (546) assert len(outputs) == 1 assert outputs[0][0] == BITCOIN_ADDRESS_TEST_COMPRESSED assert outputs[0][1] == 1500
def get_utxos(self, address, sort=False, sort_direction='asc'): """Gets all unspent transaction outputs belonging to an address. :param address: Address to get utxos for :type address: ``str`` :param sort_direction: 'desc' or 'asc' to sort unspents by descending/ascending order respectively :param sort: True or False indicates if unspents should be sorted unsorted (ignores sort_direction parameter) :rtype: ``list`` of :class:`~bitsv.network.meta.Unspent` """ if sort and sort_direction is 'asc': params = {'sort': 'value:asc'} elif sort and sort_direction is 'desc': params = {'sort': 'value:desc'} else: params = {'sort': None} r = requests.post( f'https://api.bitindex.network/api/v3/{self.network}/addrs/utxo', params=params, data=json.dumps({'addrs': address}), headers=self.headers, timeout=self.timeout, ) r.raise_for_status() return [ Unspent( amount=tx['satoshis'], confirmations=tx['confirmations'], script=tx['scriptPubKey'], txid=tx['txid'], txindex=tx['vout'], ) for tx in r.json() ]
def test_init(self): unspent = Unspent(10000, 7, 'script', 'txid', 0) assert unspent.amount == 10000 assert unspent.confirmations == 7 assert unspent.script == 'script' assert unspent.txid == 'txid' assert unspent.txindex == 0
def get_unspents(self, address, sort=False, sort_direction='asc'): """Gets all unspent transaction outputs belonging to an address. :param address: Address to get utxos for :type address: ``str`` :param sort_direction: 'desc' or 'asc' to sort unspents by descending/ascending order respectively :param sort: True or False indicates if unspents should be sorted unsorted (ignores sort_direction parameter) :rtype: ``list`` of :class:`~bitsv.network.meta.Unspent` """ if sort and sort_direction == 'asc': params = {'sort': 'value:asc'} elif sort and sort_direction == 'desc': params = {'sort': 'value:desc'} else: params = {'sort': None} r = requests.post( 'https://api.mattercloud.net/api/v3/{}/addrs/utxo'.format(self.network), params=params, data=json.dumps({'addrs': address}), headers=self.headers, ) r.raise_for_status() utxos = [Unspent( amount=tx['satoshis'], confirmations=tx['confirmations'], txid=tx['txid'], txindex=tx['vout'], ) for tx in r.json()] return sorted(utxos, key=lambda utxo: (-utxo.confirmations, utxo.amount))
def get_unspents(self, address): unspents = self.rpc.listunspent(0, 9999999, [address], True) return [Unspent( amount=int((tx['amount'] * BSV_TO_SAT_MULTIPLIER).normalize()), confirmations=tx['confirmations'], script=tx['scriptPubKey'], txid=tx['txid'], txindex=tx['vout'] ) for tx in unspents]
def get_unspents(cls, address): r = requests.get(cls.MAIN_UNSPENT_API.format(address), timeout=DEFAULT_TIMEOUT) r.raise_for_status() # pragma: no cover return [ Unspent(currency_to_satoshi(tx['amount'], 'bsv'), tx['confirmations'], tx['scriptPubKey'], tx['txid'], tx['vout']) for tx in r.json() ]
def get_unspent(cls, address): r = requests.get(cls.MAIN_UNSPENT_API.format(address), timeout=DEFAULT_TIMEOUT) if r.status_code != 200: # pragma: no cover raise ConnectionError return [ Unspent(currency_to_satoshi(tx['amount'], 'bch'), tx['confirmations'], tx['scriptPubKey'], tx['txid'], tx['vout']) for tx in r.json() ]
def get_unspents(cls, address): r = requests.get(cls.MAIN_UNSPENT_API.format(address), timeout=DEFAULT_TIMEOUT) r.raise_for_status() # pragma: no cover utxos = [ Unspent(amount=int(utxo['value']), confirmations=utxo['confirmations'], txid=utxo['txid'], txindex=utxo['vout']) for utxo in r.json() ] return sorted(utxos, key=lambda utxo: (-utxo.confirmations, utxo.amount))
def woc_utxos_to_unspents(woc_utxos, block_height): utxos = [] for utxo in woc_utxos: u = Unspent( amount=utxo['value'], confirmations=0 if utxo['height'] in [0, -1] else block_height - utxo['height'] + 1, txid=utxo['tx_hash'], txindex=utxo['tx_pos']) utxos.append(u) return sorted(utxos, key=lambda utxo: (-utxo.confirmations, utxo.amount))
def sign_transaction(self, tx_data): """Creates a signed P2PKH transaction using previously prepared transaction data. :param tx_data: Output of :func:`~bitsv.PrivateKeyTestnet.prepare_transaction`. :type tx_data: ``str`` :returns: The signed transaction as hex. :rtype: ``str`` """ data = json.loads(tx_data) unspents = [Unspent.from_dict(unspent) for unspent in data['unspents']] outputs = data['outputs'] return create_p2pkh_transaction(self, unspents, outputs)
def get_utxo(address): """gets utxos for given address BitIndex api""" address = cashaddress.to_legacy_address(address) json_payload = json.dumps({"addrs": address}) headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' } r = requests.post('https://api.bitindex.network/api/addrs/utxo', data=json_payload, headers=headers) return [ Unspent(amount=currency_to_satoshi(tx['amount'], 'bsv'), script=tx['scriptPubKey'], txid=tx['txid'], txindex=tx['vout'], confirmations=tx['confirmations']) for tx in r.json() ]
b'\x8a', (b"\x88x9\x9d\x83\xec%\xc6'\xcf\xbfu?\xf9\xca6\x027>" b"\xacCz\xb2gaT\xa3\xc2\xda#\xad\xf3"), b'\x01\x00\x00\x00', 0 ) ] INPUT_BLOCK = ('8878399d83ec25c627cfbf753ff9ca3602373eac437ab2676154a3c2da23adf30' '10000008a473044022045b743dbaaaa2cd1ef0b91346f5644e32dc70cde05091b' '3762d4cabb6ebd711a022074461056c26efeac0b448e7fa769773dd6e4436cde5' '05c8f6ca6303efe31f0950141043d5c2875c9bd116875a71a5db64cffcb13396b' '163d039b1d932782489180433476a4352a2add00ebb0d5c94c515b72eb10f1fd8' 'f3f03b42f4a2b255bfc9aa9e3ffffffff') UNSPENTS = [ Unspent(83727960, 15, 'f3ad23dac2a3546167b27a43ac3e370236caf93f75bfcf27c625ec839d397888', 1) ] OUTPUTS = [ ('n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi', 50000), ('mtrNwJxS1VyHYn3qBY1Qfsm3K3kh1mGRMS', 83658760) ] MESSAGES = [ (b'hello', 0), (b'there', 0) ] OUTPUT_BLOCK = ('50c30000000000001976a914e7c1345fc8f87c68170b3aa798a956c2fe6a9eff88ac' '0888fc04000000001976a91492461bde6283b461ece7ddf4dbf1e0a48bd113d888ac') OUTPUT_BLOCK_MESSAGES = ('50c30000000000001976a914e7c1345fc8f87c68170b3aa798a956c2fe6' 'a9eff88ac0888fc04000000001976a91492461bde6283b461ece7ddf4db' 'f1e0a48bd113d888ac000000000000000008006a0568656c6c6f0000000'
def test_dict_conversion(self): unspent = Unspent(10000, 7, 'script', 'txid', 0) assert unspent == Unspent.from_dict(unspent.to_dict())
b"hu\xa7\x1a]\xb6L\xff\xcb\x139k\x16=\x03\x9b\x1d\x93'\x82H\x91\x80C4v" b"\xa45**\xdd\x00\xeb\xb0\xd5\xc9LQ[r\xeb\x10\xf1\xfd\x8f?\x03\xb4/J+%[" b"\xfc\x9a\xa9\xe3"), b'\x8a', (b"\x88x9\x9d\x83\xec%\xc6'\xcf\xbfu?\xf9\xca6\x027>" b"\xacCz\xb2gaT\xa3\xc2\xda#\xad\xf3"), b'\x01\x00\x00\x00', 0) ] INPUT_BLOCK = ( '8878399d83ec25c627cfbf753ff9ca3602373eac437ab2676154a3c2da23adf30' '10000008a473044022045b743dbaaaa2cd1ef0b91346f5644e32dc70cde05091b' '3762d4cabb6ebd711a022074461056c26efeac0b448e7fa769773dd6e4436cde5' '05c8f6ca6303efe31f0950141043d5c2875c9bd116875a71a5db64cffcb13396b' '163d039b1d932782489180433476a4352a2add00ebb0d5c94c515b72eb10f1fd8' 'f3f03b42f4a2b255bfc9aa9e3ffffffff') UNSPENTS = [ Unspent( 83727960, 15, '76a91492461bde6283b461ece7ddf4dbf1e0a48bd113d888ac', 'f3ad23dac2a3546167b27a43ac3e370236caf93f75bfcf27c625ec839d397888', 1) ] OUTPUTS = [('n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi', 50000), ('mtrNwJxS1VyHYn3qBY1Qfsm3K3kh1mGRMS', 83658760)] MESSAGES = [(b'hello', 0), (b'there', 0)] OUTPUT_BLOCK = ( '50c30000000000001976a914e7c1345fc8f87c68170b3aa798a956c2fe6a9eff88ac' '0888fc04000000001976a91492461bde6283b461ece7ddf4dbf1e0a48bd113d888ac') OUTPUT_BLOCK_MESSAGES = ( '50c30000000000001976a914e7c1345fc8f87c68170b3aa798a956c2fe6' 'a9eff88ac0888fc04000000001976a91492461bde6283b461ece7ddf4db' 'f1e0a48bd113d888ac000000000000000008006a0568656c6c6f0000000' '00000000008006a057468657265') OUTPUT_BLOCK_MESSAGE_PUSHDATA = ( '50c30000000000001976a914e7c1345fc8f87c68170b3aa798a'
def test_equality(self): unspent1 = Unspent(10000, 7, 'script', 'txid', 0) unspent2 = Unspent(10000, 7, 'script', 'txid', 0) unspent3 = Unspent(50000, 7, 'script', 'txid', 0) assert unspent1 == unspent2 assert unspent1 != unspent3
def test_repr(self): unspent = Unspent(10000, 7, 'script', 'txid', 0) assert repr(unspent) == ("Unspent(amount=10000, confirmations=7, " "script='script', txid='txid', txindex=0)")