craft-nft

A standalone NFT implementation for real-world arts and crafts assets
Log | Files | Refs | README

commit 4acd3076355f41a4e5629b6d659f84e9dda5c3e1
parent 462a865ddaf639ac3361223c150abb729ef050a5
Author: lash <dev@holbrook.no>
Date:   Sat, 17 Dec 2022 23:13:47 +0000

Add dump tool

Diffstat:
Mpython/eth_craft_nft/nft.py | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Apython/eth_craft_nft/runnable/dump.py | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 235 insertions(+), 2 deletions(-)

diff --git a/python/eth_craft_nft/nft.py b/python/eth_craft_nft/nft.py @@ -1,15 +1,20 @@ # standard imports import os +import logging # external imports from chainlib.eth.tx import TxFormat from eth_erc721 import ERC721 from hexathon import add_0x +from hexathon import strip_0x from chainlib.eth.contract import ABIContractEncoder +from chainlib.eth.contract import ABIContractDecoder from chainlib.eth.contract import ABIContractType from chainlib.eth.contract import abi_decode_single from chainlib.jsonrpc import JSONRPCRequest from chainlib.eth.constant import ZERO_ADDRESS +from chainlib.eth.constant import ZERO_CONTENT +from chainlib.eth.address import to_checksum_address # local imports from eth_craft_nft.error import InvalidBatchError @@ -19,6 +24,44 @@ datadir = os.path.join(moddir, 'data') INVALID_BATCH = (2**256)-1 +logg = logging.getLogger(__name__) + +class TokenSpec: + + def __init__(self, count, cumulative_count, cursor): + self.count = count + self.cumulative_count = cumulative_count + self.cursor = cursor + + + def __str__(self): + return '{} / {}'.format(self.cursor, self.count) + + +class MintedToken: + + def __init__(self, owner_address=ZERO_ADDRESS, token_id=None, batched=False, minted=False): + self.minted = minted + self.batched = batched + self.owner = owner_address + self.index = 0 + self.batch = 0 + self.token_id = token_id + + + def __str__(self): + owner = to_checksum_address(self.owner) + if self.batched: + return '{} owned {}'.format( + self.token_id, + owner, + ) + return '{} batch {} index {} owned by {}'.format( + self.token_id, + self.batch, + self.index, + owner, + ) class CraftNFT(ERC721): @@ -74,6 +117,24 @@ class CraftNFT(ERC721): return tx + def token_at(self, contract_address, idx, sender_address=ZERO_ADDRESS, id_generator=None): + j = JSONRPCRequest(id_generator) + o = j.template() + o['method'] = 'eth_call' + enc = ABIContractEncoder() + enc.method('tokens') + enc.typ(ABIContractType.UINT256) + enc.uint256(idx) + data = add_0x(enc.get()) + tx = self.template(sender_address, contract_address) + tx = self.set_code(tx, data) + o['params'].append(self.normalize(tx)) + o['params'].append('latest') + o = j.finalize(o) + return o + + + def batch_of(self, contract_address, token_id, super_index, start_at=0, max_batches=0, sender_address=ZERO_ADDRESS, id_generator=None): j = JSONRPCRequest(id_generator) o = j.template() @@ -95,7 +156,7 @@ class CraftNFT(ERC721): return o - def get_token_spec_raw(self, contract_address, token_id, batch, sender_address=ZERO_ADDRESS, id_generator=None): + def get_token_spec(self, contract_address, token_id, batch, sender_address=ZERO_ADDRESS, id_generator=None): j = JSONRPCRequest(id_generator) o = j.template() o['method'] = 'eth_call' @@ -114,7 +175,7 @@ class CraftNFT(ERC721): return o - def get_token_raw(self, contract_address, token_id, sender_address=ZERO_ADDRESS, id_generator=None): + def get_token(self, contract_address, token_id, sender_address=ZERO_ADDRESS, id_generator=None): j = JSONRPCRequest(id_generator) o = j.template() o['method'] = 'eth_call' @@ -170,3 +231,35 @@ class CraftNFT(ERC721): if r == INVALID_BATCH: raise InvalidBatchError() return r + + + @classmethod + def parse_token_spec(self, v): + v = strip_0x(v) + d = ABIContractDecoder() + d.typ(ABIContractType.UINT48) + d.typ(ABIContractType.UINT48) + d.typ(ABIContractType.UINT48) + d.val(v[:64]) + d.val(v[64:128]) + d.val(v[128:192]) + r = d.decode() + return TokenSpec(r[0], r[1], r[2]) + + @classmethod + def parse_token(self, v, token_id): + v = strip_0x(v) + if v == strip_0x(ZERO_CONTENT): + return MintedToken() + + token_id = strip_0x(token_id) + c = v[:2] + addr = v[24:] + if int(c, 16) & 0x40 > 0: + return MintedToken(addr, token_id=token_id, batched=True, minted=True) + + o = MintedToken(addr, minted=True) + o.index = int(token_id[59:], 16) + o.batch = int(token_id[54:59], 16) + o.token_id = token_id[:54] + v[2:12] + return o diff --git a/python/eth_craft_nft/runnable/dump.py b/python/eth_craft_nft/runnable/dump.py @@ -0,0 +1,140 @@ +"""Deploys badge NFT + +.. moduleauthor:: Louis Holbrook <dev@holbrook.no> +.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 + +""" + +# SPDX-License-Identifier: GPL-3.0-or-later + +# standard imports +import sys +import os +import json +import argparse +import logging +import time +from enum import Enum + +# external imports +import chainlib.eth.cli +from chainlib.chain import ChainSpec +from chainlib.eth.constant import ZERO_ADDRESS +from chainlib.settings import ChainSettings +from chainlib.eth.settings import process_settings +from chainlib.eth.cli.arg import Arg +from chainlib.eth.cli.arg import ArgFlag +from chainlib.eth.cli.arg import process_args +from chainlib.eth.cli.log import process_log +from chainlib.eth.cli.config import Config +from chainlib.eth.cli.config import process_config +from hexathon import strip_0x + +# local imports +from eth_craft_nft import CraftNFT + +logg = logging.getLogger() + +def process_config_local(config, arg, args, flags): + contract = None + try: + contract = config.get('_EXEC_ADDRESS') + except KeyError: + pass + + if contract == None: + address = config.get('_POSARG') + if address: + contract = add_0x(address) + else: + contract = stdin_arg() + + config.add(contract, '_CONTRACT', False) + return config + + +arg_flags = ArgFlag() +arg = Arg(arg_flags) + +flags = arg_flags.STD_READ | arg_flags.EXEC | arg_flags.TAB + +argparser = chainlib.eth.cli.ArgumentParser() +argparser = process_args(argparser, arg, flags) +argparser.add_argument('contract_address', type=str, help='Token contract address (may also be specified by -e)') +args = argparser.parse_args() + +logg = process_log(args, logg) + +config = Config() +config = process_config(config, arg, args, flags, positional_name='contract_address') +config = process_config_local(config, arg, args, flags) +logg.debug('config loaded:\n{}'.format(config)) + +settings = ChainSettings() +settings = process_settings(settings, config) +logg.debug('settings loaded:\n{}'.format(settings)) + + +def render_token_batches(c, conn, token_address, token_id, w=sys.stdout): + i = 0 + while True: + o = c.get_token_spec(token_address, token_id, i) + r = None + try: + r = conn.do(o) + except: + break + spec = c.parse_token_spec(r) + + for j in range(spec.cursor): + cursor_hex = j.to_bytes(3, byteorder='big').hex() + batch_hex = i.to_bytes(3, byteorder='big').hex() + token_id_indexed = token_id[:54] + batch_hex[1:] + cursor_hex[1:] + render_token_mint(c, conn, token_address, token_id_indexed, w=w) + + i += 1 + + +def render_token_mint(c, conn, token_address, token_id, w=sys.stdout): + o = c.get_token(token_address, token_id) + r = conn.do(o) + token = c.parse_token(r, token_id) + if token.minted: + w.write('token {}\n'.format(token)) + + +def render_token(c, conn, token_address, token_id, w=sys.stdout): + token_id = strip_0x(token_id) + o = c.get_token_spec(token_address, token_id, 0) + r = conn.do(o) + spec = c.parse_token_spec(r) + if spec.count > 0: + return render_token_batches(c, conn, token_address, token_id, w=sys.stdout) + + return render_token_mint(c, conn, token_address, token_id, w=w) + + +def main(): + token_address = config.get('_CONTRACT') + conn = settings.get('CONN') + c = CraftNFT( + chain_spec=settings.get('CHAIN_SPEC'), + gas_oracle=settings.get('GAS_ORACLE'), + ) + + outkeys = config.get('_OUTARG') + + i = 0 + while True: + o = c.token_at(token_address, i) + r = None + try: + r = conn.do(o) + except: + break + render_token(c, conn, token_address, r) + i += 1 + + +if __name__ == '__main__': + main()