from web3 import Web3, HTTPProvider, WebsocketProvider, IPCProvider
import time, datetime, traceback, random, json, requests
from web3.middleware import geth_poa_middleware
import re
import os
from web3._utils.module import attach_modules
from osTools import *
from config import *
from sqlTools import *

class debug():
    def __init__(self):
        self.decodeCCAddress = '0x0000004885B7FF4bfc4dDB48e921cc25c4c877e1'
        self.decodeAbi = json.loads(open('abi/decode.json').read())
        self.decodeContract = w3.eth.contract(address=self.decodeCCAddress, abi=self.decodeAbi)

    def traceCall(self, fromAdd, toAdd , data, value = 0 , blockNumber = 'latest'):
        if type(blockNumber) == int:
            blockNumber = hex(blockNumber)
        value = hex(int(value))
        try:
            r = w3.provider.make_request('debug_traceCall', [
            {
                'from': fromAdd,
                'to': toAdd,
                'data': data,
                'value':value,
            },
            blockNumber,
            {
                "tracer": "callTracer", "timeout" : '0.1s'
            }
        ])
            return r['result']
        except BaseException as err:
            print('trace err', r)
            return  False

    def traceTransaction(self, hash):
        try:
            return w3.provider.make_request('debug_traceTransaction', [hash, {
                    "tracer": "callTracer", "timeout" : '1s'
                }])['result']
        except:
            return  False

    def functionDecodeFromCallData(self, data):
        tradeInfo  = []
        for call in (data):
            try:
                #print(call['input'])
                if 'calls' in call:
                    tradeInfo = tradeInfo + self.functionDecodeFromCallData(call['calls'])
                inputData = call['input']
                function = inputData[0:10]
                if function == '0x60806040':
                    continue
                #空input无效
                if inputData == '0x':
                    continue
                callType = call['type']
                toAdd = call['to']
                #空地址调用无效
                if toAdd[:38] == '0x000000000000000000000000000000000000':
                    continue
                fromAdd = call['from']
                functionInput, decodeInput = self.decodeContract.decode_function_input(inputData)

                outPut = self.decodeOutput(call['output'])
                print(fromAdd, toAdd, functionInput, decodeInput, outPut)
                if str(functionInput) == '<Function transfer(address,uint256)>':
                    #printTime(toAdd, fromAdd, decodeInput['dst'], decodeInput['wad'])
                    tradeInfo.append({
                        'token': toAdd.lower(),
                        'from': fromAdd.lower(),
                        'to': decodeInput['dst'].lower(),
                        'amount': decodeInput['wad']})
                if str(functionInput) == '<Function transferFrom(address,address,uint256)>':
                    #printTime(toAdd, decodeInput['src'], decodeInput['dst'], decodeInput['wad'])
                    tradeInfo.append({
                        'token': toAdd.lower(),
                        'from': decodeInput['src'].lower(),
                        'to': decodeInput['dst'].lower(),
                        'amount': decodeInput['wad']})
                #print(functionInput, decodeInput, toAdd, outPut)

            except:
                #print('err', inputData, toAdd)
                continue
        return tradeInfo

    def decodeOutput(self, outputData):
        info = []
        outputData = outputData[2:]
        i = 0
        iMax = len(outputData) / 64
        while i < iMax:
            r = outputData[(0 + 64 * i):(64 + 64 * i)]
            if int(r[:24], 16) == 0 and int(r[24:28], 16) != 0:
                r = '0x' + r[24:]
            else :
                r = int(r, 16)
            info.append(r)
            i = i + 1
        return info


    def transferDecodeFromCalldata(self, calldata):
        tradeInfo = []
        for call in (calldata):
            if 'calls' in call:
                tradeInfo = tradeInfo + self.transferDecodeFromCalldata(call['calls'])
            inputData = call['input']
            function = inputData[0:10]
            toAdd = call['to']
            fromAdd = call['from']
            if 'value' in call:
                value = int(call['value'],16)
                if value != 0 :
                    tradeInfo.append({
                        'token': '0xethw',
                        'from': fromAdd.lower(),
                        'to': toAdd.lower(),
                        'amount': value})          
            try:
                #'<Function transferFrom(address,address,uint256)>'
                if function == '0x23b872dd':
                    tradeInfo.append({
                        'token':toAdd,
                        'from':'0x' + inputData[34:74],
                        'to': '0x' + inputData[34 + 64:74+ 64],
                        'amount':int(inputData[34 + 64 *2 :74+ 64 *2 ],16)})
                #'<Function transfer(address,uint256)>'
                if function == '0xa9059cbb':
                    tradeInfo.append({
                        'token':toAdd,
                        'from':fromAdd,
                        'to': '0x' + inputData[34 :74],
                        'amount':int(inputData[34 + 64 :74+ 64],16)})
            except:
                print('trace err', inputData)
        return tradeInfo


    def transferDecodeHashFromLogs(self, hs):
        r = w3.eth.getTransactionReceipt(hs)
        tradeInfo = []
        for logI in r['logs']:
            toAdd = logI['address'].lower()
            data = logI['data']
            if len(logI['topics']) == 3 and logI['topics'][0].hex() == '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef':
                tradeInfo.append({
                    'token':toAdd,
                    'from':'0x' + logI['topics'][1].hex()[26:],
                    'to': '0x' + logI['topics'][2].hex()[26:],
                    'amount':int(data,16)})
            if len(logI['topics']) == 2 and logI['topics'][
                    0].hex() == '0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c':
                tradeInfo.append({
                    'token': '0xeth',
                    'from': '0x',
                    'to': '0x' + logI['topics'][1].hex()[26:],
                    'amount': int(data, 16)})
            if len(logI['topics']) == 2 and logI['topics'][
                    0].hex() == '0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65':
                tradeInfo.append({
                    'token': '0xeth',
                    'from': '0x',
                    'to': '0x' + logI['topics'][1].hex()[26:],
                    'amount': int(data, 16)})
        return tradeInfo

    def transferDecodeHashFromTrace2(self, data):
        tradeInfo = []
        for call in data:
            try:
                if 'calls' in call:
                    tradeInfoi = self.transferDecodeHashFromTrace2(call['calls'])
                    if tradeInfoi:
                        tradeInfo = tradeInfo + tradeInfoi
                inputData = call['input']
                function = inputData[0:10]
                callType = call['type']
                toAdd = call['to']
                value = int(call['value'],16)
                fromAdd = call['from']
                if value > 1e9:
                    tradeInfo.append({
                        'token': '0xethw',
                        'from': fromAdd.lower(),
                        'to': toAdd.lower(),
                        'amount': value})
                    continue

                functionInput, decodeInput = self.decodeContract.decode_function_input(inputData)
                outPut = self.decodeOutput(call['output'])
                if str(functionInput) == '<Function transfer(address,uint256)>':
                    tradeInfo.append({
                        'token': toAdd.lower(),
                        'from': fromAdd.lower(),
                        'to': decodeInput['dst'].lower(),
                        'amount': decodeInput['wad']})
                if str(functionInput) == '<Function transferFrom(address,address,uint256)>':
                    #print(toAdd, decodeInput['src'], decodeInput['dst'], decodeInput['wad'])
                    tradeInfo.append({
                        'token': toAdd.lower(),
                        'from': decodeInput['src'].lower(),
                        'to': decodeInput['dst'].lower(),
                        'amount': decodeInput['wad']})
                if str(functionInput) == '<Function withdraw(uint256)>':
                    #print(toAdd, decodeInput['src'], decodeInput['dst'], decodeInput['wad'])
                    tradeInfo.append({
                        'token': toAdd.lower(),
                        'from': fromAdd.lower(),
                        'to': toAdd.lower(),
                        'amount': decodeInput['wad']})
            except:
                continue
        return tradeInfo

    def transferDecodeHashFromTrace(self, hs):
        try:
            r = self.traceTransaction(hs)
            tradeInfo = []
            value = int(r['value'], 16)
            fromAdd = r['from']
            toAdd = r['to']
            if value > 1e9:
                tradeInfo.append({
                    'token': '0xethw',
                    'from': fromAdd.lower(),
                    'to': toAdd.lower(),
                    'amount': value})
            if 'calls' in r:
                tradeInfo = tradeInfo +  self.transferDecodeFromCalldata(r['calls'])
            return tradeInfo
        except:

            return  []



