solana-ruby-web3js 1.0.1.beta4 → 2.0.0beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ca6cbe9f7024efc46fc77b926caa77368c337d69f12b2d5bde3434cacd8be99
4
- data.tar.gz: 7e4823b24785b3ec0d54f96f2a9b43b02cbdbedfe4732bf674249918b08beeab
3
+ metadata.gz: 37360e6f27ca521194022c29c3419d2dc06e5dcae0499af04892e3ca29299b0d
4
+ data.tar.gz: c5748b0ddacfaa170e79aba9a16b883c64a05ac7f776f0589c3914a5ad418356
5
5
  SHA512:
6
- metadata.gz: 1d75c69661f9bd7353c9251d8cb7615785d5233d7832a1e596c02997a42b1f07dbfb9600abb45ea2b04dfc8d80ddbe2167e91e2504c436ca27e99ec30a4679f2
7
- data.tar.gz: e4784c227e3e24fd2bcaf79734b42602540c39c8ed4338fc5a53d740b2077aedeb76e7bb62c84d7cf4bc54c5a044281293d02adfd92fc487483d428ab03c1ea5
6
+ metadata.gz: 683573ae5898ae932dea765b65cc4d73136f198429920be4e923da76bfed3b515e941f96761238f42dd92cedadc7d28c03f43dc3357fb07451a4940b3a2e93e0
7
+ data.tar.gz: e6c3b0e84760561c6b22290f3dab047cb041f9de7ed02d1a7cd88cc5ed285a6fb5c2ca97e7a719f709044c044b3c769aa0d0c612bb35b5894e0e70f1955e9c4e
data/README.md CHANGED
@@ -332,3 +332,75 @@ The following methods are supported by the SolanaRuby::WebSocketClient:
332
332
 
333
333
  Several methods have optional parameters where default options are defined in the client. These options can be customized or overridden when calling the methods, but if left unspecified, the client will use its internal defaults.
334
334
 
