@@ -20,53 +20,70 @@ import (
2020 "fmt"
2121
2222 "github.com/gagliardetto/solana-go"
23+ "github.com/gagliardetto/solana-go/rpc"
2324)
2425
2526// 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 ) {
2728 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
3032 }
31- return price , nil
33+ return PriceAccountEntry {
34+ PriceAccount : price ,
35+ Pubkey : priceKey ,
36+ Slot : slot ,
37+ }, nil
3238}
3339
3440// 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 ) {
3642 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
3946 }
40- return product , nil
47+ return ProductAccountEntry {
48+ ProductAccount : product ,
49+ Pubkey : productKey ,
50+ Slot : slot ,
51+ }, nil
4152}
4253
4354// 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 ) {
4556 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
4860 }
49- return mapping , nil
61+ return MappingAccountEntry {
62+ MappingAccount : mapping ,
63+ Pubkey : mappingKey ,
64+ Slot : slot ,
65+ }, nil
5066}
5167
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 } )
5470 if err != nil {
55- return err
71+ return 0 , err
5672 }
5773
74+ slot = info .Context .Slot
5875 data := info .Value .Data .GetBinary ()
59- return acc .UnmarshalBinary (data )
76+ return slot , acc .UnmarshalBinary (data )
6077}
6178
6279// 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 ) {
6481 var products []solana.PublicKey
6582 next := c .Env .Mapping
6683
6784 const maxAccounts = 128 // arbitrary limit on the mapping account list length
6885 for i := 0 ; i < maxAccounts && ! next .IsZero (); i ++ {
69- acc , err := c .GetMappingAccount (ctx , next )
86+ acc , err := c .GetMappingAccount (ctx , next , commitment )
7087 if err != nil {
7188 return products , fmt .Errorf ("error getting mapping account %s (#%d): %w" , next , i + 1 , err )
7289 }
@@ -77,17 +94,11 @@ func (c *Client) GetAllProductKeys(ctx context.Context) ([]solana.PublicKey, err
7794 return products , nil
7895}
7996
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.
8798//
8899// 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 )
91102 if err != nil {
92103 return nil , err
93104 }
@@ -103,16 +114,21 @@ func (c *Client) GetAllProducts(ctx context.Context) ([]ProductAccountEntry, err
103114 keys = nil
104115 }
105116
106- if err := c .getProductsPage (ctx , & accs , nextKeys ); err != nil {
117+ if err := c .getProductAccountsPage (ctx , & accs , nextKeys , commitment ); err != nil {
107118 return accs , err
108119 }
109120 }
110121
111122 return accs , nil
112123}
113124
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 })
116132 if err != nil {
117133 return err
118134 }
@@ -123,13 +139,101 @@ func (c *Client) getProductsPage(ctx context.Context, accs *[]ProductAccountEntr
123139
124140 for i , info := range res .Value {
125141 accountData := info .Data .GetBinary ()
126- var acc ProductAccount
142+ acc := new ( ProductAccount )
127143 if err := acc .UnmarshalBinary (accountData ); err != nil {
128144 return fmt .Errorf ("failed to retrieve product account %s: %w" , keys [i ], err )
129145 }
130146 * accs = append (* accs , ProductAccountEntry {
131147 ProductAccount : acc ,
132148 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 ,
133237 })
134238 }
135239
0 commit comments