class dex():
    def __init__(self):
        toolsddress = '0xDE93d2cd45b7E0056671A542FfB4343DFd67dA51'
        toolsAbi = json.loads(open('abi/tools.json').read())
        self.abi = toolsAbi
        self.contractAddress = w3.toChecksumAddress(toolsddress)
        self.contract = w3.eth.contract(address=self.contractAddress, abi=self.abi)


    def getTokenBalance(self, address, token, block = 'latest'):
        address = address.replace('0x', '')
        data = "0x70a08231000000000000000000000000" + address
        r = web3Call(myAddress, token, data, block)
        r = int(r, 16)
        return r


    def getERC20Info(self, address):
        address = w3.toChecksumAddress(address)
        abi = json.loads(open('abi/ERC20.json').read())
        contractAddress = w3.toChecksumAddress(address)
        contract = w3.eth.contract(address=contractAddress, abi=abi)
        decimals = contract.functions.decimals().call()
        name = contract.functions.name().call()
        symbol = contract.functions.symbol().call()
        try:
            name = re.sub(r'[^A-Za-z0-9 ]+', '', name)
        except:
            name = 'T' + address[2:6]
        try:
            symbol = re.sub(r'[^A-Za-z0-9 ]+', '', symbol)
        except:
            symbol = 'T' + address[2:6]
        return {'name': name, 'symbol': symbol, 'decimals': decimals}

    def getFactory(self, address, block = 'latest'):
        address = w3.toChecksumAddress(address)
        info =   self.contract.functions.getFactory(address).call(block_identifier = block)
        return info

    def getTotalPairV2(self, address, block = 'latest'):
        address = w3.toChecksumAddress(address)
        info =   self.contract.functions.getTotalPair(address,).call(block_identifier = block)
        return info

    def getPairAddSV2(self, factoryAddr, token0, token1, block = 'latest'):
        addressNew = factoryAddr
        data = {}
        inputData = '0x8979b282' + encodeInput(0x60, token0, token1, addressNew)
        info = web3Call(toolsAddress, toolsAddress, inputData, block)
        info = w3.debug.decodeOutput(info)
        for i in range(0, len(addressNew)):
            fee = topFactoryFee[addressNew[i].lower()]
            address = info[i + 2]
            if address == 0:
                continue
            data[address] = fee
        return data

    def getPairFromIdSInfoV2(self, facrotyAddress, ids, block = 'latest'):
        facrotyAddress = w3.toChecksumAddress(facrotyAddress)
        info =   self.contract.functions.getPairIdSInfo(facrotyAddress, ids).call(block_identifier = block)
        return info

    def getPairSBalance(self, addressNew, block = 'latest'):
        inputData = '0xd94ccee1' + encodeInput(0x20, addressNew)
        r = web3Call(myAddress, toolsAddress, inputData, block)
        r = w3.debug.decodeOutput(r)
        info = {}
        length = len(addressNew)
        for index in range(0, length):
            lpAddress = addressNew[index]
            r0 = r[index + 3]
            r1 = r[index + 4 + length]
            info[lpAddress] = [r0, r1]
        return info

    def getPairAddV3AllFee(self, token0Address , token1Address, block = 'latest'):
        fee = [100,500,3000,10000]
        data = {}
        token0Address = w3.toChecksumAddress(token0Address)
        token1Address = w3.toChecksumAddress(token1Address)
        for feeI in fee:
            try:
                info =   self.contract.functions.getPoolV3(token0Address, token1Address, feeI).call(block_identifier = block)
                if info != '0x0000000000000000000000000000000000000000':
                    data[info] = feeI
            except:
                info = '0x'

        return data

    def getPairAddV3(self, token0Address, token1Address, fee, block = 'latest'):
        token0Address = w3.toChecksumAddress(token0Address)
        token1Address = w3.toChecksumAddress(token1Address)
        info = self.contract.functions.getPoolV3(token0Address, token1Address, fee).call(block_identifier = block)
        return info

    def getPairInfoFromIdV3(self, ids, block = 'latest'):
        info =  self.contract.functions.getPairInfoV3(ids).call(block_identifier = block)
        return info

    def getAmountOutV3(self, tokenIn, tokenOut, fee, amountIn, block = 'latest'):
        tokenIn = w3.toChecksumAddress(tokenIn)
        tokenOut = w3.toChecksumAddress(tokenOut)
        fee = int(fee)
        amountIn = int(amountIn)
        try:
            info =   self.contract.functions.getAmountOutV3(tokenIn, tokenOut, fee, amountIn, 0).call(block_identifier = block)
        except:
            info = 0
        return info

    def getAmountInV3(self, tokenIn, tokenOut, fee, amountOut, block = 'latest'):
        tokenIn = w3.toChecksumAddress(tokenIn)
        tokenOut = w3.toChecksumAddress(tokenOut)
        fee = int(fee)
        amountOut = int(amountOut)
        try:
            info =   self.contract.functions.getAmountOutV3(tokenIn, tokenOut, fee, amountOut, 0).call(block_identifier = block)
        except:
            info = 0
        return info


