solana-ruby-web3js 2.0.1 → 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 +59 -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 +1 -0
- data/lib/solana_ruby/transaction_helper.rb +133 -44
- 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 +5 -2
- 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 +1 -1
- 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
@@ -404,3 +404,62 @@ To transfer SOL (the native cryptocurrency of the Solana blockchain) from one ac
|
|
404
404
|
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
405
405
|
puts "Response: #{response}"
|
406
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
|
+
|
@@ -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,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,12 +20,28 @@ module SolanaRuby
|
|
19
20
|
instruction: :uint8,
|
20
21
|
amount: :uint64
|
21
22
|
},
|
22
|
-
|
23
|
+
# Create account layout
|
23
24
|
create_account: {
|
24
25
|
instruction: :uint32,
|
25
26
|
lamports: :uint64,
|
26
27
|
space: :uint64,
|
27
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
|
28
45
|
}
|
29
46
|
}
|
30
47
|
|
@@ -41,18 +58,13 @@ module SolanaRuby
|
|
41
58
|
}
|
42
59
|
)
|
43
60
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
)
|
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
|
+
]
|
53
65
|
|
54
66
|
# return instruction data
|
55
|
-
|
67
|
+
create_instruction(keys, instruction_data, program_id)
|
56
68
|
end
|
57
69
|
|
58
70
|
|
@@ -74,14 +86,12 @@ module SolanaRuby
|
|
74
86
|
def self.transfer_sol_instruction(from_pubkey, to_pubkey, lamports)
|
75
87
|
fields = INSTRUCTION_LAYOUTS[:sol_transfer]
|
76
88
|
data = encode_data(fields, { instruction: 2, lamports: lamports })
|
77
|
-
|
78
|
-
keys: [
|
89
|
+
keys = [
|
79
90
|
{ pubkey: from_pubkey, is_signer: true, is_writable: true },
|
80
91
|
{ pubkey: to_pubkey, is_signer: false, is_writable: true }
|
81
|
-
]
|
82
|
-
|
83
|
-
|
84
|
-
)
|
92
|
+
]
|
93
|
+
|
94
|
+
create_instruction(keys, data, SYSTEM_PROGRAM_ID)
|
85
95
|
end
|
86
96
|
|
87
97
|
# Helper to create a new transaction for SOL transfer
|
@@ -95,46 +105,117 @@ module SolanaRuby
|
|
95
105
|
end
|
96
106
|
|
97
107
|
# Method to create an SPL token transfer instruction
|
98
|
-
def self.transfer_spl_token(source, destination, owner, amount)
|
99
|
-
fields = INSTRUCTION_LAYOUTS[:
|
100
|
-
data = encode_data(fields, { instruction:
|
101
|
-
|
102
|
-
|
103
|
-
{ pubkey:
|
104
|
-
{ pubkey: destination, is_signer: false, is_writable: true },
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
data: data
|
109
|
-
)
|
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)
|
110
118
|
end
|
111
119
|
|
112
120
|
# Helper to create a new transaction for SPL token transfer
|
113
|
-
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=[])
|
114
122
|
transaction = Transaction.new
|
115
123
|
transaction.set_fee_payer(owner)
|
116
124
|
transaction.set_recent_blockhash(recent_blockhash)
|
117
|
-
transfer_instruction = transfer_spl_token(source, destination, owner, amount)
|
125
|
+
transfer_instruction = transfer_spl_token(source, mint, destination, owner, amount, decimals, multi_signers)
|
118
126
|
transaction.add_instruction(transfer_instruction)
|
119
127
|
transaction
|
120
128
|
end
|
121
129
|
|
122
|
-
# Method to create an associated token account
|
123
|
-
def self.create_associated_token_account(payer, mint, owner)
|
124
|
-
|
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
|
125
142
|
create_account_instruction = TransactionInstruction.new(
|
126
143
|
keys: [
|
127
|
-
{ pubkey: payer, is_signer: true, is_writable: true },
|
128
|
-
{ pubkey:
|
129
|
-
{ pubkey: owner, is_signer: false, is_writable: false },
|
130
|
-
{ pubkey: mint, is_signer: false, is_writable: false },
|
131
|
-
{ pubkey:
|
132
|
-
{ pubkey:
|
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 }
|
133
151
|
],
|
134
152
|
program_id: ASSOCIATED_TOKEN_PROGRAM_ID,
|
135
|
-
data: data
|
153
|
+
data: [] # No data required for creating an associated token account
|
136
154
|
)
|
137
|
-
|
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)
|
138
219
|
end
|
139
220
|
|
140
221
|
# Utility to encode data using predefined layouts
|
@@ -148,5 +229,13 @@ module SolanaRuby
|
|
148
229
|
layout = SolanaRuby::DataTypes::Layout.new(fields)
|
149
230
|
layout.deserialize(data)
|
150
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
|
151
240
|
end
|
152
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}"
|
@@ -11,8 +11,9 @@ recent_blockhash = client.get_latest_blockhash["blockhash"]
|
|
11
11
|
puts "Recent Blockhash: #{recent_blockhash}"
|
12
12
|
|
13
13
|
# Sender keypair and public key
|
14
|
-
private_key = "d22867a84ee1d91485a52c587793002dcaa7ce79a58bb605b3af2682099bb778"
|
15
|
-
sender_keypair = SolanaRuby::Keypair.from_private_key(private_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')
|
16
17
|
sender_pubkey = sender_keypair[:public_key]
|
17
18
|
puts "Sender Public Key: #{sender_pubkey}"
|
18
19
|
|
@@ -28,6 +29,8 @@ end
|
|
28
29
|
new_account = SolanaRuby::Keypair.generate
|
29
30
|
new_account_pubkey = new_account[:public_key]
|
30
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]}"
|
31
34
|
|
32
35
|
# Parameters for account creation
|
33
36
|
lamports = 1 * 1_000_000_000 # Lamports to transfer
|
@@ -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}"
|
@@ -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
|