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

(1CT) Set default session period to 7 days and update time periods #4000

Merged
merged 4 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion packages/trpc/src/one-click-trading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const oneClickTradingRouter = createTRPCRouter({
spendLimitTokenDecimals: usdcAsset.coinDecimals,
networkFeeLimit: OneClickTradingMaxGasLimit,
sessionPeriod: {
end: "1hour" as const,
end: "7days" as const,
},
};
}
Expand Down
8 changes: 3 additions & 5 deletions packages/types/src/one-click-trading-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ export interface OneClickTradingTimeLimit {
}

export type OneClickTradingHumanizedSessionPeriod =
| "5min"
| "10min"
| "30min"
| "1hour"
| "3hours"
| "12hours";
| "1day"
| "7days"
| "30days";
JoseRFelix marked this conversation as resolved.
Show resolved Hide resolved

export interface OneClickTradingTransactionParams {
isOneClickEnabled: boolean;
Expand Down
114 changes: 114 additions & 0 deletions packages/utils/src/__tests__/date.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { safeTimeout } from "../date";

describe("safeTimeout", () => {
jest.useFakeTimers(); // Use fake timers for controlled testing
jest.spyOn(global, "setTimeout"); // Spy on the setTimeout function

afterEach(() => {
jest.clearAllTimers(); // Clear timers after each test
jest.clearAllMocks();
});

test("calls setTimeout directly for time within safe range", () => {
const callback = jest.fn();
const timeInRange = 5000; // 5 seconds

safeTimeout(callback, timeInRange);

// Fast-forward time
jest.advanceTimersByTime(timeInRange);

// Ensure the callback is called once
expect(callback).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), timeInRange);
});

test("splits large timeout into smaller intervals", () => {
const callback = jest.fn();
const largeTimeout = 2591160000; // 29 days, 23 hours, 46 minutes
const MAX_TIMEOUT = 2 ** 31 - 1; // JavaScript's maximum timeout (~24.8 days)

safeTimeout(callback, largeTimeout);

// Fast-forward the first interval
jest.advanceTimersByTime(MAX_TIMEOUT);

// Ensure another setTimeout was scheduled for the remainder
expect(setTimeout).toHaveBeenCalledTimes(2);

// Fast-forward the remaining time
jest.advanceTimersByTime(largeTimeout - MAX_TIMEOUT);

// Ensure the callback is called once after both intervals
expect(callback).toHaveBeenCalledTimes(1);
});

test("handles exactly the maximum timeout limit", () => {
const callback = jest.fn();
const exactMaxTimeout = 2 ** 31 - 1;

safeTimeout(callback, exactMaxTimeout);

// Fast-forward time
jest.advanceTimersByTime(exactMaxTimeout);

// Ensure the callback is called once
expect(callback).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenCalledWith(
expect.any(Function),
exactMaxTimeout
);
});

test("does not call the callback prematurely", () => {
const callback = jest.fn();
const largeTimeout = 2591160000; // 29 days, 23 hours, 46 minutes

safeTimeout(callback, largeTimeout);

// Fast-forward time but do not complete all intervals
jest.advanceTimersByTime(2 ** 31 - 2); // Just shy of the max timeout

// Callback should not be called yet
expect(callback).not.toHaveBeenCalled();
});

test("clears the timeout before it completes", () => {
const callback = jest.fn(); // Mock callback function
const largeTimeout = 2591160000; // ~30 days

// Set up the safe timeout
const { clear } = safeTimeout(callback, largeTimeout);

// Fast-forward time by 5 seconds and then clear the timeout
jest.advanceTimersByTime(5000); // Advance 5 seconds
clear(); // Clear the timeout early

// Advance time further to ensure the callback does not execute
jest.advanceTimersByTime(largeTimeout);

// Verify that the callback was never called
expect(callback).not.toHaveBeenCalled();
});

test("clears the second timer in a split timeout chain", () => {
const callback = jest.fn(); // Mock callback function
const largeTimeout = 2591160000; // ~30 days
const MAX_TIMEOUT = 2 ** 31 - 1; // JavaScript's maximum timeout (~24.8 days)

// Set up the timeout
const { clear } = safeTimeout(callback, largeTimeout);

// Advance time to the end of the first timer but before the second timer finishes
jest.advanceTimersByTime(MAX_TIMEOUT);

// Clear the timeout during the second interval
clear();

// Advance time to simulate the remainder of the second timer
jest.advanceTimersByTime(largeTimeout - MAX_TIMEOUT);

// Verify that the callback was not called
expect(callback).not.toHaveBeenCalled();
});
});
35 changes: 35 additions & 0 deletions packages/utils/src/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,38 @@ export function unixNanoSecondsToSeconds(
): number {
return Number(unixNanoSeconds) / 1000000000;
}

