fix(utils): decode omitted trailing optional args in IDL instruction decoder (#185)

create_v2 instructions can legally omit the trailing is_cashback_enabled
OptionBool from the wire; decode_instruction rejected that form, so every
consumer of parse_token_creation_from_instruction silently dropped those
coins (the committed blocksubscribe fixture carries one such 145-byte
create_v2).

- When the arg buffer is exhausted and every remaining arg is
  option-typed (native option or pump.fun's OptionBool), report them as
  unset (None) instead of failing the decode; mandatory args still fail.
- Count trailing optional args as 0 bytes in the instruction min-size
  table.
- Decode Anchor's native option type (1-byte tag + value), previously
  unsupported entirely - update_buyback_config's Option<u64> could not
  even be sized.
- Add learning-examples/verify_create_v2_optional_args.py machine-checking
  the omitted, present, truncated-mandatory and native-option forms plus
  the event-parser path against the committed fixture.
- Drop the append-0x00 workaround in verify_extreme_fast_zero_rpc.py and
  update the stale CLAUDE.md note about the strict decoder.

Fixes #184

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Anton Sauchyk
2026-07-30 20:32:15 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent c16fc856d0
commit 9a3f2c0bfb
4 changed files with 275 additions and 27 deletions
+10 -6
View File
@@ -151,11 +151,13 @@ user-supplied and post-2026-04-28 may differ from the canonical `BC.creator`
**not** set the flag; the geyser parser prefers `meta.log_messages` over
instruction decoding for exactly this reason. `trade.trust_create_event:
false` is the escape hatch back to always-refresh. PumpPortal payloads carry
none of these fields and always refresh. Related pitfall: the strict IDL
instruction decoder rejects `create_v2` transactions that omit the trailing
`is_cashback_enabled` OptionBool (a legal wire form), so the instruction path
alone silently misses those coins — one more reason the log/event path is
preferred everywhere.
none of these fields and always refresh. Related pitfall (fixed in #184): the
IDL instruction decoder used to reject `create_v2` transactions that omit the
trailing `is_cashback_enabled` OptionBool (a legal wire form), silently
dropping those coins from the instruction path. It now reports omitted
trailing option-typed args as unset — `verify_create_v2_optional_args.py`
machine-checks that, and that mandatory args still fail the decode. The
log/event path stays preferred for the canonical-creator reason above.
### Verifying transaction-status handling
@@ -315,7 +317,9 @@ The IDLs under `idl/` are vendored verbatim from `github.com/pump-fun/pump-publi
`create_v2` instructions carry `0001` and `00` after `creator`: one sends both
trailing args, the other omits the last. A decoder that reads a fixed number of
trailing bytes raises `IndexError` on roughly half of all coins. Decode
trailing args defensively and report a missing one as unset.
trailing args defensively and report a missing one as unset
`utils/idl_parser.py` does this for trailing option-typed args since #184
(`uv run learning-examples/verify_create_v2_optional_args.py` checks it).
- `create_v2` accounts 1-16 are in the IDL; accounts **17-19 are optional
remaining accounts** (`quote_mint`, `associated_quote_bonding_curve`,
`quote_token_program`). All three or none. This is the only way to read a new