solana-ruby-web3js 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +72 -0
- data/lib/solana_ruby/data_types/unsigned_int.rb +8 -8
- data/lib/solana_ruby/data_types.rb +4 -0
- data/lib/solana_ruby/transaction.rb +27 -21
- data/lib/solana_ruby/transaction_helper.rb +37 -23
- data/lib/solana_ruby/version.rb +1 -1
- data/transaction_testing/create_account.rb +40 -15
- data/transaction_testing/sol_transfer.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83aeed228731c11fa62b87f4ab5761a6715754921effc58400764bbe43378409
|
4
|
+
data.tar.gz: a0cd63b8a7998539304ce06ed69692e86361fece542033d63fab7383a692ebb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29277d937c3201916bd0bd1eec7172621a4a83b2b21d5045afa5a285f4d757ba12de5f99031da0f0030e08e68adc4c7de1cb6a55301463cfb05f0b5e63072ffc
|
7
|
+
data.tar.gz: 0b627b741396f666e92fe8b032d859e944c933eda6b8ac7da29201d7913f116195b0751bcc2247537ca3a2b461bff9d2999ac234669e56a2708efe39776d881d
|
data/Gemfile.lock
CHANGED
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
|
+
|
@@ -4,26 +4,26 @@ module SolanaRuby
|
|
4
4
|
attr_reader :size
|
5
5
|
|
6
6
|
BITS = {
|
7
|
-
8 => { directive: 'C
|
8
|
-
32 => { directive: 'L
|
9
|
-
64 => { directive: 'Q
|
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
10
|
}
|
11
11
|
|
12
12
|
def initialize(bits)
|
13
13
|
@bits = bits
|
14
14
|
type = BITS[@bits]
|
15
|
-
raise "
|
15
|
+
raise "Unsupported size. Supported sizes: #{BITS.keys.join(', ')} bits" unless type
|
16
16
|
@size = type[:size]
|
17
17
|
@directive = type[:directive]
|
18
18
|
end
|
19
19
|
|
20
|
-
# Serialize the unsigned integer into bytes
|
20
|
+
# Serialize the unsigned integer into properly aligned bytes
|
21
21
|
def serialize(obj)
|
22
22
|
raise "Can only serialize integers" unless obj.is_a?(Integer)
|
23
|
-
raise "Cannot serialize negative integers" if obj
|
23
|
+
raise "Cannot serialize negative integers" if obj.negative?
|
24
24
|
|
25
25
|
if obj >= 256**@size
|
26
|
-
raise "Integer too large
|
26
|
+
raise "Integer too large to fit in #{@size} bytes"
|
27
27
|
end
|
28
28
|
|
29
29
|
[obj].pack(@directive).bytes
|
@@ -31,7 +31,7 @@ module SolanaRuby
|
|
31
31
|
|
32
32
|
# Deserialize bytes into the unsigned integer
|
33
33
|
def deserialize(bytes)
|
34
|
-
raise "Invalid serialization (
|
34
|
+
raise "Invalid serialization (expected #{@size} bytes, got #{bytes.size})" if bytes.size != @size
|
35
35
|
|
36
36
|
bytes.pack('C*').unpack(@directive).first
|
37
37
|
end
|
@@ -1,3 +1,5 @@
|
|
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'
|
@@ -70,10 +72,10 @@ module SolanaRuby
|
|
70
72
|
instructions.push(item)
|
71
73
|
end
|
72
74
|
|
73
|
-
def sign(
|
74
|
-
raise 'No signers' unless
|
75
|
+
def sign(keypairs)
|
76
|
+
raise 'No signers' unless keypairs.any?
|
75
77
|
|
76
|
-
keys =
|
78
|
+
keys = keypairs.uniq { |kp| kp[:public_key] }
|
77
79
|
@signatures = keys.map do |key|
|
78
80
|
{
|
79
81
|
signature: nil,
|
@@ -114,15 +116,17 @@ module SolanaRuby
|
|
114
116
|
def compile_message
|
115
117
|
check_for_errors
|
116
118
|
fetch_message_data
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
account_keys: @account_keys,
|
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
|
124
128
|
)
|
125
|
-
|
129
|
+
message_data
|
126
130
|
end
|
127
131
|
|
128
132
|
def check_for_errors
|
@@ -168,11 +172,8 @@ module SolanaRuby
|
|
168
172
|
# Split out signing from non-signing keys and count header values
|
169
173
|
signed_keys = []
|
170
174
|
unsigned_keys = []
|
171
|
-
|
175
|
+
@header = split_keys(unique_metas, signed_keys, unsigned_keys)
|
172
176
|
@account_keys = signed_keys + unsigned_keys
|
173
|
-
|
174
|
-
# add instruction structure
|
175
|
-
@instructs = add_instructs
|
176
177
|
end
|
177
178
|
|
178
179
|
def append_program_id(program_ids, account_metas)
|
@@ -245,19 +246,24 @@ module SolanaRuby
|
|
245
246
|
end
|
246
247
|
|
247
248
|
def split_keys(unique_metas, signed_keys, unsigned_keys)
|
248
|
-
|
249
|
-
|
250
|
-
|
249
|
+
num_required_signatures = 0
|
250
|
+
num_readonly_signed_accounts = 0
|
251
|
+
num_readonly_unsigned_accounts = 0
|
251
252
|
unique_metas.each do |meta|
|
252
253
|
if meta[:is_signer]
|
253
254
|
signed_keys.push(meta[:pubkey])
|
254
|
-
|
255
|
-
|
255
|
+
num_required_signatures += 1
|
256
|
+
num_readonly_signed_accounts += 1 if (!meta[:is_writable])
|
256
257
|
else
|
257
258
|
unsigned_keys.push(meta[:pubkey])
|
258
|
-
|
259
|
+
num_readonly_unsigned_accounts += 1 if (!meta[:is_writable])
|
259
260
|
end
|
260
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
|
+
}
|
261
267
|
end
|
262
268
|
|
263
269
|
def partial_sign(message, keys)
|
@@ -19,46 +19,59 @@ module SolanaRuby
|
|
19
19
|
instruction: :uint8,
|
20
20
|
amount: :uint64
|
21
21
|
},
|
22
|
-
|
22
|
+
# Create account layout
|
23
23
|
create_account: {
|
24
|
-
instruction: :
|
24
|
+
instruction: :uint32,
|
25
25
|
lamports: :uint64,
|
26
|
-
space: :uint64
|
26
|
+
space: :uint64,
|
27
|
+
program_id: :blob32
|
27
28
|
}
|
28
29
|
}
|
29
30
|
|
30
31
|
# Method to create a system account (e.g., for SPL token or SOL)
|
31
|
-
def self.
|
32
|
-
|
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
|
33
45
|
create_account_instruction = TransactionInstruction.new(
|
34
46
|
keys: [
|
35
|
-
{ pubkey: from_pubkey, is_signer: true, is_writable: true },
|
36
|
-
{ pubkey: new_account_pubkey, is_signer:
|
37
|
-
{ pubkey: owner_pubkey, is_signer: false, is_writable: false }
|
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
|
38
49
|
],
|
39
|
-
program_id:
|
40
|
-
data: instruction_data
|
50
|
+
program_id: program_id, # Use Solana's system program for account creation
|
51
|
+
data: instruction_data # Encoded instruction data
|
41
52
|
)
|
53
|
+
|
54
|
+
# return instruction data
|
42
55
|
create_account_instruction
|
43
56
|
end
|
44
57
|
|
45
|
-
|
58
|
+
|
59
|
+
def self.create_account(from_pubkey, new_account_pubkey, lamports, space, recent_blockhash, program_id = SYSTEM_PROGRAM_ID)
|
46
60
|
# Create the transaction
|
47
61
|
transaction = Transaction.new
|
48
62
|
transaction.set_fee_payer(from_pubkey)
|
49
63
|
transaction.set_recent_blockhash(recent_blockhash)
|
50
64
|
|
51
65
|
# Add the create account instruction to the transaction
|
52
|
-
|
53
|
-
transaction.add_instruction(
|
54
|
-
|
55
|
-
#
|
56
|
-
# Example: signing and sending 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
|
57
70
|
transaction
|
58
71
|
end
|
59
72
|
|
60
73
|
# Method to create a SOL transfer instruction
|
61
|
-
def self.
|
74
|
+
def self.transfer_sol_instruction(from_pubkey, to_pubkey, lamports)
|
62
75
|
fields = INSTRUCTION_LAYOUTS[:sol_transfer]
|
63
76
|
data = encode_data(fields, { instruction: 2, lamports: lamports })
|
64
77
|
TransactionInstruction.new(
|
@@ -72,11 +85,11 @@ module SolanaRuby
|
|
72
85
|
end
|
73
86
|
|
74
87
|
# Helper to create a new transaction for SOL transfer
|
75
|
-
def self.
|
88
|
+
def self.sol_transfer(from_pubkey, to_pubkey, lamports, recent_blockhash)
|
76
89
|
transaction = Transaction.new
|
77
90
|
transaction.set_fee_payer(from_pubkey)
|
78
91
|
transaction.set_recent_blockhash(recent_blockhash)
|
79
|
-
transfer_instruction =
|
92
|
+
transfer_instruction = transfer_sol_instruction(from_pubkey, to_pubkey, lamports)
|
80
93
|
transaction.add_instruction(transfer_instruction)
|
81
94
|
transaction
|
82
95
|
end
|
@@ -107,13 +120,14 @@ module SolanaRuby
|
|
107
120
|
end
|
108
121
|
|
109
122
|
# Method to create an associated token account for a given token mint
|
110
|
-
def self.create_associated_token_account(
|
123
|
+
def self.create_associated_token_account(payer, mint, owner)
|
111
124
|
data = [0, 0, 0, 0] # No data required for account creation
|
112
125
|
create_account_instruction = TransactionInstruction.new(
|
113
126
|
keys: [
|
114
|
-
{ pubkey:
|
115
|
-
{ pubkey:
|
116
|
-
{ pubkey:
|
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 },
|
117
131
|
{ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, is_signer: false, is_writable: false },
|
118
132
|
{ pubkey: SYSTEM_PROGRAM_ID, is_signer: false, is_writable: false }
|
119
133
|
],
|
data/lib/solana_ruby/version.rb
CHANGED
@@ -3,30 +3,55 @@
|
|
3
3
|
Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/*.rb')].each { |file| require file }
|
4
4
|
Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/**/*.rb')].each { |file| require file }
|
5
5
|
|
6
|
-
#
|
7
|
-
|
6
|
+
# Initialize Solana client
|
8
7
|
client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
|
9
8
|
|
10
9
|
# Fetch the recent blockhash
|
11
10
|
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
11
|
+
puts "Recent Blockhash: #{recent_blockhash}"
|
12
12
|
|
13
|
-
#
|
14
|
-
|
13
|
+
# Sender keypair and public key
|
14
|
+
private_key = "d22867a84ee1d91485a52c587793002dcaa7ce79a58bb605b3af2682099bb778"
|
15
|
+
sender_keypair = SolanaRuby::Keypair.from_private_key(private_key)
|
15
16
|
sender_pubkey = sender_keypair[:public_key]
|
16
|
-
|
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"
|
17
|
+
puts "Sender Public Key: #{sender_pubkey}"
|
20
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
|
21
26
|
|
22
|
-
#
|
27
|
+
# new keypair and public key (new account)
|
23
28
|
new_account = SolanaRuby::Keypair.generate
|
24
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' })
|
25
53
|
|
26
|
-
#
|
27
|
-
|
54
|
+
# Output transaction results
|
55
|
+
puts "Transaction Signature: #{response}"
|
56
|
+
puts "New account created successfully with Public Key: #{new_account_pubkey}"
|
28
57
|
|
29
|
-
signed_transaction = transaction.sign([sender_keypair])
|
30
|
-
sleep(5)
|
31
|
-
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
32
|
-
puts "Response: #{response}"
|
@@ -35,7 +35,7 @@ puts "Receiver's full private key: #{keypair[:full_private_key]}"
|
|
35
35
|
puts "Receiver's Public Key: #{keypair[:public_key]}"
|
36
36
|
|
37
37
|
# Create a new transaction
|
38
|
-
transaction = SolanaRuby::TransactionHelper.
|
38
|
+
transaction = SolanaRuby::TransactionHelper.sol_transfer(
|
39
39
|
sender_pubkey,
|
40
40
|
receiver_pubkey,
|
41
41
|
transfer_lamports,
|
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: 2.0.
|
4
|
+
version: 2.0.1
|
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
|
+
date: 2024-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: websocket-client-simple
|