Exemplo n.º 1
0
    def __init__(
        self,
        wallet_bin_path,
        datastore_path,
        wallet_password,
    ):
        """
        Manage a Parity wallet for Ethereum.

        The wallet key file must be in `datastore_path/keys/ethereum/`.
	Multiple "accounts" (wallet key files) are not supported - see the `accounts` property.

        :param wallet_bin_path: Path to Parity wallet executable
        :param datastore_path: Path to datastore directory (which includes wallet files and blockchain)
        :param wallet_password: Password to enter for decrypting the account
        """

        self.wallet_bin_path = wallet_bin_path
        self.datastore_path = datastore_path
        self.wallet_password = wallet_password

        self._server = None
        self._accounts = None
        self._block_timestamps = {}
        self.ec = EtherChain()
Exemplo n.º 2
0
class EtherchainAccountTest(unittest.TestCase):
    def setUp(self):
        self.etherchain = EtherChain()

    def test_tx_pending(self):
        self.assertIn(
            "recordsTotal",
            self.etherchain.transactions_pending(start=0, length=1).keys())

    def test_txs(self):
        self.assertIn("recordsTotal",
                      self.etherchain.transactions(start=0, length=1).keys())
        # yeah super lazy :p

    def test_blocks(self):
        self.assertIn("recordsTotal",
                      self.etherchain.blocks(start=0, length=1).keys())
        # we'll test content later

    def test_accounts(self):
        self.assertIn("recordsTotal",
                      self.etherchain.accounts(start=0, length=1).keys())

    def test_contracts(self):
        self.assertIn("processed",
                      self.etherchain.contracts(start=0, length=1).keys())
 def __init__(self, proxies={}):
     self.config = configparser.ConfigParser()
     self.config.read('config.ini')
     self.session = UserAgent(baseurl="https://etherscan.io",
                              retry=5,
                              retrydelay=8,
                              proxies=proxies)
     self.ec = EtherChain()
     self.soup = None
Exemplo n.º 4
0
def iter_contracts(start=0, length=100):
    s = EtherChain(proxies={})
    while True:
        import time
        time.sleep(30)
        s = EtherChain()
        contracts = s.contracts(start=start, length=length)
        for contract in contracts["data"]:
            yield contract
        start += contracts["processed"]
Exemplo n.º 5
0
 def setUp(self):
     self.etherchain = EtherChain()
