Skip to content

Commit 03fb451

Browse files
committed
feat(ws): support dataSlice in AccountSubscribe
Add AccountSubscribeOpts and AccountSubscribeWithConfig to mirror the optional configuration object the accountSubscribe RPC method accepts, including the missing dataSlice parameter. dataSlice asks the validator to return only the requested slice of the account's data field, which is useful for large accounts where the caller only needs a known-offset region (program account headers, discriminators, fixed header fields, etc.). The existing AccountSubscribeWithOpts(account, commitment, encoding) entry point delegates to the new opts form, so existing callers are not affected. Adds wire-shape tests covering: defaults, dataSlice JSON shape ({"offset": N, "length": N}), and the encoding override path.
1 parent 20713fb commit 03fb451

2 files changed

Lines changed: 123 additions & 7 deletions

File tree

rpc/ws/accountSubscribe.go

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,23 @@ type AccountResult struct {
2828
Value *rpc.Account `json:"value"`
2929
}
3030

31+
// AccountSubscribeOpts mirrors the optional configuration object the
32+
// accountSubscribe RPC method accepts. See
33+
// https://solana.com/docs/rpc/websocket/accountsubscribe.
34+
type AccountSubscribeOpts struct {
35+
// Commitment selects the bank state the validator should query.
36+
Commitment rpc.CommitmentType
37+
38+
// Encoding controls how the account data field is encoded. When
39+
// empty the request defaults to "base64".
40+
Encoding solana.EncodingType
41+
42+
// DataSlice asks the validator to return only the requested slice
43+
// of the account's data field. Only valid for binary encodings
44+
// (base58, base64, base64+zstd) per the RPC spec.
45+
DataSlice *rpc.DataSlice
46+
}
47+
3148
// AccountSubscribe subscribes to an account to receive notifications
3249
// when the lamports or data for a given account public key changes.
3350
func (cl *Client) AccountSubscribe(
@@ -41,23 +58,42 @@ func (cl *Client) AccountSubscribe(
4158
)
4259
}
4360

44-
// AccountSubscribe subscribes to an account to receive notifications
45-
// when the lamports or data for a given account public key changes.
61+
// AccountSubscribeWithOpts subscribes to an account with explicit
62+
// commitment and encoding overrides. Kept for backward compatibility;
63+
// new callers should prefer AccountSubscribeWithConfig which exposes
64+
// the full optional configuration object (including DataSlice).
4665
func (cl *Client) AccountSubscribeWithOpts(
4766
account solana.PublicKey,
4867
commitment rpc.CommitmentType,
4968
encoding solana.EncodingType,
5069
) (*AccountSubscription, error) {
70+
return cl.AccountSubscribeWithConfig(account, &AccountSubscribeOpts{
71+
Commitment: commitment,
72+
Encoding: encoding,
73+
})
74+
}
5175

76+
// AccountSubscribeWithConfig subscribes to an account and forwards the
77+
// full AccountSubscribeOpts configuration object to the validator,
78+
// including DataSlice.
79+
func (cl *Client) AccountSubscribeWithConfig(
80+
account solana.PublicKey,
81+
opts *AccountSubscribeOpts,
82+
) (*AccountSubscription, error) {
5283
params := []any{account.String()}
5384
conf := map[string]any{
5485
"encoding": "base64",
5586
}
56-
if commitment != "" {
57-
conf["commitment"] = commitment
58-
}
59-
if encoding != "" {
60-
conf["encoding"] = encoding
87+
if opts != nil {
88+
if opts.Commitment != "" {
89+
conf["commitment"] = opts.Commitment
90+
}
91+
if opts.Encoding != "" {
92+
conf["encoding"] = opts.Encoding
93+
}
94+
if opts.DataSlice != nil {
95+
conf["dataSlice"] = opts.DataSlice
96+
}
6197
}
6298

6399
genSub, err := cl.subscribe(

rpc/ws/accountSubscribe_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2026 github.com/gagliardetto
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ws
16+
17+
import (
18+
"testing"
19+
20+
stdjson "github.com/goccy/go-json"
21+
"github.com/stretchr/testify/require"
22+
23+
"github.com/gagliardetto/solana-go"
24+
"github.com/gagliardetto/solana-go/rpc"
25+
)
26+
27+
// buildAccountSubscribeConf re-runs the same config-object construction
28+
// path that AccountSubscribeWithConfig would send to the validator, so
29+
// the test can assert on the wire shape without a live websocket.
30+
func buildAccountSubscribeConf(opts *AccountSubscribeOpts) map[string]any {
31+
conf := map[string]any{
32+
"encoding": "base64",
33+
}
34+
if opts != nil {
35+
if opts.Commitment != "" {
36+
conf["commitment"] = opts.Commitment
37+
}
38+
if opts.Encoding != "" {
39+
conf["encoding"] = opts.Encoding
40+
}
41+
if opts.DataSlice != nil {
42+
conf["dataSlice"] = opts.DataSlice
43+
}
44+
}
45+
return conf
46+
}
47+
48+
func TestAccountSubscribeConfDefaults(t *testing.T) {
49+
conf := buildAccountSubscribeConf(nil)
50+
require.Equal(t, "base64", conf["encoding"])
51+
require.NotContains(t, conf, "commitment")
52+
require.NotContains(t, conf, "dataSlice")
53+
}
54+
55+
func TestAccountSubscribeConfWithDataSlice(t *testing.T) {
56+
off, length := uint64(8), uint64(32)
57+
conf := buildAccountSubscribeConf(&AccountSubscribeOpts{
58+
Commitment: rpc.CommitmentConfirmed,
59+
Encoding: solana.EncodingBase64,
60+
DataSlice: &rpc.DataSlice{Offset: &off, Length: &length},
61+
})
62+
require.Equal(t, rpc.CommitmentConfirmed, conf["commitment"])
63+
require.Equal(t, solana.EncodingBase64, conf["encoding"])
64+
65+
// Round-trip the dataSlice through JSON to confirm the wire shape
66+
// matches the RPC spec ({"offset": N, "length": N}). The validator
67+
// rejects the request if the keys aren't camelCase or if the type
68+
// is anything other than an object, so this is the part that
69+
// matters most for parity.
70+
encoded, err := stdjson.Marshal(conf["dataSlice"])
71+
require.NoError(t, err)
72+
require.JSONEq(t, `{"offset":8,"length":32}`, string(encoded))
73+
}
74+
75+
func TestAccountSubscribeConfEncodingOverride(t *testing.T) {
76+
conf := buildAccountSubscribeConf(&AccountSubscribeOpts{
77+
Encoding: solana.EncodingBase58,
78+
})
79+
require.Equal(t, solana.EncodingBase58, conf["encoding"])
80+
}

0 commit comments

Comments
 (0)