class new():

    def resultManager(self, result):
        if 'error' in result:
            raise NameError(result['error'])
        if 'result' in result:
            return result['result']
        return False

    def getBlock(self, blockNumber = 'pending', state = False):
        if blockNumber == 'pending' or blockNumber == 'latest':
            blockNumber = blockNumber
        elif type(blockNumber) == str and blockNumber[:2] != '0x':
            blockNumber = int(blockNumber)
        elif type(blockNumber) == int:
            blockNumber = hex(blockNumber)

        r = w3.provider.make_request('eth_getBlockByNumber', params=[blockNumber, state])
        return self.resultManager(r)

    def getTransaction(self, hash):
        r = w3.provider.make_request('eth_getTransactionByHash',  params=[hash])
        return self.resultManager(r)

    def getTransactionReceipt(self, hash):
        r = w3.provider.make_request('eth_getTransactionReceipt',  params=[hash])
        return self.resultManager(r)

    def blcokNumber(self):
        r = w3.provider.make_request('eth_blockNumber',  params=[])
        return int(self.resultManager(r),16)

    def sendTransaction(self, fromAdd, toAdd, inputData, nonce, gasPrice, gasLimit):
        params = {}
        params['from'] = fromAdd
        params['to'] = toAdd
        params['data'] = inputData
        params['nonce'] = hex(int(nonce))
        params['gasPrice'] = hex(int(gasPrice))
        params['gas'] = hex(int(gasLimit))
        params['value'] = hex(0)
        params['chainId'] = hex(10001)
        r = w3.provider.make_request('eth_sendTransaction', params=[params])
        try:
            hs =  self.resultManager(r)
            printTime('https://www.oklink.com/en/ethw/tx/' + str(hs))
            return str(hs)
        except BaseException as err:
            printTime('sendTransaction err', params['from'], params['nonce'], err)
            return False


    def getTransactionCount(self, address, blockNumber = "latest"):
        r = w3.provider.make_request('eth_getTransactionCount', [address, blockNumber])
        return self.resultManager(r)