class EtherScanIoApi(object):
    """
    Base EtherScan.io Api implementation
    TODO:
    - implement a script (client) that runs all the python script
    - fix the issue about SC with several classes. The issue is at 03 script
    - Fix the issue about solmet, for some address the tool is not able to get statistic at 02 and it brokes 03
    - fix _get_contract_name
    """
    def __init__(self, proxies={}):
        self.config = configparser.ConfigParser()
        self.config.read('config.ini')
        self.session = UserAgent(baseurl="https://etherscan.io",
                                 retry=5,
                                 retrydelay=8,
                                 proxies=proxies)
        self.ec = EtherChain()
        self.soup = None

    def get_contracts_from_block(self, block):
        soup = BeautifulSoup(requests.get('https://etherscan.io/txs?block=' +
                                          str(block)).text,
                             features="html.parser")
        addresses = soup.select("i[title='Contract']")
        for address in list(
                set(
                    map(
                        lambda x: x.findNext('a')['href'].replace(
                            '/address/', ''), addresses))):
            if not self._is_new_address(address):
                continue
            describe_contract = self.ec.account(address).describe_contract
            self._set_soup(address)
            contract = {
                'address': address,
                'name': self._get_contract_name(),
                'compiler': None,
                'compiler_version': self._get_compiler_version(),
                'balance': describe_contract.__self__['balance'],
                'txcount': describe_contract.__self__['txreceived'],
                'firstseen': describe_contract.__self__['firstseen'],
                'lastseen': describe_contract.__self__['lastseen']
            }
            yield contract

    def get_contracts_from_etherscan(self, start=0, end=None):
        page = start

        while not end or page <= end:
            resp = self.session.get("/contractsVerified/%d" % page).text
            page, lastpage = re.findall(
                r'Page <.*>(\d+)</.*> of <.*>(\d+)</.*>', resp)[0]
            page, lastpage = int(page), int(lastpage)
            if not end:
                end = lastpage
            rows = self._parse_tbodies(resp)[0]  # only use first tbody
            for col in rows:
                address = self._extract_text_from_html(col[0]).split(" ", 1)[0]
                if not self._is_new_address(address):
                    continue
                describe_contract = self.ec.account(address).describe_contract
                firstseen = describe_contract.__self__['firstseen']
                lastseen = describe_contract.__self__['lastseen']
                contract = {
                    'address':
                    address,
                    'name':
                    self._extract_text_from_html(col[1]),
                    'compiler':
                    self._extract_text_from_html(col[2]),
                    'compiler_version':
                    self._extract_text_from_html(col[3]),
                    'balance':
                    self._get_balance(self._extract_text_from_html(col[4])),
                    'txcount':
                    self._extract_text_from_html(col[5]),
                    'firstseen':
                    firstseen,
                    'lastseen':
                    firstseen
                }
                yield contract
            page += 1

    def write_etherChain_fn(self, contracts=[]):
        amount = 100
        for nr, c in enumerate(contracts):
            with open(self.config['DEFAULT']['etherChain_fn'], 'a+') as f:
                print("got contract: %s" % c)

                f_path = os.path.join(self.config['DEFAULT']['output_path'],
                                      '%s.sol' % (c["address"]))
                try:
                    source = self._get_contract_source(c["address"]).strip()
                    if not len(source):
                        raise Exception(c)
                except Exception as e:
                    continue

                f.write("%s\n" % c)
                with open(f_path, "wb") as f:
                    f.write(bytes(source, "utf8"))

                print("[%d/%d] dumped --> %s (%-20s) -> %s" %
                      (nr, amount, c["address"], c["name"], f_path))

                nr += 1
                if nr >= amount:
                    break

    def _get_contract_source(self, address):
        import time
        e = None
        for _ in range(5):
            resp = self.session.get("/address/%s" % address).text
            print("/address/%s" % address)
            if "You have reached your maximum request limit for this resource. Please try again later" in resp:
                print("[[THROTTELING]]")
                time.sleep(1 + 2.5 * _)
                continue
            try:
                print(
                    "=======================================================")
                print(address)
                resp = resp.split(
                    "<pre class='js-sourcecopyarea editor' id='editor' style='margin-top: 5px;'>",
                    1)[1]
                resp = resp.split("</pre><br>", 1)[0]
                return resp.replace("&lt;", "<").replace("&gt;", ">").replace(
                    "&le;",
                    "<=").replace("&ge;",
                                  ">=").replace("&amp;",
                                                "&").replace("&vert;", "|")
            except:
                print(traceback.format_exc())
                time.sleep(1 + 2.5 * _)
                break

    def _is_new_address(self, address):
        if (address not in open(self.config['DEFAULT']['smec_fn']).read()):
            return True
        return False

    def _set_soup(self, address):
        url = address.join(['https://etherscan.io/address/', '#code'])
        self.soup = BeautifulSoup(requests.get(url).text, 'html.parser')

    def _get_compiler_version(self):
        try:
            str = self.soup.findAll('span',
                                    text=re.compile('v0.'))[0].contents[0]
            return re.search('v(\d{1,2}.\d{1,2}.\d{1,2})', str)[1]
        except IndexError:
            return None

    def _get_contract_name(self):
        try:
            return self.soup.find(lambda tag: tag.name == "span" and "Name" in
                                  tag.text).parent.find_next(
                                      'td').contents[0].strip()
        except:
            return None

    def _get_addresses_from_fn(self, fn):
        try:
            fp = open(fn)
            return list(filter(None, map(lambda x: x.strip(), fp.readlines())))
        finally:
            fp.close()

    def _extract_text_from_html(self, s):
        return re.sub('<[^<]+?>', '', s).strip()

    def _extract_hexstr_from_html_attrib(self, s):
        return ''.join(re.findall(r".+/([^']+)'",
                                  s)) if ">" in s and "</" in s else s

    def _get_balance(self, balance):
        try:
            return int(re.sub('[a-zA-Z]', '', balance))
        except ValueError:
            return None

    def _get_pageable_data(self, path, start=0, length=10):
        params = {
            "start": start,
            "length": length,
        }
        resp = self.session.get(path, params=params).json()
        # cleanup HTML from response
        for item in resp['data']:
            keys = item.keys()
            for san_k in set(keys).intersection(
                    set(("account", "blocknumber", "type", "direction"))):
                item[san_k] = self._extract_text_from_html(item[san_k])
            for san_k in set(keys).intersection(
                ("parenthash", "from", "to", "address")):
                item[san_k] = self._extract_hexstr_from_html_attrib(
                    item[san_k])
        return resp

    def _parse_tbodies(self, data):
        tbodies = []
        for tbody in re.findall(r"<tbody.*?>(.+?)</tbody>", data, re.DOTALL):
            rows = []
            for tr in re.findall(r"<tr.*?>(.+?)</tr>", tbody):
                rows.append(re.findall(r"<td.*?>(.+?)</td>", tr))
            tbodies.append(rows)
        return tbodies
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from pyetherchain.pyetherchain import EtherChain

