diff --git a/src/index.ts b/src/index.ts index 1d64f95..8180394 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,13 +3,14 @@ import { config } from './config'; import { logger } from './utils/logger'; import transactionRoutes from './routes/transactionRoutes'; import { getConnection } from './utils/database'; -import { getTransaction } from './utils/explorer'; +import { getTransaction, getTransactionRaw } from './utils/explorer'; import { TransactionValidationService } from './services/transactionValidationService'; +import { CLUSTER_API } from './data'; logger.info("Starting server..."); const app = express(); const PORT = config.port; logger.info(`port: ${PORT}`); -logger.info(`nodeEnv: ${config.nodeEnv}`); +logger.info(`nodeEnv: ${config.nodeEnv} : ${CLUSTER_API}`); logger.info(`logLevel: ${config.logLevel}`); getConnection().then(() => { @@ -42,9 +43,19 @@ app.get('/health', (_req: Request, res: Response) => { }); app.get('/solana/tx/:txHash', async (req: Request, res: Response) => { - const txHash = req.params.txHash; - const transaction = await getTransaction(txHash); - res.json(transaction); + + const {raw} = req.query; + + if(raw=="true"){ + const tx = await getTransactionRaw(req.params.txHash); + res.json(tx); + }else{ + const txHash = req.params.txHash; + const transaction = await getTransaction(txHash); + res.json(transaction); + } + + }); // Validation service control endpoints diff --git a/src/utils/explorer.ts b/src/utils/explorer.ts index e0b0410..2041b5b 100644 --- a/src/utils/explorer.ts +++ b/src/utils/explorer.ts @@ -1,15 +1,27 @@ import { CLUSTER_API } from "../data"; -import { Connection } from "@solana/web3.js"; +import { Connection, ParsedTransactionWithMeta, PublicKey } from "@solana/web3.js"; import { TransferData } from "../types"; +import { logger } from "./logger"; -export async function getTransaction(txHash: string) { - const connection = new Connection(CLUSTER_API, { commitment: 'finalized' }); +export async function getTransactionRaw(txHash: string) { + const connection = new Connection(CLUSTER_API, { commitment: 'confirmed' }); const transaction = await connection.getParsedTransaction(txHash, { maxSupportedTransactionVersion: 0, }); - const transaction_formatted = extractTokenTransferData(transaction); + return transaction; +} + +export async function getTransaction(txHash: string) { + const transaction = await getTransactionRaw(txHash); + + if (!transaction) { + logger.error(`Blockchain couldn't find transaction: ${txHash}`); + return null; + } + + const transaction_formatted = await extractTokenTransferData(transaction); return transaction_formatted; } @@ -41,12 +53,38 @@ export function extractNativeSolTransferData(transaction: any): TransferData | n } } -export function extractSplTokenTransferData(transaction: any): TransferData | null { +async function getAtaOwner(ataAddress: string): Promise { + try { + const connection = new Connection(CLUSTER_API, { commitment: 'confirmed' }); + const ataPublicKey = new PublicKey(ataAddress); + const accountInfo = await connection.getAccountInfo(ataPublicKey); + + if (!accountInfo) { + logger.warn(`Could not find account info for ATA: ${ataAddress}`); + return ataAddress; // Fallback to ATA address if we can't get owner + } + + // The owner is stored in the account data at a specific offset + // For token accounts, the owner is at offset 32 + const ownerBytes = accountInfo.data.slice(32, 64); + const owner = new PublicKey(ownerBytes); + + return owner.toString(); + } catch (error) { + logger.error(`Error getting ATA owner: ${error}`); + return ataAddress; // Fallback to ATA address if there's an error + } +} + +export async function extractSplTokenTransferData(transaction: any): Promise { try { // Check if this is an SPL token transfer const instructions = transaction.transaction.message.instructions; + + // Try to find either transferChecked or transfer instruction const tokenTransferInstruction = instructions.find((instruction: any) => - instruction.program === "spl-token" && instruction.parsed?.type === "transferChecked" + instruction.program === "spl-token" && + (instruction.parsed?.type === "transferChecked" || instruction.parsed?.type === "transfer") ); if (!tokenTransferInstruction) { @@ -55,11 +93,37 @@ export function extractSplTokenTransferData(transaction: any): TransferData | nu const transferInfo = tokenTransferInstruction.parsed.info; + // For transferChecked type + if (tokenTransferInstruction.parsed.type === "transferChecked") { + const receiverOwner = await getAtaOwner(transferInfo.destination); + + return { + sender: transferInfo.authority, + receiver: receiverOwner, + amount: parseInt(transferInfo.tokenAmount.amount), + mint: transferInfo.mint, + time: transaction.blockTime, + blockhash: transaction.transaction.message.recentBlockhash + }; + } + + logger.debug(`It's not a transferChecked`); + + // For transfer type + const tokenBalances = transaction.meta.preTokenBalances; + const mint = tokenBalances[0]?.mint; + + if (!mint) { + return null; + } + + const receiverOwner = await getAtaOwner(transferInfo.destination); + return { - sender: transferInfo.source, - receiver: transferInfo.destination, - amount: parseInt(transferInfo.tokenAmount.amount), - mint: transferInfo.mint, + sender: transferInfo.authority, + receiver: receiverOwner, + amount: parseInt(transferInfo.amount), + mint: mint, time: transaction.blockTime, blockhash: transaction.transaction.message.recentBlockhash }; @@ -69,7 +133,7 @@ export function extractSplTokenTransferData(transaction: any): TransferData | nu } } -export function extractTokenTransferData(transaction: any): TransferData | null { +export async function extractTokenTransferData(transaction: ParsedTransactionWithMeta): Promise { // Try to extract native SOL transfer first const nativeSolData = extractNativeSolTransferData(transaction); if (nativeSolData) { @@ -77,10 +141,12 @@ export function extractTokenTransferData(transaction: any): TransferData | null } // Try to extract SPL token transfer - const splTokenData = extractSplTokenTransferData(transaction); + const splTokenData = await extractSplTokenTransferData(transaction); if (splTokenData) { return splTokenData; } + logger.error(`Error extracting token transfer data: ${transaction}`); + return null; // Not a recognized token transfer } \ No newline at end of file