diff --git a/CHANGELOG b/CHANGELOG index f64ea18..49e59e1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p --- +## 0.1.7 - 2026-07-03 + +### Added + +1. Added `Solace::Instructions::ComputeBudget::SetComputeUnitPriceInstruction` for setting a transaction's priority fee +2. Added `Solace::Instructions::ComputeBudget::SetComputeUnitLimitInstruction` for capping a transaction's compute units +3. Added `Solace::Composers::ComputeBudgetProgramSetComputeUnitPriceComposer` +4. Added `Solace::Composers::ComputeBudgetProgramSetComputeUnitLimitComposer` + +### Changed + +### Fixed + +--- + ## 0.1.6 - 2026-06-22 ### Added diff --git a/README.md b/README.md index fd2c036..4d115e8 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ more control. Every operation is reachable at more than one level. messages (legacy and versioned), instructions, account context, and address lookup tables. - **Building transactions** — instruction builders, composers, the transaction composer, and program clients. -- **Programs** — the System program, SPL Token, Token-2022, and the Associated Token - Account program. +- **Programs** — the System program, SPL Token, Token-2022, the Associated Token + Account program, and Compute Budget. - **Reference** — codecs, PDA derivation, Curve25519, constants, serialization, tokens, and errors. diff --git a/gem/Gemfile.lock b/gem/Gemfile.lock index 8290e6f..0b0753b 100644 --- a/gem/Gemfile.lock +++ b/gem/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - solace (0.1.6) + solace (0.1.7) base58 (~> 0.2) ffi (~> 1.15) rbnacl (~> 7.0) diff --git a/gem/lib/solace/composers/compute_budget_program_set_compute_unit_limit_composer.rb b/gem/lib/solace/composers/compute_budget_program_set_compute_unit_limit_composer.rb new file mode 100644 index 0000000..ed8fd93 --- /dev/null +++ b/gem/lib/solace/composers/compute_budget_program_set_compute_unit_limit_composer.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Solace + module Composers + # Composer for creating a compute budget set compute unit limit instruction. + # + # This composer resolves and orders the required accounts for a `SetComputeUnitLimit` + # instruction, sets up their access permissions, and delegates construction to the + # appropriate instruction builder (`Instructions::ComputeBudget::SetComputeUnitLimitInstruction`). + # + # It is used for capping the compute units a transaction may consume. + # + # Required accounts: + # - **Program**: Compute Budget program (readonly, non-signer) + # + # @example Compose and build a set compute unit limit instruction + # composer = ComputeBudgetProgramSetComputeUnitLimitComposer.new( + # units: 200_000 + # ) + # + # @see Instructions::ComputeBudget::SetComputeUnitLimitInstruction + # @since 0.1.7 + class ComputeBudgetProgramSetComputeUnitLimitComposer < Base + # Extracts the compute unit limit from the params + # + # @return [Integer] The compute unit limit + def units + params[:units] + end + + # Returns the compute budget program id + # + # @return [String] The compute budget program id + def compute_budget_program + Constants::COMPUTE_BUDGET_PROGRAM_ID.to_s + end + + # Setup accounts required for set compute unit limit instruction + # Called automatically during initialization + # + # @return [void] + def setup_accounts + account_context.add_readonly_nonsigner(compute_budget_program) + end + + # Build instruction with resolved account indices + # + # @param account_context [Utils::AccountContext] The account context + # @return [Solace::Instruction] + def build_instruction(account_context) + Instructions::ComputeBudget::SetComputeUnitLimitInstruction.build( + units: units, + program_index: account_context.index_of(compute_budget_program) + ) + end + end + end +end diff --git a/gem/lib/solace/composers/compute_budget_program_set_compute_unit_price_composer.rb b/gem/lib/solace/composers/compute_budget_program_set_compute_unit_price_composer.rb new file mode 100644 index 0000000..a278dbe --- /dev/null +++ b/gem/lib/solace/composers/compute_budget_program_set_compute_unit_price_composer.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Solace + module Composers + # Composer for creating a compute budget set compute unit price instruction. + # + # This composer resolves and orders the required accounts for a `SetComputeUnitPrice` + # instruction, sets up their access permissions, and delegates construction to the + # appropriate instruction builder (`Instructions::ComputeBudget::SetComputeUnitPriceInstruction`). + # + # It is used for attaching a priority fee to a transaction. + # + # Required accounts: + # - **Program**: Compute Budget program (readonly, non-signer) + # + # @example Compose and build a set compute unit price instruction + # composer = ComputeBudgetProgramSetComputeUnitPriceComposer.new( + # micro_lamports: 50_000 + # ) + # + # @see Instructions::ComputeBudget::SetComputeUnitPriceInstruction + # @since 0.1.7 + class ComputeBudgetProgramSetComputeUnitPriceComposer < Base + # Extracts the price per compute unit from the params + # + # @return [Integer] The price per compute unit (in micro-lamports) + def micro_lamports + params[:micro_lamports] + end + + # Returns the compute budget program id + # + # @return [String] The compute budget program id + def compute_budget_program + Constants::COMPUTE_BUDGET_PROGRAM_ID.to_s + end + + # Setup accounts required for set compute unit price instruction + # Called automatically during initialization + # + # @return [void] + def setup_accounts + account_context.add_readonly_nonsigner(compute_budget_program) + end + + # Build instruction with resolved account indices + # + # @param account_context [Utils::AccountContext] The account context + # @return [Solace::Instruction] + def build_instruction(account_context) + Instructions::ComputeBudget::SetComputeUnitPriceInstruction.build( + micro_lamports: micro_lamports, + program_index: account_context.index_of(compute_budget_program) + ) + end + end + end +end diff --git a/gem/lib/solace/instructions/compute_budget/set_compute_unit_limit_instruction.rb b/gem/lib/solace/instructions/compute_budget/set_compute_unit_limit_instruction.rb new file mode 100644 index 0000000..5756888 --- /dev/null +++ b/gem/lib/solace/instructions/compute_budget/set_compute_unit_limit_instruction.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Solace + module Instructions + module ComputeBudget + # Instruction for setting the compute unit limit. + # + # This instruction is used to set the maximum number of compute units a + # transaction may consume. Together with the compute unit price, it + # determines the priority fee the transaction pays. + # + # @example Build a SetComputeUnitLimit instruction + # instruction = Solace::Instructions::ComputeBudget::SetComputeUnitLimitInstruction.build( + # units: 200_000, + # program_index: 1 + # ) + # + # @since 0.1.7 + class SetComputeUnitLimitInstruction + # @!attribute [Array] INSTRUCTION_INDEX + # Instruction index for the Compute Budget Program's SetComputeUnitLimit instruction. + INSTRUCTION_INDEX = [2].freeze + + # Builds a Solace::Instruction for setting the compute unit limit + # + # @param units [Integer] Maximum compute units the transaction may consume + # @param program_index [Integer] Index of the Compute Budget program in the transaction's accounts + # @return [Solace::Instruction] + def self.build(units:, program_index:) + Solace::Instruction.new.tap do |ix| + ix.program_index = program_index + ix.accounts = [] + ix.data = data(units) + end + end + + # Instruction data for a set compute unit limit instruction + # + # The BufferLayout is: + # - [Instruction Index (1 byte)] + # - [Compute unit limit (4 bytes little-endian u32)] + # + # @param units [Integer] Maximum compute units the transaction may consume + # @return [Array] 1-byte instruction index + 4-byte limit + def self.data(units) + INSTRUCTION_INDEX + Solace::Utils::Codecs.encode_le_u32(units).bytes + end + end + end + end +end diff --git a/gem/lib/solace/instructions/compute_budget/set_compute_unit_price_instruction.rb b/gem/lib/solace/instructions/compute_budget/set_compute_unit_price_instruction.rb new file mode 100644 index 0000000..0378aa8 --- /dev/null +++ b/gem/lib/solace/instructions/compute_budget/set_compute_unit_price_instruction.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Solace + module Instructions + # The ComputeBudget module contains instruction builders for the Compute Budget Program. + # + # The Compute Budget program prices and provisions a transaction's execution. Its + # instructions take no accounts; each encodes a directive the runtime reads when + # scheduling and executing the transaction, such as the priority fee attached to it. + # + # This module contains classes that build the low-level instruction data required + # to interact with the Compute Budget Program. + # + # @see https://docs.solana.com/developing/programming-model/runtime#compute-budget + # @since 0.1.7 + module ComputeBudget + # Instruction for setting the compute unit price. + # + # This instruction is used to set the price (in micro-lamports per compute unit) + # a transaction pays as a priority fee, which validators use to order it during + # congestion. + # + # @example Build a SetComputeUnitPrice instruction + # instruction = Solace::Instructions::ComputeBudget::SetComputeUnitPriceInstruction.build( + # micro_lamports: 50_000, + # program_index: 1 + # ) + # + # @since 0.1.7 + class SetComputeUnitPriceInstruction + # @!attribute [Array] INSTRUCTION_INDEX + # Instruction index for the Compute Budget Program's SetComputeUnitPrice instruction. + INSTRUCTION_INDEX = [3].freeze + + # Builds a Solace::Instruction for setting the compute unit price + # + # @param micro_lamports [Integer] Price per compute unit (in micro-lamports) + # @param program_index [Integer] Index of the Compute Budget program in the transaction's accounts + # @return [Solace::Instruction] + def self.build(micro_lamports:, program_index:) + Solace::Instruction.new.tap do |ix| + ix.program_index = program_index + ix.accounts = [] + ix.data = data(micro_lamports) + end + end + + # Instruction data for a set compute unit price instruction + # + # The BufferLayout is: + # - [Instruction Index (1 byte)] + # - [Price (8 bytes little-endian u64)] + # + # @param micro_lamports [Integer] Price per compute unit (in micro-lamports) + # @return [Array] 1-byte instruction index + 8-byte price + def self.data(micro_lamports) + INSTRUCTION_INDEX + Solace::Utils::Codecs.encode_le_u64(micro_lamports).bytes + end + end + end + end +end diff --git a/gem/lib/solace/version.rb b/gem/lib/solace/version.rb index 48ceb6f..c4be1f9 100644 --- a/gem/lib/solace/version.rb +++ b/gem/lib/solace/version.rb @@ -2,5 +2,5 @@ module Solace # Latest version of the Solace gem. - VERSION = '0.1.6' + VERSION = '0.1.7' end diff --git a/gem/test/solace/composers/compute_budget_program_set_compute_unit_limit_composer_test.rb b/gem/test/solace/composers/compute_budget_program_set_compute_unit_limit_composer_test.rb new file mode 100644 index 0000000..db95665 --- /dev/null +++ b/gem/test/solace/composers/compute_budget_program_set_compute_unit_limit_composer_test.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'test_helper' + +describe Solace::Composers::ComputeBudgetProgramSetComputeUnitLimitComposer do + let(:bob) { Fixtures.load_keypair('bob') } + let(:anna) { Fixtures.load_keypair('anna') } + + let(:connection) { Solace::Connection.new(commitment: 'processed') } + let(:transaction_composer) { Solace::TransactionComposer.new(connection: connection) } + + let(:composer) do + Solace::Composers::ComputeBudgetProgramSetComputeUnitLimitComposer.new( + units: 20_000 + ) + end + + let(:transfer_composer) do + Solace::Composers::SystemProgramTransferComposer.new( + to: anna, + from: bob, + lamports: 10_000 + ) + end + + describe 'composed transaction' do + before(:all) do + # Add instructions and set fee payer + transaction_composer.add_instruction(composer) + transaction_composer.add_instruction(transfer_composer) + transaction_composer.set_fee_payer(bob) + + # Compose and decode the transaction message + @decoded_message = Solace::Transaction.from(transaction_composer.compose_transaction.serialize).message + end + + it 'includes the set compute unit limit instruction' do + instructions = @decoded_message.instructions.map { |ix| [@decoded_message.accounts[ix.program_index], ix.data] } + + assert_includes instructions, [ + Solace::Constants::COMPUTE_BUDGET_PROGRAM_ID, + [2] + [20_000].pack('L<').bytes + ] + end + end + + describe 'transaction consuming fewer compute units than the limit' do + before(:all) do + # Add instructions and set fee payer + transaction_composer.add_instruction(composer) + transaction_composer.add_instruction(transfer_composer) + transaction_composer.set_fee_payer(bob) + + # Compose and sign transaction + tx = transaction_composer.compose_transaction + tx.sign(bob) + + # Send transaction + @signature = connection.send_transaction(tx.serialize) + end + + it 'is confirmed by the node' do + assert(connection.wait_for_confirmed_signature { @signature['result'] }) + end + end + + describe 'transaction exceeding the compute unit limit' do + # Requests fewer units than the transaction needs + let(:composer) do + Solace::Composers::ComputeBudgetProgramSetComputeUnitLimitComposer.new( + units: 100 + ) + end + + before(:all) do + # Add instructions and set fee payer + transaction_composer.add_instruction(composer) + transaction_composer.add_instruction(transfer_composer) + transaction_composer.set_fee_payer(bob) + + # Compose and sign transaction + @transaction = transaction_composer.compose_transaction + @transaction.sign(bob) + end + + it 'is rejected by the node' do + error = assert_raises(Solace::Errors::RPCError) do + connection.send_transaction(@transaction.serialize) + end + + assert_match(/exceeded/i, error.message) + end + end +end diff --git a/gem/test/solace/composers/compute_budget_program_set_compute_unit_price_composer_test.rb b/gem/test/solace/composers/compute_budget_program_set_compute_unit_price_composer_test.rb new file mode 100644 index 0000000..8390256 --- /dev/null +++ b/gem/test/solace/composers/compute_budget_program_set_compute_unit_price_composer_test.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require 'test_helper' + +describe Solace::Composers::ComputeBudgetProgramSetComputeUnitPriceComposer do + let(:bob) { Fixtures.load_keypair('bob') } + let(:anna) { Fixtures.load_keypair('anna') } + let(:payer) { Fixtures.load_keypair('payer') } + + let(:connection) { Solace::Connection.new(commitment: 'processed') } + let(:transaction_composer) { Solace::TransactionComposer.new(connection: connection) } + + let(:composer) do + Solace::Composers::ComputeBudgetProgramSetComputeUnitPriceComposer.new( + micro_lamports: 1_000_000 + ) + end + + let(:transfer_composer) do + Solace::Composers::SystemProgramTransferComposer.new( + to: anna, + from: bob, + lamports: 10_000 + ) + end + + describe 'composed transaction' do + let(:decoded_message) do + transaction_composer.add_instruction(composer) + transaction_composer.add_instruction(transfer_composer) + transaction_composer.set_fee_payer(bob) + + Solace::Transaction.from(transaction_composer.compose_transaction.serialize).message + end + + it 'includes the set compute unit price instruction' do + instructions = decoded_message.instructions.map { |ix| [decoded_message.accounts[ix.program_index], ix.data] } + + assert_includes instructions, [ + Solace::Constants::COMPUTE_BUDGET_PROGRAM_ID, + [3] + [1_000_000].pack('Q<').bytes + ] + end + end + + describe 'sponsored transaction' do + before(:all) do + # Get starting balance + @payer_starting_balance = connection.get_balance(payer.address) + + # Add instructions and set fee payer + transaction_composer.add_instruction(composer) + transaction_composer.add_instruction(transfer_composer) + transaction_composer.set_fee_payer(payer) + + # Compose and sign transaction + tx = transaction_composer.compose_transaction + tx.sign(payer, bob) + + # Send transaction and wait for confirmation + @signature = connection.send_transaction(tx.serialize) + connection.wait_for_confirmed_signature { @signature['result'] } + + # Get ending balance + @payer_ending_balance = connection.get_balance(payer.address) + end + + it 'generates a valid transaction' do + assert(connection.wait_for_confirmed_signature { @signature['result'] }) + end + + it 'deducts the priority fee from the payer' do + # 2 signatures + 5000 lamports per signature + a priority fee for the runtime's default compute unit limit + assert_operator @payer_ending_balance, :<, @payer_starting_balance - (2 * 5000) + end + end + + describe 'non-sponsored transaction' do + before(:all) do + # Get starting balance + @bob_starting_balance = connection.get_balance(bob.address) + + # Add instructions and set fee payer + transaction_composer.add_instruction(composer) + transaction_composer.add_instruction(transfer_composer) + transaction_composer.set_fee_payer(bob) + + # Compose and sign transaction + tx = transaction_composer.compose_transaction + tx.sign(bob) + + # Send transaction and wait for confirmation + @signature = connection.send_transaction(tx.serialize) + connection.wait_for_confirmed_signature { @signature['result'] } + + # Get ending balance + @bob_ending_balance = connection.get_balance(bob.address) + end + + it 'generates a valid transaction' do + assert(connection.wait_for_confirmed_signature { @signature['result'] }) + end + + it 'deducts the priority fee from the sender' do + # 10_000 lamport transfer + 1 signature + 5000 lamports per signature + a priority fee + assert_operator @bob_ending_balance, :<, @bob_starting_balance - (10_000 + 5000) + end + end +end diff --git a/gem/test/solace/instructions/compute_budget/set_compute_unit_limit_instruction_test.rb b/gem/test/solace/instructions/compute_budget/set_compute_unit_limit_instruction_test.rb new file mode 100644 index 0000000..c82e2e0 --- /dev/null +++ b/gem/test/solace/instructions/compute_budget/set_compute_unit_limit_instruction_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'test_helper' + +describe Solace::Instructions::ComputeBudget::SetComputeUnitLimitInstruction do + describe '.build' do + # Build a set compute unit limit instruction + let(:ix) do + Solace::Instructions::ComputeBudget::SetComputeUnitLimitInstruction.build( + units: 200_000, + program_index: 1 + ) + end + + it 'returns an instruction' do + assert_kind_of Solace::Instruction, ix + end + + it 'sets the program index' do + assert_equal 1, ix.program_index + end + + it 'has no accounts' do + assert_equal [], ix.accounts + end + + it 'has the correct data' do + assert_equal [2] + [200_000].pack('L<').bytes, ix.data + end + end +end diff --git a/gem/test/solace/instructions/compute_budget/set_compute_unit_price_instruction_test.rb b/gem/test/solace/instructions/compute_budget/set_compute_unit_price_instruction_test.rb new file mode 100644 index 0000000..9b287ce --- /dev/null +++ b/gem/test/solace/instructions/compute_budget/set_compute_unit_price_instruction_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'test_helper' + +describe Solace::Instructions::ComputeBudget::SetComputeUnitPriceInstruction do + describe '.build' do + # Build a set compute unit price instruction + let(:ix) do + Solace::Instructions::ComputeBudget::SetComputeUnitPriceInstruction.build( + micro_lamports: 50_000, + program_index: 1 + ) + end + + it 'returns an instruction' do + assert_kind_of Solace::Instruction, ix + end + + it 'sets the program index' do + assert_equal 1, ix.program_index + end + + it 'has no accounts' do + assert_equal [], ix.accounts + end + + it 'has the correct data' do + assert_equal [3] + [50_000].pack('Q<').bytes, ix.data + end + end +end diff --git a/site/.vitepress/config.ts b/site/.vitepress/config.ts index ea725c4..2d3ec9f 100644 --- a/site/.vitepress/config.ts +++ b/site/.vitepress/config.ts @@ -62,6 +62,7 @@ export default defineConfig({ { text: 'SPL Token', link: '/programs/spl-token' }, { text: 'Token-2022', link: '/programs/token-2022' }, { text: 'Associated Token Account', link: '/programs/associated-token-account' }, + { text: 'Compute Budget', link: '/programs/compute-budget' }, ], }, { diff --git a/site/building/composers.md b/site/building/composers.md index 4f1cf2d..3e9cb9a 100644 --- a/site/building/composers.md +++ b/site/building/composers.md @@ -38,6 +38,7 @@ You construct composers with addresses and domain arguments; you never compute i | **SPL Token** | `SplTokenProgramInitializeMintComposer`, `SplTokenProgramMintToComposer`, `SplTokenProgramTransferComposer`, `SplTokenProgramTransferCheckedComposer`, `SplTokenProgramCloseAccountComposer` | | **Token-2022** | `Token2022Program…` — the same set for the Token-2022 program | | **Associated Token Account** | `AssociatedTokenAccountProgramCreateAccountComposer`, `AssociatedTokenAccountProgramCreateIdempotentAccountComposer` | +| **Compute Budget** | `ComputeBudgetProgramSetComputeUnitPriceComposer`, `ComputeBudgetProgramSetComputeUnitLimitComposer` | ## The Base contract diff --git a/site/building/instruction-builders.md b/site/building/instruction-builders.md index b460b98..273e9b7 100644 --- a/site/building/instruction-builders.md +++ b/site/building/instruction-builders.md @@ -38,6 +38,7 @@ program. The returned instruction's `data` is the fully-encoded byte payload. | **SPL Token** (`Instructions::SplToken`) | `InitializeMintInstruction`, `InitializeAccountInstruction`, `MintToInstruction`, `TransferInstruction`, `TransferCheckedInstruction`, `CloseAccountInstruction` | | **Token-2022** (`Instructions::Token2022`) | the same set as SPL Token, targeting the Token-2022 program | | **Associated Token Account** (`Instructions::AssociatedTokenAccount`) | `CreateAccountInstruction`, `CreateIdempotentAccountInstruction` | +| **Compute Budget** (`Instructions::ComputeBudget`) | `SetComputeUnitPriceInstruction`, `SetComputeUnitLimitInstruction` | ## Resolving indices diff --git a/site/index.md b/site/index.md index dbd6be7..cc5145f 100644 --- a/site/index.md +++ b/site/index.md @@ -57,8 +57,9 @@ you more control. Every operation is reachable at more than one level — see [transaction composer](/building/transaction-composer), and [program clients](/building/program-clients). - **Programs** — the [System Program](/programs/system-program), - [SPL Token](/programs/spl-token), [Token-2022](/programs/token-2022), and the - [Associated Token Account](/programs/associated-token-account) program. + [SPL Token](/programs/spl-token), [Token-2022](/programs/token-2022), the + [Associated Token Account](/programs/associated-token-account) program, and + [Compute Budget](/programs/compute-budget). - **Reference** — [codecs](/reference/codecs), [PDA derivation](/reference/pda), [Curve25519](/reference/curve25519), [constants](/reference/constants), [serialization](/reference/serialization), [tokens](/reference/tokens), and diff --git a/site/programs/compute-budget.md b/site/programs/compute-budget.md new file mode 100644 index 0000000..a160583 --- /dev/null +++ b/site/programs/compute-budget.md @@ -0,0 +1,129 @@ +--- +title: Compute Budget +--- + +# Compute Budget + +The Compute Budget program prices and provisions a transaction's execution: the compute +unit limit it may consume, and the priority fee validators use to order it during +congestion. Solace ships [instruction builders](/building/instruction-builders) and +[composers](/building/composers) for both. There is no dedicated `Programs::ComputeBudget` +client — these instructions only ever ride along in another transaction, so the composer +layer is the natural top level. + +Program ID: `Solace::Constants::COMPUTE_BUDGET_PROGRAM_ID` (`ComputeBudget111111111111111111111111111111`). + +## Set compute unit price + +Attach a priority fee, priced in micro-lamports per compute unit. The total priority fee a +transaction pays is this price multiplied by its compute unit limit. + +### Composer — `ComputeBudgetProgramSetComputeUnitPriceComposer` + +| Parameter | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `micro_lamports` | Integer | yes | — | Price per compute unit, in micro-lamports. | + +```ruby +tx = Solace::TransactionComposer.new(connection:) + .add_instruction( + Solace::Composers::ComputeBudgetProgramSetComputeUnitPriceComposer.new( + micro_lamports: 50_000 + ) + ) + .add_instruction( + Solace::Composers::SystemProgramTransferComposer.new( + from: payer.address, + to: recipient.address, + lamports: 1_000_000 + ) + ) + .set_fee_payer(payer.address) + .compose_transaction + +tx.sign(payer) +connection.send_transaction(tx.serialize) +``` + +### Low-level instruction (advanced) + +`Instructions::ComputeBudget::SetComputeUnitPriceInstruction.build` encodes the raw +instruction. It references no accounts — only the program itself. + +- **Encodes (`data`):** `u8(3)` (the `SetComputeUnitPrice` discriminator) + + `le_u64(micro_lamports)` + +| Parameter | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `micro_lamports` | Integer | yes | — | Price per compute unit, in micro-lamports. | +| `program_index` | Integer | yes | — | Index of the Compute Budget program in the account list. | + +```ruby +ix = Solace::Instructions::ComputeBudget::SetComputeUnitPriceInstruction.build( + micro_lamports: 50_000, + program_index: context.index_of(Solace::Constants::COMPUTE_BUDGET_PROGRAM_ID) +) +``` + +## Set compute unit limit + +Cap the compute units the transaction may consume — and, when paired with a compute unit +price, pin down the exact priority fee it pays. + +### Composer — `ComputeBudgetProgramSetComputeUnitLimitComposer` + +| Parameter | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `units` | Integer | yes | — | Maximum compute units the transaction may consume. | + +```ruby +tx = Solace::TransactionComposer.new(connection:) + .add_instruction( + Solace::Composers::ComputeBudgetProgramSetComputeUnitLimitComposer.new( + units: 200_000 + ) + ) + .add_instruction( + Solace::Composers::ComputeBudgetProgramSetComputeUnitPriceComposer.new( + micro_lamports: 50_000 + ) + ) + .add_instruction( + Solace::Composers::SystemProgramTransferComposer.new( + from: payer.address, + to: recipient.address, + lamports: 1_000_000 + ) + ) + .set_fee_payer(payer.address) + .compose_transaction + +tx.sign(payer) +connection.send_transaction(tx.serialize) # pays a 10_000-lamport priority fee +``` + +### Low-level instruction (advanced) + +`Instructions::ComputeBudget::SetComputeUnitLimitInstruction.build` encodes the raw +instruction. It references no accounts — only the program itself. + +- **Encodes (`data`):** `u8(2)` (the `SetComputeUnitLimit` discriminator) + + `le_u32(units)` + +| Parameter | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `units` | Integer | yes | — | Maximum compute units the transaction may consume. | +| `program_index` | Integer | yes | — | Index of the Compute Budget program in the account list. | + +```ruby +ix = Solace::Instructions::ComputeBudget::SetComputeUnitLimitInstruction.build( + units: 200_000, + program_index: context.index_of(Solace::Constants::COMPUTE_BUDGET_PROGRAM_ID) +) +``` + +::: tip +Compute Budget instructions conventionally sit first in a transaction — use +`prepend_instruction` on the [transaction composer](/building/transaction-composer) to +slot them ahead of instructions you've already added. +:::