Skip to content

Instantly share code, notes, and snippets.

@zhe-t
Created January 18, 2025 23:14
Show Gist options
  • Save zhe-t/e8216fe2ec183322eecb4c884b394e4e to your computer and use it in GitHub Desktop.
Save zhe-t/e8216fe2ec183322eecb4c884b394e4e to your computer and use it in GitHub Desktop.
HeliusWebhook.ts
import { Router, Request, Response } from 'express';
import { db } from '../index.js';
import { SolanaTransaction, UnlinkedSolanaTransaction } from '../../../lib/dist/index.js';
export interface TransactionUpdatedEvent {
accountData?: AccountDatum[];
description?: string;
events?: any;
fee?: number;
feePayer?: string;
instructions?: Instruction[];
nativeTransfers?: any[];
signature?: string;
slot?: number;
source?: string;
timestamp?: number;
tokenTransfers?: TokenTransfer[];
transactionError?: null;
type?: string;
}
export interface AccountDatum {
account?: string;
nativeBalanceChange?: number;
tokenBalanceChanges?: TokenBalanceChange[];
}
export interface TokenBalanceChange {
mint?: string;
rawTokenAmount?: RawTokenAmount;
tokenAccount?: string;
userAccount?: string;
}
export interface RawTokenAmount {
decimals?: number;
tokenAmount?: string;
}
export interface Instruction {
accounts?: string[];
data?: string;
innerInstructions?: any[];
programId?: string;
}
export interface TokenTransfer {
fromTokenAccount?: string;
fromUserAccount?: string;
mint?: string;
toTokenAccount?: string;
toUserAccount?: string;
tokenAmount?: number;
tokenStandard?: string;
}
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const getUSDCTransfers = (event: TransactionUpdatedEvent): TokenTransfer[] => {
return event.tokenTransfers?.filter(transfer => transfer.mint === USDC_MINT) ?? [];
}
export const webhooksRouter = Router();
enum TransferType {
DEPOSIT = 'deposit',
WITHDRAWAL = 'withdrawal'
}
webhooksRouter.post('/helius', async (req: Request, res: Response) => {
async function processUSDCTransfers(event: TransactionUpdatedEvent) {
const baseTransfers = extractTransferDetails(event);
const processedTransfers = await addWalletInfo(baseTransfers);
for (const transfer of processedTransfers) {
await handleTransaction(transfer);
}
for (const transfer of baseTransfers) {
await handleUnlinkedTransaction(transfer);
}
return processedTransfers;
}
function extractTransferDetails(event: TransactionUpdatedEvent): Omit<UnlinkedSolanaTransaction, "id" | "createdAt" | "updatedAt">[] {
return getUSDCTransfers(event).map((transfer) => ({
fromAccount: transfer.fromUserAccount,
toAccount: transfer.toUserAccount,
amount: transfer.tokenAmount,
fromTokenAccount: transfer.fromTokenAccount,
toTokenAccount: transfer.toTokenAccount,
mint: transfer.mint,
signature: event.signature,
feePayer: event.feePayer,
timestamp: event.timestamp,
slot: event.slot,
type: event.type
}));
}
async function addWalletInfo(transfers: Omit<UnlinkedSolanaTransaction, "id" | "createdAt" | "updatedAt">[]): Promise<Omit<SolanaTransaction, "id" | "createdAt" | "updatedAt">[]> {
const processedTransfers: Omit<SolanaTransaction, "id" | "createdAt" | "updatedAt">[] = [];
for (const transfer of transfers) {
const toWallet = await db.getWalletByAddress(transfer.toAccount);
const fromWallet = await db.getWalletByAddress(transfer.fromAccount);
if (toWallet) {
processedTransfers.push({
...transfer,
userId: toWallet.userId,
walletId: toWallet.id,
transactionType: TransferType.DEPOSIT
});
}
if (fromWallet) {
processedTransfers.push({
...transfer,
userId: fromWallet.userId,
walletId: fromWallet.id,
transactionType: TransferType.WITHDRAWAL
});
}
}
return processedTransfers.filter(Boolean);
}
async function handleTransaction(transaction: Omit<SolanaTransaction, "id" | "createdAt" | "updatedAt">) {
const existingTransaction = await db.getSolanaTransactionBySignature(transaction.signature);
if (existingTransaction) {
return;
}
await db.addSolanaTransaction(transaction);
}
async function handleUnlinkedTransaction(transaction: Omit<UnlinkedSolanaTransaction, "id" | "createdAt" | "updatedAt">) {
const existingTransaction = await db.getUnlinkedSolanaTransactionBySignature(transaction.signature);
if (existingTransaction) {
return;
}
await db.addUnlinkedSolanaTransaction(transaction);
}
const events = req.body as TransactionUpdatedEvent[];
for (const event of events) {
const processedTransfers = await processUSDCTransfers(event);
console.log(processedTransfers);
}
res.status(200).send('OK');
});
// webhooksRouter.get('/helius/create', async (req: Request, res: Response) => {
// const heliusClient = HeliusAPIClient.builder()
// .withApiKey(HELIUS_API_KEY)
// .build();
// const allAddresses = await db.getAllSolanaAddresses();
// const addresses = allAddresses.map(address => address.address);
// const { webhookID } = await heliusClient.createWebhook({
// webhookURL: 'https://api.betmore.fun/api/webhooks/helius',
// transactionTypes: ['TRANSFER'],
// accountAddresses: addresses,
// webhookType: 'enhanced'
// });
// await db.createHeliusWebhook(webhookID);
// res.status(200).json({ webhookId: webhookID });
// });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment