@@ -20,53 +20,70 @@ import (
20
20
"fmt"
21
21
22
22
"github.com/gagliardetto/solana-go"
23
+ "github.com/gagliardetto/solana-go/rpc"
23
24
)
24
25
25
26
// GetPriceAccount retrieves a price account from the blockchain.
26
- func (c * Client ) GetPriceAccount (ctx context.Context , priceKey solana.PublicKey ) (* PriceAccount , error ) {
27
+ func (c * Client ) GetPriceAccount (ctx context.Context , priceKey solana.PublicKey , commitment rpc. CommitmentType ) (PriceAccountEntry , error ) {
27
28
price := new (PriceAccount )
28
- if err := c .queryFor (ctx , price , priceKey ); err != nil {
29
- return nil , err
29
+ slot , err := c .queryFor (ctx , price , priceKey , commitment )
30
+ if err != nil {
31
+ return PriceAccountEntry {}, err
30
32
}
31
- return price , nil
33
+ return PriceAccountEntry {
34
+ PriceAccount : price ,
35
+ Pubkey : priceKey ,
36
+ Slot : slot ,
37
+ }, nil
32
38
}
33
39
34
40
// GetProductAccount retrieves a product account from the blockchain.
35
- func (c * Client ) GetProductAccount (ctx context.Context , productKey solana.PublicKey ) (* ProductAccount , error ) {
41
+ func (c * Client ) GetProductAccount (ctx context.Context , productKey solana.PublicKey , commitment rpc. CommitmentType ) (ProductAccountEntry , error ) {
36
42
product := new (ProductAccount )
37
- if err := c .queryFor (ctx , product , productKey ); err != nil {
38
- return nil , err
43
+ slot , err := c .queryFor (ctx , product , productKey , commitment )
44
+ if err != nil {
45
+ return ProductAccountEntry {}, err
39
46
}
40
- return product , nil
47
+ return ProductAccountEntry {
48
+ ProductAccount : product ,
49
+ Pubkey : productKey ,
50
+ Slot : slot ,
51
+ }, nil
41
52
}
42
53
43
54
// GetMappingAccount retrieves a single mapping account from the blockchain.
44
- func (c * Client ) GetMappingAccount (ctx context.Context , mappingKey solana.PublicKey ) (* MappingAccount , error ) {
55
+ func (c * Client ) GetMappingAccount (ctx context.Context , mappingKey solana.PublicKey , commitment rpc. CommitmentType ) (MappingAccountEntry , error ) {
45
56
mapping := new (MappingAccount )
46
- if err := c .queryFor (ctx , mapping , mappingKey ); err != nil {
47
- return nil , err
57
+ slot , err := c .queryFor (ctx , mapping , mappingKey , commitment )
58
+ if err != nil {
59
+ return MappingAccountEntry {}, err
48
60
}
49
- return mapping , nil
61
+ return MappingAccountEntry {
62
+ MappingAccount : mapping ,
63
+ Pubkey : mappingKey ,
64
+ Slot : slot ,
65
+ }, nil
50
66
}
51
67
52
- func (c * Client ) queryFor (ctx context.Context , acc encoding.BinaryUnmarshaler , key solana.PublicKey ) error {
53
- info , err := c .RPC .GetAccountInfo (ctx , key )
68
+ func (c * Client ) queryFor (ctx context.Context , acc encoding.BinaryUnmarshaler , key solana.PublicKey , commitment rpc. CommitmentType ) ( slot uint64 , err error ) {
69
+ info , err := c .RPC .GetAccountInfoWithOpts (ctx , key , & rpc. GetAccountInfoOpts { Commitment : commitment } )
54
70
if err != nil {
55
- return err
71
+ return 0 , err
56
72
}
57
73
74
+ slot = info .Context .Slot
58
75
data := info .Value .Data .GetBinary ()
59
- return acc .UnmarshalBinary (data )
76
+ return slot , acc .UnmarshalBinary (data )
60
77
}
61
78
62
79
// GetAllProductKeys lists all mapping accounts for product account pubkeys.
63
- func (c * Client ) GetAllProductKeys (ctx context.Context ) ([]solana.PublicKey , error ) {
80
+ func (c * Client ) GetAllProductKeys (ctx context.Context , commitment rpc. CommitmentType ) ([]solana.PublicKey , error ) {
64
81
var products []solana.PublicKey
65
82
next := c .Env .Mapping
66
83
67
84
const maxAccounts = 128 // arbitrary limit on the mapping account list length
68
85
for i := 0 ; i < maxAccounts && ! next .IsZero (); i ++ {
69
- acc , err := c .GetMappingAccount (ctx , next )
86
+ acc , err := c .GetMappingAccount (ctx , next , commitment )
70
87
if err != nil {
71
88
return products , fmt .Errorf ("error getting mapping account %s (#%d): %w" , next , i + 1 , err )
72
89
}
@@ -77,17 +94,11 @@ func (c *Client) GetAllProductKeys(ctx context.Context) ([]solana.PublicKey, err
77
94
return products , nil
78
95
}
79
96
80
- // ProductAccountEntry is a product account and its pubkey.
81
- type ProductAccountEntry struct {
82
- ProductAccount
83
- Pubkey solana.PublicKey `json:"pubkey"`
84
- }
85
-
86
- // GetAllProducts returns all product accounts.
97
+ // GetAllProductAccounts returns all product accounts.
87
98
//
88
99
// Aborts and returns an error if any product account failed to fetch.
89
- func (c * Client ) GetAllProducts (ctx context.Context ) ([]ProductAccountEntry , error ) {
90
- keys , err := c .GetAllProductKeys (ctx )
100
+ func (c * Client ) GetAllProductAccounts (ctx context.Context , commitment rpc. CommitmentType ) ([]ProductAccountEntry , error ) {
101
+ keys , err := c .GetAllProductKeys (ctx , commitment )
91
102
if err != nil {
92
103
return nil , err
93
104
}
@@ -103,16 +114,21 @@ func (c *Client) GetAllProducts(ctx context.Context) ([]ProductAccountEntry, err
103
114
keys = nil
104
115
}
105
116
106
- if err := c .getProductsPage (ctx , & accs , nextKeys ); err != nil {
117
+ if err := c .getProductAccountsPage (ctx , & accs , nextKeys , commitment ); err != nil {
107
118
return accs , err
108
119
}
109
120
}
110
121
111
122
return accs , nil
112
123
}
113
124
114
- func (c * Client ) getProductsPage (ctx context.Context , accs * []ProductAccountEntry , keys []solana.PublicKey ) error {
115
- res , err := c .RPC .GetMultipleAccounts (ctx , keys ... )
125
+ func (c * Client ) getProductAccountsPage (
126
+ ctx context.Context ,
127
+ accs * []ProductAccountEntry , // accounts out
128
+ keys []solana.PublicKey , // keys in
129
+ commitment rpc.CommitmentType ,
130
+ ) error {
131
+ res , err := c .RPC .GetMultipleAccountsWithOpts (ctx , keys , & rpc.GetMultipleAccountsOpts {Commitment : commitment })
116
132
if err != nil {
117
133
return err
118
134
}
@@ -123,13 +139,101 @@ func (c *Client) getProductsPage(ctx context.Context, accs *[]ProductAccountEntr
123
139
124
140
for i , info := range res .Value {
125
141
accountData := info .Data .GetBinary ()
126
- var acc ProductAccount
142
+ acc := new ( ProductAccount )
127
143
if err := acc .UnmarshalBinary (accountData ); err != nil {
128
144
return fmt .Errorf ("failed to retrieve product account %s: %w" , keys [i ], err )
129
145
}
130
146
* accs = append (* accs , ProductAccountEntry {
131
147
ProductAccount : acc ,
132
148
Pubkey : keys [i ],
149
+ Slot : res .Context .Slot ,
150
+ })
151
+ }
152
+
153
+ return nil
154
+ }
155
+
156
+ // GetAllPriceAccounts returns all price accounts.
157
+ //
158
+ // Aborts and returns an error if any product account failed to fetch.
159
+ func (c * Client ) GetAllPriceAccounts (ctx context.Context , commitment rpc.CommitmentType ) ([]PriceAccountEntry , error ) {
160
+ // Start by enumerating all product accounts. They contain the first price account of each product.
161
+ products , err := c .GetAllProductAccounts (ctx , commitment )
162
+ if err != nil {
163
+ return nil , err
164
+ }
165
+
166
+ // List of keys that we need to fetch.
167
+ keys := make ([]solana.PublicKey , 0 , len (products ))
168
+ for _ , product := range products {
169
+ if ! product .FirstPrice .IsZero () {
170
+ keys = append (keys , product .FirstPrice )
171
+ }
172
+ }
173
+
174
+ return c .GetPriceAccountsRecursive (ctx , commitment , keys ... )
175
+ }
176
+
177
+ // GetPriceAccountsRecursive retrieves the price accounts of the given public keys.
178
+ //
179
+ // If these price accounts have successors, their contents will be fetched as well, recursively.
180
+ // When called with the ProductAccountHeader.FirstPrice, it will fetch all price accounts of a product.
181
+ func (c * Client ) GetPriceAccountsRecursive (ctx context.Context , commitment rpc.CommitmentType , priceKeys ... solana.PublicKey ) ([]PriceAccountEntry , error ) {
182
+ // Set of accounts seen to prevent infinite loops of price account linked lists.
183
+ // Technically, infinite loops should never occur. But you never know.
184
+ seen := make (map [solana.PublicKey ]struct {})
185
+
186
+ var accs []PriceAccountEntry
187
+ for len (priceKeys ) > 0 {
188
+ // Get next block of keys from list.
189
+ nextKeys := priceKeys
190
+ if len (nextKeys ) > c .AccountsBatchSize {
191
+ nextKeys = nextKeys [:c .AccountsBatchSize ]
192
+ priceKeys = priceKeys [c .AccountsBatchSize :]
193
+ } else {
194
+ priceKeys = nil
195
+ }
196
+
197
+ if err := c .getPriceAccountsPage (ctx , & accs , nextKeys , & priceKeys , seen , commitment ); err != nil {
198
+ return accs , err
199
+ }
200
+ }
201
+
202
+ return accs , nil
203
+ }
204
+
205
+ func (c * Client ) getPriceAccountsPage (
206
+ ctx context.Context ,
207
+ accs * []PriceAccountEntry , // accounts out
208
+ nextKeys []solana.PublicKey , // keys in
209
+ allKeys * []solana.PublicKey , // keys out
210
+ visitedKeys map [solana.PublicKey ]struct {}, // keys seen
211
+ commitment rpc.CommitmentType ,
212
+ ) error {
213
+ res , err := c .RPC .GetMultipleAccountsWithOpts (ctx , nextKeys , & rpc.GetMultipleAccountsOpts {Commitment : commitment })
214
+ if err != nil {
215
+ return err
216
+ }
217
+
218
+ if len (res .Value ) != len (nextKeys ) {
219
+ return fmt .Errorf ("unexpected number of price accounts, asked for %d but got %d" , len (nextKeys ), len (res .Value ))
220
+ }
221
+
222
+ for i , info := range res .Value {
223
+ accountData := info .Data .GetBinary ()
224
+ acc := new (PriceAccount )
225
+ if err := acc .UnmarshalBinary (accountData ); err != nil {
226
+ return fmt .Errorf ("failed to retrieve product account %s: %w" , nextKeys [i ], err )
227
+ }
228
+ _ , seen := visitedKeys [acc .Next ]
229
+ if ! seen && ! acc .Next .IsZero () {
230
+ * allKeys = append (* allKeys , acc .Next )
231
+ visitedKeys [acc .Next ] = struct {}{}
232
+ }
233
+ * accs = append (* accs , PriceAccountEntry {
234
+ PriceAccount : acc ,
235
+ Pubkey : nextKeys [i ],
236
+ Slot : res .Context .Slot ,
133
237
})
134
238
}
135
239
0 commit comments