mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-07-27 15:27:44 +00:00
fix(examples): align listeners and decoders with refreshed pump.fun IDL
Refreshes the read-only learning examples (Phase A3) against the post
Feb–Apr 2026 IDL changes:
- CreateEvent now has 15 fields (timestamp, 4×u64 reserves, token_program,
is_mayhem_mode, is_cashback_enabled). Listener parsers were truncating
after `creator`; the v2 ones were also reading is_mayhem_mode at the
wrong offset.
- BondingCurve account is now 83 bytes (added is_cashback_coin).
- PumpSwap Pool account is now 245 bytes (added is_cashback_coin) — the
listen_programsubscribe dataSize filter was matching nothing.
- IDL instruction `createV2` was renamed to `create_v2`; the legacy
`create` instruction added a `creator: pubkey` arg in March 2026.
- decode_from_blockSubscribe.py decoder now handles bool, i64, u32, u16,
u8, and the OptionBool defined type (1-byte wire format).
Files touched (all under learning-examples/):
bonding-curve-progress/get_bonding_curve_status.py — V4 struct (cashback)
bonding-curve-progress/poll_bonding_curve_progress.py
decode_from_blockSubscribe.py
decode_from_getAccountInfo.py
decode_from_getTransaction.py
listen-migrations/compare_migration_listeners.py
listen-migrations/listen_programsubscribe.py
listen-new-tokens/compare_listeners.py
listen-new-tokens/listen_blocksubscribe.py
listen-new-tokens/listen_geyser.py
listen-new-tokens/listen_logsubscribe.py
listen-new-tokens/listen_logsubscribe_abc.py
Verified live against mainnet — listeners decode tokens including
mayhem-mode and cashback-enabled curves correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,19 @@ class BondingCurveState:
|
|||||||
"is_mayhem_mode" / Flag, # Added mayhem mode flag - 1 byte
|
"is_mayhem_mode" / Flag, # Added mayhem mode flag - 1 byte
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# V4: V3 + is_cashback_coin (83 bytes total: 8 discriminator + 75 data) — added in the late-Feb 2026 cashback upgrade
|
||||||
|
_STRUCT_V4 = Struct(
|
||||||
|
"virtual_token_reserves" / Int64ul,
|
||||||
|
"virtual_sol_reserves" / Int64ul,
|
||||||
|
"real_token_reserves" / Int64ul,
|
||||||
|
"real_sol_reserves" / Int64ul,
|
||||||
|
"token_total_supply" / Int64ul,
|
||||||
|
"complete" / Flag,
|
||||||
|
"creator" / Bytes(32),
|
||||||
|
"is_mayhem_mode" / Flag,
|
||||||
|
"is_cashback_coin" / Flag,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, data: bytes) -> None:
|
def __init__(self, data: bytes) -> None:
|
||||||
"""Parse bonding curve data."""
|
"""Parse bonding curve data."""
|
||||||
if data[:8] != EXPECTED_DISCRIMINATOR:
|
if data[:8] != EXPECTED_DISCRIMINATOR:
|
||||||
@@ -75,14 +88,19 @@ class BondingCurveState:
|
|||||||
if total_length == 81: # V2: Creator only
|
if total_length == 81: # V2: Creator only
|
||||||
parsed = self._STRUCT_V2.parse(data[8:])
|
parsed = self._STRUCT_V2.parse(data[8:])
|
||||||
self.__dict__.update(parsed)
|
self.__dict__.update(parsed)
|
||||||
# Convert raw bytes to Pubkey for creator field
|
|
||||||
self.creator = Pubkey.from_bytes(self.creator)
|
self.creator = Pubkey.from_bytes(self.creator)
|
||||||
self.is_mayhem_mode = False
|
self.is_mayhem_mode = False
|
||||||
|
self.is_cashback_coin = False
|
||||||
|
|
||||||
elif total_length >= 82: # V3: Creator + mayhem mode
|
elif total_length == 82: # V3: Creator + mayhem
|
||||||
parsed = self._STRUCT_V3.parse(data[8:])
|
parsed = self._STRUCT_V3.parse(data[8:])
|
||||||
self.__dict__.update(parsed)
|
self.__dict__.update(parsed)
|
||||||
# Convert raw bytes to Pubkey for creator field
|
self.creator = Pubkey.from_bytes(self.creator)
|
||||||
|
self.is_cashback_coin = False
|
||||||
|
|
||||||
|
elif total_length >= 83: # V4: Creator + mayhem + cashback
|
||||||
|
parsed = self._STRUCT_V4.parse(data[8:])
|
||||||
|
self.__dict__.update(parsed)
|
||||||
self.creator = Pubkey.from_bytes(self.creator)
|
self.creator = Pubkey.from_bytes(self.creator)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -162,6 +180,9 @@ async def check_token_status(mint_address: str) -> None:
|
|||||||
print(
|
print(
|
||||||
f"Mayhem Mode: {'✅ Enabled' if curve_state.is_mayhem_mode else '❌ Disabled'}"
|
f"Mayhem Mode: {'✅ Enabled' if curve_state.is_mayhem_mode else '❌ Disabled'}"
|
||||||
)
|
)
|
||||||
|
print(
|
||||||
|
f"Cashback Coin: {'✅ Enabled' if curve_state.is_cashback_coin else '❌ Disabled'}"
|
||||||
|
)
|
||||||
print(
|
print(
|
||||||
f"Completed: {'✅ Migrated' if curve_state.complete else '❌ Bonding curve'}"
|
f"Completed: {'✅ Migrated' if curve_state.complete else '❌ Bonding curve'}"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -104,6 +104,12 @@ def parse_curve_state(data: bytes) -> dict:
|
|||||||
else:
|
else:
|
||||||
result["is_mayhem_mode"] = False
|
result["is_mayhem_mode"] = False
|
||||||
|
|
||||||
|
# Parse is_cashback_coin if present (added in late-Feb 2026 cashback upgrade)
|
||||||
|
if data_length >= 75:
|
||||||
|
result["is_cashback_coin"] = bool(data[82])
|
||||||
|
else:
|
||||||
|
result["is_cashback_coin"] = False
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,23 +19,52 @@ def load_transaction(file_path):
|
|||||||
|
|
||||||
|
|
||||||
def decode_instruction(ix_data, ix_def):
|
def decode_instruction(ix_data, ix_def):
|
||||||
|
"""Decode an instruction's args based on its IDL definition.
|
||||||
|
|
||||||
|
Supports the primitive scalar types and the `OptionBool` defined type
|
||||||
|
(added in the late-Feb 2026 cashback upgrade — `OptionBool` is a struct
|
||||||
|
wrapping a single bool, so it serializes as 1 byte on the wire).
|
||||||
|
"""
|
||||||
args = {}
|
args = {}
|
||||||
offset = 8 # Skip 8-byte discriminator
|
offset = 8 # Skip 8-byte discriminator
|
||||||
|
|
||||||
for arg in ix_def["args"]:
|
for arg in ix_def["args"]:
|
||||||
if arg["type"] == "u64":
|
t = arg["type"]
|
||||||
|
if t == "u64":
|
||||||
value = struct.unpack_from("<Q", ix_data, offset)[0]
|
value = struct.unpack_from("<Q", ix_data, offset)[0]
|
||||||
offset += 8
|
offset += 8
|
||||||
elif arg["type"] == "pubkey":
|
elif t == "i64":
|
||||||
|
value = struct.unpack_from("<q", ix_data, offset)[0]
|
||||||
|
offset += 8
|
||||||
|
elif t == "u32":
|
||||||
|
value = struct.unpack_from("<I", ix_data, offset)[0]
|
||||||
|
offset += 4
|
||||||
|
elif t == "u16":
|
||||||
|
value = struct.unpack_from("<H", ix_data, offset)[0]
|
||||||
|
offset += 2
|
||||||
|
elif t == "u8":
|
||||||
|
value = ix_data[offset]
|
||||||
|
offset += 1
|
||||||
|
elif t == "bool":
|
||||||
|
value = bool(ix_data[offset])
|
||||||
|
offset += 1
|
||||||
|
elif t == "pubkey":
|
||||||
value = ix_data[offset : offset + 32].hex()
|
value = ix_data[offset : offset + 32].hex()
|
||||||
offset += 32
|
offset += 32
|
||||||
elif arg["type"] == "string":
|
elif t == "string":
|
||||||
length = struct.unpack_from("<I", ix_data, offset)[0]
|
length = struct.unpack_from("<I", ix_data, offset)[0]
|
||||||
offset += 4
|
offset += 4
|
||||||
value = ix_data[offset : offset + length].decode("utf-8")
|
value = ix_data[offset : offset + length].decode("utf-8")
|
||||||
offset += length
|
offset += length
|
||||||
|
elif isinstance(t, dict) and "defined" in t:
|
||||||
|
defined_name = t["defined"]["name"] if isinstance(t["defined"], dict) else t["defined"]
|
||||||
|
if defined_name == "OptionBool":
|
||||||
|
value = bool(ix_data[offset])
|
||||||
|
offset += 1
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported defined type: {defined_name}")
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported type: {arg['type']}")
|
raise ValueError(f"Unsupported type: {t}")
|
||||||
|
|
||||||
args[arg["name"]] = value
|
args[arg["name"]] = value
|
||||||
|
|
||||||
|
|||||||
@@ -51,11 +51,19 @@ class BondingCurveState:
|
|||||||
|
|
||||||
if len(data) > offset:
|
if len(data) > offset:
|
||||||
self.is_mayhem_mode = bool(data[offset])
|
self.is_mayhem_mode = bool(data[offset])
|
||||||
|
offset += 1
|
||||||
else:
|
else:
|
||||||
self.is_mayhem_mode = None
|
self.is_mayhem_mode = None
|
||||||
|
|
||||||
|
if len(data) > offset:
|
||||||
|
self.is_cashback_coin = bool(data[offset])
|
||||||
|
else:
|
||||||
|
self.is_cashback_coin = None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.creator = None
|
self.creator = None
|
||||||
|
self.is_mayhem_mode = None
|
||||||
|
self.is_cashback_coin = None
|
||||||
|
|
||||||
|
|
||||||
def calculate_bonding_curve_price(curve_state: BondingCurveState) -> float:
|
def calculate_bonding_curve_price(curve_state: BondingCurveState) -> float:
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ print(json.dumps(tx_data, indent=2))
|
|||||||
|
|
||||||
|
|
||||||
def decode_create_instruction(data):
|
def decode_create_instruction(data):
|
||||||
"""Decode legacy Create instruction (Metaplex tokens)."""
|
"""Decode legacy Create instruction (Metaplex tokens).
|
||||||
# The Create instruction has 3 string arguments: name, symbol, uri
|
|
||||||
|
IDL args (March 7, 2026 program upgrade): name, symbol, uri, creator (pubkey).
|
||||||
|
"""
|
||||||
offset = 8 # Skip the 8-byte discriminator
|
offset = 8 # Skip the 8-byte discriminator
|
||||||
results = []
|
results = []
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
@@ -37,18 +39,25 @@ def decode_create_instruction(data):
|
|||||||
string_data = data[offset : offset + length].decode("utf-8")
|
string_data = data[offset : offset + length].decode("utf-8")
|
||||||
results.append(string_data)
|
results.append(string_data)
|
||||||
offset += length
|
offset += length
|
||||||
|
|
||||||
|
creator = base58.b58encode(data[offset : offset + 32]).decode("utf-8") if offset + 32 <= len(data) else None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"name": results[0],
|
"name": results[0],
|
||||||
"symbol": results[1],
|
"symbol": results[1],
|
||||||
"uri": results[2],
|
"uri": results[2],
|
||||||
|
"creator": creator,
|
||||||
"token_standard": "legacy",
|
"token_standard": "legacy",
|
||||||
"is_mayhem_mode": False,
|
"is_mayhem_mode": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def decode_create_v2_instruction(data):
|
def decode_create_v2_instruction(data):
|
||||||
"""Decode CreateV2 instruction (Token2022 tokens)."""
|
"""Decode CreateV2 instruction (Token2022 tokens).
|
||||||
# The CreateV2 instruction has 3 string arguments: name, symbol, uri + is_mayhem_mode
|
|
||||||
|
IDL args: name, symbol, uri, creator (pubkey), is_mayhem_mode (bool),
|
||||||
|
is_cashback_enabled (OptionBool = 1 byte).
|
||||||
|
"""
|
||||||
offset = 8 # Skip the 8-byte discriminator
|
offset = 8 # Skip the 8-byte discriminator
|
||||||
results = []
|
results = []
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
@@ -58,20 +67,22 @@ def decode_create_v2_instruction(data):
|
|||||||
results.append(string_data)
|
results.append(string_data)
|
||||||
offset += length
|
offset += length
|
||||||
|
|
||||||
# Skip creator pubkey (32 bytes)
|
creator = base58.b58encode(data[offset : offset + 32]).decode("utf-8") if offset + 32 <= len(data) else None
|
||||||
offset += 32
|
offset += 32
|
||||||
|
|
||||||
# Parse is_mayhem_mode (OptionBool at the end)
|
is_mayhem_mode = bool(data[offset]) if offset < len(data) else False
|
||||||
is_mayhem_mode = False
|
offset += 1
|
||||||
if offset < len(data):
|
|
||||||
is_mayhem_mode = bool(data[offset])
|
is_cashback_enabled = bool(data[offset]) if offset < len(data) else False
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"name": results[0],
|
"name": results[0],
|
||||||
"symbol": results[1],
|
"symbol": results[1],
|
||||||
"uri": results[2],
|
"uri": results[2],
|
||||||
|
"creator": creator,
|
||||||
"token_standard": "token2022",
|
"token_standard": "token2022",
|
||||||
"is_mayhem_mode": is_mayhem_mode,
|
"is_mayhem_mode": is_mayhem_mode,
|
||||||
|
"is_cashback_enabled": is_cashback_enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -84,7 +95,7 @@ def decode_buy_instruction(data):
|
|||||||
def decode_instruction_data(instruction, accounts, data):
|
def decode_instruction_data(instruction, accounts, data):
|
||||||
if instruction["name"] == "create":
|
if instruction["name"] == "create":
|
||||||
return decode_create_instruction(data)
|
return decode_create_instruction(data)
|
||||||
elif instruction["name"] == "createV2":
|
elif instruction["name"] == "create_v2":
|
||||||
return decode_create_v2_instruction(data)
|
return decode_create_v2_instruction(data)
|
||||||
elif instruction["name"] == "buy":
|
elif instruction["name"] == "buy":
|
||||||
return decode_buy_instruction(data)
|
return decode_buy_instruction(data)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ QUOTE_MINT_SOL = base58.b58encode(
|
|||||||
).decode()
|
).decode()
|
||||||
|
|
||||||
MARKET_DISCRIMINATOR = base58.b58encode(b"\xf1\x9am\x04\x11\xb1m\xbc").decode()
|
MARKET_DISCRIMINATOR = base58.b58encode(b"\xf1\x9am\x04\x11\xb1m\xbc").decode()
|
||||||
MARKET_ACCOUNT_LENGTH = 8 + 1 + 2 + 32 * 6 + 8 + 32 + 1 # Pool account with is_mayhem_mode = 244 bytes
|
MARKET_ACCOUNT_LENGTH = 8 + 1 + 2 + 32 * 6 + 8 + 32 + 1 + 1 # Pool with is_mayhem_mode + is_cashback_coin = 245 bytes
|
||||||
|
|
||||||
|
|
||||||
class DetectionTracker:
|
class DetectionTracker:
|
||||||
@@ -333,6 +333,7 @@ def parse_market_account_data(data):
|
|||||||
("lp_supply", "u64"),
|
("lp_supply", "u64"),
|
||||||
("coin_creator", "pubkey"),
|
("coin_creator", "pubkey"),
|
||||||
("is_mayhem_mode", "bool"),
|
("is_mayhem_mode", "bool"),
|
||||||
|
("is_cashback_coin", "bool"),
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ WSS_ENDPOINT = os.environ.get("SOLANA_NODE_WSS_ENDPOINT")
|
|||||||
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
||||||
PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
||||||
|
|
||||||
MARKET_ACCOUNT_LENGTH = 8 + 1 + 2 + 32 * 6 + 8 + 32 + 1 # discriminator + pool_bump + index + 6 pubkeys + lp_supply + coin_creator + is_mayhem_mode = 244 bytes
|
MARKET_ACCOUNT_LENGTH = 8 + 1 + 2 + 32 * 6 + 8 + 32 + 1 + 1 # disc + pool_bump + index + 6 pubkeys + lp_supply + coin_creator + is_mayhem_mode + is_cashback_coin = 245 bytes
|
||||||
MARKET_DISCRIMINATOR = base58.b58encode(b"\xf1\x9am\x04\x11\xb1m\xbc").decode()
|
MARKET_DISCRIMINATOR = base58.b58encode(b"\xf1\x9am\x04\x11\xb1m\xbc").decode()
|
||||||
QUOTE_MINT_SOL = base58.b58encode(
|
QUOTE_MINT_SOL = base58.b58encode(
|
||||||
bytes(Pubkey.from_string("So11111111111111111111111111111111111111112"))
|
bytes(Pubkey.from_string("So11111111111111111111111111111111111111112"))
|
||||||
@@ -77,6 +77,7 @@ def parse_market_account_data(data):
|
|||||||
("lp_supply", "u64"),
|
("lp_supply", "u64"),
|
||||||
("coin_creator", "pubkey"),
|
("coin_creator", "pubkey"),
|
||||||
("is_mayhem_mode", "bool"),
|
("is_mayhem_mode", "bool"),
|
||||||
|
("is_cashback_coin", "bool"),
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -334,11 +334,16 @@ def decode_create_v2_instruction(ix_data, account_keys):
|
|||||||
parsed_data["uri"] = read_string()
|
parsed_data["uri"] = read_string()
|
||||||
parsed_data["creator"] = read_pubkey()
|
parsed_data["creator"] = read_pubkey()
|
||||||
|
|
||||||
# Parse is_mayhem_mode (OptionBool at the end)
|
# CreateV2 trailing args: is_mayhem_mode (bool, 1B), is_cashback_enabled (OptionBool, 1B)
|
||||||
if offset < len(ix_data):
|
if offset < len(ix_data):
|
||||||
parsed_data["is_mayhem_mode"] = bool(ix_data[offset])
|
parsed_data["is_mayhem_mode"] = bool(ix_data[offset])
|
||||||
|
offset += 1
|
||||||
else:
|
else:
|
||||||
parsed_data["is_mayhem_mode"] = False
|
parsed_data["is_mayhem_mode"] = False
|
||||||
|
if offset < len(ix_data):
|
||||||
|
parsed_data["is_cashback_enabled"] = bool(ix_data[offset])
|
||||||
|
else:
|
||||||
|
parsed_data["is_cashback_enabled"] = False
|
||||||
|
|
||||||
# Extract accounts from account_keys array
|
# Extract accounts from account_keys array
|
||||||
if len(account_keys) >= 6:
|
if len(account_keys) >= 6:
|
||||||
@@ -355,27 +360,33 @@ def decode_create_v2_instruction(ix_data, account_keys):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def parse_create_event(data):
|
_CREATE_EVENT_FIELDS = [
|
||||||
"""Parse legacy Create event from logs (event data includes all fields)."""
|
("name", "string"),
|
||||||
|
("symbol", "string"),
|
||||||
|
("uri", "string"),
|
||||||
|
("mint", "publicKey"),
|
||||||
|
("bondingCurve", "publicKey"),
|
||||||
|
("user", "publicKey"),
|
||||||
|
("creator", "publicKey"),
|
||||||
|
("timestamp", "i64"),
|
||||||
|
("virtual_token_reserves", "u64"),
|
||||||
|
("virtual_sol_reserves", "u64"),
|
||||||
|
("real_token_reserves", "u64"),
|
||||||
|
("token_total_supply", "u64"),
|
||||||
|
("token_program", "publicKey"),
|
||||||
|
("is_mayhem_mode", "bool"),
|
||||||
|
("is_cashback_enabled", "bool"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_create_event(data, token_standard_hint):
|
||||||
|
"""Parse a CreateEvent payload (same on-chain layout for create and create_v2)."""
|
||||||
if len(data) < 8:
|
if len(data) < 8:
|
||||||
return None
|
return None
|
||||||
|
offset = 8
|
||||||
offset = 8 # Skip discriminator
|
|
||||||
parsed_data = {}
|
parsed_data = {}
|
||||||
|
|
||||||
# Parse fields based on CreateEvent structure
|
|
||||||
fields = [
|
|
||||||
("name", "string"),
|
|
||||||
("symbol", "string"),
|
|
||||||
("uri", "string"),
|
|
||||||
("mint", "publicKey"),
|
|
||||||
("bondingCurve", "publicKey"),
|
|
||||||
("user", "publicKey"),
|
|
||||||
("creator", "publicKey"),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for field_name, field_type in fields:
|
for field_name, field_type in _CREATE_EVENT_FIELDS:
|
||||||
if field_type == "string":
|
if field_type == "string":
|
||||||
if offset + 4 > len(data):
|
if offset + 4 > len(data):
|
||||||
raise ValueError(f"Not enough data for {field_name} length at offset {offset}")
|
raise ValueError(f"Not enough data for {field_name} length at offset {offset}")
|
||||||
@@ -390,67 +401,31 @@ def parse_create_event(data):
|
|||||||
raise ValueError(f"Not enough data for {field_name} at offset {offset}")
|
raise ValueError(f"Not enough data for {field_name} at offset {offset}")
|
||||||
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
||||||
offset += 32
|
offset += 32
|
||||||
|
elif field_type == "u64":
|
||||||
|
value = struct.unpack("<Q", data[offset : offset + 8])[0]
|
||||||
|
offset += 8
|
||||||
|
elif field_type == "i64":
|
||||||
|
value = struct.unpack("<q", data[offset : offset + 8])[0]
|
||||||
|
offset += 8
|
||||||
|
elif field_type == "bool":
|
||||||
|
value = bool(data[offset]) if offset < len(data) else False
|
||||||
|
offset += 1
|
||||||
parsed_data[field_name] = value
|
parsed_data[field_name] = value
|
||||||
|
parsed_data["token_standard"] = token_standard_hint
|
||||||
parsed_data["token_standard"] = "legacy"
|
|
||||||
parsed_data["is_mayhem_mode"] = False
|
|
||||||
return parsed_data
|
return parsed_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] Failed to parse create event: {e}")
|
print(f"[ERROR] Failed to parse create event: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_create_event(data):
|
||||||
|
"""Parse CreateEvent emitted by legacy Create instruction."""
|
||||||
|
return _parse_create_event(data, token_standard_hint="legacy")
|
||||||
|
|
||||||
|
|
||||||
def parse_create_v2_event(data):
|
def parse_create_v2_event(data):
|
||||||
"""Parse CreateV2 event from logs (event data includes all fields)."""
|
"""Parse CreateEvent emitted by CreateV2 instruction (Token2022 tokens)."""
|
||||||
if len(data) < 8:
|
return _parse_create_event(data, token_standard_hint="token2022")
|
||||||
return None
|
|
||||||
|
|
||||||
offset = 8 # Skip discriminator
|
|
||||||
parsed_data = {}
|
|
||||||
|
|
||||||
# Parse fields based on CreateV2Event structure
|
|
||||||
fields = [
|
|
||||||
("name", "string"),
|
|
||||||
("symbol", "string"),
|
|
||||||
("uri", "string"),
|
|
||||||
("mint", "publicKey"),
|
|
||||||
("bondingCurve", "publicKey"),
|
|
||||||
("user", "publicKey"),
|
|
||||||
("creator", "publicKey"),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
for field_name, field_type in fields:
|
|
||||||
if field_type == "string":
|
|
||||||
if offset + 4 > len(data):
|
|
||||||
raise ValueError(f"Not enough data for {field_name} length at offset {offset}")
|
|
||||||
length = struct.unpack("<I", data[offset : offset + 4])[0]
|
|
||||||
offset += 4
|
|
||||||
if offset + length > len(data):
|
|
||||||
raise ValueError(f"Not enough data for {field_name} value (length={length}) at offset {offset}")
|
|
||||||
value = data[offset : offset + length].decode("utf-8")
|
|
||||||
offset += length
|
|
||||||
elif field_type == "publicKey":
|
|
||||||
if offset + 32 > len(data):
|
|
||||||
raise ValueError(f"Not enough data for {field_name} at offset {offset}")
|
|
||||||
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
|
||||||
offset += 32
|
|
||||||
|
|
||||||
parsed_data[field_name] = value
|
|
||||||
|
|
||||||
# Parse is_mayhem_mode (OptionBool at the end)
|
|
||||||
if offset < len(data):
|
|
||||||
is_mayhem_mode = bool(data[offset])
|
|
||||||
parsed_data["is_mayhem_mode"] = is_mayhem_mode
|
|
||||||
else:
|
|
||||||
parsed_data["is_mayhem_mode"] = False
|
|
||||||
|
|
||||||
parsed_data["token_standard"] = "token2022"
|
|
||||||
return parsed_data
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[ERROR] Failed to parse create v2 event: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def is_transaction_successful(logs):
|
def is_transaction_successful(logs):
|
||||||
|
|||||||
@@ -197,29 +197,34 @@ def decode_create_v2_instruction(ix_data, ix_def, accounts):
|
|||||||
args = {}
|
args = {}
|
||||||
offset = 8 # Skip 8-byte discriminator
|
offset = 8 # Skip 8-byte discriminator
|
||||||
|
|
||||||
# Parse instruction arguments according to IDL definition
|
# Parse instruction arguments according to IDL definition.
|
||||||
|
# CreateV2 args: name, symbol, uri, creator (pubkey), is_mayhem_mode (bool),
|
||||||
|
# is_cashback_enabled (OptionBool, serialized as 1 byte).
|
||||||
for arg in ix_def["args"]:
|
for arg in ix_def["args"]:
|
||||||
if arg["type"] == "string":
|
t = arg["type"]
|
||||||
# String format: 4-byte length prefix + UTF-8 encoded string
|
if t == "string":
|
||||||
length = struct.unpack_from("<I", ix_data, offset)[0]
|
length = struct.unpack_from("<I", ix_data, offset)[0]
|
||||||
offset += 4
|
offset += 4
|
||||||
value = ix_data[offset : offset + length].decode("utf-8")
|
value = ix_data[offset : offset + length].decode("utf-8")
|
||||||
offset += length
|
offset += length
|
||||||
elif arg["type"] == "pubkey":
|
elif t == "pubkey":
|
||||||
# Pubkey is 32 bytes, encoded as base58
|
|
||||||
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
|
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
|
||||||
offset += 32
|
offset += 32
|
||||||
|
elif t == "bool":
|
||||||
|
value = bool(ix_data[offset]) if offset < len(ix_data) else False
|
||||||
|
offset += 1
|
||||||
|
elif isinstance(t, dict) and "defined" in t:
|
||||||
|
defined_name = t["defined"]["name"] if isinstance(t["defined"], dict) else t["defined"]
|
||||||
|
if defined_name == "OptionBool":
|
||||||
|
value = bool(ix_data[offset]) if offset < len(ix_data) else False
|
||||||
|
offset += 1
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported defined type: {defined_name}")
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported type: {arg['type']}")
|
raise ValueError(f"Unsupported type: {t}")
|
||||||
|
|
||||||
args[arg["name"]] = value
|
args[arg["name"]] = value
|
||||||
|
|
||||||
# Parse is_mayhem_mode (OptionBool at the end)
|
|
||||||
# Format: 1 byte (0 = false/None, 1 = true)
|
|
||||||
is_mayhem_mode = False
|
|
||||||
if offset < len(ix_data):
|
|
||||||
is_mayhem_mode = bool(ix_data[offset])
|
|
||||||
|
|
||||||
# Extract account addresses from the accounts array
|
# Extract account addresses from the accounts array
|
||||||
# Account layout for CreateV2 instruction:
|
# Account layout for CreateV2 instruction:
|
||||||
# 0: mint, 1: metadata, 2: bondingCurve, 3: associatedBondingCurve,
|
# 0: mint, 1: metadata, 2: bondingCurve, 3: associatedBondingCurve,
|
||||||
@@ -229,7 +234,6 @@ def decode_create_v2_instruction(ix_data, ix_def, accounts):
|
|||||||
args["associatedBondingCurve"] = str(accounts[3])
|
args["associatedBondingCurve"] = str(accounts[3])
|
||||||
args["user"] = str(accounts[5])
|
args["user"] = str(accounts[5])
|
||||||
args["token_standard"] = "token2022"
|
args["token_standard"] = "token2022"
|
||||||
args["is_mayhem_mode"] = is_mayhem_mode
|
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@@ -338,7 +342,7 @@ async def listen_and_decode_create():
|
|||||||
# CreateV2 instruction (Token2022 tokens)
|
# CreateV2 instruction (Token2022 tokens)
|
||||||
create_v2_ix = next(
|
create_v2_ix = next(
|
||||||
(instr for instr in idl["instructions"]
|
(instr for instr in idl["instructions"]
|
||||||
if instr["name"] == "createV2"),
|
if instr["name"] == "create_v2"),
|
||||||
next(instr for instr in idl["instructions"]
|
next(instr for instr in idl["instructions"]
|
||||||
if instr["name"] == "create")
|
if instr["name"] == "create")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -194,10 +194,14 @@ def decode_create_v2_instruction(ix_data: bytes, keys, accounts) -> dict:
|
|||||||
uri = read_string()
|
uri = read_string()
|
||||||
creator = read_pubkey()
|
creator = read_pubkey()
|
||||||
|
|
||||||
# Parse is_mayhem_mode (OptionBool at the end)
|
# CreateV2 trailing args: is_mayhem_mode (bool, 1B), is_cashback_enabled (OptionBool, 1B)
|
||||||
is_mayhem_mode = False
|
is_mayhem_mode = False
|
||||||
|
is_cashback_enabled = False
|
||||||
if offset < len(ix_data):
|
if offset < len(ix_data):
|
||||||
is_mayhem_mode = bool(ix_data[offset])
|
is_mayhem_mode = bool(ix_data[offset])
|
||||||
|
offset += 1
|
||||||
|
if offset < len(ix_data):
|
||||||
|
is_cashback_enabled = bool(ix_data[offset])
|
||||||
|
|
||||||
token_info = {
|
token_info = {
|
||||||
"name": name,
|
"name": name,
|
||||||
@@ -210,6 +214,7 @@ def decode_create_v2_instruction(ix_data: bytes, keys, accounts) -> dict:
|
|||||||
"user": get_account_key(5),
|
"user": get_account_key(5),
|
||||||
"token_standard": "token2022",
|
"token_standard": "token2022",
|
||||||
"is_mayhem_mode": is_mayhem_mode,
|
"is_mayhem_mode": is_mayhem_mode,
|
||||||
|
"is_cashback_enabled": is_cashback_enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
return token_info
|
return token_info
|
||||||
|
|||||||
@@ -92,27 +92,38 @@ def parse_create_instruction(data):
|
|||||||
Returns:
|
Returns:
|
||||||
Dictionary containing decoded token information, or None if parsing fails
|
Dictionary containing decoded token information, or None if parsing fails
|
||||||
"""
|
"""
|
||||||
|
return _parse_create_event(data, token_standard_hint="legacy")
|
||||||
|
|
||||||
|
|
||||||
|
_CREATE_EVENT_FIELDS = [
|
||||||
|
("name", "string"),
|
||||||
|
("symbol", "string"),
|
||||||
|
("uri", "string"),
|
||||||
|
("mint", "publicKey"),
|
||||||
|
("bondingCurve", "publicKey"),
|
||||||
|
("user", "publicKey"),
|
||||||
|
("creator", "publicKey"),
|
||||||
|
("timestamp", "i64"),
|
||||||
|
("virtual_token_reserves", "u64"),
|
||||||
|
("virtual_sol_reserves", "u64"),
|
||||||
|
("real_token_reserves", "u64"),
|
||||||
|
("token_total_supply", "u64"),
|
||||||
|
("token_program", "publicKey"),
|
||||||
|
("is_mayhem_mode", "bool"),
|
||||||
|
("is_cashback_enabled", "bool"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_create_event(data, token_standard_hint):
|
||||||
|
"""Parse a CreateEvent payload (same on-chain layout for both create and create_v2)."""
|
||||||
if len(data) < 8:
|
if len(data) < 8:
|
||||||
print(f"⚠️ Data too short for Create event: {len(data)} bytes")
|
print(f"⚠️ Data too short for Create event: {len(data)} bytes")
|
||||||
return None
|
return None
|
||||||
offset = 8 # Skip event discriminator
|
offset = 8
|
||||||
parsed_data = {}
|
parsed_data = {}
|
||||||
|
|
||||||
# Parse fields based on CreateEvent structure
|
|
||||||
fields = [
|
|
||||||
("name", "string"),
|
|
||||||
("symbol", "string"),
|
|
||||||
("uri", "string"),
|
|
||||||
("mint", "publicKey"),
|
|
||||||
("bondingCurve", "publicKey"),
|
|
||||||
("user", "publicKey"),
|
|
||||||
("creator", "publicKey"),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for field_name, field_type in fields:
|
for field_name, field_type in _CREATE_EVENT_FIELDS:
|
||||||
if field_type == "string":
|
if field_type == "string":
|
||||||
# String format: 4-byte length prefix + UTF-8 encoded string
|
|
||||||
if offset + 4 > len(data):
|
if offset + 4 > len(data):
|
||||||
raise ValueError(f"Not enough data for {field_name} length at offset {offset}")
|
raise ValueError(f"Not enough data for {field_name} length at offset {offset}")
|
||||||
length = struct.unpack("<I", data[offset : offset + 4])[0]
|
length = struct.unpack("<I", data[offset : offset + 4])[0]
|
||||||
@@ -122,93 +133,33 @@ def parse_create_instruction(data):
|
|||||||
value = data[offset : offset + length].decode("utf-8")
|
value = data[offset : offset + length].decode("utf-8")
|
||||||
offset += length
|
offset += length
|
||||||
elif field_type == "publicKey":
|
elif field_type == "publicKey":
|
||||||
# Pubkey is 32 bytes, encoded as base58
|
|
||||||
if offset + 32 > len(data):
|
if offset + 32 > len(data):
|
||||||
raise ValueError(f"Not enough data for {field_name} at offset {offset}")
|
raise ValueError(f"Not enough data for {field_name} at offset {offset}")
|
||||||
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
||||||
offset += 32
|
offset += 32
|
||||||
|
elif field_type == "u64":
|
||||||
|
value = struct.unpack("<Q", data[offset : offset + 8])[0]
|
||||||
|
offset += 8
|
||||||
|
elif field_type == "i64":
|
||||||
|
value = struct.unpack("<q", data[offset : offset + 8])[0]
|
||||||
|
offset += 8
|
||||||
|
elif field_type == "bool":
|
||||||
|
value = bool(data[offset]) if offset < len(data) else False
|
||||||
|
offset += 1
|
||||||
parsed_data[field_name] = value
|
parsed_data[field_name] = value
|
||||||
|
|
||||||
parsed_data["token_standard"] = "legacy"
|
parsed_data["token_standard"] = token_standard_hint
|
||||||
parsed_data["is_mayhem_mode"] = False
|
|
||||||
return parsed_data
|
return parsed_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Parse Create error: {e}")
|
print(f"❌ Parse Create event error: {e}")
|
||||||
print(f" Data length: {len(data)} bytes, offset: {offset}")
|
print(f" Data length: {len(data)} bytes, offset: {offset}")
|
||||||
print(f" Data hex: {data.hex()[:200]}...")
|
print(f" Data hex: {data.hex()[:200]}...")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def parse_create_v2_instruction(data):
|
def parse_create_v2_instruction(data):
|
||||||
"""
|
"""Parse CreateEvent emitted by CreateV2 instruction (Token2022 tokens)."""
|
||||||
Parse CreateEvent data from CreateV2 instruction (Token2022 tokens).
|
return _parse_create_event(data, token_standard_hint="token2022")
|
||||||
|
|
||||||
CreateV2 uses Token-2022 standard with additional features. The event format
|
|
||||||
is identical to Create, with an additional optional is_mayhem_mode flag at the end.
|
|
||||||
|
|
||||||
Token-2022 Reference:
|
|
||||||
https://spl.solana.com/token-2022
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: Raw event data bytes from program logs
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary containing decoded token information, or None if parsing fails
|
|
||||||
"""
|
|
||||||
if len(data) < 8:
|
|
||||||
print(f"⚠️ Data too short for CreateV2 event: {len(data)} bytes")
|
|
||||||
return None
|
|
||||||
offset = 8 # Skip event discriminator
|
|
||||||
parsed_data = {}
|
|
||||||
|
|
||||||
# Parse fields based on CreateV2Event structure
|
|
||||||
fields = [
|
|
||||||
("name", "string"),
|
|
||||||
("symbol", "string"),
|
|
||||||
("uri", "string"),
|
|
||||||
("mint", "publicKey"),
|
|
||||||
("bondingCurve", "publicKey"),
|
|
||||||
("user", "publicKey"),
|
|
||||||
("creator", "publicKey"),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
for field_name, field_type in fields:
|
|
||||||
if field_type == "string":
|
|
||||||
# String format: 4-byte length prefix + UTF-8 encoded string
|
|
||||||
if offset + 4 > len(data):
|
|
||||||
raise ValueError(f"Not enough data for {field_name} length at offset {offset}")
|
|
||||||
length = struct.unpack("<I", data[offset : offset + 4])[0]
|
|
||||||
offset += 4
|
|
||||||
if offset + length > len(data):
|
|
||||||
raise ValueError(f"Not enough data for {field_name} value (length={length}) at offset {offset}")
|
|
||||||
value = data[offset : offset + length].decode("utf-8")
|
|
||||||
offset += length
|
|
||||||
elif field_type == "publicKey":
|
|
||||||
# Pubkey is 32 bytes, encoded as base58
|
|
||||||
if offset + 32 > len(data):
|
|
||||||
raise ValueError(f"Not enough data for {field_name} at offset {offset}")
|
|
||||||
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
|
||||||
offset += 32
|
|
||||||
|
|
||||||
parsed_data[field_name] = value
|
|
||||||
|
|
||||||
# Parse is_mayhem_mode (OptionBool at the end)
|
|
||||||
# Format: 1 byte (0 = false/None, 1 = true)
|
|
||||||
if offset < len(data):
|
|
||||||
is_mayhem_mode = bool(data[offset])
|
|
||||||
parsed_data["is_mayhem_mode"] = is_mayhem_mode
|
|
||||||
else:
|
|
||||||
parsed_data["is_mayhem_mode"] = False
|
|
||||||
|
|
||||||
parsed_data["token_standard"] = "token2022"
|
|
||||||
return parsed_data
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Parse CreateV2 error: {e}")
|
|
||||||
print(f" Data length: {len(data)} bytes, offset: {offset}")
|
|
||||||
print(f" Data hex: {data.hex()[:200]}...")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
async def listen_for_new_tokens():
|
async def listen_for_new_tokens():
|
||||||
|
|||||||
@@ -109,27 +109,34 @@ def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey
|
|||||||
return derived_address
|
return derived_address
|
||||||
|
|
||||||
|
|
||||||
def parse_create_instruction(data):
|
_CREATE_EVENT_FIELDS = [
|
||||||
"""Parse legacy Create instruction (Metaplex tokens)."""
|
("name", "string"),
|
||||||
|
("symbol", "string"),
|
||||||
|
("uri", "string"),
|
||||||
|
("mint", "publicKey"),
|
||||||
|
("bondingCurve", "publicKey"),
|
||||||
|
("user", "publicKey"),
|
||||||
|
("creator", "publicKey"),
|
||||||
|
("timestamp", "i64"),
|
||||||
|
("virtual_token_reserves", "u64"),
|
||||||
|
("virtual_sol_reserves", "u64"),
|
||||||
|
("real_token_reserves", "u64"),
|
||||||
|
("token_total_supply", "u64"),
|
||||||
|
("token_program", "publicKey"),
|
||||||
|
("is_mayhem_mode", "bool"),
|
||||||
|
("is_cashback_enabled", "bool"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_create_event(data, token_standard_hint):
|
||||||
|
"""Parse a CreateEvent payload (same on-chain layout for both create and create_v2)."""
|
||||||
if len(data) < 8:
|
if len(data) < 8:
|
||||||
print(f"⚠️ Data too short for Create instruction: {len(data)} bytes")
|
print(f"⚠️ Data too short for Create event: {len(data)} bytes")
|
||||||
return None
|
return None
|
||||||
offset = 8
|
offset = 8
|
||||||
parsed_data = {}
|
parsed_data = {}
|
||||||
|
|
||||||
# Parse fields based on CreateEvent structure
|
|
||||||
fields = [
|
|
||||||
("name", "string"),
|
|
||||||
("symbol", "string"),
|
|
||||||
("uri", "string"),
|
|
||||||
("mint", "publicKey"),
|
|
||||||
("bondingCurve", "publicKey"),
|
|
||||||
("user", "publicKey"),
|
|
||||||
("creator", "publicKey"),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for field_name, field_type in fields:
|
for field_name, field_type in _CREATE_EVENT_FIELDS:
|
||||||
if field_type == "string":
|
if field_type == "string":
|
||||||
if offset + 4 > len(data):
|
if offset + 4 > len(data):
|
||||||
raise ValueError(f"Not enough data for {field_name} length at offset {offset}")
|
raise ValueError(f"Not enough data for {field_name} length at offset {offset}")
|
||||||
@@ -144,69 +151,33 @@ def parse_create_instruction(data):
|
|||||||
raise ValueError(f"Not enough data for {field_name} at offset {offset}")
|
raise ValueError(f"Not enough data for {field_name} at offset {offset}")
|
||||||
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
||||||
offset += 32
|
offset += 32
|
||||||
|
elif field_type == "u64":
|
||||||
|
value = struct.unpack("<Q", data[offset : offset + 8])[0]
|
||||||
|
offset += 8
|
||||||
|
elif field_type == "i64":
|
||||||
|
value = struct.unpack("<q", data[offset : offset + 8])[0]
|
||||||
|
offset += 8
|
||||||
|
elif field_type == "bool":
|
||||||
|
value = bool(data[offset]) if offset < len(data) else False
|
||||||
|
offset += 1
|
||||||
parsed_data[field_name] = value
|
parsed_data[field_name] = value
|
||||||
|
|
||||||
parsed_data["token_standard"] = "legacy"
|
parsed_data["token_standard"] = token_standard_hint
|
||||||
parsed_data["is_mayhem_mode"] = False
|
|
||||||
return parsed_data
|
return parsed_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Parse Create error: {e}")
|
print(f"❌ Parse Create event error: {e}")
|
||||||
print(f" Data length: {len(data)} bytes, offset: {offset}")
|
print(f" Data length: {len(data)} bytes, offset: {offset}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_create_instruction(data):
|
||||||
|
"""Parse CreateEvent emitted by legacy Create instruction (Metaplex tokens)."""
|
||||||
|
return _parse_create_event(data, token_standard_hint="legacy")
|
||||||
|
|
||||||
|
|
||||||
def parse_create_v2_instruction(data):
|
def parse_create_v2_instruction(data):
|
||||||
"""Parse CreateV2 instruction (Token2022 tokens)."""
|
"""Parse CreateEvent emitted by CreateV2 instruction (Token2022 tokens)."""
|
||||||
if len(data) < 8:
|
return _parse_create_event(data, token_standard_hint="token2022")
|
||||||
print(f"⚠️ Data too short for CreateV2 instruction: {len(data)} bytes")
|
|
||||||
return None
|
|
||||||
offset = 8
|
|
||||||
parsed_data = {}
|
|
||||||
|
|
||||||
# Parse fields based on CreateV2Event structure
|
|
||||||
fields = [
|
|
||||||
("name", "string"),
|
|
||||||
("symbol", "string"),
|
|
||||||
("uri", "string"),
|
|
||||||
("mint", "publicKey"),
|
|
||||||
("bondingCurve", "publicKey"),
|
|
||||||
("user", "publicKey"),
|
|
||||||
("creator", "publicKey"),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
for field_name, field_type in fields:
|
|
||||||
if field_type == "string":
|
|
||||||
if offset + 4 > len(data):
|
|
||||||
raise ValueError(f"Not enough data for {field_name} length at offset {offset}")
|
|
||||||
length = struct.unpack("<I", data[offset : offset + 4])[0]
|
|
||||||
offset += 4
|
|
||||||
if offset + length > len(data):
|
|
||||||
raise ValueError(f"Not enough data for {field_name} value (length={length}) at offset {offset}")
|
|
||||||
value = data[offset : offset + length].decode("utf-8")
|
|
||||||
offset += length
|
|
||||||
elif field_type == "publicKey":
|
|
||||||
if offset + 32 > len(data):
|
|
||||||
raise ValueError(f"Not enough data for {field_name} at offset {offset}")
|
|
||||||
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
|
||||||
offset += 32
|
|
||||||
|
|
||||||
parsed_data[field_name] = value
|
|
||||||
|
|
||||||
# Parse is_mayhem_mode (OptionBool at the end)
|
|
||||||
if offset < len(data):
|
|
||||||
is_mayhem_mode = bool(data[offset])
|
|
||||||
parsed_data["is_mayhem_mode"] = is_mayhem_mode
|
|
||||||
else:
|
|
||||||
parsed_data["is_mayhem_mode"] = False
|
|
||||||
|
|
||||||
parsed_data["token_standard"] = "token2022"
|
|
||||||
return parsed_data
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Parse CreateV2 error: {e}")
|
|
||||||
print(f" Data length: {len(data)} bytes, offset: {offset}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
async def listen_for_new_tokens():
|
async def listen_for_new_tokens():
|
||||||
|
|||||||
Reference in New Issue
Block a user