e = EtherChain()

# getting an accoutn object
ac = e.account("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")

# show the account object (json), retrieve the source if available, show transactions
print(ac)
print(ac.source)
print(ac.swarm_hash)
print(ac.transactions())
print(ac.history())
# access the charts api
print(e.charts.market_cap())
# retrieve hardfork information
print(e.hardforks())

# list pending transaactions (takes arguments)
# print(e.transactions_pending())
# describe the constructor invokation and other transaction in a human readable way
contract = e.account("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")
# print("constructor:{}".format(contract.abi.describe_constructor(contract.constructor_args)))
# for tx in contract.transactions(direction="in", length=10000)["data"]:
#     tx_obj = e.transaction(tx["parenthash"])[0]
#     print("transaction: [IN] <== %s : %s".format((str(tx_obj["hash"]), str(contract.abi.describe_input(tx_obj["input"])))))

# or just shorthand dump contract with extra info
Exemplo n.º 8
0
import pymongo
import datetime
import requests

from pymongo import MongoClient
from HTMLParser import HTMLParser
from pyetherchain.pyetherchain import EtherChain
from pyetherchain.pyetherchain import EtherChainApi

DEBUG_MODE = False
KEYWORDS = [
    "scam", "honeypot", "honey-pot", "honey pot", "honey trap", "honey",
    "trap", "fraud"
]

etherchain = EtherChain()
etherchain_api = EtherChainApi()
prices = etherchain_api.get_stats_price_usd()


def get_one_eth_to_usd(timestamp):
    one_eth_to_usd = prices[-1]["value"]
    for index, price in enumerate(prices):
        if index < len(prices) - 1:
            if prices[index]["time"] <= timestamp and timestamp <= prices[
                    index + 1]["time"]:
                one_eth_to_usd = prices[index]["value"]
                break
    return one_eth_to_usd

def main():
    address = sys.argv[1]
    print(EtherChain().account(address).code)
Exemplo n.º 10
0
 def __init__(self, proxies={}):
     self.session = UserAgent(baseurl="https://etherscan.io",
                              retry=5,
                              retrydelay=8,
                              proxies=proxies)
     self.ec = EtherChain()
