import base64 import hashlib import json import struct import sys from solders.transaction import Transaction, VersionedTransaction def load_idl(file_path): with open(file_path) as f: return json.load(f) def load_transaction(file_path): with open(file_path) as f: data = json.load(f) return data 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). Trailing args can be absent from the wire entirely: two real mainnet `create_v2` instructions carry `0001` and `00` respectively after `creator`, so `is_cashback_enabled` is there in one and omitted in the other. Anything the data runs out for is reported as None rather than raising. """ args = {} offset = 8 # Skip 8-byte discriminator # Width each type needs before it can be read at all; strings carry their own # 4-byte length prefix. widths = { "u64": 8, "i64": 8, "u32": 4, "u16": 2, "u8": 1, "bool": 1, "pubkey": 32, "string": 4, } for arg in ix_def["args"]: t = arg["type"] needed = widths.get(t, 1) if isinstance(t, str) else 1 if offset + needed > len(ix_data): # Truncated trailing arg: the sender omitted it. args[arg["name"]] = None continue if t == "u64": value = struct.unpack_from("