solana-ruby-kit 0.1.0 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/solana/ruby/kit/programs/associated_token_account.rb +133 -0
- data/lib/solana/ruby/kit/programs/system_program.rb +69 -0
- data/lib/solana/ruby/kit/programs.rb +2 -0
- data/lib/solana/ruby/kit/rpc/api/send_transaction.rb +2 -1
- data/lib/solana/ruby/kit/transactions/compiler.rb +192 -0
- data/lib/solana/ruby/kit/transactions.rb +1 -0
- data/lib/solana/ruby/kit/version.rb +1 -1
- data/solana-ruby-kit.gemspec +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8be22a1330f9190db447c2a209a2dc44991c75efc6758bbe9e94f6e56b579473
|
|
4
|
+
data.tar.gz: c33dc52550866af9c572ce7911fe0baa9d053dc0f4723e5b700f0473ee5ffee4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee653c6ea8f2b6b2dd5359e6d49281431d9127db48600d836d560ebd32dde7e75294fc6cc44f8b609de4fb0bc565bf3e810a30ca050ce78d7e8506177e7ba6cd
|
|
7
|
+
data.tar.gz: 5483176779980f87b8102e81dca85afc4f71a413281b5b296d50edee38ebae9b000c370d75362a442abd84589ec6014b661e075623aa5ba7cf5920df0b20bd4b
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../addresses/address'
|
|
5
|
+
require_relative '../addresses/program_derived_address'
|
|
6
|
+
require_relative '../instructions/instruction'
|
|
7
|
+
require_relative '../instructions/accounts'
|
|
8
|
+
|
|
9
|
+
module Solana::Ruby::Kit
|
|
10
|
+
module Programs
|
|
11
|
+
# Ruby interface for the SPL Associated Token Account on-chain program.
|
|
12
|
+
#
|
|
13
|
+
# An Associated Token Account (ATA) is a Program Derived Address that holds
|
|
14
|
+
# SPL token balances for a given wallet + mint pair. The ATA address is
|
|
15
|
+
# deterministic: given a wallet and a mint, there is exactly one "canonical"
|
|
16
|
+
# token account for that combination.
|
|
17
|
+
#
|
|
18
|
+
# Seeds used for PDA derivation (all raw 32-byte address buffers):
|
|
19
|
+
# [ wallet_bytes, token_program_bytes, mint_bytes ]
|
|
20
|
+
# Program: PROGRAM_ID (the Associated Token Account program)
|
|
21
|
+
#
|
|
22
|
+
# Reference: https://github.com/solana-program/associated-token-account
|
|
23
|
+
module AssociatedTokenAccount
|
|
24
|
+
extend T::Sig
|
|
25
|
+
|
|
26
|
+
# SPL Associated Token Account program.
|
|
27
|
+
PROGRAM_ID = T.let(
|
|
28
|
+
Addresses::Address.new('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'),
|
|
29
|
+
Addresses::Address
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Original SPL Token program (spl-token).
|
|
33
|
+
TOKEN_PROGRAM_ID = T.let(
|
|
34
|
+
Addresses::Address.new('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
|
|
35
|
+
Addresses::Address
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Token Extensions program (spl-token-2022).
|
|
39
|
+
TOKEN_2022_PROGRAM_ID = T.let(
|
|
40
|
+
Addresses::Address.new('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'),
|
|
41
|
+
Addresses::Address
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Solana System Program.
|
|
45
|
+
SYSTEM_PROGRAM_ID = T.let(
|
|
46
|
+
Addresses::Address.new('11111111111111111111111111111111'),
|
|
47
|
+
Addresses::Address
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
module_function
|
|
51
|
+
|
|
52
|
+
# Derives the canonical Associated Token Account address (a PDA) for a
|
|
53
|
+
# given wallet and mint.
|
|
54
|
+
#
|
|
55
|
+
# @param wallet [Addresses::Address] the account that will own the ATA
|
|
56
|
+
# @param mint [Addresses::Address] the token mint
|
|
57
|
+
# @param token_program_id [Addresses::Address] TOKEN_PROGRAM_ID or TOKEN_2022_PROGRAM_ID
|
|
58
|
+
# @return [Addresses::ProgramDerivedAddress] the ATA address + bump seed
|
|
59
|
+
sig do
|
|
60
|
+
params(
|
|
61
|
+
wallet: Addresses::Address,
|
|
62
|
+
mint: Addresses::Address,
|
|
63
|
+
token_program_id: Addresses::Address
|
|
64
|
+
).returns(Addresses::ProgramDerivedAddress)
|
|
65
|
+
end
|
|
66
|
+
def get_associated_token_address(wallet:, mint:, token_program_id: TOKEN_PROGRAM_ID)
|
|
67
|
+
wallet_bytes = Addresses.decode_address(wallet)
|
|
68
|
+
token_program_bytes = Addresses.decode_address(token_program_id)
|
|
69
|
+
mint_bytes = Addresses.decode_address(mint)
|
|
70
|
+
|
|
71
|
+
Addresses.get_program_derived_address(
|
|
72
|
+
program_address: PROGRAM_ID,
|
|
73
|
+
seeds: [wallet_bytes, token_program_bytes, mint_bytes]
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Builds an instruction that creates an Associated Token Account.
|
|
78
|
+
#
|
|
79
|
+
# Account layout expected by the on-chain program (in order):
|
|
80
|
+
# 0. payer — writable, signer (funds the rent)
|
|
81
|
+
# 1. associated_token_account — writable (the new ATA; derived via PDA)
|
|
82
|
+
# 2. wallet — readonly (the ATA's owner)
|
|
83
|
+
# 3. mint — readonly
|
|
84
|
+
# 4. system_program — readonly
|
|
85
|
+
# 5. token_program — readonly
|
|
86
|
+
#
|
|
87
|
+
# Instruction data:
|
|
88
|
+
# nil / empty → "Create" (fails if the ATA already exists)
|
|
89
|
+
# "\x01" → "CreateIdempotent" (no-op if already initialised)
|
|
90
|
+
#
|
|
91
|
+
# @param payer [Addresses::Address] pays for rent
|
|
92
|
+
# @param wallet [Addresses::Address] will own the ATA
|
|
93
|
+
# @param mint [Addresses::Address] token mint
|
|
94
|
+
# @param token_program_id [Addresses::Address] which token program to use
|
|
95
|
+
# @param idempotent [Boolean] use the idempotent variant
|
|
96
|
+
# @return [Instructions::Instruction]
|
|
97
|
+
sig do
|
|
98
|
+
params(
|
|
99
|
+
payer: Addresses::Address,
|
|
100
|
+
wallet: Addresses::Address,
|
|
101
|
+
mint: Addresses::Address,
|
|
102
|
+
token_program_id: Addresses::Address,
|
|
103
|
+
idempotent: T::Boolean
|
|
104
|
+
).returns(Instructions::Instruction)
|
|
105
|
+
end
|
|
106
|
+
def create_instruction(
|
|
107
|
+
payer:,
|
|
108
|
+
wallet:,
|
|
109
|
+
mint:,
|
|
110
|
+
token_program_id: TOKEN_PROGRAM_ID,
|
|
111
|
+
idempotent: false
|
|
112
|
+
)
|
|
113
|
+
ata = get_associated_token_address(wallet: wallet, mint: mint, token_program_id: token_program_id)
|
|
114
|
+
|
|
115
|
+
# Discriminator: none for "Create", 0x01 for "CreateIdempotent"
|
|
116
|
+
data = idempotent ? "\x01".b : nil
|
|
117
|
+
|
|
118
|
+
Instructions::Instruction.new(
|
|
119
|
+
program_address: PROGRAM_ID,
|
|
120
|
+
accounts: [
|
|
121
|
+
Instructions.writable_signer_account(payer), # 0 payer
|
|
122
|
+
Instructions.writable_account(ata.address), # 1 ATA to create
|
|
123
|
+
Instructions.readonly_account(wallet), # 2 owner (readonly)
|
|
124
|
+
Instructions.readonly_account(mint), # 3 mint
|
|
125
|
+
Instructions.readonly_account(SYSTEM_PROGRAM_ID), # 4 System Program
|
|
126
|
+
Instructions.readonly_account(token_program_id), # 5 Token Program
|
|
127
|
+
],
|
|
128
|
+
data: data
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../addresses/address'
|
|
5
|
+
require_relative '../instructions/instruction'
|
|
6
|
+
require_relative '../instructions/accounts'
|
|
7
|
+
|
|
8
|
+
module Solana::Ruby::Kit
|
|
9
|
+
module Programs
|
|
10
|
+
# Ruby interface for the Solana System Program (11111111111111111111111111111111).
|
|
11
|
+
#
|
|
12
|
+
# The System Program is responsible for creating accounts, transferring SOL,
|
|
13
|
+
# and other low-level operations. Instruction data uses little-endian encoding:
|
|
14
|
+
# a u32 discriminator identifying the instruction variant, followed by any
|
|
15
|
+
# instruction-specific fields.
|
|
16
|
+
#
|
|
17
|
+
# Reference: https://github.com/solana-program/system
|
|
18
|
+
module SystemProgram
|
|
19
|
+
extend T::Sig
|
|
20
|
+
|
|
21
|
+
# The System Program address (all zeros, encoded in base58).
|
|
22
|
+
PROGRAM_ID = T.let(
|
|
23
|
+
Addresses::Address.new('11111111111111111111111111111111'),
|
|
24
|
+
Addresses::Address
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Instruction discriminators (u32 little-endian).
|
|
28
|
+
DISCRIMINATOR_TRANSFER = T.let(2, Integer)
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
# Builds a System Program Transfer instruction that moves +lamports+ from
|
|
33
|
+
# +sender+ to +recipient+.
|
|
34
|
+
#
|
|
35
|
+
# Instruction data layout (12 bytes):
|
|
36
|
+
# [0..3] u32 LE — discriminator (2)
|
|
37
|
+
# [4..11] u64 LE — lamports
|
|
38
|
+
#
|
|
39
|
+
# Accounts:
|
|
40
|
+
# 0. sender — writable, signer (source of SOL)
|
|
41
|
+
# 1. recipient — writable (destination)
|
|
42
|
+
#
|
|
43
|
+
# @param sender [Addresses::Address]
|
|
44
|
+
# @param recipient [Addresses::Address]
|
|
45
|
+
# @param lamports [Integer] amount to transfer (1 SOL = 1_000_000_000 lamports)
|
|
46
|
+
# @return [Instructions::Instruction]
|
|
47
|
+
sig do
|
|
48
|
+
params(
|
|
49
|
+
sender: Addresses::Address,
|
|
50
|
+
recipient: Addresses::Address,
|
|
51
|
+
lamports: Integer
|
|
52
|
+
).returns(Instructions::Instruction)
|
|
53
|
+
end
|
|
54
|
+
def transfer_instruction(sender:, recipient:, lamports:)
|
|
55
|
+
# Pack as: u32 LE discriminator || u64 LE lamports
|
|
56
|
+
data = [DISCRIMINATOR_TRANSFER, lamports].pack('VQ<').b
|
|
57
|
+
|
|
58
|
+
Instructions::Instruction.new(
|
|
59
|
+
program_address: PROGRAM_ID,
|
|
60
|
+
accounts: [
|
|
61
|
+
Instructions.writable_signer_account(sender), # 0 sender
|
|
62
|
+
Instructions.writable_account(recipient), # 1 recipient
|
|
63
|
+
],
|
|
64
|
+
data: data
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
# Mirrors @solana/programs — helpers for inspecting custom program errors
|
|
5
5
|
# returned in Solana transaction failures.
|
|
6
|
+
require_relative 'programs/associated_token_account'
|
|
7
|
+
require_relative 'programs/system_program'
|
|
6
8
|
#
|
|
7
9
|
# A program error in a transaction result looks like:
|
|
8
10
|
# { "InstructionError" => [0, { "Custom" => 1234 }] }
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
require 'base64'
|
|
5
5
|
require_relative '../../keys/signatures'
|
|
6
|
+
require_relative '../../transactions/compiler'
|
|
6
7
|
|
|
7
8
|
module Solana::Ruby::Kit
|
|
8
9
|
module Rpc
|
|
@@ -43,7 +44,7 @@ module Solana::Ruby::Kit
|
|
|
43
44
|
transaction
|
|
44
45
|
else
|
|
45
46
|
# Assume it responds to .message_bytes (Transactions::Transaction)
|
|
46
|
-
Base64.strict_encode64(transaction
|
|
47
|
+
Base64.strict_encode64(Transactions.wire_encode_transaction(transaction))
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
config = { 'encoding' => 'base64' }
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative 'transaction'
|
|
5
|
+
require_relative '../addresses/address'
|
|
6
|
+
require_relative '../transaction_messages/transaction_message'
|
|
7
|
+
require_relative '../instructions/roles'
|
|
8
|
+
require_relative '../errors'
|
|
9
|
+
|
|
10
|
+
module Solana::Ruby::Kit
|
|
11
|
+
module Transactions
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# compile_transaction_message
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# Compiles a TransactionMessage into a Transaction ready for signing.
|
|
18
|
+
#
|
|
19
|
+
# Only legacy (version: :legacy) transactions are supported; the serialised
|
|
20
|
+
# format follows the Solana on-wire message layout:
|
|
21
|
+
#
|
|
22
|
+
# [header: 3 bytes]
|
|
23
|
+
# [compact-u16 account count] [32-byte addresses …]
|
|
24
|
+
# [recent blockhash: 32 bytes]
|
|
25
|
+
# [compact-u16 instruction count]
|
|
26
|
+
# [for each instruction:
|
|
27
|
+
# [program_id_index: u8]
|
|
28
|
+
# [compact-u16 account count] [account indices: u8 …]
|
|
29
|
+
# [compact-u16 data length] [data bytes]
|
|
30
|
+
# ]
|
|
31
|
+
#
|
|
32
|
+
# The returned Transaction contains:
|
|
33
|
+
# - message_bytes — the serialised message (the bytes that are signed)
|
|
34
|
+
# - signatures — an ordered hash of signer_address → nil (unfilled)
|
|
35
|
+
#
|
|
36
|
+
# Use wire_encode_transaction to prepend the signatures section before
|
|
37
|
+
# submitting to the RPC node via sendTransaction.
|
|
38
|
+
sig { params(message: TransactionMessages::TransactionMessage).returns(Transaction) }
|
|
39
|
+
def compile_transaction_message(message)
|
|
40
|
+
Kernel.raise SolanaError.new(:SOLANA_ERROR__TRANSACTION__FEE_PAYER_MISSING) if message.fee_payer.nil?
|
|
41
|
+
fee_payer = T.must(message.fee_payer)
|
|
42
|
+
|
|
43
|
+
constraint = message.lifetime_constraint
|
|
44
|
+
Kernel.raise SolanaError.new(:SOLANA_ERROR__TRANSACTION__EXPECTED_BLOCKHASH_LIFETIME) unless constraint.is_a?(TransactionMessages::BlockhashLifetimeConstraint)
|
|
45
|
+
blockhash_str = constraint.blockhash
|
|
46
|
+
|
|
47
|
+
# ── 1. Collect accounts and merge roles ────────────────────────────────
|
|
48
|
+
# Insertion-ordered hash: address_str → merged AccountRole integer.
|
|
49
|
+
account_roles = T.let({}, T::Hash[String, Integer])
|
|
50
|
+
|
|
51
|
+
# Fee payer is always the first writable signer.
|
|
52
|
+
account_roles[fee_payer.value] = Instructions::AccountRole::WRITABLE_SIGNER
|
|
53
|
+
|
|
54
|
+
message.instructions.each do |ix|
|
|
55
|
+
# The instruction's program address is a readonly non-signer participant.
|
|
56
|
+
prog = ix.program_address.value
|
|
57
|
+
account_roles[prog] ||= Instructions::AccountRole::READONLY
|
|
58
|
+
|
|
59
|
+
(ix.accounts || []).each do |meta|
|
|
60
|
+
addr = meta.address.value
|
|
61
|
+
existing = account_roles[addr] || Instructions::AccountRole::READONLY
|
|
62
|
+
account_roles[addr] = Instructions::AccountRole.merge(existing, meta.role)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# ── 2. Partition into the four groups and sort within each ─────────────
|
|
67
|
+
fp = fee_payer.value
|
|
68
|
+
|
|
69
|
+
writable_signers = T.let([], T::Array[String])
|
|
70
|
+
readonly_signers = T.let([], T::Array[String])
|
|
71
|
+
writable_non_signers = T.let([], T::Array[String])
|
|
72
|
+
readonly_non_signers = T.let([], T::Array[String])
|
|
73
|
+
|
|
74
|
+
account_roles.each do |addr, role|
|
|
75
|
+
next if addr == fp # fee payer is handled separately
|
|
76
|
+
|
|
77
|
+
is_signer = Instructions::AccountRole.signer_role?(role)
|
|
78
|
+
is_writable = Instructions::AccountRole.writable_role?(role)
|
|
79
|
+
|
|
80
|
+
if is_signer && is_writable
|
|
81
|
+
writable_signers << addr
|
|
82
|
+
elsif is_signer
|
|
83
|
+
readonly_signers << addr
|
|
84
|
+
elsif is_writable
|
|
85
|
+
writable_non_signers << addr
|
|
86
|
+
else
|
|
87
|
+
readonly_non_signers << addr
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
writable_signers.sort!
|
|
92
|
+
readonly_signers.sort!
|
|
93
|
+
writable_non_signers.sort!
|
|
94
|
+
readonly_non_signers.sort!
|
|
95
|
+
|
|
96
|
+
# Fee payer first, then writable signers, readonly signers, non-signers.
|
|
97
|
+
ordered = [fp] + writable_signers + readonly_signers +
|
|
98
|
+
writable_non_signers + readonly_non_signers
|
|
99
|
+
|
|
100
|
+
# ── 3. Build index lookup ──────────────────────────────────────────────
|
|
101
|
+
account_index = T.let({}, T::Hash[String, Integer])
|
|
102
|
+
ordered.each_with_index { |addr, i| account_index[addr] = i }
|
|
103
|
+
|
|
104
|
+
# ── 4. Message header (3 bytes) ────────────────────────────────────────
|
|
105
|
+
num_required_sigs = 1 + writable_signers.size + readonly_signers.size
|
|
106
|
+
num_readonly_signed = readonly_signers.size
|
|
107
|
+
num_readonly_unsigned = readonly_non_signers.size
|
|
108
|
+
|
|
109
|
+
header = [num_required_sigs, num_readonly_signed, num_readonly_unsigned].pack('CCC').b
|
|
110
|
+
|
|
111
|
+
# ── 5. Account addresses section ───────────────────────────────────────
|
|
112
|
+
accounts_section = encode_compact_u16(ordered.size)
|
|
113
|
+
ordered.each do |addr_str|
|
|
114
|
+
accounts_section = accounts_section + Addresses.decode_address(Addresses::Address.new(addr_str))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# ── 6. Recent blockhash (32 bytes) ─────────────────────────────────────
|
|
118
|
+
blockhash_bytes = Addresses.decode_address(Addresses::Address.new(blockhash_str))
|
|
119
|
+
|
|
120
|
+
# ── 7. Instructions section ────────────────────────────────────────────
|
|
121
|
+
ixs_section = encode_compact_u16(message.instructions.size)
|
|
122
|
+
|
|
123
|
+
message.instructions.each do |ix|
|
|
124
|
+
prog_idx = T.must(account_index[ix.program_address.value])
|
|
125
|
+
ix_accounts = ix.accounts || []
|
|
126
|
+
ix_indices = ix_accounts.map { |m| T.must(account_index[m.address.value]) }
|
|
127
|
+
data_bytes = (ix.data || '').b
|
|
128
|
+
|
|
129
|
+
ixs_section = ixs_section +
|
|
130
|
+
[prog_idx].pack('C').b +
|
|
131
|
+
encode_compact_u16(ix_indices.size) +
|
|
132
|
+
ix_indices.pack('C*').b +
|
|
133
|
+
encode_compact_u16(data_bytes.bytesize) +
|
|
134
|
+
data_bytes
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
message_bytes = (header + accounts_section + blockhash_bytes + ixs_section).b
|
|
138
|
+
|
|
139
|
+
# ── 8. Signatures map (one nil slot per required signer) ───────────────
|
|
140
|
+
signer_addresses = [fp] + writable_signers + readonly_signers
|
|
141
|
+
signatures = T.let({}, T::Hash[String, T.nilable(String)])
|
|
142
|
+
signer_addresses.each { |addr| signatures[addr] = nil }
|
|
143
|
+
|
|
144
|
+
Transaction.new(message_bytes: message_bytes, signatures: signatures)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
# wire_encode_transaction
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
# Encodes a Transaction (signed or partially signed) into the full Solana
|
|
151
|
+
# on-wire format that the RPC node's sendTransaction method expects:
|
|
152
|
+
#
|
|
153
|
+
# [compact-u16 signature count]
|
|
154
|
+
# [64-byte signature or 64 zero bytes if nil] × count
|
|
155
|
+
# [message bytes]
|
|
156
|
+
#
|
|
157
|
+
# The result is a binary String; base64-encode it before sending via HTTP.
|
|
158
|
+
sig { params(transaction: Transaction).returns(String) }
|
|
159
|
+
def wire_encode_transaction(transaction)
|
|
160
|
+
sigs = transaction.signatures
|
|
161
|
+
header = encode_compact_u16(sigs.size)
|
|
162
|
+
sig_bytes = sigs.values.map { |s| (s || ("\x00" * 64)).b }.join
|
|
163
|
+
|
|
164
|
+
(header + sig_bytes + transaction.message_bytes).b
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# ---------------------------------------------------------------------------
|
|
168
|
+
# Private helpers
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
# Encodes a non-negative integer using Solana's compact-u16 format.
|
|
172
|
+
# 0–127 → 1 byte
|
|
173
|
+
# 128–16383 → 2 bytes
|
|
174
|
+
# 16384+ → 3 bytes (max value 0x7fff used in practice)
|
|
175
|
+
sig { params(value: Integer).returns(String) }
|
|
176
|
+
def encode_compact_u16(value)
|
|
177
|
+
result = T.let([], T::Array[Integer])
|
|
178
|
+
remaining = value
|
|
179
|
+
|
|
180
|
+
Kernel.loop do
|
|
181
|
+
byte = remaining & 0x7f
|
|
182
|
+
remaining = remaining >> 7
|
|
183
|
+
byte |= 0x80 if remaining > 0
|
|
184
|
+
result << byte
|
|
185
|
+
break if remaining == 0
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
result.pack('C*').b
|
|
189
|
+
end
|
|
190
|
+
private_class_method :encode_compact_u16
|
|
191
|
+
end
|
|
192
|
+
end
|
data/solana-ruby-kit.gemspec
CHANGED
|
@@ -5,7 +5,7 @@ require_relative 'lib/solana/ruby/kit/version'
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = 'solana-ruby-kit'
|
|
7
7
|
spec.version = Solana::Ruby::Kit::VERSION
|
|
8
|
-
spec.authors = ['Paul Zupan']
|
|
8
|
+
spec.authors = ['Paul Zupan, Idhra Inc.']
|
|
9
9
|
spec.summary = 'Ruby port of the Anza TypeScript SDK (@anza-xyz/kit)'
|
|
10
10
|
spec.license = 'MIT'
|
|
11
11
|
spec.required_ruby_version = '>= 3.2.0'
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solana-ruby-kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
- Paul Zupan
|
|
7
|
+
- Paul Zupan, Idhra Inc.
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-02-
|
|
10
|
+
date: 2026-02-24 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: openssl
|
|
@@ -225,6 +225,8 @@ files:
|
|
|
225
225
|
- lib/solana/ruby/kit/options/option.rb
|
|
226
226
|
- lib/solana/ruby/kit/plugin_core.rb
|
|
227
227
|
- lib/solana/ruby/kit/programs.rb
|
|
228
|
+
- lib/solana/ruby/kit/programs/associated_token_account.rb
|
|
229
|
+
- lib/solana/ruby/kit/programs/system_program.rb
|
|
228
230
|
- lib/solana/ruby/kit/promises.rb
|
|
229
231
|
- lib/solana/ruby/kit/railtie.rb
|
|
230
232
|
- lib/solana/ruby/kit/rpc.rb
|
|
@@ -285,6 +287,7 @@ files:
|
|
|
285
287
|
- lib/solana/ruby/kit/transaction_messages.rb
|
|
286
288
|
- lib/solana/ruby/kit/transaction_messages/transaction_message.rb
|
|
287
289
|
- lib/solana/ruby/kit/transactions.rb
|
|
290
|
+
- lib/solana/ruby/kit/transactions/compiler.rb
|
|
288
291
|
- lib/solana/ruby/kit/transactions/transaction.rb
|
|
289
292
|
- lib/solana/ruby/kit/version.rb
|
|
290
293
|
- solana-ruby-kit.gemspec
|