def __init__(self, prev_hash=None, prev_index=None, tx_output=None, coin_reference=None, state=CoinState.Unconfirmed): """ Create an instance. Args: prev_hash (neo.Core.UInt256): (Optional if coin_reference is given) the hash of the previous transaction. prev_index (UInt16/int): (Optional if coin_reference is given) index of the previous transaction. tx_output (neo.Core.Transaction.TransactionOutput): an object representing a transaction output. coin_reference (neo.Core.CoinReference): (Optional if prev_hash and prev_index are given) an object representing a single UTXO / transaction input. state (neo.Core.State.CoinState): """ self._address = None self._transaction = None if prev_hash and prev_index: self.Reference = CoinReference(prev_hash, prev_index) elif coin_reference: self.Reference = coin_reference else: self.Reference = None self.Output = tx_output self._state = state
def DeserializeExclusiveData(self, reader): """ Deserialize full object. Args: reader (neo.IO.BinaryReader): Raises: Exception: If the transaction type is incorrect or if there are no claims. """ self.Type = TransactionType.ClaimTransaction if self.Version != 0: raise Exception('Format Exception') numrefs = reader.ReadVarInt() claims = [] for i in range(0, numrefs): c = CoinReference() c.Deserialize(reader) claims.append(c) self.Claims = claims if len(self.Claims) == 0: raise Exception('Format Exception')
def __init__(self, prev_hash=None, prev_index=None, tx_output=None, coin_reference=None, state=CoinState.Unconfirmed): if prev_hash and prev_index: self.Reference = CoinReference(prev_hash, prev_index) elif coin_reference: self.Reference = coin_reference else: self.Reference = None self.Output = tx_output self._state = state
def DeserializeExclusiveData(self, reader): """ Deserialize full object. Args: reader (neo.IO.BinaryReader): """ if self.Type == b'\x02': # ClaimTransaction if self.Version != 0: raise FormatError('Invalid format') numrefs = reader.ReadVarInt() claims = [] for i in range(0, numrefs): c = CoinReference() c.Deserialize(reader) claims.append(c) self.Claims = claims if len(self.Claims) == 0: raise FormatError('Invalid format') elif self.Type == b'\xd1': # InvocationTransaction if self.Version > 1: raise FormatError('Invalid format') self.Script = reader.ReadVarBytes() if len(self.Script) == 0: raise FormatError('Invalid Format') if self.Version >= 1: self.Gas = reader.ReadFixed8() if self.Gas < Fixed8.Zero(): raise FormatError("Invalid Format") else: self.Gas = Fixed8(0) else: super(RawTransaction, self).DeserializeExclusiveData(reader=reader)
def DeserializeUnsignedWithoutType(self, reader): self.Version = reader.ReadByte() self.DeserializeExclusiveData(reader) self.Attributes = reader.ReadSerializableArray( 'neo.Core.TX.TransactionAttribute.TransactionAttribute') self.inputs = [ CoinReference(ref) for ref in reader.ReadSerializableArray( 'neo.Core.CoinReference.CoinReference') ] self.outputs = [ TransactionOutput(ref) for ref in reader.ReadSerializableArray( 'neo.Core.TX.Transaction.TransactionOutput') ]
def LoadCoins(self): coins = {} try: for coin in Coin.select(): reference = CoinReference(prev_hash=UInt256(coin.TxId), prev_index=coin.Index) output = TransactionOutput(UInt256(coin.AssetId), Fixed8(coin.Value), UInt160(coin.ScriptHash)) walletcoin = WalletCoin.CoinFromRef(reference, output, coin.State) coins[reference] = walletcoin except Exception as e: logger.error("could not load coins %s " % e) return coins
def LoadCoins(self): coins = [] for coin in Coin.select(): reference = CoinReference(prev_hash=coin.TxId, prev_index=coin.Index) output = TransactionOutput(coin.AssetId, coin.Value, coin.ScriptHash) walletcoin = WalletCoin.CoinFromRef(reference, output, coin.State) coins.append(walletcoin) return coins
class Coin(TrackableMixin): Output = None Reference = None _address = None _state = CoinState.Unconfirmed @staticmethod def CoinFromRef(coin_ref, tx_output, state=CoinState.Unconfirmed): coin = Coin(coin_reference=coin_ref, tx_output=tx_output, state=state) return coin def __init__(self, prev_hash=None, prev_index=None, tx_output=None, coin_reference=None, state=CoinState.Unconfirmed): if prev_hash and prev_index: self.Reference = CoinReference(prev_hash, prev_index) elif coin_reference: self.Reference = coin_reference else: self.Reference = None self.Output = tx_output self._state = state @property def Address(self): if self._address is None: self._address = Crypto.ToAddress(self.TXOutput.ScriptHash) return self._address @property def State(self): return self._state @State.setter def State(self, value): self._state = value def Equals(self, other): if other is None or other is not self: return False return True def ToJson(self): return { 'Reference': self.Reference.ToJson(), 'Output': self.Output.ToJson() }
def AddInputs(self, asset): """ Specify inputs for the transaction based on the asset to be sent. NOTE: Can be used multiple times if sending multiple assets (i.e. NEO and GAS). Args: asset: (str) the asset name or asset hash """ if not isinstance(asset, str): raise TypeError('Please enter the asset as a string.') if not self.BALANCE: raise RawTXError( 'Please specify a source address before adding inputs.') if asset[0:1] == "0x": asset == asset[2:] if asset.lower() == "neo": assetId = self.neo_asset_id elif asset == self.neo_asset_id: assetId = self.neo_asset_id elif asset.lower() == "gas": assetId = self.gas_asset_id elif asset == self.gas_asset_id: assetId = self.gas_asset_id else: raise AssetError( f'Asset {asset} not found. If trying to send tokens use the `buildTokenTransfer` function.' ) for asset in self.BALANCE: if assetId == asset['asset_hash']: if not asset['unspent']: raise AssetError('No unspent assets found.') for unspent in asset['unspent']: self.inputs.append( CoinReference(prev_hash=UInt256.ParseString( unspent['txid']), prev_index=unspent['n'])) if not self.inputs: raise AssetError( 'No matching assets found at the specified source address.')
def AddClaim(self, claim_addr, to_addr=None): """ Builds a claim transaction for the specified address. Args: claim_addr: (str) the address from which the claim is being constructed (e.g. 'AJQ6FoaSXDFzA6wLnyZ1nFN7SGSN2oNTc3'). NOTE: Claimed GAS is sent to the claim_addr by default to_addr: (str, optional) specify a different destination NEO address (e.g. 'AJQ6FoaSXDFzA6wLnyZ1nFN7SGSN2oNTc3') """ dest_scripthash = Helper.AddrStrToScriptHash( claim_addr) # also verifies if the address is valid self.SOURCE_SCRIPTHASH = dest_scripthash url = self._network + self._get_claimable + claim_addr res = requests.get(url=url) if not res.status_code == 200: raise NetworkError( 'Neoscan request failed. Please check your internet connection.' ) res = res.json() available = res["unclaimed"] if available == 0: raise AssetError( f"Address {claim_addr} has 0 unclaimed GAS. Please ensure the correct network is selected or specify a difference source address." ) for ref in res['claimable']: self.Claims.append( CoinReference(prev_hash=UInt256.ParseString(ref['txid']), prev_index=ref['n'])) if to_addr: dest_scripthash = Helper.AddrStrToScriptHash( to_addr) # also verifies if the address is valid self.outputs.append( TransactionOutput(AssetId=UInt256.ParseString(self.gas_asset_id), Value=Fixed8.FromDecimal(available), script_hash=dest_scripthash))
def ProcessNewBlock(self, block): """ Processes a block on the blockchain. This should be done in a sequential order, ie block 4 should be only processed after block 3. Args: block: (neo.Core.Block) a block on the blockchain. """ added = set() changed = set() deleted = set() try: # go through the list of transactions in the block and enumerate # over their outputs for tx in block.FullTransactions: for index, output in enumerate(tx.outputs): # check to see if the outputs in the tx are in this wallet state = self.CheckAddressState(output.ScriptHash) if state & AddressState.InWallet > 0: # if its in the wallet, check to see if the coin exists yet key = CoinReference(tx.Hash, index) # if it exists, update it, otherwise create a new one if key in self._coins.keys(): coin = self._coins[key] coin.State |= CoinState.Confirmed changed.add(coin) else: newcoin = Coin.CoinFromRef(coin_ref=key, tx_output=output, state=CoinState.Confirmed, transaction=tx) self._coins[key] = newcoin added.add(newcoin) if state & AddressState.WatchOnly > 0: self._coins[key].State |= CoinState.WatchOnly changed.add(self._coins[key]) # now iterate over the inputs of the tx and do the same for tx in block.FullTransactions: for input in tx.inputs: if input in self._coins.keys(): if self._coins[input].Output.AssetId == Blockchain.SystemShare().Hash: coin = self._coins[input] coin.State |= CoinState.Spent | CoinState.Confirmed changed.add(coin) else: deleted.add(self._coins[input]) del self._coins[input] for claimTx in [tx for tx in block.Transactions if tx.Type == TransactionType.ClaimTransaction]: for ref in claimTx.Claims: if ref in self._coins.keys(): deleted.add(self._coins[ref]) del self._coins[ref] # update the current height of the wallet self._current_height += 1 # in the case that another wallet implementation needs to do something # with the coins that have been changed ( ie persist to db ) this # method is called self.OnProcessNewBlock(block, added, changed, deleted) # this is not necessary at the moment, but any outside process # that wants to subscribe to the balance changed event could do # so from the BalanceChanged method if len(added) + len(deleted) + len(changed) > 0: self.BalanceChanged() except Exception as e: traceback.print_stack() traceback.print_exc() logger.error("could not process %s " % e)
def SaveTransaction(self, tx): """ This method is used to after a transaction has been made by this wallet. It updates the states of the coins In the wallet to reflect the new balance, but the coins remain in a ``CoinState.UNCONFIRMED`` state until The transaction has been processed by the network. The results of these updates can be used by overriding the ``OnSaveTransaction`` method, and, for example persisting the results to a database. Args: tx (Transaction): The transaction that has been made by this wallet. Returns: bool: True is successfully processes, otherwise False if input is not in the coin list, already spent or not confirmed. """ coins = self.GetCoins() changed = [] added = [] deleted = [] found_coin = False for input in tx.inputs: coin = None for coinref in coins: test_coin = coinref.Reference if test_coin == input: coin = coinref if coin is None: return False if coin.State & CoinState.Spent > 0: return False elif coin.State & CoinState.Confirmed == 0: return False coin.State |= CoinState.Spent coin.State &= ~CoinState.Confirmed changed.append(coin) for index, output in enumerate(tx.outputs): state = self.CheckAddressState(output.ScriptHash) key = CoinReference(tx.Hash, index) if state & AddressState.InWallet > 0: newcoin = Coin.CoinFromRef(coin_ref=key, tx_output=output, state=CoinState.Unconfirmed) self._coins[key] = newcoin if state & AddressState.WatchOnly > 0: newcoin.State |= CoinState.WatchOnly added.append(newcoin) if isinstance(tx, ClaimTransaction): # do claim stuff for claim in tx.Claims: claim_coin = self._coins[claim] claim_coin.State |= CoinState.Claimed claim_coin.State &= ~CoinState.Confirmed changed.append(claim_coin) self.OnSaveTransaction(tx, added, changed, deleted) return True
def example1(): neo_asset_id = Blockchain.GetSystemShare().Hash gas_asset_id = Blockchain.GetSystemCoin().Hash source_address = "AJQ6FoaSXDFzA6wLnyZ1nFN7SGSN2oNTc3" source_script_hash = address_to_scripthash(source_address) destination_address = "Ad9A1xPbuA5YBFr1XPznDwBwQzdckAjCev" destination_script_hash = address_to_scripthash(destination_address) # Let's start with building a ContractTransaction # The constructor already sets the correct `Type` and `Version` fields, so we do not have to worry about that contract_tx = ContractTransaction() # the ContractTransaction type has no special data, so we do not have to do anything there # Next we can add Attributes if we want. Again the various types are described in point 4. of the main link above # We will add a simple "description" contract_tx.Attributes.append( TransactionAttribute(usage=TransactionAttributeUsage.Description, data="My raw contract transaction description")) # The next field we will set are the inputs. The inputs neo-python expects are of the type ``CoinReference`` # To create these inputs we will need the usual `PrevHash` and `PrevIndex` values. # You can get the required data by using e.g. the neoscan.io API: https://api.neoscan.io/docs/index.html#api-v1-get-3 # The `PrevHash` field equals to neoscan's `balance.unspent[index].txid` key, and `PrevIndex` comes from `balance.unspent[index].n` # It is up to the transaction creator to make sure that the sum of all input ``value`` fields is equal to or bigger than the amount that's intended to be send # The below values are taken from data out of the `neo-test1-w.wallet` fixture wallet (a wallet neo-python uses for internal testing) input1 = CoinReference(prev_hash=UInt256(data=binascii.unhexlify( '949354ea0a8b57dfee1e257a1aedd1e0eea2e5837de145e8da9c0f101bfccc8e')), prev_index=1) contract_tx.inputs = [input1] # Next we will create the outputs. # The above input has a value of 50. We will create 2 outputs. # 1 output for sending 3 NEO to a specific destination address send_to_destination_output = TransactionOutput( AssetId=neo_asset_id, Value=Fixed8.FromDecimal(3), script_hash=destination_script_hash) # and a second output for sending the change back to ourselves return_change_output = TransactionOutput(AssetId=neo_asset_id, Value=Fixed8.FromDecimal(47), script_hash=source_script_hash) contract_tx.outputs = [send_to_destination_output, return_change_output] # at this point we've build our unsigned transaction and it's time to sign it before we get the raw output that we can send to the network via RPC # we need to create a Wallet instance for helping us with signing wallet = UserWallet.Create('path', to_aes_key('mypassword'), generate_default_key=False) # if you have a WIF use the following # this WIF comes from the `neo-test1-w.wallet` fixture wallet private_key = KeyPair.PrivateKeyFromWIF( "Ky94Rq8rb1z8UzTthYmy1ApbZa9xsKTvQCiuGUZJZbaDJZdkvLRV") # if you have a NEP2 encrypted key use the following instead # private_key = KeyPair.PrivateKeyFromNEP2("NEP2 key string", "password string") # we add the key to our wallet wallet.CreateKey(private_key) # and now we're ready to sign context = ContractParametersContext(contract_tx) wallet.Sign(context) contract_tx.scripts = context.GetScripts() print(contract_tx.Hash.ToString()) raw_tx = contract_tx.ToArray()
def ProcessNewBlock(self, block): added = set() changed = set() deleted = set() self._lock.acquire() try: for tx in block.Transactions: for index, output in enumerate(tx.outputs): state = self.CheckAddressState(output.ScriptHash) if state > 0: key = CoinReference(tx.Hash, index) found = False for coin in self._coins: if coin.CoinRef.Equals(key): coin.State |= CoinState.Confirmed changed.add(coin.CoinRef) found = True if not found: newcoin = Coin.CoinFromRef( key, output, state=CoinState.Confirmed) self._coins.append(newcoin) added.add(newcoin.CoinRef) if state == AddressState.WatchOnly: for coin in self._coins: if coin.CoinRef.Equals(key): coin.State |= CoinState.WatchOnly changed.add(coin.CoinRef) for tx in block.Transactions: for input in tx.inputs: for coin in self._coins: if coin.CoinRef.Equals(input): if coin.TXOutput.AssetId == Blockchain.SystemShare( ).Hash(): coin.State |= CoinState.Spent | CoinState.Confirmed changed.add(coin.CoinRef) else: self._coins.remove(coin) deleted.add(coin.CoinRef) for claimTx in [ tx for tx in block.Transactions if tx.Type == TransactionType.ClaimTransaction ]: for ref in claimTx.Claims: if ref in self._coins: self._coins.remove(ref) deleted.add(ref) self._current_height += 1 self.OnProcessNewBlock(block, added, changed, deleted) if len(added) + len(deleted) + len(changed) > 0: self.BalanceChanged() except Exception as e: print("could not process: %s " % e) finally: self._lock.release()
def __init__(self, prev_hash=None, prev_index=None, tx_output=None, state=CoinState.Unconfirmed): self.CoinRef = CoinReference(prev_hash, prev_index) self.TXOutput = tx_output self._state = state
def ProcessNewBlock(self, block): added = set() changed = set() deleted = set() try: for tx in block.FullTransactions: for index, output in enumerate(tx.outputs): state = self.CheckAddressState(output.ScriptHash) if state & AddressState.InWallet > 0: key = CoinReference(tx.Hash, index) if key in self._coins.keys(): coin = self._coins[key] coin.State |= CoinState.Confirmed changed.add(coin) else: newcoin = Coin.CoinFromRef( coin_ref=key, tx_output=output, state=CoinState.Confirmed) self._coins[key] = newcoin added.add(newcoin) if state & AddressState.WatchOnly > 0: self._coins[key].State |= CoinState.WatchOnly changed.add(self._coins[key]) for tx in block.FullTransactions: for input in tx.inputs: if input in self._coins.keys(): if self._coins[input].Output.AssetId.ToBytes( ) == Blockchain.SystemShare().Hash.ToBytes(): self._coins[ input].State |= CoinState.Spent | CoinState.Confirmed changed.add(self._coins[input]) else: deleted.add(self._coins[input]) del self._coins[input] for claimTx in [ tx for tx in block.Transactions if tx.Type == TransactionType.ClaimTransaction ]: for ref in claimTx.Claims: if ref in self._coins.keys(): deleted.add(self._coins[ref]) del self._coins[ref] self._current_height += 1 self.OnProcessNewBlock(block, added, changed, deleted) if len(added) + len(deleted) + len(changed) > 0: self.BalanceChanged() except Exception as e: traceback.print_stack() traceback.print_exc() print("could not process %s " % e)
class Coin(TrackableMixin): @staticmethod def CoinFromRef(coin_ref, tx_output, state=CoinState.Unconfirmed, transaction=None): """ Get a Coin object using a CoinReference. Args: coin_ref (neo.Core.CoinReference): an object representing a single UTXO / transaction input. tx_output (neo.Core.Transaction.TransactionOutput): an object representing a transaction output. state (neo.Core.State.CoinState): Returns: Coin: self. """ coin = Coin(coin_reference=coin_ref, tx_output=tx_output, state=state) coin._transaction = transaction return coin def __init__(self, prev_hash=None, prev_index=None, tx_output=None, coin_reference=None, state=CoinState.Unconfirmed): """ Create an instance. Args: prev_hash (neo.Core.UInt256): (Optional if coin_reference is given) the hash of the previous transaction. prev_index (UInt16/int): (Optional if coin_reference is given) index of the previous transaction. tx_output (neo.Core.Transaction.TransactionOutput): an object representing a transaction output. coin_reference (neo.Core.CoinReference): (Optional if prev_hash and prev_index are given) an object representing a single UTXO / transaction input. state (neo.Core.State.CoinState): """ self._address = None self._transaction = None if prev_hash and prev_index: self.Reference = CoinReference(prev_hash, prev_index) elif coin_reference: self.Reference = coin_reference else: self.Reference = None self.Output = tx_output self._state = state @property def Transaction(self): return self._transaction @property def Address(self): """ Get the wallet address associated with the coin. Returns: str: base58 encoded string representing the wallet address. """ if self._address is None: self._address = Crypto.ToAddress(self.Output.ScriptHash) return self._address @property def State(self): """ Get the coin state. Returns: neo.Core.State.CoinState: the coins state. """ return self._state @State.setter def State(self, value): """ Set the coin state. Args: value (neo.Core.State.CoinState): the new coin state. """ self._state = value def Equals(self, other): """ Compare `other` to self. Args: other (object): Returns: True if object is equal to self. False otherwise. """ if other is None or other is not self or type(other) is not Coin: return False return True def __hash__(self): return int.from_bytes( self.Reference.PrevHash.Data + bytearray(self.Reference.PrevIndex), 'little') def __eq__(self, other): return self.Equals(other) def RefToBytes(self): vin_index = bytearray(self.Reference.PrevIndex.to_bytes(1, 'little')) vin_tx = self.Reference.PrevHash.Data vindata = vin_tx + vin_index return vindata def ToJson(self): """ Convert object members to a dictionary that can be parsed as JSON. Returns: dict: """ return { 'Reference': self.Reference.ToJson(), 'Output': self.Output.ToJson(index=0), }