Skip to content

feat(sdk-coin-sol): add explicit compute-unit-limit setter to TransactionBuilder - #9851

Draft
ralph-bitgo[bot] wants to merge 1 commit into
masterfrom
CHALO-1650-sol-compute-unit-limit
Draft

ralph-bitgo[bot] wants to merge 1 commit into
masterfrom
CHALO-1650-sol-compute-unit-limit

Conversation

@ralph-bitgo

@ralph-bitgo ralph-bitgo Bot commented Sep 29, 2026

Copy link
Copy Markdown

What

Adds an explicit compute-unit-limit setter to the Solana TransactionBuilder in modules/sdk-coin-sol:

  • setComputeUnitLimit(units) on the base TransactionBuilder (and therefore every subclass), validating an integer between 1 and the on-chain per-transaction maximum of 1,400,000 (MAX_COMPUTE_UNIT_LIMIT in constants.ts).
  • buildLegacyTransaction() prepends a SetComputeUnitLimit instruction ahead of every entry of _instructionsData, so the built order is [AdvanceNonce?, SetComputeUnitLimit, SetComputeUnitPrice?, …] (web3.js serializes the durable-nonce AdvanceNonceAccount first via nonceInfo). The instruction is recorded in _instructionsData, mirroring how memo is recorded, so explain and parsing see it. When no limit is set the built bytes are unchanged.
  • initBuilder() restores _computeUnitLimit from a parsed transaction (the way SetPriorityFee is restored today), so a signing-phase rebuild from raw is byte-identical.
  • The per-type parsers (parseStakingActivate/Deactivate/Delegate/Withdraw, parseAtaInit/AtaClose, parseWalletInit) now accept leading compute-budget instructions through a shared parseComputeBudgetInstruction() helper (previously only parseSendInstructions did), and matchTransactionTypeByInstructionsOrder skips leading compute-budget instructions like it already skips AdvanceNonceAccount. Without this, raw transactions carrying a limit would misclassify (staking/ATA types fell through to CustomTx) and the round trip would drop the instruction.
  • No subclass changes were needed for the rebuild-duplication concern: all nine builders rebuild _instructionsData from their own params before buildLegacyTransaction() re-adds the instruction from _computeUnitLimit, so it is added exactly once (covered by a build-twice test per builder).

Why

The priority fee is charged on the requested compute-unit limit, not the units actually consumed, so every sponsored transaction must set an explicit, intent-sized limit (SOL Gasless Fee Payer technical design §4.1, invariant 8; PRD CHALO-983). The SetComputeUnitLimit instruction type and its parsing already existed, but no builder emitted it. This is a prerequisite for the SOL relayer/gasless fee-payer work (CHALO-1516).

Note on reach: wallet-platform consumes this module as @bitgo-beta/sdk-coin-sol, so the change reaches WP only after a beta release and the bump ticket.

Test plan

  • test/unit/transactionBuilder/computeUnitLimit.ts: for each of the nine builders (transfer, transferV2, token transfer, ATA init, close ATA, staking activate/deactivate/delegate/withdraw) plus wallet init — with a limit the compiled instruction order is [AdvanceNonce?, SetComputeUnitLimit, SetComputeUnitPrice?, …] and the instruction appears exactly once; from(raw) → build() → toBroadcastFormat() is byte-identical and the limit survives the round trip; building twice on the same builder does not duplicate the instruction; without a limit the bytes equal the pinned pre-change fixtures.
  • Validation tests: rejects 0, -1, 1.5, 1,400,001, NaN; accepts the boundaries 1 and 1,400,000.
  • Signed rebuild round trip keeps the signature and the limit.
  • Full module suite: BITGOJS_TEST_PASSWORD=x npx mocha — 803 passing, 0 failing (baseline 758 + 45 new).
  • npm run lint and tsc --noEmit clean in modules/sdk-coin-sol.

Ticket: CHALO-1650

What: add setComputeUnitLimit(units) to the Solana TransactionBuilder.
It validates an integer between 1 and the on-chain maximum of
1,400,000. When set, buildLegacyTransaction() prepends a
SetComputeUnitLimit instruction ahead of every entry of
_instructionsData (after the durable-nonce AdvanceNonceAccount,
before the optional SetComputeUnitPrice) and records it in
_instructionsData the way memo is, so explain and parsing see it.
initBuilder() restores the limit from a parsed transaction, so a
signing-phase rebuild from raw is byte-identical, and with no limit
set the built bytes are unchanged. The per-type instruction parsers
(send, staking, ATA and wallet init) now accept compute-budget
instructions through a shared helper, and
matchTransactionTypeByInstructionsOrder skips leading compute-budget
instructions, so raw transactions that carry a limit still classify
and round trip for every builder.

Why: the priority fee is charged on the requested compute-unit limit
rather than the units actually consumed, so every sponsored
transaction must set an explicit, intent-sized limit (SOL Gasless
Fee Payer TDD section 4.1, invariant 8). The instruction type already
existed but no builder emitted it, leaving sponsored transactions
without a compute cap.

Ticket: CHALO-1650
Session-Id: 3662d15a-5b50-4187-a602-918f68e8ee2f
Task-Id: cc1ab97e-1b0c-42f2-8440-deaee2d552b3
@linear-code

linear-code Bot commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

CHALO-1650

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Unit tests are failing on Node 26.x (Current release line, non-blocking). This is not an LTS version yet, so it does not block merge, but it signals an incompatibility to fix before Node 26.x becomes LTS.

View run

This branch has not been deployed

No deployments
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.

2 participants