solana-ruby-web3js 2.0.1 → 2.1.0
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 +384 -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 +171 -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/close_acccount.rb +42 -0
- data/transaction_testing/create_account.rb +6 -3
- data/transaction_testing/create_spl_token_account.rb +53 -0
- data/transaction_testing/mint_spl_tokens.rb +43 -0
- data/transaction_testing/sol_transfer.rb +1 -1
- data/transaction_testing/spl_token_transfer.rb +15 -11
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 731b11629fcf8aeccddb1041dd5380b72ca120dd3ad23fc8cf1884a963864a96
|
4
|
+
data.tar.gz: d2d42348b2eb8d58433aec922bdcb3bb1631ab119d52999d4d20d63ebcf545ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ace60d58b990d3364ff3397c0719a81089a5c73af584094ad4d1a673d55be52a9a6d60dab0db910b8f41ef4e01ce55b551ee0404b3aa075a12f1e4440ec96cbc
|
7
|
+
data.tar.gz: 9a26564405175ab409799becfeb8ea00d275689990b3319d379d6ae9df5d1c025d4ef925d21068664ce46a71f62a5abb07935b5732aa9eb5196addea4a36b5fa
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -404,3 +404,387 @@ 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
|
+
|
466
|
+
### SPL Token Account Creation
|
467
|
+
|
468
|
+
The create_associated_token_account helper allows you to create an associated token account for a specific mint and owner. This is necessary when dealing with SPL tokens on the Solana blockchain.
|
469
|
+
|
470
|
+
#### Requirements:
|
471
|
+
|
472
|
+
- **Payer Public key**: The public key of the account funding the creation.
|
473
|
+
- **Mint Public Key**: The public key of the SPL token mint.
|
474
|
+
- **Owner Public Key**: The public key of the owner for whom the associated token account is being created.
|
475
|
+
- **Recent Blockhash**: The latest blockhash for the transaction.
|
476
|
+
|
477
|
+
#### Example Usage:
|
478
|
+
|
479
|
+
require 'solana_ruby'
|
480
|
+
|
481
|
+
# Initialize the Solana client (defaults to Mainnet: https://api.mainnet-beta.solana.com)
|
482
|
+
client = SolanaRuby::HttpClient.new('https://api.devnet.solana.com')
|
483
|
+
|
484
|
+
# Fetch the recent blockhash
|
485
|
+
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
486
|
+
|
487
|
+
# Load the keypair for the payer
|
488
|
+
payer_keypair = SolanaRuby::Keypair.load_keypair('InsertYourJsonFilePathHere')
|
489
|
+
payer_pubkey = payer_keypair.public_key
|
490
|
+
|
491
|
+
# Generate or load the owner keypair
|
492
|
+
owner_keypair = SolanaRuby::Keypair.generate
|
493
|
+
owner_pubkey = owner_keypair.public_key
|
494
|
+
|
495
|
+
puts "Owner Public Key: #{owner_pubkey}"
|
496
|
+
puts "Owner Private Key: #{owner_keypair.private_key}"
|
497
|
+
|
498
|
+
# Define the mint public key for the SPL token
|
499
|
+
mint_pubkey = "InsertMintPublicKeyHere"
|
500
|
+
|
501
|
+
# Create the associated token account transaction
|
502
|
+
transaction = SolanaRuby::TransactionHelper.create_associated_token_account(
|
503
|
+
payer_pubkey,
|
504
|
+
mint_pubkey,
|
505
|
+
owner_pubkey,
|
506
|
+
recent_blockhash
|
507
|
+
)
|
508
|
+
|
509
|
+
# Sign the transaction
|
510
|
+
transaction.sign([payer_keypair])
|
511
|
+
|
512
|
+
# Send the transaction
|
513
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
514
|
+
|
515
|
+
# Output transaction results
|
516
|
+
puts "Transaction Signature: #{response}"
|
517
|
+
|
518
|
+
### Close Account
|
519
|
+
|
520
|
+
The close_account helper allows you to close an associated token account on the Solana blockchain. Any remaining balance in the account is transferred to the specified destination account.
|
521
|
+
|
522
|
+
#### Requirements:
|
523
|
+
|
524
|
+
- **Account to Close**: The public key of the associated token account to be closed.
|
525
|
+
- **Destination Public Key**: The public key of the account receiving the remaining balance.
|
526
|
+
- **Owner Public Key**: The public key of the account owner who has permission to close the account.
|
527
|
+
- **Payer Public Key**: The public key of the account paying for the transaction fees.
|
528
|
+
- **Multi-Signers (Optional)**: An array of additional signer public keys if the account requires multiple signatures.
|
529
|
+
- **Recent Blockhash**: The latest blockhash for the transaction.
|
530
|
+
|
531
|
+
#### Example Usage:
|
532
|
+
|
533
|
+
require 'solana_ruby'
|
534
|
+
|
535
|
+
# Initialize the Solana client (defaults to Mainnet: https://api.mainnet-beta.solana.com)
|
536
|
+
client = SolanaRuby::HttpClient.new('https://api.devnet.solana.com')
|
537
|
+
|
538
|
+
# Fetch the recent blockhash
|
539
|
+
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
540
|
+
|
541
|
+
# Load the keypairs
|
542
|
+
payer_keypair = SolanaRuby::Keypair.from_private_key("InsertPayerPrivateKeyHere")
|
543
|
+
owner_keypair = SolanaRuby::Keypair.from_private_key("InsertOwnerPrivateKeyHere")
|
544
|
+
|
545
|
+
payer_pubkey = payer_keypair[:public_key]
|
546
|
+
owner_pubkey = owner_keypair[:public_key]
|
547
|
+
|
548
|
+
# Define the associated token account to be closed and the destination account
|
549
|
+
account_to_close_pubkey = 'InsertAccountToClosePublicKeyHere' # Replace with the actual account to close
|
550
|
+
destination_pubkey = 'InsertDestinationPublicKeyHere' # Replace with the actual recipient address
|
551
|
+
|
552
|
+
# Multi-signers (if required)
|
553
|
+
# multi_signers = [SolanaRuby::Keypair.from_private_key("InsertAdditionalSignerPrivateKeyHere")]
|
554
|
+
multi_signers = []
|
555
|
+
|
556
|
+
# Extract public keys of multi-signers
|
557
|
+
multi_signer_pubkeys = multi_signers.map { |signer| signer[:public_key] }
|
558
|
+
|
559
|
+
# Create the close account transaction
|
560
|
+
transaction = SolanaRuby::TransactionHelper.close_account(
|
561
|
+
account_to_close_pubkey,
|
562
|
+
destination_pubkey,
|
563
|
+
owner_pubkey,
|
564
|
+
payer_pubkey,
|
565
|
+
multi_signer_pubkeys,
|
566
|
+
recent_blockhash
|
567
|
+
)
|
568
|
+
|
569
|
+
# Sign the transaction
|
570
|
+
transaction.sign([payer_keypair, owner_keypair])
|
571
|
+
|
572
|
+
# Send the transaction
|
573
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
574
|
+
|
575
|
+
# Output transaction results
|
576
|
+
puts "Transaction Signature: #{response}"
|
577
|
+
puts "Closed account: #{account_to_close_pubkey}, funds sent to: #{destination_pubkey}"
|
578
|
+
|
579
|
+
### Get Associated Token Address
|
580
|
+
|
581
|
+
The get_associated_token_address helper fetches the associated token account for a given mint address and owner public key. This is essential when interacting with SPL tokens to determine where tokens are held.
|
582
|
+
|
583
|
+
#### Requirements:
|
584
|
+
|
585
|
+
- **Mint Address**: The public key of the SPL token mint.
|
586
|
+
- **Owner Public Key**: The public key of the token holder.
|
587
|
+
|
588
|
+
#### Example Usage:
|
589
|
+
|
590
|
+
require 'solana_ruby'
|
591
|
+
|
592
|
+
# Define mint address and owner public key
|
593
|
+
mint_address = 'InsertMintPublicKeyHere'
|
594
|
+
owner_pubkey = 'InsertOwnerPublicKeyHere'
|
595
|
+
|
596
|
+
# Fetch associated token address
|
597
|
+
associated_token_address = SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(
|
598
|
+
mint_address,
|
599
|
+
owner_pubkey
|
600
|
+
)
|
601
|
+
|
602
|
+
puts "Associated Token Address: #{associated_token_address}"
|
603
|
+
|
604
|
+
### Mint SPL Tokens
|
605
|
+
|
606
|
+
The mint_spl_tokens helper allows you to mint new SPL tokens to a specified destination account. This is useful for token creators who need to distribute newly minted tokens.
|
607
|
+
|
608
|
+
#### Requirements:
|
609
|
+
|
610
|
+
- **Mint Account Public Key**: The public key of the mint account.
|
611
|
+
- **Destination Account Public Key**: The associated token account where the newly minted tokens will be sent.
|
612
|
+
- **Mint Authority Public Key**: The public key of the authority allowed to mint new tokens.
|
613
|
+
- **Amount**: The number of tokens to mint (in the smallest unit, based on token decimals).
|
614
|
+
- **Multi-Signers (Optional)**: Additional signer public keys if multi-signature authorization is required.
|
615
|
+
- **Recent Blockhash**: The latest blockhash for the transaction.
|
616
|
+
|
617
|
+
#### Example Usage:
|
618
|
+
|
619
|
+
require 'solana_ruby'
|
620
|
+
|
621
|
+
# Initialize the Solana client (defaults to Mainnet: https://api.mainnet-beta.solana.com)
|
622
|
+
client = SolanaRuby::HttpClient.new('https://api.devnet.solana.com')
|
623
|
+
|
624
|
+
# Fetch the recent blockhash
|
625
|
+
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
626
|
+
|
627
|
+
# Define the mint account and recipient
|
628
|
+
mint_account = "InsertMintPublicKeyHere"
|
629
|
+
destination_account = "InsertDestinationPublicKeyHere"
|
630
|
+
|
631
|
+
# Load the mint authority keypair
|
632
|
+
mint_authority = SolanaRuby::Keypair.load_keypair('InsertYourJsonFilePathHere')
|
633
|
+
|
634
|
+
puts "Mint Authority Public Key: #{mint_authority[:public_key]}"
|
635
|
+
|
636
|
+
# Define the amount to mint (in smallest units)
|
637
|
+
amount = 1_000_000_00_00 # Adjust based on token decimals
|
638
|
+
|
639
|
+
# Multi-signers (if required)
|
640
|
+
multi_signers = [] # Example: [additional_signer_pubkey]
|
641
|
+
|
642
|
+
# Create the mint transaction
|
643
|
+
transaction = SolanaRuby::TransactionHelper.mint_spl_tokens(
|
644
|
+
mint_account,
|
645
|
+
destination_account,
|
646
|
+
mint_authority[:public_key],
|
647
|
+
amount,
|
648
|
+
recent_blockhash,
|
649
|
+
multi_signers
|
650
|
+
)
|
651
|
+
|
652
|
+
# Sign the transaction with the mint authority
|
653
|
+
transaction.sign([mint_authority])
|
654
|
+
|
655
|
+
# Send the transaction
|
656
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
657
|
+
|
658
|
+
# Output transaction results
|
659
|
+
puts "Transaction Signature: #{response}"
|
660
|
+
puts "Minted #{amount} tokens to: #{destination_account}"
|
661
|
+
|
662
|
+
### Burn SPL Tokens
|
663
|
+
|
664
|
+
The burn_spl_tokens helper allows you to burn (destroy) a specified amount of SPL tokens from a token account. This is typically used to reduce the total supply of a token.
|
665
|
+
|
666
|
+
#### Requirements:
|
667
|
+
|
668
|
+
- **Token Account**: The associated token account holding the tokens to be burned..
|
669
|
+
- **Mint Address**: The mint address of the SPL token.
|
670
|
+
- **Owner**: The owner of the token account (must have authority to burn tokens).
|
671
|
+
- **Amount**: The number of tokens to burn (denominated in the smallest units).
|
672
|
+
- **Recent Blockhash**: The latest blockhash for the transaction.
|
673
|
+
|
674
|
+
#### Example Usage:
|
675
|
+
|
676
|
+
require 'solana_ruby'
|
677
|
+
|
678
|
+
# Initialize the Solana client (defaults to Mainnet: https://api.mainnet-beta.solana.com)
|
679
|
+
client = SolanaRuby::HttpClient.new('https://api.devnet.solana.com')
|
680
|
+
|
681
|
+
# Fetch the recent blockhash
|
682
|
+
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
683
|
+
|
684
|
+
# Define token account and mint address
|
685
|
+
token_account = "InsertTokenAccountPublicKeyHere"
|
686
|
+
mint_address = "InsertMintPublicKeyHere"
|
687
|
+
|
688
|
+
# Load the mint authority keypair
|
689
|
+
mint_authority = SolanaRuby::Keypair.load_keypair('/path/to/id.json')
|
690
|
+
owner = mint_authority[:public_key]
|
691
|
+
|
692
|
+
# Define the amount to burn
|
693
|
+
amount = 500_000 # Tokens to burn in smallest units
|
694
|
+
|
695
|
+
# Create burn transaction
|
696
|
+
transaction = SolanaRuby::TransactionHelper.burn_spl_tokens(
|
697
|
+
token_account,
|
698
|
+
mint_address,
|
699
|
+
owner,
|
700
|
+
amount,
|
701
|
+
recent_blockhash
|
702
|
+
)
|
703
|
+
|
704
|
+
# Sign the transaction
|
705
|
+
transaction.sign([mint_authority])
|
706
|
+
|
707
|
+
# Send the transaction
|
708
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
709
|
+
|
710
|
+
# Output transaction results
|
711
|
+
puts "Transaction Signature: #{response}"
|
712
|
+
|
713
|
+
### Transfer SPL Tokens
|
714
|
+
|
715
|
+
The new_spl_token_transaction helper allows you to transfer SPL tokens from one associated token account to another.
|
716
|
+
|
717
|
+
#### Requirements:
|
718
|
+
|
719
|
+
- **Sender's Token Account**: The associated token account holding the tokens to be transferred.
|
720
|
+
- **Mint Address**: The mint address of the SPL token.
|
721
|
+
- **Receiver's Token Account**: The associated token account of the recipient.
|
722
|
+
- **Fee Payer:**: The account responsible for transaction fees.
|
723
|
+
- **Amount**: The number of tokens to mint (in the smallest unit, based on token decimals).
|
724
|
+
- **Decimals**: The decimal precision of the SPL token (e.g., 9 for SOL-based tokens).
|
725
|
+
- **Recent Blockhash**: The latest blockhash for the transaction.
|
726
|
+
- **Multi-Signers (Optional)**: List of additional required signers (if applicable).
|
727
|
+
|
728
|
+
#### Example Usage:
|
729
|
+
|
730
|
+
require 'solana_ruby'
|
731
|
+
|
732
|
+
# Initialize the Solana client
|
733
|
+
client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
|
734
|
+
|
735
|
+
# Fetch the recent blockhash
|
736
|
+
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
737
|
+
|
738
|
+
# Load the fee payer's keypair
|
739
|
+
fee_payer = SolanaRuby::Keypair.from_private_key('InsertFeePayerPrivateKeyHere')
|
740
|
+
fee_payer_pubkey = fee_payer[:public_key]
|
741
|
+
|
742
|
+
# Define the SPL token mint address
|
743
|
+
mint_address = 'InsertMintPublicKeyHere'
|
744
|
+
|
745
|
+
# Define sender and receiver public keys
|
746
|
+
sender_pubkey = 'InsertSenderPublicKeyHere'
|
747
|
+
receiver_pubkey = 'InsertReceiverPublicKeyHere'
|
748
|
+
|
749
|
+
# Fetch the associated token accounts
|
750
|
+
senders_token_account = SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(mint_address, sender_pubkey)
|
751
|
+
receivers_token_account = SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(mint_address, receiver_pubkey)
|
752
|
+
|
753
|
+
puts "Sender's Token Account: #{senders_token_account}"
|
754
|
+
puts "Receiver's Token Account: #{receivers_token_account}"
|
755
|
+
|
756
|
+
# Define the transfer amount and decimals
|
757
|
+
transfer_lamports = 1_000_000 # Amount in smallest units
|
758
|
+
decimals = 9 # Adjust based on token precision
|
759
|
+
|
760
|
+
# Multi-signers (Optional, default is an empty array)
|
761
|
+
# multi_signers = [
|
762
|
+
# SolanaRuby::Keypair.from_private_key('InsertMultiSigner1PrivateKeyHere'),
|
763
|
+
# SolanaRuby::Keypair.from_private_key('InsertMultiSigner2PrivateKeyHere')
|
764
|
+
# ]
|
765
|
+
# multi_signer_pubkeys = multi_signers.map { |signer| signer[:public_key] }
|
766
|
+
multi_signers = []
|
767
|
+
|
768
|
+
# Create the transaction
|
769
|
+
transaction = SolanaRuby::TransactionHelper.new_spl_token_transaction(
|
770
|
+
senders_token_account,
|
771
|
+
mint_address,
|
772
|
+
receivers_token_account,
|
773
|
+
fee_payer_pubkey,
|
774
|
+
transfer_lamports,
|
775
|
+
decimals,
|
776
|
+
recent_blockhash,
|
777
|
+
multi_signer_pubkeys
|
778
|
+
)
|
779
|
+
|
780
|
+
# Sign the transaction (Only fee payer, unless multi-signers are provided)
|
781
|
+
transaction.sign([fee_payer] + multi_signers)
|
782
|
+
|
783
|
+
# Send the transaction
|
784
|
+
puts "Sending transaction..."
|
785
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
786
|
+
|
787
|
+
# Output transaction results
|
788
|
+
puts "Transaction Signature: #{response}"
|
789
|
+
|
790
|
+
|
@@ -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,32 @@ 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
|
45
|
+
},
|
46
|
+
# Close account layout (added here)
|
47
|
+
close_account: {
|
48
|
+
instruction: :uint8
|
28
49
|
}
|
29
50
|
}
|
30
51
|
|
@@ -41,18 +62,13 @@ module SolanaRuby
|
|
41
62
|
}
|
42
63
|
)
|
43
64
|
|
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
|
-
)
|
65
|
+
keys = [
|
66
|
+
{ pubkey: from_pubkey, is_signer: true, is_writable: true }, # Funder's account
|
67
|
+
{ pubkey: new_account_pubkey, is_signer: true, is_writable: true } # New account
|
68
|
+
]
|
53
69
|
|
54
70
|
# return instruction data
|
55
|
-
|
71
|
+
create_instruction(keys, instruction_data, program_id)
|
56
72
|
end
|
57
73
|
|
58
74
|
|
@@ -70,18 +86,50 @@ module SolanaRuby
|
|
70
86
|
transaction
|
71
87
|
end
|
72
88
|
|
89
|
+
# Method to create a close account instruction
|
90
|
+
def self.close_account_instruction( account_to_close, destination, owner, payer, multi_signers)
|
91
|
+
# Encode the instruction data
|
92
|
+
instruction_data = encode_data(
|
93
|
+
INSTRUCTION_LAYOUTS[:close_account],
|
94
|
+
{ instruction: 9 } # Close account instruction number is 9
|
95
|
+
)
|
96
|
+
|
97
|
+
signer = multi_signers.empty? ? payer : multi_signers
|
98
|
+
# Set up the keys for the close account instruction
|
99
|
+
keys = SolanaRuby::TransactionHelpers::TokenAccount.add_signers(
|
100
|
+
[{ pubkey: account_to_close, is_signer: false, is_writable: true },
|
101
|
+
{ pubkey: destination, is_signer: false, is_writable: true }],
|
102
|
+
owner, signer)
|
103
|
+
|
104
|
+
# Return the instruction
|
105
|
+
create_instruction(keys, instruction_data)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Method to close an account (helper)
|
109
|
+
def self.close_account(account_to_close, destination, owner, payer, multi_signers, recent_blockhash)
|
110
|
+
# Create the transaction
|
111
|
+
transaction = Transaction.new
|
112
|
+
transaction.set_fee_payer(payer)
|
113
|
+
transaction.set_recent_blockhash(recent_blockhash)
|
114
|
+
|
115
|
+
# Add the close account instruction to the transaction
|
116
|
+
instruction = close_account_instruction(account_to_close, destination, owner, payer, multi_signers)
|
117
|
+
transaction.add_instruction(instruction)
|
118
|
+
|
119
|
+
# Return the transaction for signing
|
120
|
+
transaction
|
121
|
+
end
|
122
|
+
|
73
123
|
# Method to create a SOL transfer instruction
|
74
124
|
def self.transfer_sol_instruction(from_pubkey, to_pubkey, lamports)
|
75
125
|
fields = INSTRUCTION_LAYOUTS[:sol_transfer]
|
76
126
|
data = encode_data(fields, { instruction: 2, lamports: lamports })
|
77
|
-
|
78
|
-
keys: [
|
127
|
+
keys = [
|
79
128
|
{ pubkey: from_pubkey, is_signer: true, is_writable: true },
|
80
129
|
{ pubkey: to_pubkey, is_signer: false, is_writable: true }
|
81
|
-
]
|
82
|
-
|
83
|
-
|
84
|
-
)
|
130
|
+
]
|
131
|
+
|
132
|
+
create_instruction(keys, data, SYSTEM_PROGRAM_ID)
|
85
133
|
end
|
86
134
|
|
87
135
|
# Helper to create a new transaction for SOL transfer
|
@@ -95,46 +143,117 @@ module SolanaRuby
|
|
95
143
|
end
|
96
144
|
|
97
145
|
# 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
|
-
)
|
146
|
+
def self.transfer_spl_token(source, token, destination, owner, amount, decimals, multi_signers)
|
147
|
+
fields = INSTRUCTION_LAYOUTS[:spl_transfer_checked]
|
148
|
+
data = encode_data(fields, { instruction: 12, amount: amount, decimals: decimals }) # Instruction type 3: Transfer tokens
|
149
|
+
keys = SolanaRuby::TransactionHelpers::TokenAccount.add_signers(
|
150
|
+
[{ pubkey: source, is_signer: false, is_writable: true },
|
151
|
+
{ pubkey: token, is_signer: false, is_writable: false },
|
152
|
+
{ pubkey: destination, is_signer: false, is_writable: true }],
|
153
|
+
owner, multi_signers)
|
154
|
+
|
155
|
+
create_instruction(keys, data)
|
110
156
|
end
|
111
157
|
|
112
158
|
# Helper to create a new transaction for SPL token transfer
|
113
|
-
def self.new_spl_token_transaction(source, destination, owner, amount, recent_blockhash)
|
159
|
+
def self.new_spl_token_transaction(source, mint, destination, owner, amount, decimals, recent_blockhash, multi_signers=[])
|
114
160
|
transaction = Transaction.new
|
115
161
|
transaction.set_fee_payer(owner)
|
116
162
|
transaction.set_recent_blockhash(recent_blockhash)
|
117
|
-
transfer_instruction = transfer_spl_token(source, destination, owner, amount)
|
163
|
+
transfer_instruction = transfer_spl_token(source, mint, destination, owner, amount, decimals, multi_signers)
|
118
164
|
transaction.add_instruction(transfer_instruction)
|
119
165
|
transaction
|
120
166
|
end
|
121
167
|
|
122
|
-
# Method to create an associated token account
|
123
|
-
def self.create_associated_token_account(payer, mint, owner)
|
124
|
-
|
168
|
+
# Method to create an associated token account
|
169
|
+
def self.create_associated_token_account(payer, mint, owner, recent_blockhash, program_id = SYSTEM_PROGRAM_ID)
|
170
|
+
transaction = Transaction.new
|
171
|
+
transaction.set_fee_payer(payer) # Payer funds the transaction
|
172
|
+
transaction.set_recent_blockhash(recent_blockhash)
|
173
|
+
|
174
|
+
# Derive the associated token account address
|
175
|
+
associated_token_account_pubkey = SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(mint, owner)
|
176
|
+
puts "associated_token_account_pubkey: #{associated_token_account_pubkey}"
|
177
|
+
|
178
|
+
|
179
|
+
# Create the associated token account instruction
|
125
180
|
create_account_instruction = TransactionInstruction.new(
|
126
181
|
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:
|
182
|
+
{ pubkey: payer, is_signer: true, is_writable: true }, # Payer account
|
183
|
+
{ pubkey: associated_token_account_pubkey, is_signer: false, is_writable: true }, # New ATA
|
184
|
+
{ pubkey: owner, is_signer: false, is_writable: false }, # Owner of the ATA
|
185
|
+
{ pubkey: mint, is_signer: false, is_writable: false }, # Token mint
|
186
|
+
{ pubkey: SYSTEM_PROGRAM_ID, is_signer: false, is_writable: false }, # System program
|
187
|
+
{ pubkey: TOKEN_PROGRAM_ID, is_signer: false, is_writable: false }, # Token program
|
188
|
+
{ pubkey: SYSVAR_RENT_ID, is_signer: false, is_writable: false }
|
133
189
|
],
|
134
190
|
program_id: ASSOCIATED_TOKEN_PROGRAM_ID,
|
135
|
-
data: data
|
191
|
+
data: [] # No data required for creating an associated token account
|
136
192
|
)
|
137
|
-
|
193
|
+
|
194
|
+
# Add the instruction to the transaction
|
195
|
+
transaction.add_instruction(create_account_instruction)
|
196
|
+
transaction
|
197
|
+
end
|
198
|
+
|
199
|
+
# Method to create a mint instruction for SPL tokens
|
200
|
+
def self.mint_spl_token(mint, destination, mint_authority, amount, multi_signers = [])
|
201
|
+
fields = INSTRUCTION_LAYOUTS[:spl_mint_to]
|
202
|
+
data = encode_data(fields, { instruction: 7, amount: amount }) # Instruction type 7: Mint to
|
203
|
+
keys = SolanaRuby::TransactionHelpers::TokenAccount.add_signers(
|
204
|
+
[{ pubkey: mint, is_signer: false, is_writable: true },
|
205
|
+
{ pubkey: destination, is_signer: false, is_writable: true }],
|
206
|
+
mint_authority, multi_signers)
|
207
|
+
|
208
|
+
create_instruction(keys, data)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Helper to create a transaction for minting SPL tokens
|
212
|
+
def self.mint_spl_tokens(mint, destination, mint_authority, amount, recent_blockhash, multi_signers = [])
|
213
|
+
transaction = Transaction.new
|
214
|
+
transaction.set_fee_payer(mint_authority)
|
215
|
+
transaction.set_recent_blockhash(recent_blockhash)
|
216
|
+
mint_instruction = mint_spl_token(mint, destination, mint_authority, amount, multi_signers)
|
217
|
+
transaction.add_instruction(mint_instruction)
|
218
|
+
transaction
|
219
|
+
end
|
220
|
+
|
221
|
+
# Method to create a burn instruction for SPL tokens
|
222
|
+
def self.burn_spl_token(token_account, mint, mint_authority, amount, multi_signers = [])
|
223
|
+
# Define the fields for the burn instruction
|
224
|
+
fields = INSTRUCTION_LAYOUTS[:spl_burn]
|
225
|
+
|
226
|
+
# Encode the instruction data
|
227
|
+
data = encode_data(fields, { instruction: 8, amount: amount }) # Instruction type 8: Burn
|
228
|
+
|
229
|
+
keys = SolanaRuby::TransactionHelpers::TokenAccount.add_signers(
|
230
|
+
[
|
231
|
+
{ pubkey: token_account, is_signer: false, is_writable: true }, # Token account holding tokens to burn
|
232
|
+
{ pubkey: mint, is_signer: false, is_writable: true } # Mint address
|
233
|
+
], mint_authority, multi_signers)
|
234
|
+
|
235
|
+
# Return the transaction instruction
|
236
|
+
create_instruction(keys, data)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Helper to create a transaction for burning SPL tokens
|
240
|
+
def self.burn_spl_tokens(token_account, mint, owner, amount, recent_blockhash, multi_signers = [])
|
241
|
+
# Create a new transaction
|
242
|
+
transaction = Transaction.new
|
243
|
+
transaction.set_fee_payer(owner)
|
244
|
+
transaction.set_recent_blockhash(recent_blockhash)
|
245
|
+
|
246
|
+
# Add the burn instruction to the transaction
|
247
|
+
burn_instruction = burn_spl_token(token_account, mint, owner, amount, multi_signers)
|
248
|
+
transaction.add_instruction(burn_instruction)
|
249
|
+
|
250
|
+
# Return the transaction for signing
|
251
|
+
transaction
|
252
|
+
end
|
253
|
+
|
254
|
+
# Derive the associated token account address
|
255
|
+
def self.get_associated_token_address(mint, owner, program_id)
|
256
|
+
SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(mint, owner, program_id)
|
138
257
|
end
|
139
258
|
|
140
259
|
# Utility to encode data using predefined layouts
|
@@ -148,5 +267,13 @@ module SolanaRuby
|
|
148
267
|
layout = SolanaRuby::DataTypes::Layout.new(fields)
|
149
268
|
layout.deserialize(data)
|
150
269
|
end
|
270
|
+
|
271
|
+
def self.create_instruction(keys, data, token_program_id = TOKEN_PROGRAM_ID)
|
272
|
+
TransactionInstruction.new(
|
273
|
+
keys: keys,
|
274
|
+
program_id: token_program_id,
|
275
|
+
data: data
|
276
|
+
)
|
277
|
+
end
|
151
278
|
end
|
152
279
|
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.is_a?(Array) && 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}"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'pry'
|
3
|
+
|
4
|
+
Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/*.rb')].each { |file| require file }
|
5
|
+
Dir[File.join(File.dirname(__dir__), 'lib/solana_ruby/**/*.rb')].each { |file| require file }
|
6
|
+
|
7
|
+
# SOL Transfer Testing Script
|
8
|
+
|
9
|
+
# Initialize the Solana client
|
10
|
+
client = SolanaRuby::HttpClient.new('http://127.0.0.1:8899')
|
11
|
+
|
12
|
+
# Fetch the recent blockhash
|
13
|
+
recent_blockhash = client.get_latest_blockhash["blockhash"]
|
14
|
+
|
15
|
+
# Assuming you already have the following public keys
|
16
|
+
payer = SolanaRuby::Keypair.from_private_key('7bcd98e0be17a23f5adbb199d614f5df28e2491ca9856a7efc276245d9d22f26')
|
17
|
+
payer_pubkey = payer[:public_key]
|
18
|
+
owner = SolanaRuby::Keypair.from_private_key('7bcd98e0be17a23f5adbb199d614f5df28e2491ca9856a7efc276245d9d22f26')
|
19
|
+
account_to_close_pubkey = 'bnKvAbZzNjF123Wa9g4yQNva5adLqj2fLNfipZ2KgP6' # associated token account closing address
|
20
|
+
destination_pubkey = 'DCm6PCsdRoEXzHUdHxXnJTP65gYSPK5p8h9Cui3quiQQ' # associated token account receiving address
|
21
|
+
|
22
|
+
# Create the transaction to close the account
|
23
|
+
transaction = SolanaRuby::TransactionHelper.close_account(
|
24
|
+
account_to_close_pubkey,
|
25
|
+
destination_pubkey,
|
26
|
+
owner[:public_key],
|
27
|
+
payer_pubkey,
|
28
|
+
[],
|
29
|
+
recent_blockhash
|
30
|
+
)
|
31
|
+
|
32
|
+
resp = transaction.sign([payer, owner])
|
33
|
+
|
34
|
+
puts "signature: #{resp}"
|
35
|
+
|
36
|
+
# Send the transaction
|
37
|
+
puts "Sending transaction..."
|
38
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
39
|
+
|
40
|
+
# Output transaction results
|
41
|
+
puts "Transaction Signature: #{response}"
|
42
|
+
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
# require 'pry'
|
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
|
|
@@ -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,53 @@
|
|
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("4b9a36383e12d13581c37a50c38a00d91ae0e80f3ce25de852ea61f499102a33")
|
20
|
+
owner_pubkey = owner[:public_key]
|
21
|
+
puts "owner public key: #{owner_pubkey}"
|
22
|
+
puts "owner private key: #{owner[:private_key]}"
|
23
|
+
puts "owner full private key: #{owner[:full_private_key]}"
|
24
|
+
|
25
|
+
# Airdrop some lamports to the sender's account
|
26
|
+
# lamports = 10 * 1_000_000_000
|
27
|
+
# sleep(1)
|
28
|
+
# result = client.request_airdrop(payer_pubkey, lamports)
|
29
|
+
# puts "Solana Balance #{lamports} lamports added sucessfully for the public key: #{payer_pubkey}"
|
30
|
+
# sleep(10)
|
31
|
+
|
32
|
+
|
33
|
+
mint_pubkey = "InsertMintPublicKeyHere"
|
34
|
+
program_id = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
35
|
+
puts "payer public key: #{payer_pubkey}"
|
36
|
+
|
37
|
+
# associated_token_address = SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(mint_pubkey, payer_pubkey, program_id)
|
38
|
+
|
39
|
+
# puts "Associated Token Address: #{associated_token_address}"
|
40
|
+
|
41
|
+
transaction = SolanaRuby::TransactionHelper.create_associated_token_account(payer_pubkey, mint_pubkey, owner_pubkey, recent_blockhash)
|
42
|
+
|
43
|
+
resp = transaction.sign([payer])
|
44
|
+
|
45
|
+
puts "signature: #{resp}"
|
46
|
+
|
47
|
+
# Send the transaction
|
48
|
+
puts "Sending transaction..."
|
49
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
50
|
+
|
51
|
+
# Output transaction results
|
52
|
+
puts "Transaction Signature: #{response}"
|
53
|
+
|
@@ -0,0 +1,43 @@
|
|
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 = "2DoNkK31X9HH5MXY6pBeb3RDZ1ZDK7wgXrcvnyipXNvf"
|
16
|
+
destination_account = "DCm6PCsdRoEXzHUdHxXnJTP65gYSPK5p8h9Cui3quiQQ"
|
17
|
+
mint_authority = SolanaRuby::Keypair.load_keypair('/Users/chinaputtaiahbellamkonda/.config/solana/id.json')
|
18
|
+
puts "Full private key: #{mint_authority[:full_private_key]}"
|
19
|
+
puts "Private key: #{mint_authority[:private_key]}"
|
20
|
+
puts "Public key: #{mint_authority[:public_key]}"
|
21
|
+
amount = 1_000_000_00_00 # Amount to mint in smallest units
|
22
|
+
multi_signers = [] # If multi-signature is used, include public keys here
|
23
|
+
|
24
|
+
# Create a mint transaction
|
25
|
+
transaction = SolanaRuby::TransactionHelper.mint_spl_tokens(
|
26
|
+
mint_account,
|
27
|
+
destination_account,
|
28
|
+
mint_authority[:public_key],
|
29
|
+
amount,
|
30
|
+
recent_blockhash,
|
31
|
+
multi_signers
|
32
|
+
)
|
33
|
+
|
34
|
+
resp = transaction.sign([mint_authority])
|
35
|
+
|
36
|
+
puts "signature: #{resp}"
|
37
|
+
|
38
|
+
# Send the transaction
|
39
|
+
puts "Sending transaction..."
|
40
|
+
response = client.send_transaction(transaction.to_base64, { encoding: 'base64' })
|
41
|
+
|
42
|
+
# Output transaction results
|
43
|
+
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.from_private_key(
|
16
|
+
fee_payer = SolanaRuby::Keypair.from_private_key('e06f61b73aa625690ef97ed3704e8dc22bc835092e94cc9ae5650b516f26c91a')
|
17
17
|
fee_payer_pubkey = fee_payer[:public_key]
|
18
18
|
lamports = 10 * 1_000_000_000
|
19
19
|
space = 165
|
@@ -24,22 +24,26 @@ puts "sender account balance: #{balance}, wait for few seconds to update the bal
|
|
24
24
|
|
25
25
|
|
26
26
|
# # Generate a receiver keypair and public key
|
27
|
-
keypair = SolanaRuby::Keypair.
|
27
|
+
keypair = SolanaRuby::Keypair.from_private_key('f7173083807e1692e844aa2ec515eca18d016fc5e4468be75be20f6093de6641')
|
28
28
|
receiver_pubkey = keypair[:public_key]
|
29
|
-
transfer_lamports =
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
transfer_lamports = 1_000_000
|
30
|
+
mint_address = 'GDAWgGT42CqgaMds81JFVoqyJ4WBvfQsHAshPggAfXCM'
|
31
|
+
|
32
|
+
senders_token_account = SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(mint_address, fee_payer_pubkey)
|
33
|
+
receivers_token_account = SolanaRuby::TransactionHelpers::TokenAccount.get_associated_token_address(mint_address, receiver_pubkey)
|
34
|
+
puts "senders_token_account: #{senders_token_account}"
|
35
|
+
puts "receivers_token_account: #{receivers_token_account}"
|
35
36
|
|
36
37
|
# Create a new transaction
|
37
38
|
transaction = SolanaRuby::TransactionHelper.new_spl_token_transaction(
|
38
|
-
|
39
|
-
|
39
|
+
senders_token_account,
|
40
|
+
mint_address,
|
41
|
+
receivers_token_account,
|
40
42
|
fee_payer_pubkey,
|
41
43
|
transfer_lamports,
|
42
|
-
|
44
|
+
9,
|
45
|
+
recent_blockhash,
|
46
|
+
[]
|
43
47
|
)
|
44
48
|
# # Get the sender's private key (ensure it's a string)
|
45
49
|
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.1.0
|
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-02-06 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,11 @@ 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
|
266
|
+
- transaction_testing/close_acccount.rb
|
262
267
|
- transaction_testing/create_account.rb
|
268
|
+
- transaction_testing/create_spl_token_account.rb
|
269
|
+
- transaction_testing/mint_spl_tokens.rb
|
263
270
|
- transaction_testing/sol_transfer.rb
|
264
271
|
- transaction_testing/spl_token_transfer.rb
|
265
272
|
homepage: https://github.com/Build-Squad/solana-ruby
|
@@ -284,7 +291,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
284
291
|
- !ruby/object:Gem::Version
|
285
292
|
version: '0'
|
286
293
|
requirements: []
|
287
|
-
rubygems_version: 3.5.
|
294
|
+
rubygems_version: 3.5.20
|
288
295
|
signing_key:
|
289
296
|
specification_version: 4
|
290
297
|
summary: Solana Ruby SDK
|