Exemplo n.º 11
0
class EtherScanIoApi(object):
    """
    Base EtherScan.io Api implementation
    """
    def __init__(self, proxies={}):
        self.session = UserAgent(baseurl="https://etherscan.io",
                                 retry=5,
                                 retrydelay=8,
                                 proxies=proxies)
        self.ec = EtherChain()

    def get_contracts(self, start=0, end=None):
        page = start

        while not end or page <= end:
            resp = self.session.get("/contractsVerified/%d" % page).text
            page, lastpage = re.findall(
                r'Page <.*>(\d+)</.*> of <.*>(\d+)</.*>', resp)[0]
            page, lastpage = int(page), int(lastpage)
            if not end:
                end = lastpage
            rows = self._parse_tbodies(resp)[0]  # only use first tbody
            for col in rows:
                address = self._extract_text_from_html(col[0]).split(" ", 1)[0]
                describe_contract = self.ec.account(address).describe_contract
                firstseen = describe_contract.__self__['firstseen']
                lastseen = describe_contract.__self__['lastseen']
                contract = {
                    'address':
                    address,
                    'name':
                    self._extract_text_from_html(col[1]),
                    'compiler':
                    self._extract_text_from_html(col[2]),
                    'compiler_version':
                    self._extract_text_from_html(col[3]),
                    'balance':
                    self._get_balance(self._extract_text_from_html(col[4])),
                    'txcount':
                    self._extract_text_from_html(col[5]),
                    'firstseen':
                    firstseen,
                    'lastseen':
                    firstseen
                }
                yield contract
            page += 1

    def get_contract_source(self, address):
        import time
        e = None
        for _ in range(20):
            resp = self.session.get("/address/%s" % address).text
            if "You have reached your maximum request limit for this resource. Please try again later" in resp:
                print("[[THROTTELING]]")
                time.sleep(1 + 2.5 * _)
                continue
            try:
                print(
                    "=======================================================")
                print(address)
                resp = resp.split(
                    "</div><pre class='js-sourcecopyarea' id='editor' style='margin-top: 5px;'>",
                    1)[1]
                resp = resp.split("</pre><br>", 1)[0]
                return resp.replace("&lt;", "<").replace("&gt;", ">").replace(
                    "&le;",
                    "<=").replace("&ge;",
                                  ">=").replace("&amp;",
                                                "&").replace("&vert;", "|")
            except Exception as e:
                print(e)
                time.sleep(1 + 2.5 * _)
                continue
        raise e

    def _extract_text_from_html(self, s):
        return re.sub('<[^<]+?>', '', s).strip()

    def _extract_hexstr_from_html_attrib(self, s):
        return ''.join(re.findall(r".+/([^']+)'",
                                  s)) if ">" in s and "</" in s else s

    def _get_balance(self, balance):
        try:
            return int(re.sub('[a-zA-Z]', '', balance))
        except ValueError:
            return None

    def _get_pageable_data(self, path, start=0, length=10):
        params = {
            "start": start,
            "length": length,
        }
        resp = self.session.get(path, params=params).json()
        # cleanup HTML from response
        for item in resp['data']:
            keys = item.keys()
            for san_k in set(keys).intersection(
                    set(("account", "blocknumber", "type", "direction"))):
                item[san_k] = self._extract_text_from_html(item[san_k])
            for san_k in set(keys).intersection(
                ("parenthash", "from", "to", "address")):
                item[san_k] = self._extract_hexstr_from_html_attrib(
                    item[san_k])
        return resp

    def _parse_tbodies(self, data):
        tbodies = []
        for tbody in re.findall(r"<tbody.*?>(.+?)</tbody>", data, re.DOTALL):
            rows = []
            for tr in re.findall(r"<tr.*?>(.+?)</tr>", tbody):
                rows.append(re.findall(r"<td.*?>(.+?)</td>", tr))
            tbodies.append(rows)
        return tbodies
