Python scripts

03 April 2020

Python functions and below Etherdoek's ABI.
Functions for creating the canvas from the blockchain and for buying and chaning pixels.
Hex values can be entered as a string in the buy and change functions. For example '000000', 'FFFFFF', 'E02619'.

import web3
import json

import pandas as pd
import numpy as np
import datetime as dt

from PIL import Image

INFURA_KEY = 'insert key here'
FROM_BLOCK = 9_825_620
ADDRESS_ETHERDOEK = '0x67B4Ef189A68133aB3a726b85d289B912C23e0B0'

w3 = web3.Web3(web3.HTTPProvider('https://mainnet.infura.io/v3/' + INFURA_KEY))
w3_events = web3.Web3(web3.WebsocketProvider('wss://mainnet.infura.io/ws/v3/' + INFURA_KEY))

gas_price = '5' # Check https://ethgasstation.info
chain_id = 1

with open('abi_etherdoek.json') as json_file: # ABI can be found at https://www.etherdoek.com/python_scripts
    abi_etherdoek = json.load(json_file)

contract_etherdoek = w3.eth.contract(address=ADDRESS_ETHERDOEK, abi=abi_etherdoek)
contract_etherdoek_events = w3_events.eth.contract(address=ADDRESS_ETHERDOEK, abi=abi_etherdoek)


def etherdoek_buy_pixel(wallet_address, wallet_private_key, location, hex_value, pixel_price=5_000_000_000_000_000):
    nonce = w3.eth.getTransactionCount(wallet_address)
    txn_dict = contract_etherdoek.functions.buy_pixel(location,
                                                      hex_value).buildTransaction({'gasPrice': w3.toWei(gas_price, 'gwei'),
                                                                                   'nonce': nonce,
                                                                                   'value': pixel_price, 
                                                                                   'chainId': chain_id,
                                                                                   'gas': 500_000})
    signed_txn = w3.eth.account.signTransaction(txn_dict, wallet_private_key)
    txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
    txn_receipt = None
    count = 0
    while txn_receipt is None:
        try:
            txn_receipt = w3.eth.getTransactionReceipt(txn_hash)
        except:
            time.sleep(1)

    if txn_receipt is None:
        return {'status': 'failed', 'error': 'timeout'}
    else:
        return {'status': 'added', 'txn_receipt': txn_receipt}
    

def etherdoek_change_pixel(wallet_address, wallet_private_key, location, hex_value, change_price=1_000_000_000_000_000):
    nonce = w3.eth.getTransactionCount(wallet_address)
    txn_dict = contract_etherdoek.functions.change_pixel_color(location,
                                                      hex_value).buildTransaction({'gasPrice': w3.toWei(gas_price, 'gwei'),
                                                                                   'nonce': nonce,
                                                                                   'value': change_price, 
                                                                                   'chainId': chain_id,
                                                                                   'gas': 500_000})
    signed_txn = w3.eth.account.signTransaction(txn_dict, wallet_private_key)
    txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
    txn_receipt = None
    count = 0
    while txn_receipt is None:
        try:
            txn_receipt = w3.eth.getTransactionReceipt(txn_hash)
        except:
            time.sleep(1)

    if txn_receipt is None:
        return {'status': 'failed', 'error': 'timeout'}
    else:
        return {'status': 'added', 'txn_receipt': txn_receipt}


def parse_log(contract, event, from_block=FROM_BLOCK):
    # logs = contract.eventFilter(event, {'fromBlock': from_block}).get_all_entries() # web3.py v4
    logs = getattr(contract.events, event).createFilter(fromBlock=from_block).get_all_entries() # web3.py v5
    if len(logs) == 0:
        return pd.DataFrame()
    else:
        df = pd.DataFrame(logs)
        for key in df['args'].iloc[0].keys():
            df[key] = df['args'].apply(lambda l: l[key])
        df = df.drop(['args'], axis=1)
        for column in ['address', 'blockHash', 'event', 'transactionHash']:
            df[column] = df[column].astype(str)
        return df


def hexbytes_to_rgb(hex_value):
    return (int.from_bytes(hex_value[0:1], 'big'),
            int.from_bytes(hex_value[1:2], 'big'),
            int.from_bytes(hex_value[2:3], 'big'))