335
+ ## Transaction Helpers
336
+
337
+ ### Transfer SOL Between Accounts
338
+
339
+ To transfer SOL (the native cryptocurrency of the Solana blockchain) from one account to another, follow these steps:
340
+
341
+ #### Requirements:
342
+
343
+ - **Sender's Keypair:** Either generate a new keypair or provide the private key for an existing sender account. This keypair is used to sign the transaction.
344
+ - **Receiver's Public Key:** Specify the public key of the destination account. You can generate a new keypair for the receiver or use an existing public key.
345
+ - **Airdrop Functionality:** For Devnet or Testnet transactions, ensure that the sender's account is funded with sufficient lamports using the Solana airdrop feature.
346
+ - An initialized client to interact with the Solana blockchain.
347
+
348
+ #### Example Usage:
349
+
350
+ require 'solana_ruby'
351
+
352
+ # Initialize the client (defaults to Mainnet(https://api.mainnet-beta.solana.com))
353
+ client = SolanaRuby::HttpClient.new('https://api.devnet.solana.com')
354
+
355
+ # Fetch the recent blockhash
356
+ recent_blockhash = client.get_latest_blockhash["blockhash"]
357
+
358
+ # Generate or fetch the sender's keypair
359
+ # Option 1: Generate a new keypair
360
+ sender_keypair = SolanaRuby::Keypair.generate
361
+ # Option 2: Use an existing private key
362
+ # sender_keypair = SolanaRuby::Keypair.from_private_key("InsertPrivateKeyHere")
363
+
364
+ sender_pubkey = sender_keypair[:public_key]
365
+
366
+
367
+ # Airdrop some lamports to the sender's account
368
+ lamports = 10 * 1_000_000_000
369
+ sleep(1)
370
+ result = client.request_airdrop(sender_pubkey, lamports)
371
+ puts "Solana Balance #{lamports} lamports added sucessfully for the public key: #{sender_pubkey}"
372
+ sleep(10)
373
+
374
+
375
+ # Generate or use an existing receiver's public key
376
+ # Option 1: Generate a new keypair for the receiver
377
+ receiver_keypair = SolanaRuby::Keypair.generate # generate receiver keypair
378
+ receiver_pubkey = receiver_keypair[:public_key]
379
+ # Option 2: Use an existing public key
380
+ # receiver_pubkey = 'InsertExistingPublicKeyHere'
381
+
382
+ transfer_lamports = 1 * 1_000_000
383
+ puts "Payer's full private key: #{sender_keypair[:full_private_key]}"
384
+ puts "Receiver's full private key: #{receiver_keypair[:full_private_key]}"
385
+ puts "Receiver's Public Key: #{receiver_keypair[:public_key]}"
386
+
387
+ # Create a new transaction
388
+ transaction = SolanaRuby::TransactionHelper.new_sol_transaction(
389
+ sender_pubkey,
390
+ receiver_pubkey,
391
+ transfer_lamports,
392
+ recent_blockhash
393
+ )
394
+
395
+ # Get the sender's private key (ensure it's a string)
396
+ private_key = sender_keypair[:private_key]
397
+ puts "Private key type: #{private_key.class}, Value: #{private_key.inspect}"
398
+
399
+ # Sign the transaction
400
+ signed_transaction = transaction.sign([sender_keypair])
401
+
402
+ # Send the transaction to the Solana network
403
+ sleep(5)
404
+ response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
405
+ puts "Response: #{response}"
406
+
@@ -0,0 +1,30 @@
1
+ module SolanaRuby
2
+ module DataTypes
3
+ class Blob
4
+ attr_reader :size
5
+
6
+ # Constructor to initialize size of the blob
7
+ def initialize(size)
8
+ raise ArgumentError, "Size must be a positive integer" unless size.is_a?(Integer) && size > 0
9
+ @size = size
10
+ end
11
+
12
+ # Serialize the given object to a byte array
13
+ def serialize(obj)
14
+ # Ensure obj is an array and then convert to byte array
15
+ obj = [obj] unless obj.is_a?(Array)
16
+ raise ArgumentError, "Object must be an array of bytes" unless obj.all? { |e| e.is_a?(Integer) && e.between?(0, 255) }
17
+
18
+ obj.pack('C*').bytes
19
+ end
20
+
21
+ # Deserialize a byte array into the original object format
22
+ def deserialize(bytes)
23
+ # Ensure the byte array is of the correct size
24
+ raise ArgumentError, "Byte array size must match the expected size" unless bytes.length == @size
25
+
26
+ bytes.pack('C*')
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ module SolanaRuby
2
+ module DataTypes
3
+ class Layout
4
+ attr_reader :fields
5
+
6
+ def initialize(fields)
7
+ @fields = fields
8
+ end
9
+
10
+ def serialize(params)
11
+ fields.flat_map do |field, type|
12
+ data_type = type.is_a?(Symbol) ? SolanaRuby::DataTypes.send(type) : type
13
+ data_type.serialize(params[field])
14
+ end
15
+ end
16
+
17
+ def deserialize(bytes)
18
+ result = {}
19
+ fields.map do |field, type|
20
+ data_type = type.is_a?(Symbol) ? SolanaRuby::DataTypes.send(type) : type
21
+ result[field] = data_type.deserialize(bytes.shift(data_type.size))
22
+ end
23
+ result
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,40 @@
1
+ module SolanaRuby
2
+ module DataTypes
3
+ class NearInt64
4
+ attr_reader :size
5
+
6
+ V2E32 = 2.pow(32)
7
+
8
+ def initialize
9
+ @size = 8
10
+ end
11
+
12
+ def serialize(obj)
13
+ uint = UnsignedInt.new(32)
14
+ numbers = divmod_int64(obj)
15
+ numbers.map{|x| uint.serialize(x)}.flatten
16
+ end
17
+
18
+ def deserialize(bytes)
19
+ raise "Invalid serialization (wrong size)" if @size && bytes.size != @size
20
+ uint = UnsignedInt.new(32)
21
+ half_size = @size/2
22
+
23
+ lo, hi = [bytes[0..half_size-1], bytes[half_size..-1]].map{|x| uint.deserialize(x)}
24
+
25
+ rounded_int64(hi, lo)
26
+ end
27
+
28
+ def divmod_int64(obj)
29
+ obj = obj * 1.0
30
+ hi32 = (obj / V2E32).floor
31
+ lo32 = (obj - (hi32 * V2E32)).floor
32
+ [lo32, hi32]
33
+ end
34
+
35
+ def rounded_int64(hi32, lo32)
36
+ hi32 * V2E32 + lo32
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ module SolanaRuby
2
+ module DataTypes
3
+ class Sequence
4
+ def initialize count, type
5
+ @count = count
6
+ @type = type
7
+ end
8
+
9
+ def serialize items
10
+ items.map do |item|
11
+ @type.serialize(item)
12
+ end.flatten
13
+ end
14
+
15
+ def deserialize bytes
16
+ @count.times.map do
17
+ current_bytes = bytes.shift(@type.size)
18
+ @type.deserialize(current_bytes)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ module SolanaRuby
2
+ module DataTypes
3
+ class UnsignedInt
4
+ attr_reader :size
5
+
6
+ BITS = {
7
+ 8 => { directive: 'C*', size: 1 },
8
+ 32 => { directive: 'L*', size: 4 },
9
+ 64 => { directive: 'Q*', size: 8 }
10
+ }
11
+
12
+ def initialize(bits)
13
+ @bits = bits
14
+ type = BITS[@bits]
15
+ raise "Can only fit #{BITS.keys}" unless type
16
+ @size = type[:size]
17
+ @directive = type[:directive]
18
+ end
19
+
20
+ # Serialize the unsigned integer into bytes
21
+ def serialize(obj)
22
+ raise "Can only serialize integers" unless obj.is_a?(Integer)
23
+ raise "Cannot serialize negative integers" if obj < 0
24
+
25
+ if obj >= 256**@size
26
+ raise "Integer too large (does not fit in #{@size} bytes)"
27
+ end
28
+
29
+ [obj].pack(@directive).bytes
30
+ end
31
+
32
+ # Deserialize bytes into the unsigned integer
33
+ def deserialize(bytes)
34
+ raise "Invalid serialization (wrong size)" if bytes.size != @size
35
+
36
+ bytes.pack('C*').unpack(@directive).first
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ module SolanaRuby
2
+ module DataTypes
3
+ extend self
4
+
5
+ def uint8
6
+ UnsignedInt.new(8)
7
+ end
8
+
9
+ def uint32
10
+ UnsignedInt.new(32)
11
+ end
12
+
13
+ def uint64
14
+ UnsignedInt.new(64)
15
+ end
16
+
17
+ def near_int64
18
+ NearInt64.new
19
+ end
20
+
21
+ def blob1
22
+ Blob.new(1)
23
+ end
24
+ end
25
+ end
@@ -23,7 +23,8 @@ module SolanaRuby
23
23
 
24
24
  def request(method, params = [])
25
25
  http = Net::HTTP.new(@uri.host, @uri.port)
26
- http.use_ssl = true
26
+ local_hosts = ['localhost', '127.0.0.1', '[::1]']
27
+ http.use_ssl = true unless local_hosts.include?(@uri.host.downcase)
27
28
 
28
29
  request = Net::HTTP::Post.new(@uri.request_uri, {'Content-Type' => 'application/json'})
