diff --git a/packages/grid_client/scripts/currency.ts b/packages/grid_client/scripts/currency.ts index 408836fbd0..c8b3dfd286 100644 --- a/packages/grid_client/scripts/currency.ts +++ b/packages/grid_client/scripts/currency.ts @@ -62,7 +62,7 @@ function yearlyUSD(hourlyUSD) { } async function main() { const grid = await getClient(); - const rate = await grid.tfclient.tftPrice.get(); + const rate = await grid.tfclient.tftPrice.getTFTBillingRateUSD(); const decimals = 3; currency = new TFTUSDConversionService(rate, decimals); diff --git a/packages/grid_client/src/clients/tf-grid/contracts.ts b/packages/grid_client/src/clients/tf-grid/contracts.ts index be5084abd1..32ddae1c3f 100644 --- a/packages/grid_client/src/clients/tf-grid/contracts.ts +++ b/packages/grid_client/src/clients/tf-grid/contracts.ts @@ -20,6 +20,7 @@ import { Decimal } from "decimal.js"; import { bytesToGB, formatErrorMessage } from "../../helpers"; import { calculator, ContractStates, currency } from "../../modules"; import { Graphql } from "../graphql/client"; +import { TFTPrice } from "./tftPrice"; export type DiscountLevel = "None" | "Default" | "Bronze" | "Silver" | "Gold"; @@ -387,7 +388,7 @@ class TFContracts extends Contracts { */ private async convertToTFT(USD: Decimal) { try { - const tftPrice = (await this.client.tftPrice.get()) ?? 0; + const tftPrice = await (this.client.tftPrice as TFTPrice).getTFTBillingRateUSD(); const tft = new currency(tftPrice, 15).convertUSDtoTFT({ amount: USD.toNumber() }); return new Decimal(tft); } catch (error) { diff --git a/packages/grid_client/src/clients/tf-grid/tftPrice.ts b/packages/grid_client/src/clients/tf-grid/tftPrice.ts index 9c19668fbe..0f88feba5e 100644 --- a/packages/grid_client/src/clients/tf-grid/tftPrice.ts +++ b/packages/grid_client/src/clients/tf-grid/tftPrice.ts @@ -5,6 +5,11 @@ class TFTPrice extends QueryTFTPrice { const priceInMili = await super.get(); return priceInMili / 1000; } + + async getTFTBillingRateUSD(): Promise { + const priceInMili = await super.getTFTBillingRateMUSD(); + return priceInMili / 1000; + } } export { TFTPrice }; diff --git a/packages/grid_client/src/modules/calculator.ts b/packages/grid_client/src/modules/calculator.ts index 7ad86ef682..5f5e19af18 100644 --- a/packages/grid_client/src/modules/calculator.ts +++ b/packages/grid_client/src/modules/calculator.ts @@ -149,8 +149,12 @@ class Calculator { @expose @validateInput async tftPrice(): Promise { - const pricing = await this.client.tftPrice.get(); - return this.client instanceof TFClient ? pricing : pricing / 1000; + if (this.client instanceof TFClient) { + return await this.client.tftPrice.getTFTBillingRateUSD(); + } else { + const pricing = await this.client.tftPrice.getTFTBillingRateMUSD(); + return pricing / 1000; + } } /** diff --git a/packages/playground/src/weblets/tf_contracts_list.vue b/packages/playground/src/weblets/tf_contracts_list.vue index a6c5fa57d9..21ab08597b 100644 --- a/packages/playground/src/weblets/tf_contracts_list.vue +++ b/packages/playground/src/weblets/tf_contracts_list.vue @@ -514,7 +514,7 @@ async function getTotalCost() { loadingTotalCost.value = true; const res = await gridProxyClient.twins.getConsumption(profileManager.profile!.twinId); totalTFT.value = +(res.last_hour_consumption || 0).toFixed(3); - const tftPrice = await queryClient.tftPrice.get(); + const tftPrice = await queryClient.tftPrice.getTFTBillingRateMUSD(); totalCostUSD.value = +(totalTFT.value * (tftPrice / 1000)).toFixed(3); } catch (error: any) { totalTFT.value = 0; diff --git a/packages/tfchain_client/src/tft_price.ts b/packages/tfchain_client/src/tft_price.ts index c86899a489..2d032fe9ec 100644 --- a/packages/tfchain_client/src/tft_price.ts +++ b/packages/tfchain_client/src/tft_price.ts @@ -11,6 +11,46 @@ class QueryTFTPrice { const res = await this.client.api.query.tftPriceModule.tftPrice(); return res.toPrimitive() as number; } + + @checkConnection + async getTFTBillingRateMUSD(): Promise { + const [minRes, maxRes, avgRes] = await Promise.all([ + this.client.api.query.tftPriceModule.minTftPrice(), + this.client.api.query.tftPriceModule.maxTftPrice(), + this.client.api.query.tftPriceModule.averageTftPrice(), + ]); + + const minPrice = minRes.toPrimitive(); + const maxPrice = maxRes.toPrimitive(); + const averagePrice = avgRes.toPrimitive(); + + if (typeof minPrice !== "number" || isNaN(minPrice)) { + throw new Error("Failed to retrieve minimum TFT price: invalid value returned"); + } + if (typeof maxPrice !== "number" || isNaN(maxPrice)) { + throw new Error("Failed to retrieve maximum TFT price: invalid value returned"); + } + if (typeof averagePrice !== "number" || isNaN(averagePrice)) { + throw new Error("Failed to retrieve average TFT price: invalid value returned"); + } + + // Clamp the average between the configured bounds, matching on-chain billing logic + return this.clampTFTPrice(averagePrice, minPrice, maxPrice); + } + + /** + * tft_price = max(AverageTftPrice, MinTftPrice) then min(_, MaxTftPrice) + */ + private clampTFTPrice(avg: number, min: number, max: number): number { + let rate = avg; + if (rate < min) { + rate = min; + } + if (rate > max) { + rate = max; + } + return rate; + } } export { QueryTFTPrice };