Skip to content
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
4 changes: 3 additions & 1 deletion abis.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Abi } from "viem";

export const DELEGATION_ABI = [
{ inputs: [], stateMutability: "nonpayable", type: "constructor" },
{
Expand Down Expand Up @@ -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" },
Expand Down
99 changes: 41 additions & 58 deletions components/meme-calendar/meme-calendar.helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) ----
Expand Down Expand Up @@ -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
);
}

Expand All @@ -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
);
}

Expand Down
59 changes: 26 additions & 33 deletions hooks/useManifoldClaim.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand All @@ -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),
},
];
}
Expand All @@ -102,7 +98,7 @@ export interface ManifoldClaim {
export function useManifoldClaim(
contract: string,
proxy: string,
abi: any,
abi: Abi,
tokenId: number,
onError?: () => void
) {
Expand All @@ -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;
}
Expand All @@ -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({
Expand All @@ -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,
Expand Down