From 56ce444929fa66b6fa296da4ec5f16df150525ff Mon Sep 17 00:00:00 2001 From: bathord Date: Thu, 14 Mar 2024 19:01:41 +0300 Subject: [PATCH 01/19] Create getActiveDCAsFieldsByPackage() --- src/managers/dca/DCAManager.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/managers/dca/DCAManager.ts b/src/managers/dca/DCAManager.ts index 055c094..b78b528 100644 --- a/src/managers/dca/DCAManager.ts +++ b/src/managers/dca/DCAManager.ts @@ -37,7 +37,7 @@ import BigNumber from "bignumber.js"; */ export class DCAManagerSingleton { // TODO: Change DCA_PACKAGE_ADDRESS & maybe move all that params to args for singleton - public static DCA_PACKAGE_ADDRESS = "0x9a6721b2b4f60c8db8d6e57fa226135f45cdbc8e5729c2b314205bcddcc9c8a2"; + public static DCA_PACKAGE_ADDRESS = "0x89b1372fa44ac2312a3876d83612d1dc9d298af332a42a153913558332a564d0"; public static DCA_EVENT_TYPE = `${DCAManagerSingleton.DCA_PACKAGE_ADDRESS}::dca::DCACreatedEvent`; public static DCA_GAS_BUGET = 50_000_000; public static DCA_MINIMUM_GAS_FUNDS = 25_000_000; @@ -137,6 +137,12 @@ export class DCAManagerSingleton { return dcaList; } + public async getActiveDCAsFieldsByPackage() { + const dcasByPackage = await this.getDCAsByPackage(); + + return dcasByPackage.filter((dca) => dca.fields.active === true).map((dca) => dca.fields); + } + public async getDCAEventsByUser({ publicKey }: { publicKey: string }): Promise { // TODO: Move that logic into separate util (e.g. `fetchEventsByUser`) // TODO: Unify that method with `getDCAEventsByPackage` From d851ad7d6faac8093e447232ad500f4c37a76e9e Mon Sep 17 00:00:00 2001 From: bathord Date: Thu, 14 Mar 2024 19:02:03 +0300 Subject: [PATCH 02/19] Create DCATimescaleToMillisecondsMap --- src/managers/dca/types.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/managers/dca/types.ts b/src/managers/dca/types.ts index 5ccdea3..634fc05 100644 --- a/src/managers/dca/types.ts +++ b/src/managers/dca/types.ts @@ -11,6 +11,22 @@ export enum DCATimescale { Months = 5, } +export const SECOND_IN_MS = 1_000; +export const MINUTE_IN_MS = 60 * SECOND_IN_MS; +export const HOUR_IN_MS = 60 * MINUTE_IN_MS; +export const DAY_IN_MS = 24 * HOUR_IN_MS; +export const WEEK_IN_MS = 7 * DAY_IN_MS; +export const MONTH_IN_MS = 30 * DAY_IN_MS; + +export const DCATimescaleToMillisecondsMap = new Map([ + [DCATimescale.Seconds, SECOND_IN_MS], + [DCATimescale.Minutes, MINUTE_IN_MS], + [DCATimescale.Hours, HOUR_IN_MS], + [DCATimescale.Days, DAY_IN_MS], + [DCATimescale.Weeks, WEEK_IN_MS], + [DCATimescale.Months, MONTH_IN_MS], +]); + export type GetDCAInitTransactionArgs = { baseCoinType: string; quoteCoinType: string; From 60ca91e65ffbdda61df7cd3a5855c0af26ccdf56 Mon Sep 17 00:00:00 2001 From: bathord Date: Thu, 14 Mar 2024 19:02:12 +0300 Subject: [PATCH 03/19] Create getMillisecondsByDcaEveryParams() --- src/managers/dca/utils.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/managers/dca/utils.ts b/src/managers/dca/utils.ts index ecd0e4e..757efa7 100644 --- a/src/managers/dca/utils.ts +++ b/src/managers/dca/utils.ts @@ -1,8 +1,9 @@ /* eslint-disable require-jsdoc */ -import { MoveStruct, SuiParsedData, SuiObjectResponse } from "@mysten/sui.js/client"; -import { DCAContent, DCAContentFields, DCAResponse } from "./types"; +import { MoveStruct, SuiObjectResponse, SuiParsedData } from "@mysten/sui.js/client"; +import BigNumber from "bignumber.js"; import { TOKEN_ADDRESS_BASE_REGEX } from "../../providers/common"; +import { DCAContent, DCAContentFields, DCAResponse, DCATimescaleToMillisecondsMap } from "./types"; export function isValidDCAFields(fields: MoveStruct): fields is DCAContentFields { const expectedKeys: (keyof DCAContentFields)[] = [ @@ -103,3 +104,13 @@ export function hasMinMaxPriceParams(params: { }): params is { minPrice: string; maxPrice: string } { return params.minPrice !== undefined && params.maxPrice !== undefined; } + +export function getMillisecondsByDcaEveryParams(every: string, timeScale: number): number { + const milliseconds = DCATimescaleToMillisecondsMap.get(timeScale); + + if (milliseconds === undefined) { + throw new Error(); + } + + return new BigNumber(every).multipliedBy(milliseconds).toNumber(); +} From 178ac28dd9cc4f4c7742999c416f36d98c6f8e3d Mon Sep 17 00:00:00 2001 From: bathord Date: Fri, 15 Mar 2024 04:38:43 +0300 Subject: [PATCH 04/19] Create DCAObjectFields type --- src/managers/dca/types.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/managers/dca/types.ts b/src/managers/dca/types.ts index 634fc05..dcd148c 100644 --- a/src/managers/dca/types.ts +++ b/src/managers/dca/types.ts @@ -178,11 +178,13 @@ export type DCAContentFields = { }; }; +export type DCAObjectFields = DCAContentFields & { + base_coin_type: string; + quote_coin_type: string; +}; + export interface DCAObject extends DCAContent { - fields: DCAContentFields & { - base_coin_type: string; - quote_coin_type: string; - }; + fields: DCAObjectFields; } export interface DCAResponseData extends SuiObjectData { From ca996816d6dd441f4629085341c131049a4b4118 Mon Sep 17 00:00:00 2001 From: bathord Date: Fri, 15 Mar 2024 04:39:19 +0300 Subject: [PATCH 05/19] Add DCAObjectFields to StorageValue, upd StorageProperty & GetCacheParams --- src/storages/types.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/storages/types.ts b/src/storages/types.ts index c9b47e0..25cc07f 100644 --- a/src/storages/types.ts +++ b/src/storages/types.ts @@ -1,11 +1,12 @@ import { createClient } from "redis"; +import { DCAObjectFields } from "../managers/dca/types"; import { CommonCoinData } from "../managers/types"; +import { CetusPathForStorage } from "../providers/cetus/types"; import { ShortCoinMetadata } from "../providers/flowx/types"; import { ShortPoolData } from "../providers/turbos/types"; import { CommonPoolData } from "../providers/types"; import { InMemoryStorageSingleton } from "./InMemoryStorage"; import { RedisStorageSingleton } from "./RedisStorage"; -import { CetusPathForStorage } from "../providers/cetus/types"; export type Storage = InMemoryStorageSingleton | RedisStorageSingleton; @@ -15,7 +16,7 @@ export interface IStorage { } export type GetCacheParams = { - provider: string; + provider?: string; property: StorageProperty; }; @@ -29,6 +30,7 @@ export enum StorageProperty { Pools = "pools", CoinsMetadata = "coinsMetadata", CetusPaths = "cetusPaths", + DCAs = "dcas", } export type StorageValue = @@ -37,6 +39,7 @@ export type StorageValue = | { value: ShortCoinMetadata[]; timestamp: string } | { value: ShortPoolData[]; timestamp: string } | { value: CetusPathForStorage[]; timestamp: string } + | { value: DCAObjectFields[]; timestamp: string } | null; export type RedisStorageClient = ReturnType; From 1255968a154685ff9e102343804e5fca5f075e3b Mon Sep 17 00:00:00 2001 From: bathord Date: Fri, 15 Mar 2024 04:39:41 +0300 Subject: [PATCH 06/19] Create & use type guard for DCAObjectFields --- src/storages/utils/typeguards.ts | 57 +++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/storages/utils/typeguards.ts b/src/storages/utils/typeguards.ts index 39e1ac2..30b5ab9 100644 --- a/src/storages/utils/typeguards.ts +++ b/src/storages/utils/typeguards.ts @@ -1,5 +1,6 @@ /* eslint-disable require-jsdoc */ +import { DCAObjectFields } from "../../managers/dca/types"; import { CommonCoinData } from "../../managers/types"; import { CetusPathForStorage } from "../../providers/cetus/types"; import { ShortCoinMetadata } from "../../providers/flowx/types"; @@ -16,7 +17,9 @@ export function isStorageValue(data: unknown): data is StorageValue { (isCommonCoinDataArray(data.value) || isCommonPoolDataArray(data.value) || isShortCoinMetadataArray(data.value) || - isShortPoolDataArray(data.value)) + isShortPoolDataArray(data.value) || + isCetusPathForStorageArray(data.value) || + isDCAObjectFieldsArray(data.value)) ); } @@ -102,3 +105,55 @@ export function isCetusPathForStorageArray(data: unknown): data is CetusPathForS ), ); } + +export function isDCAObjectFieldsArray(data: unknown): data is DCAObjectFields[] { + if (!Array.isArray(data)) return false; + + return data.every( + (item) => + typeof item === "object" && + item !== null && + "active" in item && + typeof (item as DCAObjectFields).active === "boolean" && + "input_balance" in item && + typeof (item as DCAObjectFields).input_balance === "string" && + "delegatee" in item && + typeof (item as DCAObjectFields).delegatee === "string" && + "every" in item && + typeof (item as DCAObjectFields).every === "string" && + "gas_budget" in item && + typeof (item as DCAObjectFields).gas_budget === "string" && + "id" in item && + typeof (item as DCAObjectFields).id === "object" && + "id" in (item as DCAObjectFields).id && + typeof (item as DCAObjectFields).id.id === "string" && + "last_time_ms" in item && + typeof (item as DCAObjectFields).last_time_ms === "string" && + "owner" in item && + typeof (item as DCAObjectFields).owner === "string" && + "remaining_orders" in item && + typeof (item as DCAObjectFields).remaining_orders === "string" && + "split_allocation" in item && + typeof (item as DCAObjectFields).split_allocation === "string" && + "start_time_ms" in item && + typeof (item as DCAObjectFields).start_time_ms === "string" && + "time_scale" in item && + typeof (item as DCAObjectFields).time_scale === "number" && + "trade_params" in item && + typeof (item as DCAObjectFields).trade_params === "object" && + "type" in (item as DCAObjectFields).trade_params && + typeof (item as DCAObjectFields).trade_params.type === "string" && + "fields" in (item as DCAObjectFields).trade_params && + typeof (item as DCAObjectFields).trade_params.fields === "object" && + "max_price" in (item as DCAObjectFields).trade_params.fields && + (typeof (item as DCAObjectFields).trade_params.fields.max_price === "string" || + (item as DCAObjectFields).trade_params.fields.max_price === null) && + "min_price" in (item as DCAObjectFields).trade_params.fields && + (typeof (item as DCAObjectFields).trade_params.fields.min_price === "string" || + (item as DCAObjectFields).trade_params.fields.min_price === null) && + "base_coin_type" in item && + typeof (item as DCAObjectFields).base_coin_type === "string" && + "quote_coin_type" in item && + typeof (item as DCAObjectFields).quote_coin_type === "string", + ); +} From 616989a10394f45aadda6e0e92aa1555e45c7bea Mon Sep 17 00:00:00 2001 From: bathord Date: Fri, 15 Mar 2024 04:40:38 +0300 Subject: [PATCH 07/19] Add return type --- src/managers/dca/DCAManager.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/managers/dca/DCAManager.ts b/src/managers/dca/DCAManager.ts index b78b528..94ce268 100644 --- a/src/managers/dca/DCAManager.ts +++ b/src/managers/dca/DCAManager.ts @@ -2,6 +2,7 @@ import { EventId, PaginatedEvents, SuiClient, SuiEvent } from "@mysten/sui.js/client"; import { TransactionBlock } from "@mysten/sui.js/transactions"; import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui.js/utils"; +import BigNumber from "bignumber.js"; import { MAX_BATCH_EVENTS_PER_QUERY_EVENTS_REQUEST } from "../../providers/common"; import { getAllObjects } from "../../providers/utils/getAllObjects"; import { GetTransactionType } from "../../transactions/types"; @@ -12,6 +13,7 @@ import { CreateDCAInitTransactionArgs, DCACreateEventParsedJson, DCAObject, + DCAObjectFields, GetDCAAddGasBudgetTransactionArgs, GetDCADepositBaseTransactionArgs, GetDCAIncreaseOrdersRemainingTransactionArgs, @@ -26,7 +28,6 @@ import { SuiEventDCACreate, } from "./types"; import { filterValidDCAObjects, getBaseQuoteCoinTypesFromDCAType, hasMinMaxPriceParams } from "./utils"; -import BigNumber from "bignumber.js"; /** * @class DCAManagerSingleton @@ -137,7 +138,7 @@ export class DCAManagerSingleton { return dcaList; } - public async getActiveDCAsFieldsByPackage() { + public async getActiveDCAsFieldsByPackage(): Promise { const dcasByPackage = await this.getDCAsByPackage(); return dcasByPackage.filter((dca) => dca.fields.active === true).map((dca) => dca.fields); From a6458822adf7383eab72e538a9e05da2e97349c9 Mon Sep 17 00:00:00 2001 From: bathord Date: Fri, 15 Mar 2024 04:41:04 +0300 Subject: [PATCH 08/19] Upd setCache & getCache to work with properties without provider --- src/storages/RedisStorage.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/storages/RedisStorage.ts b/src/storages/RedisStorage.ts index b9468ad..be6c74d 100644 --- a/src/storages/RedisStorage.ts +++ b/src/storages/RedisStorage.ts @@ -50,7 +50,14 @@ export class RedisStorageSingleton implements IStorage { */ public async setCache(params: SetCacheParams): Promise { const { provider, property, value } = params; - const key = `${provider}.${property}.${RedisStorageSingleton.version}`; + let key; + + if (provider === undefined) { + key = `${property}.${RedisStorageSingleton.version}`; + } else { + key = `${provider}.${property}.${RedisStorageSingleton.version}`; + } + const stringifiedValue: string = JSON.stringify(value); const setResult = await this.client.set(key, stringifiedValue); @@ -69,7 +76,14 @@ export class RedisStorageSingleton implements IStorage { */ public async getCache(params: GetCacheParams): Promise { const { provider, property } = params; - const key = `${provider}.${property}.${RedisStorageSingleton.version}`; + let key; + + if (provider === undefined) { + key = `${property}.${RedisStorageSingleton.version}`; + } else { + key = `${provider}.${property}.${RedisStorageSingleton.version}`; + } + const value = await this.client.get(key); if (value === null) { From ebd35332ffca4f269559fa7eb1cdef7dd69227a4 Mon Sep 17 00:00:00 2001 From: bathord Date: Sun, 17 Mar 2024 16:37:49 +0300 Subject: [PATCH 09/19] Add isDcaTrading as possible StorageProperty --- src/storages/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/storages/types.ts b/src/storages/types.ts index 25cc07f..1bf7c4b 100644 --- a/src/storages/types.ts +++ b/src/storages/types.ts @@ -31,6 +31,7 @@ export enum StorageProperty { CoinsMetadata = "coinsMetadata", CetusPaths = "cetusPaths", DCAs = "dcas", + IsDCATrading = "isDcaTrading", } export type StorageValue = @@ -40,6 +41,7 @@ export type StorageValue = | { value: ShortPoolData[]; timestamp: string } | { value: CetusPathForStorage[]; timestamp: string } | { value: DCAObjectFields[]; timestamp: string } + | { value: boolean; timestamp: string } | null; export type RedisStorageClient = ReturnType; From 9554d0aca985e4a574c49b9f3d195c6c8d69076e Mon Sep 17 00:00:00 2001 From: bathord Date: Sun, 17 Mar 2024 16:38:45 +0300 Subject: [PATCH 10/19] Create type guard for isDcaTrading --- src/storages/utils/typeguards.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/storages/utils/typeguards.ts b/src/storages/utils/typeguards.ts index 30b5ab9..1443f01 100644 --- a/src/storages/utils/typeguards.ts +++ b/src/storages/utils/typeguards.ts @@ -19,7 +19,8 @@ export function isStorageValue(data: unknown): data is StorageValue { isShortCoinMetadataArray(data.value) || isShortPoolDataArray(data.value) || isCetusPathForStorageArray(data.value) || - isDCAObjectFieldsArray(data.value)) + isDCAObjectFieldsArray(data.value) || + isDcaIsTradingField(data.value)) ); } @@ -157,3 +158,7 @@ export function isDCAObjectFieldsArray(data: unknown): data is DCAObjectFields[] typeof (item as DCAObjectFields).quote_coin_type === "string", ); } + +export function isDcaIsTradingField(data: unknown): data is boolean { + return typeof data === "boolean"; +} From 5545a59015d11e277727a0bd005b7c26f463a6a4 Mon Sep 17 00:00:00 2001 From: bathord Date: Sun, 17 Mar 2024 16:39:35 +0300 Subject: [PATCH 11/19] Fix getFromStorage methods to print entire value data --- src/storages/utils/getCetusPathsCache.ts | 4 ++-- src/storages/utils/getCoinsCache.ts | 4 ++-- src/storages/utils/getCoinsMetadataCache.ts | 6 +++--- src/storages/utils/getPathsCache.ts | 4 ++-- src/storages/utils/getPoolsCache.ts | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/storages/utils/getCetusPathsCache.ts b/src/storages/utils/getCetusPathsCache.ts index fdf0e9e..e6cc068 100644 --- a/src/storages/utils/getCetusPathsCache.ts +++ b/src/storages/utils/getCetusPathsCache.ts @@ -41,10 +41,10 @@ export const getCetusPathsCache = async ({ } else if (paths === null) { console.warn(`[getCetusPathsCache] ${provider} Received empty paths from strorage, paths === null `); } else { - const stringifiedPath: string = JSON.stringify(paths.value[0]); + const stringifiedPaths: string = JSON.stringify(paths.value); throw new Error( `[${provider}] prefillCaches: paths from storage are not (PathLink[] or null). ` + - `Example of path: ${stringifiedPath}`, + `Paths from storage: ${stringifiedPaths}`, ); } diff --git a/src/storages/utils/getCoinsCache.ts b/src/storages/utils/getCoinsCache.ts index 452abda..cb71f33 100644 --- a/src/storages/utils/getCoinsCache.ts +++ b/src/storages/utils/getCoinsCache.ts @@ -36,10 +36,10 @@ export const getCoinsCache = async ({ } else if (coins === null) { console.warn(`[getCoinsCache] ${provider} Received empty coins from strorage, coins === null `); } else { - const stringifiedCoin: string = JSON.stringify(coins.value[0]); + const stringifiedCoins: string = JSON.stringify(coins.value); throw new Error( `[${provider}] prefillCaches: coins from storage are not (CommonCoinData[] or null). ` + - `Example of coin: ${stringifiedCoin}`, + `Coins from storage: ${stringifiedCoins}`, ); } diff --git a/src/storages/utils/getCoinsMetadataCache.ts b/src/storages/utils/getCoinsMetadataCache.ts index 8dab582..1190004 100644 --- a/src/storages/utils/getCoinsMetadataCache.ts +++ b/src/storages/utils/getCoinsMetadataCache.ts @@ -1,5 +1,5 @@ import { ExtractedCoinMetadataType } from "../../providers/flowx/types"; -import { StorageValue, StorageProperty, Storage } from "../types"; +import { Storage, StorageProperty, StorageValue } from "../types"; import { isShortCoinMetadataArray } from "./typeguards"; /** @@ -36,10 +36,10 @@ export async function getCoinsMetadataCache({ coinsMetadataCache === null `, ); } else { - const stringifiedCoinMetadata: string = JSON.stringify(coinsMetadata.value[0]); + const stringifiedCoinMetadata: string = JSON.stringify(coinsMetadata.value); throw new Error( `[${provider}] getCoinsMetadataCache: coins metadata from storage is not ` + - `(ExtractedCoinMetadataType[] or null). Example of coin metadata: ${stringifiedCoinMetadata}`, + `(ExtractedCoinMetadataType[] or null). Coin metadata from storage: ${stringifiedCoinMetadata}`, ); } diff --git a/src/storages/utils/getPathsCache.ts b/src/storages/utils/getPathsCache.ts index 4c4b648..0a555b4 100644 --- a/src/storages/utils/getPathsCache.ts +++ b/src/storages/utils/getPathsCache.ts @@ -37,10 +37,10 @@ export const getPathsCache = async ({ } else if (paths === null) { console.warn(`[getPathsCache] ${provider} Received empty paths from strorage, paths === null `); } else { - const stringifiedPath: string = JSON.stringify(paths.value[0]); + const stringifiedPaths: string = JSON.stringify(paths.value); throw new Error( `[${provider}] prefillCaches: paths from storage are not (CommonPoolData[] or null). ` + - `Example of path: ${stringifiedPath}`, + `Paths from storage: ${stringifiedPaths}`, ); } diff --git a/src/storages/utils/getPoolsCache.ts b/src/storages/utils/getPoolsCache.ts index 772146a..13fc509 100644 --- a/src/storages/utils/getPoolsCache.ts +++ b/src/storages/utils/getPoolsCache.ts @@ -1,6 +1,6 @@ +import { ShortPoolData } from "../../providers/turbos/types"; import { Storage, StorageProperty, StorageValue } from "../types"; import { isShortPoolDataArray } from "./typeguards"; -import { ShortPoolData } from "../../providers/turbos/types"; /** * Returns pools cache from storage. If cache is not up to date, empty array is returned. @@ -33,10 +33,10 @@ export const getPoolsCache = async ({ } else if (pools === null) { console.warn(`[getPoolsCache] ${provider} Received empty pools from strorage, pools === null `); } else { - const stringifiedPool: string = JSON.stringify(pools.value[0]); + const stringifiedPools: string = JSON.stringify(pools.value); throw new Error( `[${provider}] getPoolsCache: pools from storage are not ` + - `(ShortPoolData[] or null). Example of pool: ${stringifiedPool}`, + `(ShortPoolData[] or null). Pools from storage: ${stringifiedPools}`, ); } From 80b8db2d41d9b5d55d309e62bbb5d1948bdfb991 Mon Sep 17 00:00:00 2001 From: bathord Date: Sun, 17 Mar 2024 16:40:19 +0300 Subject: [PATCH 12/19] Create methods to get & store isDcaTrading storage field --- src/index.ts | 2 ++ src/storages/utils/getIsDcaTradingCache.ts | 24 ++++++++++++++++++++ src/storages/utils/storeIsDcaTradingCache.ts | 24 ++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 src/storages/utils/getIsDcaTradingCache.ts create mode 100644 src/storages/utils/storeIsDcaTradingCache.ts diff --git a/src/index.ts b/src/index.ts index 2efdfe6..92620d0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,6 +41,8 @@ export * from "./storages/RedisStorage"; export * from "./storages/InMemoryStorage"; export * from "./storages/types"; export * from "./storages/utils/typeguards"; +export * from "./storages/utils/getIsDcaTradingCache"; +export * from "./storages/utils/storeIsDcaTradingCache"; // Misc export { SUI_DECIMALS, isValidSuiAddress } from "@mysten/sui.js/utils"; diff --git a/src/storages/utils/getIsDcaTradingCache.ts b/src/storages/utils/getIsDcaTradingCache.ts new file mode 100644 index 0000000..c90517e --- /dev/null +++ b/src/storages/utils/getIsDcaTradingCache.ts @@ -0,0 +1,24 @@ +/* eslint-disable require-jsdoc */ + +import { Storage, StorageProperty, StorageValue } from "../types"; +import { isDcaIsTradingField } from "./typeguards"; + +export async function getIsDcaTradingCache({ storage }: { storage: Storage }): Promise { + let isDcaTrading = false; + + const isDcaTradingCache: StorageValue = await storage.getCache({ property: StorageProperty.IsDCATrading }); + + if (isDcaIsTradingField(isDcaTradingCache?.value)) { + isDcaTrading = isDcaTradingCache.value; + } else if (isDcaTradingCache === null) { + console.warn("[getIsDcaTradingCache] Received empty isDcaTrading from strorage, isDcaTrading === null"); + } else { + const stringifiedIsDcaTrading: string = JSON.stringify(isDcaTradingCache.value); + throw new Error( + "[getIsDcaTradingCache] isDcaTrading from storage is not boolean or null. " + + `Value from storage: ${stringifiedIsDcaTrading}`, + ); + } + + return isDcaTrading; +} diff --git a/src/storages/utils/storeIsDcaTradingCache.ts b/src/storages/utils/storeIsDcaTradingCache.ts new file mode 100644 index 0000000..317d597 --- /dev/null +++ b/src/storages/utils/storeIsDcaTradingCache.ts @@ -0,0 +1,24 @@ +/* eslint-disable require-jsdoc */ + +import { Storage, StorageProperty } from "../types"; + +export async function storeIsDcaTradingCache({ + storage, + isDcaTrading, +}: { + storage: Storage; + isDcaTrading: boolean; +}): Promise { + try { + const timestamp = Date.now().toString(); + + await storage.setCache({ + property: StorageProperty.IsDCATrading, + value: { value: isDcaTrading, timestamp }, + }); + } catch (error) { + console.error("[storeIsDcaTrading] Error occured while storing:", error); + + throw error; + } +} From 2e7649c16aa1725b616f71e23a76cba0bc3bdc6d Mon Sep 17 00:00:00 2001 From: bathord Date: Sun, 17 Mar 2024 19:29:55 +0300 Subject: [PATCH 13/19] Export Ed25519Keypair from mysten --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 92620d0..2a02a62 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,6 +47,7 @@ export * from "./storages/utils/storeIsDcaTradingCache"; // Misc export { SUI_DECIMALS, isValidSuiAddress } from "@mysten/sui.js/utils"; export { TransactionBlock, isTransactionBlock } from "@mysten/sui.js/transactions"; +export { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519"; // Launchpad export * from "./launchpad/surfdog/surfdog"; From 2c2b9252451950b8894c589851b952dff63fefd4 Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 18 Mar 2024 20:13:36 +0300 Subject: [PATCH 14/19] Export FeeManager from index --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 2a02a62..c5477d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ export * from "./managers/types"; export * from "./managers/dca/DCAManager"; export * from "./managers/dca/types"; export * from "./managers/dca/utils"; +export * from "./managers/FeeManager"; // Providers (common & utils) export * from "./providers/common"; From 5659a87e20546c30840a6cc6cd8ec00489f9e06b Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 18 Mar 2024 20:52:00 +0300 Subject: [PATCH 15/19] Add comments & TODO in RedisStorage --- src/storages/RedisStorage.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/storages/RedisStorage.ts b/src/storages/RedisStorage.ts index be6c74d..2056ec4 100644 --- a/src/storages/RedisStorage.ts +++ b/src/storages/RedisStorage.ts @@ -42,6 +42,7 @@ export class RedisStorageSingleton implements IStorage { return RedisStorageSingleton._instance; } + // TODO: Refactor this method to set specified types of value to avoid `provider` accidental property non-indication /** * Sets cache data in Redis. * @param {SetCacheParams} params - Parameters containing provider, property, and value. @@ -52,9 +53,12 @@ export class RedisStorageSingleton implements IStorage { const { provider, property, value } = params; let key; + // When `provider` is not defined (e.g. for StorageProperty.DCAs), don't use it in `key` + // TODO: Make this clearer and more graceful if (provider === undefined) { key = `${property}.${RedisStorageSingleton.version}`; } else { + // When `provider` is defined (e.g. for StorageProperty.Coins), use it in `key` key = `${provider}.${property}.${RedisStorageSingleton.version}`; } @@ -68,6 +72,7 @@ export class RedisStorageSingleton implements IStorage { } } + // TODO: Refactor this method to get specified types of value to avoid `provider` accidental property non-indication /** * Retrieves cache data from Redis. * @param {GetCacheParams} params - Parameters containing provider and property. @@ -78,9 +83,12 @@ export class RedisStorageSingleton implements IStorage { const { provider, property } = params; let key; + // When `provider` is not defined (e.g. for StorageProperty.DCAs), don't use it in `key` + // TODO: Make this clearer and more graceful if (provider === undefined) { key = `${property}.${RedisStorageSingleton.version}`; } else { + // When `provider` is defined (e.g. for StorageProperty.Coins), use it in `key` key = `${provider}.${property}.${RedisStorageSingleton.version}`; } From 05fc7458eb14312bae502bfe23ffd59b34a4a42e Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 18 Mar 2024 21:09:07 +0300 Subject: [PATCH 16/19] Upd isValidDCAFields() to receive unknown --- src/managers/dca/utils.ts | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/managers/dca/utils.ts b/src/managers/dca/utils.ts index f2ff62b..a6d5025 100644 --- a/src/managers/dca/utils.ts +++ b/src/managers/dca/utils.ts @@ -1,6 +1,6 @@ /* eslint-disable require-jsdoc */ -import { MoveStruct, SuiObjectResponse, SuiParsedData } from "@mysten/sui.js/client"; +import { SuiObjectResponse, SuiParsedData } from "@mysten/sui.js/client"; import BigNumber from "bignumber.js"; import { TOKEN_ADDRESS_BASE_REGEX } from "../../providers/common"; import { DCAContent, DCAContentFields, DCAResponse, DCATimescaleToMillisecondsMap } from "./types"; @@ -14,39 +14,35 @@ export function feeAmount(amount: number): number { return scaledFee / 1_000_000; } -export function isValidDCAFields(fields: MoveStruct): fields is DCAContentFields { - const expectedKeys: (keyof DCAContentFields)[] = [ - "active", - "input_balance", - "delegatee", - "every", - "gas_budget", - "id", - "last_time_ms", - "owner", - "remaining_orders", - "split_allocation", - "start_time_ms", - "time_scale", - "trade_params", - ]; - +export function isValidDCAFields(fields: unknown): fields is DCAContentFields { return ( - expectedKeys.every((key) => key in fields) && - // the "active" in fields is the ts-check bypass for MoveStruct type + typeof fields === "object" && + fields !== null && "active" in fields && typeof fields.active === "boolean" && + "input_balance" in fields && typeof fields.input_balance === "string" && + "delegatee" in fields && typeof fields.delegatee === "string" && + "every" in fields && typeof fields.every === "string" && + "gas_budget" in fields && typeof fields.gas_budget === "string" && - typeof fields.id === "object" && // Assuming id is always an object + "id" in fields && + typeof fields.id === "object" && + "last_time_ms" in fields && typeof fields.last_time_ms === "string" && + "owner" in fields && typeof fields.owner === "string" && + "remaining_orders" in fields && typeof fields.remaining_orders === "string" && + "split_allocation" in fields && typeof fields.split_allocation === "string" && + "start_time_ms" in fields && typeof fields.start_time_ms === "string" && + "time_scale" in fields && typeof fields.time_scale === "number" && + "trade_params" in fields && typeof fields.trade_params === "object" && fields.trade_params !== null && "type" in fields.trade_params && From d35ec69fe1fd33e96ef26c7893d22b8c34e31d46 Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 18 Mar 2024 21:09:35 +0300 Subject: [PATCH 17/19] Remove deplicating type guard & use isValidDCAFields() --- src/storages/utils/typeguards.ts | 56 ++------------------------------ 1 file changed, 2 insertions(+), 54 deletions(-) diff --git a/src/storages/utils/typeguards.ts b/src/storages/utils/typeguards.ts index 1443f01..772fa22 100644 --- a/src/storages/utils/typeguards.ts +++ b/src/storages/utils/typeguards.ts @@ -1,6 +1,6 @@ /* eslint-disable require-jsdoc */ -import { DCAObjectFields } from "../../managers/dca/types"; +import { isValidDCAFields } from "../.."; import { CommonCoinData } from "../../managers/types"; import { CetusPathForStorage } from "../../providers/cetus/types"; import { ShortCoinMetadata } from "../../providers/flowx/types"; @@ -19,7 +19,7 @@ export function isStorageValue(data: unknown): data is StorageValue { isShortCoinMetadataArray(data.value) || isShortPoolDataArray(data.value) || isCetusPathForStorageArray(data.value) || - isDCAObjectFieldsArray(data.value) || + isValidDCAFields(data.value) || isDcaIsTradingField(data.value)) ); } @@ -107,58 +107,6 @@ export function isCetusPathForStorageArray(data: unknown): data is CetusPathForS ); } -export function isDCAObjectFieldsArray(data: unknown): data is DCAObjectFields[] { - if (!Array.isArray(data)) return false; - - return data.every( - (item) => - typeof item === "object" && - item !== null && - "active" in item && - typeof (item as DCAObjectFields).active === "boolean" && - "input_balance" in item && - typeof (item as DCAObjectFields).input_balance === "string" && - "delegatee" in item && - typeof (item as DCAObjectFields).delegatee === "string" && - "every" in item && - typeof (item as DCAObjectFields).every === "string" && - "gas_budget" in item && - typeof (item as DCAObjectFields).gas_budget === "string" && - "id" in item && - typeof (item as DCAObjectFields).id === "object" && - "id" in (item as DCAObjectFields).id && - typeof (item as DCAObjectFields).id.id === "string" && - "last_time_ms" in item && - typeof (item as DCAObjectFields).last_time_ms === "string" && - "owner" in item && - typeof (item as DCAObjectFields).owner === "string" && - "remaining_orders" in item && - typeof (item as DCAObjectFields).remaining_orders === "string" && - "split_allocation" in item && - typeof (item as DCAObjectFields).split_allocation === "string" && - "start_time_ms" in item && - typeof (item as DCAObjectFields).start_time_ms === "string" && - "time_scale" in item && - typeof (item as DCAObjectFields).time_scale === "number" && - "trade_params" in item && - typeof (item as DCAObjectFields).trade_params === "object" && - "type" in (item as DCAObjectFields).trade_params && - typeof (item as DCAObjectFields).trade_params.type === "string" && - "fields" in (item as DCAObjectFields).trade_params && - typeof (item as DCAObjectFields).trade_params.fields === "object" && - "max_price" in (item as DCAObjectFields).trade_params.fields && - (typeof (item as DCAObjectFields).trade_params.fields.max_price === "string" || - (item as DCAObjectFields).trade_params.fields.max_price === null) && - "min_price" in (item as DCAObjectFields).trade_params.fields && - (typeof (item as DCAObjectFields).trade_params.fields.min_price === "string" || - (item as DCAObjectFields).trade_params.fields.min_price === null) && - "base_coin_type" in item && - typeof (item as DCAObjectFields).base_coin_type === "string" && - "quote_coin_type" in item && - typeof (item as DCAObjectFields).quote_coin_type === "string", - ); -} - export function isDcaIsTradingField(data: unknown): data is boolean { return typeof data === "boolean"; } From 80cf6172fc86d1b4cdca84df0f6bfeeea44c9f91 Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 18 Mar 2024 21:12:41 +0300 Subject: [PATCH 18/19] Create isValidDCAFieldsArray type guard --- src/managers/dca/utils.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/managers/dca/utils.ts b/src/managers/dca/utils.ts index a6d5025..9c36851 100644 --- a/src/managers/dca/utils.ts +++ b/src/managers/dca/utils.ts @@ -58,6 +58,12 @@ export function isValidDCAFields(fields: unknown): fields is DCAContentFields { ); } +export function isValidDCAFieldsArray(data: unknown): data is DCAContentFields[] { + if (!Array.isArray(data)) return false; + + return data.every((item) => isValidDCAFields(item)); +} + export function isDCAContent(data: SuiParsedData | null): data is DCAContent { return ( !!data && From e09714e5962cadbffe72307feb5aac8bd0465fdb Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 18 Mar 2024 21:14:39 +0300 Subject: [PATCH 19/19] Use isValidDCAFieldsArray in isStorageValue type guard --- src/storages/utils/typeguards.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/storages/utils/typeguards.ts b/src/storages/utils/typeguards.ts index 772fa22..9ecc2dc 100644 --- a/src/storages/utils/typeguards.ts +++ b/src/storages/utils/typeguards.ts @@ -1,6 +1,6 @@ /* eslint-disable require-jsdoc */ -import { isValidDCAFields } from "../.."; +import { isValidDCAFieldsArray } from "../.."; import { CommonCoinData } from "../../managers/types"; import { CetusPathForStorage } from "../../providers/cetus/types"; import { ShortCoinMetadata } from "../../providers/flowx/types"; @@ -19,7 +19,7 @@ export function isStorageValue(data: unknown): data is StorageValue { isShortCoinMetadataArray(data.value) || isShortPoolDataArray(data.value) || isCetusPathForStorageArray(data.value) || - isValidDCAFields(data.value) || + isValidDCAFieldsArray(data.value) || isDcaIsTradingField(data.value)) ); }