29
30
  request.body = {
@@ -7,12 +7,14 @@ module SolanaRuby
7
7
  def self.generate
8
8
  signing_key = RbNaCl::Signatures::Ed25519::SigningKey.generate
9
9
  public_key_bytes = signing_key.verify_key.to_bytes # Binary format for public key
10
- private_key_hex = signing_key.to_bytes.unpack1('H*') # Hex format for private key
10
+ private_key_bytes = signing_key.to_bytes
11
+ private_key_hex = private_key_bytes.unpack1('H*') # Hex format for private key
11
12
 
12
13
  # Convert public key binary to Base58 for readability and compatibility
13
14
  {
14
- public_key: Base58.binary_to_base58(public_key_bytes),
15
- private_key: private_key_hex
15
+ public_key: Base58.binary_to_base58(public_key_bytes, :bitcoin),
16
+ private_key: private_key_hex,
17
+ full_private_key: Base58.binary_to_base58((private_key_bytes + public_key_bytes), :bitcoin)
16
18
  }
17
19
  end
18
20
 
@@ -31,8 +33,9 @@ module SolanaRuby
31
33
 
32
34
  # Return public key in Base58 format and private key in hex format
33
35
  {
34
- public_key: Base58.binary_to_base58(public_key_bytes),
35
- private_key: private_key_hex
36
+ public_key: Base58.binary_to_base58(public_key_bytes, :bitcoin),
37
+ private_key: private_key_hex,
38
+ full_private_key: Base58.binary_to_base58((private_key_bytes + public_key_bytes), :bitcoin)
36
39
  }
37
40
  end
38
41
  end
@@ -0,0 +1,107 @@
1
+ module SolanaRuby
2
+ class Message
3
+ PUBKEY_LENGTH = 32
4
+
5
+ attr_reader :header, :account_keys, :recent_blockhash, :instructions
6
+
7
+ def initialize(header:, account_keys:, recent_blockhash:, instructions:)
8
+ @header = header
9
+ @account_keys = account_keys
10
+ @recent_blockhash = recent_blockhash
11
+ @instructions = instructions
12
+ end
13
+
14
+ def self.from(bytes)
15
+ bytes = bytes.dup
16
+ num_required_signatures = bytes.shift
17
+ num_readonly_signed_accounts = bytes.shift
18
+ num_readonly_unsigned_accounts = bytes.shift
19
+ account_count = Utils.decode_length(bytes)
20
+
21
+ account_keys = account_count.times.map do
22
+ account_bytes = bytes.slice!(0, PUBKEY_LENGTH)
23
+ Utils.bytes_to_base58(account_bytes)
24
+ end
25
+
26
+ recent_blockhash_bytes = bytes.slice!(0, PUBKEY_LENGTH)
27
+ recent_blockhash = Utils.bytes_to_base58(recent_blockhash_bytes)
28
+
29
+ instruction_count = Utils.decode_length(bytes)
30
+ instructions = instruction_count.times.map do
31
+ program_id_index = bytes.shift
32
+ account_count = Utils.decode_length(bytes)
33
+
34
+ accounts = bytes.slice!(0, account_count)
35
+
36
+ data_length = Utils.decode_length(bytes)
37
+ data_bytes = bytes.slice!(0, data_length)
38
+ {program_id_index: program_id_index, accounts: accounts, data: data_bytes}
39
+ end
40
+ self.new({
41
+ header: {
42
+ num_required_signatures: num_required_signatures,
43
+ num_readonly_signed_accounts:num_readonly_signed_accounts,
44
+ num_readonly_unsigned_accounts:num_readonly_unsigned_accounts,
45
+ },
46
+ account_keys: account_keys,
47
+ recent_blockhash: recent_blockhash,
48
+ instructions: instructions
49
+ })
50
+ end
51
+
52
+ def serialize
53
+ num_keys = account_keys.length
54
+ key_count = Utils.encode_length(num_keys)
55
+
56
+ layout = SolanaRuby::DataTypes::Layout.new({
57
+ num_required_signatures: :blob1,
58
+ num_readonly_signed_accounts: :blob1,
59
+ num_readonly_unsigned_accounts: :blob1,
60
+ key_count: SolanaRuby::DataTypes::Blob.new(key_count.length),
61
+ keys: SolanaRuby::DataTypes::Sequence.new(num_keys, SolanaRuby::DataTypes::Blob.new(32)),
62
+ recent_blockhash: SolanaRuby::DataTypes::Blob.new(32)
63
+ })
64
+
65
+ sign_data = layout.serialize({
66
+ num_required_signatures: header[:num_required_signatures],
67
+ num_readonly_signed_accounts: header[:num_readonly_signed_accounts],
68
+ num_readonly_unsigned_accounts: header[:num_readonly_unsigned_accounts],
69
+ key_count: key_count,
70
+ keys: account_keys.map{|x| Utils.base58_to_bytes(x)},
71
+ recent_blockhash: Utils.base58_to_bytes(recent_blockhash)
72
+ })
73
+
74
+ instruction_count = Utils.encode_length(@instructions.length)
75
+ sign_data += instruction_count
76
+
77
+ data = @instructions.map do |instruction|
78
+ instruction_layout = SolanaRuby::DataTypes::Layout.new({
79
+ program_id_index: :uint8,
80
+ key_indices_count: SolanaRuby::DataTypes::Blob.new(key_count.length),
81
+ key_indices: SolanaRuby::DataTypes::Sequence.new(num_keys, SolanaRuby::DataTypes::Blob.new(8)),
82
+ data_length: SolanaRuby::DataTypes::Blob.new(key_count.length),
83
+ data: SolanaRuby::DataTypes::Sequence.new(num_keys, SolanaRuby::DataTypes::UnsignedInt.new(8)),
84
+ })
85
+
86
+ key_indices_count = Utils.encode_length(instruction[:accounts].length)
87
+ data_count = Utils.encode_length(instruction[:data].length)
88
+
89
+ instruction_layout.serialize({
90
+ program_id_index: instruction[:program_id_index],
91
+ key_indices_count: key_indices_count,
92
+ key_indices: instruction[:accounts],
93
+ data_length: data_count,
94
+ data: instruction[:data]
95
+ })
96
+ end.flatten
97
+
98
+ sign_data += data
99
+ sign_data
100
+ end
101
+
102
+ def is_account_writable(index)
103
+ index < header[:num_required_signatures] - header[:num_readonly_signed_accounts] ||
104
+ (index >= header[:num_required_signatures] && index < account_keys.length - header[:num_readonly_unsigned_accounts])
105
+ end
106
+ end
107
+ end
@@ -1,14 +1,19 @@
1
+ Dir[File.join(__dir__, 'data_types', '*.rb')].each { |file| require file }
2
+
1
3
  module SolanaRuby
2
4
  class Transaction
3
5
  require 'rbnacl'
6
+ SIGNATURE_LENGTH = 64
7
+ PACKET_DATA_SIZE = 1280 - 40 - 8
8
+ DEFAULT_SIGNATURE = Array.new(64, 0)
4
9
 
5
- attr_accessor :instructions, :signatures, :fee_payer, :recent_blockhash
10
+ attr_accessor :instructions, :signatures, :fee_payer, :recent_blockhash, :message
6
11
 
7
- def initialize
8
- @instructions = []
9
- @signatures = []
10
- @fee_payer = nil
11
- @recent_blockhash = nil
12
+ def initialize(recent_blockhash: nil, signatures: [], instructions: [], fee_payer: nil)
13
+ @recent_blockhash = recent_blockhash
14
+ @signatures = signatures
15
+ @instructions = instructions
16
+ @fee_payer = fee_payer
12
17
  end
13
18
 
14
19
  def add_instruction(instruction)
@@ -17,92 +22,262 @@ module SolanaRuby
17
22
 
18
23
  def set_fee_payer(pubkey)
19
24
  puts "Setting fee payer: #{pubkey.inspect}" # Debugging output
20
- unless Base58.valid?(pubkey)
21
- raise "Invalid Base58 public key for fee payer: #{pubkey.inspect}"
22
- end
23
25
  @fee_payer = pubkey # Store as-is since Base58 gem can handle encoding/decoding
24
26
  end
25
27
 
26
28
  def set_recent_blockhash(blockhash)
27
- raise "Invalid Base58 blockhash" unless Base58.valid?(blockhash)
29
+ # raise "Invalid Base58 blockhash" unless Base58.valid?(blockhash)
28
30
  @recent_blockhash = blockhash # Store as-is for similar reasons
29
31
  end
30
32
 
33
+ def self.from(base64_string)
34
+ bytes = Base64.decode64(base64_string).bytes
35
+ signature_count = Utils.decode_length(bytes)
36
+ signatures = signature_count.times.map do
37
+ signature_bytes = bytes.slice!(0, SIGNATURE_LENGTH)
38
+ Utils.bytes_to_base58(signature_bytes)
39
+ end
40
+ msg = Message.from(bytes)
41
+ self.populate(msg, signatures)
42
+ end
43
+
31
44
  def serialize
32
- raise "Recent blockhash not set" if @recent_blockhash.nil?
33
- raise "Fee payer not set" if @fee_payer.nil?
45
+ sign_data = serialize_message
34
46
 
35
- transaction_data = []
36
- transaction_data << Base58.base58_to_binary(@recent_blockhash) # Convert as needed here
37
- transaction_data << Base58.base58_to_binary(@fee_payer)
38
- transaction_data << [@instructions.length].pack("C")
47
+ signature_count = Utils.encode_length(signatures.length)
48
+ raise 'invalid length!' if signatures.length > 256
49
+
50
+ wire_transaction = signature_count
39
51
 
40
- @instructions.each do |instruction|
41
- serialized_instruction = instruction.serialize
42
- raise "Instruction serialization failed" if serialized_instruction.nil?
43
- transaction_data << serialized_instruction
52
+ signatures.each do |signature|
53
+ if signature
54
+ signature_bytes = signature[:signature]
55
+ raise 'signature is empty' unless (signature_bytes)
56
+ raise 'signature has invalid length' unless (signature_bytes.length == 64)
57
+ wire_transaction += signature_bytes
58
+ raise "Transaction too large: #{wire_transaction.length} > #{PACKET_DATA_SIZE}" unless wire_transaction.length <= PACKET_DATA_SIZE
59
+ wire_transaction
60
+ end
44
61
  end
45
62
 
46
- serialized = transaction_data.join
47
- puts "Serialized Transaction Data: #{serialized.bytes.inspect}" # Debugging output
63
+ wire_transaction += sign_data
64
+ wire_transaction
65
+ end
66
+
67
+ def to_base64
68
+ Base64.strict_encode64(serialize.pack('C*'))
69
+ end
48
70
 
49
- serialized
71
+ def add(item)
72
+ instructions.push(item)
50
73
  end
51
74
 
52
- def sign(private_key_hex)
53
- private_key_bytes = [private_key_hex].pack('H*')
54
- signing_key = RbNaCl::Signatures::Ed25519::SigningKey.new(private_key_bytes)
75
+ def sign(keys)
76
+ raise 'No signers' unless keys.any?
55
77
 
56
- message = serialize_message
57
- signature = signing_key.sign(message)
78
+ keys = keys.uniq{ |k| key[:public_key] }
79
+ @signatures = keys.map do |key|
80
+ {
81
+ signature: nil,
82
+ public_key: key[:public_key]
83
+ }
84
+ end
58
85
 
59
- @signatures << signature # Store as binary
60
- Base58.binary_to_base58(signature) # Convert to Base58 for external use
86
+ message = compile_message
87
+ partial_sign(message, keys)
88
+ true
61
89
  end
62
90
 
63
91
  private
64
92
 
65
93
  def serialize_message
66
- accounts = collect_accounts
94
+ compile.serialize
95
+ end
96
+
97
+ def compile
98
+ message = compile_message
99
+ signed_keys = message.account_keys.slice(0, message.header[:num_required_signatures])
100
+
101
+ if signatures.length == signed_keys.length
102
+ valid = signatures.each_with_index.all?{|pair, i| signed_keys[i] == pair[:public_key]}
103
+ return message if valid
104
+ end
105
+
106
+ @signatures = signed_keys.map do |public_key|
107
+ {
108
+ signature: nil,
109
+ public_key: public_key
110
+ }
111
+ end
112
+
113
+ message
114
+ end
115
+
116
+ def compile_message
117
+ check_for_errors
118
+ fetch_message_data
119
+ message = Message.new(
120
+ header: {
121
+ num_required_signatures: @num_required_signatures,
122
+ num_readonly_signed_accounts: @num_readonly_signed_accounts,
123
+ num_readonly_unsigned_accounts: @num_readonly_unsigned_accounts,
124
+ },
125
+ account_keys: @account_keys, recent_blockhash: recent_blockhash, instructions: @instructs
126
+ )
127
+ message
128
+ end
129
+
130
+ def check_for_errors
131
+ raise 'Transaction recent_blockhash required' unless recent_blockhash
132
+
133
+ puts 'No instructions provided' if instructions.length < 1
134
+
135
+ if fee_payer.nil? && signatures.length > 0 && signatures[0][:public_key]
136
+ @fee_payer = signatures[0][:public_key] if (signatures.length > 0 && signatures[0][:public_key])
137
+ end
138
+
139
+ raise('Transaction fee payer required') if @fee_payer.nil?
140
+
141
+ instructions.each_with_index do |instruction, i|
142
+ raise("Transaction instruction index #{i} has undefined program id") unless instruction.program_id
143
+ end
144
+ end
67
145
 
68
- message_data = []
69
- message_data << Base58.base58_to_binary(@recent_blockhash)
70
- message_data << [accounts.length].pack("C")
146
+ def fetch_message_data
147
+ program_ids = []
148
+ account_metas= []
71
149
 
72
- accounts.each do |account|
73
- message_data << account
150
+ instructions.each do |instruction|
151
+ account_metas += instruction.keys
152
+ program_ids.push(instruction.program_id) unless program_ids.include?(instruction.program_id)
74
153
  end
75
154
 
76
- message_data << [@instructions.length].pack("C")
155
+ # Append programID account metas
156
+ append_program_id(program_ids, account_metas)
77
157
 
78
- @instructions.each do |instruction|
79
- message_data << instruction.serialize
158
+ # Sort. Prioritizing first by signer, then by writable
159
+ signer_order(account_metas)
160
+
161
+ # Cull duplicate account metas
162
+ unique_metas = []
163
+ add_unique_meta_data(unique_metas, account_metas)
164
+
165
+ add_fee_payer_meta(unique_metas)
166
+
167
+ # Disallow unknown signers
168
+ disallow_signers(signatures, unique_metas)
169
+
170
+ # Split out signing from non-signing keys and count header values
171
+ signed_keys = []
172
+ unsigned_keys = []
173
+ header_params = split_keys(unique_metas, signed_keys, unsigned_keys)
174
+ @account_keys = signed_keys + unsigned_keys
175
+
176
+ # add instruction structure
177
+ @instructs = add_instructs
178
+ end
179
+
180
+ def append_program_id(program_ids, account_metas)
181
+ program_ids.each do |programId|
182
+ account_metas.push({
183
+ pubkey: programId,
184
+ is_signer: false,
185
+ is_writable: false,
186
+ })
187
+ end
188
+ end
189
+
190
+ def signer_order(account_metas)
191
+ account_metas.sort! do |x, y|
192
+ check_signer = x[:is_signer] == y[:is_signer] ? nil : x[:is_signer] ? -1 : 1
193
+ check_writable = x[:is_writable] == y[:is_writable] ? nil : (x[:is_writable] ? -1 : 1)
194
+ (check_signer || check_writable) || 0
80
195
  end
196
+ end
81
197
 
82
- message_data.join
198
+ def add_unique_meta_data(unique_metas, account_metas)
199
+ account_metas.each do |account_meta|
200
+ pubkey_string = account_meta[:pubkey]
201
+ unique_index = unique_metas.find_index{|x| x[:pubkey] == pubkey_string }
202
+ if unique_index
203
+ unique_metas[unique_index][:is_writable] = unique_metas[unique_index][:is_writable] || account_meta[:is_writable]
204
+ else
205
+ unique_metas.push(account_meta);
206
+ end
207
+ end
83
208
  end
84
209
 
85
- def collect_accounts
86
- accounts = []
87
- accounts << Base58.base58_to_binary(@fee_payer) if @fee_payer
210
+ def add_fee_payer_meta(unique_metas)
211
+ # Move fee payer to the front
212
+ fee_payer_index = unique_metas.find_index { |x| x[:pubkey] == fee_payer }
213
+ if fee_payer_index
214
+ payer_meta = unique_metas.delete_at(fee_payer_index)
215
+ payer_meta[:is_signer] = true
216
+ payer_meta[:is_writable] = true
217
+ unique_metas.unshift(payer_meta)
218
+ else
219
+ unique_metas.unshift({
220
+ pubkey: fee_payer,
221
+ is_signer: true,
222
+ is_writable: true,
223
+ })
224
+ end
225
+ end
88
226
 
89
- @instructions.each do |instruction|
90
- instruction.keys.each do |key_meta|
91
- pubkey_binary = Base58.base58_to_binary(key_meta[:pubkey])
92
- accounts << pubkey_binary unless accounts.include?(pubkey_binary)
227
+ def disallow_signers(signatures, unique_metas)
228
+ signatures.each do |signature|
229
+ unique_index = unique_metas.find_index{ |x| x[:pubkey] == signature[:public_key] }
230
+
231
+ if unique_index
232
+ unique_metas[unique_index][:is_signer] = true unless unique_metas[unique_index][:is_signer]
233
+ else
234
+ raise "unknown signer: #{signature[:public_key]}"
93
235
  end
94
236
  end
237
+ end
95
238
 
96
- accounts.uniq
239
+ def add_instructs
240
+ instructions.map do |instruction|
241
+ {
242
+ program_id_index: @account_keys.index(instruction.program_id),
243
+ accounts: instruction.keys.map { |meta| @account_keys.index(meta[:pubkey]) },
244
+ data: instruction.data
245
+ }
246
+ end
97
247
  end
98
- end
99
- end
100
248
 
101
- class Base58
102
- ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'.freeze
249
+ def split_keys(unique_metas, signed_keys, unsigned_keys)
250
+ @num_required_signatures = 0
251
+ @num_readonly_signed_accounts = 0
252
+ @num_readonly_unsigned_accounts = 0
253
+ unique_metas.each do |meta|
254
+ if meta[:is_signer]
255
+ signed_keys.push(meta[:pubkey])
256
+ @num_required_signatures += 1
257
+ @num_readonly_signed_accounts += 1 if (!meta[:is_writable])
258
+ else
259
+ unsigned_keys.push(meta[:pubkey])
260
+ @num_readonly_unsigned_accounts += 1 if (!meta[:is_writable])
261
+ end
262
+ end
263
+ end
264
+
265
+ def partial_sign(message, keys)
266
+ sign_data = message.serialize
267
+ keys.each do |key|
268
+ private_key_bytes = [key[:private_key]].pack('H*')
269
+ signing_key = RbNaCl::Signatures::Ed25519::SigningKey.new(private_key_bytes)
270
+ signature = signing_key.sign(sign_data.pack('C*')).bytes
271
+ add_signature(key[:public_key], signature)
272
+ end
273
+ end
274
+
275
+ def add_signature(pubkey, signature)
276
+ raise 'error' unless signature.length === 64
277
+ index = signatures.find_index{|s| s[:public_key] == pubkey}
278
+ raise "unknown signer: #{pubkey}" unless index
103
279
 
104
- # Checks if a string contains only valid Base58 characters
105
- def self.valid?(base58_str)
106
- base58_str.chars.all? { |char| ALPHABET.include?(char) }
280
+ @signatures[index][:signature] = signature
281
+ end
107
282
  end
108
283
  end
@@ -1,29 +1,138 @@
1
1
  module SolanaRuby
2
2
  class TransactionHelper
3
3
  require 'base58'
4
- PROGRAM_ID = '11111111111111111111111111111111'
4
+ require 'pry'
5
5
 
6
- def self.create_transfer(from_pubkey, to_pubkey, lamports, program_id = PROGRAM_ID)
7
- transfer_instruction = TransactionInstruction.new(
6
+ # Constants for program IDs
7
+ SYSTEM_PROGRAM_ID = '11111111111111111111111111111111'
8
+ TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
9
+ ASSOCIATED_TOKEN_PROGRAM_ID = 'ATokenGP3evbxxpQ7bYPLNNaxD2c4bqtvWjpKbmz6HjH'
10
+
11
+ INSTRUCTION_LAYOUTS = {
12
+ # Native SOL transfer
13
+ sol_transfer: {
14
+ instruction: :uint32,
15
+ lamports: :near_int64
16
+ },
17
+ # SPL token transfer
18
+ spl_transfer: {
19
+ instruction: :uint8,
20
+ amount: :uint64
21
+ },
22
+ # Create account layout
23
+ create_account: {
24
+ instruction: :uint8,
25
+ lamports: :uint64,
26
+ space: :uint64
27
+ }
28
+ }
29
+
30
+ # Method to create a system account (e.g., for SPL token or SOL)
31
+ def self.create_account(from_pubkey, new_account_pubkey, lamports, space, owner_pubkey = SYSTEM_PROGRAM_ID)
32
+ instruction_data = encode_data(INSTRUCTION_LAYOUTS[:create_account], { instruction: 0, lamports: lamports, space: space })
33
+ create_account_instruction = TransactionInstruction.new(
34
+ keys: [
35
+ { pubkey: from_pubkey, is_signer: true, is_writable: true },
36
+ { pubkey: new_account_pubkey, is_signer: false, is_writable: true },
37
+ { pubkey: owner_pubkey, is_signer: false, is_writable: false }
38
+ ],
39
+ program_id: owner_pubkey,
40
+ data: instruction_data.bytes
41
+ )
42
+ create_account_instruction
43
+ end
44
+
45
+ def self.create_and_sign_transaction(from_pubkey, new_account_pubkey, lamports, space, recent_blockhash)
46
+ # Create the transaction
47
+ transaction = Transaction.new
48
+ transaction.set_fee_payer(from_pubkey)
49
+ transaction.set_recent_blockhash(recent_blockhash)
50
+
51
+ # Add the create account instruction to the transaction
52
+ create_account_instruction = create_account(from_pubkey, new_account_pubkey, lamports, space)
53
+ transaction.add_instruction(create_account_instruction)
54
+
55
+ # You would then sign the transaction and send it as needed
56
+ # Example: signing and sending the transaction
57
+ transaction
58
+ end
59
+
60
+ # Method to create a SOL transfer instruction
61
+ def self.transfer_sol_transaction(from_pubkey, to_pubkey, lamports)
62
+ fields = INSTRUCTION_LAYOUTS[:sol_transfer]
63
+ data = encode_data(fields, { instruction: 2, lamports: lamports })
64
+ TransactionInstruction.new(
8
65
  keys: [
9
66
  { pubkey: from_pubkey, is_signer: true, is_writable: true },
10
67
  { pubkey: to_pubkey, is_signer: false, is_writable: true }
11
68
  ],
12
- program_id: program_id,
13
- data: [2, lamports].pack('CQ<') # Instruction type 2 (transfer) + lamports (u64)
69
+ program_id: SYSTEM_PROGRAM_ID,
70
+ data: data
14
71
  )
15
- transfer_instruction
16
72
  end
17
73
 
18
- # Helper to construct a new transaction
19
- def self.new_transaction(from_pubkey, to_pubkey, lamports, recent_blockhash, program_id = PROGRAM_ID)
74
+ # Helper to create a new transaction for SOL transfer
75
+ def self.new_sol_transaction(from_pubkey, to_pubkey, lamports, recent_blockhash)
20
76
  transaction = Transaction.new
21
77
  transaction.set_fee_payer(from_pubkey)
22
78
  transaction.set_recent_blockhash(recent_blockhash)
79
+ transfer_instruction = transfer_sol_transaction(from_pubkey, to_pubkey, lamports)
80
+ transaction.add_instruction(transfer_instruction)
81
+ transaction
82
+ end
23
83
 
24
- transfer_instruction = create_transfer(from_pubkey, to_pubkey, lamports, program_id)
84
+ # Method to create an SPL token transfer instruction
85
+ def self.transfer_spl_token(source, destination, owner, amount)
86
+ fields = INSTRUCTION_LAYOUTS[:spl_transfer]
87
+ data = encode_data(fields, { instruction: 3, amount: amount }) # Instruction type 3: Transfer tokens
88
+ TransactionInstruction.new(
89
+ keys: [
90
+ { pubkey: source, is_signer: false, is_writable: true },
91
+ { pubkey: destination, is_signer: false, is_writable: true },
92
+ { pubkey: owner, is_signer: true, is_writable: false }
93
+ ],
94
+ program_id: TOKEN_PROGRAM_ID,
95
+ data: data
96
+ )
97
+ end
98
+
99
+ # Helper to create a new transaction for SPL token transfer
100
+ def self.new_spl_token_transaction(source, destination, owner, amount, recent_blockhash)
101
+ transaction = Transaction.new
102
+ transaction.set_fee_payer(owner)
103
+ transaction.set_recent_blockhash(recent_blockhash)
104
+ transfer_instruction = transfer_spl_token(source, destination, owner, amount)
25
105
  transaction.add_instruction(transfer_instruction)
26
106
  transaction
27
107
  end
108
+
109
+ # Method to create an associated token account for a given token mint
110
+ def self.create_associated_token_account(from_pubkey, token_mint, owner_pubkey)
111
+ data = [0, 0, 0, 0] # No data required for account creation
112
+ create_account_instruction = TransactionInstruction.new(
113
+ keys: [
114
+ { pubkey: from_pubkey, is_signer: true, is_writable: true },
115
+ { pubkey: owner_pubkey, is_signer: false, is_writable: true },
116
+ { pubkey: token_mint, is_signer: false, is_writable: false },
117
+ { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, is_signer: false, is_writable: false },
118
+ { pubkey: SYSTEM_PROGRAM_ID, is_signer: false, is_writable: false }
119
+ ],
120
+ program_id: ASSOCIATED_TOKEN_PROGRAM_ID,
121
+ data: data
122
+ )
123
+ create_account_instruction
124
+ end
125
+
126
+ # Utility to encode data using predefined layouts
127
+ def self.encode_data(fields, data)
128
+ layout = SolanaRuby::DataTypes::Layout.new(fields)
129
+ layout.serialize(data)
130
+ end
131
+
132
+ # Utility to decode data using predefined layouts
133
+ def self.decode_data(fields, data)
134
+ layout = SolanaRuby::DataTypes::Layout.new(fields)
135
+ layout.deserialize(data)
136
+ end
28
137
  end
29
138
  end
@@ -0,0 +1,66 @@
1
+ require 'base58'
2
+ require 'digest/sha2'
3
+
4
+ module SolanaRuby
5
+ class Utils
6
+ class << self
7
+ # Decodes a length-prefixed byte array using a variable-length encoding.
8
+ def decode_length(bytes)
9
+ raise ArgumentError, "Input must be an array of bytes" unless bytes.is_a?(Array)
10
+
11
+ length = 0
12
+ size = 0
13
+ loop do
14
+ raise "Unexpected end of bytes during length decoding" if bytes.empty?
15
+
16
+ byte = bytes.shift
17
+ length |= (byte & 0x7F) << (size * 7)
18
+ size += 1
19
+ break if (byte & 0x80).zero?
20
+ end
21
+ length
22
+ end
23
+
24
+ # Encodes a length as a variable-length byte array.
25
+ def encode_length(length)
26
+ raise ArgumentError, "Length must be a non-negative integer" unless length.is_a?(Integer) && length >= 0
27
+
28
+ bytes = []
29
+ loop do
30
+ byte = length & 0x7F
31
+ length >>= 7
32
+ if length.zero?
33
+ bytes << byte
34
+ break
35
+ else
36
+ bytes << (byte | 0x80)
37
+ end
38
+ end
39
+ bytes
40
+ end
41
+
42
+ # Converts a byte array to a Base58-encoded string.
43
+ def bytes_to_base58(bytes)
44
+ raise ArgumentError, "Input must be an array of bytes" unless bytes.is_a?(Array)
45
+
46
+ Base58.binary_to_base58(bytes.pack('C*'), :bitcoin)
47
+ end
48
+
49
+ # Converts a Base58-encoded string to a byte array.
50
+ def base58_to_bytes(base58_string)
51
+ raise ArgumentError, "Input must be a non-empty string" unless base58_string.is_a?(String) && !base58_string.empty?
52
+
53
+ Base58.base58_to_binary(base58_string, :bitcoin).bytes
54
+ rescue ArgumentError
55
+ raise "Invalid Base58 string: #{base58_string}"
56
+ end
57
+
58
+ # Computes the SHA-256 hash of the given data and returns it as a hexadecimal string.
59
+ def sha256(data)
60
+ raise ArgumentError, "Data must be a string" unless data.is_a?(String)
61
+
62
+ Digest::SHA256.hexdigest(data)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolanaRuby
4
- VERSION = "1.0.1.beta4"
4
+ VERSION = "2.0.0beta1"
5
5
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/*.rb')].each { |file| require file }
4
+ Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/**/*.rb')].each { |file| require file }
5
+
6
+ # Testing Script
7
+
8
+ client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
9
+
10
+ # Fetch the recent blockhash
11
+ recent_blockhash = client.get_latest_blockhash["blockhash"]
12
+
13
+ # Generate a sender keypair and public key
14
+ sender_keypair = SolanaRuby::Keypair.from_private_key("d22867a84ee1d91485a52c587793002dcaa7ce79a58bb605b3af2682099bb778")
15
+ sender_pubkey = sender_keypair[:public_key]
16
+ lamports = 1 * 1_000_000_000
17
+ space = 165
18
+ balance = client.get_balance(sender_pubkey)
19
+ puts "sender account balance: #{balance}, wait for few seconds to update the balance in solana when the balance 0"
20
+
21
+
22
+ # Generate a receiver keypair and public key
23
+ new_account = SolanaRuby::Keypair.generate
24
+ new_account_pubkey = new_account[:public_key]
25
+
26
+ # create a transaction instruction
27
+ transaction = SolanaRuby::TransactionHelper.create_and_sign_transaction(sender_pubkey, new_account_pubkey, lamports, space, recent_blockhash)
28
+
29
+ signed_transaction = transaction.sign([sender_keypair])
30
+ sleep(5)
31
+ response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
32
+ puts "Response: #{response}"
@@ -0,0 +1,54 @@
1
+ require 'pry'
2
+
3
+ # SOL Transfer Testing Script
4
+
5
+ # Initialize the Solana client
6
+ client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
7
+
8
+ # Fetch the recent blockhash
9
+ recent_blockhash = client.get_latest_blockhash["blockhash"]
10
+
11
+ # Generate a sender keypair and public key or Fetch payers keypair using private key
12
+ # sender_keypair = SolanaRuby::Keypair.from_private_key("InsertPrivateKeyHere")
13
+ sender_keypair = SolanaRuby::Keypair.generate
14
+ sender_pubkey = sender_keypair[:public_key]
15
+
16
+
17
+ # Airdrop some lamports to the sender's account
18
+ lamports = 10 * 1_000_000_000
19
+ sleep(1)
20
+ result = client.request_airdrop(sender_pubkey, lamports)
21
+ puts "Solana Balance #{lamports} lamports added sucessfully for the public key: #{sender_pubkey}"
22
+ sleep(10)
23
+
24
+
25
+ # Generate or existing receiver keypair and public key
26
+ keypair = SolanaRuby::Keypair.generate # generate receiver keypair
27
+ receiver_pubkey = keypair[:public_key]
28
+ # receiver_pubkey = 'InsertExistingPublicKeyHere'
29
+
30
+ transfer_lamports = 1 * 1_000_000
31
+ puts "Payer's full private key: #{sender_keypair[:full_private_key]}"
32
+ puts "Receiver's full private key: #{keypair[:full_private_key]}"
33
+ puts "Receiver's Public Key: #{keypair[:public_key]}"
34
+
35
+ # Create a new transaction
36
+ transaction = SolanaRuby::TransactionHelper.new_sol_transaction(
37
+ sender_pubkey,
38
+ receiver_pubkey,
39
+ transfer_lamports,
40
+ recent_blockhash
41
+ )
42
+
43
+ # Get the sender's private key (ensure it's a string)
44
+ private_key = sender_keypair[:private_key]
45
+ puts "Private key type: #{private_key.class}, Value: #{private_key.inspect}"
46
+
47
+ # Sign the transaction
48
+ signed_transaction = transaction.sign([sender_keypair])
49
+
50
+ # Send the transaction to the Solana network
51
+ sleep(5)
52
+ response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
53
+ puts "Response: #{response}"
54
+
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/*.rb')].each { |file| require file }
4
+ Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/**/*.rb')].each { |file| require file }
5
+ # Dir["solana_ruby/*.rb"].each { |f| require_relative f.delete(".rb") }
6
+
7
+
8
+ # Testing Script
9
+
10
+ client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
11
+
12
+ # Fetch the recent blockhash
13
+ recent_blockhash = client.get_latest_blockhash["blockhash"]
14
+
15
+ # Generate a sender keypair and public key
16
+ fee_payer = SolanaRuby::Keypair.from_private_key("d22867a84ee1d91485a52c587793002dcaa7ce79a58bb605b3af2682099bb778")
17
+ fee_payer_pubkey = fee_payer[:public_key]
18
+ lamports = 10 * 1_000_000_000
19
+ space = 165
20
+
21
+ # get balance for the fee payer
22
+ balance = client.get_balance(fee_payer_pubkey)
23
+ puts "sender account balance: #{balance}, wait for few seconds to update the balance in solana when the balance 0"
24
+
25
+
26
+ # # Generate a receiver keypair and public key
27
+ keypair = SolanaRuby::Keypair.generate
28
+ receiver_pubkey = keypair[:public_key]
29
+ transfer_lamports = 1 * 1_000_000
30
+ # puts "Payer's full private key: #{sender_keypair[:full_private_key]}"
31
+ # # puts "Receiver's full private key: #{keypair[:full_private_key]}"
32
+ # # puts "Receiver's Public Key: #{keypair[:public_key]}"
33
+ mint_address = '9BvJGQC5FkLJzUC2TmYpi1iU8n9vt2388GLT5zvu8S1G'
34
+ token_program_id = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
35
+
36
+ # Create a new transaction
37
+ transaction = SolanaRuby::TransactionHelper.new_spl_token_transaction(
38
+ "9BvJGQC5FkLJzUC2TmYpi1iU8n9vt2388GLT5zvu8S1G",
39
+ receiver_pubkey,
40
+ fee_payer_pubkey,
41
+ transfer_lamports,
42
+ recent_blockhash
43
+ )
44
+ # # Get the sender's private key (ensure it's a string)
45
+ private_key = fee_payer[:private_key]
46
+ puts "Private key type: #{private_key.class}, Value: #{private_key.inspect}"
47
+
48
+ # Sign the transaction
49
+ signed_transaction = transaction.sign([fee_payer])
50
+
51
+ # Send the transaction to the Solana network
52
+ sleep(5)
53
+ response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
54
+ puts "Response: #{response}"
55
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solana-ruby-web3js
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1.beta4
4
+ version: 2.0.0beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - BuildSquad
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-11 00:00:00.000000000 Z
11
+ date: 2024-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket-client-simple
@@ -229,6 +229,12 @@ files:
229
229
  - lib/solana_ruby.rb
230
230
  - lib/solana_ruby/.DS_Store
231
231
  - lib/solana_ruby/base_client.rb
232
+ - lib/solana_ruby/data_types.rb
233
+ - lib/solana_ruby/data_types/blob.rb
234
+ - lib/solana_ruby/data_types/layout.rb
235
+ - lib/solana_ruby/data_types/near_int64.rb
236
+ - lib/solana_ruby/data_types/sequence.rb
237
+ - lib/solana_ruby/data_types/unsigned_int.rb
232
238
  - lib/solana_ruby/http_client.rb
233
239
  - lib/solana_ruby/http_methods/account_methods.rb
234
240
  - lib/solana_ruby/http_methods/basic_methods.rb
@@ -240,9 +246,11 @@ files:
240
246
  - lib/solana_ruby/http_methods/token_methods.rb
241
247
  - lib/solana_ruby/http_methods/transaction_methods.rb
242
248
  - lib/solana_ruby/keypair.rb
249
+ - lib/solana_ruby/message.rb
243
250
  - lib/solana_ruby/transaction.rb
244
251
  - lib/solana_ruby/transaction_helper.rb
245
252
  - lib/solana_ruby/transaction_instruction.rb
253
+ - lib/solana_ruby/utils.rb
246
254
  - lib/solana_ruby/version.rb
247
255
  - lib/solana_ruby/web_socket_client.rb
248
256
  - lib/solana_ruby/web_socket_handlers.rb
@@ -251,6 +259,9 @@ files:
251
259
  - lib/solana_ruby/web_socket_methods/root_methods.rb
252
260
  - lib/solana_ruby/web_socket_methods/signature_methods.rb
253
261
  - lib/solana_ruby/web_socket_methods/slot_methods.rb
262
+ - transaction_testing/create_account.rb
263
+ - transaction_testing/sol_transfer.rb
264
+ - transaction_testing/spl_token_transfer.rb
254
265
  homepage: https://github.com/Build-Squad/solana-ruby
255
266
  licenses:
256
267
  - MIT
@@ -273,7 +284,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
273
284
  - !ruby/object:Gem::Version
274
285
  version: '0'
275
286
  requirements: []
276
- rubygems_version: 3.5.20
287
+ rubygems_version: 3.5.23
277
288
  signing_key:
278
289
  specification_version: 4
279
290
  summary: Solana Ruby SDK