/** * MoonPay API utilities — fiat-to-crypto on-ramp. * * MoonPay widget URLs are built with query params and signed server-side * with HMAC-SHA256 using the API secret. No session API needed. * * Environment: MOONPAY_ENV controls sandbox vs production. * Docs: https://dev.moonpay.com/ */ import { createHmac } from 'crypto'; export type MoonPayEnv = 'sandbox' | 'production'; const WIDGET_URLS = { sandbox: 'https://buy-sandbox.moonpay.com', production: 'https://buy.moonpay.com', } as const; /** Get current MoonPay environment */ export function getMoonPayEnv(): MoonPayEnv { const env = process.env.MOONPAY_ENV?.toLowerCase(); return env === 'production' ? 'production' : 'sandbox'; } /** Get MoonPay publishable API key */ export function getMoonPayApiKey(): string { const env = getMoonPayEnv(); return (env === 'production' ? process.env.MOONPAY_API_KEY_PRODUCTION : process.env.MOONPAY_API_KEY_SANDBOX ) || process.env.MOONPAY_API_KEY || ''; } /** Get MoonPay secret key (for URL signing) */ export function getMoonPaySecretKey(): string { const env = getMoonPayEnv(); return (env === 'production' ? process.env.MOONPAY_SECRET_KEY_PRODUCTION : process.env.MOONPAY_SECRET_KEY_SANDBOX ) || process.env.MOONPAY_SECRET_KEY || ''; } /** Chain ID to MoonPay network name */ const NETWORK_MAP: Record = { 8453: 'base', 84532: 'base', 1: 'ethereum', 137: 'polygon', 42161: 'arbitrum', }; /** Token symbol to MoonPay currency code */ const CURRENCY_MAP: Record> = { USDC: { base: 'usdc_base', ethereum: 'usdc', polygon: 'usdc_polygon', arbitrum: 'usdc_arbitrum', }, ETH: { base: 'eth_base', ethereum: 'eth', arbitrum: 'eth_arbitrum', }, }; export interface MoonPayWidgetParams { walletAddress: string; email?: string; currencyCode?: string; // e.g. 'usdc_base' baseCurrencyCode?: string; // e.g. 'usd' baseCurrencyAmount?: number; // fiat amount quoteCurrencyAmount?: number; // crypto amount externalTransactionId?: string; colorCode?: string; // hex without # (e.g. '6366f1') redirectURL?: string; showWalletAddressForm?: boolean; } /** * Build a signed MoonPay widget URL. * The URL is signed with HMAC-SHA256 using the secret key. */ export function createMoonPayWidgetUrl(params: MoonPayWidgetParams): string { const env = getMoonPayEnv(); const apiKey = getMoonPayApiKey(); const secretKey = getMoonPaySecretKey(); if (!apiKey) throw new Error('MoonPay API key not configured'); if (!secretKey) throw new Error('MoonPay secret key not configured'); const base = WIDGET_URLS[env]; const url = new URL(base); url.searchParams.set('apiKey', apiKey); url.searchParams.set('walletAddress', params.walletAddress); if (params.currencyCode) url.searchParams.set('currencyCode', params.currencyCode); if (params.baseCurrencyCode) url.searchParams.set('baseCurrencyCode', params.baseCurrencyCode); if (params.baseCurrencyAmount) url.searchParams.set('baseCurrencyAmount', String(params.baseCurrencyAmount)); if (params.quoteCurrencyAmount) url.searchParams.set('quoteCurrencyAmount', String(params.quoteCurrencyAmount)); if (params.email) url.searchParams.set('email', params.email); if (params.externalTransactionId) url.searchParams.set('externalTransactionId', params.externalTransactionId); if (params.colorCode) url.searchParams.set('colorCode', params.colorCode); if (params.redirectURL) url.searchParams.set('redirectURL', params.redirectURL); if (params.showWalletAddressForm === false) url.searchParams.set('showWalletAddressForm', 'false'); // Sign the URL: HMAC-SHA256 of the query string (including the leading '?') const signature = createHmac('sha256', secretKey) .update(url.search) .digest('base64url'); url.searchParams.set('signature', signature); return url.toString(); } /** * Build a MoonPay widget URL for a payment request. * Convenience wrapper that maps token/chainId to MoonPay currency codes. */ export function createMoonPayPaymentUrl(opts: { walletAddress: string; token: string; chainId: number; amount?: string; fiatAmount?: string; fiatCurrency?: string; email?: string; paymentId?: string; }): string { const network = NETWORK_MAP[opts.chainId] || 'base'; const currencyCode = CURRENCY_MAP[opts.token]?.[network] || 'usdc_base'; const params: MoonPayWidgetParams = { walletAddress: opts.walletAddress, currencyCode, email: opts.email, externalTransactionId: opts.paymentId ? `pay-${opts.paymentId}` : undefined, colorCode: '6366f1', showWalletAddressForm: false, }; // Prefer fiat amount if available, otherwise use crypto amount if (opts.fiatAmount && parseFloat(opts.fiatAmount) > 0) { params.baseCurrencyAmount = parseFloat(opts.fiatAmount); params.baseCurrencyCode = (opts.fiatCurrency || 'USD').toLowerCase(); } else if (opts.amount && parseFloat(opts.amount) > 0) { params.quoteCurrencyAmount = parseFloat(opts.amount); } return createMoonPayWidgetUrl(params); }