UNPKG

@pump-fun/shared-contracts

Version:

Shared contracts for Pump.fun microservices.

275 lines (239 loc) 10.5 kB
import { z } from "zod"; // Define notification channels export const NotificationChannelSchema = z.enum(["push", "email"]); export type NotificationChannel = z.infer<typeof NotificationChannelSchema>; // Define notification preferences with channel support export const NotificationPreferenceSchema = z.object({ channels: z .object({ email: z.boolean().default(false), push: z.boolean().default(false), }) .default({ email: false, push: false }), enabled: z.boolean(), }); export type NotificationPreference = z.infer<typeof NotificationPreferenceSchema>; // Define category-level preferences (allows toggling entire categories) export const CategoryPreferenceSchema = z.object({ enabled: z.boolean().default(true), }); export const UserSettingsSchema = z.object({ // Legacy boolean fields (deprecated - kept for backwards compatibility) allowChatRequestNotifications: z.boolean().optional(), allowCoinWatcherNotifications: z.boolean().optional(), allowDmNotifications: z.boolean().optional(), allowFollowerNotifications: z.boolean().optional(), allowGroupNotifications: z.boolean().optional(), allowHolderMilestoneNotifications: z.boolean().optional(), allowLargePurchaseNotifications: z.boolean().optional(), allowLikeNotifications: z.boolean().optional(), allowLivestreamNotifications: z.boolean().optional(), allowMentionNotifications: z.boolean().optional(), allowNewCoinNotifications: z.boolean().optional(), allowPriceMovementNotifications: z.boolean().optional(), allowThresholdNotifications: z.boolean().optional(), allowWatchlistMovementNotifications: z.boolean().optional(), createdAt: z.number().optional(), // Email preferences emailAddress: z.string().email().optional(), emailVerified: z.boolean().optional(), // New structure supporting multiple channels per notification type, organized by category notificationPreferences: z .object({ // Category-level toggles categories: z .object({ content: CategoryPreferenceSchema.optional(), creator: CategoryPreferenceSchema.optional(), livestream: CategoryPreferenceSchema.optional(), milestones: CategoryPreferenceSchema.optional(), portfolio: CategoryPreferenceSchema.optional(), social: CategoryPreferenceSchema.optional(), trending: CategoryPreferenceSchema.optional(), watchlist: CategoryPreferenceSchema.optional(), }) .optional(), // Content notifications content: z .object({ article: NotificationPreferenceSchema.optional(), // new news article trending: NotificationPreferenceSchema.optional(), // [coin] is now trending }) .optional(), creator: z.object({ revenueShareReceived: NotificationPreferenceSchema.optional(), // you got money }), milestones: z .object({ marketCapMilestone: NotificationPreferenceSchema.optional(), // hit $500M, $1B, etc }) .optional(), // Portfolio Notifications - Coins you hold portfolio: z .object({ coinGraduated: NotificationPreferenceSchema.optional(), // coin you hold/watch graduated devActivity: NotificationPreferenceSchema.optional(), // dev buys / sells livestreamStarted: NotificationPreferenceSchema.optional(), // coin you hold started streaming priceMovement: NotificationPreferenceSchema.optional(), // hit market cap/price threshold sinceAped: NotificationPreferenceSchema.optional(), // coin graduated to top 100 }) .optional(), // Social interactions social: z .object({ chatRequest: NotificationPreferenceSchema.optional(), directMessage: NotificationPreferenceSchema.optional(), follower: NotificationPreferenceSchema.optional(), group: NotificationPreferenceSchema.optional(), // creator you hold/watch started streaming like: NotificationPreferenceSchema.optional(), livestreamStarted: NotificationPreferenceSchema.optional(), mention: NotificationPreferenceSchema.optional(), referralSignup: NotificationPreferenceSchema.optional(), }) .optional(), // Now Trending trending: z .object({ nowTrending: NotificationPreferenceSchema.optional(), // [coin] is now trending upSinceListing: NotificationPreferenceSchema.optional(), // [coin] is up X% since listing }) .optional(), // Watchlist Alerts watchlist: z .object({ allTimeHigh: NotificationPreferenceSchema.optional(), // moved by X% within intervals coinGraduated: NotificationPreferenceSchema.optional(), // hit market cap/price threshold devActivity: NotificationPreferenceSchema.optional(), // moved X% since added to watchlist holderMilestone: NotificationPreferenceSchema.optional(), // creator you hold/watch started streaming largePurchase: NotificationPreferenceSchema.optional(), // someone bought $X worth livestreamStarted: NotificationPreferenceSchema.optional(), // new ATH priceMovement: NotificationPreferenceSchema.optional(), // hit X holders sinceAdded: NotificationPreferenceSchema.optional(), // dev buy/sells threshold: NotificationPreferenceSchema.optional(), // coin graduated to top 100 }) .optional(), }) .optional(), PK: z.string().startsWith("WALLET#"), // Configuration for price movement intervals (user can select which intervals they want) priceMovementIntervals: z.array(z.enum(["10m", "1h", "6h", "24h"])).optional(), SK: z.literal("SETTINGS"), updatedAt: z.number().optional(), // Unix timestamp in seconds or milliseconds walletAddress: z.string(), // Unix timestamp in seconds or milliseconds }); export type UserSettingsItem = z.infer<typeof UserSettingsSchema>; export const NotificationTypes = { AllTimeHigh: "allowAllTimeHighNotifications", Article: "allowArticleNotifications", ChatRequest: "allowChatRequestNotifications", CoinWatcher: "allowCoinWatcherNotifications", DirectMessage: "allowDmNotifications", Follower: "allowFollowerNotifications", Group: "allowGroupNotifications", HolderMilestone: "allowHolderMilestoneNotifications", LargePurchase: "allowLargePurchaseNotifications", Like: "allowLikeNotifications", Livestream: "allowLivestreamNotifications", Mention: "allowMentionNotifications", NewCoin: "allowNewCoinNotifications", PriceMovement: "allowPriceMovementNotifications", Threshold: "allowThresholdNotifications", WatchlistMovement: "allowWatchlistMovementNotifications", } as const; // Notification type keys for the new structure export const NotificationTypeKeys = { AllTimeHigh: "allTimeHigh", Article: "article", ChatRequest: "chatRequest", CoinWatcher: "coinWatcher", DirectMessage: "directMessage", Follower: "follower", Group: "group", HolderMilestone: "holderMilestone", LargePurchase: "largePurchase", Like: "like", Livestream: "livestream", Mention: "mention", NewCoin: "newCoin", PriceMovement: "priceMovement", Threshold: "threshold", WatchlistMovement: "watchlistMovement", } as const; export type NotificationTypeKey = (typeof NotificationTypeKeys)[keyof typeof NotificationTypeKeys]; // Helper type for notification preferences object export type UserNotificationPreferences = z.infer<typeof UserSettingsSchema>["notificationPreferences"]; // Type for categories that contain notifications export type NotificationCategory = Exclude<keyof NonNullable<UserNotificationPreferences>, "categories">; // Helper function to check if a notification is enabled for a specific channel export function isNotificationEnabledForChannel( preferences: UserNotificationPreferences, category: NotificationCategory, notificationType: string, channel: NotificationChannel, ): boolean { if (!preferences) return false; // Check if category is enabled if (preferences.categories?.[category]?.enabled === false) { return false; } const categoryPreferences = preferences[category]; if (!categoryPreferences || typeof categoryPreferences !== "object") { return false; } const pref = categoryPreferences[notificationType as keyof typeof categoryPreferences]; // Type guard to check if pref is a NotificationPreference if (!pref || typeof pref !== "object" || !("enabled" in pref) || !("channels" in pref)) { return false; } // Now TypeScript knows pref has the shape we need const typedPref = pref as NotificationPreference; return typedPref.enabled === true && typedPref.channels[channel] === true; } // Helper function to check if a category is enabled export function isCategoryEnabled( preferences: UserNotificationPreferences, category: keyof NonNullable<NonNullable<UserNotificationPreferences>["categories"]>, ): boolean { return preferences?.categories?.[category]?.enabled !== false; } // Helper to get all enabled notifications for a user export function getEnabledNotifications( preferences: UserNotificationPreferences, channel: NotificationChannel, ): Array<{ category: string; type: string }> { const enabled: Array<{ category: string; type: string }> = []; if (!preferences) return enabled; const categories: NotificationCategory[] = [ "creator", "trending", "watchlist", "portfolio", "milestones", "social", "content", ]; for (const category of categories) { // Check if this category has a category-level toggle const hasCategories = preferences.categories && category in preferences.categories; if ( hasCategories && !isCategoryEnabled( preferences, category as keyof NonNullable<NonNullable<UserNotificationPreferences>["categories"]>, ) ) { continue; } const categoryPreferences = preferences[category]; if (!categoryPreferences || typeof categoryPreferences !== "object") continue; for (const [type, pref] of Object.entries(categoryPreferences)) { if (pref && typeof pref === "object" && "enabled" in pref && "channels" in pref) { const typedPref = pref as NotificationPreference; if (typedPref.enabled && typedPref.channels[channel] === true) { enabled.push({ category, type }); } } } } return enabled; }