import flask import pbc_client import base64 import dataclasses import requests_cache from collections.abc import Iterator import logging HTML_INDEX = """ Hello from Flask
{% for notamon in notamons %}

{{ notamon.nickname }}

{{ notamon.species_name }}

{% endfor %}
""" @dataclasses.dataclass(frozen=True) class Notamon: image_src: str effect_css: str nickname: str species_name: str @dataclasses.dataclass(frozen=True) class NotamonNFTData: nickname: str owner: str species_id: int skin_id: int effect_id: int stat_hp: int stat_attack: int stat_defense: int @dataclasses.dataclass(frozen=True) class AssetVariable: variable_id: int asset_id: int opened: bytes | None app = flask.Flask(__name__) TEST_NOTAMON = Notamon( image_src = 'https://img.pokemondb.net/sprites/ruby-sapphire/normal/mudkip.png', effect_css = '', nickname = 'Dude', species_name = 'Mudkip', ) ADDRESS_NFTS = '02abbd5e40de91f4ab7f4a76f4b37b08de8f51bb2c' ADDRESS_ASSETS = '036080dc8a15e496e5b970f65112b4fe4ac022e62c' ASSET_ID_OFFSET_SPECIES = 0x0100_0000 ASSET_ID_OFFSET_SKIN = 0x0200_0000 ASSET_ID_OFFSET_EFFECT = 0x0300_0000 SESSION = requests_cache.CachedSession() def compress(b: bytes) -> bytes: l = [] for i in range(0, len(b)//8): derp = 0 for j in range(0, 8): derp |= b[i*8+j] << j del j l.append(derp) del i, derp return bytes(l) def decode_opened_variable(value: str) -> bytes: return compress(base64.b64decode(value))[4:] def get_asset_variables(client, address) -> Iterator[AssetVariable]: asset_contract_state, _ = client.get_contract_state(ADDRESS_ASSETS) for variable in asset_contract_state['variables']: asset_id = int.from_bytes(base64.b64decode(variable['value']['information']['data'])) opened = decode_opened_variable(variable['value']['openValue']['data']) if 'openValue' in variable['value'] else None yield AssetVariable( variable_id = variable['key'], asset_id = asset_id, opened = opened, ) def to_base64_png(b: bytes) -> str: encoded = base64.b64encode(b).decode('utf8') return f'data:image/png;base64,{encoded}' def get_notamon_nfts(client: pbc_client.PbcClient) -> Iterator[NotamonNFTData]: asset_contract_state, _ = client.get_typed_contract_state(ADDRESS_NFTS) owners, _ = client.get_typed_contract_avl_tree(ADDRESS_NFTS, asset_contract_state['owners']) attributes, _ = client.get_typed_contract_avl_tree(ADDRESS_NFTS, asset_contract_state['notamon_attributes']) for notamon_id in owners: attr = attributes.get(notamon_id) yield NotamonNFTData( nickname = 'Dude', owner =owners.get(notamon_id), species_id= attr['species_id']['id'], skin_id = attr['skin_id']['id'], effect_id = attr['effect_id']['id'], stat_hp = attr['stat_hp'], stat_attack = attr['stat_attack'], stat_defense = attr['stat_defense'], ) def get_asset(assets, asset_type_id, asset_type_offset) -> bytes | None: asset_id = asset_type_id | asset_type_offset asset = assets.get(asset_id) if asset is None: app.logger.error('Unknown asset with id %s = %s | %s', asset_id, asset_type_id, asset_type_offset) return None asset = asset.opened if asset is None: app.logger.warning('Secret asset with id %s = %s | %s', asset_id, asset_type_id, asset_type_offset) return asset def nft_to_notamon_view(nft: NotamonNFTData, assets) -> Notamon | None: species_image_bytes = get_asset(assets, nft.species_id, ASSET_ID_OFFSET_SPECIES) effect_str = get_asset(assets, nft.effect_id, ASSET_ID_OFFSET_EFFECT) if species_image_bytes is None or effect_str is None: return None return Notamon( image_src = to_base64_png(species_image_bytes), effect_css = effect_str, nickname = nft.nickname, species_name = 'Mudkip', ) def select_notamons(): client = pbc_client.PbcClient(SESSION, pbc_client.HOSTNAME_TESTNET) nfts = list(get_notamon_nfts(client)) assets = {v.asset_id: v for v in get_asset_variables(client, ADDRESS_ASSETS)} notamon_views = [nft_to_notamon_view(nft, assets) for nft in nfts] return [n for n in notamon_views if n is not None] @app.route("/") def hello_world(): return flask.render_template_string(HTML_INDEX, notamons = select_notamons())