Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Feature/dca cron job methods #31

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
56ce444
Create getActiveDCAsFieldsByPackage()
bathord Mar 14, 2024
d851ad7
Create DCATimescaleToMillisecondsMap
bathord Mar 14, 2024
60ca91e
Create getMillisecondsByDcaEveryParams()
bathord Mar 14, 2024
178ac28
Create DCAObjectFields type
bathord Mar 15, 2024
ca99681
Add DCAObjectFields to StorageValue, upd StorageProperty & GetCachePa…
bathord Mar 15, 2024
1255968
Create & use type guard for DCAObjectFields
bathord Mar 15, 2024
616989a
Add return type
bathord Mar 15, 2024
a645882
Upd setCache & getCache to work with properties without provider
bathord Mar 15, 2024
ebd3533
Add isDcaTrading as possible StorageProperty
bathord Mar 17, 2024
9554d0a
Create type guard for isDcaTrading
bathord Mar 17, 2024
5545a59
Fix getFromStorage methods to print entire value data
bathord Mar 17, 2024
80b8db2
Create methods to get & store isDcaTrading storage field
bathord Mar 17, 2024
b8d0ace
Merge branch 'example/trade-dcas' into feature/dca-cron-job-methods
bathord Mar 17, 2024
f368a3f
Merge branch 'example/trade-dcas' into feature/dca-cron-job-methods
bathord Mar 17, 2024
2e7649c
Export Ed25519Keypair from mysten
bathord Mar 17, 2024
4b6cb5d
Merge branch 'example/trade-dcas' into feature/dca-cron-job-methods
bathord Mar 17, 2024
2b2bbc0
Merge branch 'master' into feature/dca-cron-job-methods
bathord Mar 17, 2024
325f568
Merge branch 'example/trade-dcas' into feature/dca-cron-job-methods
bathord Mar 18, 2024
2c9710a
Merge branch 'master' into feature/dca-cron-job-methods
bathord Mar 18, 2024
8d82e47
Merge branch 'example/trade-dcas' into feature/dca-cron-job-methods
bathord Mar 18, 2024
2c2b925
Export FeeManager from index
bathord Mar 18, 2024
957dd58
Merge branch 'master' into feature/dca-cron-job-methods
bathord Mar 18, 2024
5659a87
Add comments & TODO in RedisStorage
bathord Mar 18, 2024
05fc745
Upd isValidDCAFields() to receive unknown
bathord Mar 18, 2024
d35ec69
Remove deplicating type guard & use isValidDCAFields()
bathord Mar 18, 2024
80cf617
Create isValidDCAFieldsArray type guard
bathord Mar 18, 2024
e09714e
Use isValidDCAFieldsArray in isStorageValue type guard
bathord Mar 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -41,10 +42,13 @@ 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";
export { TransactionBlock, isTransactionBlock } from "@mysten/sui.js/transactions";
export { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";

// Launchpad
export * from "./launchpad/surfdog/surfdog";
Expand Down
9 changes: 8 additions & 1 deletion src/managers/dca/DCAManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -12,6 +13,7 @@ import {
CreateDCAInitTransactionArgs,
DCACreateEventParsedJson,
DCAObject,
DCAObjectFields,
GetDCAAddGasBudgetTransactionArgs,
GetDCADepositBaseTransactionArgs,
GetDCAIncreaseOrdersRemainingTransactionArgs,
Expand All @@ -26,7 +28,6 @@ import {
SuiEventDCACreate,
} from "./types";
import { filterValidDCAObjects, getBaseQuoteCoinTypesFromDCAType, hasMinMaxPriceParams } from "./utils";
import BigNumber from "bignumber.js";
import { DCA_CONFIG } from "./config";

/**
Expand Down Expand Up @@ -140,6 +141,12 @@ export class DCAManagerSingleton {
return dcaList;
}

public async getActiveDCAsFieldsByPackage(): Promise<DCAObjectFields[]> {
const dcasByPackage = await this.getDCAsByPackage();

return dcasByPackage.filter((dca) => dca.fields.active === true).map((dca) => dca.fields);
}

public async getDCAEventsByUser({ publicKey }: { publicKey: string }): Promise<SuiEventDCACreate[]> {
// TODO: Move that logic into separate util (e.g. `fetchEventsByUser`)
// TODO: Unify that method with `getDCAEventsByPackage`
Expand Down
26 changes: 22 additions & 4 deletions src/managers/dca/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -162,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 {
Expand Down
57 changes: 35 additions & 22 deletions src/managers/dca/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable require-jsdoc */

import { MoveStruct, SuiParsedData, SuiObjectResponse } from "@mysten/sui.js/client";
import { DCAContent, DCAContentFields, DCAResponse } from "./types";
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";
import { Argument } from "./txBlock";
import { DCA_CONFIG } from "./config";

Expand All @@ -13,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 &&
Expand All @@ -61,6 +58,12 @@ export function isValidDCAFields(fields: MoveStruct): 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 &&
Expand Down Expand Up @@ -113,6 +116,16 @@ export function hasMinMaxPriceParams(params: {
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();
}

export const fromArgument = (arg: Argument, idx: number) => {
// console.log(`Processing argument at index ${idx}:`, arg);

Expand Down
26 changes: 24 additions & 2 deletions src/storages/RedisStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -50,7 +51,17 @@ export class RedisStorageSingleton implements IStorage {
*/
public async setCache(params: SetCacheParams): Promise<void> {
const { provider, property, value } = params;
const key = `${provider}.${property}.${RedisStorageSingleton.version}`;
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's fix this

key = `${property}.${RedisStorageSingleton.version}`;
} else {
// When `provider` is defined (e.g. for StorageProperty.Coins), use it in `key`
key = `${provider}.${property}.${RedisStorageSingleton.version}`;
}

const stringifiedValue: string = JSON.stringify(value);

const setResult = await this.client.set(key, stringifiedValue);
Expand All @@ -61,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.
Expand All @@ -69,7 +81,17 @@ export class RedisStorageSingleton implements IStorage {
*/
public async getCache(params: GetCacheParams): Promise<StorageValue> {
const { provider, property } = params;
const key = `${provider}.${property}.${RedisStorageSingleton.version}`;
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}`;
}

const value = await this.client.get(key);

if (value === null) {
Expand Down
9 changes: 7 additions & 2 deletions src/storages/types.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -15,7 +16,7 @@ export interface IStorage {
}

export type GetCacheParams = {
provider: string;
provider?: string;
property: StorageProperty;
};

Expand All @@ -29,6 +30,8 @@ export enum StorageProperty {
Pools = "pools",
CoinsMetadata = "coinsMetadata",
CetusPaths = "cetusPaths",
DCAs = "dcas",
IsDCATrading = "isDcaTrading",
}

export type StorageValue =
Expand All @@ -37,6 +40,8 @@ export type StorageValue =
| { value: ShortCoinMetadata[]; timestamp: string }
| { value: ShortPoolData[]; timestamp: string }
| { value: CetusPathForStorage[]; timestamp: string }
| { value: DCAObjectFields[]; timestamp: string }
| { value: boolean; timestamp: string }
| null;

export type RedisStorageClient = ReturnType<typeof createClient>;
4 changes: 2 additions & 2 deletions src/storages/utils/getCetusPathsCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/storages/utils/getCoinsCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
);
}

Expand Down
6 changes: 3 additions & 3 deletions src/storages/utils/getCoinsMetadataCache.ts
Original file line number Diff line number Diff line change
@@ -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";

/**
Expand Down Expand Up @@ -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}`,
);
}

Expand Down
24 changes: 24 additions & 0 deletions src/storages/utils/getIsDcaTradingCache.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> {
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;
}
4 changes: 2 additions & 2 deletions src/storages/utils/getPathsCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
);
}

Expand Down
6 changes: 3 additions & 3 deletions src/storages/utils/getPoolsCache.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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}`,
);
}

Expand Down
Loading
Loading