/**
* A safe version of setTimeout that splits large timeouts into smaller intervals.
* This is necessary because the maximum timeout for setTimeout is 24.8 days.
* @param callback - The function to call after the timeout.
* @param milliseconds - The number of milliseconds to wait before calling the callback.
*/
export function safeTimeout(callback: () => void, milliseconds: number) {
const MAX_TIMEOUT = 2 ** 31 - 1; // Maximum safe timeout
let timeoutId: NodeJS.Timeout | null = null; // Track the current timeout
let cleared = false; // Track if the timeout has been cleared

const clear = () => {
cleared = true; // Mark as cleared
if (timeoutId) {
clearTimeout(timeoutId); // Clear the current timeout
}
};

const runTimeout = (remainingTime: number) => {
if (cleared) return; // Do nothing if cleared

if (remainingTime <= MAX_TIMEOUT) {
timeoutId = setTimeout(callback, remainingTime);
} else {
timeoutId = setTimeout(() => {
runTimeout(remainingTime - MAX_TIMEOUT); // Recurse with the remaining time
}, MAX_TIMEOUT);
}
};

runTimeout(milliseconds);

return { clear };
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,26 @@ import {
} from "~/components/screen-manager";
import { useOneClickTradingSession, useTranslation } from "~/hooks";

export const oneClickTradingTimeMappings = {
"1hour": "oneClickTrading.sessionPeriods.1hour",
"1day": "oneClickTrading.sessionPeriods.1day",
"7days": "oneClickTrading.sessionPeriods.7days",
"30days": "oneClickTrading.sessionPeriods.30days",
};

export function getSessionPeriodTranslationKey(
input: OneClickTradingHumanizedSessionPeriod
) {
const timeMappings = {
"5min": "oneClickTrading.sessionPeriods.5min",
"10min": "oneClickTrading.sessionPeriods.10min",
"30min": "oneClickTrading.sessionPeriods.30min",
"1hour": "oneClickTrading.sessionPeriods.1hour",
"3hours": "oneClickTrading.sessionPeriods.3hours",
"12hours": "oneClickTrading.sessionPeriods.12hours",
};
const mapped = timeMappings[input];
const mapped = oneClickTradingTimeMappings[input];
if (!mapped) throw new Error(`No mapping for ${input}`);
return mapped;
}

const SessionPeriods: OneClickTradingHumanizedSessionPeriod[] = [
"5min",
"10min",
"30min",
"1hour",
"3hours",
"12hours",
"1day",
"7days",
"30days",
];

interface SessionPeriodScreenProps extends OneClickTradingBaseScreenProps {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,36 +295,27 @@ export async function makeCreate1CTSessionMessage({

let sessionPeriod: OneClickTradingTimeLimit;
switch (transaction1CTParams.sessionPeriod.end) {
case "5min":
sessionPeriod = {
end: unixSecondsToNanoSeconds(dayjs().add(5, "minute").unix()),
};
break;
case "10min":
sessionPeriod = {
end: unixSecondsToNanoSeconds(dayjs().add(10, "minute").unix()),
};
break;
case "30min":
case "1hour":
sessionPeriod = {
end: unixSecondsToNanoSeconds(dayjs().add(30, "minute").unix()),
end: unixSecondsToNanoSeconds(dayjs().add(1, "hour").unix()),
};
break;
case "1hour":
case "1day":
sessionPeriod = {
end: unixSecondsToNanoSeconds(dayjs().add(1, "hour").unix()),
end: unixSecondsToNanoSeconds(dayjs().add(1, "day").unix()),
};
break;
case "3hours":
case "7days":
sessionPeriod = {
end: unixSecondsToNanoSeconds(dayjs().add(3, "hours").unix()),
end: unixSecondsToNanoSeconds(dayjs().add(7, "day").unix()),
};
break;
case "12hours":
case "30days":
sessionPeriod = {
end: unixSecondsToNanoSeconds(dayjs().add(12, "hours").unix()),
end: unixSecondsToNanoSeconds(dayjs().add(30, "day").unix()),
};
break;

default:
throw new Error(
`Unsupported time limit: ${transaction1CTParams.sessionPeriod.end}`
Expand Down
Loading
Loading