diff --git a/rpc/examples/getBlock/getBlock.go b/rpc/examples/getBlock/getBlock.go index 8769b13b8..2b6b66bec 100644 --- a/rpc/examples/getBlock/getBlock.go +++ b/rpc/examples/getBlock/getBlock.go @@ -16,6 +16,7 @@ package main import ( "context" + "fmt" "github.com/davecgh/go-spew/spew" "github.com/gagliardetto/solana-go" @@ -23,36 +24,40 @@ import ( ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + ctx := context.Background() + client := rpc.New(rpc.MainNetBeta_RPC) - slot, err := client.GetSlot(context.TODO(), rpc.CommitmentFinalized) + slot, err := client.GetSlot(ctx, rpc.CommitmentFinalized) if err != nil { panic(err) } + // Mainnet blocks contain v0 (versioned) transactions; GetBlock + // requires MaxSupportedTransactionVersion or it errors out. + maxVersion := uint64(0) + { - out, err := client.GetBlock(context.TODO(), slot) + out, err := client.GetBlockWithOpts(ctx, slot, &rpc.GetBlockOpts{ + MaxSupportedTransactionVersion: &maxVersion, + }) if err != nil { panic(err) } - // spew.Dump(out) // NOTE: This generates a lot of output. - spew.Dump(len(out.Transactions)) + // Full block is large; just show the tx count. + fmt.Println("transactions in block:", len(out.Transactions)) } { includeRewards := false out, err := client.GetBlockWithOpts( - context.TODO(), + ctx, slot, - // You can specify more options here: &rpc.GetBlockOpts{ - Encoding: solana.EncodingBase64, - Commitment: rpc.CommitmentFinalized, - // Get only signatures: - TransactionDetails: rpc.TransactionDetailsSignatures, - // Exclude rewards: - Rewards: &includeRewards, + Encoding: solana.EncodingBase64, + Commitment: rpc.CommitmentFinalized, + TransactionDetails: rpc.TransactionDetailsSignatures, + Rewards: &includeRewards, + MaxSupportedTransactionVersion: &maxVersion, }, ) if err != nil { diff --git a/rpc/examples/getBlockProduction/getBlockProduction.go b/rpc/examples/getBlockProduction/getBlockProduction.go index e4398b1a1..0f4ae94ea 100644 --- a/rpc/examples/getBlockProduction/getBlockProduction.go +++ b/rpc/examples/getBlockProduction/getBlockProduction.go @@ -22,30 +22,19 @@ import ( ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + ctx := context.Background() + // Testnet has few validators; mainnet-beta has the real schedule. + client := rpc.New(rpc.MainNetBeta_RPC) - { - out, err := client.GetBlockProduction(context.TODO()) - if err != nil { - panic(err) - } - spew.Dump(out) - } - { - out, err := client.GetBlockProductionWithOpts( - context.TODO(), - &rpc.GetBlockProductionOpts{ - Commitment: rpc.CommitmentFinalized, - // Range: &rpc.SlotRangeRequest{ - // FirstSlot: XXXXXX, - // Identity: solana.MustPublicKeyFromBase58("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"), - // }, - }, - ) - if err != nil { - panic(err) - } - spew.Dump(out) + out, err := client.GetBlockProductionWithOpts( + ctx, + &rpc.GetBlockProductionOpts{ + Commitment: rpc.CommitmentFinalized, + // Range: &rpc.SlotRangeRequest{ FirstSlot: ..., Identity: ... }, + }, + ) + if err != nil { + panic(err) } + spew.Dump(out) } diff --git a/rpc/examples/getFeeForMessage/getFeeForMessage.go b/rpc/examples/getFeeForMessage/getFeeForMessage.go index 0a2605462..8c7d3fc0f 100644 --- a/rpc/examples/getFeeForMessage/getFeeForMessage.go +++ b/rpc/examples/getFeeForMessage/getFeeForMessage.go @@ -16,22 +16,51 @@ package main import ( "context" + "fmt" "github.com/davecgh/go-spew/spew" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/programs/system" "github.com/gagliardetto/solana-go/rpc" ) +// GetFeeForMessage returns the fee for a base64-encoded Solana Message +// (not a full Transaction). Here we construct a realistic Transfer +// message to show how the base64 argument is produced. func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + ctx := context.Background() + client := rpc.New(rpc.MainNetBeta_RPC) - example, err := client.GetFeeForMessage( - context.Background(), - "AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA", + from := solana.NewWallet().PublicKey() + to := solana.NewWallet().PublicKey() + + recent, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) + if err != nil { + panic(fmt.Errorf("get blockhash: %w", err)) + } + + tx, err := solana.NewTransaction( + []solana.Instruction{ + system.NewTransferInstruction( + solana.LAMPORTS_PER_SOL/1000, // 0.001 SOL + from, + to, + ).Build(), + }, + recent.Value.Blockhash, + solana.TransactionPayer(from), + ) + if err != nil { + panic(fmt.Errorf("build tx: %w", err)) + } + + out, err := client.GetFeeForMessage( + ctx, + tx.Message.ToBase64(), rpc.CommitmentProcessed, ) if err != nil { panic(err) } - spew.Dump(example) + spew.Dump(out) } diff --git a/rpc/examples/getInflationGovernor/getInflationGovernor.go b/rpc/examples/getInflationGovernor/getInflationGovernor.go index e7ff28857..ea0fb454c 100644 --- a/rpc/examples/getInflationGovernor/getInflationGovernor.go +++ b/rpc/examples/getInflationGovernor/getInflationGovernor.go @@ -22,8 +22,7 @@ import ( ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + client := rpc.New(rpc.MainNetBeta_RPC) out, err := client.GetInflationGovernor( context.TODO(), diff --git a/rpc/examples/getInflationRate/getInflationRate.go b/rpc/examples/getInflationRate/getInflationRate.go index f6c644885..dd01e2dc2 100644 --- a/rpc/examples/getInflationRate/getInflationRate.go +++ b/rpc/examples/getInflationRate/getInflationRate.go @@ -22,8 +22,7 @@ import ( ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + client := rpc.New(rpc.MainNetBeta_RPC) out, err := client.GetInflationRate( context.TODO(), diff --git a/rpc/examples/getInflationReward/getInflationReward.go b/rpc/examples/getInflationReward/getInflationReward.go index 45acee483..913555d26 100644 --- a/rpc/examples/getInflationReward/getInflationReward.go +++ b/rpc/examples/getInflationReward/getInflationReward.go @@ -23,16 +23,23 @@ import ( ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + ctx := context.Background() + client := rpc.New(rpc.MainNetBeta_RPC) - pubKey := solana.MustPublicKeyFromBase58("6dmNQ5jwLeLk5REvio1JcMshcbvkYMwy26sJ8pbkvStu") + // Fetch any currently-voting validator so the example keeps working + // across epochs without a hardcoded pubkey. + voteAccounts, err := client.GetVoteAccounts(ctx, nil) + if err != nil { + panic(err) + } + if len(voteAccounts.Current) == 0 { + panic("no current vote accounts") + } + votePubkey := voteAccounts.Current[0].VotePubkey out, err := client.GetInflationReward( - context.TODO(), - []solana.PublicKey{ - pubKey, - }, + ctx, + []solana.PublicKey{votePubkey}, &rpc.GetInflationRewardOpts{ Commitment: rpc.CommitmentFinalized, }, diff --git a/rpc/examples/getLargestAccounts/getLargestAccounts.go b/rpc/examples/getLargestAccounts/getLargestAccounts.go index 88aefa174..a21152332 100644 --- a/rpc/examples/getLargestAccounts/getLargestAccounts.go +++ b/rpc/examples/getLargestAccounts/getLargestAccounts.go @@ -22,8 +22,7 @@ import ( ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + client := rpc.New(rpc.MainNetBeta_RPC) out, err := client.GetLargestAccounts( context.TODO(), diff --git a/rpc/examples/getLeaderSchedule/getLeaderSchedule.go b/rpc/examples/getLeaderSchedule/getLeaderSchedule.go index b785058d1..6522fe16b 100644 --- a/rpc/examples/getLeaderSchedule/getLeaderSchedule.go +++ b/rpc/examples/getLeaderSchedule/getLeaderSchedule.go @@ -16,20 +16,29 @@ package main import ( "context" + "fmt" - "github.com/davecgh/go-spew/spew" "github.com/gagliardetto/solana-go/rpc" ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + ctx := context.Background() + client := rpc.New(rpc.MainNetBeta_RPC) - out, err := client.GetLeaderSchedule( - context.TODO(), - ) + out, err := client.GetLeaderSchedule(ctx) if err != nil { panic(err) } - spew.Dump(out) // NOTE: this creates a lot of output + + // The response maps every validator in the current epoch to its + // scheduled slots; on mainnet this is huge. Only print a summary. + fmt.Println("validators in current epoch:", len(out)) + count := 0 + for validator, slots := range out { + if count >= 5 { + break + } + fmt.Printf(" %s -> %d slots\n", validator, len(slots)) + count++ + } } diff --git a/rpc/examples/getProgramAccounts/getProgramAccounts.go b/rpc/examples/getProgramAccounts/getProgramAccounts.go index 654a8b13b..7f5271996 100644 --- a/rpc/examples/getProgramAccounts/getProgramAccounts.go +++ b/rpc/examples/getProgramAccounts/getProgramAccounts.go @@ -16,23 +16,43 @@ package main import ( "context" + "fmt" - "github.com/davecgh/go-spew/spew" "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" ) +// getProgramAccounts returns every account owned by a program. Public +// mainnet RPCs reject it without a restrictive filter because the +// response would be too large. This example filters Token-2022 accounts +// down to just mint accounts (82-byte data) and prints the first few. func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + ctx := context.Background() + client := rpc.New(rpc.MainNetBeta_RPC) - out, err := client.GetProgramAccounts( - context.TODO(), - solana.MustPublicKeyFromBase58("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), + out, err := client.GetProgramAccountsWithOpts( + ctx, + solana.Token2022ProgramID, + &rpc.GetProgramAccountsOpts{ + Commitment: rpc.CommitmentFinalized, + Filters: []rpc.RPCFilter{ + {DataSize: 82}, // SPL Token mint account size + }, + // Only fetch the first byte of data so the response stays small. + DataSlice: &rpc.DataSlice{Offset: ptrU64(0), Length: ptrU64(1)}, + }, ) if err != nil { panic(err) } - spew.Dump(len(out)) - spew.Dump(out) // NOTE: this can generate a lot of output + + fmt.Println("Token-2022 mints found:", len(out)) + for i, acc := range out { + if i >= 5 { + break + } + fmt.Printf(" %d: %s\n", i, acc.Pubkey) + } } + +func ptrU64(v uint64) *uint64 { return &v } diff --git a/rpc/examples/getSignatureStatuses/getSignatureStatuses.go b/rpc/examples/getSignatureStatuses/getSignatureStatuses.go index b246c2408..7db2ac623 100644 --- a/rpc/examples/getSignatureStatuses/getSignatureStatuses.go +++ b/rpc/examples/getSignatureStatuses/getSignatureStatuses.go @@ -16,6 +16,7 @@ package main import ( "context" + "fmt" "github.com/davecgh/go-spew/spew" "github.com/gagliardetto/solana-go" @@ -23,15 +24,34 @@ import ( ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + ctx := context.Background() + client := rpc.New(rpc.MainNetBeta_RPC) + + // Fetch fresh signatures so the example keeps working as the + // cluster advances. + limit := 2 + sigs, err := client.GetSignaturesForAddressWithOpts( + ctx, + solana.TokenProgramID, + &rpc.GetSignaturesForAddressOpts{Limit: &limit}, + ) + if err != nil { + panic(fmt.Errorf("getSignaturesForAddress: %w", err)) + } + if len(sigs) == 0 { + panic("no recent signatures found") + } + + toLookup := make([]solana.Signature, 0, len(sigs)) + for _, s := range sigs { + toLookup = append(toLookup, s.Signature) + } + fmt.Println("querying statuses for", len(toLookup), "signatures") out, err := client.GetSignatureStatuses( - context.TODO(), - true, - // All the transactions you want the get the status for: - solana.MustSignatureFromBase58("2CwH8SqVZWFa1EvsH7vJXGFors1NdCuWJ7Z85F8YqjCLQ2RuSHQyeGKkfo1Tj9HitSTeLoMWnxpjxF2WsCH8nGWh"), - solana.MustSignatureFromBase58("5YJHZPeHZuZjhunBc1CCB1NDRNf2tTJNpdb3azGsR7PfyEncCDhr95wG8EWrvjNXBc4wCKixkheSbCxoC2NCG3X7"), + ctx, + true, // searchTransactionHistory + toLookup..., ) if err != nil { panic(err) diff --git a/rpc/examples/getTransaction/getTransaction.go b/rpc/examples/getTransaction/getTransaction.go index 4afd7d40a..16f8d28bb 100644 --- a/rpc/examples/getTransaction/getTransaction.go +++ b/rpc/examples/getTransaction/getTransaction.go @@ -16,6 +16,7 @@ package main import ( "context" + "fmt" "github.com/davecgh/go-spew/spew" bin "github.com/gagliardetto/binary" @@ -24,28 +25,34 @@ import ( ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + ctx := context.Background() + client := rpc.New(rpc.MainNetBeta_RPC) - txSig := solana.MustSignatureFromBase58("4bjVLV1g9SAfv7BSAdNnuSPRbSscADHFe4HegL6YVcuEBMY83edLEvtfjE4jfr6rwdLwKBQbaFiGgoLGtVicDzHq") - { - out, err := client.GetTransaction( - context.TODO(), - txSig, - nil, - ) - if err != nil { - panic(err) - } - spew.Dump(out) - spew.Dump(out.Transaction.GetTransaction()) + // Fetch a fresh signature so the example keeps working as the + // cluster advances. The SPL Token program always has recent activity + // on mainnet-beta. + limit := 1 + sigs, err := client.GetSignaturesForAddressWithOpts( + ctx, + solana.TokenProgramID, + &rpc.GetSignaturesForAddressOpts{Limit: &limit}, + ) + if err != nil { + panic(fmt.Errorf("getSignaturesForAddress: %w", err)) + } + if len(sigs) == 0 { + panic("no recent signatures found") } + txSig := sigs[0].Signature + fmt.Println("fetching tx:", txSig) + + maxVersion := uint64(0) { out, err := client.GetTransaction( - context.TODO(), + ctx, txSig, &rpc.GetTransactionOpts{ - Encoding: solana.EncodingJSON, + MaxSupportedTransactionVersion: &maxVersion, }, ) if err != nil { @@ -56,24 +63,26 @@ func main() { } { out, err := client.GetTransaction( - context.TODO(), + ctx, txSig, &rpc.GetTransactionOpts{ - Encoding: solana.EncodingBase58, + Encoding: solana.EncodingJSON, + MaxSupportedTransactionVersion: &maxVersion, }, ) if err != nil { panic(err) } spew.Dump(out) - spew.Dump(out.Transaction.GetBinary()) + spew.Dump(out.Transaction.GetTransaction()) } { out, err := client.GetTransaction( - context.TODO(), + ctx, txSig, &rpc.GetTransactionOpts{ - Encoding: solana.EncodingBase64, + Encoding: solana.EncodingBase64, + MaxSupportedTransactionVersion: &maxVersion, }, ) if err != nil { diff --git a/rpc/examples/getVoteAccounts/getVoteAccounts.go b/rpc/examples/getVoteAccounts/getVoteAccounts.go index 42d5b08e7..2f682628c 100644 --- a/rpc/examples/getVoteAccounts/getVoteAccounts.go +++ b/rpc/examples/getVoteAccounts/getVoteAccounts.go @@ -16,24 +16,30 @@ package main import ( "context" + "fmt" - "github.com/davecgh/go-spew/spew" - "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + ctx := context.Background() + client := rpc.New(rpc.MainNetBeta_RPC) - out, err := client.GetVoteAccounts( - context.TODO(), - &rpc.GetVoteAccountsOpts{ - VotePubkey: solana.MustPublicKeyFromBase58("vot33MHDqT6nSwubGzqtc6m16ChcUywxV7tNULF19Vu").ToPointer(), - }, - ) + // Without options, returns every vote account on the cluster. To + // filter to one validator, pass &GetVoteAccountsOpts{VotePubkey: &...}. + out, err := client.GetVoteAccounts(ctx, nil) if err != nil { panic(err) } - spew.Dump(out) + + // Full response is large on mainnet; print a summary. + fmt.Println("current vote accounts:", len(out.Current)) + fmt.Println("delinquent vote accounts:", len(out.Delinquent)) + for i, v := range out.Current { + if i >= 5 { + break + } + fmt.Printf(" %s stake=%d commission=%d%%\n", + v.VotePubkey, v.ActivatedStake, v.Commission) + } } diff --git a/rpc/examples/isBlockhashValid/isValidBlockhash.go b/rpc/examples/isBlockhashValid/isValidBlockhash.go index 0cbba74c4..50fa380ba 100644 --- a/rpc/examples/isBlockhashValid/isValidBlockhash.go +++ b/rpc/examples/isBlockhashValid/isValidBlockhash.go @@ -19,25 +19,29 @@ import ( "fmt" "github.com/davecgh/go-spew/spew" - "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" ) func main() { - endpoint := rpc.MainNetBeta_RPC - client := rpc.New(endpoint) + ctx := context.Background() + client := rpc.New(rpc.MainNetBeta_RPC) + + // Blockhashes are only valid for ~150 blocks (~1 minute), so fetch + // a live one instead of hardcoding. + recent, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) + if err != nil { + panic(fmt.Errorf("get blockhash: %w", err)) + } - blockHash := solana.MustHashFromBase58("J7rBdM6AecPDEZp8aPq5iPSNKVkU5Q76F3oAV4eW5wsW") out, err := client.IsBlockhashValid( - context.TODO(), - blockHash, + ctx, + recent.Value.Blockhash, rpc.CommitmentFinalized, ) if err != nil { panic(err) } spew.Dump(out) - spew.Dump(out.Value) // true or false - fmt.Println("is blockhash valid:", out.Value) + fmt.Println("is blockhash valid:", out.Value) // true or false } diff --git a/rpc/examples/requestAirdrop/requestAirdrop.go b/rpc/examples/requestAirdrop/requestAirdrop.go index c9e923f94..1a7b1750b 100644 --- a/rpc/examples/requestAirdrop/requestAirdrop.go +++ b/rpc/examples/requestAirdrop/requestAirdrop.go @@ -16,26 +16,29 @@ package main import ( "context" + "fmt" - "github.com/davecgh/go-spew/spew" "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" ) func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) + ctx := context.Background() + // The testnet airdrop faucet is frequently dry; devnet is reliable. + client := rpc.New(rpc.DevNet_RPC) - amount := solana.LAMPORTS_PER_SOL // 1 sol - pubKey := solana.MustPublicKeyFromBase58("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") - out, err := client.RequestAirdrop( - context.TODO(), - pubKey, - amount, - "", + wallet := solana.NewWallet() + fmt.Println("airdropping to:", wallet.PublicKey()) + + sig, err := client.RequestAirdrop( + ctx, + wallet.PublicKey(), + solana.LAMPORTS_PER_SOL, // 1 SOL + rpc.CommitmentFinalized, ) if err != nil { - panic(err) + panic(fmt.Errorf("airdrop: %w", err)) } - spew.Dump(out) + + fmt.Println("airdrop signature:", sig) } diff --git a/rpc/examples/sendEncodedTransaction/sendEncodedTransaction.go b/rpc/examples/sendEncodedTransaction/sendEncodedTransaction.go index 1a8140912..e525e1bd3 100644 --- a/rpc/examples/sendEncodedTransaction/sendEncodedTransaction.go +++ b/rpc/examples/sendEncodedTransaction/sendEncodedTransaction.go @@ -17,19 +17,80 @@ package main import ( "context" "fmt" + "time" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/programs/system" "github.com/gagliardetto/solana-go/rpc" ) +// SendEncodedTransaction takes a base64-encoded, fully-signed +// transaction — useful when a wallet or external service hands you the +// encoded string directly. This example builds and signs a transaction +// here only to produce that string; in real code you would receive it. +// +// If you have a *solana.Transaction, use SendTransaction instead +// (see the sendTransaction example). If you have raw bytes, use +// SendRawTransaction (see sendRawTransaction). func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) - base64Tx := "AepZ357SXFu9tOBeX8v8aqkPNyGq/RUN4EvDzWAT/Fsg1htQu0Zp6F6OxO645I5z98VI4XacPKJp8rtHOE7Q9Q0BAAED8gJOoYf0IvSirfhOq6ZFf3R3ekB5vFWlaGTEq3irvwFakK+tD9il+9jzzs+gU1wzZxkmXZqyeBhbaXogNlk1GQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAou6RU2rKBCg2RzcJqZwEr4y2VBsWbkYLYMKFcOz64j4BAgIAAQwCAAAAAOH1BQAAAAA=" + ctx := context.Background() + client := rpc.New(rpc.DevNet_RPC) + + sender := solana.NewWallet() + fmt.Println("sender:", sender.PublicKey()) + + airdropSig, err := client.RequestAirdrop( + ctx, + sender.PublicKey(), + solana.LAMPORTS_PER_SOL, + rpc.CommitmentFinalized, + ) + if err != nil { + panic(fmt.Errorf("airdrop: %w", err)) + } + fmt.Println("airdrop signature:", airdropSig) + time.Sleep(20 * time.Second) // wait for the airdrop to finalize + + recipient := solana.NewWallet().PublicKey() + + recent, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) + if err != nil { + panic(fmt.Errorf("get blockhash: %w", err)) + } + + tx, err := solana.NewTransaction( + []solana.Instruction{ + system.NewTransferInstruction( + solana.LAMPORTS_PER_SOL/1000, + sender.PublicKey(), + recipient, + ).Build(), + }, + recent.Value.Blockhash, + solana.TransactionPayer(sender.PublicKey()), + ) + if err != nil { + panic(fmt.Errorf("build tx: %w", err)) + } + + if _, err := tx.Sign(func(key solana.PublicKey) *solana.PrivateKey { + if sender.PublicKey().Equals(key) { + return &sender.PrivateKey + } + return nil + }); err != nil { + panic(fmt.Errorf("sign: %w", err)) + } + + encoded, err := tx.ToBase64() + if err != nil { + panic(fmt.Errorf("encode: %w", err)) + } - sig, err := client.SendEncodedTransaction(context.TODO(), base64Tx) + sig, err := client.SendEncodedTransaction(ctx, encoded) if err != nil { - panic(err) + panic(fmt.Errorf("send encoded: %w", err)) } - fmt.Println("Submitted tx signature: ", sig.String()) + fmt.Println("submitted tx signature:", sig.String()) } diff --git a/rpc/examples/sendRawTransaction/sendRawTransaction.go b/rpc/examples/sendRawTransaction/sendRawTransaction.go index f7a18f0a3..3940b759c 100644 --- a/rpc/examples/sendRawTransaction/sendRawTransaction.go +++ b/rpc/examples/sendRawTransaction/sendRawTransaction.go @@ -16,26 +16,82 @@ package main import ( "context" - "encoding/base64" "fmt" + "time" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/programs/system" "github.com/gagliardetto/solana-go/rpc" ) +// SendRawTransaction takes already-serialized, fully-signed transaction +// bytes — useful when a wallet or external service hands you the bytes +// to relay. This example builds and signs a transaction here only to +// produce those bytes; in real code you would receive them. +// +// If you have a *solana.Transaction, use SendTransaction instead +// (see the sendTransaction example). If you have base64 instead of +// raw bytes, use SendEncodedTransaction (see sendEncodedTransaction). func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) - base64Tx := "AfjEs3XhTc3hrxEvlnMPkm/cocvAUbFNbCl00qKnrFue6J53AhEqIFmcJJlJW3EDP5RmcMz+cNTTcZHW/WJYwAcBAAEDO8hh4VddzfcO5jbCt95jryl6y8ff65UcgukHNLWH+UQGgxCGGpgyfQVQV02EQYqm4QwzUt2qf9f1gVLM7rI4hwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6ANIF55zOZWROWRkeh+lExxZBnKFqbvIxZDLE7EijjoBAgIAAQwCAAAAOTAAAAAAAAA=" + ctx := context.Background() + client := rpc.New(rpc.DevNet_RPC) + + sender := solana.NewWallet() + fmt.Println("sender:", sender.PublicKey()) + + airdropSig, err := client.RequestAirdrop( + ctx, + sender.PublicKey(), + solana.LAMPORTS_PER_SOL, + rpc.CommitmentFinalized, + ) + if err != nil { + panic(fmt.Errorf("airdrop: %w", err)) + } + fmt.Println("airdrop signature:", airdropSig) + time.Sleep(20 * time.Second) // wait for the airdrop to finalize + + recipient := solana.NewWallet().PublicKey() + + recent, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) + if err != nil { + panic(fmt.Errorf("get blockhash: %w", err)) + } + + tx, err := solana.NewTransaction( + []solana.Instruction{ + system.NewTransferInstruction( + solana.LAMPORTS_PER_SOL/1000, + sender.PublicKey(), + recipient, + ).Build(), + }, + recent.Value.Blockhash, + solana.TransactionPayer(sender.PublicKey()), + ) + if err != nil { + panic(fmt.Errorf("build tx: %w", err)) + } + + if _, err := tx.Sign(func(key solana.PublicKey) *solana.PrivateKey { + if sender.PublicKey().Equals(key) { + return &sender.PrivateKey + } + return nil + }); err != nil { + panic(fmt.Errorf("sign: %w", err)) + } - txRaw, err := base64.StdEncoding.DecodeString(base64Tx) + // Serialize the fully-signed transaction to its wire bytes. + raw, err := tx.MarshalBinary() if err != nil { - panic(err) + panic(fmt.Errorf("marshal: %w", err)) } - sig, err := client.SendRawTransaction(context.TODO(), txRaw) + sig, err := client.SendRawTransaction(ctx, raw) if err != nil { - panic(err) + panic(fmt.Errorf("send raw: %w", err)) } - fmt.Println("Submitted tx signature: ", sig.String()) + fmt.Println("submitted tx signature:", sig.String()) } diff --git a/rpc/examples/sendTransaction/sendTransaction.go b/rpc/examples/sendTransaction/sendTransaction.go index f53d6a854..48a2f56b1 100644 --- a/rpc/examples/sendTransaction/sendTransaction.go +++ b/rpc/examples/sendTransaction/sendTransaction.go @@ -16,33 +16,78 @@ package main import ( "context" - "encoding/base64" "fmt" + "time" - bin "github.com/gagliardetto/binary" "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/programs/system" "github.com/gagliardetto/solana-go/rpc" ) +// This example builds, signs, and submits a SOL transfer on devnet. +// It demonstrates the idiomatic flow for `client.SendTransaction`, which +// accepts a *solana.Transaction directly. +// +// For submitting a transaction that is already serialized, see the +// sibling examples `sendRawTransaction` (raw bytes) and +// `sendEncodedTransaction` (base64 string). func main() { - endpoint := rpc.TestNet_RPC - client := rpc.New(endpoint) - base64Tx := "AfjEs3XhTc3hrxEvlnMPkm/cocvAUbFNbCl00qKnrFue6J53AhEqIFmcJJlJW3EDP5RmcMz+cNTTcZHW/WJYwAcBAAEDO8hh4VddzfcO5jbCt95jryl6y8ff65UcgukHNLWH+UQGgxCGGpgyfQVQV02EQYqm4QwzUt2qf9f1gVLM7rI4hwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6ANIF55zOZWROWRkeh+lExxZBnKFqbvIxZDLE7EijjoBAgIAAQwCAAAAOTAAAAAAAAA=" + ctx := context.Background() + client := rpc.New(rpc.DevNet_RPC) + + // Generate a fresh sender and fund it via airdrop. In real code, load + // an existing keypair instead, e.g.: + // sender, _ := solana.PrivateKeyFromSolanaKeygenFile("/path/to/id.json") + sender := solana.NewWallet() + fmt.Println("sender:", sender.PublicKey()) + + airdropSig, err := client.RequestAirdrop( + ctx, + sender.PublicKey(), + solana.LAMPORTS_PER_SOL, + rpc.CommitmentFinalized, + ) + if err != nil { + panic(fmt.Errorf("airdrop: %w", err)) + } + fmt.Println("airdrop signature:", airdropSig) + time.Sleep(20 * time.Second) // wait for the airdrop to finalize + + recipient := solana.NewWallet().PublicKey() - data, err := base64.StdEncoding.DecodeString(base64Tx) + recent, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) if err != nil { - panic(err) + panic(fmt.Errorf("get blockhash: %w", err)) } - tx, err := solana.TransactionFromDecoder(bin.NewBinDecoder(data)) + tx, err := solana.NewTransaction( + []solana.Instruction{ + system.NewTransferInstruction( + solana.LAMPORTS_PER_SOL/1000, // 0.001 SOL + sender.PublicKey(), + recipient, + ).Build(), + }, + recent.Value.Blockhash, + solana.TransactionPayer(sender.PublicKey()), + ) if err != nil { - panic(err) + panic(fmt.Errorf("build tx: %w", err)) + } + + if _, err := tx.Sign(func(key solana.PublicKey) *solana.PrivateKey { + if sender.PublicKey().Equals(key) { + return &sender.PrivateKey + } + return nil + }); err != nil { + panic(fmt.Errorf("sign: %w", err)) } - sig, err := client.SendTransaction(context.TODO(), tx) + sig, err := client.SendTransaction(ctx, tx) if err != nil { - panic(err) + panic(fmt.Errorf("send: %w", err)) } - fmt.Println("Submitted tx signature: ", sig.String()) + fmt.Println("submitted tx signature:", sig.String()) } diff --git a/rpc/examples/simulateTransaction/simulateTransaction.go b/rpc/examples/simulateTransaction/simulateTransaction.go index 203025844..fefdc051b 100644 --- a/rpc/examples/simulateTransaction/simulateTransaction.go +++ b/rpc/examples/simulateTransaction/simulateTransaction.go @@ -14,6 +14,68 @@ package main +import ( + "context" + "encoding/json" + "fmt" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/programs/system" + "github.com/gagliardetto/solana-go/rpc" +) + +// This example simulates a SOL transfer on mainnet without submitting it, +// without needing any private key, and without funding any wallet. +// +// The trick: SimulateTransactionWithOpts lets us disable signature +// verification (SigVerify=false) and substitute a recent blockhash +// (ReplaceRecentBlockhash=true), so we only need a payer that already +// has SOL on-chain. Here we use a well-known, publicly documented +// funded address (the Binance-2 hot wallet) purely as the fee payer +// for the simulation. +// +// Swap the payer for any address that has SOL if you want to simulate +// against a different balance. func main() { + ctx := context.Background() + client := rpc.New(rpc.MainNetBeta_RPC) + + payer := solana.MustPublicKeyFromBase58("9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM") + recipient := solana.NewWallet().PublicKey() + + recent, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) + if err != nil { + panic(fmt.Errorf("get blockhash: %w", err)) + } + + tx, err := solana.NewTransaction( + []solana.Instruction{ + system.NewTransferInstruction( + solana.LAMPORTS_PER_SOL/1000, // 0.001 SOL + payer, + recipient, + ).Build(), + }, + recent.Value.Blockhash, + solana.TransactionPayer(payer), + ) + if err != nil { + panic(fmt.Errorf("build tx: %w", err)) + } + + // Placeholder signature slot; SigVerify=false so the contents are not checked. + tx.Signatures = []solana.Signature{{}} + + replace := true + out, err := client.SimulateTransactionWithOpts(ctx, tx, &rpc.SimulateTransactionOpts{ + SigVerify: false, + ReplaceRecentBlockhash: replace, + Commitment: rpc.CommitmentProcessed, + }) + if err != nil { + panic(fmt.Errorf("simulate: %w", err)) + } + pretty, _ := json.MarshalIndent(out.Value, "", " ") + fmt.Println(string(pretty)) }