def create_df_pixel(web3_contract, from_block=FROM_BLOCK):
    df_log_buy = parse_log(web3_contract, 'pixel_buy', from_block=from_block)
    if len(df_log_buy) == 0:
        return pd.DataFrame()

    df_log_change = parse_log(web3_contract, 'pixel_change', from_block=from_block)
    df_png = df_log_buy.append(df_log_change).drop_duplicates(subset=['location'],
                                                              keep='last').reset_index(drop=True)[['location', 'color']]
    df_png.loc[:, 'location_0_x'] = df_png['location'].apply(lambda l: (l - 1) % 1_000)
    df_png.loc[:, 'location_0_y'] = df_png['location'].apply(lambda l: int((l - 1) / 1_000))
    df_png.loc[:, 'color_rgb'] = df_png['color'].apply(hexbytes_to_rgb)
    return df_png


def create_doek(df_png):
    png_out = Image.new('RGBA', (1_000, 1_000))
    if len(df_png) == 0:
        return png_out

    for index, row in df_png.iterrows():
        png_out.putpixel((row['location_0_x'], row['location_0_y']), row['color_rgb'])
    return png_out

ABI

Etherdoek address
0x67B4Ef189A68133aB3a726b85d289B912C23e0B0

[
    {
      "name": "pixel_buy",
      "inputs": [
        {
          "type": "uint256",
          "name": "location",
          "indexed": false
        },
        {
          "type": "bytes",
          "name": "color",
          "indexed": false
        }
      ],
      "anonymous": false,
      "type": "event"
    },
    {
      "name": "pixel_change",
      "inputs": [
        {
          "type": "uint256",
          "name": "location",
          "indexed": false
        },
        {
          "type": "bytes",
          "name": "color",
          "indexed": false
        }
      ],
      "anonymous": false,
      "type": "event"
    },
    {
      "outputs": [],
      "inputs": [
        {
          "type": "address",
          "name": "_pixel_token"
        }
      ],
      "constant": false,
      "payable": false,
      "type": "constructor"
    },
    {
      "name": "change_contract_owner",
      "outputs": [],
      "inputs": [
        {
          "type": "address",
          "name": "new_owner"
        }
      ],
      "constant": false,
      "payable": false,
      "type": "function",
      "gas": 36247
    },
    {
      "name": "set_fees",
      "outputs": [],
      "inputs": [
        {
          "type": "uint256",
          "name": "_pixel_price"
        },
        {
          "type": "uint256",
          "name": "_pixel_change_fee"
        }
      ],
      "constant": false,
      "payable": false,
      "type": "function",
      "gas": 71247
    },
    {
      "name": "buy_pixel",
      "outputs": [],
      "inputs": [
        {
          "type": "uint256",
          "name": "location"
        },
        {
          "type": "bytes",
          "name": "hex_value"
        }
      ],
      "constant": false,
      "payable": true,
      "type": "function",
      "gas": 119741
    },
    {
      "name": "change_pixel_color",
      "outputs": [],
      "inputs": [
        {
          "type": "uint256",
          "name": "location"
        },
        {
          "type": "bytes",
          "name": "hex_value"
        }
      ],
      "constant": false,
      "payable": true,
      "type": "function",
      "gas": 117802
    },
    {
      "name": "pixel_token",
      "outputs": [
        {
          "type": "address",
          "name": ""
        }
      ],
      "inputs": [],
      "constant": true,
      "payable": false,
      "type": "function",
      "gas": 1271
    },
    {
      "name": "pixel_price",
      "outputs": [
        {
          "type": "uint256",
          "name": ""
        }
      ],
      "inputs": [],
      "constant": true,
      "payable": false,
      "type": "function",
      "gas": 1301
    },
    {
      "name": "pixel_change_fee",
      "outputs": [
        {
          "type": "uint256",
          "name": ""
        }
      ],
      "inputs": [],
      "constant": true,
      "payable": false,
      "type": "function",
      "gas": 1331
    },
    {
      "name": "pixel_color",
      "outputs": [
        {
          "type": "bytes",
          "name": ""
        }
      ],
      "inputs": [
        {
          "type": "uint256",
          "name": "arg0"
        }
      ],
      "constant": true,
      "payable": false,
      "type": "function",
      "gas": 6901
    }
  ]