Skip to content

Support Token-2022 in BeginSettle and FinalizeSettle - #128

Open
kaze-cow wants to merge 2 commits into
kaze/sc-153-token-2022-programfrom
kaze/sc-153-token-2022-settle
Open

Support Token-2022 in BeginSettle and FinalizeSettle#128
kaze-cow wants to merge 2 commits into
kaze/sc-153-token-2022-programfrom
kaze/sc-153-token-2022-settle

Conversation

@kaze-cow

@kaze-cow kaze-cow commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Add support for Token2022. Both token programs can be used in the same settlement without issues.

Motivation

BeginSettle and FinalizeSettle each took a token_program account and rejected anything but the legacy SPL Token program. As settlements may need to pull or push from many different in the same settlement, we need a way to specify both.

Methodology

Rather than including one token program per order, since there are only two possible token programs we are ever anticipating needing to support, we add the token2022 program to the list of fixed accounts for both BeginSettle and FinalizeSettle.

If either the token2022 program or the SPL token program is not required to complete the relevant settlement instruction, they can be excluded without incurring account cost by setting the slot to the system program.

Compute cost

bench-report.json is regenerated. The settle benchmarks each gain one account, 34 transaction bytes, and 50–170 CU on top of the Token-2022 dispatch above.

Out of scope

Integration tests are expanded in #121

Test Plan

Verify methodology.

🤖 Generated with Claude Code

@kaze-cow
kaze-cow requested a review from a team as a code owner August 28, 2026 07:23
@linear-code

linear-code Bot commented Aug 28, 2026

Copy link
Copy Markdown

SC-153

kaze-cow and others added 2 commits August 28, 2026 17:23
`BeginSettle` and `FinalizeSettle` each took a `token_program` account and
rejected anything that wasn't the legacy SPL Token program, so the buffers
`CreateBuffer` can now open under Token-2022 had no way to be settled. They
accept it too, and issue every transfer against whichever of the two they were
handed, through the same `token::validate_token_program` gate the buffer
instructions use.

Token-2022 encodes `Transfer` exactly as the legacy program does, so only the
CPI target changes. What differs is the account data: a Token-2022 account
carrying extensions is longer than the base layout, and the legacy reader
insists on an exact length. Both sides now read through
`token::read_token_account`, which dispatches on the validated program — the
sell account's owner in `BeginSettle`, the destination's mint in
`FinalizeSettle`.

That reader grows the `mint` and `owner` fields the settlement needs and the
buffer instructions didn't, which costs `ReclaimBuffer` a little: its
`max_buffers_in_one_instruction` goes 137,046 -> 138,392 CU for the wider read.
The settle benchmarks rise 0.3-1.6% from the added dispatch.

The `token_program` account is shared by the whole instruction, so every token
one settlement touches must live under the same program; a mixed settlement
still needs two instruction pairs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`BeginSettle` and `FinalizeSettle` took one `token_program` account, so
every token an instruction touched had to live under the same program:
settling a legacy SPL mint and a Token-2022 mint meant two instruction
pairs. They now take one account per supported program, at fixed
positions after the state PDA, and issue each transfer against the
program that owns the account it moves. One pair can settle both, and the
two sides of a single order need not agree — the pull follows the sell
account's owner, the push the buy account's.

A program the settlement doesn't touch is left out by putting the system
program in its slot. The transfers still need their program named by the
transaction, so the placeholder is how an instruction says this one isn't;
in a transaction that already references the system program it costs an
account index instead of another 32-byte address.

Nothing new goes into the instruction data: the owner is the authority on
which program an account belongs to, and the slots only decide whether
the settlement can reach it. Resolving an account gives one of three
answers:

- owned by a carried program: its transfers CPI into that program;
- owned by a supported program whose slot holds the placeholder: the new
  `SettlementError::TokenProgramNotProvided` (36), so a forgotten slot
  reads as itself rather than as a malformed account;
- owned by neither: the existing `SellTokenAccountInvalid` /
  `InvalidBuyTokenAccount`, unchanged.

The slots are positional. Each holds its own program or the placeholder;
anything else, swapping the two included, is `IncorrectProgramId`.

`FINALIZE_FIXED_ACCOUNTS` becomes 4, which `push_destinations` follows on
its own. `CreateBuffer` and `ReclaimBuffer` keep their single
`token_program` account: each works on one mint at a time, so there is
nothing to mix.

The settle benchmarks each gain one account, 34 transaction bytes, and
50-170 CU. The one- and two-unit drift on the unrelated create/reclaim
lines is codegen, not behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kaze-cow
kaze-cow force-pushed the kaze/sc-153-token-2022-settle branch from 7af181d to 35bb3fe Compare August 28, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant