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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83aeed228731c11fa62b87f4ab5761a6715754921effc58400764bbe43378409
4
- data.tar.gz: a0cd63b8a7998539304ce06ed69692e86361fece542033d63fab7383a692ebb3
3
+ metadata.gz: d2bd60b642f794a5923a3ff1d1b39e29e60aca6444445b0cf2e36e7393b18ff3
4
+ data.tar.gz: 95a61aab91ec4eaacb94fdc671027a2005b320b938a7a2132dc0daf9b643d45b
5
5
  SHA512:
6
- metadata.gz: 29277d937c3201916bd0bd1eec7172621a4a83b2b21d5045afa5a285f4d757ba12de5f99031da0f0030e08e68adc4c7de1cb6a55301463cfb05f0b5e63072ffc
7
- data.tar.gz: 0b627b741396f666e92fe8b032d859e944c933eda6b8ac7da29201d7913f116195b0751bcc2247537ca3a2b461bff9d2999ac234669e56a2708efe39776d881d
6
+ metadata.gz: 85acebeaeaebce2d39fe74f7d931a7e339e1a773d73f4774003a06a84bed4f94343b4f41269ac4bd1b2c7fbd186145ccba7d8d25b35b9aadee9a2b9f9a56c06e
7
+ data.tar.gz: 68b1d9bf25f64d1424e660387cc802e12842ea428547c4d4643b23489f2dab3db75c4d63a35d23159521b41634b11aef0befa9fa3193c2cf275f14f22412b70f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- solana-ruby-web3js (2.0.0beta1)
4
+ solana-ruby-web3js (2.0.1)
5
5
  base58 (~> 0.2.3)
6
6
  base64 (~> 0.2.0)
7
7
  ed25519
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
@@ -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
- # Convert public key binary to Base58 for readability and compatibility
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,4 +1,5 @@
1
1
  Dir[File.join(__dir__, 'data_types', '*.rb')].each { |file| require file }
2
+ Dir[File.join(__dir__, 'transaction_helpers', '*.rb')].each { |file| require file }
2
3
 
3
4
  module SolanaRuby
4
5
  class Transaction
@@ -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 = 'ATokenGP3evbxxpQ7bYPLNNaxD2c4bqtvWjpKbmz6HjH'
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
- # Create account layout
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
- # Construct the transaction instruction
45
- create_account_instruction = TransactionInstruction.new(
46
- keys: [
47
- { pubkey: from_pubkey, is_signer: true, is_writable: true }, # Funder's account
48
- { pubkey: new_account_pubkey, is_signer: true, is_writable: true } # New account
49
- ],
50
- program_id: program_id, # Use Solana's system program for account creation
51
- data: instruction_data # Encoded instruction data
52
- )
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
- create_account_instruction
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
- TransactionInstruction.new(
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
- program_id: SYSTEM_PROGRAM_ID,
83
- data: data
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[:spl_transfer]
100
- data = encode_data(fields, { instruction: 3, amount: amount }) # Instruction type 3: Transfer tokens
101
- TransactionInstruction.new(
102
- keys: [
103
- { pubkey: source, is_signer: false, is_writable: true },
104
- { pubkey: destination, is_signer: false, is_writable: true },
105
- { pubkey: owner, is_signer: true, is_writable: false }
106
- ],
107
- program_id: TOKEN_PROGRAM_ID,
108
- data: data
109
- )
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 for a given token mint
123
- def self.create_associated_token_account(payer, mint, owner)
124
- data = [0, 0, 0, 0] # No data required for account creation
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: associated_token, is_signer: false, is_writable: true },
129
- { pubkey: owner, is_signer: false, is_writable: false },
130
- { pubkey: mint, is_signer: false, is_writable: false },
131
- { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, is_signer: false, is_writable: false },
132
- { pubkey: SYSTEM_PROGRAM_ID, is_signer: false, is_writable: false }
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
- create_account_instruction
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolanaRuby
4
- VERSION = "2.0.1"
4
+ VERSION = "2.0.2"
5
5
  end
data/lib/solana_ruby.rb CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  Dir[File.join(__dir__, 'solana_ruby', '*.rb')].each { |file| require file }
4
4
  # Dir["solana_ruby/*.rb"].each { |f| require_relative f.delete(".rb") }
5
- require 'pry'
6
5
  module SolanaRuby
7
6
  class Error < StandardError; end
8
7
  end
@@ -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}"
@@ -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
 
@@ -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.from_private_key("d22867a84ee1d91485a52c587793002dcaa7ce79a58bb605b3af2682099bb778")
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 = 1 * 1_000_000
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 = '9BvJGQC5FkLJzUC2TmYpi1iU8n9vt2388GLT5zvu8S1G'
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
- "9BvJGQC5FkLJzUC2TmYpi1iU8n9vt2388GLT5zvu8S1G",
38
+ "86w17eQHEoBcHY2rTdd5q9LjJL6Rx9QE6J1C9xgGkjt",
39
+ mint_address,
39
40
  receiver_pubkey,
40
41
  fee_payer_pubkey,
41
42
  transfer_lamports,
42
- recent_blockhash
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.1
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: 2024-11-28 00:00:00.000000000 Z
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.23
293
+ rubygems_version: 3.5.20
288
294
  signing_key:
289
295
  specification_version: 4
290
296
  summary: Solana Ruby SDK