diff --git a/packages/grid_client/scripts/tft.ts b/packages/grid_client/scripts/tft.ts new file mode 100644 index 0000000000..408836fbd0 --- /dev/null +++ b/packages/grid_client/scripts/tft.ts @@ -0,0 +1,85 @@ +import { CurrencyModel } from "../src"; +import { currency as TFTUSDConversionService } from "../src"; +import { getClient } from "./client_loader"; +import { log } from "./utils"; + +let currency: TFTUSDConversionService; + +function convertTFTtoUSD(amount) { + const res = currency.convertTFTtoUSD(amount); + log("================= Convert TFT ================="); + log(res); + log("================= Convert TFT ================="); +} + +function convertUSDtoTFT(amount) { + const res = currency.convertUSDtoTFT(amount); + log("================= Convert USD ================="); + log(res); + log("================= Convert USD ================="); +} + +function dailyTFT(hourlyTFT) { + const res = currency.dailyTFT(hourlyTFT); + log("================= Daily TFT ================="); + log(res); + log("================= Daily TFT ================="); +} + +function monthlyTFT(hourlyTFT) { + const res = currency.monthlyTFT(hourlyTFT); + log("================= Monthly TFT ================="); + log(res); + log("================= Monthly TFT ================="); +} + +function yearlyTFT(hourlyTFT) { + const res = currency.yearlyTFT(hourlyTFT); + log("================= Yearly TFT ================="); + log(res); + log("================= Yearly TFT ================="); +} + +function dailyUSD(hourlyUSD) { + const res = currency.dailyUSD(hourlyUSD); + log("================= Daily USD ================="); + log(res); + log("================= Daily USD ================="); +} + +function monthlyUSD(hourlyUSD) { + const res = currency.monthlyUSD(hourlyUSD); + log("================= Monthly USD ================="); + log(res); + log("================= Monthly USD ================="); +} + +function yearlyUSD(hourlyUSD) { + const res = currency.yearlyUSD(hourlyUSD); + log("================= Yearly USD ================="); + log(res); + log("================= Yearly USD ================="); +} +async function main() { + const grid = await getClient(); + const rate = await grid.tfclient.tftPrice.get(); + const decimals = 3; + currency = new TFTUSDConversionService(rate, decimals); + + const amount: CurrencyModel = { + amount: 1, + }; + + convertTFTtoUSD(amount); + convertUSDtoTFT(amount); + dailyTFT(amount); + monthlyTFT(amount); + yearlyTFT(amount); + dailyUSD(amount); + monthlyUSD(amount); + yearlyUSD(amount); + + await grid.disconnect(); +} + +main(); diff --git a/packages/grid_client/src/client.ts b/packages/grid_client/src/client.ts index 1b06614da2..0496d321cc 100644 --- a/packages/grid_client/src/client.ts +++ b/packages/grid_client/src/client.ts @@ -13,7 +13,7 @@ import { isExposed } from "./helpers/expose"; import { formatErrorMessage, generateString } from "./helpers/utils"; import * as modules from "./modules/index"; import { appPath } from "./storage/backend"; -import { BackendStorage, BackendStorageType } from "./storage/backend"; +import { BackendStorageType } from "./storage/backend"; import { KeypairType } from "./zos/deployment"; class GridClient { @@ -41,6 +41,7 @@ class GridClient { stellar: modules.stellar; blockchain: modules.blockchain; calculator: modules.calculator; + currency: modules.currency; utility: modules.utility; farmerbot: modules.farmerbot; farms: modules.farms; diff --git a/packages/grid_client/src/helpers/validator.ts b/packages/grid_client/src/helpers/validator.ts index 16d84b693d..15bb9c4a75 100644 --- a/packages/grid_client/src/helpers/validator.ts +++ b/packages/grid_client/src/helpers/validator.ts @@ -1,9 +1,9 @@ import { ValidationError } from "@threefold/types"; import { plainToInstance } from "class-transformer"; -import { validate } from "class-validator"; +import { validateSync } from "class-validator"; -async function validateObject(obj) { - const errors = await validate(obj); +function validateObject(obj) { + const errors = validateSync(obj); // errors is an array of validation errors if (errors.length > 0) { console.log("Validation failed. errors:", errors); @@ -13,13 +13,13 @@ async function validateObject(obj) { // used as decorator function validateInput(target, propertyKey: string, descriptor: PropertyDescriptor) { const method = descriptor.value; - descriptor.value = async function (...args) { + descriptor.value = function (...args) { const types = Reflect.getMetadata("design:paramtypes", target, propertyKey); for (let i = 0; i < args.length; i++) { const input = plainToInstance(types[i], args[i], { excludeExtraneousValues: true }); - await validateObject(input); + validateObject(input); } - return await method.apply(this, args); + return method.apply(this, args); }; } diff --git a/packages/grid_client/src/modules/index.ts b/packages/grid_client/src/modules/index.ts index dc71ba5035..dd55eb713a 100644 --- a/packages/grid_client/src/modules/index.ts +++ b/packages/grid_client/src/modules/index.ts @@ -22,4 +22,5 @@ export * from "./farmerbot"; export * from "./farms"; export * from "./networks"; export * from "./bridge"; +export * from "./tft"; export * from "./base"; diff --git a/packages/grid_client/src/modules/models.ts b/packages/grid_client/src/modules/models.ts index b3ebd6cd75..1faf5821dd 100644 --- a/packages/grid_client/src/modules/models.ts +++ b/packages/grid_client/src/modules/models.ts @@ -832,6 +832,10 @@ class GetActiveContractsModel { @Expose() @IsInt() @IsNotEmpty() @Min(1) nodeId: number; } +class CurrencyModel { + @Expose() @IsNumber() @IsNotEmpty() @Min(0) amount: number; // hourly amount +} + interface GPUCardInfo { id: string; contract: number; @@ -977,4 +981,5 @@ export { NodeCPUTest, NodeIPValidation, NodeIPerf, + CurrencyModel, }; diff --git a/packages/grid_client/src/modules/tft.ts b/packages/grid_client/src/modules/tft.ts new file mode 100644 index 0000000000..37c02eb8be --- /dev/null +++ b/packages/grid_client/src/modules/tft.ts @@ -0,0 +1,77 @@ +import Decimal from "decimal.js"; + +import { expose, validateInput } from "../helpers"; +import { CurrencyModel } from "./models"; + +class TFTUSDConversionService { + // TFT rate: 1 TFT = x USD + constructor(protected rate: number, private decimals = 2) {} + + get _rate() { + return this.rate; + } + + @expose + @validateInput + normalizeCurrency(options: CurrencyModel): string { + return new Decimal(options.amount).toFixed(this.decimals); + } + + @expose + @validateInput + convertUSDtoTFT(options: CurrencyModel): string { + const amount = options.amount / this.rate; + return this.normalizeCurrency({ amount }); + } + + @expose + @validateInput + convertTFTtoUSD(options: CurrencyModel): string { + const amount = options.amount * this.rate; + return this.normalizeCurrency({ amount }); + } + + @expose + @validateInput + dailyTFT(options: CurrencyModel): string { + const hours = options.amount * 24; + return this.normalizeCurrency({ amount: hours }); + } + + @expose + @validateInput + monthlyTFT(options: CurrencyModel): string { + const months = +this.dailyTFT(options) * 30; + return this.normalizeCurrency({ amount: months }); + } + + @expose + @validateInput + yearlyTFT(options: CurrencyModel): string { + const years = +this.monthlyTFT(options) * 12; + return this.normalizeCurrency({ amount: years }); + } + + @expose + @validateInput + dailyUSD(options: CurrencyModel): string { + const hours = options.amount * 24; + return this.normalizeCurrency({ amount: hours }); + } + + @expose + @validateInput + monthlyUSD(options: CurrencyModel): string { + const months = +this.dailyUSD(options) * 30; + return this.normalizeCurrency({ amount: months }); + } + + @expose + @validateInput + yearlyUSD(options: CurrencyModel): string { + const years = +this.monthlyUSD(options) * 12; + return this.normalizeCurrency({ amount: years }); + } +} + +export { TFTUSDConversionService as currency }; diff --git a/packages/grid_client/tests/modules/tft.test.ts b/packages/grid_client/tests/modules/tft.test.ts new file mode 100644 index 0000000000..b5eb4f9f77 --- /dev/null +++ b/packages/grid_client/tests/modules/tft.test.ts @@ -0,0 +1,184 @@ +import { ValidationError } from "@threefold/types"; +import Decimal from "decimal.js"; + +import { currency as TFTUSDConversionService, type GridClient } from "../../src"; +import { getClient } from "../client_loader"; + +jest.setTimeout(300000); +let grid: GridClient; +let rate: number; +let decimals: number; +let currency: TFTUSDConversionService; + +beforeAll(async () => { + grid = await getClient(); + rate = await grid.tfclient.tftPrice.get(); + decimals = 5; + currency = new TFTUSDConversionService(rate, decimals); +}); + +afterAll(async () => { + await grid.disconnect(); +}); + +describe("Testing TFT module", () => { + test("tft module to be instance of TFTUSDConversionService", () => { + expect(currency).toBeInstanceOf(TFTUSDConversionService); + }); + + test("should return value with 2 decimals.", () => { + const result = currency.normalizeCurrency({ amount: 1 }); + + expect(typeof result).toBe("string"); + expect(result).toBe(new Decimal(1).toFixed(decimals)); + }); + + test("should convert to the correct value based on tftPrice.", () => { + const hourlyTFT = { amount: 1 }; + const result = currency.convertTFTtoUSD(hourlyTFT); + + expect(typeof result).toBe("string"); + expect(result).toBe(new Decimal(1 * rate).toFixed(decimals)); + }); + + test("convertTFTtoUSD function to throw if passed a negative value.", () => { + const result = () => currency.convertTFTtoUSD({ amount: -1 }); + try { + expect(result).toThrow(); + } catch (error) { + expect(result).toBeInstanceOf(ValidationError); + } + }); + + test("convertUSDtoTFT function returns a valid value.", () => { + const usd = 1; + const result = currency.convertUSDtoTFT({ amount: usd }); + + expect(typeof result).toBe("string"); + expect(result).toEqual(new Decimal(1 / rate).toFixed(decimals)); + }); + + test("convertUSDtoTFT function to throw if passed a negative value.", () => { + const result = () => currency.convertUSDtoTFT({ amount: -1 }); + + try { + expect(result).toThrow(); + } catch (error) { + expect(result).toBeInstanceOf(ValidationError); + } + }); + + test("dailyTFT function returns a valid value.", () => { + const tfts = 1; + const result = currency.dailyTFT({ amount: tfts }); + const expected_result = new Decimal(tfts * 24).toFixed(decimals); + + expect(typeof result).toBe("string"); + expect(result).toBe(expected_result); + }); + + test("dailyTFT function throws if passed anything other than a positive value.", () => { + const result = () => currency.dailyTFT({ amount: -1 }); + + try { + expect(result).toThrow(); + } catch (error) { + expect(result).toBeInstanceOf(ValidationError); + } + }); + + test("monthlyTFT function returns a valid value.", () => { + const tfts = 1; + const result = currency.monthlyTFT({ amount: tfts }); + const expected_result = new Decimal(tfts * 24 * 30).toFixed(decimals); + + expect(typeof result).toBe("string"); + expect(result).toBe(expected_result); + }); + + test("monthlyTFT function throws if passed anything other than a positive value.", () => { + const result = () => currency.monthlyTFT({ amount: -1 }); + + try { + expect(result).toThrow(); + } catch (error) { + expect(result).toBeInstanceOf(ValidationError); + } + }); + + test("yearlyTFT function returns a valid value.", () => { + const tfts = 1; + const result = currency.yearlyTFT({ amount: tfts }); + const expected_result = new Decimal(+currency.monthlyTFT({ amount: tfts }) * 12).toFixed(decimals); + + expect(typeof result).toBe("string"); + expect(result).toBe(expected_result); + }); + + test("yearlyTFT function throws if passed anything other than a positive value.", () => { + const result = () => currency.yearlyTFT({ amount: -1 }); + + try { + expect(result).toThrow(); + } catch (error) { + expect(result).toBeInstanceOf(ValidationError); + } + }); + + test("dailyUSD function returns a valid value.", () => { + const tfts = 1; + const result = currency.dailyUSD({ amount: tfts }); + const expected_result = new Decimal(tfts * 24).toFixed(decimals); + + expect(typeof result).toBe("string"); + expect(result).toBe(expected_result); + }); + + test("dailyUSD function throws if passed anything other than a positive value.", () => { + const result = () => currency.dailyUSD({ amount: -1 }); + + try { + expect(result).toThrow(); + } catch (error) { + expect(result).toBeInstanceOf(ValidationError); + } + }); + + test("monthlyUSD function returns a valid value.", () => { + const tfts = 1; + const result = currency.monthlyUSD({ amount: tfts }); + const expected_result = new Decimal(tfts * 24 * 30).toFixed(decimals); + + expect(typeof result).toBe("string"); + expect(result).toBe(expected_result); + }); + + test("monthlyUSD function throws if passed anything other than a positive value.", () => { + const result = () => currency.monthlyUSD({ amount: -1 }); + + try { + expect(result).toThrow(); + } catch (error) { + expect(result).toBeInstanceOf(ValidationError); + } + }); + + test("yearlyUSD function returns a valid value.", () => { + const tfts = 1; + const result = currency.yearlyUSD({ amount: tfts }); + const expected_result = new Decimal(+currency.monthlyUSD({ amount: tfts }) * 12).toFixed(decimals); + + expect(typeof result).toBe("string"); + expect(result).toBe(expected_result); + }); + + test("yearlyUSD function throws if passed anything other than a positive value.", () => { + const result = () => currency.yearlyUSD({ amount: -1 }); + + try { + expect(result).toThrow(); + } catch (error) { + expect(result).toBeInstanceOf(ValidationError); + } + }); +});