Upgrade to latest RPC spec

This commit is contained in:
gagliardetto 2022-02-16 17:05:39 +01:00
parent 259ffea731
commit 8aa9b205c6
29 changed files with 812 additions and 150 deletions

141
README.md
View File

@ -74,7 +74,7 @@ More contracts to come.
There is currently **no stable release**. The SDK is actively developed and latest is `v1.2.1` which is an `alpha` release.
The RPC and WS client implementation is based on [this RPC spec](https://github.com/solana-labs/solana/blob/dff9c88193da142693cabebfcd3bf68fa8e8b873/docs/src/developing/clients/jsonrpc-api.md).
The RPC and WS client implementation is based on [this RPC spec](https://github.com/solana-labs/solana/blob/c2435363f39723cef59b91322f3b6a815008af29/docs/src/developing/clients/jsonrpc-api.md).
Note
----
@ -645,19 +645,22 @@ func main() {
- [GetBlocks](#index--rpc--getblocks)
- [GetBlocksWithLimit](#index--rpc--getblockswithlimit)
- [GetClusterNodes](#index--rpc--getclusternodes)
- [GetConfirmedBlock](#index--rpc--getconfirmedblock)
- [GetConfirmedBlocks](#index--rpc--getconfirmedblocks)
- [GetConfirmedBlocksWithLimit](#index--rpc--getconfirmedblockswithlimit)
- [GetConfirmedSignaturesForAddress2](#index--rpc--getconfirmedsignaturesforaddress2)
- [GetConfirmedTransaction](#index--rpc--getconfirmedtransaction)
- [GetConfirmedBlock](#index--rpc--getconfirmedblock) **DEPRECATED: Please use [GetBlock](#index--rpc--getblock) instead** (This method is expected to be removed in **solana-core v2.0**)
- [GetConfirmedBlocks](#index--rpc--getconfirmedblocks) **DEPRECATED: Please use [GetBlocks](#index--rpc--getblocks) instead** (This method is expected to be removed in **solana-core v2.0**)
- [GetConfirmedBlocksWithLimit](#index--rpc--getconfirmedblockswithlimit) **DEPRECATED: Please use [GetBlocksWithLimit](#index--rpc--getblockswithlimit) instead** (This method is expected to be removed in **solana-core v2.0**)
- [GetConfirmedSignaturesForAddress2](#index--rpc--getconfirmedsignaturesforaddress2) **DEPRECATED: Please use [GetSignaturesForAddress](#index--rpc--getsignaturesforaddress) instead** (This method is expected to be removed in **solana-core v2.0**)
- [GetConfirmedTransaction](#index--rpc--getconfirmedtransaction) **DEPRECATED: Please use [GetTransaction](#index--rpc--gettransaction) instead** (This method is expected to be removed in **solana-core v2.0**)
- [GetEpochInfo](#index--rpc--getepochinfo)
- [GetEpochSchedule](#index--rpc--getepochschedule)
- [GetFeeCalculatorForBlockhash](#index--rpc--getfeecalculatorforblockhash)
- [GetFeeRateGovernor](#index--rpc--getfeerategovernor)
- [GetFees](#index--rpc--getfees)
- [GetFeeCalculatorForBlockhash](#index--rpc--getfeecalculatorforblockhash) **DEPRECATED: Please use [IsBlockhashValid](#index--rpc--isblockhashvalid) or [GetFeeForMessage](#index--rpc--getfeeformessage) instead** (This method is expected to be removed in **solana-core v2.0**)
- [GetFeeRateGovernor](#index--rpc--getfeerategovernor) **DEPRECATED**
- [GetFees](#index--rpc--getfees) **DEPRECATED: Please use [GetFeeForMessage](#index--rpc--getfeeformessage) instead** (This method is expected to be removed in **solana-core v2.0**)
- [GetFeeForMessage](#index--rpc--getfeeformessage)
- [GetFirstAvailableBlock](#index--rpc--getfirstavailableblock)
- [GetGenesisHash](#index--rpc--getgenesishash)
- [GetHealth](#index--rpc--gethealth)
- [GetHighestSnapshotSlot](#index--rpc--gethighestsnapshotslot)
- [GetLatestBlockhash](#index--rpc--getlatestblockhash)
- [GetIdentity](#index--rpc--getidentity)
- [GetInflationGovernor](#index--rpc--getinflationgovernor)
- [GetInflationRate](#index--rpc--getinflationrate)
@ -669,14 +672,14 @@ func main() {
- [GetMinimumBalanceForRentExemption](#index--rpc--getminimumbalanceforrentexemption)
- [GetMultipleAccounts](#index--rpc--getmultipleaccounts)
- [GetProgramAccounts](#index--rpc--getprogramaccounts)
- [GetRecentBlockhash](#index--rpc--getrecentblockhash)
- [GetRecentBlockhash](#index--rpc--getrecentblockhash) **DEPRECATED: Please use [GetFeeForMessage](#index--rpc--getfeeformessage) instead** (This method is expected to be removed in **solana-core v2.0**)
- [GetRecentPerformanceSamples](#index--rpc--getrecentperformancesamples)
- [GetSignatureStatuses](#index--rpc--getsignaturestatuses)
- [GetSignaturesForAddress](#index--rpc--getsignaturesforaddress)
- [GetSlot](#index--rpc--getslot)
- [GetSlotLeader](#index--rpc--getslotleader)
- [GetSlotLeaders](#index--rpc--getslotleaders)
- [GetSnapshotSlot](#index--rpc--getsnapshotslot)
- [GetSnapshotSlot](#index--rpc--getsnapshotslot) **DEPRECATED: Please use [GetHighestSnapshotSlot](#index--rpc--gethighestsnapshotslot) instead** (This method is expected to be removed in **solana-core v2.0**)
- [GetStakeActivation](#index--rpc--getstakeactivation)
- [GetSupply](#index--rpc--getsupply)
- [GetTokenAccountBalance](#index--rpc--gettokenaccountbalance)
@ -688,6 +691,7 @@ func main() {
- [GetTransactionCount](#index--rpc--gettransactioncount)
- [GetVersion](#index--rpc--getversion)
- [GetVoteAccounts](#index--rpc--getvoteaccounts)
- [IsBlockhashValid](#index--rpc--isblockhashvalid)
- [MinimumLedgerSlot](#index--rpc--minimumledgerslot)
- [RequestAirdrop](#index--rpc--requestairdrop)
- [SendTransaction](#index--rpc--sendtransaction)
@ -1497,6 +1501,35 @@ func main() {
}
```
#### [index](#contents) > [RPC](#rpc-methods) > GetFeeForMessage
```go
package main
import (
"context"
"github.com/davecgh/go-spew/spew"
"github.com/gagliardetto/solana-go/rpc"
)
func main() {
endpoint := rpc.TestNet_RPC
client := rpc.New(endpoint)
example, err := client.GetFeeForMessage(
context.Background(),
"AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA",
rpc.CommitmentProcessed,
)
if err != nil {
panic(err)
}
spew.Dump(example)
}
```
#### [index](#contents) > [RPC](#rpc-methods) > GetFirstAvailableBlock
```go
@ -1576,6 +1609,59 @@ func main() {
}
```
#### [index](#contents) > [RPC](#rpc-methods) > GetHighestSnapshotSlot
```go
package main
import (
"context"
"github.com/davecgh/go-spew/spew"
"github.com/gagliardetto/solana-go/rpc"
)
func main() {
endpoint := rpc.TestNet_RPC
client := rpc.New(endpoint)
example, err := client.GetHighestSnapshotSlot(
context.Background(),
)
if err != nil {
panic(err)
}
spew.Dump(example)
}
```
#### [index](#contents) > [RPC](#rpc-methods) > GetLatestBlockhash
```go
package main
import (
"context"
"github.com/davecgh/go-spew/spew"
"github.com/gagliardetto/solana-go/rpc"
)
func main() {
endpoint := rpc.TestNet_RPC
client := rpc.New(endpoint)
example, err := client.GetLatestBlockhash(
context.Background(),
rpc.CommitmentFinalized,
)
if err != nil {
panic(err)
}
spew.Dump(example)
}
```
#### [index](#contents) > [RPC](#rpc-methods) > GetIdentity
```go
@ -2507,6 +2593,39 @@ func main() {
}
```
#### [index](#contents) > [RPC](#rpc-methods) > IsBlockhashValid
```go
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.MainNetBeta_RPC
client := rpc.New(endpoint)
blockHash := solana.MustHashFromBase58("J7rBdM6AecPDEZp8aPq5iPSNKVkU5Q76F3oAV4eW5wsW")
out, err := client.IsBlockhashValid(
context.TODO(),
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)
}
```
#### [index](#contents) > [RPC](#rpc-methods) > MinimumLedgerSlot
```go

View File

@ -75,7 +75,7 @@ var getTransactionsCmd = &cobra.Command{
return fmt.Errorf("unable to get confirmed transaction with signature %q: %s", cs.Signature, ct.Meta.Err)
}
_, err = ct.Transaction.EncodeTree(text.NewTreeEncoder(os.Stdout, text.Bold("INSTRUCTIONS")))
_, err = ct.MustGetTransaction().EncodeTree(text.NewTreeEncoder(os.Stdout, text.Bold("INSTRUCTIONS")))
if err != nil {
panic(err)
}

12
keys.go
View File

@ -208,13 +208,13 @@ func (p PublicKey) Short(n int) string {
}
func formatShortPubkey(n int, pubkey PublicKey) string {
if n > 10 {
n = 10
}
if n < 3 {
n = 3
}
str := pubkey.String()
if n > (len(str)/2)-1 {
n = (len(str) / 2) - 1
}
if n < 2 {
n = 2
}
return str[:n] + "..." + str[len(str)-n:]
}

View File

@ -18,6 +18,7 @@
package solana
import (
"encoding/base64"
"fmt"
bin "github.com/gagliardetto/binary"
@ -101,6 +102,11 @@ func (mx *Message) MarshalWithEncoder(encoder *bin.Encoder) error {
return encoder.WriteBytes(out, false)
}
func (mx Message) ToBase64() string {
out, _ := mx.MarshalBinary()
return base64.StdEncoding.EncodeToString(out)
}
func (mx *Message) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) {
{
mx.Header.NumRequiredSignatures, err = decoder.ReadUint8()
@ -158,7 +164,6 @@ func (mx *Message) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) {
if err != nil {
return err
}
compInst.AccountCount = bin.Varuint16(numAccounts)
for i := 0; i < numAccounts; i++ {
accountIndex, err := decoder.ReadUint8()
if err != nil {
@ -176,7 +181,6 @@ func (mx *Message) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) {
if err != nil {
return err
}
compInst.DataLength = bin.Varuint16(dataLen)
compInst.Data = Base58(dataBytes)
}
mx.Instructions = append(mx.Instructions, compInst)

View File

@ -157,7 +157,7 @@ func TestClient_GetConfirmedSignaturesForAddress2(t *testing.T) {
}
func TestClient_GetConfirmedTransaction(t *testing.T) {
server, closer := mockJSONRPC(t, stdjson.RawMessage(`{"jsonrpc":"2.0","result":{"meta":{"err":null,"fee":5000,"innerInstructions":[],"logMessages":[],"postBalances":[],"preBalances":[],"status":{"Ok":null}},"slot":48291656,"transaction":{"message":{"accountKeys":["GKu2xfGZopa8C9K11wduQWgP4W4H7EEcaNdsUb7mxhyr"],"header":{"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":3,"numRequiredSignatures":1},"instructions":[{"accounts":[1,2,3,0],"data":"3yZe7d","programIdIndex":4}],"recentBlockhash":"uoEAQCWCKjV9ecsBvngctJ7upNBZX7hpN4SfdR6TaUz"},"signatures":["53hoZ98EsCMA6L63GWM65M3Bd3WqA4LxD8bcJkbKoKWhbJFqX9M1WZ4fSjt8bYyZn21NwNnV2A25zirBni9Qk6LR"]}},"id":0}`))
server, closer := mockJSONRPC(t, stdjson.RawMessage(`{"jsonrpc":"2.0","result":{"meta":{"err":null,"fee":5000,"innerInstructions":[],"logMessages":[],"postBalances":[],"preBalances":[],"status":{"Ok":null}},"slot":48291656,"transaction":["AcpmPgtaSCzI2vuOUXduljmnoc1zIqMETzEJ8zmF+\/yy2AABHMNonpVleveVw4a4Fo7LUDWtxo2FkyzFr2x9DQIBAAMB47aX3y9Dfp+\/ycSDXt0Ph3TfZQBqPSXMQYToKtUtr5kNhniVeV7Las6qkeV8d0rksxV9de0GF7p4nzQUVEnrWwEEBAECAwAEdGVzdA==","base64"]},"id":0}`))
defer closer()
client := New(server.URL)
@ -183,29 +183,28 @@ func TestClient_GetConfirmedTransaction(t *testing.T) {
signature, err := solana.SignatureFromBase58("53hoZ98EsCMA6L63GWM65M3Bd3WqA4LxD8bcJkbKoKWhbJFqX9M1WZ4fSjt8bYyZn21NwNnV2A25zirBni9Qk6LR")
require.NoError(t, err)
assert.Equal(t, &TransactionWithMeta{
Transaction: &solana.Transaction{
Message: solana.Message{
Header: solana.MessageHeader{NumRequiredSignatures: 1, NumReadonlySignedAccounts: 0, NumReadonlyUnsignedAccounts: 3},
RecentBlockhash: solana.MustHashFromBase58("uoEAQCWCKjV9ecsBvngctJ7upNBZX7hpN4SfdR6TaUz"),
AccountKeys: []solana.PublicKey{solana.MustPublicKeyFromBase58("GKu2xfGZopa8C9K11wduQWgP4W4H7EEcaNdsUb7mxhyr")},
Instructions: []solana.CompiledInstruction{
{Accounts: []uint16{1, 2, 3, 0}, Data: solana.Base58([]byte{0x74, 0x65, 0x73, 0x74}), ProgramIDIndex: 4},
},
},
Signatures: []solana.Signature{signature},
assert.Equal(t, &TransactionMeta{
Fee: 5000,
PreBalances: []uint64{},
PostBalances: []uint64{},
InnerInstructions: []InnerInstruction{},
LogMessages: []string{},
Status: DeprecatedTransactionMetaStatus{
"Ok": nil,
},
Meta: &TransactionMeta{
Fee: 5000,
PreBalances: []uint64{},
PostBalances: []uint64{},
InnerInstructions: []InnerInstruction{},
LogMessages: []string{},
Status: DeprecatedTransactionMetaStatus{
"Ok": nil,
}, out.Meta)
assert.Equal(t, &solana.Transaction{
Message: solana.Message{
Header: solana.MessageHeader{NumRequiredSignatures: 1, NumReadonlySignedAccounts: 0, NumReadonlyUnsignedAccounts: 3},
RecentBlockhash: solana.MustHashFromBase58("uoEAQCWCKjV9ecsBvngctJ7upNBZX7hpN4SfdR6TaUz"),
AccountKeys: []solana.PublicKey{solana.MustPublicKeyFromBase58("GKu2xfGZopa8C9K11wduQWgP4W4H7EEcaNdsUb7mxhyr")},
Instructions: []solana.CompiledInstruction{
{Accounts: []uint16{1, 2, 3, 0}, Data: solana.Base58([]byte{0x74, 0x65, 0x73, 0x74}), ProgramIDIndex: 4},
},
},
}, out)
Signatures: []solana.Signature{signature},
}, out.MustGetTransaction())
}
// mustAnyToJSON marshals the provided variable
@ -323,7 +322,7 @@ func TestClient_GetBalance(t *testing.T) {
}
func TestClient_GetBlock(t *testing.T) {
responseBody := `{"blockHeight":69213636,"blockTime":1625227950,"blockhash":"5M77sHdwzH6rckuQwF8HL1w52n7hjrh4GVTFiF6T8QyB","parentSlot":83987983,"previousBlockhash":"Aq9jSXe1jRzfiaBcRFLe4wm7j499vWVEeFQrq5nnXfZN","rewards":[{"lamports":1595000,"postBalance":482032983798,"pubkey":"5rL3AaidKJa4ChSV3ys1SvpDg9L4amKiwYayGR5oL3dq","rewardType":"Fee"}],"transactions":[{"meta":{"err":null,"fee":5000,"innerInstructions":[],"logMessages":["Program Vote111111111111111111111111111111111111111 invoke [1]","Program Vote111111111111111111111111111111111111111 success"],"postBalances":[441866063495,40905918933763,1,1,1],"postTokenBalances":[],"preBalances":[441866068495,40905918933763,1,1,1],"preTokenBalances":[],"rewards":[],"status":{"Ok":null}},"transaction":{"message":{"accountKeys":["EVd8FFVB54svYdZdG6hH4F4hTbqre5mpQ7XyF5rKUmes","72miaovmbPqccdbAA861r2uxwB5yL1sMjrgbCnc4JfVT","SysvarS1otHashes111111111111111111111111111","SysvarC1ock11111111111111111111111111111111","Vote111111111111111111111111111111111111111"],"header":{"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":3,"numRequiredSignatures":1},"instructions":[{"accounts":[1,2,3,0],"data":"3yZe7d","programIdIndex":4}],"recentBlockhash":"CnyzpJmBydX1X2FyXXzsPFc5WPT9UFdLVkEhnvW33at"},"signatures":["D8emaP3CaepSGigD3TCrev7j67yPLMi82qfzTb9iZYPxHcCmm6sQBKTU4bzAee4445zbnbWduVAZ87WfbWbXoAU"]}},{"meta":{"err":null,"fee":5000,"innerInstructions":[],"logMessages":["Program Vote111111111111111111111111111111111111111 invoke [1]","Program Vote111111111111111111111111111111111111111 success"],"postBalances":[334759887662,151357332545078,1,1,1],"postTokenBalances":[],"preBalances":[334759892662,151357332545078,1,1,1],"preTokenBalances":[],"rewards":[],"status":{"Ok":null}},"transaction":{"message":{"accountKeys":["5rxRt2GVpSUFJTqQ5E4urqJCDbcBPakb46t6URyxQ5Za","HdzdTTjrmRLYVRy3umzZX4NcUmGTHu6hvYLQN2jGJo53","SysvarS1otHashes111111111111111111111111111","SysvarC1ock11111111111111111111111111111111","Vote111111111111111111111111111111111111111"],"header":{"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":3,"numRequiredSignatures":1},"instructions":[{"accounts":[1,2,3,0],"data":"3yZe7d","programIdIndex":4}],"recentBlockhash":"BL8oo42yoSTKUYpbXR3kdxeV5X1P8JUUZBZaeBL8K6G"},"signatures":["xvrkWXwj5h9SsJvboPMtn4jbR6XNmnHYp4MAikKFwdtkpwMxceFZ46QRzeyGUqm5P1kmCagdUubr3aPdxo7vzyq"]}}]}`
responseBody := `{"blockHeight":69213636,"blockTime":1625227950,"blockhash":"5M77sHdwzH6rckuQwF8HL1w52n7hjrh4GVTFiF6T8QyB","parentSlot":83987983,"previousBlockhash":"Aq9jSXe1jRzfiaBcRFLe4wm7j499vWVEeFQrq5nnXfZN","rewards":[{"lamports":1595000,"postBalance":482032983798,"pubkey":"5rL3AaidKJa4ChSV3ys1SvpDg9L4amKiwYayGR5oL3dq","rewardType":"Fee"}],"transactions":[{"meta":{"err":null,"fee":5000,"innerInstructions":[],"logMessages":["Program Vote111111111111111111111111111111111111111 invoke [1]","Program Vote111111111111111111111111111111111111111 success"],"postBalances":[441866063495,40905918933763,1,1,1],"postTokenBalances":[],"preBalances":[441866068495,40905918933763,1,1,1],"preTokenBalances":[],"rewards":[],"status":{"Ok":null}},"transaction":["AQp2TH1spzjBAVM3alvnpaePFx3YEo9dvRglDuSChZUoTMD\/\/2h0HY5+89LJjCdiGJ7Ph3+Fyvbeiz1uJF8gxw0BAAMFyH0KDkXtjL1xebUYflZxYGlpV+LvjazzZCb\/mF2T67xZmkOUM\/A0iDSEkFzD5m4Ol82vsojigvqxrmp7Z1vrQgan1RcZLwqvxvJl4\/t3zHragsUp0L47E24tAFUgAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAHYUgdNXR0u3xNdiTr072z2DVec9EQQ\/wNo1OAAAAAAAMFYbeqrsxJ9\/vZxtOaFi3rT2w9RF5Xi4jsyu61f3t1AQQEAQIDAAR0ZXN0","base64"]},{"meta":{"err":null,"fee":5000,"innerInstructions":[],"logMessages":["Program Vote111111111111111111111111111111111111111 invoke [1]","Program Vote111111111111111111111111111111111111111 success"],"postBalances":[334759887662,151357332545078,1,1,1],"postTokenBalances":[],"preBalances":[334759892662,151357332545078,1,1,1],"preTokenBalances":[],"rewards":[],"status":{"Ok":null}},"transaction":["ATA7DkBatbe2JB43QV+QRj2yoXSMXXttYFggDxZYOBfsRyYuGtzrbUevivclchxVccRIPlRP9PtS\/9NPXlwmhwwBAAMFSDrhjiNPuNqc4BWwitZz7xJ2NIXtv6XZtwtEOmgLj3n3NQ+OONLFlsu0LoUBSDsp40i9jOjZJBsliMtvTfdV+gan1RcZLwqvxvJl4\/t3zHragsUp0L47E24tAFUgAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAHYUgdNXR0u3xNdiTr072z2DVec9EQQ\/wNo1OAAAAAAAKlcZMqS\/Oh0v+kOq2Ipg73NqbvKBRGQJDK8\/01K+MBAQQEAQIDAAR0ZXN0","base64"]}]}`
server, closer := mockJSONRPC(t, stdjson.RawMessage(wrapIntoRPC(responseBody)))
defer closer()
@ -344,7 +343,7 @@ func TestClient_GetBlock(t *testing.T) {
"params": []interface{}{
float64(block),
map[string]interface{}{
"encoding": string(solana.EncodingJSON),
"encoding": string(solana.EncodingBase64),
},
},
},
@ -354,6 +353,66 @@ func TestClient_GetBlock(t *testing.T) {
// TODO:
// - test also when requesting only signatures
tx1 := &solana.Transaction{
Message: solana.Message{
AccountKeys: []solana.PublicKey{
solana.MustPublicKeyFromBase58("EVd8FFVB54svYdZdG6hH4F4hTbqre5mpQ7XyF5rKUmes"),
solana.MustPublicKeyFromBase58("72miaovmbPqccdbAA861r2uxwB5yL1sMjrgbCnc4JfVT"),
solana.MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111"),
solana.MustPublicKeyFromBase58("SysvarC1ock11111111111111111111111111111111"),
solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111"),
},
Header: solana.MessageHeader{
NumReadonlySignedAccounts: 0,
NumReadonlyUnsignedAccounts: 3,
NumRequiredSignatures: 1,
},
Instructions: []solana.CompiledInstruction{
{
Accounts: []uint16{1, 2, 3, 0},
Data: solana.Base58([]byte{0x74, 0x65, 0x73, 0x74}),
ProgramIDIndex: 4,
},
},
RecentBlockhash: solana.MustHashFromBase58("CnyzpJmBydX1X2FyXXzsPFc5WPT9UFdLVkEhnvW33at"),
},
Signatures: []solana.Signature{
solana.MustSignatureFromBase58("D8emaP3CaepSGigD3TCrev7j67yPLMi82qfzTb9iZYPxHcCmm6sQBKTU4bzAee4445zbnbWduVAZ87WfbWbXoAU"),
},
}
tx1Data, err := DataBytesOrJSONFromBase64(tx1.MustToBase64())
require.NoError(t, err)
tx2 := &solana.Transaction{
Message: solana.Message{
AccountKeys: []solana.PublicKey{
solana.MustPublicKeyFromBase58("5rxRt2GVpSUFJTqQ5E4urqJCDbcBPakb46t6URyxQ5Za"),
solana.MustPublicKeyFromBase58("HdzdTTjrmRLYVRy3umzZX4NcUmGTHu6hvYLQN2jGJo53"),
solana.MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111"),
solana.MustPublicKeyFromBase58("SysvarC1ock11111111111111111111111111111111"),
solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111"),
},
Header: solana.MessageHeader{
NumReadonlySignedAccounts: 0,
NumReadonlyUnsignedAccounts: 3,
NumRequiredSignatures: 1,
},
Instructions: []solana.CompiledInstruction{
{
Accounts: []uint16{1, 2, 3, 0},
Data: solana.Base58([]byte{0x74, 0x65, 0x73, 0x74}),
ProgramIDIndex: 4,
},
},
RecentBlockhash: solana.MustHashFromBase58("BL8oo42yoSTKUYpbXR3kdxeV5X1P8JUUZBZaeBL8K6G"),
},
Signatures: []solana.Signature{
solana.MustSignatureFromBase58("xvrkWXwj5h9SsJvboPMtn4jbR6XNmnHYp4MAikKFwdtkpwMxceFZ46QRzeyGUqm5P1kmCagdUubr3aPdxo7vzyq"),
},
}
tx2Data, err := DataBytesOrJSONFromBase64(tx2.MustToBase64())
require.NoError(t, err)
blockTime := solana.UnixTimeSeconds(1625227950)
assert.Equal(t,
&GetBlockResult{
@ -392,33 +451,7 @@ func TestClient_GetBlock(t *testing.T) {
"Ok": nil,
},
},
Transaction: &solana.Transaction{
Message: solana.Message{
AccountKeys: []solana.PublicKey{
solana.MustPublicKeyFromBase58("EVd8FFVB54svYdZdG6hH4F4hTbqre5mpQ7XyF5rKUmes"),
solana.MustPublicKeyFromBase58("72miaovmbPqccdbAA861r2uxwB5yL1sMjrgbCnc4JfVT"),
solana.MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111"),
solana.MustPublicKeyFromBase58("SysvarC1ock11111111111111111111111111111111"),
solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111"),
},
Header: solana.MessageHeader{
NumReadonlySignedAccounts: 0,
NumReadonlyUnsignedAccounts: 3,
NumRequiredSignatures: 1,
},
Instructions: []solana.CompiledInstruction{
{
Accounts: []uint16{1, 2, 3, 0},
Data: solana.Base58([]byte{0x74, 0x65, 0x73, 0x74}),
ProgramIDIndex: 4,
},
},
RecentBlockhash: solana.MustHashFromBase58("CnyzpJmBydX1X2FyXXzsPFc5WPT9UFdLVkEhnvW33at"),
},
Signatures: []solana.Signature{
solana.MustSignatureFromBase58("D8emaP3CaepSGigD3TCrev7j67yPLMi82qfzTb9iZYPxHcCmm6sQBKTU4bzAee4445zbnbWduVAZ87WfbWbXoAU"),
},
},
Transaction: tx1Data,
},
{
Meta: &TransactionMeta{
@ -441,33 +474,7 @@ func TestClient_GetBlock(t *testing.T) {
"Ok": nil,
},
},
Transaction: &solana.Transaction{
Message: solana.Message{
AccountKeys: []solana.PublicKey{
solana.MustPublicKeyFromBase58("5rxRt2GVpSUFJTqQ5E4urqJCDbcBPakb46t6URyxQ5Za"),
solana.MustPublicKeyFromBase58("HdzdTTjrmRLYVRy3umzZX4NcUmGTHu6hvYLQN2jGJo53"),
solana.MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111"),
solana.MustPublicKeyFromBase58("SysvarC1ock11111111111111111111111111111111"),
solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111"),
},
Header: solana.MessageHeader{
NumReadonlySignedAccounts: 0,
NumReadonlyUnsignedAccounts: 3,
NumRequiredSignatures: 1,
},
Instructions: []solana.CompiledInstruction{
{
Accounts: []uint16{1, 2, 3, 0},
Data: solana.Base58([]byte{0x74, 0x65, 0x73, 0x74}),
ProgramIDIndex: 4,
},
},
RecentBlockhash: solana.MustHashFromBase58("BL8oo42yoSTKUYpbXR3kdxeV5X1P8JUUZBZaeBL8K6G"),
},
Signatures: []solana.Signature{
solana.MustSignatureFromBase58("xvrkWXwj5h9SsJvboPMtn4jbR6XNmnHYp4MAikKFwdtkpwMxceFZ46QRzeyGUqm5P1kmCagdUubr3aPdxo7vzyq"),
},
},
Transaction: tx2Data,
},
},
}, out)
@ -501,7 +508,7 @@ func TestClient_GetBlockWithOpts(t *testing.T) {
"params": []interface{}{
float64(block),
map[string]interface{}{
"encoding": string(solana.EncodingJSON),
"encoding": string(solana.EncodingBase64),
"transactionDetails": string(TransactionDetailsSignatures),
"rewards": rewards,
"commitment": string(CommitmentMax),
@ -799,7 +806,7 @@ func TestClient_GetBlockTime(t *testing.T) {
}
func TestClient_GetClusterNodes(t *testing.T) {
responseBody := `[{"featureSet":743297851,"gossip":"162.55.111.250:8001","pubkey":"DMeohMfD3JzmYZA34jL9iiTXp5N7tpAR3rAoXMygdH3U","rpc":null,"shredVersion":18122,"tpu":"162.55.111.250:8004","version":"1.7.3"},{"featureSet":743297851,"gossip":"136.243.131.82:8000","pubkey":"59TSbYfnbb4zx4xf54ApjE8fJRhwzTiSjh9vdHfgyg1U","rpc":"136.243.131.82:8899","shredVersion":18122,"tpu":"136.243.131.82:8003","version":"1.7.3"},{"featureSet":743297851,"gossip":"135.181.114.15:8001","pubkey":"7vu7Q2d4uu9V4xnySHXieeyWvoNh37321kqTd2ATuoj6","rpc":null,"shredVersion":18122,"tpu":null,"version":"1.7.3"}]`
responseBody := `[{"featureSet":743297851,"gossip":"162.55.111.250:8001","pubkey":"DMeohMfD3JzmYZA34jL9iiTXp5N7tpAR3rAoXMygdH3U","rpc":"135.181.114.15:8005","shredVersion":18122,"tpu":"162.55.111.250:8004","version":"1.7.3"},{"featureSet":743297851,"gossip":"136.243.131.82:8000","pubkey":"59TSbYfnbb4zx4xf54ApjE8fJRhwzTiSjh9vdHfgyg1U","rpc":"136.243.131.82:8899","shredVersion":18122,"tpu":"136.243.131.82:8003","version":"1.7.3"},{"featureSet":743297851,"gossip":"135.181.114.15:8001","pubkey":"7vu7Q2d4uu9V4xnySHXieeyWvoNh37321kqTd2ATuoj6","rpc":"135.181.114.15:8005","shredVersion":18122,"tpu":"135.181.114.15:8006","version":"1.7.3"}]`
server, closer := mockJSONRPC(t, stdjson.RawMessage(wrapIntoRPC(responseBody)))
defer closer()
client := New(server.URL)
@ -2040,7 +2047,7 @@ func TestClient_GetTransaction(t *testing.T) {
tx := "KBVcTWwgEhVzwywtunhAXRKjXYYEdPcSCpuEkg484tiE3dFGzHDu9LKKH23uBMdfYt3JCPHeaVeDTZWecboyTrd"
opts := GetTransactionOpts{
Encoding: solana.EncodingJSON,
Encoding: solana.EncodingBase64,
Commitment: CommitmentMax,
}
out, err := client.GetTransaction(
@ -2058,7 +2065,7 @@ func TestClient_GetTransaction(t *testing.T) {
"params": []interface{}{
tx,
map[string]interface{}{
"encoding": string(solana.EncodingJSON),
"encoding": string(solana.EncodingBase64),
"commitment": string(CommitmentMax),
},
},
@ -2594,3 +2601,98 @@ func TestClient_IsBlockhashValid(t *testing.T) {
func TestClient_SimulateTransaction(t *testing.T) {
// TODO
}
func TestClient_GetFeeForMessage(t *testing.T) {
responseBody := `{"context":{"slot":5068},"value":5000}`
server, closer := mockJSONRPC(t, stdjson.RawMessage(wrapIntoRPC(responseBody)))
defer closer()
client := New(server.URL)
out, err := client.GetFeeForMessage(
context.Background(),
"AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA",
CommitmentProcessed,
)
require.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"id": float64(0),
"jsonrpc": "2.0",
"method": "getFeeForMessage",
"params": []interface{}{
"AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA",
map[string]interface{}{
"commitment": string(CommitmentProcessed),
},
},
},
server.RequestBody(t),
)
expected := mustJSONToInterface([]byte(responseBody))
got := mustJSONToInterface(mustAnyToJSON(out))
assert.Equal(t, expected, got, "both deserialized values must be equal")
}
func TestClient_GetHighestSnapshotSlot(t *testing.T) {
responseBody := `{"full":100,"incremental":110}`
server, closer := mockJSONRPC(t, stdjson.RawMessage(wrapIntoRPC(responseBody)))
defer closer()
client := New(server.URL)
out, err := client.GetHighestSnapshotSlot(
context.Background(),
)
require.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"id": float64(0),
"jsonrpc": "2.0",
"method": "getHighestSnapshotSlot",
},
server.RequestBody(t),
)
expected := mustJSONToInterface([]byte(responseBody))
got := mustJSONToInterface(mustAnyToJSON(out))
assert.Equal(t, expected, got, "both deserialized values must be equal")
}
func TestClient_GetLatestBlockhash(t *testing.T) {
responseBody := `{"context":{"slot":2792},"value":{"blockhash":"EkSnNWid2cvwEVnVx9aBqawnmiCNiDgp3gUdkDPTKN1N","lastValidBlockHeight":3090}}`
server, closer := mockJSONRPC(t, stdjson.RawMessage(wrapIntoRPC(responseBody)))
defer closer()
client := New(server.URL)
out, err := client.GetLatestBlockhash(
context.Background(),
CommitmentProcessed,
)
require.NoError(t, err)
assert.Equal(t,
map[string]interface{}{
"id": float64(0),
"jsonrpc": "2.0",
"method": "getLatestBlockhash",
"params": []interface{}{
map[string]interface{}{
"commitment": string(CommitmentProcessed),
},
},
},
server.RequestBody(t),
)
expected := mustJSONToInterface([]byte(responseBody))
got := mustJSONToInterface(mustAnyToJSON(out))
assert.Equal(t, expected, got, "both deserialized values must be equal")
}

View File

@ -200,10 +200,11 @@ func (cl *Client) GetConfirmedTransactionWithOpts(
if !solana.IsAnyOfEncodingType(
opts.Encoding,
// Valid encodings:
solana.EncodingJSON,
// solana.EncodingJSON, // TODO
// solana.EncodingJSONParsed, // TODO
solana.EncodingBase58,
solana.EncodingBase64,
solana.EncodingBase64Zstd,
) {
return nil, fmt.Errorf("provided encoding is not supported: %s", opts.Encoding)
}

View File

@ -0,0 +1,37 @@
// Copyright 2022 github.com/gagliardetto
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"github.com/davecgh/go-spew/spew"
"github.com/gagliardetto/solana-go/rpc"
)
func main() {
endpoint := rpc.TestNet_RPC
client := rpc.New(endpoint)
example, err := client.GetFeeForMessage(
context.Background(),
"AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA",
rpc.CommitmentProcessed,
)
if err != nil {
panic(err)
}
spew.Dump(example)
}

View File

@ -0,0 +1,35 @@
// Copyright 2022 github.com/gagliardetto
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"github.com/davecgh/go-spew/spew"
"github.com/gagliardetto/solana-go/rpc"
)
func main() {
endpoint := rpc.TestNet_RPC
client := rpc.New(endpoint)
example, err := client.GetHighestSnapshotSlot(
context.Background(),
)
if err != nil {
panic(err)
}
spew.Dump(example)
}

View File

@ -0,0 +1,36 @@
// Copyright 2022 github.com/gagliardetto
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"github.com/davecgh/go-spew/spew"
"github.com/gagliardetto/solana-go/rpc"
)
func main() {
endpoint := rpc.TestNet_RPC
client := rpc.New(endpoint)
example, err := client.GetLatestBlockhash(
context.Background(),
rpc.CommitmentFinalized,
)
if err != nil {
panic(err)
}
spew.Dump(example)
}

View File

@ -17,6 +17,7 @@ package main
import (
"context"
"fmt"
"github.com/davecgh/go-spew/spew"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
@ -26,7 +27,7 @@ func main() {
endpoint := rpc.MainNetBeta_RPC
client := rpc.New(endpoint)
blockHash := solana.MustHashFromBase58("krakeNd6ednDPEXxHAmoBs1qKVM8kLg79PvWF2mhXV1")
blockHash := solana.MustHashFromBase58("J7rBdM6AecPDEZp8aPq5iPSNKVkU5Q76F3oAV4eW5wsW")
out, err := client.IsBlockhashValid(
context.TODO(),
blockHash,

View File

@ -60,9 +60,6 @@ type GetBlockOpts struct {
}
// GetBlock returns identity and transaction information about a confirmed block in the ledger.
//
// NEW: This method is only available in solana-core v1.7 or newer.
// Please use `getConfirmedBlock` for solana-core v1.6
func (cl *Client) GetBlock(
ctx context.Context,
slot uint64,
@ -85,7 +82,7 @@ func (cl *Client) GetBlockWithOpts(
) (out *GetBlockResult, err error) {
obj := M{
"encoding": solana.EncodingJSON,
"encoding": solana.EncodingBase64,
}
if opts != nil {
@ -102,10 +99,11 @@ func (cl *Client) GetBlockWithOpts(
if !solana.IsAnyOfEncodingType(
opts.Encoding,
// Valid encodings:
solana.EncodingJSON,
solana.EncodingJSONParsed,
// solana.EncodingJSON, // TODO
// solana.EncodingJSONParsed, // TODO
solana.EncodingBase58,
solana.EncodingBase64,
solana.EncodingBase64Zstd,
) {
return nil, fmt.Errorf("provided encoding is not supported: %s", opts.Encoding)
}

View File

@ -22,9 +22,6 @@ import (
// The result will be an array of u64 integers listing confirmed blocks
// between start_slot and either end_slot, if provided, or latest
// confirmed block, inclusive. Max range allowed is 500,000 slots.
//
// NEW: This method is only available in solana-core v1.7 or newer.
// Please use `getConfirmedBlocks` for solana-core v1.6.
func (cl *Client) GetBlocks(
ctx context.Context,
startSlot uint64,

View File

@ -21,9 +21,6 @@ import (
// GetBlocksWithLimit returns a list of confirmed blocks starting at the given slot.
// The result field will be an array of u64 integers listing
// confirmed blocks starting at startSlot for up to limit blocks, inclusive.
//
// NEW: This method is only available in solana-core v1.7 or newer.
// Please use getConfirmedBlocksWithLimit for solana-core v1.6
func (cl *Client) GetBlocksWithLimit(
ctx context.Context,
startSlot uint64,

View File

@ -30,24 +30,24 @@ type GetClusterNodesResult struct {
// Node public key.
Pubkey solana.PublicKey `json:"pubkey"`
// Gossip network address for the node.
Gossip *string `json:"gossip"`
// TPU network address for the node.
TPU *string `json:"tpu"`
// TODO: "" or nil ?
// Gossip network address for the node.
Gossip *string `json:"gossip,omitempty"`
// TPU network address for the node.
TPU *string `json:"tpu,omitempty"`
// JSON RPC network address for the node, or empty if the JSON RPC service is not enabled.
RPC *string `json:"rpc"`
RPC *string `json:"rpc,omitempty"`
// The software version of the node, or empty if the version information is not available.
Version *string `json:"version"`
Version *string `json:"version,omitempty"`
// TODO: what type is this?
// The unique identifier of the node's feature set.
FeatureSet int64 `json:"featureSet"`
FeatureSet uint32 `json:"featureSet,omitempty"`
// The shred version the node has been configured to use.
ShredVersion int64 `json:"shredVersion"`
ShredVersion uint16 `json:"shredVersion,omitempty"`
}

View File

@ -47,5 +47,5 @@ type GetEpochInfoResult struct {
// The number of slots in this epoch.
SlotsInEpoch uint64 `json:"slotsInEpoch"`
TransactionCount uint64 `json:"transactionCount"`
TransactionCount *uint64 `json:"transactionCount,omitempty"`
}

View File

@ -22,6 +22,8 @@ import (
// GetFeeCalculatorForBlockhash returns the fee calculator
// associated with the query blockhash, or null if the blockhash has expired.
//
// NOTE: DEPRECATED
func (cl *Client) GetFeeCalculatorForBlockhash(
ctx context.Context,
hash solana.Hash, // query blockhash

43
rpc/getFeeForMessage.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2022 github.com/gagliardetto
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpc
import (
"context"
)
// Get the fee the network will charge for a particular Message.
//
// **NEW**: This method is only available in solana-core v1.9 or newer. Please use
// `getFees` for solana-core v1.8.
func (cl *Client) GetFeeForMessage(
ctx context.Context,
message string, // Base-64 encoded Message
commitment CommitmentType, // optional
) (out *GetFeeForMessageResult, err error) {
params := []interface{}{message}
if commitment != "" {
params = append(params, M{"commitment": commitment})
}
err = cl.rpcClient.CallForInto(ctx, &out, "getFeeForMessage", params)
return
}
type GetFeeForMessageResult struct {
RPCContext
// Fee corresponding to the message at the specified blockhash.
Value *uint64 `json:"value"`
}

View File

@ -0,0 +1,35 @@
// Copyright 2021 github.com/gagliardetto
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpc
import (
"context"
)
// Returns the highest slot information that the node has snapshots for.
// This will find the highest full snapshot slot, and the highest incremental
// snapshot slot _based on_ the full snapshot slot, if there is one.
//
// **NEW: This method is only available in solana-core v1.9 or newer. Please use
// `getSnapshotSlot` for solana-core v1.8**
func (cl *Client) GetHighestSnapshotSlot(ctx context.Context) (out *GetHighestSnapshotSlotResult, err error) {
err = cl.rpcClient.CallForInto(ctx, &out, "getHighestSnapshotSlot", nil)
return
}
type GetHighestSnapshotSlotResult struct {
Full uint64 `json:"full"` // Highest full snapshot slot.
Incremental *uint64 `json:"incremental,omitempty"` // Highest incremental snapshot slot based on full.
}

View File

@ -28,7 +28,7 @@ type GetInflationRewardOpts struct {
Epoch *uint64
}
// GetInflationReward returns the inflation reward for a list of addresses for an epoch.
// GetInflationReward returns the inflation / staking reward for a list of addresses for an epoch.
func (cl *Client) GetInflationReward(
ctx context.Context,

50
rpc/getLatestBlockhash.go Normal file
View File

@ -0,0 +1,50 @@
// Copyright 2021 github.com/gagliardetto
// This file has been modified by github.com/gagliardetto
//
// Copyright 2020 dfuse Platform Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpc
import (
"context"
"github.com/gagliardetto/solana-go"
)
// Returns the latest blockhash.
//
// **NEW: This method is only available in solana-core v1.9 or newer. Please use
// `getRecentBlockhash` for solana-core v1.8**
func (cl *Client) GetLatestBlockhash(
ctx context.Context,
commitment CommitmentType, // optional
) (out *GetLatestBlockhashResult, err error) {
params := []interface{}{}
if commitment != "" {
params = append(params, M{"commitment": commitment})
}
err = cl.rpcClient.CallForInto(ctx, &out, "getLatestBlockhash", params)
return
}
type GetLatestBlockhashResult struct {
RPCContext
Value *LatestBlockhashResult `json:"value"`
}
type LatestBlockhashResult struct {
Blockhash solana.Hash `json:"blockhash"`
LastValidBlockHeight uint64 `json:"lastValidBlockHeight"` // Slot.
}

View File

@ -20,7 +20,7 @@ import (
"context"
)
// GetSlot returns the current slot the node is processing.
// GetSlot returns the slot that has reached the given or default commitment level.
func (cl *Client) GetSlot(
ctx context.Context,
commitment CommitmentType, // optional

View File

@ -47,7 +47,7 @@ func (cl *Client) GetSupplyWithOpts(
type GetSupplyOpts struct {
Commitment CommitmentType `json:"commitment,omitempty"`
ExcludeNonCirculatingAccountsList bool `json:"excludeNonCirculatingAccountsList,omitempty"`
ExcludeNonCirculatingAccountsList bool `json:"excludeNonCirculatingAccountsList,omitempty"` // exclude non circulating accounts list from response
}
type GetSupplyResult struct {
@ -66,5 +66,6 @@ type SupplyResult struct {
NonCirculating uint64 `json:"nonCirculating"`
// An array of account addresses of non-circulating accounts.
// If `excludeNonCirculatingAccountsList` is enabled, the returned array will be empty.
NonCirculatingAccounts []solana.PublicKey `json:"nonCirculatingAccounts"`
}

View File

@ -45,10 +45,11 @@ func (cl *Client) GetTransaction(
if !solana.IsAnyOfEncodingType(
opts.Encoding,
// Valid encodings:
solana.EncodingJSON,
// solana.EncodingJSON, // TODO
// solana.EncodingJSONParsed, // TODO
solana.EncodingBase58,
solana.EncodingBase64,
solana.EncodingBase64Zstd,
) {
return nil, fmt.Errorf("provided encoding is not supported: %s", opts.Encoding)
}

View File

@ -2,12 +2,14 @@ package rpc
import (
"context"
"github.com/gagliardetto/solana-go"
)
// IsBlockhashValid returns the balance of the account of provided publicKey.
// Returns whether a blockhash is still valid or not
//
// NEW: This method is only available in solana-core v1.9 or newer. Please use getFeeCalculatorForBlockhash for solana-core v1.8
// **NEW: This method is only available in solana-core v1.9 or newer. Please use
// `getFeeCalculatorForBlockhash` for solana-core v1.8**
func (cl *Client) IsBlockhashValid(
ctx context.Context,
// Blockhash to be queried. Required.

View File

@ -18,9 +18,11 @@
package rpc
import (
"encoding/base64"
stdjson "encoding/json"
"fmt"
bin "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
)
@ -92,9 +94,26 @@ const (
)
type TransactionWithMeta struct {
Transaction *DataBytesOrJSON `json:"transaction"`
// Transaction status metadata object
Meta *TransactionMeta `json:"meta,omitempty"`
Transaction *solana.Transaction `json:"transaction"`
Meta *TransactionMeta `json:"meta,omitempty"`
}
func (twm TransactionWithMeta) MustGetTransaction() *solana.Transaction {
tx, err := twm.GetTransaction()
if err != nil {
panic(err)
}
return tx
}
func (twm TransactionWithMeta) GetTransaction() (*solana.Transaction, error) {
tx := new(solana.Transaction)
err := tx.UnmarshalWithDecoder(bin.NewBinDecoder(twm.Transaction.GetBinary()))
if err != nil {
return nil, err
}
return tx, nil
}
type TransactionParsed struct {
@ -106,6 +125,9 @@ type TokenBalance struct {
// Index of the account in which the token balance is provided for.
AccountIndex uint16 `json:"accountIndex"`
// Pubkey of token balance's owner.
Owner *solana.PublicKey `json:"owner,omitempty"`
// Pubkey of the token's mint.
Mint solana.PublicKey `json:"mint"`
UiTokenAmount *UiTokenAmount `json:"uiTokenAmount"`
@ -202,7 +224,7 @@ type GetAccountInfoResult struct {
type IsValidBlockhashResult struct {
RPCContext
Value bool `json:"value"`
Value bool `json:"value"` // True if the blockhash is still valid.
}
type Account struct {
@ -228,6 +250,20 @@ type DataBytesOrJSON struct {
asJSON stdjson.RawMessage
}
func DataBytesOrJSONFromBase64(stringBase64 string) (*DataBytesOrJSON, error) {
decoded, err := base64.StdEncoding.DecodeString(stringBase64)
if err != nil {
return nil, err
}
return &DataBytesOrJSON{
rawDataEncoding: solana.EncodingBase64,
asDecodedBinary: solana.Data{
Encoding: solana.EncodingBase64,
Content: decoded,
},
}, nil
}
func (dt DataBytesOrJSON) MarshalJSON() ([]byte, error) {
if dt.rawDataEncoding == solana.EncodingJSONParsed || dt.rawDataEncoding == solana.EncodingJSON {
return json.Marshal(dt.asJSON)
@ -346,7 +382,7 @@ const (
// - This confirmation level also upholds "optimistic confirmation" guarantees in release 1.3 and onwards.
CommitmentConfirmed CommitmentType = "confirmed"
// The node will query its most recent block. Note that the block may not be complete.
// The node will query its most recent block. Note that the block may still be skipped by the cluster.
CommitmentProcessed CommitmentType = "processed"
)

161
rpc/ws/blockSubscribe.go Normal file
View File

@ -0,0 +1,161 @@
// Copyright 2022 github.com/gagliardetto
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ws
import (
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
)
type BlockResult struct {
Context struct {
Slot uint64
} `json:"context"`
Value struct {
Slot uint64 `json:"slot"`
Err interface{} `json:"err,omitempty"`
Block *rpc.GetBlockResult `json:"block,omitempty"`
} `json:"value"`
}
type BlockSubscribeFilter interface {
isBlockSubscribeFilter()
}
var _ BlockSubscribeFilter = BlockSubscribeFilterAll("")
type BlockSubscribeFilterAll string
func (_ BlockSubscribeFilterAll) isBlockSubscribeFilter() {}
type BlockSubscribeFilterMentionsAccountOrProgram struct {
Pubkey solana.PublicKey `json:"pubkey"`
}
func (_ BlockSubscribeFilterMentionsAccountOrProgram) isBlockSubscribeFilter() {}
func NewBlockSubscribeFilterAll() BlockSubscribeFilter {
return BlockSubscribeFilterAll("")
}
func NewBlockSubscribeFilterMentionsAccountOrProgram(pubkey solana.PublicKey) *BlockSubscribeFilterMentionsAccountOrProgram {
return &BlockSubscribeFilterMentionsAccountOrProgram{
Pubkey: pubkey,
}
}
type BlockSubscribeOpts struct {
Commitment rpc.CommitmentType
Encoding solana.EncodingType `json:"encoding,omitempty"`
// Level of transaction detail to return.
TransactionDetails rpc.TransactionDetailsType
// Whether to populate the rewards array. If parameter not provided, the default includes rewards.
Rewards *bool
}
// NOTE: Unstable, disabled by default
//
// Subscribe to receive notification anytime a new block is Confirmed or Finalized.
//
// **This subscription is unstable and only available if the validator was started
// with the `--rpc-pubsub-enable-block-subscription` flag. The format of this
// subscription may change in the future**
func (cl *Client) BlockSubscribe(
filter BlockSubscribeFilter,
opts *BlockSubscribeOpts,
) (*BlockSubscription, error) {
var params []interface{}
if filter != nil {
switch v := filter.(type) {
case BlockSubscribeFilterAll:
params = append(params, rpc.M{
"filter": "all",
})
case *BlockSubscribeFilterMentionsAccountOrProgram:
params = append(params, rpc.M{
"filter": rpc.M{
"mentionsAccountOrProgram": v.Pubkey,
},
})
}
}
if opts != nil {
obj := make(rpc.M)
if opts.Commitment != "" {
obj["commitment"] = opts.Commitment
}
if opts.Encoding != "" {
if !solana.IsAnyOfEncodingType(
opts.Encoding,
// Valid encodings:
// solana.EncodingJSON, // TODO
// solana.EncodingJSONParsed, // TODO
solana.EncodingBase58,
solana.EncodingBase64,
solana.EncodingBase64Zstd,
) {
return nil, fmt.Errorf("provided encoding is not supported: %s", opts.Encoding)
}
obj["encoding"] = opts.Encoding
}
if opts.TransactionDetails != "" {
obj["transactionDetails"] = opts.TransactionDetails
}
if opts.Rewards != nil {
obj["rewards"] = opts.Rewards
}
if len(obj) > 0 {
params = append(params, obj)
}
}
genSub, err := cl.subscribe(
params,
nil,
"blockSubscribe",
"blockUnsubscribe",
func(msg []byte) (interface{}, error) {
var res BlockResult
err := decodeResponseFromMessage(msg, &res)
return &res, err
},
)
if err != nil {
return nil, err
}
return &BlockSubscription{
sub: genSub,
}, nil
}
type BlockSubscription struct {
sub *Subscription
}
func (sw *BlockSubscription) Recv() (*BlockResult, error) {
select {
case d := <-sw.sub.stream:
return d.(*BlockResult), nil
case err := <-sw.sub.err:
return nil, err
}
}
func (sw *BlockSubscription) Unsubscribe() {
sw.sub.Unsubscribe()
}

View File

@ -19,6 +19,7 @@ package solana
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"sort"
@ -74,9 +75,6 @@ type CompiledInstruction struct {
// and that can be an issue.
ProgramIDIndex uint16 `json:"programIdIndex"`
AccountCount bin.Varuint16 `json:"-" bin:"sizeof=Accounts"`
DataLength bin.Varuint16 `json:"-" bin:"sizeof=Data"`
// List of ordered indices into the message.accountKeys array indicating which accounts to pass to the program.
// NOTE: it is actually a []uint8, but using a uint16 because []uint8 is treated as a []byte everywhere,
// and that can be an issue.
@ -315,9 +313,7 @@ func NewTransaction(instructions []Instruction, recentBlockHash Hash, opts ...Tr
}
message.Instructions = append(message.Instructions, CompiledInstruction{
ProgramIDIndex: accountKeyIndex[instruction.ProgramID().String()],
AccountCount: bin.Varuint16(uint16(len(accountIndex))),
Accounts: accountIndex,
DataLength: bin.Varuint16(uint16(len(data))),
Data: data,
})
}
@ -425,6 +421,22 @@ func (tx *Transaction) String() string {
return buf.String()
}
func (tx Transaction) ToBase64() (string, error) {
out, err := tx.MarshalBinary()
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(out), nil
}
func (tx Transaction) MustToBase64() string {
out, err := tx.ToBase64()
if err != nil {
panic(err)
}
return out
}
func (tx *Transaction) EncodeToTree(parent treeout.Branches) {
parent.ParentFunc(func(txTree treeout.Branches) {

View File

@ -96,16 +96,12 @@ func TestNewTransaction(t *testing.T) {
assert.Equal(t, trx.Message.Instructions, []CompiledInstruction{
{
ProgramIDIndex: 5,
AccountCount: 2,
Accounts: []uint16{0, 01},
DataLength: 2,
Data: []byte{0xaa, 0xbb},
},
{
ProgramIDIndex: 6,
AccountCount: 4,
Accounts: []uint16{4, 3, 1, 2},
DataLength: 2,
Data: []byte{0xcc, 0xdd},
},
})
@ -155,8 +151,6 @@ func TestTransactionDecode(t *testing.T) {
[]CompiledInstruction{
{
ProgramIDIndex: 2,
AccountCount: 2,
DataLength: 12,
Accounts: []uint16{
0,
1,

View File

@ -31,14 +31,12 @@ func TestCompiledInstructions(t *testing.T) {
ci := &CompiledInstruction{
ProgramIDIndex: 5,
AccountCount: 3,
Accounts: []uint16{2, 5, 8},
DataLength: 5,
Data: Base58([]byte{1, 2, 3, 4, 5}),
}
buf := &bytes.Buffer{}
encoder := bin.NewBinEncoder(buf)
err := encoder.Encode(ci)
require.NoError(t, err)
assert.Equal(t, []byte{0x5, 0x0, 0x3, 0x5, 0x2, 0x0, 0x5, 0x0, 0x8, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5}, buf.Bytes())
assert.Equal(t, []byte{0x5, 0x0, 0x3, 0x2, 0x0, 0x5, 0x0, 0x8, 0x0, 0x5, 0x1, 0x2, 0x3, 0x4, 0x5}, buf.Bytes())
}