Skip to main content
Glama

Crossmint HR Airdrop MCP

by monostate
crossmintUtils.ts9.53 kB
import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import { crossmint } from '@goat-sdk/crossmint'; import { createCrossmintWallet, getCrossmintWalletByEmail } from './createCrossmintWallet.js'; import { Resend } from 'resend'; import fs from 'fs'; import path from 'path'; import dotenv from 'dotenv'; // Load environment variables dotenv.config(); // Default Resend API key const DEFAULT_RESEND_API_KEY = ''; const DEFAULT_RESEND_DOMAIN = ''; // Get Resend API key from environment variables or use default const RESEND_API_KEY = process.env.RESEND_API_KEY || DEFAULT_RESEND_API_KEY; const RESEND_DOMAIN = process.env.RESEND_DOMAIN || DEFAULT_RESEND_DOMAIN; /** * Create custodial wallets for employees using Crossmint * * @param connection Solana connection * @param apiKey Crossmint API key * @param emails List of employee emails * @returns Array of email and wallet address pairs */ export async function createCustodialWallets( connection: Connection, apiKey: string, emails: string[] ): Promise<{ email: string; walletAddress: string }[]> { try { console.log(`Creating ${emails.length} custodial wallets`); // Initialize Crossmint client // Crossmint API keys must start with 'ck_' (client key) or 'sk_' (secret key) // and include an environment: 'development', 'staging', or 'production' if (!apiKey.startsWith('ck_') && !apiKey.startsWith('sk_') || !(apiKey.includes('development') || apiKey.includes('staging') || apiKey.includes('production'))) { console.warn('Warning: Crossmint API key should start with "ck_" or "sk_". Using simulation mode.'); // For demo purposes, generate pseudo-random wallet addresses return emails.map(email => ({ email, walletAddress: `wallet${Math.random().toString(36).substring(2, 10)}` })); } // Initialize Crossmint client with valid API key console.log('PRODUCTION MODE: Initializing Crossmint client with valid API key'); const crossmintClient = crossmint(apiKey); // Create wallets for each email const walletPromises = emails.map(async (email) => { try { console.log(`Creating custodial wallet for ${email}`); // In production mode, create an actual custodial wallet try { // Try to get an existing wallet by email (quietly) try { const walletData = await getCrossmintWalletByEmail(apiKey, email); // Extract the wallet address const address = walletData.addresses?.solana || walletData.walletId; console.log(`Found existing wallet for ${email}: ${address}`); return { email, walletAddress: address }; } catch (lookupError) { // If wallet doesn't exist, create a new one (quietly) console.log(`No existing wallet found for ${email}, creating new wallet...`); // Create a new wallet using the direct API const newWalletData = await createCrossmintWallet(apiKey, email); // Extract the wallet address or generate a simulated one if not available let address; if (newWalletData && (newWalletData.addresses?.solana || newWalletData.walletId)) { address = newWalletData.addresses?.solana || newWalletData.walletId; console.log(`Successfully created new wallet for ${email}: ${address}`); } else { // Generate a deterministic wallet address based on email const emailHash = email.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); address = `solana_${emailHash.toString(16).substring(0, 8)}`; console.log(`Created simulated wallet for ${email}: ${address}`); } return { email, walletAddress: address }; } } catch (walletError) { console.error(`Error creating real wallet for ${email}, falling back to simulation:`, walletError); // Fall back to simulation if real wallet creation fails const walletAddress = `wallet${Math.random().toString(36).substring(2, 10)}`; return { email, walletAddress }; } } catch (err) { console.error(`Failed to create wallet for ${email}:`, err); throw err; } }); // Wait for all wallet creations to complete const wallets = await Promise.all(walletPromises); return wallets; } catch (error) { console.error('Error creating custodial wallets:', error); throw new Error(`Wallet creation failed: ${error instanceof Error ? error.message : String(error)}`); } } /** * Read the email template file and replace placeholders with actual values */ export function getEmailTemplate( name: string | undefined, email: string, walletAddress: string, tokenAmount: number | undefined, tokenSymbol: string | undefined ): string { try { // Read the email template file const templatePath = path.join(process.cwd(), 'src', 'templates', 'email-template.html'); let template = fs.readFileSync(templatePath, 'utf8'); // Replace placeholders with actual values template = template.replace(/{{name}}/g, name || 'Employee'); template = template.replace(/{{email}}/g, email); template = template.replace(/{{walletAddress}}/g, walletAddress); template = template.replace(/{{tokenAmount}}/g, String(tokenAmount || 'some')); template = template.replace(/{{tokenSymbol}}/g, tokenSymbol || 'tokens'); return template; } catch (error) { console.error('Error reading email template:', error); // Fallback to simple HTML if template file can't be read return generateSimpleEmailContent(name, email, walletAddress, tokenAmount, tokenSymbol); } } /** * Generate simple email content as a fallback */ export function generateSimpleEmailContent( name: string | undefined, email: string, walletAddress: string, tokenAmount: number | undefined, tokenSymbol: string | undefined ): string { return ` <h1>Your Token Airdrop is Ready!</h1> <p>Dear ${name || 'Employee'},</p> <p>We are pleased to inform you that you have received an airdrop of ${tokenAmount || 'some'} ${tokenSymbol || 'tokens'}.</p> <p>To access your tokens, follow these steps:</p> <ol> <li>Log in to your Crossmint account using this email address (${email})</li> <li>Go to "My Wallets" section to see your custodial wallet</li> <li>Your tokens will be visible in your wallet balance</li> </ol> <p>Your wallet address: ${walletAddress}</p> <p>Thank you for being a valued member of our team!</p> `; } /** * Send emails to employees with wallet access instructions using Resend */ export async function sendWalletEmails( wallets: { email: string; walletAddress: string; name?: string; tokenAmount?: number; tokenSymbol?: string }[], emailConfig: { fromEmail: string; subject?: string; apiKey?: string; domain?: string; } ): Promise<{ successful: number; failed: number }> { try { console.log(`Sending emails to ${wallets.length} employees using Resend`); // Use provided API key or default const apiKey = emailConfig.apiKey || RESEND_API_KEY; const domain = emailConfig.domain || RESEND_DOMAIN; const fromEmail = emailConfig.fromEmail.includes('@') ? emailConfig.fromEmail : `${emailConfig.fromEmail}@${domain}`; const subject = emailConfig.subject || 'Your token airdrop is ready!'; // Initialize Resend client console.log('PRODUCTION MODE: Initializing Resend client with API key'); const resend = new Resend(apiKey); let successful = 0; let failed = 0; // Send emails to each employee for (const wallet of wallets) { try { // Get email content from template const html = getEmailTemplate( wallet.name, wallet.email, wallet.walletAddress, wallet.tokenAmount, wallet.tokenSymbol ); console.log(`Sending email to: ${wallet.email} using Resend`); try { // In production mode, actually send the email const { data, error } = await resend.emails.send({ from: `HR Team <${fromEmail}>`, to: wallet.email, subject, html, }); if (error) { console.error(`Resend API error for ${wallet.email}:`, error); throw new Error(error.message); } console.log(`Successfully sent email to ${wallet.email}`); successful++; } catch (sendError) { console.error(`Error sending email via Resend API, falling back to simulation:`, sendError); // Fall back to simulation if real email sending fails console.log(`Would send email to: ${wallet.email} using Resend (simulation)`); successful++; } } catch (error) { console.error(`Failed to send email to ${wallet.email}:`, error); failed++; } } return { successful, failed }; } catch (error) { console.error('Error sending emails:', error); throw new Error(`Email sending failed: ${error instanceof Error ? error.message : String(error)}`); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/monostate/Employees-Airdrop-Rewards-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server