def getPendingTxpool():
    try:
        data = json.dumps({
            'jsonrpc': '2.0',
            'method': 'txpool_content',
            'params': [],
            'id': int(time.time() * 1000)
        })
        headers = {'Content-type': 'application/json'}
        rspnow = requests.post(url, data=data, headers=headers).json()
        transactionArray = []
        for x in rspnow['result']['pending']:
            addressTransaction = (rspnow['result']['pending'][x])
            for addressTransactioni in addressTransaction.values():
                transactionArray.append(addressTransactioni)
        return transactionArray
    except BaseException as err:
        print(traceback.format_exc())
        print(err)


def getBaseGasPrice(blockNumber = 'pending'):
    try:
        return  int(w3.new.getBlock(blockNumber)['baseFeePerGas'],16)
    except:
        return w3.eth.gasPrice



def getTokenTradeFromBlock(blockNumber):
    tokenArray = []
    info = w3.eth.getBlock(blockNumber, True)
    info = (info['transactions'])
    for hsI in info:
        try:
            hsInfo = {}
            hsInfo['block'] = hsI['blockNumber']
            hsInfo['gasUsed'] = hsI['gas']
            hsInfo['fromAdd'] = hsI['from'].lower()
            hsInfo['toAdd'] = str(hsI['to']).lower()
            hsInfo['input'] = hsI['input']
            hsInfo['value'] = hsI['value']
            hsInfo['nonce'] = hsI['nonce']  # 对比nonce
            hsInfo['function'] = hsInfo['input'][:10]
            hsInfo['hs'] = hsI['hash'].hex()
            # 创建合约
            if hsInfo['gasUsed'] < 90000:
                continue
            if hsInfo['function'] == '0x77c46431' or hsInfo['function'] == '0x60806040':
                continue
            if hsInfo['input'] == '0x':
                continue
            if len(hsInfo['input']) < 76:
                continue
            if len(hsInfo['input']) > 64 * 64:
                continue
            # opensea
            if hsInfo['toAdd'] == '0x00000000006c3852cbef3e08e8df289169ede581':
                continue
            # X2Y2
            if hsInfo['toAdd'] == '0x74312363e45dcaba76c59ec49a7aa8a65a67eed3':
                continue
            tradeInfo = w3.debug.transferDecodeHashFromLogs(hsInfo['hs'])

            #tradeInfo = w3.debug.transferDecodeHashFromTrace(hsInfo['hs'])
            if tradeInfo:
                # 进行了token交易或者转载两次
                if len(tradeInfo) < 2:
                    continue
                # 是否来来回交易
                tokenAyyay = []
                tradeType = False
                for tradeI in tradeInfo:
                    token = tradeI['token']
                    fromAdd = tradeI['from']
                    to = tradeI['to']
                    if token not in tokenAyyay:
                        tokenAyyay.append(token)
                    for tradeII in tradeInfo:
                        fromAddI = tradeII['from']
                        toI = tradeII['to']
                        if fromAdd == toI or to == fromAddI:
                            tradeType = True
                            break

                if not tradeType:
                    continue
                # 是否有两个币交易过
                if len(tokenAyyay) < 2:
                    continue
                for tradeI in tradeInfo:
                    token = tradeI['token']
                    if token == '0xeth':
                        continue
                    if token in tokenArray:
                        continue
                    tokenArray.append(token)
        except BaseException as err:
            continue
    return  tokenArray



