1515package pyth
1616
1717import (
18+ "encoding/json"
1819 "errors"
20+ "fmt"
1921
2022 bin "github.com/gagliardetto/binary"
2123 "github.com/gagliardetto/solana-go"
@@ -67,6 +69,9 @@ type ProductAccount struct {
6769 AttrsData [464 ]byte // key-value string pairs of additional data
6870}
6971
72+ // ProductAccountAttrsDataOffset is the binary offset of the AttrsData field within ProductAccount.
73+ const ProductAccountAttrsDataOffset = 48
74+
7075// UnmarshalBinary decodes the product account from the on-chain format.
7176func (p * ProductAccount ) UnmarshalBinary (buf []byte ) error {
7277 decoder := bin .NewBinDecoder (buf )
@@ -86,7 +91,7 @@ func (p *ProductAccount) UnmarshalBinary(buf []byte) error {
8691func (p * ProductAccount ) GetAttrsMap () (AttrsMap , error ) {
8792 // Length of attrs is determined by size value in header.
8893 data := p .AttrsData [:]
89- maxSize := int (p .Size ) - 48
94+ maxSize := int (p .Size ) - ProductAccountAttrsDataOffset
9095 if maxSize > 0 && len (data ) > maxSize {
9196 data = data [:maxSize ]
9297 }
@@ -96,6 +101,50 @@ func (p *ProductAccount) GetAttrsMap() (AttrsMap, error) {
96101 return attrs , err
97102}
98103
104+ // UnmarshalJSON loads the product account's content from JSON.
105+ func (p * ProductAccount ) UnmarshalJSON (data []byte ) error {
106+ // Decode JSON as strings map.
107+ var content map [string ]string
108+ if err := json .Unmarshal (data , & content ); err != nil {
109+ return err
110+ }
111+ // Re-encode as binary data.
112+ attrsMap , err := NewAttrsMap (content )
113+ if err != nil {
114+ return err
115+ }
116+ mapData , err := attrsMap .MarshalBinary ()
117+ if err != nil {
118+ return err // unreachable
119+ }
120+ if len (mapData ) > len (p .AttrsData ) {
121+ return fmt .Errorf ("data does not fit in product account" )
122+ }
123+ // Copy binary data into product account, zero remaining bytes.
124+ p .AccountHeader = AccountHeader {
125+ Magic : Magic ,
126+ Version : V2 ,
127+ AccountType : AccountTypeProduct ,
128+ Size : uint32 (ProductAccountAttrsDataOffset + len (mapData )),
129+ }
130+ copy (p .AttrsData [:], mapData )
131+ for i := len (mapData ); i < len (p .AttrsData ); i ++ {
132+ p .AttrsData [i ] = 0
133+ }
134+ return nil
135+ }
136+
137+ // MarshalJSON returns a JSON-representation of the product account contents.
138+ func (p * ProductAccount ) MarshalJSON () ([]byte , error ) {
139+ // Decode binary data from product account.
140+ content , err := p .GetAttrsMap ()
141+ if err != nil {
142+ return nil , err
143+ }
144+ // Re-encode as JSON.
145+ return json .Marshal (content .KVs ())
146+ }
147+
99148// Ema is an exponentially-weighted moving average.
100149type Ema struct {
101150 Val int64
@@ -179,9 +228,9 @@ func (p *PriceAccount) GetComponent(publisher *solana.PublicKey) *PriceComp {
179228// MappingAccount is a piece of a singly linked-list of all products on Pyth.
180229type MappingAccount struct {
181230 AccountHeader
182- Num uint32
183- Unused uint32
184- Next solana.PublicKey
231+ Num uint32 // number of keys
232+ Pad1 uint32 // reserved field
233+ Next solana.PublicKey // pubkey of next mapping account
185234 Products [640 ]solana.PublicKey
186235}
187236
@@ -199,3 +248,11 @@ func (m *MappingAccount) UnmarshalBinary(buf []byte) error {
199248 }
200249 return nil
201250}
251+
252+ // ProductKeys returns the slice of product keys referenced by this mapping, excluding empty entries.
253+ func (m * MappingAccount ) ProductKeys () []solana.PublicKey {
254+ if m .Num > uint32 (len (m .Products )) {
255+ return nil
256+ }
257+ return m .Products [:m .Num ]
258+ }
0 commit comments