const WebSocket = require('ws');
const bs58 = require('bs58').default;
/* ───────────────────── 1. CONFIG ──────────────────────────── */
const API_KEY = process.env.HELIUS_API_KEY || (() => { throw new Error('Set HELIUS_API_KEY'); })();
const HELIUS_WS = `wss://mainnet.helius-rpc.com?api-key=${API_KEY}`;
const HELIUS_RPC = `https://mainnet.helius-rpc.com/?api-key=${API_KEY}`;
const DCA_PROGRAM_ID = 'DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M';
/* ───────────────────── 2. BINARY DECODER ──────────────────── */
function decodeOpenDcaV2(base58Data) {
const buf = Buffer.from(bs58.decode(base58Data));
return {
appIdx: buf.readBigUInt64LE(8), // Application Index
inAmount: buf.readBigUInt64LE(16), // Input Amount
perCycle: buf.readBigUInt64LE(24), // Per Cycle
interval: buf.readBigUInt64LE(32) // Interval
};
}
const TOKEN_META = new Map(); // mint → { symbol, decimals }
/**
* Fetch symbol & decimals for a mint once then cache.
* Uses Helius getAsset DAS method: https://www.helius.dev/docs/api-reference/das/getasset
*/
async function getMeta(mint) {
if (TOKEN_META.has(mint)) return TOKEN_META.get(mint);
const body = {
jsonrpc: '2.0',
id: 'meow',
method: 'getAsset',
params: { id: mint, displayOptions: { showFungible: true } }
};
const { result } = await fetch(HELIUS_RPC, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
}).then(r => r.json());
const tokenInfo = result.token_info || {};
const metadata = { symbol: tokenInfo.symbol || '?', decimals: tokenInfo.decimals ?? 0 };
TOKEN_META.set(mint, metadata);
return metadata;
}
/* ───────────────────── 4. PRETTY HELPERS ──────────────────── */
function formatTimestamp(unixSeconds) {
return new Date(Number(unixSeconds) * 1_000)
.toISOString()
.replace('T', ' ')
.replace('.000Z', ' UTC');
}
function formatInterval(seconds) {
if (seconds % 86_400 === 0) return `every ${seconds / 86_400}d`;
if (seconds % 3_600 === 0) return `every ${seconds / 3_600}h`;
if (seconds % 60 === 0) return `every ${seconds / 60}m`;
return `every ${seconds}s`;
}
function formatAmount(raw, decimals, symbol) {
const ui = Number(raw) / 10 ** decimals;
return `${ui} ${symbol}`;
}
/* ───────────────────── 5. WEBSOCKET SETUP ─────────────────── */
const ws = new WebSocket(HELIUS_WS);
ws.on('open', () => {
ws.send(JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'transactionSubscribe',
params: [
{ failed: false, accountInclude: [DCA_PROGRAM_ID] },
{
commitment: 'confirmed',
encoding: 'jsonParsed',
transactionDetails: 'full',
maxSupportedTransactionVersion: 0
}
]
}));
setInterval(() => ws.ping(), 10_000);
});
/* ───────────────────── 6. MAIN MESSAGE HANDLER ────────────── */
ws.on('message', async raw => {
const payload = JSON.parse(raw);
const result = payload.params?.result;
if (!result) return;
// Look for the `OpenDcaV2` log message
const logs = result.transaction.meta.logMessages || [];
if (!logs.some(l => l.includes('OpenDcaV2'))) return;
// loop through all instructions in the transaction to find the DCA instruction
for (const ix of result.transaction.transaction.message.instructions) {
if (ix.programId !== DCA_PROGRAM_ID) continue;
try {
// 1) decode binary payload
const d = decodeOpenDcaV2(ix.data);
// 2) fetch token symbols / decimals (cached)
const [inMeta, outMeta] = await Promise.all([
getMeta(ix.accounts[3]), // input mint
getMeta(ix.accounts[4]) // output mint
]);
// 3) create a nice looking table
console.table({
user: ix.accounts[2],
pair: `${inMeta.symbol} → ${outMeta.symbol}`,
opened: formatTimestamp(d.appIdx),
'total in': formatAmount(d.inAmount, inMeta.decimals, inMeta.symbol),
'per cycle': formatAmount(d.perCycle, inMeta.decimals, inMeta.symbol),
interval: formatInterval(Number(d.interval))
});
} catch (e) {}
}
});
ws.on('error', console.error);
ws.on('close', () => process.exit(1));