solana-ruby-web3js 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +131 -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/ed25519_curve_checker.rb +70 -0
- data/lib/solana_ruby/keypair.rb +32 -8
- data/lib/solana_ruby/public_key.rb +62 -0
- data/lib/solana_ruby/transaction.rb +28 -21
- data/lib/solana_ruby/transaction_helper.rb +158 -55
- data/lib/solana_ruby/transaction_helpers/token_account.rb +73 -0
- data/lib/solana_ruby/version.rb +1 -1
- data/lib/solana_ruby.rb +0 -1
- data/transaction_testing/burn_spl_tokens.rb +37 -0
- data/transaction_testing/create_account.rb +43 -15
- data/transaction_testing/create_spl_token_account.rb +52 -0
- data/transaction_testing/mint_spl_tokens.rb +40 -0
- data/transaction_testing/sol_transfer.rb +2 -2
- data/transaction_testing/spl_token_transfer.rb +8 -5
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2bd60b642f794a5923a3ff1d1b39e29e60aca6444445b0cf2e36e7393b18ff3
|
4
|
+
data.tar.gz: 95a61aab91ec4eaacb94fdc671027a2005b320b938a7a2132dc0daf9b643d45b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85acebeaeaebce2d39fe74f7d931a7e339e1a773d73f4774003a06a84bed4f94343b4f41269ac4bd1b2c7fbd186145ccba7d8d25b35b9aadee9a2b9f9a56c06e
|
7
|
+
data.tar.gz: 68b1d9bf25f64d1424e660387cc802e12842ea428547c4d4643b23489f2dab3db75c4d63a35d23159521b41634b11aef0befa9fa3193c2cf275f14f22412b70f
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -332,3 +332,134 @@ 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
|
+
|
407
|
+
### Account Creation
|
408
|
+
|
409
|
+
The create_account helper allows creating a new account with specified parameters. This is commonly used to set up accounts for tokens, programs, or other allocations on the Solana blockchain.
|
410
|
+
|
411
|
+
#### Requirements:
|
412
|
+
|
413
|
+
- **Payer Public key**: The public key of the account funding the creation.
|
414
|
+
- **New Account Public Key**: The public key of the account to be created.
|
415
|
+
- **Lamports**: The amount of lamports to transfer to the new account.
|
416
|
+
- **Space**: The amount of space (in bytes) to allocate for the new account.
|
417
|
+
- **Recent Blockhash**: The latest blockhash for the transaction.
|
418
|
+
- **Program Id**: The program ID associated with the new account (default: System Program).
|
419
|
+
|
420
|
+
#### Example Usage:
|
421
|
+
|
422
|
+
require 'solana_ruby'
|
423
|
+
|
424
|
+
# Initialize the client (defaults to Mainnet(https://api.mainnet-beta.solana.com))
|
425
|
+
client = SolanaRuby::HttpClient.new('https://api.devnet.solana.com')
|
426
|
+
|
427
|
+
# Fetch the recent blockhash
|
428
|
+
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
429
|
+
|
430
|
+
# Generate or fetch the sender/payer keypair
|
431
|
+
# Option 1: Generate a new keypair
|
432
|
+
sender_keypair = SolanaRuby::Keypair.generate
|
433
|
+
# Option 2: Use an existing private key
|
434
|
+
# sender_keypair = SolanaRuby::Keypair.from_private_key("InsertPrivateKeyHere")
|
435
|
+
sender_pubkey = sender_keypair[:public_key]
|
436
|
+
|
437
|
+
# Generate new account keypair
|
438
|
+
new_account = SolanaRuby::Keypair.generate
|
439
|
+
new_account_pubkey = new_account[:public_key]
|
440
|
+
|
441
|
+
# Parameters for account creation
|
442
|
+
lamports = 1_000_000_000
|
443
|
+
space = 165
|
444
|
+
program_id = SolanaRuby::TransactionHelper::SYSTEM_PROGRAM_ID
|
445
|
+
|
446
|
+
# Create the account creation transaction
|
447
|
+
transaction = SolanaRuby::TransactionHelper.create_account(
|
448
|
+
sender_pubkey,
|
449
|
+
new_account_pubkey,
|
450
|
+
lamports,
|
451
|
+
space,
|
452
|
+
recent_blockhash,
|
453
|
+
program_id
|
454
|
+
)
|
455
|
+
|
456
|
+
# Sign with both keypairs
|
457
|
+
transaction.sign([sender_keypair, new_account])
|
458
|
+
|
459
|
+
# Send the transaction
|
460
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
461
|
+
|
462
|
+
# Output transaction results
|
463
|
+
puts "Transaction Signature: #{response}"
|
464
|
+
puts "New account created with Public Key: #{new_account_pubkey}"
|
465
|
+
|
@@ -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
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module SolanaRuby
|
2
|
+
class Ed25519CurveChecker
|
3
|
+
# Constants for Ed25519
|
4
|
+
P = 2**255 - 19
|
5
|
+
ONE = 1
|
6
|
+
|
7
|
+
# Main function to check if a public key is on the Ed25519 curve
|
8
|
+
def self.on_curve?(public_key_bytes)
|
9
|
+
# Validate public key length
|
10
|
+
return false unless public_key_bytes.bytesize == 32 # Public key must be 32 bytes
|
11
|
+
|
12
|
+
begin
|
13
|
+
# Decode the y-coordinate from the public key
|
14
|
+
y = decode_y(public_key_bytes)
|
15
|
+
|
16
|
+
# Validate if y is a quadratic residue on the curve equation
|
17
|
+
y_squared = (y * y) % P
|
18
|
+
numerator = (y_squared - 1) % P
|
19
|
+
denominator = (D * y_squared + 1) % P
|
20
|
+
|
21
|
+
# Ensure denominator isn't zero to avoid invalid computation
|
22
|
+
return false if denominator.zero?
|
23
|
+
|
24
|
+
# Calculate x_squared = numerator * modular_inverse(denominator, P) mod P
|
25
|
+
x_squared = (numerator * modular_inverse(denominator, P)) % P
|
26
|
+
|
27
|
+
# Check if x_squared is a valid quadratic residue
|
28
|
+
quadratic_residue?(x_squared)
|
29
|
+
rescue StandardError => e
|
30
|
+
puts "Error during curve check: #{e.message}"
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Decode the y-coordinate from the public key
|
38
|
+
def self.decode_y(public_key_bytes)
|
39
|
+
# Converts byte array directly to integer and maps it onto the curve's modulus
|
40
|
+
public_key_bytes.unpack1('H*').to_i(16) % P
|
41
|
+
end
|
42
|
+
|
43
|
+
# Determine if value is a quadratic residue modulo P
|
44
|
+
def self.quadratic_residue?(value)
|
45
|
+
# Quadratic residues satisfy value^((p - 1) / 2) mod P == 1
|
46
|
+
value.pow((P - 1) / 2, P) == 1
|
47
|
+
end
|
48
|
+
|
49
|
+
# Modular inverse using the Extended Euclidean Algorithm
|
50
|
+
def self.modular_inverse(value, mod_value)
|
51
|
+
t, new_t = 0, 1
|
52
|
+
r, new_r = mod_value, value
|
53
|
+
|
54
|
+
while new_r != 0
|
55
|
+
quotient = r / new_r
|
56
|
+
t, new_t = new_t, t - quotient * new_t
|
57
|
+
r, new_r = new_r, r - quotient * new_r
|
58
|
+
end
|
59
|
+
|
60
|
+
raise ArgumentError, 'Value has no modular inverse' if r > 1
|
61
|
+
|
62
|
+
t += mod_value if t.negative?
|
63
|
+
t % mod_value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Calculate the Ed25519 constant D
|
68
|
+
# D = -121665 * modular_inverse(121666, P) mod P
|
69
|
+
D = (-121665 * Ed25519CurveChecker.modular_inverse(121666, Ed25519CurveChecker::P)) % Ed25519CurveChecker::P
|
70
|
+
end
|
data/lib/solana_ruby/keypair.rb
CHANGED
@@ -6,16 +6,9 @@ module SolanaRuby
|
|
6
6
|
# Generates a new Ed25519 keypair
|
7
7
|
def self.generate
|
8
8
|
signing_key = RbNaCl::Signatures::Ed25519::SigningKey.generate
|
9
|
-
public_key_bytes = signing_key.verify_key.to_bytes # Binary format for public key
|
10
9
|
private_key_bytes = signing_key.to_bytes
|
11
|
-
private_key_hex = private_key_bytes.unpack1('H*') # Hex format for private key
|
12
10
|
|
13
|
-
|
14
|
-
{
|
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)
|
18
|
-
}
|
11
|
+
keys(signing_key, private_key_bytes)
|
19
12
|
end
|
20
13
|
|
21
14
|
# Restores a keypair from a private key in hex format
|
@@ -28,8 +21,39 @@ module SolanaRuby
|
|
28
21
|
# Initialize signing key
|
29
22
|
signing_key = RbNaCl::Signatures::Ed25519::SigningKey.new(private_key_bytes)
|
30
23
|
|
24
|
+
keys(signing_key, private_key_bytes)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Load a keypair from a JSON file
|
28
|
+
def self.load_keypair(file_path)
|
29
|
+
# Parse the JSON file
|
30
|
+
keypair_data = JSON.parse(File.read(file_path))
|
31
|
+
|
32
|
+
# Ensure it contains exactly 64 bytes for Ed25519 (32 private + 32 public)
|
33
|
+
raise "Invalid keypair length: expected 64 bytes, got #{keypair_data.length}" unless keypair_data.length == 64
|
34
|
+
|
35
|
+
# Convert the array to a binary string
|
36
|
+
private_key_bytes = keypair_data[0, 32].pack('C*')
|
37
|
+
public_key_bytes = keypair_data[32, 32].pack('C*')
|
38
|
+
|
39
|
+
# Create the signing key
|
40
|
+
signing_key = RbNaCl::Signatures::Ed25519::SigningKey.new(private_key_bytes)
|
41
|
+
|
42
|
+
# Verify the public key matches
|
43
|
+
raise "Public key mismatch" unless signing_key.verify_key.to_bytes == public_key_bytes
|
44
|
+
|
45
|
+
keys(signing_key, private_key_bytes)
|
46
|
+
rescue JSON::ParserError => e
|
47
|
+
raise "Failed to parse JSON file: #{e.message}"
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def self.keys(signing_key, private_key_bytes)
|
31
54
|
# Extract public key in binary format
|
32
55
|
public_key_bytes = signing_key.verify_key.to_bytes
|
56
|
+
private_key_hex = private_key_bytes.unpack1('H*') # Hex format for private key
|
33
57
|
|
34
58
|
# Return public key in Base58 format and private key in hex format
|
35
59
|
{
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'base58'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module SolanaRuby
|
5
|
+
class PublicKey
|
6
|
+
PUBLIC_KEY_LENGTH = 32
|
7
|
+
|
8
|
+
attr_reader :bn
|
9
|
+
|
10
|
+
def initialize(value)
|
11
|
+
case value
|
12
|
+
when PublicKey
|
13
|
+
@bn = value.bn
|
14
|
+
when String
|
15
|
+
decoded = decode_base58(value)
|
16
|
+
validate_length(decoded)
|
17
|
+
@bn = to_bn(decoded)
|
18
|
+
when Array
|
19
|
+
binary = value.pack('C*')
|
20
|
+
validate_length(binary)
|
21
|
+
@bn = to_bn(binary)
|
22
|
+
else
|
23
|
+
raise ArgumentError, "Unsupported input type: #{value.class}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Converts the public key to Base58
|
28
|
+
def to_base58
|
29
|
+
Base58.binary_to_base58(to_bytes, :bitcoin)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Converts the public key to a binary string
|
33
|
+
def to_bytes
|
34
|
+
padded_bn = @bn.to_s(2) # Binary string from BigNum
|
35
|
+
if padded_bn.bytesize < PUBLIC_KEY_LENGTH
|
36
|
+
"\x00" * (PUBLIC_KEY_LENGTH - padded_bn.bytesize) + padded_bn
|
37
|
+
elsif padded_bn.bytesize > PUBLIC_KEY_LENGTH
|
38
|
+
raise "PublicKey byte length exceeds #{PUBLIC_KEY_LENGTH} bytes"
|
39
|
+
else
|
40
|
+
padded_bn
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def decode_base58(value)
|
47
|
+
Base58.base58_to_binary(value, :bitcoin)
|
48
|
+
rescue ArgumentError => e
|
49
|
+
raise ArgumentError, "Invalid Base58 encoding: #{e.message}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_length(data)
|
53
|
+
unless data.bytesize == PUBLIC_KEY_LENGTH
|
54
|
+
raise ArgumentError, "Invalid public key length: expected #{PUBLIC_KEY_LENGTH} bytes, got #{data.bytesize}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_bn(input)
|
59
|
+
OpenSSL::BN.new(input, 2)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
Dir[File.join(__dir__, 'data_types', '*.rb')].each { |file| require file }
|
2
|
+
Dir[File.join(__dir__, 'transaction_helpers', '*.rb')].each { |file| require file }
|
3
|
+
|
1
4
|
module SolanaRuby
|
2
5
|
class Transaction
|
3
6
|
require 'rbnacl'
|
@@ -70,10 +73,10 @@ module SolanaRuby
|
|
70
73
|
instructions.push(item)
|
71
74
|
end
|
72
75
|
|
73
|
-
def sign(
|
74
|
-
raise 'No signers' unless
|
76
|
+
def sign(keypairs)
|
77
|
+
raise 'No signers' unless keypairs.any?
|
75
78
|
|
76
|
-
keys =
|
79
|
+
keys = keypairs.uniq { |kp| kp[:public_key] }
|
77
80
|
@signatures = keys.map do |key|
|
78
81
|
{
|
79
82
|
signature: nil,
|
@@ -114,15 +117,17 @@ module SolanaRuby
|
|
114
117
|
def compile_message
|
115
118
|
check_for_errors
|
116
119
|
fetch_message_data
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
account_keys: @account_keys,
|
120
|
+
|
121
|
+
# add instruction structure
|
122
|
+
instructs = add_instructs
|
123
|
+
|
124
|
+
message_data = Message.new(
|
125
|
+
header: @header,
|
126
|
+
account_keys: @account_keys,
|
127
|
+
recent_blockhash: recent_blockhash,
|
128
|
+
instructions: instructs
|
124
129
|
)
|
125
|
-
|
130
|
+
message_data
|
126
131
|
end
|
127
132
|
|
128
133
|
def check_for_errors
|
@@ -168,11 +173,8 @@ module SolanaRuby
|
|
168
173
|
# Split out signing from non-signing keys and count header values
|
169
174
|
signed_keys = []
|
170
175
|
unsigned_keys = []
|
171
|
-
|
176
|
+
@header = split_keys(unique_metas, signed_keys, unsigned_keys)
|
172
177
|
@account_keys = signed_keys + unsigned_keys
|
173
|
-
|
174
|
-
# add instruction structure
|
175
|
-
@instructs = add_instructs
|
176
178
|
end
|
177
179
|
|
178
180
|
def append_program_id(program_ids, account_metas)
|
@@ -245,19 +247,24 @@ module SolanaRuby
|
|
245
247
|
end
|
246
248
|
|
247
249
|
def split_keys(unique_metas, signed_keys, unsigned_keys)
|
248
|
-
|
249
|
-
|
250
|
-
|
250
|
+
num_required_signatures = 0
|
251
|
+
num_readonly_signed_accounts = 0
|
252
|
+
num_readonly_unsigned_accounts = 0
|
251
253
|
unique_metas.each do |meta|
|
252
254
|
if meta[:is_signer]
|
253
255
|
signed_keys.push(meta[:pubkey])
|
254
|
-
|
255
|
-
|
256
|
+
num_required_signatures += 1
|
257
|
+
num_readonly_signed_accounts += 1 if (!meta[:is_writable])
|
256
258
|
else
|
257
259
|
unsigned_keys.push(meta[:pubkey])
|
258
|
-
|
260
|
+
num_readonly_unsigned_accounts += 1 if (!meta[:is_writable])
|
259
261
|
end
|
260
262
|
end
|
263
|
+
{
|
264
|
+
num_required_signatures: num_required_signatures,
|
265
|
+
num_readonly_signed_accounts: num_readonly_signed_accounts,
|
266
|
+
num_readonly_unsigned_accounts: num_readonly_unsigned_accounts,
|
267
|
+
}
|
261
268
|
end
|
262
269
|
|
263
270
|
def partial_sign(message, keys)
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module SolanaRuby
|
2
2
|
class TransactionHelper
|
3
3
|
require 'base58'
|
4
|
-
require 'pry'
|
5
4
|
|
6
5
|
# Constants for program IDs
|
7
6
|
SYSTEM_PROGRAM_ID = '11111111111111111111111111111111'
|
8
7
|
TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
|
9
|
-
ASSOCIATED_TOKEN_PROGRAM_ID = '
|
8
|
+
ASSOCIATED_TOKEN_PROGRAM_ID = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'
|
9
|
+
SYSVAR_RENT_ID = 'SysvarRent111111111111111111111111111111111'
|
10
|
+
|
10
11
|
|
11
12
|
INSTRUCTION_LAYOUTS = {
|
12
13
|
# Native SOL transfer
|
@@ -19,108 +20,202 @@ module SolanaRuby
|
|
19
20
|
instruction: :uint8,
|
20
21
|
amount: :uint64
|
21
22
|
},
|
22
|
-
|
23
|
+
# Create account layout
|
23
24
|
create_account: {
|
24
|
-
instruction: :
|
25
|
+
instruction: :uint32,
|
25
26
|
lamports: :uint64,
|
26
|
-
space: :uint64
|
27
|
+
space: :uint64,
|
28
|
+
program_id: :blob32
|
29
|
+
},
|
30
|
+
# SPL token transfer_checked
|
31
|
+
spl_transfer_checked: {
|
32
|
+
instruction: :uint8,
|
33
|
+
amount: :uint64,
|
34
|
+
decimals: :uint8
|
35
|
+
},
|
36
|
+
# mint spl tokens
|
37
|
+
spl_mint_to: {
|
38
|
+
instruction: :uint8,
|
39
|
+
amount: :uint64
|
40
|
+
},
|
41
|
+
# burn spl tokens
|
42
|
+
spl_burn: {
|
43
|
+
instruction: :uint8,
|
44
|
+
amount: :uint64
|
27
45
|
}
|
28
46
|
}
|
29
47
|
|
30
48
|
# Method to create a system account (e.g., for SPL token or SOL)
|
31
|
-
def self.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
49
|
+
def self.account_instruction(from_pubkey, new_account_pubkey, lamports, space, program_id)
|
50
|
+
# Encode the instruction data
|
51
|
+
instruction_data = encode_data(
|
52
|
+
INSTRUCTION_LAYOUTS[:create_account],
|
53
|
+
{
|
54
|
+
instruction: 0, # '0' corresponds to the Create Account instruction
|
55
|
+
lamports: lamports, # The amount of lamports to transfer to the new account
|
56
|
+
space: space, # Amount of space allocated for the account's data
|
57
|
+
program_id: Base58.base58_to_binary(program_id, :bitcoin).bytes # Convert public key to binary
|
58
|
+
}
|
41
59
|
)
|
42
|
-
|
60
|
+
|
61
|
+
keys = [
|
62
|
+
{ pubkey: from_pubkey, is_signer: true, is_writable: true }, # Funder's account
|
63
|
+
{ pubkey: new_account_pubkey, is_signer: true, is_writable: true } # New account
|
64
|
+
]
|
65
|
+
|
66
|
+
# return instruction data
|
67
|
+
create_instruction(keys, instruction_data, program_id)
|
43
68
|
end
|
44
69
|
|
45
|
-
|
70
|
+
|
71
|
+
def self.create_account(from_pubkey, new_account_pubkey, lamports, space, recent_blockhash, program_id = SYSTEM_PROGRAM_ID)
|
46
72
|
# Create the transaction
|
47
73
|
transaction = Transaction.new
|
48
74
|
transaction.set_fee_payer(from_pubkey)
|
49
75
|
transaction.set_recent_blockhash(recent_blockhash)
|
50
76
|
|
51
77
|
# Add the create account instruction to the transaction
|
52
|
-
|
53
|
-
transaction.add_instruction(
|
54
|
-
|
55
|
-
#
|
56
|
-
# Example: signing and sending the transaction
|
78
|
+
instruction = account_instruction(from_pubkey, new_account_pubkey, lamports, space, program_id)
|
79
|
+
transaction.add_instruction(instruction)
|
80
|
+
|
81
|
+
# return the transaction for signing
|
57
82
|
transaction
|
58
83
|
end
|
59
84
|
|
60
85
|
# Method to create a SOL transfer instruction
|
61
|
-
def self.
|
86
|
+
def self.transfer_sol_instruction(from_pubkey, to_pubkey, lamports)
|
62
87
|
fields = INSTRUCTION_LAYOUTS[:sol_transfer]
|
63
88
|
data = encode_data(fields, { instruction: 2, lamports: lamports })
|
64
|
-
|
65
|
-
keys: [
|
89
|
+
keys = [
|
66
90
|
{ pubkey: from_pubkey, is_signer: true, is_writable: true },
|
67
91
|
{ pubkey: to_pubkey, is_signer: false, is_writable: true }
|
68
|
-
]
|
69
|
-
|
70
|
-
|
71
|
-
)
|
92
|
+
]
|
93
|
+
|
94
|
+
create_instruction(keys, data, SYSTEM_PROGRAM_ID)
|
72
95
|
end
|
73
96
|
|
74
97
|
# Helper to create a new transaction for SOL transfer
|
75
|
-
def self.
|
98
|
+
def self.sol_transfer(from_pubkey, to_pubkey, lamports, recent_blockhash)
|
76
99
|
transaction = Transaction.new
|
77
100
|
transaction.set_fee_payer(from_pubkey)
|
78
101
|
transaction.set_recent_blockhash(recent_blockhash)
|
79
|
-
transfer_instruction =
|
102
|
+
transfer_instruction = transfer_sol_instruction(from_pubkey, to_pubkey, lamports)
|
80
103
|
transaction.add_instruction(transfer_instruction)
|
81
104
|
transaction
|
82
105
|
end
|
83
106
|
|
84
107
|
# Method to create an SPL token transfer instruction
|
85
|
-
def self.transfer_spl_token(source, destination, owner, amount)
|
86
|
-
fields = INSTRUCTION_LAYOUTS[:
|
87
|
-
data = encode_data(fields, { instruction:
|
88
|
-
|
89
|
-
|
90
|
-
{ pubkey:
|
91
|
-
{ pubkey: destination, is_signer: false, is_writable: true },
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
data: data
|
96
|
-
)
|
108
|
+
def self.transfer_spl_token(source, token, destination, owner, amount, decimals, multi_signers)
|
109
|
+
fields = INSTRUCTION_LAYOUTS[:spl_transfer_checked]
|
110
|
+
data = encode_data(fields, { instruction: 12, amount: amount, decimals: decimals }) # Instruction type 3: Transfer tokens
|
111
|
+
keys = SolanaRuby::TransactionHelpers::TokenAccount.add_signers(
|
112
|
+
[{ pubkey: source, is_signer: false, is_writable: true },
|
113
|
+
{ pubkey: token, is_signer: false, is_writable: false },
|
114
|
+
{ pubkey: destination, is_signer: false, is_writable: true }],
|
115
|
+
owner, multi_signers)
|
116
|
+
|
117
|
+
create_instruction(keys, data)
|
97
118
|
end
|
98
119
|
|
99
120
|
# Helper to create a new transaction for SPL token transfer
|
100
|
-
def self.new_spl_token_transaction(source, destination, owner, amount, recent_blockhash)
|
121
|
+
def self.new_spl_token_transaction(source, mint, destination, owner, amount, decimals, recent_blockhash, multi_signers=[])
|
101
122
|
transaction = Transaction.new
|
102
123
|
transaction.set_fee_payer(owner)
|
103
124
|
transaction.set_recent_blockhash(recent_blockhash)
|
104
|
-
transfer_instruction = transfer_spl_token(source, destination, owner, amount)
|
125
|
+
transfer_instruction = transfer_spl_token(source, mint, destination, owner, amount, decimals, multi_signers)
|
105
126
|
transaction.add_instruction(transfer_instruction)
|
106
127
|
transaction
|
107
128
|
end
|
108
129
|
|
109
|
-
# Method to create an associated token account
|
110
|
-
def self.create_associated_token_account(
|
111
|
-
|
130
|
+
# Method to create an associated token account
|
131
|
+
def self.create_associated_token_account(payer, mint, owner, recent_blockhash, program_id = SYSTEM_PROGRAM_ID)
|
132
|
+
transaction = Transaction.new
|
133
|
+
transaction.set_fee_payer(payer) # Payer funds the transaction
|
134
|
+
transaction.set_recent_blockhash(recent_blockhash)
|
135
|
+
|
136
|
+
# Derive the associated token account address
|
137
|
+
associated_token_account_pubkey = SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(mint, owner)
|
138
|
+
puts "associated_token_account_pubkey: #{associated_token_account_pubkey}"
|
139
|
+
|
140
|
+
|
141
|
+
# Create the associated token account instruction
|
112
142
|
create_account_instruction = TransactionInstruction.new(
|
113
143
|
keys: [
|
114
|
-
{ pubkey:
|
115
|
-
{ pubkey:
|
116
|
-
{ pubkey:
|
117
|
-
{ pubkey:
|
118
|
-
{ pubkey: SYSTEM_PROGRAM_ID, is_signer: false, is_writable: false }
|
144
|
+
{ pubkey: payer, is_signer: true, is_writable: true }, # Payer account
|
145
|
+
{ pubkey: associated_token_account_pubkey, is_signer: false, is_writable: true }, # New ATA
|
146
|
+
{ pubkey: owner, is_signer: false, is_writable: false }, # Owner of the ATA
|
147
|
+
{ pubkey: mint, is_signer: false, is_writable: false }, # Token mint
|
148
|
+
{ pubkey: SYSTEM_PROGRAM_ID, is_signer: false, is_writable: false }, # System program
|
149
|
+
{ pubkey: TOKEN_PROGRAM_ID, is_signer: false, is_writable: false }, # Token program
|
150
|
+
{ pubkey: SYSVAR_RENT_ID, is_signer: false, is_writable: false }
|
119
151
|
],
|
120
152
|
program_id: ASSOCIATED_TOKEN_PROGRAM_ID,
|
121
|
-
data: data
|
153
|
+
data: [] # No data required for creating an associated token account
|
122
154
|
)
|
123
|
-
|
155
|
+
|
156
|
+
# Add the instruction to the transaction
|
157
|
+
transaction.add_instruction(create_account_instruction)
|
158
|
+
transaction
|
159
|
+
end
|
160
|
+
|
161
|
+
# Method to create a mint instruction for SPL tokens
|
162
|
+
def self.mint_spl_token(mint, destination, mint_authority, amount, multi_signers = [])
|
163
|
+
fields = INSTRUCTION_LAYOUTS[:spl_mint_to]
|
164
|
+
data = encode_data(fields, { instruction: 7, amount: amount }) # Instruction type 7: Mint to
|
165
|
+
keys = SolanaRuby::TransactionHelpers::TokenAccount.add_signers(
|
166
|
+
[{ pubkey: mint, is_signer: false, is_writable: true },
|
167
|
+
{ pubkey: destination, is_signer: false, is_writable: true }],
|
168
|
+
mint_authority, multi_signers)
|
169
|
+
|
170
|
+
create_instruction(keys, data)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Helper to create a transaction for minting SPL tokens
|
174
|
+
def self.mint_spl_tokens(mint, destination, mint_authority, amount, recent_blockhash, multi_signers = [])
|
175
|
+
transaction = Transaction.new
|
176
|
+
transaction.set_fee_payer(mint_authority)
|
177
|
+
transaction.set_recent_blockhash(recent_blockhash)
|
178
|
+
mint_instruction = mint_spl_token(mint, destination, mint_authority, amount, multi_signers)
|
179
|
+
transaction.add_instruction(mint_instruction)
|
180
|
+
transaction
|
181
|
+
end
|
182
|
+
|
183
|
+
# Method to create a burn instruction for SPL tokens
|
184
|
+
def self.burn_spl_token(token_account, mint, mint_authority, amount, multi_signers = [])
|
185
|
+
# Define the fields for the burn instruction
|
186
|
+
fields = INSTRUCTION_LAYOUTS[:spl_burn]
|
187
|
+
|
188
|
+
# Encode the instruction data
|
189
|
+
data = encode_data(fields, { instruction: 8, amount: amount }) # Instruction type 8: Burn
|
190
|
+
|
191
|
+
keys = SolanaRuby::TransactionHelpers::TokenAccount.add_signers(
|
192
|
+
[
|
193
|
+
{ pubkey: token_account, is_signer: false, is_writable: true }, # Token account holding tokens to burn
|
194
|
+
{ pubkey: mint, is_signer: false, is_writable: true } # Mint address
|
195
|
+
], mint_authority, multi_signers)
|
196
|
+
|
197
|
+
# Return the transaction instruction
|
198
|
+
create_instruction(keys, data)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Helper to create a transaction for burning SPL tokens
|
202
|
+
def self.burn_spl_tokens(token_account, mint, owner, amount, recent_blockhash, multi_signers = [])
|
203
|
+
# Create a new transaction
|
204
|
+
transaction = Transaction.new
|
205
|
+
transaction.set_fee_payer(owner)
|
206
|
+
transaction.set_recent_blockhash(recent_blockhash)
|
207
|
+
|
208
|
+
# Add the burn instruction to the transaction
|
209
|
+
burn_instruction = burn_spl_token(token_account, mint, owner, amount, multi_signers)
|
210
|
+
transaction.add_instruction(burn_instruction)
|
211
|
+
|
212
|
+
# Return the transaction for signing
|
213
|
+
transaction
|
214
|
+
end
|
215
|
+
|
216
|
+
# Derive the associated token account address
|
217
|
+
def self.get_associated_token_address(mint, owner, program_id)
|
218
|
+
SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(mint, owner, program_id)
|
124
219
|
end
|
125
220
|
|
126
221
|
# Utility to encode data using predefined layouts
|
@@ -134,5 +229,13 @@ module SolanaRuby
|
|
134
229
|
layout = SolanaRuby::DataTypes::Layout.new(fields)
|
135
230
|
layout.deserialize(data)
|
136
231
|
end
|
232
|
+
|
233
|
+
def self.create_instruction(keys, data, toke_program_id = TOKEN_PROGRAM_ID)
|
234
|
+
TransactionInstruction.new(
|
235
|
+
keys: keys,
|
236
|
+
program_id: toke_program_id,
|
237
|
+
data: data
|
238
|
+
)
|
239
|
+
end
|
137
240
|
end
|
138
241
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module SolanaRuby
|
2
|
+
module TransactionHelpers
|
3
|
+
class TokenAccount
|
4
|
+
# Associated Token Program ID
|
5
|
+
ASSOCIATED_TOKEN_PROGRAM_ID = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'.freeze
|
6
|
+
|
7
|
+
# Token Program ID
|
8
|
+
TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'.freeze
|
9
|
+
|
10
|
+
def self.get_associated_token_address(mint, payer)
|
11
|
+
mint_bytes = Base58.base58_to_binary(mint, :bitcoin)
|
12
|
+
payer_bytes = Base58.base58_to_binary(payer, :bitcoin)
|
13
|
+
associated_program_bytes = Base58.base58_to_binary(ASSOCIATED_TOKEN_PROGRAM_ID, :bitcoin)
|
14
|
+
|
15
|
+
# Derive associated token account PDA
|
16
|
+
seeds = [
|
17
|
+
payer_bytes,
|
18
|
+
Base58.base58_to_binary(TOKEN_PROGRAM_ID, :bitcoin),
|
19
|
+
mint_bytes
|
20
|
+
]
|
21
|
+
|
22
|
+
# Attempt to find the first valid off-curve PDA
|
23
|
+
associated_token_account_pubkey = find_program_address(seeds, associated_program_bytes)
|
24
|
+
|
25
|
+
# Return the computed Base58 PDA string
|
26
|
+
Base58.binary_to_base58(associated_token_account_pubkey, :bitcoin)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.add_signers(keys, owner_or_authority, multi_signers)
|
30
|
+
if multi_signers.any?
|
31
|
+
keys.push({ pubkey: owner_or_authority, is_signer: false, is_writable: false })
|
32
|
+
multi_signers.each do |signer|
|
33
|
+
pubkey = signer.is_a?(String) ? signer : signer.public_key
|
34
|
+
keys.push({ pubkey: pubkey, is_signer: true, is_writable: false })
|
35
|
+
end
|
36
|
+
else
|
37
|
+
keys.push({ pubkey: owner_or_authority, is_signer: true, is_writable: false })
|
38
|
+
end
|
39
|
+
keys
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def self.find_program_address(seeds, program_id)
|
45
|
+
nonce = 255
|
46
|
+
loop do
|
47
|
+
# Combine the current nonce with the seeds
|
48
|
+
seeds_with_nonce = seeds + [[nonce].pack('C*')]
|
49
|
+
hashed_buffer = hash_seeds(seeds_with_nonce, program_id)
|
50
|
+
|
51
|
+
# Debugging: Log every generated address for inspection
|
52
|
+
puts "Testing nonce #{nonce}: #{Base58.binary_to_base58(hashed_buffer, :bitcoin)}"
|
53
|
+
|
54
|
+
# Check if it's valid and off-curve
|
55
|
+
if !SolanaRuby::Ed25519CurveChecker.on_curve?(hashed_buffer)
|
56
|
+
puts "Found valid PDA with nonce #{nonce}: #{Base58.binary_to_base58(hashed_buffer, :bitcoin)}"
|
57
|
+
return hashed_buffer
|
58
|
+
end
|
59
|
+
|
60
|
+
# Decrement nonce safely
|
61
|
+
nonce -= 1
|
62
|
+
raise "Unable to find a valid PDA address off the curve" if nonce < 0
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.hash_seeds(seeds, program_id)
|
67
|
+
# Combine seeds and program ID with the PDA derivation logic
|
68
|
+
buffer = seeds.flatten.join + program_id + "ProgramDerivedAddress"
|
69
|
+
RbNaCl::Hash.sha256(buffer)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/solana_ruby/version.rb
CHANGED
data/lib/solana_ruby.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
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
|
+
# SOL Transfer Testing Script
|
7
|
+
|
8
|
+
# Initialize the Solana client
|
9
|
+
client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
|
10
|
+
|
11
|
+
# Fetch the recent blockhash
|
12
|
+
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
13
|
+
token_account = "C2wY5TKnj52S4s9yRUTNqitRe5gmFokSCoppJS6t63aa"
|
14
|
+
mint_address = "5FQhi6Kq3CKDaB3bus21ZqcL7wyeZNR18otFGoDfrZXU"
|
15
|
+
mint_authority = SolanaRuby::Keypair.load_keypair('/Users/chinaputtaiahbellamkonda/.config/solana/id.json')
|
16
|
+
owner = mint_authority[:public_key]
|
17
|
+
amount = 500_000 # Number of tokens to burn
|
18
|
+
|
19
|
+
transaction = SolanaRuby::TransactionHelper.burn_spl_tokens(
|
20
|
+
token_account,
|
21
|
+
mint_address,
|
22
|
+
owner,
|
23
|
+
amount,
|
24
|
+
recent_blockhash
|
25
|
+
)
|
26
|
+
|
27
|
+
# Sign and send the transaction
|
28
|
+
resp = transaction.sign([mint_authority])
|
29
|
+
|
30
|
+
puts "signature: #{resp}"
|
31
|
+
|
32
|
+
# Send the transaction
|
33
|
+
puts "Sending transaction..."
|
34
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
35
|
+
|
36
|
+
# Output transaction results
|
37
|
+
puts "Transaction Signature: #{response}"
|
@@ -3,30 +3,58 @@
|
|
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)
|
16
|
+
sender_keypair = SolanaRuby::Keypair.load_keypair('/Users/chinaputtaiahbellamkonda/.config/solana/id.json')
|
15
17
|
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"
|
18
|
+
puts "Sender Public Key: #{sender_pubkey}"
|
20
19
|
|
20
|
+
# Check sender's account balance
|
21
|
+
balance = client.get_balance(sender_pubkey)
|
22
|
+
puts "Sender account balance: #{balance} lamports"
|
23
|
+
if balance == 0
|
24
|
+
puts "Balance is zero, waiting for balance update..."
|
25
|
+
sleep(10)
|
26
|
+
end
|
21
27
|
|
22
|
-
#
|
28
|
+
# new keypair and public key (new account)
|
23
29
|
new_account = SolanaRuby::Keypair.generate
|
24
30
|
new_account_pubkey = new_account[:public_key]
|
31
|
+
puts "New Account Public Key: #{new_account_pubkey}"
|
32
|
+
puts "New Account Private Key: #{new_account[:private_key]}"
|
33
|
+
puts "New Account Full Private Key: #{new_account[:full_private_key]}"
|
34
|
+
|
35
|
+
# Parameters for account creation
|
36
|
+
lamports = 1 * 1_000_000_000 # Lamports to transfer
|
37
|
+
space = 165 # Space allocation (bytes)
|
38
|
+
program_id = SolanaRuby::TransactionHelper::SYSTEM_PROGRAM_ID
|
39
|
+
|
40
|
+
# Create and sign the transaction
|
41
|
+
transaction = SolanaRuby::TransactionHelper.create_account(
|
42
|
+
sender_pubkey,
|
43
|
+
new_account_pubkey,
|
44
|
+
lamports,
|
45
|
+
space,
|
46
|
+
recent_blockhash,
|
47
|
+
program_id
|
48
|
+
)
|
49
|
+
|
50
|
+
# Sign transaction with both sender and new account keypairs
|
51
|
+
transaction.sign([sender_keypair, new_account])
|
52
|
+
|
53
|
+
# Send the transaction
|
54
|
+
puts "Sending transaction..."
|
55
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
25
56
|
|
26
|
-
#
|
27
|
-
|
57
|
+
# Output transaction results
|
58
|
+
puts "Transaction Signature: #{response}"
|
59
|
+
puts "New account created successfully with Public Key: #{new_account_pubkey}"
|
28
60
|
|
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,52 @@
|
|
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
|
+
# SOL Transfer Testing Script
|
7
|
+
|
8
|
+
# Initialize the Solana client
|
9
|
+
client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
|
10
|
+
|
11
|
+
# Fetch the recent blockhash
|
12
|
+
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
13
|
+
|
14
|
+
payer = SolanaRuby::Keypair.load_keypair('/Users/chinaputtaiahbellamkonda/.config/solana/id.json')
|
15
|
+
payer_pubkey = payer[:public_key]
|
16
|
+
|
17
|
+
# Generate a sender keypair and public key
|
18
|
+
owner = SolanaRuby::Keypair.generate
|
19
|
+
# owner = SolanaRuby::Keypair.from_private_key("2ce523e98cfd207a216a9ac4ef8b41c38c53a302af2022d2e89e1256d1b6a1d0")
|
20
|
+
owner_pubkey = owner[:public_key]
|
21
|
+
puts "owner public key: #{owner_pubkey}"
|
22
|
+
puts "payer private key: #{owner[:private_key]}"
|
23
|
+
|
24
|
+
# Airdrop some lamports to the sender's account
|
25
|
+
# lamports = 10 * 1_000_000_000
|
26
|
+
# sleep(1)
|
27
|
+
# result = client.request_airdrop(payer_pubkey, lamports)
|
28
|
+
# puts "Solana Balance #{lamports} lamports added sucessfully for the public key: #{payer_pubkey}"
|
29
|
+
# sleep(10)
|
30
|
+
|
31
|
+
|
32
|
+
mint_pubkey = "5xxFuuvLiB6Gz3vbaqgkjf8fvEDXowftFiL14qUSgPiM"
|
33
|
+
program_id = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
34
|
+
puts "payer public key: #{payer_pubkey}"
|
35
|
+
|
36
|
+
# associated_token_address = SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(mint_pubkey, payer_pubkey, program_id)
|
37
|
+
|
38
|
+
# puts "Associated Token Address: #{associated_token_address}"
|
39
|
+
|
40
|
+
transaction = SolanaRuby::TransactionHelper.create_associated_token_account(payer_pubkey, mint_pubkey, owner_pubkey, recent_blockhash)
|
41
|
+
|
42
|
+
resp = transaction.sign([payer])
|
43
|
+
|
44
|
+
puts "signature: #{resp}"
|
45
|
+
|
46
|
+
# Send the transaction
|
47
|
+
puts "Sending transaction..."
|
48
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
49
|
+
|
50
|
+
# Output transaction results
|
51
|
+
puts "Transaction Signature: #{response}"
|
52
|
+
|
@@ -0,0 +1,40 @@
|
|
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
|
+
# SOL Transfer Testing Script
|
7
|
+
|
8
|
+
# Initialize the Solana client
|
9
|
+
client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
|
10
|
+
|
11
|
+
# Fetch the recent blockhash
|
12
|
+
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
13
|
+
|
14
|
+
# Example parameters
|
15
|
+
mint_account = "5FQhi6Kq3CKDaB3bus21ZqcL7wyeZNR18otFGoDfrZXU"
|
16
|
+
destination_account = "C2wY5TKnj52S4s9yRUTNqitRe5gmFokSCoppJS6t63aa"
|
17
|
+
mint_authority = SolanaRuby::Keypair.load_keypair('/Users/chinaputtaiahbellamkonda/.config/solana/id.json')
|
18
|
+
amount = 1_000_000 # Amount to mint in smallest units
|
19
|
+
multi_signers = [] # If multi-signature is used, include public keys here
|
20
|
+
|
21
|
+
# Create a mint transaction
|
22
|
+
transaction = SolanaRuby::TransactionHelper.mint_spl_tokens(
|
23
|
+
mint_account,
|
24
|
+
destination_account,
|
25
|
+
mint_authority[:public_key],
|
26
|
+
amount,
|
27
|
+
recent_blockhash,
|
28
|
+
multi_signers
|
29
|
+
)
|
30
|
+
|
31
|
+
resp = transaction.sign([mint_authority])
|
32
|
+
|
33
|
+
puts "signature: #{resp}"
|
34
|
+
|
35
|
+
# Send the transaction
|
36
|
+
puts "Sending transaction..."
|
37
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
38
|
+
|
39
|
+
# Output transaction results
|
40
|
+
puts "Transaction Signature: #{response}"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/*.rb')].each { |file| require file }
|
2
2
|
Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/**/*.rb')].each { |file| require file }
|
3
|
-
require 'pry'
|
3
|
+
# require 'pry'
|
4
4
|
|
5
5
|
# SOL Transfer Testing Script
|
6
6
|
|
@@ -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,
|
@@ -13,7 +13,7 @@ client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
|
|
13
13
|
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
14
14
|
|
15
15
|
# Generate a sender keypair and public key
|
16
|
-
fee_payer = SolanaRuby::Keypair.
|
16
|
+
fee_payer = SolanaRuby::Keypair.load_keypair('/Users/chinaputtaiahbellamkonda/.config/solana/id.json')
|
17
17
|
fee_payer_pubkey = fee_payer[:public_key]
|
18
18
|
lamports = 10 * 1_000_000_000
|
19
19
|
space = 165
|
@@ -26,20 +26,23 @@ puts "sender account balance: #{balance}, wait for few seconds to update the bal
|
|
26
26
|
# # Generate a receiver keypair and public key
|
27
27
|
keypair = SolanaRuby::Keypair.generate
|
28
28
|
receiver_pubkey = keypair[:public_key]
|
29
|
-
transfer_lamports =
|
29
|
+
transfer_lamports = 1_000_000
|
30
30
|
# puts "Payer's full private key: #{sender_keypair[:full_private_key]}"
|
31
31
|
# # puts "Receiver's full private key: #{keypair[:full_private_key]}"
|
32
32
|
# # puts "Receiver's Public Key: #{keypair[:public_key]}"
|
33
|
-
mint_address = '
|
33
|
+
mint_address = 'G4JEi3UprH2rNz6hjBfQakLSR5EHXtS5Ry8MRFTQS5wG'
|
34
34
|
token_program_id = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
|
35
35
|
|
36
36
|
# Create a new transaction
|
37
37
|
transaction = SolanaRuby::TransactionHelper.new_spl_token_transaction(
|
38
|
-
"
|
38
|
+
"86w17eQHEoBcHY2rTdd5q9LjJL6Rx9QE6J1C9xgGkjt",
|
39
|
+
mint_address,
|
39
40
|
receiver_pubkey,
|
40
41
|
fee_payer_pubkey,
|
41
42
|
transfer_lamports,
|
42
|
-
|
43
|
+
6,
|
44
|
+
recent_blockhash,
|
45
|
+
[]
|
43
46
|
)
|
44
47
|
# # Get the sender's private key (ensure it's a string)
|
45
48
|
private_key = fee_payer[:private_key]
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- BuildSquad
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: websocket-client-simple
|
@@ -235,6 +235,7 @@ files:
|
|
235
235
|
- lib/solana_ruby/data_types/near_int64.rb
|
236
236
|
- lib/solana_ruby/data_types/sequence.rb
|
237
237
|
- lib/solana_ruby/data_types/unsigned_int.rb
|
238
|
+
- lib/solana_ruby/ed25519_curve_checker.rb
|
238
239
|
- lib/solana_ruby/http_client.rb
|
239
240
|
- lib/solana_ruby/http_methods/account_methods.rb
|
240
241
|
- lib/solana_ruby/http_methods/basic_methods.rb
|
@@ -247,8 +248,10 @@ files:
|
|
247
248
|
- lib/solana_ruby/http_methods/transaction_methods.rb
|
248
249
|
- lib/solana_ruby/keypair.rb
|
249
250
|
- lib/solana_ruby/message.rb
|
251
|
+
- lib/solana_ruby/public_key.rb
|
250
252
|
- lib/solana_ruby/transaction.rb
|
251
253
|
- lib/solana_ruby/transaction_helper.rb
|
254
|
+
- lib/solana_ruby/transaction_helpers/token_account.rb
|
252
255
|
- lib/solana_ruby/transaction_instruction.rb
|
253
256
|
- lib/solana_ruby/utils.rb
|
254
257
|
- lib/solana_ruby/version.rb
|
@@ -259,7 +262,10 @@ files:
|
|
259
262
|
- lib/solana_ruby/web_socket_methods/root_methods.rb
|
260
263
|
- lib/solana_ruby/web_socket_methods/signature_methods.rb
|
261
264
|
- lib/solana_ruby/web_socket_methods/slot_methods.rb
|
265
|
+
- transaction_testing/burn_spl_tokens.rb
|
262
266
|
- transaction_testing/create_account.rb
|
267
|
+
- transaction_testing/create_spl_token_account.rb
|
268
|
+
- transaction_testing/mint_spl_tokens.rb
|
263
269
|
- transaction_testing/sol_transfer.rb
|
264
270
|
- transaction_testing/spl_token_transfer.rb
|
265
271
|
homepage: https://github.com/Build-Squad/solana-ruby
|
@@ -284,7 +290,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
284
290
|
- !ruby/object:Gem::Version
|
285
291
|
version: '0'
|
286
292
|
requirements: []
|
287
|
-
rubygems_version: 3.5.
|
293
|
+
rubygems_version: 3.5.20
|
288
294
|
signing_key:
|
289
295
|
specification_version: 4
|
290
296
|
summary: Solana Ruby SDK
|