solana-ruby-web3js 1.0.1.beta4 → 2.0.0beta2

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: 6bb6650de7a5beae14649fe9cfac20973923d0be4c4f74e9d1e8f373e70842b9
4
+ data.tar.gz: c3ad4316d61d4326327bb281a3eb64991011ae14d51a7974de6eaf9705d4ffa5
5
5
  SHA512:
6
- metadata.gz: 1d75c69661f9bd7353c9251d8cb7615785d5233d7832a1e596c02997a42b1f07dbfb9600abb45ea2b04dfc8d80ddbe2167e91e2504c436ca27e99ec30a4679f2
7
- data.tar.gz: e4784c227e3e24fd2bcaf79734b42602540c39c8ed4338fc5a53d740b2077aedeb76e7bb62c84d7cf4bc54c5a044281293d02adfd92fc487483d428ab03c1ea5
6
+ metadata.gz: 94c8304f1d05d7cbd8ec22947d98ede21c1892b210ad5cdb43f3e6293f4c6ab288c8aa83b1ba6e40a28d08a191a32ba775966eeb370631d02112cac981309fb6
7
+ data.tar.gz: '0085b47b4f44786e0bd0af77827a12cbc191744a16aff61448cafff641a53efa798677ef51b3748966f76547b883fd767d3f48ffe5297f8519cc08eb1ee81e90'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- solana-ruby-web3js (1.0.1.beta3)
4
+ solana-ruby-web3js (2.0.0beta1)
5
5
  base58 (~> 0.2.3)
6
6
  base64 (~> 0.2.0)
7
7
  ed25519
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 Mainnet, 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 when needed.
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
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.sol_transfer(
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-bit unsigned integer
8
+ 32 => { directive: 'L<', size: 4 }, # 32-bit little-endian unsigned integer
9
+ 64 => { directive: 'Q<', size: 8 } # 64-bit little-endian unsigned integer
10
+ }
11
+
12
+ def initialize(bits)
13
+ @bits = bits
14
+ type = BITS[@bits]
15
+ raise "Unsupported size. Supported sizes: #{BITS.keys.join(', ')} bits" unless type
16
+ @size = type[:size]
17
+ @directive = type[:directive]
18
+ end
19
+
20
+ # Serialize the unsigned integer into properly aligned bytes
21
+ def serialize(obj)
22
+ raise "Can only serialize integers" unless obj.is_a?(Integer)
23
+ raise "Cannot serialize negative integers" if obj.negative?
24
+
25
+ if obj >= 256**@size
26
+ raise "Integer too large to 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 (expected #{@size} bytes, got #{bytes.size})" if bytes.size != @size
35
+
36
+ bytes.pack('C*').unpack(@directive).first
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
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
+
25
+ def blob32
26
+ Blob.new(32)
27
+ end
28
+ end
29
+ 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,266 @@ 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(keypairs)
76
+ raise 'No signers' unless keypairs.any?
55
77
 