Exemplo n.º 12
0
class PiggyETH:
    TXN_GAS_LIMIT = 21000

    def __init__(
        self,
        wallet_bin_path,
        datastore_path,
        wallet_password,
    ):
        """
        Manage a Parity wallet for Ethereum.

        The wallet key file must be in `datastore_path/keys/ethereum/`.
	Multiple "accounts" (wallet key files) are not supported - see the `accounts` property.

        :param wallet_bin_path: Path to Parity wallet executable
        :param datastore_path: Path to datastore directory (which includes wallet files and blockchain)
        :param wallet_password: Password to enter for decrypting the account
        """

        self.wallet_bin_path = wallet_bin_path
        self.datastore_path = datastore_path
        self.wallet_password = wallet_password

        self._server = None
        self._accounts = None
        self._block_timestamps = {}
        self.ec = EtherChain()

    @property
    def server(self):
        if self._server is None:
            ipc = Web3.IPCProvider(
                os.path.join(self.datastore_path, 'jsonrpc.ipc'))

            self._server = Web3(ipc)
        return self._server

    @property
    def accounts(self):
        if self._accounts is None:
            self._accounts = self.server.eth.accounts

            # We only handle 1 account (address) as of now.
            # In order to support multiple accounts you need to alter:
            # `get_receive_address`, `get_balance`, `transactions_since`, `perform_transaction`
            assert len(self.accounts) == 1
        return self._accounts

    def start_server(self):
        if self._rpc_loaded():
            return

        command = [
            self.wallet_bin_path,
            '-d',
            self.datastore_path,
            '--log-file',
            os.path.join(os.getcwd(), self.datastore_path, 'parity_log.txt'),
            '--no-ancient-blocks',
            '--no-ws',
            '--no-jsonrpc',
            '--ipc-apis=web3,eth,personal',  # We need personal to actually perform a transaction
            '--warp-barrier',
            '5842205',
        ]

        logger.info("Starting ETH wallet: {}".format(' '.join(command)))
        popen_spawn.PopenSpawn(command)

        wait_for_success(self._rpc_loaded, 'ETH RPC')

    def stop_server(self):
        """Use pkill to kill the parity wallet."""
        p = pexpect.spawn('/usr/bin/pkill', ['-f', self.wallet_bin_path])
        p.wait()
        if p.status is not 0:
            raise ValueError('Error pkilling ETH:\n{}'.format(p.read()))

    def _rpc_loaded(self):
        """Attempt a connection to the RPC"""
        try:
            self.server.eth.getBlock(self.server.eth.blockNumber)
            return True
        except (web3.utils.threads.Timeout, ConnectionRefusedError,
                FileNotFoundError):
            return False

    def _from_wei(self, wei):
        """Convert wei to ether"""
        return self.server.fromWei(wei, 'ether')

    def get_receive_address(self):
        return self.accounts[0]

    def get_balance(self):
        wei = self.server.eth.getBalance(self.get_receive_address(), 'latest')
        return self._from_wei(wei)

    def suggest_miner_fee(self):
        gas_needed = self.server.eth.estimateGas({
            'from':
            self.get_receive_address(),
            'to':
            self.get_receive_address(),
            'value':
            1
        })

        # Get the gas price in the latest unconfirmed block
        gas_price = self.server.eth.gasPrice

        return self._from_wei(gas_price * gas_needed)

    def transactions_since(self, since_unix_time, only_look_at=10):
        """
        Gets transactions since specified unix timestamp.
        We use Etherchain.org's server, because it's impractical to create an index for ETH
            transactions ourselves.
        Caveats:
            - this has privacy implications (you reveal your ETH address to Etherchain)
            - only looks at last `only_look_at` transactions to avoid abusing the server
            - only shows 5 decimals after the point (minimum value 0.00001 ETH)
        """

        acc = self.ec.account(self.accounts[0])
        raw_txns = acc.transactions(length=only_look_at, direction='in')
        return self._process_history(raw_txns, since_unix_time,
                                     self._timestamp_getter)

    @classmethod
    def _process_history(cls, raw_txns, since_unix_time, timestamp_getter):
        def to_eth(value_string):
            value, unit = value_string.split()
            if unit != 'ETH':
                raise ValueError(
                    'Txn not valued in ETH: {}'.format(value_string))
            return Decimal(value)

        txn_list = [
            {
                'txid': tx['parenthash'],
                'time': timestamp_getter(int(tx['blocknumber'])),
                'value': to_eth(tx['value'])
            } for tx in raw_txns['data']
            if tx['direction'].lower() == 'in' and tx['value'] != '0 ETH'
        ]

        return [
            tx for tx in txn_list
            if tx['value'] > 0 and tx['time'] >= since_unix_time
        ]

    def _timestamp_getter(self, blocknumber):
        if blocknumber not in self._block_timestamps:
            # Try the local Eth server
            logger.debug('Retrieving block #{}...'.format(blocknumber))
            block = self.server.eth.getBlock(blocknumber,
                                             full_transactions=False)
            if block:
                self._block_timestamps[blocknumber] = block.timestamp
            else:
                # Fallback to Etherchain.org
                block = self.ec.api.get_block(3865982)
                ts = self._parse_utc_time(block['time'])
                self._block_timestamps[blocknumber] = ts

        return self._block_timestamps[blocknumber]

    @classmethod
    def _parse_utc_time(cls, timestr):
        dt = datetime.datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S.%fZ")
        return calendar.timegm(dt.timetuple())

    def perform_transaction(self, net_amount, miner_fee, target_address):
        """
        Send Ether to target_address (total cost: net_amount + miner_fee)
        """
        assert isinstance(net_amount, Decimal)
        assert isinstance(miner_fee, Decimal)

        net_amount_wei = self.server.toWei(net_amount, 'ether')
        if net_amount_wei != net_amount * Decimal('1e18'):
            raise ValueError('net_amount is not an integer multiple of wei.')

        gas_price_wei = self._get_gas_price(miner_fee)

        txid = self.server.personal.sendTransaction(
            {
                'to': target_address,
                'gas': self.TXN_GAS_LIMIT,
                'gasPrice': gas_price_wei,
                'value': net_amount_wei,
            }, self.wallet_password)

        return txid

    def _get_gas_price(self, eth_to_spend):
        assert isinstance(eth_to_spend, Decimal)

        wei_to_spend = self.server.toWei(eth_to_spend, 'ether')
        gas_price_wei = wei_to_spend / self.TXN_GAS_LIMIT
        if gas_price_wei != int(gas_price_wei):
            raise ValueError(
                'Transaction fee is not an integer multiple of the gas price.')

        return int(gas_price_wei)