├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── fumarole_geyser ├── geyser.go ├── geyser_test.go └── pb │ ├── fumarole.pb.go │ ├── fumarole_grpc.pb.go │ ├── geyser.pb.go │ ├── geyser_grpc.pb.go │ └── solana-storage.pb.go ├── go.mod ├── go.sum ├── jito_geyser ├── geyser.go ├── geyser_test.go └── pb │ ├── confirmed_block.pb.go │ ├── confirmed_block.proto │ ├── entries.pb.go │ ├── entries.proto │ ├── geyser.pb.go │ ├── geyser.proto │ ├── geyser_grpc.pb.go │ ├── transaction_by_addr.pb.go │ └── transaction_by_addr.proto ├── pkg ├── convert.go └── grpc.go └── yellowstone_geyser ├── geyser.go ├── geyser_test.go └── pb ├── geyser.pb.go ├── geyser_grpc.pb.go └── solana-storage.pb.go /.env.example: -------------------------------------------------------------------------------- 1 | GEYSER_RPC= # geyser rpc url 2 | FUMAROLE_GRPC= 3 | FUMAROLE_AUTH= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Go template 2 | # If you prefer the allow list template instead of the deny list, see community template: 3 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 4 | # 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | .env 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | 19 | # Go workspace file 20 | go.work 21 | 22 | /scripts -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2024 Ryan Riviere 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solana Geyser SDK 2 | [![GoDoc](https://pkg.go.dev/badge/github.com/weeaa/goyser?status.svg)](https://pkg.go.dev/github.com/weeaa/goyser?tab=doc) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/weeaa/goyser)](https://goreportcard.com/report/github.com/weeaa/goyser) 4 | [![License](https://img.shields.io/badge/license-Apache_2.0-crimson)](https://opensource.org/license/apache-2-0) 5 | 6 | 7 | This library contains tooling to interact with **[Yellowstone](https://github.com/rpcpool/yellowstone-grpc)** & **[Jito](https://github.com/jito-foundation/geyser-grpc-plugin)** Geyser plugins. 8 | 9 |
10 | yellowstone 11 |
12 | 13 | ## ❇️ Contents 14 | - [Support](#-support) 15 | - [Methods](#-methods) 16 | - [Installing](#-installing) 17 | - [Examples](#-examples) 18 | - [Subscribe to Account](#subscribe-to-account) 19 | - [License](#-license) 20 | 21 | ## 🛟 Support 22 | If my work has been useful in building your for-profit services/infra/bots/etc, consider donating at 23 | `EcrHvqa5Vh4NhR3bitRZVrdcUGr1Z3o6bXHz7xgBU2FB` (SOL). 24 | 25 | ## 📡 Methods 26 | - **Yellowstone** ✅ 27 | - `SubscribeAccounts` 28 | - `AppendAccounts` 29 | - `UnsubscribeAccounts` 30 | - `UnsubscribeAccountsByFilterName` 31 | - `UnsubscribeAllAccounts` 32 | - `SubscribeSlots` 33 | - `UnsubscribeSlots` 34 | - `SubscribeTransaction` 35 | - `UnsubscribeTransaction` 36 | - `SubscribeTransactionStatus` 37 | - `UnsubscribeTransactionStatus` 38 | - `SubscribeBlocks` 39 | - `UnsubscribeBlocks` 40 | - `SubscribeBlocksMeta` 41 | - `UnsubscribeBlocksMeta` 42 | - `SubscribeEntry` 43 | - `UnsubscribeEntry` 44 | - `SubscribeAccountDataSlice` 45 | - `UnsubscribeAccountDataSlice` 46 | - **Jito** (TBD) 47 | 48 | 💡 It also contains a `ConvertTransaction` function which converts from Goyser to [github.com/gagliardetto/solana-go](https://github.com/gagliardetto/solana-go) types :) 49 | 50 | ## 💾 Installing 51 | 52 | Go 1.22.0 or higher. 53 | ```shell 54 | go get github.com/weeaa/goyser@latest 55 | ``` 56 | 57 | ## 💻 Examples 58 | 59 | ### `Subscribe to Account – Yellowstone` 60 | Simple example on how to monitor an account for transactions with explanations. 61 | ```go 62 | package main 63 | 64 | import ( 65 | "context" 66 | "github.com/weeaa/goyser/yellowstone_geyser" 67 | geyser_pb "github.com/weeaa/goyser/yellowstone_geyser/pb" 68 | "log" 69 | "os" 70 | "time" 71 | ) 72 | 73 | const subAccount = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" 74 | 75 | func main() { 76 | ctx := context.Background() 77 | 78 | // get the geyser rpc address 79 | geyserRPC := os.Getenv("GEYSER_RPC") 80 | 81 | // create geyser client 82 | client, err := yellowstone_geyser.New(ctx, geyserRPC, nil) 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | // create a new subscribe client which is tied, for our example we will name it main 88 | // the created client is stored in client.Streams 89 | if err = client.AddStreamClient(ctx, "main", geyser_pb.CommitmentLevel_CONFIRMED); err != nil { 90 | log.Fatal(err) 91 | } 92 | 93 | // get the stream client 94 | streamClient := client.GetStreamClient("main") 95 | if streamClient == nil { 96 | log.Fatal("client does not have a stream named main") 97 | } 98 | 99 | // subscribe to the account you want to see txns from and set a custom filter name to filter them out later 100 | if err = streamClient.SubscribeAccounts("accounts", &geyser_pb.SubscribeRequestFilterAccounts{ 101 | Account: []string{subAccount}, 102 | }); err != nil { 103 | log.Fatal(err) 104 | } 105 | 106 | // loop through the stream and print the output 107 | for out := range streamClient.Ch { 108 | // u can filter the output by checking the filters 109 | go func() { 110 | filters := out.GetFilters() 111 | for _, filter := range filters { 112 | switch filter { 113 | case "accounts": 114 | log.Printf("account filter: %+v", out.GetAccount()) 115 | default: 116 | log.Printf("unknown filter: %s", filter) 117 | } 118 | } 119 | }() 120 | break 121 | } 122 | 123 | time.Sleep(5 * time.Second) 124 | 125 | // unsubscribe from the account 126 | if err = streamClient.UnsubscribeAccounts("accounts", subAccount); err != nil { 127 | log.Fatal(err) 128 | } 129 | } 130 | ``` 131 | 132 | ## 📃 License 133 | 134 | [Apache-2.0 License](https://github.com/weeaa/jito-go/blob/main/LICENSE). 135 | -------------------------------------------------------------------------------- /fumarole_geyser/geyser.go: -------------------------------------------------------------------------------- 1 | package fumarole_geyser 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/weeaa/goyser/fumarole_geyser/pb" 8 | "github.com/weeaa/goyser/pkg" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/metadata" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | // Client represents a client for the Fumarole service. 16 | type Client struct { 17 | Ctx context.Context 18 | Cancel context.CancelFunc 19 | GrpcConn *grpc.ClientConn 20 | Fumarole pb.FumaroleClient 21 | ErrCh chan error 22 | s *streamManager 23 | } 24 | 25 | // streamManager manages multiple stream clients. 26 | type streamManager struct { 27 | clients map[string]*StreamClient 28 | mu sync.RWMutex 29 | } 30 | 31 | // StreamClient represents a client for a specific stream. 32 | type StreamClient struct { 33 | Ctx context.Context 34 | Cancel context.CancelFunc 35 | GrpcConn *grpc.ClientConn 36 | streamName string 37 | fumarole pb.Fumarole_SubscribeClient 38 | fumaroleConn pb.FumaroleClient 39 | request *pb.SubscribeRequest 40 | Ch chan *pb.SubscribeUpdate 41 | ErrCh chan error 42 | mu sync.RWMutex 43 | countMu sync.RWMutex 44 | count int32 45 | latestCount time.Time 46 | } 47 | 48 | // New creates a new Client instance. 49 | func New(ctx context.Context, grpcDialURL string, md metadata.MD, opts ...grpc.DialOption) (*Client, error) { 50 | ch := make(chan error) 51 | 52 | if md != nil { 53 | ctx = metadata.NewOutgoingContext(ctx, md) 54 | } 55 | 56 | conn, err := pkg.CreateAndObserveGRPCConn(ctx, ch, grpcDialURL, opts...) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | fumaroleClient := pb.NewFumaroleClient(conn) 62 | if fumaroleClient == nil { 63 | return nil, errors.New("fumarole client is equal to nil") 64 | } 65 | 66 | client := &Client{ 67 | GrpcConn: conn, 68 | Ctx: ctx, 69 | Fumarole: fumaroleClient, 70 | ErrCh: ch, 71 | s: &streamManager{ 72 | clients: make(map[string]*StreamClient), 73 | mu: sync.RWMutex{}, 74 | }, 75 | } 76 | 77 | return client, nil 78 | } 79 | 80 | // Close closes the client and all the streams. 81 | func (c *Client) Close() error { 82 | for _, sc := range c.s.clients { 83 | sc.Stop() 84 | } 85 | close(c.ErrCh) 86 | return c.GrpcConn.Close() 87 | } 88 | 89 | // AddStreamClient creates a new Fumarole subscribe stream client. 90 | func (c *Client) AddStreamClient(ctx context.Context, streamName string, opts ...grpc.CallOption) (*StreamClient, error) { 91 | c.s.mu.Lock() 92 | defer c.s.mu.Unlock() 93 | 94 | if _, exists := c.s.clients[streamName]; exists { 95 | return nil, fmt.Errorf("client with name %s already exists", streamName) 96 | } 97 | 98 | opts = append(opts, grpc.MaxCallRecvMsgSize(100*1024*1024)) 99 | stream, err := c.Fumarole.Subscribe(ctx, opts...) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | streamClient := StreamClient{ 105 | Ctx: ctx, 106 | GrpcConn: c.GrpcConn, 107 | streamName: streamName, 108 | fumarole: stream, 109 | fumaroleConn: c.Fumarole, 110 | request: &pb.SubscribeRequest{}, 111 | Ch: make(chan *pb.SubscribeUpdate), 112 | ErrCh: make(chan error), 113 | mu: sync.RWMutex{}, 114 | } 115 | 116 | c.s.clients[streamName] = &streamClient 117 | go streamClient.listen() 118 | 119 | return &streamClient, nil 120 | } 121 | 122 | func (s *StreamClient) Stop() { 123 | s.Ctx.Done() 124 | close(s.Ch) 125 | close(s.ErrCh) 126 | } 127 | 128 | func (s *StreamClient) listen() { 129 | for { 130 | select { 131 | case <-s.Ctx.Done(): 132 | return 133 | default: 134 | recv, err := s.fumarole.Recv() 135 | if err != nil { 136 | s.ErrCh <- err 137 | continue 138 | } 139 | 140 | s.Ch <- recv 141 | } 142 | } 143 | } 144 | 145 | func (s *StreamClient) GetConsumerGroupInfo(label string) (*pb.ConsumerGroupInfo, error) { 146 | return s.fumaroleConn.GetConsumerGroupInfo(s.Ctx, &pb.GetConsumerGroupInfoRequest{ 147 | ConsumerGroupLabel: label, 148 | }) 149 | } 150 | 151 | func (s *StreamClient) CreateStaticConsumerGroup( 152 | label string, 153 | memberCount uint32, 154 | initialOffsetPolicy pb.InitialOffsetPolicy, 155 | commitmentLevel pb.CommitmentLevel, 156 | eventSubPolicy pb.EventSubscriptionPolicy, 157 | atSlot int64, 158 | ) (*pb.CreateStaticConsumerGroupResponse, error) { 159 | return s.fumaroleConn.CreateStaticConsumerGroup(s.Ctx, &pb.CreateStaticConsumerGroupRequest{ 160 | ConsumerGroupLabel: label, 161 | MemberCount: &memberCount, 162 | InitialOffsetPolicy: initialOffsetPolicy, 163 | CommitmentLevel: commitmentLevel, 164 | EventSubscriptionPolicy: eventSubPolicy, 165 | AtSlot: &atSlot, 166 | }) 167 | } 168 | 169 | func (s *StreamClient) DeleteConsumerGroup(label string) (*pb.DeleteConsumerGroupResponse, error) { 170 | return s.fumaroleConn.DeleteConsumerGroup(s.Ctx, &pb.DeleteConsumerGroupRequest{ 171 | ConsumerGroupLabel: label, 172 | }) 173 | } 174 | 175 | func (s *StreamClient) ListConsumerGroups() (*pb.ListConsumerGroupsResponse, error) { 176 | return s.fumaroleConn.ListConsumerGroups(s.Ctx, &pb.ListConsumerGroupsRequest{}) 177 | } 178 | 179 | func (s *StreamClient) GetOldestSlot(commitmentLevel pb.CommitmentLevel) (*pb.GetOldestSlotResponse, error) { 180 | return s.fumaroleConn.GetOldestSlot(s.Ctx, &pb.GetOldestSlotRequest{ 181 | CommitmentLevel: commitmentLevel, 182 | }) 183 | } 184 | 185 | func (s *StreamClient) GetSlotLagInfo(label string) (*pb.GetSlotLagInfoResponse, error) { 186 | return s.fumaroleConn.GetSlotLagInfo(s.Ctx, &pb.GetSlotLagInfoRequest{ 187 | ConsumerGroupLabel: label, 188 | }) 189 | } 190 | 191 | func (s *StreamClient) ListAvailableCommitmentLevels() (*pb.ListAvailableCommitmentLevelsResponse, error) { 192 | return s.fumaroleConn.ListAvailableCommitmentLevels(s.Ctx, &pb.ListAvailableCommitmentLevelsRequest{}) 193 | } 194 | 195 | func (s *StreamClient) Subscribe(req *pb.SubscribeRequest) error { 196 | return s.fumarole.Send(req) 197 | } 198 | -------------------------------------------------------------------------------- /fumarole_geyser/geyser_test.go: -------------------------------------------------------------------------------- 1 | package fumarole_geyser 2 | 3 | import ( 4 | "context" 5 | "github.com/google/uuid" 6 | "github.com/joho/godotenv" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/weeaa/goyser/fumarole_geyser/pb" 9 | "google.golang.org/grpc/metadata" 10 | "os" 11 | "path/filepath" 12 | "runtime" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | func TestMain(m *testing.M) { 18 | _, filename, _, _ := runtime.Caller(0) 19 | godotenv.Load(filepath.Join(filepath.Dir(filename), "..", "..", "..", "goyser", ".env")) 20 | os.Exit(m.Run()) 21 | } 22 | 23 | func TestGeyser(t *testing.T) { 24 | var ctx = context.Background() 25 | ctx, cancel := context.WithTimeout(ctx, 15*time.Second) 26 | defer cancel() 27 | 28 | fumaroleDialURL, ok := os.LookupEnv("FUMAROLE_GRPC") 29 | assert.True(t, ok) 30 | assert.NotEqual(t, fumaroleDialURL, "") 31 | 32 | fumaroleAuth, ok := os.LookupEnv("FUMAROLE_AUTH") 33 | assert.True(t, ok) 34 | assert.NotEqual(t, fumaroleAuth, "") 35 | 36 | md := metadata.New(map[string]string{"x-token": fumaroleAuth}) 37 | ctx = metadata.NewOutgoingContext(ctx, md) 38 | 39 | client, err := New(ctx, fumaroleDialURL, nil) 40 | assert.NoError(t, err) 41 | assert.NotNil(t, client) 42 | 43 | stream, err := client.AddStreamClient(ctx, "main") 44 | assert.NoError(t, err) 45 | assert.NotNil(t, stream) 46 | 47 | groupLabel := uuid.NewString() 48 | grp, err := stream.CreateStaticConsumerGroup(groupLabel, 1, pb.InitialOffsetPolicy_LATEST, pb.CommitmentLevel_CONFIRMED, pb.EventSubscriptionPolicy_BOTH, 0) 49 | assert.NoError(t, err) 50 | assert.NotNil(t, grp) 51 | 52 | err = stream.Subscribe(&pb.SubscribeRequest{ 53 | ConsumerGroupLabel: groupLabel, 54 | Accounts: map[string]*pb.SubscribeRequestFilterAccounts{ 55 | "USDC": { 56 | Account: []string{"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"}, 57 | }, 58 | }, 59 | }) 60 | assert.NoError(t, err) 61 | 62 | for data := range stream.Ch { 63 | if data != nil { 64 | break 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /fumarole_geyser/pb/fumarole_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.5.1 4 | // - protoc v4.25.3 5 | // source: fumarole.proto 6 | 7 | package pb 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.64.0 or later. 19 | const _ = grpc.SupportPackageIsVersion9 20 | 21 | const ( 22 | Fumarole_ListAvailableCommitmentLevels_FullMethodName = "/fumarole.Fumarole/ListAvailableCommitmentLevels" 23 | Fumarole_GetConsumerGroupInfo_FullMethodName = "/fumarole.Fumarole/GetConsumerGroupInfo" 24 | Fumarole_ListConsumerGroups_FullMethodName = "/fumarole.Fumarole/ListConsumerGroups" 25 | Fumarole_DeleteConsumerGroup_FullMethodName = "/fumarole.Fumarole/DeleteConsumerGroup" 26 | Fumarole_CreateStaticConsumerGroup_FullMethodName = "/fumarole.Fumarole/CreateStaticConsumerGroup" 27 | Fumarole_Subscribe_FullMethodName = "/fumarole.Fumarole/Subscribe" 28 | Fumarole_GetSlotLagInfo_FullMethodName = "/fumarole.Fumarole/GetSlotLagInfo" 29 | Fumarole_GetOldestSlot_FullMethodName = "/fumarole.Fumarole/GetOldestSlot" 30 | ) 31 | 32 | // FumaroleClient is the client API for Fumarole service. 33 | // 34 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 35 | type FumaroleClient interface { 36 | ListAvailableCommitmentLevels(ctx context.Context, in *ListAvailableCommitmentLevelsRequest, opts ...grpc.CallOption) (*ListAvailableCommitmentLevelsResponse, error) 37 | GetConsumerGroupInfo(ctx context.Context, in *GetConsumerGroupInfoRequest, opts ...grpc.CallOption) (*ConsumerGroupInfo, error) 38 | ListConsumerGroups(ctx context.Context, in *ListConsumerGroupsRequest, opts ...grpc.CallOption) (*ListConsumerGroupsResponse, error) 39 | DeleteConsumerGroup(ctx context.Context, in *DeleteConsumerGroupRequest, opts ...grpc.CallOption) (*DeleteConsumerGroupResponse, error) 40 | CreateStaticConsumerGroup(ctx context.Context, in *CreateStaticConsumerGroupRequest, opts ...grpc.CallOption) (*CreateStaticConsumerGroupResponse, error) 41 | Subscribe(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate], error) 42 | GetSlotLagInfo(ctx context.Context, in *GetSlotLagInfoRequest, opts ...grpc.CallOption) (*GetSlotLagInfoResponse, error) 43 | GetOldestSlot(ctx context.Context, in *GetOldestSlotRequest, opts ...grpc.CallOption) (*GetOldestSlotResponse, error) 44 | } 45 | 46 | type fumaroleClient struct { 47 | cc grpc.ClientConnInterface 48 | } 49 | 50 | func NewFumaroleClient(cc grpc.ClientConnInterface) FumaroleClient { 51 | return &fumaroleClient{cc} 52 | } 53 | 54 | func (c *fumaroleClient) ListAvailableCommitmentLevels(ctx context.Context, in *ListAvailableCommitmentLevelsRequest, opts ...grpc.CallOption) (*ListAvailableCommitmentLevelsResponse, error) { 55 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 56 | out := new(ListAvailableCommitmentLevelsResponse) 57 | err := c.cc.Invoke(ctx, Fumarole_ListAvailableCommitmentLevels_FullMethodName, in, out, cOpts...) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return out, nil 62 | } 63 | 64 | func (c *fumaroleClient) GetConsumerGroupInfo(ctx context.Context, in *GetConsumerGroupInfoRequest, opts ...grpc.CallOption) (*ConsumerGroupInfo, error) { 65 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 66 | out := new(ConsumerGroupInfo) 67 | err := c.cc.Invoke(ctx, Fumarole_GetConsumerGroupInfo_FullMethodName, in, out, cOpts...) 68 | if err != nil { 69 | return nil, err 70 | } 71 | return out, nil 72 | } 73 | 74 | func (c *fumaroleClient) ListConsumerGroups(ctx context.Context, in *ListConsumerGroupsRequest, opts ...grpc.CallOption) (*ListConsumerGroupsResponse, error) { 75 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 76 | out := new(ListConsumerGroupsResponse) 77 | err := c.cc.Invoke(ctx, Fumarole_ListConsumerGroups_FullMethodName, in, out, cOpts...) 78 | if err != nil { 79 | return nil, err 80 | } 81 | return out, nil 82 | } 83 | 84 | func (c *fumaroleClient) DeleteConsumerGroup(ctx context.Context, in *DeleteConsumerGroupRequest, opts ...grpc.CallOption) (*DeleteConsumerGroupResponse, error) { 85 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 86 | out := new(DeleteConsumerGroupResponse) 87 | err := c.cc.Invoke(ctx, Fumarole_DeleteConsumerGroup_FullMethodName, in, out, cOpts...) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return out, nil 92 | } 93 | 94 | func (c *fumaroleClient) CreateStaticConsumerGroup(ctx context.Context, in *CreateStaticConsumerGroupRequest, opts ...grpc.CallOption) (*CreateStaticConsumerGroupResponse, error) { 95 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 96 | out := new(CreateStaticConsumerGroupResponse) 97 | err := c.cc.Invoke(ctx, Fumarole_CreateStaticConsumerGroup_FullMethodName, in, out, cOpts...) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return out, nil 102 | } 103 | 104 | func (c *fumaroleClient) Subscribe(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate], error) { 105 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 106 | stream, err := c.cc.NewStream(ctx, &Fumarole_ServiceDesc.Streams[0], Fumarole_Subscribe_FullMethodName, cOpts...) 107 | if err != nil { 108 | return nil, err 109 | } 110 | x := &grpc.GenericClientStream[SubscribeRequest, SubscribeUpdate]{ClientStream: stream} 111 | return x, nil 112 | } 113 | 114 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 115 | type Fumarole_SubscribeClient = grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate] 116 | 117 | func (c *fumaroleClient) GetSlotLagInfo(ctx context.Context, in *GetSlotLagInfoRequest, opts ...grpc.CallOption) (*GetSlotLagInfoResponse, error) { 118 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 119 | out := new(GetSlotLagInfoResponse) 120 | err := c.cc.Invoke(ctx, Fumarole_GetSlotLagInfo_FullMethodName, in, out, cOpts...) 121 | if err != nil { 122 | return nil, err 123 | } 124 | return out, nil 125 | } 126 | 127 | func (c *fumaroleClient) GetOldestSlot(ctx context.Context, in *GetOldestSlotRequest, opts ...grpc.CallOption) (*GetOldestSlotResponse, error) { 128 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 129 | out := new(GetOldestSlotResponse) 130 | err := c.cc.Invoke(ctx, Fumarole_GetOldestSlot_FullMethodName, in, out, cOpts...) 131 | if err != nil { 132 | return nil, err 133 | } 134 | return out, nil 135 | } 136 | 137 | // FumaroleServer is the server API for Fumarole service. 138 | // All implementations must embed UnimplementedFumaroleServer 139 | // for forward compatibility. 140 | type FumaroleServer interface { 141 | ListAvailableCommitmentLevels(context.Context, *ListAvailableCommitmentLevelsRequest) (*ListAvailableCommitmentLevelsResponse, error) 142 | GetConsumerGroupInfo(context.Context, *GetConsumerGroupInfoRequest) (*ConsumerGroupInfo, error) 143 | ListConsumerGroups(context.Context, *ListConsumerGroupsRequest) (*ListConsumerGroupsResponse, error) 144 | DeleteConsumerGroup(context.Context, *DeleteConsumerGroupRequest) (*DeleteConsumerGroupResponse, error) 145 | CreateStaticConsumerGroup(context.Context, *CreateStaticConsumerGroupRequest) (*CreateStaticConsumerGroupResponse, error) 146 | Subscribe(grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]) error 147 | GetSlotLagInfo(context.Context, *GetSlotLagInfoRequest) (*GetSlotLagInfoResponse, error) 148 | GetOldestSlot(context.Context, *GetOldestSlotRequest) (*GetOldestSlotResponse, error) 149 | mustEmbedUnimplementedFumaroleServer() 150 | } 151 | 152 | // UnimplementedFumaroleServer must be embedded to have 153 | // forward compatible implementations. 154 | // 155 | // NOTE: this should be embedded by value instead of pointer to avoid a nil 156 | // pointer dereference when methods are called. 157 | type UnimplementedFumaroleServer struct{} 158 | 159 | func (UnimplementedFumaroleServer) ListAvailableCommitmentLevels(context.Context, *ListAvailableCommitmentLevelsRequest) (*ListAvailableCommitmentLevelsResponse, error) { 160 | return nil, status.Errorf(codes.Unimplemented, "method ListAvailableCommitmentLevels not implemented") 161 | } 162 | func (UnimplementedFumaroleServer) GetConsumerGroupInfo(context.Context, *GetConsumerGroupInfoRequest) (*ConsumerGroupInfo, error) { 163 | return nil, status.Errorf(codes.Unimplemented, "method GetConsumerGroupInfo not implemented") 164 | } 165 | func (UnimplementedFumaroleServer) ListConsumerGroups(context.Context, *ListConsumerGroupsRequest) (*ListConsumerGroupsResponse, error) { 166 | return nil, status.Errorf(codes.Unimplemented, "method ListConsumerGroups not implemented") 167 | } 168 | func (UnimplementedFumaroleServer) DeleteConsumerGroup(context.Context, *DeleteConsumerGroupRequest) (*DeleteConsumerGroupResponse, error) { 169 | return nil, status.Errorf(codes.Unimplemented, "method DeleteConsumerGroup not implemented") 170 | } 171 | func (UnimplementedFumaroleServer) CreateStaticConsumerGroup(context.Context, *CreateStaticConsumerGroupRequest) (*CreateStaticConsumerGroupResponse, error) { 172 | return nil, status.Errorf(codes.Unimplemented, "method CreateStaticConsumerGroup not implemented") 173 | } 174 | func (UnimplementedFumaroleServer) Subscribe(grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]) error { 175 | return status.Errorf(codes.Unimplemented, "method Subscribe not implemented") 176 | } 177 | func (UnimplementedFumaroleServer) GetSlotLagInfo(context.Context, *GetSlotLagInfoRequest) (*GetSlotLagInfoResponse, error) { 178 | return nil, status.Errorf(codes.Unimplemented, "method GetSlotLagInfo not implemented") 179 | } 180 | func (UnimplementedFumaroleServer) GetOldestSlot(context.Context, *GetOldestSlotRequest) (*GetOldestSlotResponse, error) { 181 | return nil, status.Errorf(codes.Unimplemented, "method GetOldestSlot not implemented") 182 | } 183 | func (UnimplementedFumaroleServer) mustEmbedUnimplementedFumaroleServer() {} 184 | func (UnimplementedFumaroleServer) testEmbeddedByValue() {} 185 | 186 | // UnsafeFumaroleServer may be embedded to opt out of forward compatibility for this service. 187 | // Use of this interface is not recommended, as added methods to FumaroleServer will 188 | // result in compilation errors. 189 | type UnsafeFumaroleServer interface { 190 | mustEmbedUnimplementedFumaroleServer() 191 | } 192 | 193 | func RegisterFumaroleServer(s grpc.ServiceRegistrar, srv FumaroleServer) { 194 | // If the following call pancis, it indicates UnimplementedFumaroleServer was 195 | // embedded by pointer and is nil. This will cause panics if an 196 | // unimplemented method is ever invoked, so we test this at initialization 197 | // time to prevent it from happening at runtime later due to I/O. 198 | if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { 199 | t.testEmbeddedByValue() 200 | } 201 | s.RegisterService(&Fumarole_ServiceDesc, srv) 202 | } 203 | 204 | func _Fumarole_ListAvailableCommitmentLevels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 205 | in := new(ListAvailableCommitmentLevelsRequest) 206 | if err := dec(in); err != nil { 207 | return nil, err 208 | } 209 | if interceptor == nil { 210 | return srv.(FumaroleServer).ListAvailableCommitmentLevels(ctx, in) 211 | } 212 | info := &grpc.UnaryServerInfo{ 213 | Server: srv, 214 | FullMethod: Fumarole_ListAvailableCommitmentLevels_FullMethodName, 215 | } 216 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 217 | return srv.(FumaroleServer).ListAvailableCommitmentLevels(ctx, req.(*ListAvailableCommitmentLevelsRequest)) 218 | } 219 | return interceptor(ctx, in, info, handler) 220 | } 221 | 222 | func _Fumarole_GetConsumerGroupInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 223 | in := new(GetConsumerGroupInfoRequest) 224 | if err := dec(in); err != nil { 225 | return nil, err 226 | } 227 | if interceptor == nil { 228 | return srv.(FumaroleServer).GetConsumerGroupInfo(ctx, in) 229 | } 230 | info := &grpc.UnaryServerInfo{ 231 | Server: srv, 232 | FullMethod: Fumarole_GetConsumerGroupInfo_FullMethodName, 233 | } 234 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 235 | return srv.(FumaroleServer).GetConsumerGroupInfo(ctx, req.(*GetConsumerGroupInfoRequest)) 236 | } 237 | return interceptor(ctx, in, info, handler) 238 | } 239 | 240 | func _Fumarole_ListConsumerGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 241 | in := new(ListConsumerGroupsRequest) 242 | if err := dec(in); err != nil { 243 | return nil, err 244 | } 245 | if interceptor == nil { 246 | return srv.(FumaroleServer).ListConsumerGroups(ctx, in) 247 | } 248 | info := &grpc.UnaryServerInfo{ 249 | Server: srv, 250 | FullMethod: Fumarole_ListConsumerGroups_FullMethodName, 251 | } 252 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 253 | return srv.(FumaroleServer).ListConsumerGroups(ctx, req.(*ListConsumerGroupsRequest)) 254 | } 255 | return interceptor(ctx, in, info, handler) 256 | } 257 | 258 | func _Fumarole_DeleteConsumerGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 259 | in := new(DeleteConsumerGroupRequest) 260 | if err := dec(in); err != nil { 261 | return nil, err 262 | } 263 | if interceptor == nil { 264 | return srv.(FumaroleServer).DeleteConsumerGroup(ctx, in) 265 | } 266 | info := &grpc.UnaryServerInfo{ 267 | Server: srv, 268 | FullMethod: Fumarole_DeleteConsumerGroup_FullMethodName, 269 | } 270 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 271 | return srv.(FumaroleServer).DeleteConsumerGroup(ctx, req.(*DeleteConsumerGroupRequest)) 272 | } 273 | return interceptor(ctx, in, info, handler) 274 | } 275 | 276 | func _Fumarole_CreateStaticConsumerGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 277 | in := new(CreateStaticConsumerGroupRequest) 278 | if err := dec(in); err != nil { 279 | return nil, err 280 | } 281 | if interceptor == nil { 282 | return srv.(FumaroleServer).CreateStaticConsumerGroup(ctx, in) 283 | } 284 | info := &grpc.UnaryServerInfo{ 285 | Server: srv, 286 | FullMethod: Fumarole_CreateStaticConsumerGroup_FullMethodName, 287 | } 288 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 289 | return srv.(FumaroleServer).CreateStaticConsumerGroup(ctx, req.(*CreateStaticConsumerGroupRequest)) 290 | } 291 | return interceptor(ctx, in, info, handler) 292 | } 293 | 294 | func _Fumarole_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { 295 | return srv.(FumaroleServer).Subscribe(&grpc.GenericServerStream[SubscribeRequest, SubscribeUpdate]{ServerStream: stream}) 296 | } 297 | 298 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 299 | type Fumarole_SubscribeServer = grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate] 300 | 301 | func _Fumarole_GetSlotLagInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 302 | in := new(GetSlotLagInfoRequest) 303 | if err := dec(in); err != nil { 304 | return nil, err 305 | } 306 | if interceptor == nil { 307 | return srv.(FumaroleServer).GetSlotLagInfo(ctx, in) 308 | } 309 | info := &grpc.UnaryServerInfo{ 310 | Server: srv, 311 | FullMethod: Fumarole_GetSlotLagInfo_FullMethodName, 312 | } 313 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 314 | return srv.(FumaroleServer).GetSlotLagInfo(ctx, req.(*GetSlotLagInfoRequest)) 315 | } 316 | return interceptor(ctx, in, info, handler) 317 | } 318 | 319 | func _Fumarole_GetOldestSlot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 320 | in := new(GetOldestSlotRequest) 321 | if err := dec(in); err != nil { 322 | return nil, err 323 | } 324 | if interceptor == nil { 325 | return srv.(FumaroleServer).GetOldestSlot(ctx, in) 326 | } 327 | info := &grpc.UnaryServerInfo{ 328 | Server: srv, 329 | FullMethod: Fumarole_GetOldestSlot_FullMethodName, 330 | } 331 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 332 | return srv.(FumaroleServer).GetOldestSlot(ctx, req.(*GetOldestSlotRequest)) 333 | } 334 | return interceptor(ctx, in, info, handler) 335 | } 336 | 337 | // Fumarole_ServiceDesc is the grpc.ServiceDesc for Fumarole service. 338 | // It's only intended for direct use with grpc.RegisterService, 339 | // and not to be introspected or modified (even as a copy) 340 | var Fumarole_ServiceDesc = grpc.ServiceDesc{ 341 | ServiceName: "fumarole.Fumarole", 342 | HandlerType: (*FumaroleServer)(nil), 343 | Methods: []grpc.MethodDesc{ 344 | { 345 | MethodName: "ListAvailableCommitmentLevels", 346 | Handler: _Fumarole_ListAvailableCommitmentLevels_Handler, 347 | }, 348 | { 349 | MethodName: "GetConsumerGroupInfo", 350 | Handler: _Fumarole_GetConsumerGroupInfo_Handler, 351 | }, 352 | { 353 | MethodName: "ListConsumerGroups", 354 | Handler: _Fumarole_ListConsumerGroups_Handler, 355 | }, 356 | { 357 | MethodName: "DeleteConsumerGroup", 358 | Handler: _Fumarole_DeleteConsumerGroup_Handler, 359 | }, 360 | { 361 | MethodName: "CreateStaticConsumerGroup", 362 | Handler: _Fumarole_CreateStaticConsumerGroup_Handler, 363 | }, 364 | { 365 | MethodName: "GetSlotLagInfo", 366 | Handler: _Fumarole_GetSlotLagInfo_Handler, 367 | }, 368 | { 369 | MethodName: "GetOldestSlot", 370 | Handler: _Fumarole_GetOldestSlot_Handler, 371 | }, 372 | }, 373 | Streams: []grpc.StreamDesc{ 374 | { 375 | StreamName: "Subscribe", 376 | Handler: _Fumarole_Subscribe_Handler, 377 | ServerStreams: true, 378 | ClientStreams: true, 379 | }, 380 | }, 381 | Metadata: "fumarole.proto", 382 | } 383 | -------------------------------------------------------------------------------- /fumarole_geyser/pb/geyser_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.5.1 4 | // - protoc v4.25.3 5 | // source: geyser.proto 6 | 7 | package pb 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.64.0 or later. 19 | const _ = grpc.SupportPackageIsVersion9 20 | 21 | const ( 22 | Geyser_Subscribe_FullMethodName = "/geyser.Geyser/Subscribe" 23 | Geyser_Ping_FullMethodName = "/geyser.Geyser/Ping" 24 | Geyser_GetLatestBlockhash_FullMethodName = "/geyser.Geyser/GetLatestBlockhash" 25 | Geyser_GetBlockHeight_FullMethodName = "/geyser.Geyser/GetBlockHeight" 26 | Geyser_GetSlot_FullMethodName = "/geyser.Geyser/GetSlot" 27 | Geyser_IsBlockhashValid_FullMethodName = "/geyser.Geyser/IsBlockhashValid" 28 | Geyser_GetVersion_FullMethodName = "/geyser.Geyser/GetVersion" 29 | ) 30 | 31 | // GeyserClient is the client API for Geyser service. 32 | // 33 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 34 | type GeyserClient interface { 35 | Subscribe(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate], error) 36 | Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongResponse, error) 37 | GetLatestBlockhash(ctx context.Context, in *GetLatestBlockhashRequest, opts ...grpc.CallOption) (*GetLatestBlockhashResponse, error) 38 | GetBlockHeight(ctx context.Context, in *GetBlockHeightRequest, opts ...grpc.CallOption) (*GetBlockHeightResponse, error) 39 | GetSlot(ctx context.Context, in *GetSlotRequest, opts ...grpc.CallOption) (*GetSlotResponse, error) 40 | IsBlockhashValid(ctx context.Context, in *IsBlockhashValidRequest, opts ...grpc.CallOption) (*IsBlockhashValidResponse, error) 41 | GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) 42 | } 43 | 44 | type geyserClient struct { 45 | cc grpc.ClientConnInterface 46 | } 47 | 48 | func NewGeyserClient(cc grpc.ClientConnInterface) GeyserClient { 49 | return &geyserClient{cc} 50 | } 51 | 52 | func (c *geyserClient) Subscribe(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate], error) { 53 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 54 | stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[0], Geyser_Subscribe_FullMethodName, cOpts...) 55 | if err != nil { 56 | return nil, err 57 | } 58 | x := &grpc.GenericClientStream[SubscribeRequest, SubscribeUpdate]{ClientStream: stream} 59 | return x, nil 60 | } 61 | 62 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 63 | type Geyser_SubscribeClient = grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate] 64 | 65 | func (c *geyserClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongResponse, error) { 66 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 67 | out := new(PongResponse) 68 | err := c.cc.Invoke(ctx, Geyser_Ping_FullMethodName, in, out, cOpts...) 69 | if err != nil { 70 | return nil, err 71 | } 72 | return out, nil 73 | } 74 | 75 | func (c *geyserClient) GetLatestBlockhash(ctx context.Context, in *GetLatestBlockhashRequest, opts ...grpc.CallOption) (*GetLatestBlockhashResponse, error) { 76 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 77 | out := new(GetLatestBlockhashResponse) 78 | err := c.cc.Invoke(ctx, Geyser_GetLatestBlockhash_FullMethodName, in, out, cOpts...) 79 | if err != nil { 80 | return nil, err 81 | } 82 | return out, nil 83 | } 84 | 85 | func (c *geyserClient) GetBlockHeight(ctx context.Context, in *GetBlockHeightRequest, opts ...grpc.CallOption) (*GetBlockHeightResponse, error) { 86 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 87 | out := new(GetBlockHeightResponse) 88 | err := c.cc.Invoke(ctx, Geyser_GetBlockHeight_FullMethodName, in, out, cOpts...) 89 | if err != nil { 90 | return nil, err 91 | } 92 | return out, nil 93 | } 94 | 95 | func (c *geyserClient) GetSlot(ctx context.Context, in *GetSlotRequest, opts ...grpc.CallOption) (*GetSlotResponse, error) { 96 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 97 | out := new(GetSlotResponse) 98 | err := c.cc.Invoke(ctx, Geyser_GetSlot_FullMethodName, in, out, cOpts...) 99 | if err != nil { 100 | return nil, err 101 | } 102 | return out, nil 103 | } 104 | 105 | func (c *geyserClient) IsBlockhashValid(ctx context.Context, in *IsBlockhashValidRequest, opts ...grpc.CallOption) (*IsBlockhashValidResponse, error) { 106 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 107 | out := new(IsBlockhashValidResponse) 108 | err := c.cc.Invoke(ctx, Geyser_IsBlockhashValid_FullMethodName, in, out, cOpts...) 109 | if err != nil { 110 | return nil, err 111 | } 112 | return out, nil 113 | } 114 | 115 | func (c *geyserClient) GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) { 116 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 117 | out := new(GetVersionResponse) 118 | err := c.cc.Invoke(ctx, Geyser_GetVersion_FullMethodName, in, out, cOpts...) 119 | if err != nil { 120 | return nil, err 121 | } 122 | return out, nil 123 | } 124 | 125 | // GeyserServer is the server API for Geyser service. 126 | // All implementations must embed UnimplementedGeyserServer 127 | // for forward compatibility. 128 | type GeyserServer interface { 129 | Subscribe(grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]) error 130 | Ping(context.Context, *PingRequest) (*PongResponse, error) 131 | GetLatestBlockhash(context.Context, *GetLatestBlockhashRequest) (*GetLatestBlockhashResponse, error) 132 | GetBlockHeight(context.Context, *GetBlockHeightRequest) (*GetBlockHeightResponse, error) 133 | GetSlot(context.Context, *GetSlotRequest) (*GetSlotResponse, error) 134 | IsBlockhashValid(context.Context, *IsBlockhashValidRequest) (*IsBlockhashValidResponse, error) 135 | GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) 136 | mustEmbedUnimplementedGeyserServer() 137 | } 138 | 139 | // UnimplementedGeyserServer must be embedded to have 140 | // forward compatible implementations. 141 | // 142 | // NOTE: this should be embedded by value instead of pointer to avoid a nil 143 | // pointer dereference when methods are called. 144 | type UnimplementedGeyserServer struct{} 145 | 146 | func (UnimplementedGeyserServer) Subscribe(grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]) error { 147 | return status.Errorf(codes.Unimplemented, "method Subscribe not implemented") 148 | } 149 | func (UnimplementedGeyserServer) Ping(context.Context, *PingRequest) (*PongResponse, error) { 150 | return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") 151 | } 152 | func (UnimplementedGeyserServer) GetLatestBlockhash(context.Context, *GetLatestBlockhashRequest) (*GetLatestBlockhashResponse, error) { 153 | return nil, status.Errorf(codes.Unimplemented, "method GetLatestBlockhash not implemented") 154 | } 155 | func (UnimplementedGeyserServer) GetBlockHeight(context.Context, *GetBlockHeightRequest) (*GetBlockHeightResponse, error) { 156 | return nil, status.Errorf(codes.Unimplemented, "method GetBlockHeight not implemented") 157 | } 158 | func (UnimplementedGeyserServer) GetSlot(context.Context, *GetSlotRequest) (*GetSlotResponse, error) { 159 | return nil, status.Errorf(codes.Unimplemented, "method GetSlot not implemented") 160 | } 161 | func (UnimplementedGeyserServer) IsBlockhashValid(context.Context, *IsBlockhashValidRequest) (*IsBlockhashValidResponse, error) { 162 | return nil, status.Errorf(codes.Unimplemented, "method IsBlockhashValid not implemented") 163 | } 164 | func (UnimplementedGeyserServer) GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) { 165 | return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented") 166 | } 167 | func (UnimplementedGeyserServer) mustEmbedUnimplementedGeyserServer() {} 168 | func (UnimplementedGeyserServer) testEmbeddedByValue() {} 169 | 170 | // UnsafeGeyserServer may be embedded to opt out of forward compatibility for this service. 171 | // Use of this interface is not recommended, as added methods to GeyserServer will 172 | // result in compilation errors. 173 | type UnsafeGeyserServer interface { 174 | mustEmbedUnimplementedGeyserServer() 175 | } 176 | 177 | func RegisterGeyserServer(s grpc.ServiceRegistrar, srv GeyserServer) { 178 | // If the following call pancis, it indicates UnimplementedGeyserServer was 179 | // embedded by pointer and is nil. This will cause panics if an 180 | // unimplemented method is ever invoked, so we test this at initialization 181 | // time to prevent it from happening at runtime later due to I/O. 182 | if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { 183 | t.testEmbeddedByValue() 184 | } 185 | s.RegisterService(&Geyser_ServiceDesc, srv) 186 | } 187 | 188 | func _Geyser_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { 189 | return srv.(GeyserServer).Subscribe(&grpc.GenericServerStream[SubscribeRequest, SubscribeUpdate]{ServerStream: stream}) 190 | } 191 | 192 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 193 | type Geyser_SubscribeServer = grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate] 194 | 195 | func _Geyser_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 196 | in := new(PingRequest) 197 | if err := dec(in); err != nil { 198 | return nil, err 199 | } 200 | if interceptor == nil { 201 | return srv.(GeyserServer).Ping(ctx, in) 202 | } 203 | info := &grpc.UnaryServerInfo{ 204 | Server: srv, 205 | FullMethod: Geyser_Ping_FullMethodName, 206 | } 207 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 208 | return srv.(GeyserServer).Ping(ctx, req.(*PingRequest)) 209 | } 210 | return interceptor(ctx, in, info, handler) 211 | } 212 | 213 | func _Geyser_GetLatestBlockhash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 214 | in := new(GetLatestBlockhashRequest) 215 | if err := dec(in); err != nil { 216 | return nil, err 217 | } 218 | if interceptor == nil { 219 | return srv.(GeyserServer).GetLatestBlockhash(ctx, in) 220 | } 221 | info := &grpc.UnaryServerInfo{ 222 | Server: srv, 223 | FullMethod: Geyser_GetLatestBlockhash_FullMethodName, 224 | } 225 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 226 | return srv.(GeyserServer).GetLatestBlockhash(ctx, req.(*GetLatestBlockhashRequest)) 227 | } 228 | return interceptor(ctx, in, info, handler) 229 | } 230 | 231 | func _Geyser_GetBlockHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 232 | in := new(GetBlockHeightRequest) 233 | if err := dec(in); err != nil { 234 | return nil, err 235 | } 236 | if interceptor == nil { 237 | return srv.(GeyserServer).GetBlockHeight(ctx, in) 238 | } 239 | info := &grpc.UnaryServerInfo{ 240 | Server: srv, 241 | FullMethod: Geyser_GetBlockHeight_FullMethodName, 242 | } 243 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 244 | return srv.(GeyserServer).GetBlockHeight(ctx, req.(*GetBlockHeightRequest)) 245 | } 246 | return interceptor(ctx, in, info, handler) 247 | } 248 | 249 | func _Geyser_GetSlot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 250 | in := new(GetSlotRequest) 251 | if err := dec(in); err != nil { 252 | return nil, err 253 | } 254 | if interceptor == nil { 255 | return srv.(GeyserServer).GetSlot(ctx, in) 256 | } 257 | info := &grpc.UnaryServerInfo{ 258 | Server: srv, 259 | FullMethod: Geyser_GetSlot_FullMethodName, 260 | } 261 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 262 | return srv.(GeyserServer).GetSlot(ctx, req.(*GetSlotRequest)) 263 | } 264 | return interceptor(ctx, in, info, handler) 265 | } 266 | 267 | func _Geyser_IsBlockhashValid_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 268 | in := new(IsBlockhashValidRequest) 269 | if err := dec(in); err != nil { 270 | return nil, err 271 | } 272 | if interceptor == nil { 273 | return srv.(GeyserServer).IsBlockhashValid(ctx, in) 274 | } 275 | info := &grpc.UnaryServerInfo{ 276 | Server: srv, 277 | FullMethod: Geyser_IsBlockhashValid_FullMethodName, 278 | } 279 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 280 | return srv.(GeyserServer).IsBlockhashValid(ctx, req.(*IsBlockhashValidRequest)) 281 | } 282 | return interceptor(ctx, in, info, handler) 283 | } 284 | 285 | func _Geyser_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 286 | in := new(GetVersionRequest) 287 | if err := dec(in); err != nil { 288 | return nil, err 289 | } 290 | if interceptor == nil { 291 | return srv.(GeyserServer).GetVersion(ctx, in) 292 | } 293 | info := &grpc.UnaryServerInfo{ 294 | Server: srv, 295 | FullMethod: Geyser_GetVersion_FullMethodName, 296 | } 297 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 298 | return srv.(GeyserServer).GetVersion(ctx, req.(*GetVersionRequest)) 299 | } 300 | return interceptor(ctx, in, info, handler) 301 | } 302 | 303 | // Geyser_ServiceDesc is the grpc.ServiceDesc for Geyser service. 304 | // It's only intended for direct use with grpc.RegisterService, 305 | // and not to be introspected or modified (even as a copy) 306 | var Geyser_ServiceDesc = grpc.ServiceDesc{ 307 | ServiceName: "geyser.Geyser", 308 | HandlerType: (*GeyserServer)(nil), 309 | Methods: []grpc.MethodDesc{ 310 | { 311 | MethodName: "Ping", 312 | Handler: _Geyser_Ping_Handler, 313 | }, 314 | { 315 | MethodName: "GetLatestBlockhash", 316 | Handler: _Geyser_GetLatestBlockhash_Handler, 317 | }, 318 | { 319 | MethodName: "GetBlockHeight", 320 | Handler: _Geyser_GetBlockHeight_Handler, 321 | }, 322 | { 323 | MethodName: "GetSlot", 324 | Handler: _Geyser_GetSlot_Handler, 325 | }, 326 | { 327 | MethodName: "IsBlockhashValid", 328 | Handler: _Geyser_IsBlockhashValid_Handler, 329 | }, 330 | { 331 | MethodName: "GetVersion", 332 | Handler: _Geyser_GetVersion_Handler, 333 | }, 334 | }, 335 | Streams: []grpc.StreamDesc{ 336 | { 337 | StreamName: "Subscribe", 338 | Handler: _Geyser_Subscribe_Handler, 339 | ServerStreams: true, 340 | ClientStreams: true, 341 | }, 342 | }, 343 | Metadata: "geyser.proto", 344 | } 345 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/weeaa/goyser 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.2 6 | 7 | require ( 8 | github.com/gagliardetto/solana-go v1.12.0 9 | github.com/google/uuid v1.6.0 10 | github.com/joho/godotenv v1.5.1 11 | github.com/stretchr/testify v1.10.0 12 | google.golang.org/grpc v1.72.2 13 | google.golang.org/protobuf v1.36.6 14 | ) 15 | 16 | require ( 17 | filippo.io/edwards25519 v1.1.0 // indirect 18 | github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect 19 | github.com/benbjohnson/clock v1.3.5 // indirect 20 | github.com/blendle/zapdriver v1.3.1 // indirect 21 | github.com/davecgh/go-spew v1.1.1 // indirect 22 | github.com/fatih/color v1.18.0 // indirect 23 | github.com/gagliardetto/binary v0.8.0 // indirect 24 | github.com/gagliardetto/treeout v0.1.4 // indirect 25 | github.com/golang/mock v1.6.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/klauspost/compress v1.18.0 // indirect 28 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect 29 | github.com/mattn/go-colorable v0.1.14 // indirect 30 | github.com/mattn/go-isatty v0.0.20 // indirect 31 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 32 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 33 | github.com/modern-go/reflect2 v1.0.2 // indirect 34 | github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect 35 | github.com/mr-tron/base58 v1.2.0 // indirect 36 | github.com/pmezard/go-difflib v1.0.0 // indirect 37 | github.com/streamingfast/logging v0.0.0-20250404134358-92b15d2fbd2e // indirect 38 | github.com/stretchr/objx v0.5.2 // indirect 39 | go.mongodb.org/mongo-driver v1.17.3 // indirect 40 | go.uber.org/atomic v1.11.0 // indirect 41 | go.uber.org/multierr v1.11.0 // indirect 42 | go.uber.org/ratelimit v0.3.1 // indirect 43 | go.uber.org/zap v1.27.0 // indirect 44 | golang.org/x/crypto v0.38.0 // indirect 45 | golang.org/x/net v0.40.0 // indirect 46 | golang.org/x/sys v0.33.0 // indirect 47 | golang.org/x/term v0.32.0 // indirect 48 | golang.org/x/text v0.25.0 // indirect 49 | golang.org/x/time v0.11.0 // indirect 50 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect 51 | gopkg.in/yaml.v3 v3.0.1 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= 2 | filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= 3 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 4 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 5 | github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= 6 | github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= 7 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 8 | github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= 9 | github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 10 | github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= 11 | github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= 16 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 17 | github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= 18 | github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= 19 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 20 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 21 | github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg= 22 | github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c= 23 | github.com/gagliardetto/solana-go v1.11.0 h1:g6mR7uRNVT0Y0LVR0bvJNfKV6TyO6oUzBYu03ZmkEmY= 24 | github.com/gagliardetto/solana-go v1.11.0/go.mod h1:afBEcIRrDLJst3lvAahTr63m6W2Ns6dajZxe2irF7Jg= 25 | github.com/gagliardetto/solana-go v1.12.0 h1:rzsbilDPj6p+/DOPXBMLhwMZeBgeRuXjm5zQFCoXgsg= 26 | github.com/gagliardetto/solana-go v1.12.0/go.mod h1:l/qqqIN6qJJPtxW/G1PF4JtcE3Zg2vD2EliZrr9Gn5k= 27 | github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= 28 | github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= 29 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 30 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 31 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 32 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 34 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 35 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 36 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 37 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 38 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 39 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 40 | github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 41 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= 42 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 43 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 44 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 45 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 46 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 47 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 48 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 49 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 50 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 51 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 52 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 53 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 54 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 55 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= 56 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 57 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 58 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 59 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 60 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 61 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 62 | github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= 63 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 64 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 65 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 66 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 67 | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= 68 | github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= 69 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 70 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 71 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 72 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 73 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 74 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 75 | github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk= 76 | github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE= 77 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 78 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 79 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 80 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 81 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 82 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 83 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 84 | github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 h1:RN5mrigyirb8anBEtdjtHFIufXdacyTi6i4KBfeNXeo= 85 | github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= 86 | github.com/streamingfast/logging v0.0.0-20250404134358-92b15d2fbd2e h1:qGVGDR2/bXLyR498un1hvhDQPUJ/m14JBRTJz+c67Bc= 87 | github.com/streamingfast/logging v0.0.0-20250404134358-92b15d2fbd2e/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= 88 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 89 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 90 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 91 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 92 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 93 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 94 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 95 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 96 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 97 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 98 | github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= 99 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 100 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 101 | github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= 102 | github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= 103 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 104 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 105 | go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE= 106 | go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= 107 | go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4= 108 | go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= 109 | go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8= 110 | go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= 111 | go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= 112 | go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= 113 | go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= 114 | go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= 115 | go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= 116 | go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= 117 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 118 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 119 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 120 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 121 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 122 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 123 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 124 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 125 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 126 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 127 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 128 | go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= 129 | go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= 130 | go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= 131 | go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= 132 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 133 | go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= 134 | go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= 135 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 136 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 137 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 138 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 139 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 140 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 141 | golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= 142 | golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= 143 | golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= 144 | golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= 145 | golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= 146 | golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= 147 | golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= 148 | golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 149 | golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= 150 | golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 151 | golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= 152 | golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= 153 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 154 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 155 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 156 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 157 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 158 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 159 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 160 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 161 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 162 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 163 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 164 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 165 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 166 | golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= 167 | golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 168 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 169 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 170 | golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= 171 | golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= 172 | golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= 173 | golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 174 | golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= 175 | golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= 176 | golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= 177 | golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 178 | golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= 179 | golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= 180 | golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= 181 | golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 182 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 183 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 184 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 185 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 186 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 187 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 188 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 189 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 190 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 191 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 192 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 193 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 194 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 195 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 196 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 197 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 198 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 199 | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= 200 | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 201 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 202 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 203 | golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= 204 | golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 205 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 206 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 207 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 208 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 209 | golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= 210 | golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 211 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 212 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 213 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 214 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 215 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 216 | golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= 217 | golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= 218 | golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= 219 | golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= 220 | golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= 221 | golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= 222 | golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= 223 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 224 | golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= 225 | golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 226 | golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= 227 | golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= 228 | golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= 229 | golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 230 | golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= 231 | golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 232 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 233 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 234 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 235 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 236 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 237 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 238 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 239 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 240 | golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= 241 | golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 242 | golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= 243 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 244 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 245 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 246 | golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= 247 | golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= 248 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 249 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 250 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 251 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 252 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= 253 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 254 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 255 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 256 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= 257 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 258 | golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= 259 | golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 260 | golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= 261 | golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 262 | golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= 263 | golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 264 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 265 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 266 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 267 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 268 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 269 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 270 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 271 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 272 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 273 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 274 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 275 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= 276 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= 277 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk= 278 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= 279 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A= 280 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= 281 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c h1:Kqjm4WpoWvwhMPcrAczoTyMySQmYa9Wy2iL6Con4zn8= 282 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= 283 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= 284 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= 285 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= 286 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= 287 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= 288 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 289 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= 290 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 291 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= 292 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 293 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241113154021-e0fbfb71d213 h1:L+WcQXqkyf5MX6g7AudgYEuJjmYbqSRkTmJqGfAPw+Y= 294 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241113154021-e0fbfb71d213/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 295 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f h1:C1QccEa9kUwvMgEUORqQD9S17QesQijxjZ84sO82mfo= 296 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 297 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= 298 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= 299 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 h1:2duwAxN2+k0xLNpjnHTXoMUgnv6VPSp5fiqTuwSxjmI= 300 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= 301 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99 h1:ZSlhAUqC4r8TPzqLXQ0m3upBNZeF+Y8jQ3c4CR3Ujms= 302 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= 303 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e h1:YA5lmSs3zc/5w+xsRcHqpETkaYyK63ivEPzNTcUUlSA= 304 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= 305 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= 306 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= 307 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= 308 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 309 | google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= 310 | google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= 311 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= 312 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 313 | google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= 314 | google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= 315 | google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= 316 | google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= 317 | google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= 318 | google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= 319 | google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= 320 | google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= 321 | google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= 322 | google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= 323 | google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= 324 | google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 325 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 326 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 327 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 328 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 329 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 330 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 331 | google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= 332 | google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 333 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 334 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 335 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 336 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 337 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 338 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 339 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 340 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 341 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 342 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 343 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 344 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 345 | -------------------------------------------------------------------------------- /jito_geyser/geyser.go: -------------------------------------------------------------------------------- 1 | package jito_geyser 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | jito_geyser_pb "github.com/weeaa/goyser/jito_geyser/pb" 7 | "github.com/weeaa/goyser/pkg" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/metadata" 10 | "sync" 11 | ) 12 | 13 | type Client struct { 14 | GrpcConn *grpc.ClientConn 15 | Ctx context.Context 16 | Geyser jito_geyser_pb.GeyserClient 17 | ErrCh chan error 18 | mu sync.Mutex 19 | Stream *StreamClient 20 | s *streamManager 21 | } 22 | 23 | type streamManager struct { 24 | geyser *jito_geyser_pb.GeyserClient 25 | clients map[string]*StreamClient 26 | mu sync.RWMutex 27 | } 28 | 29 | type StreamClient struct { 30 | Ctx context.Context 31 | geyser jito_geyser_pb.GeyserClient 32 | 33 | Accounts map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedAccountUpdate] 34 | PartialAccounts map[string]grpc.ServerStreamingClient[jito_geyser_pb.MaybePartialAccountUpdate] 35 | Slots map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedSlotUpdate] 36 | SlotsEntry map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedSlotEntryUpdate] 37 | Programs map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedAccountUpdate] 38 | Blocks map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedBlockUpdate] 39 | Transactions map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedTransactionUpdate] 40 | 41 | ErrCh chan error 42 | mu sync.RWMutex 43 | } 44 | 45 | // New creates a new Client instance. 46 | func New(ctx context.Context, grpcDialURL string, md metadata.MD) (*Client, error) { 47 | ch := make(chan error) 48 | conn, err := pkg.CreateAndObserveGRPCConn(ctx, ch, grpcDialURL, md) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | geyserClient := jito_geyser_pb.NewGeyserClient(conn) 54 | if md != nil { 55 | ctx = metadata.NewOutgoingContext(ctx, md) 56 | } 57 | 58 | return &Client{ 59 | GrpcConn: conn, 60 | Ctx: ctx, 61 | Geyser: geyserClient, 62 | ErrCh: ch, 63 | s: &streamManager{}, 64 | }, nil 65 | } 66 | 67 | func (c *Client) Close() error { 68 | c.Ctx.Done() 69 | return c.GrpcConn.Close() 70 | } 71 | 72 | // AddStreamClient creates a new Geyser subscribe stream client. You can retrieve it with GetStreamClient. 73 | func (c *Client) AddStreamClient(ctx context.Context, streamName string, opts ...grpc.CallOption) error { 74 | c.s.mu.Lock() 75 | defer c.s.mu.Unlock() 76 | 77 | if _, exists := c.s.clients[streamName]; exists { 78 | return fmt.Errorf("client with name %s already exists", streamName) 79 | } 80 | 81 | streamClient := StreamClient{ 82 | Ctx: ctx, 83 | geyser: c.Geyser, 84 | ErrCh: make(chan error), 85 | mu: sync.RWMutex{}, 86 | 87 | Accounts: make(map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedAccountUpdate]), 88 | PartialAccounts: make(map[string]grpc.ServerStreamingClient[jito_geyser_pb.MaybePartialAccountUpdate]), 89 | Slots: make(map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedSlotUpdate]), 90 | SlotsEntry: make(map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedSlotEntryUpdate]), 91 | Programs: make(map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedAccountUpdate]), 92 | Blocks: make(map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedBlockUpdate]), 93 | Transactions: make(map[string]grpc.ServerStreamingClient[jito_geyser_pb.TimestampedTransactionUpdate]), 94 | } 95 | 96 | c.s.clients[streamName] = &streamClient 97 | 98 | return nil 99 | } 100 | 101 | // GetStreamClient returns a StreamClient for the given streamName from the client's map. 102 | func (c *Client) GetStreamClient(streamName string) *StreamClient { 103 | defer c.s.mu.RUnlock() 104 | c.s.mu.RLock() 105 | return c.s.clients[streamName] 106 | } 107 | 108 | func (c *Client) GetFilters() []string { 109 | filters := make([]string, 0) 110 | for k := range c.s.clients { 111 | filters = append(filters, k) 112 | } 113 | return filters 114 | } 115 | 116 | // GetHeartbeatInterval returns 117 | func (s *StreamClient) GetHeartbeatInterval(opts ...grpc.CallOption) (*jito_geyser_pb.GetHeartbeatIntervalResponse, error) { 118 | return s.geyser.GetHeartbeatInterval(s.Ctx, &jito_geyser_pb.EmptyRequest{}, opts...) 119 | } 120 | 121 | // SubscribeAccounts subscribes to account updates. 122 | func (s *StreamClient) SubscribeAccounts(filterName string, accounts ...string) error { 123 | var err error 124 | s.mu.Lock() 125 | defer s.mu.Unlock() 126 | s.Accounts[filterName], err = s.geyser.SubscribeAccountUpdates(s.Ctx, &jito_geyser_pb.SubscribeAccountUpdatesRequest{ 127 | Accounts: pkg.StrSliceToByteSlices(accounts), 128 | }) 129 | return err 130 | } 131 | 132 | // UnsubscribeAccounts unsubscribes all accounts. 133 | func (s *StreamClient) UnsubscribeAccounts(filterName string) { 134 | s.Accounts[filterName].Context().Done() 135 | delete(s.Accounts, filterName) 136 | } 137 | 138 | // SubscribeSlots subscribes to slot updates. 139 | func (s *StreamClient) SubscribeSlots(filterName string) error { 140 | var err error 141 | s.mu.Lock() 142 | defer s.mu.Unlock() 143 | s.Slots[filterName], err = s.geyser.SubscribeSlotUpdates(s.Ctx, &jito_geyser_pb.SubscribeSlotUpdateRequest{}) 144 | return err 145 | } 146 | 147 | // UnsubscribeSlots unsubscribes from slot updates. 148 | func (s *StreamClient) UnsubscribeSlots(filterName string) { 149 | s.Slots[filterName].Context().Done() 150 | delete(s.Slots, filterName) 151 | } 152 | 153 | func (s *StreamClient) SubscribeSlotsEntry(filterName string) error { 154 | var err error 155 | s.mu.Lock() 156 | defer s.mu.Unlock() 157 | s.SlotsEntry[filterName], err = s.geyser.SubscribeSlotEntryUpdates(s.Ctx, &jito_geyser_pb.SubscribeSlotEntryUpdateRequest{}) 158 | return err 159 | } 160 | 161 | // SubscribeTransaction subscribes to transaction updates. 162 | func (s *StreamClient) SubscribeTransaction(filterName string) error { 163 | var err error 164 | s.mu.Lock() 165 | defer s.mu.Unlock() 166 | s.Transactions[filterName], err = s.geyser.SubscribeTransactionUpdates(s.Ctx, &jito_geyser_pb.SubscribeTransactionUpdatesRequest{}) 167 | return err 168 | } 169 | 170 | // UnsubscribeTransaction unsubscribes from transaction updates. 171 | func (s *StreamClient) UnsubscribeTransaction(filterName string) { 172 | s.Transactions[filterName].Context().Done() 173 | delete(s.Transactions, filterName) 174 | } 175 | 176 | // SubscribePartialAccount subscribes . 177 | func (s *StreamClient) SubscribePartialAccount(filterName string, skipVoteAccounts bool) error { 178 | var err error 179 | s.mu.Lock() 180 | defer s.mu.Unlock() 181 | s.PartialAccounts[filterName], err = s.geyser.SubscribePartialAccountUpdates(s.Ctx, &jito_geyser_pb.SubscribePartialAccountUpdatesRequest{ 182 | SkipVoteAccounts: skipVoteAccounts, 183 | }) 184 | return err 185 | } 186 | 187 | func (s *StreamClient) UnsubscribePartialAccount(filterName string) { 188 | s.PartialAccounts[filterName].Context().Done() 189 | delete(s.PartialAccounts, filterName) 190 | } 191 | 192 | // SubscribeBlocks subscribes to block updates. 193 | func (s *StreamClient) SubscribeBlocks(filterName string) error { 194 | var err error 195 | s.mu.Lock() 196 | defer s.mu.Unlock() 197 | s.Blocks[filterName], err = s.geyser.SubscribeBlockUpdates(s.Ctx, &jito_geyser_pb.SubscribeBlockUpdatesRequest{}) 198 | return err 199 | } 200 | 201 | func (s *StreamClient) UnsubscribeBlocks(filterName string) { 202 | s.Blocks[filterName].Context().Done() 203 | delete(s.Blocks, filterName) 204 | } 205 | 206 | // SubscribePrograms subscribes to program updates. 207 | func (s *StreamClient) SubscribePrograms(filterName string, programs ...string) error { 208 | var err error 209 | s.mu.Lock() 210 | defer s.mu.Unlock() 211 | s.Programs[filterName], err = s.geyser.SubscribeProgramUpdates(s.Ctx, &jito_geyser_pb.SubscribeProgramsUpdatesRequest{ 212 | Programs: pkg.StrSliceToByteSlices(programs), 213 | }) 214 | return err 215 | } 216 | 217 | func (s *StreamClient) UnsubscribePrograms(filterName string) { 218 | s.Programs[filterName].Context().Done() 219 | delete(s.Programs, filterName) 220 | } 221 | 222 | /* 223 | // ConvertTransaction converts a Geyser parsed transaction into an rpc.GetTransactionResult format. 224 | func ConvertTransaction(geyserTx *yellowstone_geyser_pb.SubscribeUpdateTransaction) (*rpc.GetTransactionResult, error) { 225 | 226 | meta := geyserTx.Transaction.Meta 227 | transaction := geyserTx.Transaction.Transaction 228 | 229 | tx := &rpc.GetTransactionResult{ 230 | Transaction: &rpc.TransactionResultEnvelope{}, 231 | Meta: &rpc.TransactionMeta{ 232 | InnerInstructions: make([]rpc.InnerInstruction, 0), 233 | LogMessages: make([]string, 0), 234 | PostBalances: make([]uint64, 0), 235 | PostTokenBalances: make([]rpc.TokenBalance, 0), 236 | PreBalances: make([]uint64, 0), 237 | PreTokenBalances: make([]rpc.TokenBalance, 0), 238 | Rewards: make([]rpc.BlockReward, 0), 239 | LoadedAddresses: rpc.LoadedAddresses{ 240 | ReadOnly: make([]solana.PublicKey, 0), 241 | Writable: make([]solana.PublicKey, 0), 242 | }, 243 | }, 244 | } 245 | 246 | tx.Meta.PreBalances = meta.PreBalances 247 | tx.Meta.PostBalances = meta.PostBalances 248 | tx.Meta.Err = meta.Err 249 | tx.Meta.Fee = meta.Fee 250 | tx.Meta.ComputeUnitsConsumed = meta.ComputeUnitsConsumed 251 | tx.Meta.LogMessages = meta.LogMessages 252 | 253 | for _, preTokenBalance := range meta.PreTokenBalances { 254 | owner := solana.MustPublicKeyFromBase58(preTokenBalance.Owner) 255 | tx.Meta.PreTokenBalances = append(tx.Meta.PreTokenBalances, rpc.TokenBalance{ 256 | AccountIndex: uint16(preTokenBalance.AccountIndex), 257 | Owner: &owner, 258 | Mint: solana.MustPublicKeyFromBase58(preTokenBalance.Mint), 259 | UiTokenAmount: &rpc.UiTokenAmount{ 260 | Amount: preTokenBalance.UiTokenAmount.Amount, 261 | Decimals: uint8(preTokenBalance.UiTokenAmount.Decimals), 262 | UiAmount: &preTokenBalance.UiTokenAmount.UiAmount, 263 | UiAmountString: preTokenBalance.UiTokenAmount.UiAmountString, 264 | }, 265 | }) 266 | } 267 | 268 | for _, postTokenBalance := range meta.PostTokenBalances { 269 | owner := solana.MustPublicKeyFromBase58(postTokenBalance.Owner) 270 | tx.Meta.PostTokenBalances = append(tx.Meta.PostTokenBalances, rpc.TokenBalance{ 271 | AccountIndex: uint16(postTokenBalance.AccountIndex), 272 | Owner: &owner, 273 | Mint: solana.MustPublicKeyFromBase58(postTokenBalance.Mint), 274 | UiTokenAmount: &rpc.UiTokenAmount{ 275 | Amount: postTokenBalance.UiTokenAmount.Amount, 276 | Decimals: uint8(postTokenBalance.UiTokenAmount.Decimals), 277 | UiAmount: &postTokenBalance.UiTokenAmount.UiAmount, 278 | UiAmountString: postTokenBalance.UiTokenAmount.UiAmountString, 279 | }, 280 | }) 281 | } 282 | 283 | for i, innerInst := range meta.InnerInstructions { 284 | tx.Meta.InnerInstructions[i].Index = uint16(innerInst.Index) 285 | for x, inst := range innerInst.Instructions { 286 | accounts, err := bytesToUint16Slice(inst.Accounts) 287 | if err != nil { 288 | return nil, err 289 | } 290 | 291 | tx.Meta.InnerInstructions[i].Instructions[x].Accounts = accounts 292 | tx.Meta.InnerInstructions[i].Instructions[x].ProgramIDIndex = uint16(inst.ProgramIdIndex) 293 | if err = tx.Meta.InnerInstructions[i].Instructions[x].Data.UnmarshalJSON(inst.Data); err != nil { 294 | return nil, err 295 | } 296 | } 297 | } 298 | 299 | for _, reward := range meta.Rewards { 300 | comm, _ := strconv.ParseUint(reward.Commission, 10, 64) 301 | commission := uint8(comm) 302 | tx.Meta.Rewards = append(tx.Meta.Rewards, rpc.BlockReward{ 303 | Pubkey: solana.MustPublicKeyFromBase58(reward.Pubkey), 304 | Lamports: reward.Lamports, 305 | PostBalance: reward.PostBalance, 306 | RewardType: rpc.RewardType(reward.RewardType.String()), 307 | Commission: &commission, 308 | }) 309 | } 310 | 311 | for _, readOnlyAddress := range meta.LoadedReadonlyAddresses { 312 | tx.Meta.LoadedAddresses.ReadOnly = append(tx.Meta.LoadedAddresses.ReadOnly, solana.PublicKeyFromBytes(readOnlyAddress)) 313 | } 314 | 315 | for _, writableAddress := range meta.LoadedWritableAddresses { 316 | tx.Meta.LoadedAddresses.ReadOnly = append(tx.Meta.LoadedAddresses.ReadOnly, solana.PublicKeyFromBytes(writableAddress)) 317 | } 318 | 319 | solTx, err := tx.Transaction.GetTransaction() 320 | if err != nil { 321 | return nil, err 322 | } 323 | 324 | solTx = &solana.Transaction{ 325 | Signatures: make([]solana.Signature, 0), 326 | Message: solana.Message{ 327 | AccountKeys: make(solana.PublicKeySlice, len(transaction.Message.AccountKeys)), 328 | Instructions: make([]solana.CompiledInstruction, len(transaction.Message.Instructions)), 329 | AddressTableLookups: make(solana.MessageAddressTableLookupSlice, len(transaction.Message.AddressTableLookups)), 330 | }, 331 | } 332 | 333 | if transaction.Message.Versioned { 334 | solTx.Message.SetVersion(1) 335 | } 336 | 337 | solTx.Message.RecentBlockhash = solana.HashFromBytes(transaction.Message.RecentBlockhash) 338 | solTx.Message.Header = solana.MessageHeader{ 339 | NumRequiredSignatures: uint8(transaction.Message.Header.NumRequiredSignatures), 340 | NumReadonlySignedAccounts: uint8(transaction.Message.Header.NumReadonlySignedAccounts), 341 | NumReadonlyUnsignedAccounts: uint8(transaction.Message.Header.NumReadonlyUnsignedAccounts), 342 | } 343 | 344 | for _, sig := range transaction.Signatures { 345 | solTx.Signatures = append(solTx.Signatures, solana.SignatureFromBytes(sig)) 346 | } 347 | 348 | for _, table := range transaction.Message.AddressTableLookups { 349 | solTx.Message.AddressTableLookups = append(solTx.Message.AddressTableLookups, solana.MessageAddressTableLookup{ 350 | AccountKey: solana.PublicKeyFromBytes(table.AccountKey), 351 | WritableIndexes: table.WritableIndexes, 352 | ReadonlyIndexes: table.ReadonlyIndexes, 353 | }) 354 | } 355 | 356 | for _, inst := range transaction.Message.Instructions { 357 | accounts, err := bytesToUint16Slice(inst.Accounts) 358 | if err != nil { 359 | return nil, err 360 | } 361 | 362 | solTx.Message.Instructions = append(solTx.Message.Instructions, solana.CompiledInstruction{ 363 | ProgramIDIndex: uint16(inst.ProgramIdIndex), 364 | Accounts: accounts, 365 | Data: inst.Data, 366 | }) 367 | } 368 | 369 | return tx, nil 370 | } 371 | 372 | func BatchConvertTransaction(geyserTxns ...*yellowstone_geyser_pb.SubscribeUpdateTransaction) []*rpc.GetTransactionResult { 373 | txns := make([]*rpc.GetTransactionResult, len(geyserTxns), 0) 374 | for _, tx := range geyserTxns { 375 | txn, err := ConvertTransaction(tx) 376 | if err != nil { 377 | continue 378 | } 379 | txns = append(txns, txn) 380 | } 381 | return txns 382 | } 383 | 384 | // ConvertBlockHash converts a Geyser type block to a github.com/gagliardetto/solana-go Solana block. 385 | func ConvertBlockHash(geyserBlock *yellowstone_geyser_pb.SubscribeUpdateBlock) *rpc.GetBlockResult { 386 | block := new(rpc.GetBlockResult) 387 | 388 | blockTime := solana.UnixTimeSeconds(geyserBlock.BlockTime.Timestamp) 389 | block.BlockTime = &blockTime 390 | block.BlockHeight = &geyserBlock.BlockHeight.BlockHeight 391 | block.Blockhash = solana.Hash{[]byte(geyserBlock.Blockhash)[32]} 392 | block.ParentSlot = geyserBlock.ParentSlot 393 | 394 | for _, reward := range geyserBlock.Rewards.Rewards { 395 | commission, err := strconv.ParseUint(reward.Commission, 10, 8) 396 | if err != nil { 397 | return nil 398 | } 399 | 400 | var rewardType rpc.RewardType 401 | switch reward.RewardType { 402 | case 1: 403 | rewardType = rpc.RewardTypeFee 404 | case 2: 405 | rewardType = rpc.RewardTypeRent 406 | case 3: 407 | rewardType = rpc.RewardTypeStaking 408 | case 4: 409 | rewardType = rpc.RewardTypeVoting 410 | } 411 | 412 | comm := uint8(commission) 413 | block.Rewards = append(block.Rewards, rpc.BlockReward{ 414 | Pubkey: solana.MustPublicKeyFromBase58(reward.Pubkey), 415 | Lamports: reward.Lamports, 416 | PostBalance: reward.PostBalance, 417 | Commission: &comm, 418 | RewardType: rewardType, 419 | }) 420 | } 421 | 422 | return block 423 | } 424 | 425 | func BatchConvertBlockHash(geyserBlocks ...*yellowstone_geyser_pb.SubscribeUpdateBlock) []*rpc.GetBlockResult { 426 | blocks := make([]*rpc.GetBlockResult, len(geyserBlocks), 0) 427 | for _, block := range geyserBlocks { 428 | blocks = append(blocks, ConvertBlockHash(block)) 429 | } 430 | return blocks 431 | } 432 | 433 | func bytesToUint16Slice(data []byte) ([]uint16, error) { 434 | if len(data)%2 != 0 { 435 | return nil, fmt.Errorf("length of byte slice must be even to convert to uint16 slice") 436 | } 437 | 438 | uint16s := make([]uint16, len(data)/2) 439 | 440 | for i := 0; i < len(data); i += 2 { 441 | uint16s[i/2] = binary.LittleEndian.Uint16(data[i : i+2]) 442 | } 443 | 444 | return uint16s, nil 445 | } 446 | */ 447 | -------------------------------------------------------------------------------- /jito_geyser/geyser_test.go: -------------------------------------------------------------------------------- 1 | package jito_geyser 2 | -------------------------------------------------------------------------------- /jito_geyser/pb/confirmed_block.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/weeaa/goyser/jito/jito_pb"; 4 | 5 | package solana.storage.ConfirmedBlock; 6 | 7 | message ConfirmedBlock { 8 | string previous_blockhash = 1; 9 | string blockhash = 2; 10 | uint64 parent_slot = 3; 11 | repeated ConfirmedTransaction transactions = 4; 12 | repeated Reward rewards = 5; 13 | UnixTimestamp block_time = 6; 14 | BlockHeight block_height = 7; 15 | NumPartitions num_partitions = 8; 16 | } 17 | 18 | message ConfirmedTransaction { 19 | Transaction transaction = 1; 20 | TransactionStatusMeta meta = 2; 21 | } 22 | 23 | message Transaction { 24 | repeated bytes signatures = 1; 25 | Message message = 2; 26 | } 27 | 28 | message Message { 29 | MessageHeader header = 1; 30 | repeated bytes account_keys = 2; 31 | bytes recent_blockhash = 3; 32 | repeated CompiledInstruction instructions = 4; 33 | bool versioned = 5; 34 | repeated MessageAddressTableLookup address_table_lookups = 6; 35 | } 36 | 37 | message MessageHeader { 38 | uint32 num_required_signatures = 1; 39 | uint32 num_readonly_signed_accounts = 2; 40 | uint32 num_readonly_unsigned_accounts = 3; 41 | } 42 | 43 | message MessageAddressTableLookup { 44 | bytes account_key = 1; 45 | bytes writable_indexes = 2; 46 | bytes readonly_indexes = 3; 47 | } 48 | 49 | message TransactionStatusMeta { 50 | TransactionError err = 1; 51 | uint64 fee = 2; 52 | repeated uint64 pre_balances = 3; 53 | repeated uint64 post_balances = 4; 54 | repeated InnerInstructions inner_instructions = 5; 55 | bool inner_instructions_none = 10; 56 | repeated string log_messages = 6; 57 | bool log_messages_none = 11; 58 | repeated TokenBalance pre_token_balances = 7; 59 | repeated TokenBalance post_token_balances = 8; 60 | repeated Reward rewards = 9; 61 | repeated bytes loaded_writable_addresses = 12; 62 | repeated bytes loaded_readonly_addresses = 13; 63 | ReturnData return_data = 14; 64 | bool return_data_none = 15; 65 | 66 | // Sum of compute units consumed by all instructions. 67 | // Available since Solana v1.10.35 / v1.11.6. 68 | // Set to `None` for txs executed on earlier versions. 69 | optional uint64 compute_units_consumed = 16; 70 | } 71 | 72 | message TransactionError { 73 | bytes err = 1; 74 | } 75 | 76 | message InnerInstructions { 77 | uint32 index = 1; 78 | repeated InnerInstruction instructions = 2; 79 | } 80 | 81 | message InnerInstruction { 82 | uint32 program_id_index = 1; 83 | bytes accounts = 2; 84 | bytes data = 3; 85 | 86 | // Invocation stack height of an inner instruction. 87 | // Available since Solana v1.14.6 88 | // Set to `None` for txs executed on earlier versions. 89 | optional uint32 stack_height = 4; 90 | } 91 | 92 | message CompiledInstruction { 93 | uint32 program_id_index = 1; 94 | bytes accounts = 2; 95 | bytes data = 3; 96 | } 97 | 98 | message TokenBalance { 99 | uint32 account_index = 1; 100 | string mint = 2; 101 | UiTokenAmount ui_token_amount = 3; 102 | string owner = 4; 103 | string program_id = 5; 104 | } 105 | 106 | message UiTokenAmount { 107 | double ui_amount = 1; 108 | uint32 decimals = 2; 109 | string amount = 3; 110 | string ui_amount_string = 4; 111 | } 112 | 113 | message ReturnData { 114 | bytes program_id = 1; 115 | bytes data = 2; 116 | } 117 | 118 | enum RewardType { 119 | Unspecified = 0; 120 | Fee = 1; 121 | Rent = 2; 122 | Staking = 3; 123 | Voting = 4; 124 | } 125 | 126 | message Reward { 127 | string pubkey = 1; 128 | int64 lamports = 2; 129 | uint64 post_balance = 3; 130 | RewardType reward_type = 4; 131 | string commission = 5; 132 | } 133 | 134 | message Rewards { 135 | repeated Reward rewards = 1; 136 | NumPartitions num_partitions = 2; 137 | } 138 | 139 | message UnixTimestamp { 140 | int64 timestamp = 1; 141 | } 142 | 143 | message BlockHeight { 144 | uint64 block_height = 1; 145 | } 146 | 147 | message NumPartitions { 148 | uint64 num_partitions = 1; 149 | } 150 | -------------------------------------------------------------------------------- /jito_geyser/pb/entries.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.35.2 4 | // protoc v4.25.3 5 | // source: entries.proto 6 | 7 | package jito_pb 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type Entries struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Entries []*Entry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` 29 | } 30 | 31 | func (x *Entries) Reset() { 32 | *x = Entries{} 33 | mi := &file_entries_proto_msgTypes[0] 34 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 35 | ms.StoreMessageInfo(mi) 36 | } 37 | 38 | func (x *Entries) String() string { 39 | return protoimpl.X.MessageStringOf(x) 40 | } 41 | 42 | func (*Entries) ProtoMessage() {} 43 | 44 | func (x *Entries) ProtoReflect() protoreflect.Message { 45 | mi := &file_entries_proto_msgTypes[0] 46 | if x != nil { 47 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 48 | if ms.LoadMessageInfo() == nil { 49 | ms.StoreMessageInfo(mi) 50 | } 51 | return ms 52 | } 53 | return mi.MessageOf(x) 54 | } 55 | 56 | // Deprecated: Use Entries.ProtoReflect.Descriptor instead. 57 | func (*Entries) Descriptor() ([]byte, []int) { 58 | return file_entries_proto_rawDescGZIP(), []int{0} 59 | } 60 | 61 | func (x *Entries) GetEntries() []*Entry { 62 | if x != nil { 63 | return x.Entries 64 | } 65 | return nil 66 | } 67 | 68 | type Entry struct { 69 | state protoimpl.MessageState 70 | sizeCache protoimpl.SizeCache 71 | unknownFields protoimpl.UnknownFields 72 | 73 | Index uint32 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"` 74 | NumHashes uint64 `protobuf:"varint,2,opt,name=num_hashes,json=numHashes,proto3" json:"num_hashes,omitempty"` 75 | Hash []byte `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty"` 76 | NumTransactions uint64 `protobuf:"varint,4,opt,name=num_transactions,json=numTransactions,proto3" json:"num_transactions,omitempty"` 77 | StartingTransactionIndex uint32 `protobuf:"varint,5,opt,name=starting_transaction_index,json=startingTransactionIndex,proto3" json:"starting_transaction_index,omitempty"` 78 | } 79 | 80 | func (x *Entry) Reset() { 81 | *x = Entry{} 82 | mi := &file_entries_proto_msgTypes[1] 83 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 84 | ms.StoreMessageInfo(mi) 85 | } 86 | 87 | func (x *Entry) String() string { 88 | return protoimpl.X.MessageStringOf(x) 89 | } 90 | 91 | func (*Entry) ProtoMessage() {} 92 | 93 | func (x *Entry) ProtoReflect() protoreflect.Message { 94 | mi := &file_entries_proto_msgTypes[1] 95 | if x != nil { 96 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 97 | if ms.LoadMessageInfo() == nil { 98 | ms.StoreMessageInfo(mi) 99 | } 100 | return ms 101 | } 102 | return mi.MessageOf(x) 103 | } 104 | 105 | // Deprecated: Use Entry.ProtoReflect.Descriptor instead. 106 | func (*Entry) Descriptor() ([]byte, []int) { 107 | return file_entries_proto_rawDescGZIP(), []int{1} 108 | } 109 | 110 | func (x *Entry) GetIndex() uint32 { 111 | if x != nil { 112 | return x.Index 113 | } 114 | return 0 115 | } 116 | 117 | func (x *Entry) GetNumHashes() uint64 { 118 | if x != nil { 119 | return x.NumHashes 120 | } 121 | return 0 122 | } 123 | 124 | func (x *Entry) GetHash() []byte { 125 | if x != nil { 126 | return x.Hash 127 | } 128 | return nil 129 | } 130 | 131 | func (x *Entry) GetNumTransactions() uint64 { 132 | if x != nil { 133 | return x.NumTransactions 134 | } 135 | return 0 136 | } 137 | 138 | func (x *Entry) GetStartingTransactionIndex() uint32 { 139 | if x != nil { 140 | return x.StartingTransactionIndex 141 | } 142 | return 0 143 | } 144 | 145 | var File_entries_proto protoreflect.FileDescriptor 146 | 147 | var file_entries_proto_rawDesc = []byte{ 148 | 0x0a, 0x0d, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 149 | 0x16, 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 150 | 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x42, 0x0a, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 151 | 0x65, 0x73, 0x12, 0x37, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 152 | 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x2e, 0x73, 0x74, 0x6f, 153 | 0x72, 0x61, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x45, 0x6e, 0x74, 154 | 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xb9, 0x01, 0x0a, 0x05, 155 | 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 156 | 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 157 | 0x75, 0x6d, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 158 | 0x09, 0x6e, 0x75, 0x6d, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 159 | 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x29, 160 | 0x0a, 0x10, 0x6e, 0x75, 0x6d, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 161 | 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6e, 0x75, 0x6d, 0x54, 0x72, 0x61, 162 | 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x73, 0x74, 0x61, 163 | 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 164 | 0x6e, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x18, 0x73, 165 | 0x74, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 166 | 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 167 | 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x77, 0x65, 0x65, 0x61, 0x61, 0x2f, 0x67, 0x6f, 0x79, 0x73, 168 | 0x65, 0x72, 0x2f, 0x6a, 0x69, 0x74, 0x6f, 0x2f, 0x6a, 0x69, 0x74, 0x6f, 0x5f, 0x70, 0x62, 0x62, 169 | 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 170 | } 171 | 172 | var ( 173 | file_entries_proto_rawDescOnce sync.Once 174 | file_entries_proto_rawDescData = file_entries_proto_rawDesc 175 | ) 176 | 177 | func file_entries_proto_rawDescGZIP() []byte { 178 | file_entries_proto_rawDescOnce.Do(func() { 179 | file_entries_proto_rawDescData = protoimpl.X.CompressGZIP(file_entries_proto_rawDescData) 180 | }) 181 | return file_entries_proto_rawDescData 182 | } 183 | 184 | var file_entries_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 185 | var file_entries_proto_goTypes = []any{ 186 | (*Entries)(nil), // 0: solana.storage.Entries.Entries 187 | (*Entry)(nil), // 1: solana.storage.Entries.Entry 188 | } 189 | var file_entries_proto_depIdxs = []int32{ 190 | 1, // 0: solana.storage.Entries.Entries.entries:type_name -> solana.storage.Entries.Entry 191 | 1, // [1:1] is the sub-list for method output_type 192 | 1, // [1:1] is the sub-list for method input_type 193 | 1, // [1:1] is the sub-list for extension type_name 194 | 1, // [1:1] is the sub-list for extension extendee 195 | 0, // [0:1] is the sub-list for field type_name 196 | } 197 | 198 | func init() { file_entries_proto_init() } 199 | func file_entries_proto_init() { 200 | if File_entries_proto != nil { 201 | return 202 | } 203 | type x struct{} 204 | out := protoimpl.TypeBuilder{ 205 | File: protoimpl.DescBuilder{ 206 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 207 | RawDescriptor: file_entries_proto_rawDesc, 208 | NumEnums: 0, 209 | NumMessages: 2, 210 | NumExtensions: 0, 211 | NumServices: 0, 212 | }, 213 | GoTypes: file_entries_proto_goTypes, 214 | DependencyIndexes: file_entries_proto_depIdxs, 215 | MessageInfos: file_entries_proto_msgTypes, 216 | }.Build() 217 | File_entries_proto = out.File 218 | file_entries_proto_rawDesc = nil 219 | file_entries_proto_goTypes = nil 220 | file_entries_proto_depIdxs = nil 221 | } 222 | -------------------------------------------------------------------------------- /jito_geyser/pb/entries.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/weeaa/goyser/jito/jito_pb"; 4 | 5 | package solana.storage.Entries; 6 | 7 | message Entries { 8 | repeated Entry entries = 1; 9 | } 10 | 11 | message Entry { 12 | uint32 index = 1; 13 | uint64 num_hashes = 2; 14 | bytes hash = 3; 15 | uint64 num_transactions = 4; 16 | uint32 starting_transaction_index = 5; 17 | } 18 | -------------------------------------------------------------------------------- /jito_geyser/pb/geyser.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/weeaa/goyser/jito/jito_pb"; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | import "confirmed_block.proto"; 7 | 8 | package solana.geyser; 9 | 10 | message PartialAccountUpdate { 11 | // Slot this update occurred. 12 | uint64 slot = 1; 13 | 14 | // Account's pubkey. 15 | bytes pubkey = 2; 16 | 17 | // Account's owner. 18 | bytes owner = 3; 19 | 20 | // Flags whether this update was streamed as part of startup, hence not a realtime update. 21 | bool is_startup = 4; 22 | 23 | // A monotonically increasing number specifying the order of this update. 24 | // Can be used to determine what the latest update for an account was at 25 | // a given slot, assuming there were multiple updates. 26 | uint64 seq = 5; 27 | 28 | // Transaction signature that caused this update. 29 | optional string tx_signature = 6; 30 | 31 | // AccountReplica version. 32 | uint32 replica_version = 7; 33 | } 34 | 35 | message AccountUpdate { 36 | // Slot this update occurred. 37 | uint64 slot = 1; 38 | 39 | // Account's pubkey. 40 | bytes pubkey = 2; 41 | 42 | // Account's lamports post update. 43 | uint64 lamports = 3; 44 | 45 | // Account's owner. 46 | bytes owner = 4; 47 | 48 | // Flags whether an account is executable. 49 | bool is_executable = 5; 50 | 51 | // The epoch at which this account will next owe rent. 52 | uint64 rent_epoch = 6; 53 | 54 | // Account's data post update. 55 | bytes data = 7; 56 | 57 | // A monotonically increasing number specifying the order of this update. 58 | // Can be used to determine what the latest update for an account was at 59 | // a given slot, assuming there were multiple updates. 60 | uint64 seq = 8; 61 | 62 | // Flags whether this update was streamed as part of startup i.e. not a real-time update. 63 | bool is_startup = 9; 64 | 65 | // Transaction signature that caused this update. 66 | optional string tx_signature = 10; 67 | 68 | // AccountReplica version. 69 | uint32 replica_version = 11; 70 | } 71 | 72 | enum SlotUpdateStatus { 73 | CONFIRMED = 0; 74 | PROCESSED = 1; 75 | ROOTED = 2; 76 | } 77 | 78 | message SlotUpdate { 79 | uint64 slot = 1; 80 | optional uint64 parent_slot = 2; 81 | SlotUpdateStatus status = 3; 82 | } 83 | 84 | message TimestampedSlotUpdate { 85 | // Time at which the message was generated 86 | google.protobuf.Timestamp ts = 1; 87 | // Slot update 88 | SlotUpdate slot_update = 2; 89 | } 90 | 91 | message TimestampedAccountUpdate { 92 | // Time at which the message was generated 93 | google.protobuf.Timestamp ts = 1; 94 | // Account update 95 | AccountUpdate account_update = 2; 96 | } 97 | 98 | message SubscribeTransactionUpdatesRequest {} 99 | 100 | message SubscribeBlockUpdatesRequest {} 101 | 102 | message MaybePartialAccountUpdate { 103 | oneof msg { 104 | PartialAccountUpdate partial_account_update = 1; 105 | Heartbeat hb = 2; 106 | } 107 | } 108 | 109 | message Heartbeat {} 110 | message EmptyRequest {} 111 | 112 | message BlockUpdate { 113 | uint64 slot = 1; 114 | string blockhash = 2; 115 | repeated storage.ConfirmedBlock.Reward rewards = 3; 116 | google.protobuf.Timestamp block_time = 4; 117 | optional uint64 block_height = 5; 118 | optional uint64 executed_transaction_count = 6; 119 | optional uint64 entry_count = 7; 120 | } 121 | 122 | message TimestampedBlockUpdate { 123 | // Time at which the message was generated 124 | google.protobuf.Timestamp ts = 1; 125 | // Block contents 126 | BlockUpdate block_update = 2; 127 | } 128 | 129 | message TransactionUpdate { 130 | uint64 slot = 1; 131 | string signature = 2; 132 | bool is_vote = 3; 133 | uint64 tx_idx = 4; 134 | storage.ConfirmedBlock.ConfirmedTransaction tx = 5; 135 | } 136 | 137 | message TimestampedTransactionUpdate { 138 | google.protobuf.Timestamp ts = 1; 139 | TransactionUpdate transaction = 2; 140 | } 141 | 142 | 143 | message SubscribeSlotUpdateRequest {} 144 | 145 | message SubscribeAccountUpdatesRequest { 146 | repeated bytes accounts = 1; 147 | } 148 | 149 | message SubscribeProgramsUpdatesRequest { 150 | repeated bytes programs = 1; 151 | } 152 | 153 | message SubscribePartialAccountUpdatesRequest { 154 | // If true, will not stream vote account updates. 155 | bool skip_vote_accounts = 1; 156 | } 157 | 158 | message GetHeartbeatIntervalResponse { 159 | uint64 heartbeat_interval_ms = 1; 160 | } 161 | 162 | /// Modelled based off of https://github.com/solana-labs/solana/blob/v2.0/geyser-plugin-interface/src/geyser_plugin_interface.rs#L210 163 | /// If more details are needed can extend this structure. 164 | message SlotEntryUpdate { 165 | // The slot number of the block containing this Entry 166 | uint64 slot = 1; 167 | // The Entry's index in the block 168 | uint64 index = 2; 169 | // The number of executed transactions in the Entry 170 | // If this number is zero, we can assume its a tick entry 171 | uint64 executed_transaction_count = 3; 172 | } 173 | 174 | message TimestampedSlotEntryUpdate { 175 | // Time at which the message was generated 176 | // Send relative timestamp in micros using u32 to reduce overhead. Provides ~71 mins of accuracy between sender and receiver 177 | // See [compact_timestamp::to_system_time] 178 | uint32 ts = 1; 179 | // SlotEntryUpdate update 180 | SlotEntryUpdate entry_update = 2; 181 | } 182 | 183 | message SubscribeSlotEntryUpdateRequest {} 184 | 185 | // The following __must__ be assumed: 186 | // - Clients may receive data for slots out of order. 187 | // - Clients may receive account updates for a given slot out of order. 188 | service Geyser { 189 | // Invoke to get the expected heartbeat interval. 190 | rpc GetHeartbeatInterval(EmptyRequest) returns (GetHeartbeatIntervalResponse) {} 191 | 192 | // Subscribes to account updates in the accounts database; additionally pings clients with empty heartbeats. 193 | // Upon initially connecting the client can expect a `highest_write_slot` set in the http headers. 194 | // Subscribe to account updates 195 | rpc SubscribeAccountUpdates(SubscribeAccountUpdatesRequest) returns (stream TimestampedAccountUpdate) {} 196 | 197 | // Subscribes to updates given a list of program IDs. When an account update comes in that's owned by a provided 198 | // program id, one will receive an update 199 | rpc SubscribeProgramUpdates(SubscribeProgramsUpdatesRequest) returns (stream TimestampedAccountUpdate) {} 200 | 201 | // Functions similarly to `SubscribeAccountUpdates`, but consumes less bandwidth. 202 | // Returns the highest slot seen thus far in the http headers named `highest-write-slot`. 203 | rpc SubscribePartialAccountUpdates(SubscribePartialAccountUpdatesRequest) returns (stream MaybePartialAccountUpdate) {} 204 | 205 | // Subscribes to slot updates. 206 | // Returns the highest slot seen thus far in the http headers named `highest-write-slot`. 207 | rpc SubscribeSlotUpdates(SubscribeSlotUpdateRequest) returns (stream TimestampedSlotUpdate) {} 208 | 209 | // Subscribes to transaction updates. 210 | rpc SubscribeTransactionUpdates(SubscribeTransactionUpdatesRequest) returns (stream TimestampedTransactionUpdate) {} 211 | 212 | // Subscribes to block updates. 213 | rpc SubscribeBlockUpdates(SubscribeBlockUpdatesRequest) returns (stream TimestampedBlockUpdate) {} 214 | 215 | // Subscribes to entry updates. 216 | // Returns the highest slot seen thus far and the entry index corresponding to the tick 217 | rpc SubscribeSlotEntryUpdates(SubscribeSlotEntryUpdateRequest) returns (stream TimestampedSlotEntryUpdate) {} 218 | } 219 | -------------------------------------------------------------------------------- /jito_geyser/pb/geyser_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.5.1 4 | // - protoc v4.25.3 5 | // source: geyser.proto 6 | 7 | package jito_pb 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.64.0 or later. 19 | const _ = grpc.SupportPackageIsVersion9 20 | 21 | const ( 22 | Geyser_GetHeartbeatInterval_FullMethodName = "/solana.geyser.Geyser/GetHeartbeatInterval" 23 | Geyser_SubscribeAccountUpdates_FullMethodName = "/solana.geyser.Geyser/SubscribeAccountUpdates" 24 | Geyser_SubscribeProgramUpdates_FullMethodName = "/solana.geyser.Geyser/SubscribeProgramUpdates" 25 | Geyser_SubscribePartialAccountUpdates_FullMethodName = "/solana.geyser.Geyser/SubscribePartialAccountUpdates" 26 | Geyser_SubscribeSlotUpdates_FullMethodName = "/solana.geyser.Geyser/SubscribeSlotUpdates" 27 | Geyser_SubscribeTransactionUpdates_FullMethodName = "/solana.geyser.Geyser/SubscribeTransactionUpdates" 28 | Geyser_SubscribeBlockUpdates_FullMethodName = "/solana.geyser.Geyser/SubscribeBlockUpdates" 29 | Geyser_SubscribeSlotEntryUpdates_FullMethodName = "/solana.geyser.Geyser/SubscribeSlotEntryUpdates" 30 | ) 31 | 32 | // GeyserClient is the client API for Geyser service. 33 | // 34 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 35 | // 36 | // The following __must__ be assumed: 37 | // - Clients may receive data for slots out of order. 38 | // - Clients may receive account updates for a given slot out of order. 39 | type GeyserClient interface { 40 | // Invoke to get the expected heartbeat interval. 41 | GetHeartbeatInterval(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*GetHeartbeatIntervalResponse, error) 42 | // Subscribes to account updates in the accounts database; additionally pings clients with empty heartbeats. 43 | // Upon initially connecting the client can expect a `highest_write_slot` set in the http headers. 44 | // Subscribe to account updates 45 | SubscribeAccountUpdates(ctx context.Context, in *SubscribeAccountUpdatesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedAccountUpdate], error) 46 | // Subscribes to updates given a list of program IDs. When an account update comes in that's owned by a provided 47 | // program id, one will receive an update 48 | SubscribeProgramUpdates(ctx context.Context, in *SubscribeProgramsUpdatesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedAccountUpdate], error) 49 | // Functions similarly to `SubscribeAccountUpdates`, but consumes less bandwidth. 50 | // Returns the highest slot seen thus far in the http headers named `highest-write-slot`. 51 | SubscribePartialAccountUpdates(ctx context.Context, in *SubscribePartialAccountUpdatesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[MaybePartialAccountUpdate], error) 52 | // Subscribes to slot updates. 53 | // Returns the highest slot seen thus far in the http headers named `highest-write-slot`. 54 | SubscribeSlotUpdates(ctx context.Context, in *SubscribeSlotUpdateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedSlotUpdate], error) 55 | // Subscribes to transaction updates. 56 | SubscribeTransactionUpdates(ctx context.Context, in *SubscribeTransactionUpdatesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedTransactionUpdate], error) 57 | // Subscribes to block updates. 58 | SubscribeBlockUpdates(ctx context.Context, in *SubscribeBlockUpdatesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedBlockUpdate], error) 59 | // Subscribes to entry updates. 60 | // Returns the highest slot seen thus far and the entry index corresponding to the tick 61 | SubscribeSlotEntryUpdates(ctx context.Context, in *SubscribeSlotEntryUpdateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedSlotEntryUpdate], error) 62 | } 63 | 64 | type geyserClient struct { 65 | cc grpc.ClientConnInterface 66 | } 67 | 68 | func NewGeyserClient(cc grpc.ClientConnInterface) GeyserClient { 69 | return &geyserClient{cc} 70 | } 71 | 72 | func (c *geyserClient) GetHeartbeatInterval(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*GetHeartbeatIntervalResponse, error) { 73 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 74 | out := new(GetHeartbeatIntervalResponse) 75 | err := c.cc.Invoke(ctx, Geyser_GetHeartbeatInterval_FullMethodName, in, out, cOpts...) 76 | if err != nil { 77 | return nil, err 78 | } 79 | return out, nil 80 | } 81 | 82 | func (c *geyserClient) SubscribeAccountUpdates(ctx context.Context, in *SubscribeAccountUpdatesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedAccountUpdate], error) { 83 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 84 | stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[0], Geyser_SubscribeAccountUpdates_FullMethodName, cOpts...) 85 | if err != nil { 86 | return nil, err 87 | } 88 | x := &grpc.GenericClientStream[SubscribeAccountUpdatesRequest, TimestampedAccountUpdate]{ClientStream: stream} 89 | if err := x.ClientStream.SendMsg(in); err != nil { 90 | return nil, err 91 | } 92 | if err := x.ClientStream.CloseSend(); err != nil { 93 | return nil, err 94 | } 95 | return x, nil 96 | } 97 | 98 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 99 | type Geyser_SubscribeAccountUpdatesClient = grpc.ServerStreamingClient[TimestampedAccountUpdate] 100 | 101 | func (c *geyserClient) SubscribeProgramUpdates(ctx context.Context, in *SubscribeProgramsUpdatesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedAccountUpdate], error) { 102 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 103 | stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[1], Geyser_SubscribeProgramUpdates_FullMethodName, cOpts...) 104 | if err != nil { 105 | return nil, err 106 | } 107 | x := &grpc.GenericClientStream[SubscribeProgramsUpdatesRequest, TimestampedAccountUpdate]{ClientStream: stream} 108 | if err := x.ClientStream.SendMsg(in); err != nil { 109 | return nil, err 110 | } 111 | if err := x.ClientStream.CloseSend(); err != nil { 112 | return nil, err 113 | } 114 | return x, nil 115 | } 116 | 117 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 118 | type Geyser_SubscribeProgramUpdatesClient = grpc.ServerStreamingClient[TimestampedAccountUpdate] 119 | 120 | func (c *geyserClient) SubscribePartialAccountUpdates(ctx context.Context, in *SubscribePartialAccountUpdatesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[MaybePartialAccountUpdate], error) { 121 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 122 | stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[2], Geyser_SubscribePartialAccountUpdates_FullMethodName, cOpts...) 123 | if err != nil { 124 | return nil, err 125 | } 126 | x := &grpc.GenericClientStream[SubscribePartialAccountUpdatesRequest, MaybePartialAccountUpdate]{ClientStream: stream} 127 | if err := x.ClientStream.SendMsg(in); err != nil { 128 | return nil, err 129 | } 130 | if err := x.ClientStream.CloseSend(); err != nil { 131 | return nil, err 132 | } 133 | return x, nil 134 | } 135 | 136 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 137 | type Geyser_SubscribePartialAccountUpdatesClient = grpc.ServerStreamingClient[MaybePartialAccountUpdate] 138 | 139 | func (c *geyserClient) SubscribeSlotUpdates(ctx context.Context, in *SubscribeSlotUpdateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedSlotUpdate], error) { 140 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 141 | stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[3], Geyser_SubscribeSlotUpdates_FullMethodName, cOpts...) 142 | if err != nil { 143 | return nil, err 144 | } 145 | x := &grpc.GenericClientStream[SubscribeSlotUpdateRequest, TimestampedSlotUpdate]{ClientStream: stream} 146 | if err := x.ClientStream.SendMsg(in); err != nil { 147 | return nil, err 148 | } 149 | if err := x.ClientStream.CloseSend(); err != nil { 150 | return nil, err 151 | } 152 | return x, nil 153 | } 154 | 155 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 156 | type Geyser_SubscribeSlotUpdatesClient = grpc.ServerStreamingClient[TimestampedSlotUpdate] 157 | 158 | func (c *geyserClient) SubscribeTransactionUpdates(ctx context.Context, in *SubscribeTransactionUpdatesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedTransactionUpdate], error) { 159 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 160 | stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[4], Geyser_SubscribeTransactionUpdates_FullMethodName, cOpts...) 161 | if err != nil { 162 | return nil, err 163 | } 164 | x := &grpc.GenericClientStream[SubscribeTransactionUpdatesRequest, TimestampedTransactionUpdate]{ClientStream: stream} 165 | if err := x.ClientStream.SendMsg(in); err != nil { 166 | return nil, err 167 | } 168 | if err := x.ClientStream.CloseSend(); err != nil { 169 | return nil, err 170 | } 171 | return x, nil 172 | } 173 | 174 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 175 | type Geyser_SubscribeTransactionUpdatesClient = grpc.ServerStreamingClient[TimestampedTransactionUpdate] 176 | 177 | func (c *geyserClient) SubscribeBlockUpdates(ctx context.Context, in *SubscribeBlockUpdatesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedBlockUpdate], error) { 178 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 179 | stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[5], Geyser_SubscribeBlockUpdates_FullMethodName, cOpts...) 180 | if err != nil { 181 | return nil, err 182 | } 183 | x := &grpc.GenericClientStream[SubscribeBlockUpdatesRequest, TimestampedBlockUpdate]{ClientStream: stream} 184 | if err := x.ClientStream.SendMsg(in); err != nil { 185 | return nil, err 186 | } 187 | if err := x.ClientStream.CloseSend(); err != nil { 188 | return nil, err 189 | } 190 | return x, nil 191 | } 192 | 193 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 194 | type Geyser_SubscribeBlockUpdatesClient = grpc.ServerStreamingClient[TimestampedBlockUpdate] 195 | 196 | func (c *geyserClient) SubscribeSlotEntryUpdates(ctx context.Context, in *SubscribeSlotEntryUpdateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TimestampedSlotEntryUpdate], error) { 197 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 198 | stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[6], Geyser_SubscribeSlotEntryUpdates_FullMethodName, cOpts...) 199 | if err != nil { 200 | return nil, err 201 | } 202 | x := &grpc.GenericClientStream[SubscribeSlotEntryUpdateRequest, TimestampedSlotEntryUpdate]{ClientStream: stream} 203 | if err := x.ClientStream.SendMsg(in); err != nil { 204 | return nil, err 205 | } 206 | if err := x.ClientStream.CloseSend(); err != nil { 207 | return nil, err 208 | } 209 | return x, nil 210 | } 211 | 212 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 213 | type Geyser_SubscribeSlotEntryUpdatesClient = grpc.ServerStreamingClient[TimestampedSlotEntryUpdate] 214 | 215 | // GeyserServer is the server API for Geyser service. 216 | // All implementations must embed UnimplementedGeyserServer 217 | // for forward compatibility. 218 | // 219 | // The following __must__ be assumed: 220 | // - Clients may receive data for slots out of order. 221 | // - Clients may receive account updates for a given slot out of order. 222 | type GeyserServer interface { 223 | // Invoke to get the expected heartbeat interval. 224 | GetHeartbeatInterval(context.Context, *EmptyRequest) (*GetHeartbeatIntervalResponse, error) 225 | // Subscribes to account updates in the accounts database; additionally pings clients with empty heartbeats. 226 | // Upon initially connecting the client can expect a `highest_write_slot` set in the http headers. 227 | // Subscribe to account updates 228 | SubscribeAccountUpdates(*SubscribeAccountUpdatesRequest, grpc.ServerStreamingServer[TimestampedAccountUpdate]) error 229 | // Subscribes to updates given a list of program IDs. When an account update comes in that's owned by a provided 230 | // program id, one will receive an update 231 | SubscribeProgramUpdates(*SubscribeProgramsUpdatesRequest, grpc.ServerStreamingServer[TimestampedAccountUpdate]) error 232 | // Functions similarly to `SubscribeAccountUpdates`, but consumes less bandwidth. 233 | // Returns the highest slot seen thus far in the http headers named `highest-write-slot`. 234 | SubscribePartialAccountUpdates(*SubscribePartialAccountUpdatesRequest, grpc.ServerStreamingServer[MaybePartialAccountUpdate]) error 235 | // Subscribes to slot updates. 236 | // Returns the highest slot seen thus far in the http headers named `highest-write-slot`. 237 | SubscribeSlotUpdates(*SubscribeSlotUpdateRequest, grpc.ServerStreamingServer[TimestampedSlotUpdate]) error 238 | // Subscribes to transaction updates. 239 | SubscribeTransactionUpdates(*SubscribeTransactionUpdatesRequest, grpc.ServerStreamingServer[TimestampedTransactionUpdate]) error 240 | // Subscribes to block updates. 241 | SubscribeBlockUpdates(*SubscribeBlockUpdatesRequest, grpc.ServerStreamingServer[TimestampedBlockUpdate]) error 242 | // Subscribes to entry updates. 243 | // Returns the highest slot seen thus far and the entry index corresponding to the tick 244 | SubscribeSlotEntryUpdates(*SubscribeSlotEntryUpdateRequest, grpc.ServerStreamingServer[TimestampedSlotEntryUpdate]) error 245 | mustEmbedUnimplementedGeyserServer() 246 | } 247 | 248 | // UnimplementedGeyserServer must be embedded to have 249 | // forward compatible implementations. 250 | // 251 | // NOTE: this should be embedded by value instead of pointer to avoid a nil 252 | // pointer dereference when methods are called. 253 | type UnimplementedGeyserServer struct{} 254 | 255 | func (UnimplementedGeyserServer) GetHeartbeatInterval(context.Context, *EmptyRequest) (*GetHeartbeatIntervalResponse, error) { 256 | return nil, status.Errorf(codes.Unimplemented, "method GetHeartbeatInterval not implemented") 257 | } 258 | func (UnimplementedGeyserServer) SubscribeAccountUpdates(*SubscribeAccountUpdatesRequest, grpc.ServerStreamingServer[TimestampedAccountUpdate]) error { 259 | return status.Errorf(codes.Unimplemented, "method SubscribeAccountUpdates not implemented") 260 | } 261 | func (UnimplementedGeyserServer) SubscribeProgramUpdates(*SubscribeProgramsUpdatesRequest, grpc.ServerStreamingServer[TimestampedAccountUpdate]) error { 262 | return status.Errorf(codes.Unimplemented, "method SubscribeProgramUpdates not implemented") 263 | } 264 | func (UnimplementedGeyserServer) SubscribePartialAccountUpdates(*SubscribePartialAccountUpdatesRequest, grpc.ServerStreamingServer[MaybePartialAccountUpdate]) error { 265 | return status.Errorf(codes.Unimplemented, "method SubscribePartialAccountUpdates not implemented") 266 | } 267 | func (UnimplementedGeyserServer) SubscribeSlotUpdates(*SubscribeSlotUpdateRequest, grpc.ServerStreamingServer[TimestampedSlotUpdate]) error { 268 | return status.Errorf(codes.Unimplemented, "method SubscribeSlotUpdates not implemented") 269 | } 270 | func (UnimplementedGeyserServer) SubscribeTransactionUpdates(*SubscribeTransactionUpdatesRequest, grpc.ServerStreamingServer[TimestampedTransactionUpdate]) error { 271 | return status.Errorf(codes.Unimplemented, "method SubscribeTransactionUpdates not implemented") 272 | } 273 | func (UnimplementedGeyserServer) SubscribeBlockUpdates(*SubscribeBlockUpdatesRequest, grpc.ServerStreamingServer[TimestampedBlockUpdate]) error { 274 | return status.Errorf(codes.Unimplemented, "method SubscribeBlockUpdates not implemented") 275 | } 276 | func (UnimplementedGeyserServer) SubscribeSlotEntryUpdates(*SubscribeSlotEntryUpdateRequest, grpc.ServerStreamingServer[TimestampedSlotEntryUpdate]) error { 277 | return status.Errorf(codes.Unimplemented, "method SubscribeSlotEntryUpdates not implemented") 278 | } 279 | func (UnimplementedGeyserServer) mustEmbedUnimplementedGeyserServer() {} 280 | func (UnimplementedGeyserServer) testEmbeddedByValue() {} 281 | 282 | // UnsafeGeyserServer may be embedded to opt out of forward compatibility for this service. 283 | // Use of this interface is not recommended, as added methods to GeyserServer will 284 | // result in compilation errors. 285 | type UnsafeGeyserServer interface { 286 | mustEmbedUnimplementedGeyserServer() 287 | } 288 | 289 | func RegisterGeyserServer(s grpc.ServiceRegistrar, srv GeyserServer) { 290 | // If the following call pancis, it indicates UnimplementedGeyserServer was 291 | // embedded by pointer and is nil. This will cause panics if an 292 | // unimplemented method is ever invoked, so we test this at initialization 293 | // time to prevent it from happening at runtime later due to I/O. 294 | if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { 295 | t.testEmbeddedByValue() 296 | } 297 | s.RegisterService(&Geyser_ServiceDesc, srv) 298 | } 299 | 300 | func _Geyser_GetHeartbeatInterval_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 301 | in := new(EmptyRequest) 302 | if err := dec(in); err != nil { 303 | return nil, err 304 | } 305 | if interceptor == nil { 306 | return srv.(GeyserServer).GetHeartbeatInterval(ctx, in) 307 | } 308 | info := &grpc.UnaryServerInfo{ 309 | Server: srv, 310 | FullMethod: Geyser_GetHeartbeatInterval_FullMethodName, 311 | } 312 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 313 | return srv.(GeyserServer).GetHeartbeatInterval(ctx, req.(*EmptyRequest)) 314 | } 315 | return interceptor(ctx, in, info, handler) 316 | } 317 | 318 | func _Geyser_SubscribeAccountUpdates_Handler(srv interface{}, stream grpc.ServerStream) error { 319 | m := new(SubscribeAccountUpdatesRequest) 320 | if err := stream.RecvMsg(m); err != nil { 321 | return err 322 | } 323 | return srv.(GeyserServer).SubscribeAccountUpdates(m, &grpc.GenericServerStream[SubscribeAccountUpdatesRequest, TimestampedAccountUpdate]{ServerStream: stream}) 324 | } 325 | 326 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 327 | type Geyser_SubscribeAccountUpdatesServer = grpc.ServerStreamingServer[TimestampedAccountUpdate] 328 | 329 | func _Geyser_SubscribeProgramUpdates_Handler(srv interface{}, stream grpc.ServerStream) error { 330 | m := new(SubscribeProgramsUpdatesRequest) 331 | if err := stream.RecvMsg(m); err != nil { 332 | return err 333 | } 334 | return srv.(GeyserServer).SubscribeProgramUpdates(m, &grpc.GenericServerStream[SubscribeProgramsUpdatesRequest, TimestampedAccountUpdate]{ServerStream: stream}) 335 | } 336 | 337 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 338 | type Geyser_SubscribeProgramUpdatesServer = grpc.ServerStreamingServer[TimestampedAccountUpdate] 339 | 340 | func _Geyser_SubscribePartialAccountUpdates_Handler(srv interface{}, stream grpc.ServerStream) error { 341 | m := new(SubscribePartialAccountUpdatesRequest) 342 | if err := stream.RecvMsg(m); err != nil { 343 | return err 344 | } 345 | return srv.(GeyserServer).SubscribePartialAccountUpdates(m, &grpc.GenericServerStream[SubscribePartialAccountUpdatesRequest, MaybePartialAccountUpdate]{ServerStream: stream}) 346 | } 347 | 348 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 349 | type Geyser_SubscribePartialAccountUpdatesServer = grpc.ServerStreamingServer[MaybePartialAccountUpdate] 350 | 351 | func _Geyser_SubscribeSlotUpdates_Handler(srv interface{}, stream grpc.ServerStream) error { 352 | m := new(SubscribeSlotUpdateRequest) 353 | if err := stream.RecvMsg(m); err != nil { 354 | return err 355 | } 356 | return srv.(GeyserServer).SubscribeSlotUpdates(m, &grpc.GenericServerStream[SubscribeSlotUpdateRequest, TimestampedSlotUpdate]{ServerStream: stream}) 357 | } 358 | 359 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 360 | type Geyser_SubscribeSlotUpdatesServer = grpc.ServerStreamingServer[TimestampedSlotUpdate] 361 | 362 | func _Geyser_SubscribeTransactionUpdates_Handler(srv interface{}, stream grpc.ServerStream) error { 363 | m := new(SubscribeTransactionUpdatesRequest) 364 | if err := stream.RecvMsg(m); err != nil { 365 | return err 366 | } 367 | return srv.(GeyserServer).SubscribeTransactionUpdates(m, &grpc.GenericServerStream[SubscribeTransactionUpdatesRequest, TimestampedTransactionUpdate]{ServerStream: stream}) 368 | } 369 | 370 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 371 | type Geyser_SubscribeTransactionUpdatesServer = grpc.ServerStreamingServer[TimestampedTransactionUpdate] 372 | 373 | func _Geyser_SubscribeBlockUpdates_Handler(srv interface{}, stream grpc.ServerStream) error { 374 | m := new(SubscribeBlockUpdatesRequest) 375 | if err := stream.RecvMsg(m); err != nil { 376 | return err 377 | } 378 | return srv.(GeyserServer).SubscribeBlockUpdates(m, &grpc.GenericServerStream[SubscribeBlockUpdatesRequest, TimestampedBlockUpdate]{ServerStream: stream}) 379 | } 380 | 381 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 382 | type Geyser_SubscribeBlockUpdatesServer = grpc.ServerStreamingServer[TimestampedBlockUpdate] 383 | 384 | func _Geyser_SubscribeSlotEntryUpdates_Handler(srv interface{}, stream grpc.ServerStream) error { 385 | m := new(SubscribeSlotEntryUpdateRequest) 386 | if err := stream.RecvMsg(m); err != nil { 387 | return err 388 | } 389 | return srv.(GeyserServer).SubscribeSlotEntryUpdates(m, &grpc.GenericServerStream[SubscribeSlotEntryUpdateRequest, TimestampedSlotEntryUpdate]{ServerStream: stream}) 390 | } 391 | 392 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 393 | type Geyser_SubscribeSlotEntryUpdatesServer = grpc.ServerStreamingServer[TimestampedSlotEntryUpdate] 394 | 395 | // Geyser_ServiceDesc is the grpc.ServiceDesc for Geyser service. 396 | // It's only intended for direct use with grpc.RegisterService, 397 | // and not to be introspected or modified (even as a copy) 398 | var Geyser_ServiceDesc = grpc.ServiceDesc{ 399 | ServiceName: "solana.geyser.Geyser", 400 | HandlerType: (*GeyserServer)(nil), 401 | Methods: []grpc.MethodDesc{ 402 | { 403 | MethodName: "GetHeartbeatInterval", 404 | Handler: _Geyser_GetHeartbeatInterval_Handler, 405 | }, 406 | }, 407 | Streams: []grpc.StreamDesc{ 408 | { 409 | StreamName: "SubscribeAccountUpdates", 410 | Handler: _Geyser_SubscribeAccountUpdates_Handler, 411 | ServerStreams: true, 412 | }, 413 | { 414 | StreamName: "SubscribeProgramUpdates", 415 | Handler: _Geyser_SubscribeProgramUpdates_Handler, 416 | ServerStreams: true, 417 | }, 418 | { 419 | StreamName: "SubscribePartialAccountUpdates", 420 | Handler: _Geyser_SubscribePartialAccountUpdates_Handler, 421 | ServerStreams: true, 422 | }, 423 | { 424 | StreamName: "SubscribeSlotUpdates", 425 | Handler: _Geyser_SubscribeSlotUpdates_Handler, 426 | ServerStreams: true, 427 | }, 428 | { 429 | StreamName: "SubscribeTransactionUpdates", 430 | Handler: _Geyser_SubscribeTransactionUpdates_Handler, 431 | ServerStreams: true, 432 | }, 433 | { 434 | StreamName: "SubscribeBlockUpdates", 435 | Handler: _Geyser_SubscribeBlockUpdates_Handler, 436 | ServerStreams: true, 437 | }, 438 | { 439 | StreamName: "SubscribeSlotEntryUpdates", 440 | Handler: _Geyser_SubscribeSlotEntryUpdates_Handler, 441 | ServerStreams: true, 442 | }, 443 | }, 444 | Metadata: "geyser.proto", 445 | } 446 | -------------------------------------------------------------------------------- /jito_geyser/pb/transaction_by_addr.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/weeaa/goyser/jito/jito_pb"; 4 | 5 | package solana.storage.TransactionByAddr; 6 | 7 | message TransactionByAddr { 8 | repeated TransactionByAddrInfo tx_by_addrs = 1; 9 | } 10 | 11 | message TransactionByAddrInfo { 12 | bytes signature = 1; 13 | TransactionError err = 2; 14 | uint32 index = 3; 15 | Memo memo = 4; 16 | UnixTimestamp block_time = 5; 17 | } 18 | 19 | message Memo { 20 | string memo = 1; 21 | } 22 | 23 | message TransactionError { 24 | TransactionErrorType transaction_error = 1; 25 | InstructionError instruction_error = 2; 26 | TransactionDetails transaction_details = 3; 27 | } 28 | 29 | enum TransactionErrorType { 30 | ACCOUNT_IN_USE = 0; 31 | ACCOUNT_LOADED_TWICE = 1; 32 | ACCOUNT_NOT_FOUND = 2; 33 | PROGRAM_ACCOUNT_NOT_FOUND = 3; 34 | INSUFFICIENT_FUNDS_FOR_FEE = 4; 35 | INVALID_ACCOUNT_FOR_FEE = 5; 36 | ALREADY_PROCESSED = 6; 37 | BLOCKHASH_NOT_FOUND = 7; 38 | INSTRUCTION_ERROR = 8; 39 | CALL_CHAIN_TOO_DEEP = 9; 40 | MISSING_SIGNATURE_FOR_FEE = 10; 41 | INVALID_ACCOUNT_INDEX = 11; 42 | SIGNATURE_FAILURE = 12; 43 | INVALID_PROGRAM_FOR_EXECUTION = 13; 44 | SANITIZE_FAILURE = 14; 45 | CLUSTER_MAINTENANCE = 15; 46 | ACCOUNT_BORROW_OUTSTANDING_TX = 16; 47 | WOULD_EXCEED_MAX_BLOCK_COST_LIMIT = 17; 48 | UNSUPPORTED_VERSION = 18; 49 | INVALID_WRITABLE_ACCOUNT = 19; 50 | WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT = 20; 51 | WOULD_EXCEED_ACCOUNT_DATA_BLOCK_LIMIT = 21; 52 | TOO_MANY_ACCOUNT_LOCKS = 22; 53 | ADDRESS_LOOKUP_TABLE_NOT_FOUND = 23; 54 | INVALID_ADDRESS_LOOKUP_TABLE_OWNER = 24; 55 | INVALID_ADDRESS_LOOKUP_TABLE_DATA = 25; 56 | INVALID_ADDRESS_LOOKUP_TABLE_INDEX = 26; 57 | INVALID_RENT_PAYING_ACCOUNT = 27; 58 | WOULD_EXCEED_MAX_VOTE_COST_LIMIT = 28; 59 | WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT = 29; 60 | DUPLICATE_INSTRUCTION = 30; 61 | INSUFFICIENT_FUNDS_FOR_RENT = 31; 62 | MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED = 32; 63 | INVALID_LOADED_ACCOUNTS_DATA_SIZE_LIMIT = 33; 64 | RESANITIZATION_NEEDED = 34; 65 | PROGRAM_EXECUTION_TEMPORARILY_RESTRICTED = 35; 66 | UNBALANCED_TRANSACTION = 36; 67 | PROGRAM_CACHE_HIT_MAX_LIMIT = 37; 68 | } 69 | 70 | message InstructionError { 71 | uint32 index = 1; 72 | InstructionErrorType error = 2; 73 | CustomError custom = 3; 74 | } 75 | 76 | message TransactionDetails { 77 | uint32 index = 1; 78 | } 79 | 80 | enum InstructionErrorType { 81 | GENERIC_ERROR = 0; 82 | INVALID_ARGUMENT = 1; 83 | INVALID_INSTRUCTION_DATA = 2; 84 | INVALID_ACCOUNT_DATA = 3; 85 | ACCOUNT_DATA_TOO_SMALL = 4; 86 | INSUFFICIENT_FUNDS = 5; 87 | INCORRECT_PROGRAM_ID = 6; 88 | MISSING_REQUIRED_SIGNATURE = 7; 89 | ACCOUNT_ALREADY_INITIALIZED = 8; 90 | UNINITIALIZED_ACCOUNT = 9; 91 | UNBALANCED_INSTRUCTION = 10; 92 | MODIFIED_PROGRAM_ID = 11; 93 | EXTERNAL_ACCOUNT_LAMPORT_SPEND = 12; 94 | EXTERNAL_ACCOUNT_DATA_MODIFIED = 13; 95 | READONLY_LAMPORT_CHANGE = 14; 96 | READONLY_DATA_MODIFIED = 15; 97 | DUPLICATE_ACCOUNT_INDEX = 16; 98 | EXECUTABLE_MODIFIED = 17; 99 | RENT_EPOCH_MODIFIED = 18; 100 | NOT_ENOUGH_ACCOUNT_KEYS = 19; 101 | ACCOUNT_DATA_SIZE_CHANGED = 20; 102 | ACCOUNT_NOT_EXECUTABLE = 21; 103 | ACCOUNT_BORROW_FAILED = 22; 104 | ACCOUNT_BORROW_OUTSTANDING = 23; 105 | DUPLICATE_ACCOUNT_OUT_OF_SYNC = 24; 106 | CUSTOM = 25; 107 | INVALID_ERROR = 26; 108 | EXECUTABLE_DATA_MODIFIED = 27; 109 | EXECUTABLE_LAMPORT_CHANGE = 28; 110 | EXECUTABLE_ACCOUNT_NOT_RENT_EXEMPT = 29; 111 | UNSUPPORTED_PROGRAM_ID = 30; 112 | CALL_DEPTH = 31; 113 | MISSING_ACCOUNT = 32; 114 | REENTRANCY_NOT_ALLOWED = 33; 115 | MAX_SEED_LENGTH_EXCEEDED = 34; 116 | INVALID_SEEDS = 35; 117 | INVALID_REALLOC = 36; 118 | COMPUTATIONAL_BUDGET_EXCEEDED = 37; 119 | PRIVILEGE_ESCALATION = 38; 120 | PROGRAM_ENVIRONMENT_SETUP_FAILURE = 39; 121 | PROGRAM_FAILED_TO_COMPLETE = 40; 122 | PROGRAM_FAILED_TO_COMPILE = 41; 123 | IMMUTABLE = 42; 124 | INCORRECT_AUTHORITY = 43; 125 | BORSH_IO_ERROR = 44; 126 | ACCOUNT_NOT_RENT_EXEMPT = 45; 127 | INVALID_ACCOUNT_OWNER = 46; 128 | ARITHMETIC_OVERFLOW = 47; 129 | UNSUPPORTED_SYSVAR = 48; 130 | ILLEGAL_OWNER = 49; 131 | MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED = 50; 132 | MAX_ACCOUNTS_EXCEEDED = 51; 133 | MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED = 52; 134 | BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS = 53; 135 | } 136 | 137 | message UnixTimestamp { 138 | int64 timestamp = 1; 139 | } 140 | 141 | message CustomError { 142 | uint32 custom = 1; 143 | } 144 | -------------------------------------------------------------------------------- /pkg/convert.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | func StrSliceToByteSlices(strs []string) [][]byte { 4 | result := make([][]byte, len(strs)) 5 | for i, s := range strs { 6 | result[i] = []byte(s) 7 | } 8 | return result 9 | } 10 | -------------------------------------------------------------------------------- /pkg/grpc.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "context" 5 | "crypto/x509" 6 | "errors" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/connectivity" 9 | "google.golang.org/grpc/credentials" 10 | "google.golang.org/grpc/credentials/insecure" 11 | "google.golang.org/grpc/keepalive" 12 | "net/url" 13 | "time" 14 | ) 15 | 16 | // CreateAndObserveGRPCConn creates a new gRPC connection and observes its conn status. 17 | func CreateAndObserveGRPCConn(ctx context.Context, ch chan error, target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { 18 | u, err := url.Parse(target) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | port := u.Port() 24 | if port == "" { 25 | port = "443" 26 | } 27 | 28 | if u.Scheme == "http" { 29 | opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) 30 | } else { 31 | pool, _ := x509.SystemCertPool() 32 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(pool, ""))) 33 | } 34 | 35 | hostname := u.Hostname() 36 | if hostname == "" { 37 | return nil, errors.New("please provide URL format endpoint e.g. http(s)://:") 38 | } 39 | 40 | address := hostname + ":" + port 41 | 42 | opts = append(opts, grpc.WithDefaultCallOptions( 43 | grpc.MaxCallRecvMsgSize(100*1024*1024), 44 | grpc.MaxCallSendMsgSize(100*1024*1024), 45 | ), 46 | grpc.WithKeepaliveParams(keepalive.ClientParameters{ 47 | Time: 10 * time.Second, 48 | Timeout: 5 * time.Second, 49 | }), 50 | ) 51 | 52 | conn, err := grpc.NewClient(address, opts...) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | go func() { 58 | var retries int 59 | for { 60 | select { 61 | case <-ctx.Done(): 62 | if err = conn.Close(); err != nil { 63 | ch <- err 64 | } 65 | return 66 | default: 67 | state := conn.GetState() 68 | if state == connectivity.Ready { 69 | retries = 0 70 | time.Sleep(1 * time.Second) 71 | continue 72 | } 73 | 74 | if state == connectivity.TransientFailure || state == connectivity.Connecting || state == connectivity.Idle { 75 | if retries < 5 { 76 | time.Sleep(time.Duration(retries) * time.Second) 77 | conn.ResetConnectBackoff() 78 | retries++ 79 | } else { 80 | conn.Close() 81 | conn, err = grpc.NewClient(target, opts...) 82 | if err != nil { 83 | ch <- err 84 | } 85 | retries = 0 86 | } 87 | } else if state == connectivity.Shutdown { 88 | conn, err = grpc.NewClient(target, opts...) 89 | if err != nil { 90 | ch <- err 91 | } 92 | retries = 0 93 | } 94 | 95 | if !conn.WaitForStateChange(ctx, state) { 96 | continue 97 | } 98 | } 99 | } 100 | }() 101 | 102 | return conn, nil 103 | } 104 | -------------------------------------------------------------------------------- /yellowstone_geyser/geyser.go: -------------------------------------------------------------------------------- 1 | package yellowstone_geyser 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/gagliardetto/solana-go" 8 | "github.com/gagliardetto/solana-go/rpc" 9 | "github.com/weeaa/goyser/pkg" 10 | "github.com/weeaa/goyser/yellowstone_geyser/pb" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/connectivity" 13 | "google.golang.org/grpc/metadata" 14 | "io" 15 | "reflect" 16 | "slices" 17 | "strconv" 18 | "sync" 19 | "time" 20 | "unsafe" 21 | ) 22 | 23 | type Client struct { 24 | Ctx context.Context 25 | GrpcConn *grpc.ClientConn 26 | KeepAliveCount int32 27 | Geyser yellowstone_geyser_pb.GeyserClient 28 | ErrCh chan error 29 | s *streamManager 30 | } 31 | 32 | type streamManager struct { 33 | clients map[string]*StreamClient 34 | mu sync.RWMutex 35 | } 36 | 37 | type StreamClient struct { 38 | Ctx context.Context 39 | GrpcConn *grpc.ClientConn 40 | streamName string 41 | geyserConn yellowstone_geyser_pb.GeyserClient 42 | geyser yellowstone_geyser_pb.Geyser_SubscribeClient 43 | request *yellowstone_geyser_pb.SubscribeRequest 44 | Ch chan *yellowstone_geyser_pb.SubscribeUpdate 45 | ErrCh chan error 46 | mu sync.RWMutex 47 | 48 | countMu sync.RWMutex 49 | count int32 50 | latestCount time.Time 51 | } 52 | 53 | // New creates a new Client instance. 54 | func New(ctx context.Context, grpcDialURL string, md metadata.MD) (*Client, error) { 55 | ch := make(chan error) 56 | 57 | if md != nil { 58 | ctx = metadata.NewOutgoingContext(ctx, md) 59 | } 60 | 61 | conn, err := pkg.CreateAndObserveGRPCConn(ctx, ch, grpcDialURL) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | geyserClient := yellowstone_geyser_pb.NewGeyserClient(conn) 67 | 68 | return &Client{ 69 | GrpcConn: conn, 70 | Ctx: ctx, 71 | Geyser: geyserClient, 72 | ErrCh: ch, 73 | s: &streamManager{ 74 | clients: make(map[string]*StreamClient), 75 | mu: sync.RWMutex{}, 76 | }, 77 | }, nil 78 | } 79 | 80 | // Close closes the client and all the streams. 81 | func (c *Client) Close() error { 82 | for _, sc := range c.s.clients { 83 | sc.Stop() 84 | } 85 | close(c.ErrCh) 86 | return c.GrpcConn.Close() 87 | } 88 | 89 | func (c *Client) Ping(count int32) (*yellowstone_geyser_pb.PongResponse, error) { 90 | return c.Geyser.Ping(c.Ctx, &yellowstone_geyser_pb.PingRequest{Count: count}) 91 | } 92 | 93 | // AddStreamClient creates a new Geyser subscribe stream client. You can retrieve it with GetStreamClient. 94 | func (c *Client) AddStreamClient(ctx context.Context, streamName string, commitmentLevel yellowstone_geyser_pb.CommitmentLevel, opts ...grpc.CallOption) error { 95 | c.s.mu.Lock() 96 | defer c.s.mu.Unlock() 97 | 98 | if _, exists := c.s.clients[streamName]; exists { 99 | return fmt.Errorf("client with name %s already exists", streamName) 100 | } 101 | 102 | stream, err := c.Geyser.Subscribe(ctx, opts...) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | streamClient := StreamClient{ 108 | Ctx: ctx, 109 | GrpcConn: c.GrpcConn, 110 | streamName: streamName, 111 | geyser: stream, 112 | geyserConn: c.Geyser, 113 | request: &yellowstone_geyser_pb.SubscribeRequest{ 114 | Accounts: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterAccounts), 115 | Slots: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterSlots), 116 | Transactions: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterTransactions), 117 | TransactionsStatus: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterTransactions), 118 | Blocks: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterBlocks), 119 | BlocksMeta: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterBlocksMeta), 120 | Entry: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterEntry), 121 | AccountsDataSlice: make([]*yellowstone_geyser_pb.SubscribeRequestAccountsDataSlice, 0), 122 | Commitment: &commitmentLevel, 123 | }, 124 | Ch: make(chan *yellowstone_geyser_pb.SubscribeUpdate), 125 | ErrCh: make(chan error), 126 | mu: sync.RWMutex{}, 127 | } 128 | 129 | c.s.clients[streamName] = &streamClient 130 | go streamClient.listen() 131 | go streamClient.keepAlive() 132 | 133 | return nil 134 | } 135 | 136 | func (s *StreamClient) Stop() { 137 | s.Ctx.Done() 138 | close(s.Ch) 139 | close(s.ErrCh) 140 | } 141 | 142 | // GetStreamClient returns a StreamClient for the given streamName from the client's map. 143 | func (c *Client) GetStreamClient(streamName string) *StreamClient { 144 | defer c.s.mu.RUnlock() 145 | c.s.mu.RLock() 146 | return c.s.clients[streamName] 147 | } 148 | 149 | func (s *StreamClient) GetStreamName() string { 150 | return s.streamName 151 | } 152 | 153 | // SetRequest sets a custom request to be used across all methods. 154 | func (s *StreamClient) SetRequest(req *yellowstone_geyser_pb.SubscribeRequest) { 155 | s.mu.Lock() 156 | defer s.mu.Unlock() 157 | s.request = req 158 | } 159 | 160 | // SetCommitmentLevel modifies the commitment level of the stream's request. 161 | func (s *StreamClient) SetCommitmentLevel(commitmentLevel yellowstone_geyser_pb.CommitmentLevel) { 162 | s.mu.Lock() 163 | defer s.mu.Unlock() 164 | s.request.Commitment = &commitmentLevel 165 | } 166 | 167 | // NewRequest creates a new empty *geyser_pb.SubscribeRequest. 168 | func (s *StreamClient) NewRequest() *yellowstone_geyser_pb.SubscribeRequest { 169 | return &yellowstone_geyser_pb.SubscribeRequest{ 170 | Accounts: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterAccounts), 171 | Slots: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterSlots), 172 | Transactions: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterTransactions), 173 | TransactionsStatus: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterTransactions), 174 | Blocks: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterBlocks), 175 | BlocksMeta: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterBlocksMeta), 176 | Entry: make(map[string]*yellowstone_geyser_pb.SubscribeRequestFilterEntry), 177 | AccountsDataSlice: make([]*yellowstone_geyser_pb.SubscribeRequestAccountsDataSlice, 0), 178 | } 179 | } 180 | 181 | // SendCustomRequest sends a custom *geyser_pb.SubscribeRequest using the Geyser client. 182 | func (s *StreamClient) SendCustomRequest(request *yellowstone_geyser_pb.SubscribeRequest) error { 183 | return s.geyser.Send(request) 184 | } 185 | 186 | func (s *StreamClient) sendRequest() error { 187 | return s.geyser.Send(s.request) 188 | } 189 | 190 | // SubscribeAccounts subscribes to account updates. 191 | // Note: This will overwrite existing subscriptions for the given ID. 192 | // To add new accounts without overwriting, use AppendAccounts. 193 | func (s *StreamClient) SubscribeAccounts(filterName string, req *yellowstone_geyser_pb.SubscribeRequestFilterAccounts) error { 194 | s.mu.Lock() 195 | s.request.Accounts[filterName] = req 196 | s.mu.Unlock() 197 | return s.geyser.Send(s.request) 198 | } 199 | 200 | // GetAccounts returns all account addresses for the given filter name. 201 | func (s *StreamClient) GetAccounts(filterName string) []string { 202 | defer s.mu.RUnlock() 203 | s.mu.RLock() 204 | return s.request.Accounts[filterName].Account 205 | } 206 | 207 | // AppendAccounts appends accounts to an existing subscription and sends the request. 208 | func (s *StreamClient) AppendAccounts(filterName string, accounts ...string) error { 209 | s.request.Accounts[filterName].Account = append(s.request.Accounts[filterName].Account, accounts...) 210 | return s.geyser.Send(s.request) 211 | } 212 | 213 | // UnsubscribeAccounts unsubscribes specific accounts. 214 | func (s *StreamClient) UnsubscribeAccounts(filterName string, accounts ...string) error { 215 | defer s.mu.Unlock() 216 | s.mu.Lock() 217 | if filter, exists := s.request.Accounts[filterName]; exists { 218 | filter.Account = slices.DeleteFunc(filter.Account, func(a string) bool { 219 | return slices.Contains(accounts, a) 220 | }) 221 | } 222 | return s.sendRequest() 223 | } 224 | 225 | func (s *StreamClient) UnsubscribeAllAccounts(filterName string) error { 226 | delete(s.request.Accounts, filterName) 227 | return s.geyser.Send(s.request) 228 | } 229 | 230 | // SubscribeSlots subscribes to slot updates. 231 | func (s *StreamClient) SubscribeSlots(filterName string, req *yellowstone_geyser_pb.SubscribeRequestFilterSlots) error { 232 | s.request.Slots[filterName] = req 233 | return s.geyser.Send(s.request) 234 | } 235 | 236 | // UnsubscribeSlots unsubscribes from slot updates. 237 | func (s *StreamClient) UnsubscribeSlots(filterName string) error { 238 | delete(s.request.Slots, filterName) 239 | return s.geyser.Send(s.request) 240 | } 241 | 242 | // SubscribeTransaction subscribes to transaction updates. 243 | func (s *StreamClient) SubscribeTransaction(filterName string, req *yellowstone_geyser_pb.SubscribeRequestFilterTransactions) error { 244 | s.request.Transactions[filterName] = req 245 | return s.geyser.Send(s.request) 246 | } 247 | 248 | // UnsubscribeTransaction unsubscribes from transaction updates. 249 | func (s *StreamClient) UnsubscribeTransaction(filterName string) error { 250 | delete(s.request.Transactions, filterName) 251 | return s.geyser.Send(s.request) 252 | } 253 | 254 | // SubscribeTransactionStatus subscribes to transaction status updates. 255 | func (s *StreamClient) SubscribeTransactionStatus(filterName string, req *yellowstone_geyser_pb.SubscribeRequestFilterTransactions) error { 256 | s.request.TransactionsStatus[filterName] = req 257 | return s.geyser.Send(s.request) 258 | } 259 | 260 | func (s *StreamClient) UnsubscribeTransactionStatus(filterName string) error { 261 | delete(s.request.TransactionsStatus, filterName) 262 | return s.geyser.Send(s.request) 263 | } 264 | 265 | // SubscribeBlocks subscribes to block updates. 266 | func (s *StreamClient) SubscribeBlocks(filterName string, req *yellowstone_geyser_pb.SubscribeRequestFilterBlocks) error { 267 | s.request.Blocks[filterName] = req 268 | return s.geyser.Send(s.request) 269 | } 270 | 271 | func (s *StreamClient) UnsubscribeBlocks(filterName string) error { 272 | delete(s.request.Blocks, filterName) 273 | return s.geyser.Send(s.request) 274 | } 275 | 276 | // SubscribeBlocksMeta subscribes to block metadata updates. 277 | func (s *StreamClient) SubscribeBlocksMeta(filterName string, req *yellowstone_geyser_pb.SubscribeRequestFilterBlocksMeta) error { 278 | s.request.BlocksMeta[filterName] = req 279 | return s.geyser.Send(s.request) 280 | } 281 | 282 | func (s *StreamClient) UnsubscribeBlocksMeta(filterName string) error { 283 | delete(s.request.BlocksMeta, filterName) 284 | return s.geyser.Send(s.request) 285 | } 286 | 287 | // SubscribeEntry subscribes to entry updates. 288 | func (s *StreamClient) SubscribeEntry(filterName string, req *yellowstone_geyser_pb.SubscribeRequestFilterEntry) error { 289 | s.request.Entry[filterName] = req 290 | return s.geyser.Send(s.request) 291 | } 292 | 293 | func (s *StreamClient) UnsubscribeEntry(filterName string) error { 294 | delete(s.request.Entry, filterName) 295 | return s.geyser.Send(s.request) 296 | } 297 | 298 | // SubscribeAccountDataSlice subscribes to account data slice updates. 299 | func (s *StreamClient) SubscribeAccountDataSlice(req []*yellowstone_geyser_pb.SubscribeRequestAccountsDataSlice) error { 300 | s.request.AccountsDataSlice = req 301 | return s.geyser.Send(s.request) 302 | } 303 | 304 | func (s *StreamClient) UnsubscribeAccountDataSlice() error { 305 | s.request.AccountsDataSlice = nil 306 | return s.geyser.Send(s.request) 307 | } 308 | 309 | // listen starts listening for responses and errors. 310 | func (s *StreamClient) listen() { 311 | for { 312 | select { 313 | case <-s.Ctx.Done(): 314 | if err := s.Ctx.Err(); err != nil { 315 | s.ErrCh <- fmt.Errorf("stream context cancelled: %w", err) 316 | } 317 | return 318 | default: 319 | recv, err := s.geyser.Recv() 320 | if err != nil { 321 | if err == io.EOF { 322 | s.ErrCh <- errors.New("stream cancelled: EOF") 323 | return 324 | } 325 | select { 326 | case s.ErrCh <- fmt.Errorf("error receiving from stream: %w", err): 327 | case <-s.Ctx.Done(): 328 | if err = s.Ctx.Err(); err != nil { 329 | s.ErrCh <- fmt.Errorf("stream context cancelled: %w", err) 330 | } 331 | return 332 | } 333 | return 334 | } 335 | select { 336 | case s.Ch <- recv: 337 | case <-s.Ctx.Done(): 338 | return 339 | } 340 | } 341 | } 342 | } 343 | 344 | // keepAlive sends every 10 second a ping to the gRPC conn in order to keep it alive. 345 | func (s *StreamClient) keepAlive() { 346 | ticker := time.NewTicker(10 * time.Second) 347 | for { 348 | select { 349 | case <-s.Ctx.Done(): 350 | return 351 | case <-ticker.C: 352 | s.count++ 353 | s.latestCount = time.Now() 354 | 355 | state := s.GrpcConn.GetState() 356 | if state == connectivity.Idle || state == connectivity.Ready { 357 | if _, err := s.geyserConn.Ping(s.Ctx, 358 | &yellowstone_geyser_pb.PingRequest{ 359 | Count: s.count, 360 | }, 361 | ); err != nil { 362 | if isChannelClosed(s.ErrCh) { 363 | s.ErrCh = make(chan error) 364 | } 365 | s.ErrCh <- err 366 | } 367 | } else { 368 | if isChannelClosed(s.ErrCh) { 369 | s.ErrCh = make(chan error) 370 | } 371 | s.ErrCh <- fmt.Errorf("%s: error keeping alive conn: expected %s or %s, got %s", s.streamName, connectivity.Idle.String(), connectivity.Ready.String(), state.String()) 372 | } 373 | } 374 | } 375 | } 376 | 377 | func isChannelClosed(ch <-chan error) bool { 378 | select { 379 | case _, ok := <-ch: 380 | return !ok 381 | default: 382 | } 383 | return false 384 | } 385 | 386 | // GetKeepAliveCountAndTimestamp returns the 387 | func (s *StreamClient) GetKeepAliveCountAndTimestamp() (int32, time.Time) { 388 | return s.count, s.latestCount 389 | } 390 | 391 | // ConvertTransaction converts a Geyser parsed transaction into *rpc.GetTransactionResult. 392 | func ConvertTransaction(geyserTx *yellowstone_geyser_pb.SubscribeUpdateTransaction) (*rpc.GetTransactionResult, error) { 393 | meta := geyserTx.Transaction.Meta 394 | transaction := geyserTx.Transaction.Transaction 395 | 396 | tx := &rpc.GetTransactionResult{ 397 | Transaction: &rpc.TransactionResultEnvelope{}, 398 | Meta: &rpc.TransactionMeta{ 399 | InnerInstructions: make([]rpc.InnerInstruction, 0), 400 | LogMessages: make([]string, 0), 401 | PostBalances: make([]uint64, 0), 402 | PostTokenBalances: make([]rpc.TokenBalance, 0), 403 | PreBalances: make([]uint64, 0), 404 | PreTokenBalances: make([]rpc.TokenBalance, 0), 405 | Rewards: make([]rpc.BlockReward, 0), 406 | LoadedAddresses: rpc.LoadedAddresses{ 407 | ReadOnly: make([]solana.PublicKey, 0), 408 | Writable: make([]solana.PublicKey, 0), 409 | }, 410 | }, 411 | Slot: geyserTx.Slot, 412 | } 413 | 414 | tx.Meta.PreBalances = meta.PreBalances 415 | tx.Meta.PostBalances = meta.PostBalances 416 | tx.Meta.Err = meta.Err 417 | tx.Meta.Fee = meta.Fee 418 | tx.Meta.ComputeUnitsConsumed = meta.ComputeUnitsConsumed 419 | tx.Meta.LogMessages = meta.LogMessages 420 | 421 | for _, preTokenBalance := range meta.PreTokenBalances { 422 | owner := solana.MustPublicKeyFromBase58(preTokenBalance.Owner) 423 | tx.Meta.PreTokenBalances = append(tx.Meta.PreTokenBalances, rpc.TokenBalance{ 424 | AccountIndex: uint16(preTokenBalance.AccountIndex), 425 | Owner: &owner, 426 | Mint: solana.MustPublicKeyFromBase58(preTokenBalance.Mint), 427 | UiTokenAmount: &rpc.UiTokenAmount{ 428 | Amount: preTokenBalance.UiTokenAmount.Amount, 429 | Decimals: uint8(preTokenBalance.UiTokenAmount.Decimals), 430 | UiAmount: &preTokenBalance.UiTokenAmount.UiAmount, 431 | UiAmountString: preTokenBalance.UiTokenAmount.UiAmountString, 432 | }, 433 | }) 434 | } 435 | 436 | for _, postTokenBalance := range meta.PostTokenBalances { 437 | owner := solana.MustPublicKeyFromBase58(postTokenBalance.Owner) 438 | tx.Meta.PostTokenBalances = append(tx.Meta.PostTokenBalances, rpc.TokenBalance{ 439 | AccountIndex: uint16(postTokenBalance.AccountIndex), 440 | Owner: &owner, 441 | Mint: solana.MustPublicKeyFromBase58(postTokenBalance.Mint), 442 | UiTokenAmount: &rpc.UiTokenAmount{ 443 | Amount: postTokenBalance.UiTokenAmount.Amount, 444 | Decimals: uint8(postTokenBalance.UiTokenAmount.Decimals), 445 | UiAmount: &postTokenBalance.UiTokenAmount.UiAmount, 446 | UiAmountString: postTokenBalance.UiTokenAmount.UiAmountString, 447 | }, 448 | }) 449 | } 450 | 451 | for i, innerInst := range meta.InnerInstructions { 452 | tx.Meta.InnerInstructions = append(tx.Meta.InnerInstructions, rpc.InnerInstruction{}) 453 | tx.Meta.InnerInstructions[i].Index = uint16(innerInst.Index) 454 | for x, inst := range innerInst.Instructions { 455 | tx.Meta.InnerInstructions[i].Instructions = append(tx.Meta.InnerInstructions[i].Instructions, solana.CompiledInstruction{}) 456 | accounts, err := bytesToUint16Slice(inst.Accounts) 457 | if err != nil { 458 | return nil, err 459 | } 460 | 461 | tx.Meta.InnerInstructions[i].Instructions[x].Accounts = accounts 462 | tx.Meta.InnerInstructions[i].Instructions[x].ProgramIDIndex = uint16(inst.ProgramIdIndex) 463 | tx.Meta.InnerInstructions[i].Instructions[x].Data = inst.Data 464 | // if err = tx.Meta.InnerInstructions[i].Instructions[x].Data.UnmarshalJSON(inst.Data); err != nil { 465 | // return nil, err 466 | // } 467 | } 468 | } 469 | 470 | for _, reward := range meta.Rewards { 471 | comm, _ := strconv.ParseUint(reward.Commission, 10, 64) 472 | commission := uint8(comm) 473 | tx.Meta.Rewards = append(tx.Meta.Rewards, rpc.BlockReward{ 474 | Pubkey: solana.MustPublicKeyFromBase58(reward.Pubkey), 475 | Lamports: reward.Lamports, 476 | PostBalance: reward.PostBalance, 477 | RewardType: rpc.RewardType(reward.RewardType.String()), 478 | Commission: &commission, 479 | }) 480 | } 481 | 482 | for _, readOnlyAddress := range meta.LoadedReadonlyAddresses { 483 | tx.Meta.LoadedAddresses.ReadOnly = append(tx.Meta.LoadedAddresses.ReadOnly, solana.PublicKeyFromBytes(readOnlyAddress)) 484 | } 485 | 486 | for _, writableAddress := range meta.LoadedWritableAddresses { 487 | tx.Meta.LoadedAddresses.Writable = append(tx.Meta.LoadedAddresses.Writable, solana.PublicKeyFromBytes(writableAddress)) 488 | } 489 | 490 | // solTx, err := tx.Transaction.GetTransaction() 491 | // if err != nil { 492 | // return nil, err 493 | // } 494 | 495 | solTx := &solana.Transaction{ 496 | Signatures: make([]solana.Signature, 0), 497 | Message: solana.Message{ 498 | AccountKeys: make(solana.PublicKeySlice, 0), 499 | Instructions: make([]solana.CompiledInstruction, 0), 500 | AddressTableLookups: make(solana.MessageAddressTableLookupSlice, 0), 501 | }, 502 | } 503 | 504 | if transaction.Message.Versioned { 505 | solTx.Message.SetVersion(1) 506 | } 507 | 508 | solTx.Message.RecentBlockhash = solana.HashFromBytes(transaction.Message.RecentBlockhash) 509 | solTx.Message.Header = solana.MessageHeader{ 510 | NumRequiredSignatures: uint8(transaction.Message.Header.NumRequiredSignatures), 511 | NumReadonlySignedAccounts: uint8(transaction.Message.Header.NumReadonlySignedAccounts), 512 | NumReadonlyUnsignedAccounts: uint8(transaction.Message.Header.NumReadonlyUnsignedAccounts), 513 | } 514 | 515 | for _, sig := range transaction.Signatures { 516 | solTx.Signatures = append(solTx.Signatures, solana.SignatureFromBytes(sig)) 517 | } 518 | 519 | for _, table := range transaction.Message.AddressTableLookups { 520 | solTx.Message.AddressTableLookups = append(solTx.Message.AddressTableLookups, solana.MessageAddressTableLookup{ 521 | AccountKey: solana.PublicKeyFromBytes(table.AccountKey), 522 | WritableIndexes: table.WritableIndexes, 523 | ReadonlyIndexes: table.ReadonlyIndexes, 524 | }) 525 | } 526 | 527 | for _, key := range transaction.Message.AccountKeys { 528 | solTx.Message.AccountKeys = append(solTx.Message.AccountKeys, solana.PublicKeyFromBytes(key)) 529 | } 530 | 531 | for _, inst := range transaction.Message.Instructions { 532 | accounts, err := bytesToUint16Slice(inst.Accounts) 533 | if err != nil { 534 | return nil, err 535 | } 536 | 537 | solTx.Message.Instructions = append(solTx.Message.Instructions, solana.CompiledInstruction{ 538 | ProgramIDIndex: uint16(inst.ProgramIdIndex), 539 | Accounts: accounts, 540 | Data: inst.Data, 541 | }) 542 | } 543 | 544 | func(obj interface{}, fieldName string, value interface{}) { 545 | v := reflect.ValueOf(obj).Elem() 546 | f := v.FieldByName(fieldName) 547 | ptr := unsafe.Pointer(f.UnsafeAddr()) 548 | reflect.NewAt(f.Type(), ptr).Elem().Set(reflect.ValueOf(value)) 549 | }(tx.Transaction, "asParsedTransaction", solTx) 550 | 551 | return tx, nil 552 | } 553 | 554 | /* 555 | // ConvertTransactionf converts a Geyser parsed transaction into an rpc.GetTransactionResult format. 556 | func ConvertTransactionf(geyserTx *yellowstone_geyser_pb.SubscribeUpdateTransaction) (*rpc.GetParsedTransactionResult, error) { 557 | meta := geyserTx.Transaction.Meta 558 | transaction := geyserTx.Transaction.Transaction 559 | 560 | tx := &rpc.GetParsedTransactionResult{ 561 | Transaction: &rpc.ParsedTransaction{ 562 | Signatures: make([]solana.Signature, 0), 563 | Message: rpc.ParsedMessage{ 564 | AccountKeys: make([]rpc.ParsedMessageAccount, 0), 565 | Instructions: make([]*rpc.ParsedInstruction, 0), 566 | RecentBlockHash: string(transaction.Message.RecentBlockhash), 567 | }, 568 | }, 569 | Meta: &rpc.ParsedTransactionMeta{ 570 | InnerInstructions: make([]rpc.ParsedInnerInstruction, 0), 571 | LogMessages: meta.LogMessages, 572 | PostBalances: make([]uint64, 0), 573 | PostTokenBalances: make([]rpc.TokenBalance, 0), 574 | PreBalances: make([]uint64, 0), 575 | PreTokenBalances: make([]rpc.TokenBalance, 0), 576 | //Rewards: make([]rpc.BlockReward, 0), 577 | }, 578 | Slot: geyserTx.Slot, 579 | //Version: transaction.Message.Versioned, todo 580 | } 581 | 582 | tx.Meta.PreBalances = meta.PreBalances 583 | tx.Meta.PostBalances = meta.PostBalances 584 | tx.Meta.Err = meta.Err 585 | tx.Meta.Fee = meta.Fee 586 | //tx.Meta.ComputeUnitsConsumed = meta.ComputeUnitsConsumed 587 | tx.Meta.LogMessages = meta.LogMessages 588 | 589 | for _, preTokenBalance := range meta.PreTokenBalances { 590 | owner := solana.MustPublicKeyFromBase58(preTokenBalance.Owner) 591 | tx.Meta.PreTokenBalances = append(tx.Meta.PreTokenBalances, rpc.TokenBalance{ 592 | AccountIndex: uint16(preTokenBalance.AccountIndex), 593 | Owner: &owner, 594 | Mint: solana.MustPublicKeyFromBase58(preTokenBalance.Mint), 595 | UiTokenAmount: &rpc.UiTokenAmount{ 596 | Amount: preTokenBalance.UiTokenAmount.Amount, 597 | Decimals: uint8(preTokenBalance.UiTokenAmount.Decimals), 598 | UiAmount: &preTokenBalance.UiTokenAmount.UiAmount, 599 | UiAmountString: preTokenBalance.UiTokenAmount.UiAmountString, 600 | }, 601 | }) 602 | } 603 | 604 | for _, postTokenBalance := range meta.PostTokenBalances { 605 | owner := solana.MustPublicKeyFromBase58(postTokenBalance.Owner) 606 | tx.Meta.PostTokenBalances = append(tx.Meta.PostTokenBalances, rpc.TokenBalance{ 607 | AccountIndex: uint16(postTokenBalance.AccountIndex), 608 | Owner: &owner, 609 | Mint: solana.MustPublicKeyFromBase58(postTokenBalance.Mint), 610 | UiTokenAmount: &rpc.UiTokenAmount{ 611 | Amount: postTokenBalance.UiTokenAmount.Amount, 612 | Decimals: uint8(postTokenBalance.UiTokenAmount.Decimals), 613 | UiAmount: &postTokenBalance.UiTokenAmount.UiAmount, 614 | UiAmountString: postTokenBalance.UiTokenAmount.UiAmountString, 615 | }, 616 | }) 617 | } 618 | 619 | for i, innerInst := range meta.InnerInstructions { 620 | tx.Meta.InnerInstructions = append(tx.Meta.InnerInstructions, rpc.ParsedInnerInstruction{}) 621 | tx.Meta.InnerInstructions[i].Index = uint64(innerInst.Index) 622 | for x, inst := range innerInst.Instructions { 623 | tx.Meta.InnerInstructions[i].Instructions = append(tx.Meta.InnerInstructions[i].Instructions, &rpc.ParsedInstruction{}) 624 | accounts, err := bytesToUint16Slice(inst.Accounts) 625 | if err != nil { 626 | return nil, err 627 | } 628 | 629 | //tx.Meta.InnerInstructions[i].Instructions[x].Parsed 630 | 631 | tx.Meta.InnerInstructions[i].Instructions[x].Accounts = accounts 632 | tx.Meta.InnerInstructions[i].Instructions[x].ProgramIDIndex = uint16(inst.ProgramIdIndex) 633 | tx.Meta.InnerInstructions[i].Instructions[x].Data = inst.Data 634 | tx.Meta.InnerInstructions[i].Instructions[x].Program = inst. 635 | // if err = tx.Meta.InnerInstructions[i].Instructions[x].Data.UnmarshalJSON(inst.Data); err != nil { 636 | // return nil, err 637 | // } 638 | } 639 | } 640 | 641 | for _, reward := range meta.Rewards { 642 | comm, _ := strconv.ParseUint(reward.Commission, 10, 64) 643 | commission := uint8(comm) 644 | tx.Meta.Rewards = append(tx.Meta.Rewards, rpc.BlockReward{ 645 | Pubkey: solana.MustPublicKeyFromBase58(reward.Pubkey), 646 | Lamports: reward.Lamports, 647 | PostBalance: reward.PostBalance, 648 | RewardType: rpc.RewardType(reward.RewardType.String()), 649 | Commission: &commission, 650 | }) 651 | } 652 | 653 | for _, readOnlyAddress := range meta.LoadedReadonlyAddresses { 654 | tx.Meta.LoadedAddresses.ReadOnly = append(tx.Meta.LoadedAddresses.ReadOnly, solana.PublicKeyFromBytes(readOnlyAddress)) 655 | } 656 | 657 | for _, writableAddress := range meta.LoadedWritableAddresses { 658 | tx.Meta.LoadedAddresses.ReadOnly = append(tx.Meta.LoadedAddresses.ReadOnly, solana.PublicKeyFromBytes(writableAddress)) 659 | } 660 | 661 | // solTx, err := tx.Transaction.GetTransaction() 662 | // if err != nil { 663 | // return nil, err 664 | // } 665 | 666 | solTx := &solana.Transaction{ 667 | Signatures: make([]solana.Signature, 0), 668 | Message: solana.Message{ 669 | AccountKeys: make(solana.PublicKeySlice, 0), 670 | Instructions: make([]solana.CompiledInstruction, 0), 671 | AddressTableLookups: make(solana.MessageAddressTableLookupSlice, 0), 672 | }, 673 | } 674 | 675 | if transaction.Message.Versioned { 676 | solTx.Message.SetVersion(1) 677 | } 678 | 679 | solTx.Message.RecentBlockhash = solana.HashFromBytes(transaction.Message.RecentBlockhash) 680 | solTx.Message.Header = solana.MessageHeader{ 681 | NumRequiredSignatures: uint8(transaction.Message.Header.NumRequiredSignatures), 682 | NumReadonlySignedAccounts: uint8(transaction.Message.Header.NumReadonlySignedAccounts), 683 | NumReadonlyUnsignedAccounts: uint8(transaction.Message.Header.NumReadonlyUnsignedAccounts), 684 | } 685 | 686 | for _, sig := range transaction.Signatures { 687 | solTx.Signatures = append(solTx.Signatures, solana.SignatureFromBytes(sig)) 688 | } 689 | 690 | for _, table := range transaction.Message.AddressTableLookups { 691 | solTx.Message.AddressTableLookups = append(solTx.Message.AddressTableLookups, solana.MessageAddressTableLookup{ 692 | AccountKey: solana.PublicKeyFromBytes(table.AccountKey), 693 | WritableIndexes: table.WritableIndexes, 694 | ReadonlyIndexes: table.ReadonlyIndexes, 695 | }) 696 | } 697 | 698 | for _, key := range transaction.Message.AccountKeys { 699 | solTx.Message.AccountKeys = append(solTx.Message.AccountKeys, solana.PublicKeyFromBytes(key)) 700 | } 701 | 702 | for _, inst := range transaction.Message.Instructions { 703 | accounts, err := bytesToUint16Slice(inst.Accounts) 704 | if err != nil { 705 | return nil, err 706 | } 707 | 708 | solTx.Message.Instructions = append(solTx.Message.Instructions, solana.CompiledInstruction{ 709 | ProgramIDIndex: uint16(inst.ProgramIdIndex), 710 | Accounts: accounts, 711 | Data: inst.Data, 712 | }) 713 | } 714 | 715 | func(obj interface{}, fieldName string, value interface{}) { 716 | v := reflect.ValueOf(obj).Elem() 717 | f := v.FieldByName(fieldName) 718 | ptr := unsafe.Pointer(f.UnsafeAddr()) 719 | reflect.NewAt(f.Type(), ptr).Elem().Set(reflect.ValueOf(value)) 720 | }(tx.Transaction, "asParsedTransaction", solTx) 721 | 722 | return tx, nil 723 | } 724 | */ 725 | 726 | /* 727 | func BatchConvertTransaction(geyserTxns ...*yellowstone_geyser_pb.SubscribeUpdateTransaction) []*rpc.GetTransactionResult { 728 | txns := make([]*rpc.GetTransactionResult, len(geyserTxns), 0) 729 | for _, tx := range geyserTxns { 730 | txn, err := ConvertTransaction(tx) 731 | if err != nil { 732 | continue 733 | } 734 | txns = append(txns, txn) 735 | } 736 | return txns 737 | } 738 | */ 739 | 740 | // ConvertBlockHash converts a Geyser type block to a github.com/gagliardetto/solana-go Solana block. 741 | func ConvertBlockHash(geyserBlock *yellowstone_geyser_pb.SubscribeUpdateBlock) *rpc.GetBlockResult { 742 | block := new(rpc.GetBlockResult) 743 | 744 | blockTime := solana.UnixTimeSeconds(geyserBlock.BlockTime.Timestamp) 745 | block.BlockTime = &blockTime 746 | block.BlockHeight = &geyserBlock.BlockHeight.BlockHeight 747 | block.Blockhash = solana.Hash{[]byte(geyserBlock.Blockhash)[32]} 748 | block.ParentSlot = geyserBlock.ParentSlot 749 | 750 | for _, reward := range geyserBlock.Rewards.Rewards { 751 | commission, err := strconv.ParseUint(reward.Commission, 10, 8) 752 | if err != nil { 753 | return nil 754 | } 755 | 756 | var rewardType rpc.RewardType 757 | switch reward.RewardType { 758 | case 1: 759 | rewardType = rpc.RewardTypeFee 760 | case 2: 761 | rewardType = rpc.RewardTypeRent 762 | case 3: 763 | rewardType = rpc.RewardTypeStaking 764 | case 4: 765 | rewardType = rpc.RewardTypeVoting 766 | } 767 | 768 | comm := uint8(commission) 769 | block.Rewards = append(block.Rewards, rpc.BlockReward{ 770 | Pubkey: solana.MustPublicKeyFromBase58(reward.Pubkey), 771 | Lamports: reward.Lamports, 772 | PostBalance: reward.PostBalance, 773 | Commission: &comm, 774 | RewardType: rewardType, 775 | }) 776 | } 777 | 778 | return block 779 | } 780 | 781 | func BatchConvertBlockHash(geyserBlocks ...*yellowstone_geyser_pb.SubscribeUpdateBlock) []*rpc.GetBlockResult { 782 | blocks := make([]*rpc.GetBlockResult, len(geyserBlocks), 0) 783 | for _, block := range geyserBlocks { 784 | blocks = append(blocks, ConvertBlockHash(block)) 785 | } 786 | return blocks 787 | } 788 | 789 | // func bytesToUint16Slice(data []byte) ([]uint16, error) { 790 | // if len(data)%2 != 0 { 791 | // return nil, fmt.Errorf("length of byte slice must be even to convert to uint16 slice") 792 | // } 793 | 794 | // uint16s := make([]uint16, len(data)/2) 795 | 796 | // for i := 0; i < len(data); i += 2 { 797 | // uint16s[i/2] = binary.LittleEndian.Uint16(data[i : i+2]) 798 | // } 799 | 800 | // return uint16s, nil 801 | // } 802 | 803 | func bytesToUint16Slice(data []byte) ([]uint16, error) { 804 | uint16s := make([]uint16, len(data)) 805 | 806 | for i := 0; i < len(data); i += 1 { 807 | uint16s[i] = uint16(data[i]) 808 | } 809 | 810 | return uint16s, nil 811 | } 812 | -------------------------------------------------------------------------------- /yellowstone_geyser/geyser_test.go: -------------------------------------------------------------------------------- 1 | package yellowstone_geyser 2 | 3 | import ( 4 | "github.com/gagliardetto/solana-go/rpc" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/stretchr/testify/require" 7 | yellowstone_geyser_pb "github.com/weeaa/goyser/yellowstone_geyser/pb" 8 | "testing" 9 | ) 10 | 11 | var mockCu uint64 = 200000 12 | 13 | // todo add more test coverage 14 | func TestConvertTransaction(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | input *yellowstone_geyser_pb.SubscribeUpdateTransaction 18 | expectError bool 19 | verify func(*testing.T, *rpc.GetTransactionResult) 20 | }{ 21 | { 22 | name: "Basic Transaction Conversion", 23 | input: &yellowstone_geyser_pb.SubscribeUpdateTransaction{ 24 | Transaction: &yellowstone_geyser_pb.SubscribeUpdateTransactionInfo{ 25 | Transaction: &yellowstone_geyser_pb.Transaction{ 26 | Message: &yellowstone_geyser_pb.Message{ 27 | Header: &yellowstone_geyser_pb.MessageHeader{ 28 | NumRequiredSignatures: 1, 29 | NumReadonlySignedAccounts: 0, 30 | NumReadonlyUnsignedAccounts: 1, 31 | }, 32 | RecentBlockhash: make([]byte, 32), 33 | Instructions: []*yellowstone_geyser_pb.CompiledInstruction{ 34 | { 35 | ProgramIdIndex: 0, 36 | Accounts: []byte{0, 1}, 37 | Data: []byte{1, 2, 3}, 38 | }, 39 | }, 40 | Versioned: false, 41 | }, 42 | Signatures: [][]byte{make([]byte, 64)}, 43 | }, 44 | Meta: &yellowstone_geyser_pb.TransactionStatusMeta{ 45 | Fee: 1000, 46 | ComputeUnitsConsumed: &mockCu, 47 | PreBalances: []uint64{100, 200}, 48 | PostBalances: []uint64{90, 210}, 49 | LogMessages: []string{"log1", "log2"}, 50 | PreTokenBalances: []*yellowstone_geyser_pb.TokenBalance{ 51 | { 52 | AccountIndex: 1, 53 | Mint: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 54 | Owner: "11111111111111111111111111111111", 55 | UiTokenAmount: &yellowstone_geyser_pb.UiTokenAmount{ 56 | Amount: "1000000", 57 | Decimals: 6, 58 | UiAmount: 1.0, 59 | UiAmountString: "1.0", 60 | }, 61 | }, 62 | }, 63 | }, 64 | }, 65 | }, 66 | expectError: false, 67 | verify: func(t *testing.T, result *rpc.GetTransactionResult) { 68 | assert.NotNil(t, result) 69 | assert.Equal(t, uint64(1000), result.Meta.Fee) 70 | assert.Equal(t, &mockCu, result.Meta.ComputeUnitsConsumed) 71 | assert.Equal(t, []uint64{100, 200}, result.Meta.PreBalances) 72 | assert.Equal(t, []uint64{90, 210}, result.Meta.PostBalances) 73 | assert.Equal(t, []string{"log1", "log2"}, result.Meta.LogMessages) 74 | 75 | // Verify PreTokenBalances 76 | require.Len(t, result.Meta.PreTokenBalances, 1) 77 | assert.Equal(t, uint16(1), result.Meta.PreTokenBalances[0].AccountIndex) 78 | assert.Equal(t, "1.0", result.Meta.PreTokenBalances[0].UiTokenAmount.UiAmountString) 79 | }, 80 | }, 81 | } 82 | 83 | for _, tt := range tests { 84 | t.Run(tt.name, func(t *testing.T) { 85 | result, err := ConvertTransaction(tt.input) 86 | 87 | if tt.expectError { 88 | assert.Error(t, err) 89 | assert.Nil(t, result) 90 | return 91 | } 92 | 93 | assert.NoError(t, err) 94 | assert.NotNil(t, result) 95 | 96 | if tt.verify != nil { 97 | tt.verify(t, result) 98 | } 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /yellowstone_geyser/pb/geyser_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.5.1 4 | // - protoc v3.19.6 5 | // source: geyser.proto 6 | 7 | package yellowstone_geyser_pb 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.64.0 or later. 19 | const _ = grpc.SupportPackageIsVersion9 20 | 21 | const ( 22 | Geyser_Subscribe_FullMethodName = "/geyser.Geyser/Subscribe" 23 | Geyser_SubscribeReplayInfo_FullMethodName = "/geyser.Geyser/SubscribeReplayInfo" 24 | Geyser_Ping_FullMethodName = "/geyser.Geyser/Ping" 25 | Geyser_GetLatestBlockhash_FullMethodName = "/geyser.Geyser/GetLatestBlockhash" 26 | Geyser_GetBlockHeight_FullMethodName = "/geyser.Geyser/GetBlockHeight" 27 | Geyser_GetSlot_FullMethodName = "/geyser.Geyser/GetSlot" 28 | Geyser_IsBlockhashValid_FullMethodName = "/geyser.Geyser/IsBlockhashValid" 29 | Geyser_GetVersion_FullMethodName = "/geyser.Geyser/GetVersion" 30 | ) 31 | 32 | // GeyserClient is the client API for Geyser service. 33 | // 34 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 35 | type GeyserClient interface { 36 | Subscribe(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate], error) 37 | SubscribeReplayInfo(ctx context.Context, in *SubscribeReplayInfoRequest, opts ...grpc.CallOption) (*SubscribeReplayInfoResponse, error) 38 | Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongResponse, error) 39 | GetLatestBlockhash(ctx context.Context, in *GetLatestBlockhashRequest, opts ...grpc.CallOption) (*GetLatestBlockhashResponse, error) 40 | GetBlockHeight(ctx context.Context, in *GetBlockHeightRequest, opts ...grpc.CallOption) (*GetBlockHeightResponse, error) 41 | GetSlot(ctx context.Context, in *GetSlotRequest, opts ...grpc.CallOption) (*GetSlotResponse, error) 42 | IsBlockhashValid(ctx context.Context, in *IsBlockhashValidRequest, opts ...grpc.CallOption) (*IsBlockhashValidResponse, error) 43 | GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) 44 | } 45 | 46 | type geyserClient struct { 47 | cc grpc.ClientConnInterface 48 | } 49 | 50 | func NewGeyserClient(cc grpc.ClientConnInterface) GeyserClient { 51 | return &geyserClient{cc} 52 | } 53 | 54 | func (c *geyserClient) Subscribe(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate], error) { 55 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 56 | stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[0], Geyser_Subscribe_FullMethodName, cOpts...) 57 | if err != nil { 58 | return nil, err 59 | } 60 | x := &grpc.GenericClientStream[SubscribeRequest, SubscribeUpdate]{ClientStream: stream} 61 | return x, nil 62 | } 63 | 64 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 65 | type Geyser_SubscribeClient = grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate] 66 | 67 | func (c *geyserClient) SubscribeReplayInfo(ctx context.Context, in *SubscribeReplayInfoRequest, opts ...grpc.CallOption) (*SubscribeReplayInfoResponse, error) { 68 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 69 | out := new(SubscribeReplayInfoResponse) 70 | err := c.cc.Invoke(ctx, Geyser_SubscribeReplayInfo_FullMethodName, in, out, cOpts...) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return out, nil 75 | } 76 | 77 | func (c *geyserClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongResponse, error) { 78 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 79 | out := new(PongResponse) 80 | err := c.cc.Invoke(ctx, Geyser_Ping_FullMethodName, in, out, cOpts...) 81 | if err != nil { 82 | return nil, err 83 | } 84 | return out, nil 85 | } 86 | 87 | func (c *geyserClient) GetLatestBlockhash(ctx context.Context, in *GetLatestBlockhashRequest, opts ...grpc.CallOption) (*GetLatestBlockhashResponse, error) { 88 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 89 | out := new(GetLatestBlockhashResponse) 90 | err := c.cc.Invoke(ctx, Geyser_GetLatestBlockhash_FullMethodName, in, out, cOpts...) 91 | if err != nil { 92 | return nil, err 93 | } 94 | return out, nil 95 | } 96 | 97 | func (c *geyserClient) GetBlockHeight(ctx context.Context, in *GetBlockHeightRequest, opts ...grpc.CallOption) (*GetBlockHeightResponse, error) { 98 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 99 | out := new(GetBlockHeightResponse) 100 | err := c.cc.Invoke(ctx, Geyser_GetBlockHeight_FullMethodName, in, out, cOpts...) 101 | if err != nil { 102 | return nil, err 103 | } 104 | return out, nil 105 | } 106 | 107 | func (c *geyserClient) GetSlot(ctx context.Context, in *GetSlotRequest, opts ...grpc.CallOption) (*GetSlotResponse, error) { 108 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 109 | out := new(GetSlotResponse) 110 | err := c.cc.Invoke(ctx, Geyser_GetSlot_FullMethodName, in, out, cOpts...) 111 | if err != nil { 112 | return nil, err 113 | } 114 | return out, nil 115 | } 116 | 117 | func (c *geyserClient) IsBlockhashValid(ctx context.Context, in *IsBlockhashValidRequest, opts ...grpc.CallOption) (*IsBlockhashValidResponse, error) { 118 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 119 | out := new(IsBlockhashValidResponse) 120 | err := c.cc.Invoke(ctx, Geyser_IsBlockhashValid_FullMethodName, in, out, cOpts...) 121 | if err != nil { 122 | return nil, err 123 | } 124 | return out, nil 125 | } 126 | 127 | func (c *geyserClient) GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) { 128 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 129 | out := new(GetVersionResponse) 130 | err := c.cc.Invoke(ctx, Geyser_GetVersion_FullMethodName, in, out, cOpts...) 131 | if err != nil { 132 | return nil, err 133 | } 134 | return out, nil 135 | } 136 | 137 | // GeyserServer is the server API for Geyser service. 138 | // All implementations must embed UnimplementedGeyserServer 139 | // for forward compatibility. 140 | type GeyserServer interface { 141 | Subscribe(grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]) error 142 | SubscribeReplayInfo(context.Context, *SubscribeReplayInfoRequest) (*SubscribeReplayInfoResponse, error) 143 | Ping(context.Context, *PingRequest) (*PongResponse, error) 144 | GetLatestBlockhash(context.Context, *GetLatestBlockhashRequest) (*GetLatestBlockhashResponse, error) 145 | GetBlockHeight(context.Context, *GetBlockHeightRequest) (*GetBlockHeightResponse, error) 146 | GetSlot(context.Context, *GetSlotRequest) (*GetSlotResponse, error) 147 | IsBlockhashValid(context.Context, *IsBlockhashValidRequest) (*IsBlockhashValidResponse, error) 148 | GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) 149 | mustEmbedUnimplementedGeyserServer() 150 | } 151 | 152 | // UnimplementedGeyserServer must be embedded to have 153 | // forward compatible implementations. 154 | // 155 | // NOTE: this should be embedded by value instead of pointer to avoid a nil 156 | // pointer dereference when methods are called. 157 | type UnimplementedGeyserServer struct{} 158 | 159 | func (UnimplementedGeyserServer) Subscribe(grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]) error { 160 | return status.Errorf(codes.Unimplemented, "method Subscribe not implemented") 161 | } 162 | func (UnimplementedGeyserServer) SubscribeReplayInfo(context.Context, *SubscribeReplayInfoRequest) (*SubscribeReplayInfoResponse, error) { 163 | return nil, status.Errorf(codes.Unimplemented, "method SubscribeReplayInfo not implemented") 164 | } 165 | func (UnimplementedGeyserServer) Ping(context.Context, *PingRequest) (*PongResponse, error) { 166 | return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") 167 | } 168 | func (UnimplementedGeyserServer) GetLatestBlockhash(context.Context, *GetLatestBlockhashRequest) (*GetLatestBlockhashResponse, error) { 169 | return nil, status.Errorf(codes.Unimplemented, "method GetLatestBlockhash not implemented") 170 | } 171 | func (UnimplementedGeyserServer) GetBlockHeight(context.Context, *GetBlockHeightRequest) (*GetBlockHeightResponse, error) { 172 | return nil, status.Errorf(codes.Unimplemented, "method GetBlockHeight not implemented") 173 | } 174 | func (UnimplementedGeyserServer) GetSlot(context.Context, *GetSlotRequest) (*GetSlotResponse, error) { 175 | return nil, status.Errorf(codes.Unimplemented, "method GetSlot not implemented") 176 | } 177 | func (UnimplementedGeyserServer) IsBlockhashValid(context.Context, *IsBlockhashValidRequest) (*IsBlockhashValidResponse, error) { 178 | return nil, status.Errorf(codes.Unimplemented, "method IsBlockhashValid not implemented") 179 | } 180 | func (UnimplementedGeyserServer) GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) { 181 | return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented") 182 | } 183 | func (UnimplementedGeyserServer) mustEmbedUnimplementedGeyserServer() {} 184 | func (UnimplementedGeyserServer) testEmbeddedByValue() {} 185 | 186 | // UnsafeGeyserServer may be embedded to opt out of forward compatibility for this service. 187 | // Use of this interface is not recommended, as added methods to GeyserServer will 188 | // result in compilation errors. 189 | type UnsafeGeyserServer interface { 190 | mustEmbedUnimplementedGeyserServer() 191 | } 192 | 193 | func RegisterGeyserServer(s grpc.ServiceRegistrar, srv GeyserServer) { 194 | // If the following call pancis, it indicates UnimplementedGeyserServer was 195 | // embedded by pointer and is nil. This will cause panics if an 196 | // unimplemented method is ever invoked, so we test this at initialization 197 | // time to prevent it from happening at runtime later due to I/O. 198 | if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { 199 | t.testEmbeddedByValue() 200 | } 201 | s.RegisterService(&Geyser_ServiceDesc, srv) 202 | } 203 | 204 | func _Geyser_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { 205 | return srv.(GeyserServer).Subscribe(&grpc.GenericServerStream[SubscribeRequest, SubscribeUpdate]{ServerStream: stream}) 206 | } 207 | 208 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 209 | type Geyser_SubscribeServer = grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate] 210 | 211 | func _Geyser_SubscribeReplayInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 212 | in := new(SubscribeReplayInfoRequest) 213 | if err := dec(in); err != nil { 214 | return nil, err 215 | } 216 | if interceptor == nil { 217 | return srv.(GeyserServer).SubscribeReplayInfo(ctx, in) 218 | } 219 | info := &grpc.UnaryServerInfo{ 220 | Server: srv, 221 | FullMethod: Geyser_SubscribeReplayInfo_FullMethodName, 222 | } 223 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 224 | return srv.(GeyserServer).SubscribeReplayInfo(ctx, req.(*SubscribeReplayInfoRequest)) 225 | } 226 | return interceptor(ctx, in, info, handler) 227 | } 228 | 229 | func _Geyser_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 230 | in := new(PingRequest) 231 | if err := dec(in); err != nil { 232 | return nil, err 233 | } 234 | if interceptor == nil { 235 | return srv.(GeyserServer).Ping(ctx, in) 236 | } 237 | info := &grpc.UnaryServerInfo{ 238 | Server: srv, 239 | FullMethod: Geyser_Ping_FullMethodName, 240 | } 241 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 242 | return srv.(GeyserServer).Ping(ctx, req.(*PingRequest)) 243 | } 244 | return interceptor(ctx, in, info, handler) 245 | } 246 | 247 | func _Geyser_GetLatestBlockhash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 248 | in := new(GetLatestBlockhashRequest) 249 | if err := dec(in); err != nil { 250 | return nil, err 251 | } 252 | if interceptor == nil { 253 | return srv.(GeyserServer).GetLatestBlockhash(ctx, in) 254 | } 255 | info := &grpc.UnaryServerInfo{ 256 | Server: srv, 257 | FullMethod: Geyser_GetLatestBlockhash_FullMethodName, 258 | } 259 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 260 | return srv.(GeyserServer).GetLatestBlockhash(ctx, req.(*GetLatestBlockhashRequest)) 261 | } 262 | return interceptor(ctx, in, info, handler) 263 | } 264 | 265 | func _Geyser_GetBlockHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 266 | in := new(GetBlockHeightRequest) 267 | if err := dec(in); err != nil { 268 | return nil, err 269 | } 270 | if interceptor == nil { 271 | return srv.(GeyserServer).GetBlockHeight(ctx, in) 272 | } 273 | info := &grpc.UnaryServerInfo{ 274 | Server: srv, 275 | FullMethod: Geyser_GetBlockHeight_FullMethodName, 276 | } 277 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 278 | return srv.(GeyserServer).GetBlockHeight(ctx, req.(*GetBlockHeightRequest)) 279 | } 280 | return interceptor(ctx, in, info, handler) 281 | } 282 | 283 | func _Geyser_GetSlot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 284 | in := new(GetSlotRequest) 285 | if err := dec(in); err != nil { 286 | return nil, err 287 | } 288 | if interceptor == nil { 289 | return srv.(GeyserServer).GetSlot(ctx, in) 290 | } 291 | info := &grpc.UnaryServerInfo{ 292 | Server: srv, 293 | FullMethod: Geyser_GetSlot_FullMethodName, 294 | } 295 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 296 | return srv.(GeyserServer).GetSlot(ctx, req.(*GetSlotRequest)) 297 | } 298 | return interceptor(ctx, in, info, handler) 299 | } 300 | 301 | func _Geyser_IsBlockhashValid_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 302 | in := new(IsBlockhashValidRequest) 303 | if err := dec(in); err != nil { 304 | return nil, err 305 | } 306 | if interceptor == nil { 307 | return srv.(GeyserServer).IsBlockhashValid(ctx, in) 308 | } 309 | info := &grpc.UnaryServerInfo{ 310 | Server: srv, 311 | FullMethod: Geyser_IsBlockhashValid_FullMethodName, 312 | } 313 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 314 | return srv.(GeyserServer).IsBlockhashValid(ctx, req.(*IsBlockhashValidRequest)) 315 | } 316 | return interceptor(ctx, in, info, handler) 317 | } 318 | 319 | func _Geyser_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 320 | in := new(GetVersionRequest) 321 | if err := dec(in); err != nil { 322 | return nil, err 323 | } 324 | if interceptor == nil { 325 | return srv.(GeyserServer).GetVersion(ctx, in) 326 | } 327 | info := &grpc.UnaryServerInfo{ 328 | Server: srv, 329 | FullMethod: Geyser_GetVersion_FullMethodName, 330 | } 331 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 332 | return srv.(GeyserServer).GetVersion(ctx, req.(*GetVersionRequest)) 333 | } 334 | return interceptor(ctx, in, info, handler) 335 | } 336 | 337 | // Geyser_ServiceDesc is the grpc.ServiceDesc for Geyser service. 338 | // It's only intended for direct use with grpc.RegisterService, 339 | // and not to be introspected or modified (even as a copy) 340 | var Geyser_ServiceDesc = grpc.ServiceDesc{ 341 | ServiceName: "geyser.Geyser", 342 | HandlerType: (*GeyserServer)(nil), 343 | Methods: []grpc.MethodDesc{ 344 | { 345 | MethodName: "SubscribeReplayInfo", 346 | Handler: _Geyser_SubscribeReplayInfo_Handler, 347 | }, 348 | { 349 | MethodName: "Ping", 350 | Handler: _Geyser_Ping_Handler, 351 | }, 352 | { 353 | MethodName: "GetLatestBlockhash", 354 | Handler: _Geyser_GetLatestBlockhash_Handler, 355 | }, 356 | { 357 | MethodName: "GetBlockHeight", 358 | Handler: _Geyser_GetBlockHeight_Handler, 359 | }, 360 | { 361 | MethodName: "GetSlot", 362 | Handler: _Geyser_GetSlot_Handler, 363 | }, 364 | { 365 | MethodName: "IsBlockhashValid", 366 | Handler: _Geyser_IsBlockhashValid_Handler, 367 | }, 368 | { 369 | MethodName: "GetVersion", 370 | Handler: _Geyser_GetVersion_Handler, 371 | }, 372 | }, 373 | Streams: []grpc.StreamDesc{ 374 | { 375 | StreamName: "Subscribe", 376 | Handler: _Geyser_Subscribe_Handler, 377 | ServerStreams: true, 378 | ClientStreams: true, 379 | }, 380 | }, 381 | Metadata: "geyser.proto", 382 | } 383 | --------------------------------------------------------------------------------