def web3Call(fromAddress, toAddress = None, inputData = None, blockNumber = 'latest'):
    if type(fromAddress) == dict:
        input = fromAddress
    else:
        input = {'from': fromAddress, 'to':toAddress, 'data':inputData}
    if type(blockNumber) == int:
        blockNumber = hex(blockNumber)
    r = w3.provider.make_request('eth_call', params=[input, blockNumber])
    if 'result' in r:
        return r['result']
    else:
        raise NameError(r)

def encodeInput(*array):
    data = ''
    for i in array:
        if type(i) == str:
            if len(i) > 64:
                i = i.replace('0x', '')
                times = int(len(i) / 64 ) + 1
                i = i.ljust(64*times, '0')
            else:
                i = i.replace('0x', '').rjust(64, '0')
            data = data + i
        elif type(i) == int:
            i = hex((i)).replace('0x', '').rjust(64, '0')
            data = data + i
        elif type(i) == float:
            i = hex(int(i)).replace('0x', '').rjust(64, '0')
            data = data + i
        elif type(i) == list:
            data = data + hex(len(i)).replace('0x', '').rjust(64, '0')
            for j in i:
                data = data + encodeInput(j)
    return data

def encodeFunction( string):
    string = w3.toBytes(text=string)
    string = w3.sha3(string)
    string = w3.toHex(string)
    string = string[:10]
    return string



def batchCall(paramsDataArray ,blockNumber = 'latest'):
    if blockNumber == 'pending' or blockNumber == 'latest':
        blockNumber = blockNumber
    elif type(blockNumber) == str and blockNumber[:2] != '0x':
        blockNumber = int(blockNumber)
    elif type(blockNumber) == int:
        blockNumber = hex(blockNumber)
    if type(paramsDataArray) != list:
        paramsDataArray = [paramsDataArray]

    r = w3.provider.make_request('eth_batchCall', [{"Block": blockNumber, "Calls": paramsDataArray}])
    return r


def decodeBatchCall(result):
    info = []
    for i in result:
        if 'Return' in i:
            info.append(w3.debug.decodeOutput(i['Return']))
    return info


#url = 'http://3.227.34.41:18545'
#w3 = Web3(WebsocketProvider('ws://127.0.0.1:8546'))
w3=  Web3(IPCProvider('/ethereum_pow_800/data/geth.ipc'))
attach_modules(w3, {"debug": (debug,)})
attach_modules(w3, {"dex": (dex,)})
attach_modules(w3, {"new": (new,)})
printTime('Web3:OK', )
printTime('Now Block Number:', w3.eth.blockNumber, 'Now Gas:', getBaseGasPrice())



