├── .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 | [](https://pkg.go.dev/github.com/weeaa/goyser?tab=doc)
3 | [](https://goreportcard.com/report/github.com/weeaa/goyser)
4 | [](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 |

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 |
--------------------------------------------------------------------------------