@@ -97,3 +97,142 @@ func (s *Substrate) GetAverageTFTPrice() (price types.U32, err error) {
9797
9898 return
9999}
100+
101+ // GetTFTBillingRate returns the current billing rate (in mUSD) used on-chain,
102+ // computed as AverageTftPrice clamped between MinTftPrice and MaxTftPrice.
103+ func (s * Substrate ) GetTFTBillingRate () (rate types.U32 , err error ) {
104+ cl , meta , err := s .GetClient ()
105+ if err != nil {
106+ return
107+ }
108+
109+ // Build keys
110+ keyAvg , err := types .CreateStorageKey (meta , "TFTPriceModule" , "AverageTftPrice" )
111+ if err != nil {
112+ return rate , errors .Wrap (err , "failed to create storage key (AverageTftPrice)" )
113+ }
114+ keyMin , err := types .CreateStorageKey (meta , "TFTPriceModule" , "MinTftPrice" )
115+ if err != nil {
116+ return rate , errors .Wrap (err , "failed to create storage key (MinTftPrice)" )
117+ }
118+ keyMax , err := types .CreateStorageKey (meta , "TFTPriceModule" , "MaxTftPrice" )
119+ if err != nil {
120+ return rate , errors .Wrap (err , "failed to create storage key (MaxTftPrice)" )
121+ }
122+
123+ // Read latest values
124+ var avg , min , max types.U32
125+ ok , err := cl .RPC .State .GetStorageLatest (keyAvg , & avg )
126+ if err != nil {
127+ return rate , errors .Wrap (err , "failed to read AverageTftPrice" )
128+ }
129+ if ! ok {
130+ return rate , errors .Wrap (ErrNotFound , "AverageTftPrice not found" )
131+ }
132+
133+ ok , err = cl .RPC .State .GetStorageLatest (keyMin , & min )
134+ if err != nil {
135+ return rate , errors .Wrap (err , "failed to read MinTftPrice" )
136+ }
137+ if ! ok {
138+ return rate , errors .Wrap (ErrNotFound , "MinTftPrice not found" )
139+ }
140+
141+ ok , err = cl .RPC .State .GetStorageLatest (keyMax , & max )
142+ if err != nil {
143+ return rate , errors .Wrap (err , "failed to read MaxTftPrice" )
144+ }
145+ if ! ok {
146+ return rate , errors .Wrap (ErrNotFound , "MaxTftPrice not found" )
147+ }
148+
149+ rate = clampTFTPrice (avg , min , max )
150+ return
151+ }
152+
153+ // GetTFTBillingRateAt returns the billing rate (in mUSD) at a specific block number,
154+ // computed as AverageTftPrice clamped between MinTftPrice and MaxTftPrice at that block.
155+ func (s * Substrate ) GetTFTBillingRateAt (block uint64 ) (rate types.U32 , err error ) {
156+ cl , _ , err := s .GetClient ()
157+ if err != nil {
158+ return
159+ }
160+
161+ // Resolve block hash
162+ bh , err := cl .RPC .Chain .GetBlockHash (block )
163+ if err != nil {
164+ return rate , errors .Wrap (err , "failed to resolve block hash" )
165+ }
166+
167+ // Metadata at block
168+ metaAtBlock , err := cl .RPC .State .GetMetadata (bh )
169+ if err != nil {
170+ return rate , errors .Wrap (err , "failed to get metadata at block" )
171+ }
172+
173+ // Keys at block
174+ keyAvg , err := types .CreateStorageKey (metaAtBlock , "TFTPriceModule" , "AverageTftPrice" )
175+ if err != nil {
176+ return rate , errors .Wrap (err , "failed to create storage key (AverageTftPrice)" )
177+ }
178+ keyMin , err := types .CreateStorageKey (metaAtBlock , "TFTPriceModule" , "MinTftPrice" )
179+ if err != nil {
180+ return rate , errors .Wrap (err , "failed to create storage key (MinTftPrice)" )
181+ }
182+ keyMax , err := types .CreateStorageKey (metaAtBlock , "TFTPriceModule" , "MaxTftPrice" )
183+ if err != nil {
184+ return rate , errors .Wrap (err , "failed to create storage key (MaxTftPrice)" )
185+ }
186+
187+ // Read at block
188+ var avg , min , max types.U32
189+ raw , err := cl .RPC .State .GetStorageRaw (keyAvg , bh )
190+ if err != nil {
191+ return rate , errors .Wrap (err , "failed to get AverageTftPrice at block" )
192+ }
193+ if len (* raw ) == 0 {
194+ return rate , errors .Wrap (ErrNotFound , "AverageTftPrice not found at block" )
195+ }
196+ if err := Decode (* raw , & avg ); err != nil {
197+ return rate , errors .Wrap (err , "failed to decode AverageTftPrice" )
198+ }
199+
200+ raw , err = cl .RPC .State .GetStorageRaw (keyMin , bh )
201+ if err != nil {
202+ return rate , errors .Wrap (err , "failed to get MinTftPrice at block" )
203+ }
204+ if len (* raw ) == 0 {
205+ return rate , errors .Wrap (ErrNotFound , "MinTftPrice not found at block" )
206+ }
207+ if err := Decode (* raw , & min ); err != nil {
208+ return rate , errors .Wrap (err , "failed to decode MinTftPrice" )
209+ }
210+
211+ raw , err = cl .RPC .State .GetStorageRaw (keyMax , bh )
212+ if err != nil {
213+ return rate , errors .Wrap (err , "failed to get MaxTftPrice at block" )
214+ }
215+ if len (* raw ) == 0 {
216+ return rate , errors .Wrap (ErrNotFound , "MaxTftPrice not found at block" )
217+ }
218+ if err := Decode (* raw , & max ); err != nil {
219+ return rate , errors .Wrap (err , "failed to decode MaxTftPrice" )
220+ }
221+
222+ // Clamp
223+ rate = clampTFTPrice (avg , min , max )
224+ return
225+ }
226+
227+ // clampTFTPrice mirrors the on-chain clamping logic used by pallet-smart-contract
228+ // tft_price = max(AverageTftPrice, MinTftPrice) then min(_, MaxTftPrice)
229+ func clampTFTPrice (avg , min , max types.U32 ) types.U32 {
230+ rate := avg
231+ if rate < min {
232+ rate = min
233+ }
234+ if rate > max {
235+ rate = max
236+ }
237+ return rate
238+ }
0 commit comments