56
- message = serialize_message
57
- signature = signing_key.sign(message)
78
+ keys = keypairs.uniq { |kp| kp[: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
67
96
 
68
- message_data = []
69
- message_data << Base58.base58_to_binary(@recent_blockhash)
70
- message_data << [accounts.length].pack("C")
97
+ def compile
98
+ message = compile_message
99
+ signed_keys = message.account_keys.slice(0, message.header[:num_required_signatures])
71
100
 
72
- accounts.each do |account|
73
- message_data << account
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
74
104
  end
75
105
 
76
- message_data << [@instructions.length].pack("C")
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
+
120
+ # add instruction structure
121
+ instructs = add_instructs
122
+
123
+ message_data = Message.new(
124
+ header: @header,
125
+ account_keys: @account_keys,
126
+ recent_blockhash: recent_blockhash,
127
+ instructions: instructs
128
+ )
129
+ message_data
130
+ end
131
+
132
+ def check_for_errors
133
+ raise 'Transaction recent_blockhash required' unless recent_blockhash
77
134
 
78
- @instructions.each do |instruction|
79
- message_data << instruction.serialize
135
+ puts 'No instructions provided' if instructions.length < 1
136
+
137
+ if fee_payer.nil? && signatures.length > 0 && signatures[0][:public_key]
138
+ @fee_payer = signatures[0][:public_key] if (signatures.length > 0 && signatures[0][:public_key])
80
139
  end
140
+
141
+ raise('Transaction fee payer required') if @fee_payer.nil?
81
142
 
82
- message_data.join
143
+ instructions.each_with_index do |instruction, i|
144
+ raise("Transaction instruction index #{i} has undefined program id") unless instruction.program_id
145
+ end
83
146
  end
84
147
 
85
- def collect_accounts
86
- accounts = []
87
- accounts << Base58.base58_to_binary(@fee_payer) if @fee_payer
148
+ def fetch_message_data
149
+ program_ids = []
150
+ account_metas= []
151
+
152
+ instructions.each do |instruction|
153
+ account_metas += instruction.keys
154
+ program_ids.push(instruction.program_id) unless program_ids.include?(instruction.program_id)
155
+ end
156
+
157
+ # Append programID account metas
158
+ append_program_id(program_ids, account_metas)
159
+
160
+ # Sort. Prioritizing first by signer, then by writable
161
+ signer_order(account_metas)
162
+
163
+ # Cull duplicate account metas
164
+ unique_metas = []
165
+ add_unique_meta_data(unique_metas, account_metas)
88
166
 
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)
167
+ add_fee_payer_meta(unique_metas)
168
+
169
+ # Disallow unknown signers
170
+ disallow_signers(signatures, unique_metas)
171
+
172
+ # Split out signing from non-signing keys and count header values
173
+ signed_keys = []
174
+ unsigned_keys = []
175
+ @header = split_keys(unique_metas, signed_keys, unsigned_keys)
176
+ @account_keys = signed_keys + unsigned_keys
177
+ end
178
+
179
+ def append_program_id(program_ids, account_metas)
180
+ program_ids.each do |programId|
181
+ account_metas.push({
182
+ pubkey: programId,
183
+ is_signer: false,
184
+ is_writable: false,
185
+ })
186
+ end
187
+ end
188
+
189
+ def signer_order(account_metas)
190
+ account_metas.sort! do |x, y|
191
+ check_signer = x[:is_signer] == y[:is_signer] ? nil : x[:is_signer] ? -1 : 1
192
+ check_writable = x[:is_writable] == y[:is_writable] ? nil : (x[:is_writable] ? -1 : 1)
193
+ (check_signer || check_writable) || 0
194
+ end
195
+ end
196
+
197
+ def add_unique_meta_data(unique_metas, account_metas)
198
+ account_metas.each do |account_meta|
199
+ pubkey_string = account_meta[:pubkey]
200
+ unique_index = unique_metas.find_index{|x| x[:pubkey] == pubkey_string }
201
+ if unique_index
202
+ unique_metas[unique_index][:is_writable] = unique_metas[unique_index][:is_writable] || account_meta[:is_writable]
203
+ else
204
+ unique_metas.push(account_meta);
93
205
  end
94
206
  end
207
+ end
95
208
 
96
- accounts.uniq
209
+ def add_fee_payer_meta(unique_metas)
210
+ # Move fee payer to the front
211
+ fee_payer_index = unique_metas.find_index { |x| x[:pubkey] == fee_payer }
212
+ if fee_payer_index
213
+ payer_meta = unique_metas.delete_at(fee_payer_index)
214
+ payer_meta[:is_signer] = true
215
+ payer_meta[:is_writable] = true
216
+ unique_metas.unshift(payer_meta)
217
+ else
218
+ unique_metas.unshift({
219
+ pubkey: fee_payer,
220
+ is_signer: true,
221
+ is_writable: true,
222
+ })
223
+ end
97
224
  end
98
- end
99
- end
100
225
 
101
- class Base58
102
- ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'.freeze
226
+ def disallow_signers(signatures, unique_metas)
227
+ signatures.each do |signature|
228
+ unique_index = unique_metas.find_index{ |x| x[:pubkey] == signature[:public_key] }
103
229
 
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) }
230
+ if unique_index
231
+ unique_metas[unique_index][:is_signer] = true unless unique_metas[unique_index][:is_signer]
232
+ else
233
+ raise "unknown signer: #{signature[:public_key]}"
234
+ end
235
+ end
236
+ end
237
+
238
+ def add_instructs
239
+ instructions.map do |instruction|
240
+ {
241
+ program_id_index: @account_keys.index(instruction.program_id),
242
+ accounts: instruction.keys.map { |meta| @account_keys.index(meta[:pubkey]) },
243
+ data: instruction.data
244
+ }
245
+ end
246
+ end
247
+
248
+ def split_keys(unique_metas, signed_keys, unsigned_keys)
249
+ num_required_signatures = 0
250
+ num_readonly_signed_accounts = 0
251
+ num_readonly_unsigned_accounts = 0
252
+ unique_metas.each do |meta|
253
+ if meta[:is_signer]
254
+ signed_keys.push(meta[:pubkey])
255
+ num_required_signatures += 1
256
+ num_readonly_signed_accounts += 1 if (!meta[:is_writable])
257
+ else
258
+ unsigned_keys.push(meta[:pubkey])
259
+ num_readonly_unsigned_accounts += 1 if (!meta[:is_writable])
260
+ end
261
+ end
262
+ {
263
+ num_required_signatures: num_required_signatures,
264
+ num_readonly_signed_accounts: num_readonly_signed_accounts,
265
+ num_readonly_unsigned_accounts: num_readonly_unsigned_accounts,
266
+ }
267
+ end
268
+
269
+ def partial_sign(message, keys)
270
+ sign_data = message.serialize
271
+ keys.each do |key|
272
+ private_key_bytes = [key[:private_key]].pack('H*')
273
+ signing_key = RbNaCl::Signatures::Ed25519::SigningKey.new(private_key_bytes)
274
+ signature = signing_key.sign(sign_data.pack('C*')).bytes
275
+ add_signature(key[:public_key], signature)
276
+ end
277
+ end
278
+
279
+ def add_signature(pubkey, signature)
280
+ raise 'error' unless signature.length === 64
281
+ index = signatures.find_index{|s| s[:public_key] == pubkey}
282
+ raise "unknown signer: #{pubkey}" unless index
283
+
284
+ @signatures[index][:signature] = signature
285
+ end
107
286
  end
108
287
  end
@@ -1,29 +1,152 @@
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: :uint32,
25
+ lamports: :uint64,
26
+ space: :uint64,
27
+ program_id: :blob32
28
+ }
29
+ }
30
+
31
+ # Method to create a system account (e.g., for SPL token or SOL)
32
+ def self.account_instruction(from_pubkey, new_account_pubkey, lamports, space, program_id)
33
+ # Encode the instruction data
34
+ instruction_data = encode_data(
35
+ INSTRUCTION_LAYOUTS[:create_account],
36
+ {
37
+ instruction: 0, # '0' corresponds to the Create Account instruction
38
+ lamports: lamports, # The amount of lamports to transfer to the new account
39
+ space: space, # Amount of space allocated for the account's data
40
+ program_id: Base58.base58_to_binary(program_id, :bitcoin).bytes # Convert public key to binary
41
+ }
42
+ )
43
+
44
+ # Construct the transaction instruction
45
+ create_account_instruction = TransactionInstruction.new(
46
+ keys: [
47
+ { pubkey: from_pubkey, is_signer: true, is_writable: true }, # Funder's account
48
+ { pubkey: new_account_pubkey, is_signer: true, is_writable: true } # New account
49
+ ],
50
+ program_id: program_id, # Use Solana's system program for account creation
51
+ data: instruction_data # Encoded instruction data
52
+ )
53
+
54
+ # return instruction data
55
+ create_account_instruction
56
+ end
57
+
58
+
59
+ def self.create_account(from_pubkey, new_account_pubkey, lamports, space, recent_blockhash, program_id = SYSTEM_PROGRAM_ID)
60
+ # Create the transaction
61
+ transaction = Transaction.new
62
+ transaction.set_fee_payer(from_pubkey)
63
+ transaction.set_recent_blockhash(recent_blockhash)
64
+
65
+ # Add the create account instruction to the transaction
66
+ instruction = account_instruction(from_pubkey, new_account_pubkey, lamports, space, program_id)
67
+ transaction.add_instruction(instruction)
68
+
69
+ # return the transaction for signing
70
+ transaction
71
+ end
72
+
73
+ # Method to create a SOL transfer instruction
74
+ def self.transfer_sol_instruction(from_pubkey, to_pubkey, lamports)
75
+ fields = INSTRUCTION_LAYOUTS[:sol_transfer]
76
+ data = encode_data(fields, { instruction: 2, lamports: lamports })
77
+ TransactionInstruction.new(
8
78
  keys: [
9
79
  { pubkey: from_pubkey, is_signer: true, is_writable: true },
10
80
  { pubkey: to_pubkey, is_signer: false, is_writable: true }
11
81
  ],
12
- program_id: program_id,
13
- data: [2, lamports].pack('CQ<') # Instruction type 2 (transfer) + lamports (u64)
82
+ program_id: SYSTEM_PROGRAM_ID,
83
+ data: data
14
84
  )
15
- transfer_instruction
16
85
  end
17
86
 
18
- # Helper to construct a new transaction
19
- def self.new_transaction(from_pubkey, to_pubkey, lamports, recent_blockhash, program_id = PROGRAM_ID)
87
+ # Helper to create a new transaction for SOL transfer
88
+ def self.sol_transfer(from_pubkey, to_pubkey, lamports, recent_blockhash)
20
89
  transaction = Transaction.new
21
90
  transaction.set_fee_payer(from_pubkey)
22
91
  transaction.set_recent_blockhash(recent_blockhash)
92
+ transfer_instruction = transfer_sol_instruction(from_pubkey, to_pubkey, lamports)
93
+ transaction.add_instruction(transfer_instruction)
94
+ transaction
95
+ end
23
96
 
24
- transfer_instruction = create_transfer(from_pubkey, to_pubkey, lamports, program_id)
97
+ # Method to create an SPL token transfer instruction
98
+ def self.transfer_spl_token(source, destination, owner, amount)
99
+ fields = INSTRUCTION_LAYOUTS[:spl_transfer]
100
+ data = encode_data(fields, { instruction: 3, amount: amount }) # Instruction type 3: Transfer tokens
101
+ TransactionInstruction.new(
102
+ keys: [
103
+ { pubkey: source, is_signer: false, is_writable: true },
104
+ { pubkey: destination, is_signer: false, is_writable: true },
105
+ { pubkey: owner, is_signer: true, is_writable: false }
106
+ ],
107
+ program_id: TOKEN_PROGRAM_ID,
108
+ data: data
109
+ )
110
+ end
111
+
112
+ # Helper to create a new transaction for SPL token transfer
113
+ def self.new_spl_token_transaction(source, destination, owner, amount, recent_blockhash)
114
+ transaction = Transaction.new
115
+ transaction.set_fee_payer(owner)
116
+ transaction.set_recent_blockhash(recent_blockhash)
117
+ transfer_instruction = transfer_spl_token(source, destination, owner, amount)
25
118
  transaction.add_instruction(transfer_instruction)
26
119
  transaction
27
120
  end
121
+
122
+ # Method to create an associated token account for a given token mint
123
+ def self.create_associated_token_account(payer, mint, owner)
124
+ data = [0, 0, 0, 0] # No data required for account creation
125
+ create_account_instruction = TransactionInstruction.new(
126
+ keys: [
127
+ { pubkey: payer, is_signer: true, is_writable: true },
128
+ { pubkey: associated_token, is_signer: false, is_writable: true },
129
+ { pubkey: owner, is_signer: false, is_writable: false },
130
+ { pubkey: mint, is_signer: false, is_writable: false },
131
+ { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, is_signer: false, is_writable: false },
132
+ { pubkey: SYSTEM_PROGRAM_ID, is_signer: false, is_writable: false }
133
+ ],
134
+ program_id: ASSOCIATED_TOKEN_PROGRAM_ID,
135
+ data: data
136
+ )
137
+ create_account_instruction
138
+ end
139
+
140
+ # Utility to encode data using predefined layouts
141
+ def self.encode_data(fields, data)
142
+ layout = SolanaRuby::DataTypes::Layout.new(fields)
143
+ layout.serialize(data)
144
+ end
145
+
146
+ # Utility to decode data using predefined layouts
147
+ def self.decode_data(fields, data)
148
+ layout = SolanaRuby::DataTypes::Layout.new(fields)
149
+ layout.deserialize(data)
150
+ end
28
151
  end
29
152
  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.0beta2"
5
5
  end
@@ -0,0 +1,57 @@
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
+ # Initialize Solana client
7
+ client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
8
+
9
+ # Fetch the recent blockhash
10
+ recent_blockhash = client.get_latest_blockhash["blockhash"]
11
+ puts "Recent Blockhash: #{recent_blockhash}"
12
+
13
+ # Sender keypair and public key
14
+ private_key = "d22867a84ee1d91485a52c587793002dcaa7ce79a58bb605b3af2682099bb778"
15
+ sender_keypair = SolanaRuby::Keypair.from_private_key(private_key)
16
+ sender_pubkey = sender_keypair[:public_key]
17
+ puts "Sender Public Key: #{sender_pubkey}"
18
+
19
+ # Check sender's account balance
20
+ balance = client.get_balance(sender_pubkey)
21
+ puts "Sender account balance: #{balance} lamports"
22
+ if balance == 0
23
+ puts "Balance is zero, waiting for balance update..."
24
+ sleep(10)
25
+ end
26
+
27
+ # new keypair and public key (new account)
28
+ new_account = SolanaRuby::Keypair.generate
29
+ new_account_pubkey = new_account[:public_key]
30
+ puts "New Account Public Key: #{new_account_pubkey}"
31
+
32
+ # Parameters for account creation
33
+ lamports = 1 * 1_000_000_000 # Lamports to transfer
34
+ space = 165 # Space allocation (bytes)
35
+ program_id = SolanaRuby::TransactionHelper::SYSTEM_PROGRAM_ID
36
+
37
+ # Create and sign the transaction
38
+ transaction = SolanaRuby::TransactionHelper.create_account(
39
+ sender_pubkey,
40
+ new_account_pubkey,
41
+ lamports,
42
+ space,
43
+ recent_blockhash,
44
+ program_id
45
+ )
46
+
47
+ # Sign transaction with both sender and new account keypairs
48
+ transaction.sign([sender_keypair, new_account])
49
+
50
+ # Send the transaction
51
+ puts "Sending transaction..."
52
+ response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
53
+
54
+ # Output transaction results
55
+ puts "Transaction Signature: #{response}"
56
+ puts "New account created successfully with Public Key: #{new_account_pubkey}"
57
+
@@ -0,0 +1,56 @@
1
+ Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/*.rb')].each { |file| require file }
2
+ Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/**/*.rb')].each { |file| require file }
3
+ require 'pry'
4
+
5
+ # SOL Transfer Testing Script
6
+
7
+ # Initialize the Solana client
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 or Fetch payers keypair using private key
14
+ # sender_keypair = SolanaRuby::Keypair.from_private_key("InsertPrivateKeyHere")
15
+ sender_keypair = SolanaRuby::Keypair.generate
16
+ sender_pubkey = sender_keypair[:public_key]
17
+
18
+
19
+ # Airdrop some lamports to the sender's account
20
+ lamports = 10 * 1_000_000_000
21
+ sleep(1)
22
+ result = client.request_airdrop(sender_pubkey, lamports)
23
+ puts "Solana Balance #{lamports} lamports added sucessfully for the public key: #{sender_pubkey}"
24
+ sleep(10)
25
+
26
+
27
+ # Generate or existing receiver keypair and public key
28
+ keypair = SolanaRuby::Keypair.generate # generate receiver keypair
29
+ receiver_pubkey = keypair[:public_key]
30
+ # receiver_pubkey = 'InsertExistingPublicKeyHere'
31
+
32
+ transfer_lamports = 1 * 1_000_000
33
+ puts "Payer's full private key: #{sender_keypair[:full_private_key]}"
34
+ puts "Receiver's full private key: #{keypair[:full_private_key]}"
35
+ puts "Receiver's Public Key: #{keypair[:public_key]}"
36
+
37
+ # Create a new transaction
38
+ transaction = SolanaRuby::TransactionHelper.sol_transfer(
39
+ sender_pubkey,
40
+ receiver_pubkey,
41
+ transfer_lamports,
42
+ recent_blockhash
43
+ )
44
+
45
+ # Get the sender's private key (ensure it's a string)
46
+ private_key = sender_keypair[:private_key]
47
+ puts "Private key type: #{private_key.class}, Value: #{private_key.inspect}"
48
+
49
+ # Sign the transaction
50
+ signed_transaction = transaction.sign([sender_keypair])
51
+
52
+ # Send the transaction to the Solana network
53
+ sleep(5)
54
+ response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
55
+ puts "Response: #{response}"
56
+
@@ -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.0beta2
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-28 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