2

I am trying to obtain the metadata URI of a Solana token using Python. Specifically, I need to fetch the description and images associated with the token. Currently, I am using DexScreener to get some token details, but it does not provide the metadata URI, description, or images.

I would prefer a solution that does not rely on third-party APIs if possible. Below is my current approach using the Solana JSON-RPC API to get the account info and decode the metadata:

import requests
import base64
import struct
from solders.pubkey import Pubkey

SOLANA_RPC_ENDPOINT = "https://..."

def get_account_info(pubkey):
    headers = {"Content-Type": "application/json"}
    payload = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "getAccountInfo",
        "params": [
            pubkey,
            {
                "encoding": "base64"
            }
        ]
    }
    response = requests.post(SOLANA_RPC_ENDPOINT, json=payload, headers=headers)
    return response.json()

def decode_metadata(data):
    decoded_data = base64.b64decode(data)
    
    try:
        name_len = struct.unpack_from('<B', decoded_data, 1 + 32 + 4)[0]
        name = struct.unpack_from(f'<{name_len}s', decoded_data, 1 + 32 + 4 + 1)[0].decode('utf-8').rstrip('\x00')
        
        symbol_len = struct.unpack_from('<B', decoded_data, 1 + 32 + 4 + 1 + name_len)[0]
        symbol = struct.unpack_from(f'<{symbol_len}s', decoded_data, 1 + 32 + 4 + 1 + name_len + 1)[0].decode('utf-8').rstrip('\x00')
        
        uri_len = struct.unpack_from('<B', decoded_data, 1 + 32 + 4 + 1 + name_len + 1 + symbol_len)[0]
        uri = struct.unpack_from(f'<{uri_len}s', decoded_data, 1 + 32 + 4 + 1 + name_len + 1 + symbol_len + 1)[0].decode('utf-8').rstrip('\x00')
        
        return {
            'name': name,
            'symbol': symbol,
            'uri': uri
        }
    except (struct.error, UnicodeDecodeError) as e:
        print(f"Error decoding metadata: {e}")
        return None

def get_token_metadata(mint_address):
    metadata_program_id = Pubkey.from_string('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s')
    mint_pubkey = Pubkey.from_string(mint_address)
    metadata_pubkey = Pubkey.find_program_address(
        [b'metadata', bytes(metadata_program_id), bytes(mint_pubkey)],
        metadata_program_id
    )[0]
    
    account_info = get_account_info(str(metadata_pubkey))
    if 'result' in account_info and 'value' in account_info['result'] and account_info['result']['value']:
        data = account_info['result']['value']['data'][0]
        return decode_metadata(data)
    return None

When running this code, I encounter the following error:

Error decoding metadata: 'utf-8' codec can't decode byte 0xa4 in position 0: invalid start byte
No metadata found for the given mint address

The error indicates an issue with decoding the metadata. How can I correctly decode the metadata to obtain the URI, description, and images of the token? Is there a better approach to achieve this without relying on third-party APIs?

1 Answer 1

2

Looking at the source code, I think the offsets are incorrect. Given the sizes at https://github.com/metaplex-foundation/mpl-token-metadata/blob/e86de64101fc386dd4cc97b6f107da3de258833a/programs/token-metadata/program/src/state/metadata.rs#L19, you should do:

        name_len = struct.unpack_from('<B', decoded_data, 1 + 32 + 32)[0:4] # actually need 4 bytes
        name = struct.unpack_from(f'<{name_len}s', decoded_data, 1 + 32 + 32 + 4)[0:32].decode('utf-8').rstrip('\x00') # name length is hardcoded to 32 bytes
        
        symbol_len = struct.unpack_from('<B', decoded_data, 1 + 32 + 32 + 4 + 32)[0:4] # 4 bytes gain
        symbol = struct.unpack_from(f'<{symbol_len}s', decoded_data, 1 + 32 +  + 32 + 4 + 32 + 4)[0:10].decode('utf-8').rstrip('\x00') # symbol length is 10
        
        uri_len = struct.unpack_from('<B', decoded_data, 1 + 32 + 32 + 4 + 32 4 + 10)[0:4]
        uri = struct.unpack_from(f'<{uri_len}s', decoded_data, 1 + 32 + 32 + 4 + 32 + 4 + 10 + 4)[0:200].decode('utf-8').rstrip('\x00') # max uri length is 200

I'm not sure what struct.unpack_from is doing, but at least the offsets should be correct now!

1
  • Hi, in the end, I used this RPC from Helius: Helius DAS API - Get Asset. However, I couldn't retrieve the metadata with the code I originally posted. But if anyone needs to do this in the future using Python with the DAS API method, it can be easily achieved. Also thank you for correct the offsets!
    – Nerevaine
    Commented May 20 at 22:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.