diff --git a/abis.ts b/abis.ts index a08d2366d8..e3f66c1150 100644 --- a/abis.ts +++ b/abis.ts @@ -1,3 +1,5 @@ +import { Abi } from "viem"; + export const DELEGATION_ABI = [ { inputs: [], stateMutability: "nonpayable", type: "constructor" }, { @@ -2858,7 +2860,7 @@ export const NEXTGEN_ADMIN_ABI = [ }, ]; -export const MEMES_MANIFOLD_PROXY_ABI = [ +export const MEMES_MANIFOLD_PROXY_ABI: Abi = [ { inputs: [ { internalType: "address", name: "initialOwner", type: "address" }, diff --git a/components/meme-calendar/meme-calendar.helpers.tsx b/components/meme-calendar/meme-calendar.helpers.tsx index 77af679e82..34192d4edf 100644 --- a/components/meme-calendar/meme-calendar.helpers.tsx +++ b/components/meme-calendar/meme-calendar.helpers.tsx @@ -18,13 +18,13 @@ export const SZN1_RANGE = { } as const; export const SZN1_SEASON_INDEX = -13; -// ---- Eastern Time mint schedule ---- -const EASTERN_STANDARD_OFFSET_HOURS = -5; // UTC-5 during winter (EST) -const EASTERN_DAYLIGHT_OFFSET_HOURS = -4; // UTC-4 during summer (EDT) -const MINT_EASTERN_HOUR = 10; -const MINT_EASTERN_MINUTE = 40; -const MINT_END_EASTERN_HOUR = 10; -const MINT_END_EASTERN_MINUTE = 0; +const EUROPE_TZ = "Europe/Nicosia"; // EET/EEST +const EUROPE_WINTER_OFFSET_HOURS = +2; // EET (UTC+2) +const EUROPE_SUMMER_OFFSET_HOURS = +3; // EEST (UTC+3) +const MINT_EUROPE_HOUR = 17; +const MINT_EUROPE_MINUTE = 40; +const MINT_END_EUROPE_HOUR = 17; +const MINT_END_EUROPE_MINUTE = 0; export const MILLIS_PER_DAY = 1000 * 60 * 60 * 24; @@ -101,55 +101,38 @@ export const FIRST_MINT_DATE: Date = (() => { return nextMintDateOnOrAfter(phase.startUtcDay); })(); -// ---- US Eastern Time DST helpers ---- -function getNthWeekdayOfMonthUtc( - year: number, - monthIndex: number, - weekday: number, - occurrence: number -): number { - const firstOfMonth = new Date(Date.UTC(year, monthIndex, 1)); - const firstDow = firstOfMonth.getUTCDay(); - const offset = (weekday - firstDow + 7) % 7; - return 1 + offset + (occurrence - 1) * 7; -} - -function isEasternDaylightTime(utcDay: Date): boolean { - const year = utcDay.getUTCFullYear(); - const month = utcDay.getUTCMonth(); - const day = utcDay.getUTCDate(); - - if (month < 2 || month > 10) { - return false; - } - if (month > 2 && month < 10) { - return true; - } - - if (month === 2) { - const dstStartDay = getNthWeekdayOfMonthUtc(year, 2, 0, 2); // second Sunday in March - return day >= dstStartDay; - } - - const dstEndDay = getNthWeekdayOfMonthUtc(year, 10, 0, 1); // first Sunday in November - return day < dstEndDay; -} - -function getEasternOffsetHours(utcDay: Date): number { - return isEasternDaylightTime(utcDay) - ? EASTERN_DAYLIGHT_OFFSET_HOURS - : EASTERN_STANDARD_OFFSET_HOURS; -} - -function easternWallTimeToUtcInstant( +export function wallTimeToUtcInstantInZone( utcDay: Date, hour: number, minute: number ): Date { - const offsetHours = getEasternOffsetHours(utcDay); - const out = new Date(utcDay); - out.setUTCHours(hour - offsetHours, minute, 0, 0); - return out; + const mk = (offsetHours: number) => { + const out = new Date(utcDay); + out.setUTCHours(hour - offsetHours, minute, 0, 0); + return out; + }; + + const winterCandidate = mk(EUROPE_WINTER_OFFSET_HOURS); + const summerCandidate = mk(EUROPE_SUMMER_OFFSET_HOURS); + + const showsTargetWallTime = (d: Date) => { + const parts = new Intl.DateTimeFormat(undefined, { + timeZone: EUROPE_TZ, + hour12: false, + hour: "2-digit", + minute: "2-digit", + }).format(d); + return ( + parts === + `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}` + ); + }; + + if (showsTargetWallTime(summerCandidate)) return summerCandidate; + if (showsTargetWallTime(winterCandidate)) return winterCandidate; + + // Fallback: prefer the offset that matches the zone's DST on that date (summer first); + return summerCandidate; } // ---- Mint schedule helpers (Mon/Wed/Fri only, by UTC weekday) ---- @@ -184,10 +167,10 @@ export function prevMintDateOnOrBefore(d: Date): Date { } export function mintStartInstantUtcForMintDay(utcDay: Date): Date { - return easternWallTimeToUtcInstant( + return wallTimeToUtcInstantInZone( utcDay, - MINT_EASTERN_HOUR, - MINT_EASTERN_MINUTE + MINT_EUROPE_HOUR, + MINT_EUROPE_MINUTE ); } @@ -199,10 +182,10 @@ export function mintEndInstantUtcForMintDay(utcDay: Date): Date { utcDay.getUTCDate() + 1 ) ); - return easternWallTimeToUtcInstant( + return wallTimeToUtcInstantInZone( nextDay, - MINT_END_EASTERN_HOUR, - MINT_END_EASTERN_MINUTE + MINT_END_EUROPE_HOUR, + MINT_END_EUROPE_MINUTE ); } diff --git a/hooks/useManifoldClaim.ts b/hooks/useManifoldClaim.ts index 07b917c828..c26849e8d5 100644 --- a/hooks/useManifoldClaim.ts +++ b/hooks/useManifoldClaim.ts @@ -1,6 +1,7 @@ "use client"; import { MEMES_MANIFOLD_PROXY_ABI } from "@/abis"; +import { wallTimeToUtcInstantInZone } from "@/components/meme-calendar/meme-calendar.helpers"; import { MANIFOLD_NETWORK, MEMES_CONTRACT, @@ -9,8 +10,8 @@ import { } from "@/constants"; import { areEqualAddresses } from "@/helpers/Helpers"; import { Time } from "@/helpers/time"; -import { DateTime } from "luxon"; import { useCallback, useEffect, useState } from "react"; +import type { Abi } from "viem"; import { useReadContract } from "wagmi"; export enum ManifoldClaimStatus { @@ -33,50 +34,45 @@ export interface MemePhase { } export function buildMemesPhases(mintDate: Time): MemePhase[] { - const zone = "America/New_York"; - - const base = DateTime.fromJSDate(mintDate.toDate()).setZone(zone); - - const toUtc = (hour: number, minute: number): Time => - Time.seconds(base.set({ hour, minute, second: 0 }).toUTC().toSeconds()); - - const toUtcNextDay = (hour: number, minute: number): Time => - Time.seconds( - base - .plus({ days: 1 }) - .set({ hour, minute, second: 0 }) - .toUTC() - .toSeconds() + const buildTime = ( + hour: number, + minute: number, + nextDay: boolean = false + ) => { + const ref = nextDay ? mintDate.plusDays(1) : mintDate; + return Time.fromString( + wallTimeToUtcInstantInZone(ref.toDate(), hour, minute).toISOString() ); + }; return [ { id: "0", name: "Phase 0 (Allowlist)", type: ManifoldPhase.ALLOWLIST, - start: toUtc(10, 40), - end: toUtc(11, 20), + start: buildTime(17, 40), + end: buildTime(18, 20), }, { id: "1", name: "Phase 1 (Allowlist)", type: ManifoldPhase.ALLOWLIST, - start: toUtc(11, 30), - end: toUtc(11, 50), + start: buildTime(18, 30), + end: buildTime(18, 50), }, { id: "2", name: "Phase 2 (Allowlist)", type: ManifoldPhase.ALLOWLIST, - start: toUtc(12, 0), - end: toUtc(12, 20), + start: buildTime(19, 0), + end: buildTime(19, 20), }, { id: "public", name: "Public Phase", type: ManifoldPhase.PUBLIC, - start: toUtc(12, 20), - end: toUtcNextDay(10, 0), + start: buildTime(19, 20), + end: buildTime(17, 0, true), }, ]; } @@ -102,7 +98,7 @@ export interface ManifoldClaim { export function useManifoldClaim( contract: string, proxy: string, - abi: any, + abi: Abi, tokenId: number, onError?: () => void ) { @@ -113,14 +109,14 @@ export function useManifoldClaim( const now = Date.now() / 1000; if (now < start) { return ManifoldClaimStatus.UPCOMING; - } else if (now > start && now < end) { + } else if (now >= start && now < end) { return ManifoldClaimStatus.ACTIVE; } return ManifoldClaimStatus.ENDED; }, []); const getMemePhase = useCallback( - (phase: ManifoldPhase, start: number, end: number) => { + (phase: ManifoldPhase, end: number) => { if (!areEqualAddresses(contract, MEMES_CONTRACT)) { return undefined; } @@ -129,9 +125,10 @@ export function useManifoldClaim( return MEME_PHASES.find((mp) => mp.id === "public"); } - return MEME_PHASES.filter((mp) => mp.end >= Time.seconds(end))[0]; + const endTime = Time.seconds(end); + return MEME_PHASES.find((mp) => mp.end.gte(endTime)); }, - [] + [contract] ); const readContract = useReadContract({ @@ -158,11 +155,7 @@ export function useManifoldClaim( publicMerkle && claimData.total > 0 ? ManifoldPhase.PUBLIC : ManifoldPhase.ALLOWLIST; - const memePhase = getMemePhase( - phase, - claimData.startDate, - claimData.endDate - ); + const memePhase = getMemePhase(phase, claimData.endDate); const remaining = Number(claimData.totalMax) - Number(claimData.total); const newClaim: ManifoldClaim = { instanceId: instanceId,