chore: remove solana wallet stack
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user