chore: remove solana wallet stack

This commit is contained in:
2026-04-22 13:32:14 -04:00
parent 8ec91acc25
commit 4bfcbb6b7e
8 changed files with 104 additions and 744 deletions
-267
View File
@@ -1,267 +0,0 @@
import {
createBurnInstruction,
createMint,
createMintToInstruction,
getAccount,
getOrCreateAssociatedTokenAccount,
} from "@solana/spl-token";
import {
Connection,
Keypair,
PublicKey,
sendAndConfirmTransaction,
Transaction,
} from "@solana/web3.js";
import bs58 from "bs58";
import { prisma } from "../utils/prisma";
const RPC_URL = process.env.SOLANA_RPC_URL || "https://api.devnet.solana.com";
const DECIMALS = parseInt(process.env.SOLANA_TOKEN_DECIMALS || "6");
// Backend mint authority keypair (stored securely)
let mintAuthority: Keypair | null = null;
try {
if (process.env.SOLANA_MINT_AUTHORITY_KEYPAIR) {
const raw = process.env.SOLANA_MINT_AUTHORITY_KEYPAIR.trim();
let secretKey: Uint8Array;
if (raw.startsWith("[")) {
secretKey = new Uint8Array(JSON.parse(raw));
} else {
secretKey = bs58.decode(raw);
}
mintAuthority = Keypair.fromSecretKey(secretKey);
}
} catch (error) {
console.warn("Mint authority not configured");
}
export const connection = new Connection(RPC_URL, "confirmed");
let cachedMint: PublicKey | null = null;
// Get or create token mint address
export async function getTokenMint(): Promise<PublicKey | null> {
try {
// Return cached mint
if (cachedMint) {
return cachedMint;
}
// Check env first
if (process.env.SOLANA_MINT_ADDRESS) {
cachedMint = new PublicKey(process.env.SOLANA_MINT_ADDRESS);
return cachedMint;
}
// Auto-create mint on devnet if authority exists
if (mintAuthority && RPC_URL.includes("devnet")) {
try {
// Fund mint authority with SOL first
const balance = await connection.getBalance(mintAuthority.publicKey);
if (balance < 500000000) {
const signature = await connection.requestAirdrop(
mintAuthority.publicKey,
2 * 1000000000,
);
await connection.confirmTransaction(signature);
}
const mintKeypair = Keypair.generate();
const mint = await createMint(
connection,
mintAuthority,
mintAuthority.publicKey,
null,
DECIMALS,
mintKeypair,
);
cachedMint = mint;
console.log(`Token mint created: ${mint.toBase58()}`);
console.log(
`Add SOLANA_MINT_ADDRESS=${mint.toBase58()} to your .env to persist it`,
);
return mint;
} catch {
// Devnet faucet rate-limited, will use DB-only credits
return null;
}
}
return null;
} catch (error) {
console.error("Failed to get token mint:", error);
return null;
}
}
// Create a new Solana wallet for a user
export function createWallet(): { publicKey: string; secretKey: string } {
const keypair = Keypair.generate();
return {
publicKey: keypair.publicKey.toBase58(),
secretKey: bs58.encode(keypair.secretKey),
};
}
// Get or create token account for user
export async function getOrCreateTokenAccount(
userPublicKey: PublicKey,
mint: PublicKey,
): Promise<PublicKey> {
const tokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
mintAuthority!, // payer
mint,
userPublicKey,
);
return tokenAccount.address;
}
// Mint tokens to user (called by backend after watch event)
export async function mintTokens(
userWalletAddress: string,
amount: number,
_metadata: {
sessionId: string;
contentTitle: string;
watchDurationMinutes: number;
},
): Promise<string | null> {
if (!mintAuthority) {
console.warn("Mint authority not configured, using DB-only credits");
return `db-only-${Date.now()}`;
}
try {
const userPublicKey = new PublicKey(userWalletAddress);
const mint = await getTokenMint();
if (!mint) {
console.warn("Token mint not available, using DB-only credits");
return `db-only-${Date.now()}`;
}
// Get or create user's token account
const tokenAccount = await getOrCreateTokenAccount(userPublicKey, mint);
// Calculate amount with decimals
const amountWithDecimals = amount * 10 ** DECIMALS;
// Create mint instruction
const mintInstruction = createMintToInstruction(
mint,
tokenAccount,
mintAuthority.publicKey,
BigInt(Math.floor(amountWithDecimals)),
);
// Create and send transaction
const transaction = new Transaction().add(mintInstruction);
const signature = await sendAndConfirmTransaction(connection, transaction, [
mintAuthority,
]);
console.log(`Minted ${amount} COOP to ${userWalletAddress}: ${signature}`);
return signature;
} catch (error) {
console.error("Solana minting failed, using DB-only credits:", error);
return `db-only-${Date.now()}`;
}
}
// Burn tokens from user (called when content request is approved)
export async function burnTokens(
userWalletAddress: string,
userSecretKey: string,
amount: number,
): Promise<string | null> {
try {
const userKeypair = Keypair.fromSecretKey(bs58.decode(userSecretKey));
const mint = await getTokenMint();
if (!mint) {
throw new Error("Token mint not found");
}
const userPublicKey = new PublicKey(userWalletAddress);
const tokenAccount = await getOrCreateTokenAccount(userPublicKey, mint);
// Check balance
const accountInfo = await getAccount(connection, tokenAccount);
const amountWithDecimals = BigInt(Math.floor(amount * 10 ** DECIMALS));
if (accountInfo.amount < amountWithDecimals) {
throw new Error("Insufficient balance");
}
// Create burn instruction
const burnInstruction = createBurnInstruction(
tokenAccount,
mint,
userKeypair.publicKey,
amountWithDecimals,
);
const transaction = new Transaction().add(burnInstruction);
const signature = await sendAndConfirmTransaction(connection, transaction, [
userKeypair,
]);
console.log(
`Burned ${amount} COOP from ${userWalletAddress}: ${signature}`,
);
return signature;
} catch (error) {
console.error("Failed to burn tokens:", error);
return null;
}
}
// Get token balance for user
export async function getTokenBalance(walletAddress: string): Promise<number> {
try {
const mint = await getTokenMint();
if (!mint) return 0;
const userPublicKey = new PublicKey(walletAddress);
try {
const tokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
mintAuthority!,
mint,
userPublicKey,
);
const accountInfo = await getAccount(connection, tokenAccount.address);
return Number(accountInfo.amount) / 10 ** DECIMALS;
} catch {
return 0;
}
} catch (error) {
console.error("Failed to get token balance:", error);
return 0;
}
}
// Request airdrop for testing (devnet only)
export async function requestAirdrop(
walletAddress: string,
): Promise<string | null> {
try {
const publicKey = new PublicKey(walletAddress);
const signature = await connection.requestAirdrop(
publicKey,
2 * 1000000000,
); // 2 SOL
await connection.confirmTransaction(signature);
return signature;
} catch (error) {
console.error("Airdrop failed:", error);
return null;
}
}