Viewing File: /home/ubuntu/efiexchange-node-base/src/controllers/userApp/solana.controller.ts
import {
SUPPORTED_RPC_URLS,
CHAIN_INFO,
SupportedTokens,
SOL_SPL_CONTRACT_ADDRESS
} from "../../config/chains";
require('dotenv').config();
import jwt from "jsonwebtoken";
import * as bip39 from "bip39";
import * as ed25519HdKey from "ed25519-hd-key";
const {
Connection,
clusterApiUrl,
Keypair,
LAMPORTS_PER_SOL,
Transaction,
SystemProgram,
sendAndConfirmTransaction,
PublicKey,
} = require('@solana/web3.js');
const { getOrCreateAssociatedTokenAccount, transfer } = require('@solana/spl-token');
import { decrypt } from '../../utils/decrypt';
import speakeasy from 'speakeasy'
import bs58 from 'bs58'
/**
* @method transferSolanaTokensHandler
*
* @description transfer Solana Tokens
*
* @param req
*
* @param res
*
* @returns json response
*/
export async function transferSolanaTokensHandler(req: any, res: any) {
try {
let inputs = req.body;
// console.log("inputs", inputs)
if (!inputs.receiver_wallet_address) {
return res.sendError("Receiver Wallet Address is required");
}
if (!inputs.tokens) {
return res.sendError("Amount is required");
}
if (!inputs.token_type) {
return res.sendError("Token Type is required");
}
if (!inputs.validate_token) {
return res.sendError("Validation Token field is required");
}
if (!inputs.order_id) {
return res.sendError("Order ID field is required");
}
const decoded = jwt.verify(inputs.validate_token, process.env.JWT_SECRET);
if (decoded.sub != inputs.order_id) {
return res.sendError("Transaction validation failed");
}
let signature;
// Create a connection to the Solana devnet
const network = process.env.NODE_ENV === "production" ? "mainnet-beta" : "devnet";
const connection = new Connection(clusterApiUrl(network));
// const connection = new Connection(SUPPORTED_RPC_URLS[SupportedTokens["SOL"]]);
const base58SecretKey = process.env.ADMIN_SOLANA_PRIVATE_KEY;
const senderSecretKey = bs58.decode(base58SecretKey);
const fromWallet = Keypair.fromSecretKey(senderSecretKey);
if (inputs.token_type == "SOL") {
// Replace with the recipient's public key
const recipientPublicKey = inputs.receiver_wallet_address;
// Amount to send in SOL (e.g., 0.1 SOL)
const amount = inputs.tokens * LAMPORTS_PER_SOL;
console.log('amount:', amount);
// Create a transaction instruction for the transfer
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: fromWallet.publicKey,
toPubkey: recipientPublicKey,
lamports: amount,
})
);
// Sign and send the transaction
signature = await sendAndConfirmTransaction(
connection,
transaction,
[fromWallet]
);
} else {
const toPublicKey = new PublicKey(inputs.receiver_wallet_address);
// Replace with the mint address of the SPL token you want to transfer (e.g., USDC)
const mintAddress = new PublicKey(SOL_SPL_CONTRACT_ADDRESS[inputs.token_type]);
// Create or retrieve the associated token account for the sender
const fromTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
fromWallet,
mintAddress,
fromWallet.publicKey
);
// Create or retrieve the associated token account for the recipient
const toTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
fromWallet,
mintAddress,
toPublicKey
);
// Specify the number of tokens to transfer (considering token decimals)
const amount = inputs.tokens * Math.pow(10, 8); // e.g., transferring 100 tokens for a token with 6 decimals
console.log('amount:', amount);
// Transfer the tokens
signature = await transfer(
connection,
fromWallet,
fromTokenAccount.address,
toTokenAccount.address,
fromWallet.publicKey,
amount
);
}
const transactionDetails = await connection.getTransaction(signature, {
commitment: 'confirmed',
maxSupportedTransactionVersion: 0,
});
inputs = {};
let responseData = {
signature: signature,
transferTokenResponse: transactionDetails,
fromWalletAddress: process.env.ADMIN_SOLANA_WALLET_ADDRESS,
};
return res.sendResponse(responseData, "Token transfer success");
} catch (error) {
return res.sendError(error.message);
}
}
/**
* @method getSolanaTransactionDetails
*
* @description getSolanaTransactionDetails
*
* @param req
*
* @param res
*
* @returns json response
*/
export async function getSolanaTransactionDetails(req: any, res: any) {
try {
let inputs = req.body;
if (!inputs.transaction_hash) {
return res.sendError("Transaction Hash is required");
}
if (!inputs.token_type) {
return res.sendError("Token Type is required");
}
let transaction_fee; let value; let token_value; let receiver_wallet_address; let from_wallet_address;
const network = process.env.NODE_ENV === "production" ? "mainnet-beta" : "devnet";
let connection;
if(network == "mainnet-beta") {
connection = new Connection(clusterApiUrl(network));
} else {
connection = new Connection(SUPPORTED_RPC_URLS[SupportedTokens["SOL"]]);
}
let explorer_url = CHAIN_INFO[SupportedTokens["SOL"]].explorer + 'tx/' + inputs.transaction_hash + (process.env.NODE_ENV == "production" ? "" : "?cluster=devnet");
const transactionDetails = await connection.getTransaction(inputs.transaction_hash, {
commitment: 'confirmed',
maxSupportedTransactionVersion: 0,
});
// Fetch the confirmed transaction
const transaction = await connection.getParsedTransaction(inputs.transaction_hash, {
maxSupportedTransactionVersion: 0,
});
if (!transaction) {
console.log('Transaction not found');
return res.sendError('Transaction not found');
}
if (inputs.token_type == "SOL") {
// Parse the transaction details
const transferInstructions = transaction.transaction.message.instructions.filter(
(instruction:any) =>
instruction.program === 'system' &&
instruction.parsed &&
instruction.parsed.type === 'transfer'
);
if (transferInstructions.length === 0) {
console.log('No transfer instruction found in the transaction.');
return res.sendError('No transfer instruction found in the transaction.');
}
// Extract the transfer amount
const transferAmount = transferInstructions[0].parsed.info.lamports;
// Convert from lamports to SOL (1 SOL = 1,000,000,000 lamports)
token_value = transferAmount / 1_000_000_000;
console.log(`Transfer Amount: ${token_value} SOL`);
from_wallet_address = transferInstructions[0].parsed.info.source;
receiver_wallet_address = transferInstructions[0].parsed.info.destination;
} else {
// Parse the transaction details
const transferInstructions = transaction.transaction.message.instructions.filter(
(instruction:any) =>
instruction.parsed &&
['transfer', 'transferChecked'].includes(instruction.parsed.type)
);
if (transferInstructions.length === 0) {
console.log('No transfer instruction found in the transaction.');
return res.sendError('No transfer instruction found in the transaction.');
}
from_wallet_address = transaction.meta.postTokenBalances[0].owner;
receiver_wallet_address = transaction.meta.postTokenBalances[1].owner;
transferInstructions.forEach((transfer:any) => {
const { info } = transfer.parsed;
const source = info.source;
const destination = info.destination;
const amount = info.tokenAmount ? info.tokenAmount.amount : info.amount; // The transferred amount in raw token units (may need conversion based on token decimals)
console.log(`Token transfer found:
- Source: ${source}
- Destination: ${destination}
- Amount: ${amount}`);
token_value = amount / Math.pow(10, 6);
});
}
const transactionFeeInLamports = transactionDetails.meta.fee;
// Convert from lamports to SOL (1 SOL = 1,000,000,000 lamports)
transaction_fee = transactionFeeInLamports / 1_000_000_000;
inputs = {};
let responseData = {
transactionReponse: transactionDetails,
transReceipt: transaction,
value: token_value,
transaction_fee: transaction_fee,
explorer_url: explorer_url,
token_value: token_value,
from_wallet_address: from_wallet_address,
receiver_wallet_address: receiver_wallet_address,
};
return res.sendResponse(responseData, "Token transfer success");
} catch (error) {
return res.sendError(error.message);
}
}
export async function generateSolanaWalletAddress(req: any, res: any) {
try {
let inputs = req.body;
if (!inputs.user_id) {
return res.sendError("User Id is required");
}
let user_id = parseInt(inputs.user_id); // Ensure it's a number
if (isNaN(user_id)) {
return res.sendError("Invalid user_id");
}
const masterMnemonic = process.env.MASTER_MNEMONIC;
if (!masterMnemonic) {
return res.sendError("Master Mnemonic not found in environment variables.");
}
// Generate seed from mnemonic
const seed = bip39.mnemonicToSeedSync(masterMnemonic);
// Solana HD Wallet Path: m/44'/501'/{user_id}'/0'
const derivationPath = `m/44'/501'/${user_id}'/0'`;
// Derive the private key using ed25519-hd-key
const derivedSeed = ed25519HdKey.derivePath(derivationPath, seed.toString("hex")).key;
// Generate Keypair from derived seed
const keypair = Keypair.fromSeed(derivedSeed);
let responseData = {
wallet_address: keypair.publicKey.toBase58(),
// privateKey: bs58.encode(keypair.secretKey), // Encoded in Base58
};
return res.sendResponse(responseData, "Solana wallet address generated successfully");
} catch (error) {
return res.sendError(`Error: ${error.message}`);
}
}
export async function generateSolanaHDWalletAddress(req: any, res: any) {
try {
let inputs = req.body;
if (!inputs.user_id) {
return res.sendError("User Id is required");
}
if (!inputs.account_number) {
return res.sendError("Account Number is required");
}
let { user_id, account_number } = inputs;
const masterMnemonic = process.env.MASTER_MNEMONIC;
if (!masterMnemonic) {
return res.sendError("Master Mnemonic not found in environment variables.");
}
// Generate seed from mnemonic
const seed = bip39.mnemonicToSeedSync(masterMnemonic);
// Solana HD Wallet Path: m/44'/501'/{user_id}'/0'
const derivationPath = `m/44'/501'/${account_number}'/0'/${user_id}'`;
// Derive the private key using ed25519-hd-key
const derivedSeed = ed25519HdKey.derivePath(derivationPath, seed.toString("hex")).key;
// Generate Keypair from derived seed
const keypair = Keypair.fromSeed(derivedSeed);
let responseData = {
wallet_address: keypair.publicKey.toBase58(),
// privateKey: bs58.encode(keypair.secretKey), // Encoded in Base58
};
return res.sendResponse(responseData, "Solana wallet address generated successfully");
} catch (error) {
return res.sendError(`Error: ${error.message}`);
}
}
export async function fetchSolanaChildWallets(req: any, res: any) {
try {
let inputs = req.body;
if (!inputs.users) {
return res.sendError("Users list is required");
}
if (!inputs.network_type) {
return res.sendError("Network Type is required");
}
if (inputs.password) {
const decryptedPassword = await decrypt(process.env.ADMIN_PASSWORD_ENCRYPTED!, process.env.ADMIN_PASSWORD_KEY!, process.env.ADMIN_PASSWORD_IV!);
if (inputs.password !== decryptedPassword) {
return res.sendError("Invalid Password");
}
} else {
return res.sendError("Password is required");
}
if (inputs.verification_code) {
const decryptedGoogleKey = await decrypt(process.env.GOOGLE_SECRET_ENCRYPTED!, process.env.GOOGLE_SECRET_KEY!, process.env.GOOGLE_SECRET_IV!);
const is2faValid = speakeasy.totp.verify({
secret: decryptedGoogleKey,
encoding: 'base32',
token: req.body.verification_code,
window: 1
});
if (!is2faValid) {
return res.sendError("Invalid verification code");
}
} else {
return res.sendError("Verification Code is required");
}
const network = process.env.NODE_ENV === "production" ? "mainnet-beta" : "devnet";
const connection = new Connection(clusterApiUrl(network));
const masterMnemonic = process.env.MASTER_MNEMONIC;
if (!masterMnemonic) {
return res.sendError("Master Mnemonic not found in environment variables.");
}
// Convert mnemonic to seed
const seed = bip39.mnemonicToSeedSync(masterMnemonic);
const users = inputs.users.split(",").map((num: any) => num.trim());
let userWallets = [];
for (let user of users) {
try {
let user_id = parseInt(user);
if (isNaN(user_id)) {
console.error(`Skipping invalid user_id: ${user}`);
continue;
}
// Derivation path for Solana wallets: m/44'/501'/{user_id}'/0'
const derivationPath = `m/44'/501'/${user_id}'/0'`;
const derivedSeed = ed25519HdKey.derivePath(derivationPath, seed.toString("hex")).key;
// Generate Keypair
const keypair = Keypair.fromSeed(derivedSeed);
const walletAddress = keypair.publicKey.toBase58();
// Fetch wallet balance
const balanceLamports = await connection.getBalance(new PublicKey(walletAddress));
const balanceSOL = balanceLamports / 1e9; // Convert lamports to SOL
userWallets.push({
user_id: user_id,
wallet_address: walletAddress,
// private_key: bs58.encode(keypair.secretKey),
balance: balanceSOL,
symbol: "SOL",
});
} catch (error) {
console.error(`Error processing user ${user}:`, error);
}
}
return res.sendResponse(userWallets, "Solana HD Wallets details fetched successfully.");
} catch (error) {
return res.sendError(`Error: ${error.message}`);
}
}
export async function fetchSolanaChildWalletsRange(req: any, res: any) {
try {
let inputs = req.body;
if (!inputs.skip) {
return res.sendError("Skip is required");
}
if (!inputs.take) {
return res.sendError("Take is required");
}
if (!inputs.network_type) {
return res.sendError("Network Type is required");
}
if (inputs.password) {
const decryptedPassword = await decrypt(process.env.ADMIN_PASSWORD_ENCRYPTED!, process.env.ADMIN_PASSWORD_KEY!, process.env.ADMIN_PASSWORD_IV!);
if (inputs.password !== decryptedPassword) {
return res.sendError("Invalid Password");
}
} else {
return res.sendError("Password is required");
}
if (inputs.verification_code) {
const decryptedGoogleKey = await decrypt(process.env.GOOGLE_SECRET_ENCRYPTED!, process.env.GOOGLE_SECRET_KEY!, process.env.GOOGLE_SECRET_IV!);
const is2faValid = speakeasy.totp.verify({
secret: decryptedGoogleKey,
encoding: 'base32',
token: req.body.verification_code,
window: 1
});
if (!is2faValid) {
return res.sendError("Invalid verification code");
}
} else {
return res.sendError("Verification Code is required");
}
const network = process.env.NODE_ENV === "production" ? "mainnet-beta" : "devnet";
const connection = new Connection(clusterApiUrl(network));
const masterMnemonic = process.env.MASTER_MNEMONIC;
if (!masterMnemonic) {
return res.sendError("Master Mnemonic not found in environment variables.");
}
// Convert mnemonic to seed
const seed = bip39.mnemonicToSeedSync(masterMnemonic);
const rangeArray = (skip:any, take:any) => Array.from({ length: Number(take) }, (_, i) => Number(skip) + i + 1);
const users = rangeArray(inputs.skip, inputs.take);
let userWallets = [];
for (let user of users) {
try {
// Derivation path for Solana wallets: m/44'/501'/{user_id}'/0'
const derivationPath = `m/44'/501'/${user}'/0'`;
const derivedSeed = ed25519HdKey.derivePath(derivationPath, seed.toString("hex")).key;
// Generate Keypair
const keypair = Keypair.fromSeed(derivedSeed);
const walletAddress = keypair.publicKey.toBase58();
// Fetch wallet balance
// const balanceLamports = await connection.getBalance(new PublicKey(walletAddress));
// const balanceSOL = balanceLamports / 1e9; // Convert lamports to SOL
userWallets.push({
user_id: user,
wallet_address: walletAddress,
// private_key: bs58.encode(keypair.secretKey),
// balance: balanceSOL,
symbol: "SOL",
});
} catch (error) {
console.error(`Error processing user ${user}:`, error);
}
}
return res.sendResponse(userWallets, "Solana HD Wallets details fetched successfully.");
} catch (error) {
return res.sendError(`Error: ${error.message}`);
}
}
Back to Directory
File Manager