npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

aleo-record-scanner

v1.1.1

Published

A config-driven Aleo blockchain scanner that surfaces encrypted records from program transitions

Readme

Aleo Record Scanner

A config-driven TypeScript package for scanning the Aleo blockchain and surfacing encrypted (and optionally decrypted) records from program transitions. Perfect for building indexers, monitoring applications, and data pipelines on top of Aleo.

Features

  • Multi-Program Filtering: Scan multiple programs and functions in a single scanner instance
  • Encrypted Record Surfacing: Emits every encrypted record found in matching program transitions
  • Optional Decryption: Decrypt records on-the-fly using an Aleo view key
  • Batch Processing: Efficiently scan large block ranges with configurable batch sizes
  • Event-Driven: Receive real-time notifications via a typed event emitter
  • Retry Logic: Built-in retry with exponential backoff on transient API failures
  • Type-Safe: Full TypeScript support with comprehensive type definitions
  • Progressive Scanning: Resume scanning from any block height
  • Configurable Polling: Automatic detection of new blocks with adjustable polling intervals

Installation

npm install aleo-record-scanner

Requirements:

  • Node.js >= 18.0.0
  • npm >= 7.0.0

Quick Start

Basic Usage

import { RecordScanner } from 'aleo-record-scanner';

const scanner = new RecordScanner({
  baseUrl: 'https://api.explorer.provable.com/v1/testnet',
  programs: [
    { programName: 'token_v1.aleo', functionNames: ['transfer_private'] }
  ],
  startBlockHeight: 0,
  pollingInterval: 5000,  // Poll every 5 seconds
  batchAmount: 100        // Process 100 blocks at a time
});

scanner.on('record', (record) => {
  console.log('Found record:', record);
});

scanner.on('progress', ({ currentBlock, latestBlock }) => {
  console.log(`Progress: ${currentBlock}/${latestBlock}`);
});

scanner.on('error', (err) => {
  console.error('Scanner error:', err);
});

await scanner.start();

Configuration

Required Properties

| Property | Type | Description | |----------|------|-------------| | programs | ProgramFilter[] | One or more program/function filters (see below) | | baseUrl | string | RPC base URL of the Aleo node to query | | startBlockHeight | number | Block height to start scanning from (inclusive) | | pollingInterval | number | Milliseconds between polls after catching up to chain tip | | batchAmount | number | Number of blocks to fetch per batch request |

ProgramFilter

Each entry in programs describes one program to watch:

| Property | Type | Description | |----------|------|-------------| | programName | string | Aleo program name (prefix match), e.g. "token_v1.aleo" | | functionNames | string[] | (optional) Function names to include. If omitted, all functions are included |

Optional Properties

| Property | Type | Default | Description | |----------|------|---------|-------------| | maxRetries | number | 5 | Maximum HTTP retry attempts on transient failures | | delayBetweenBatches | number | 300 | Milliseconds to wait between consecutive batch fetches | | decrypt | boolean | false | When true, decrypt records using viewKey before emitting | | viewKey | string | undefined | Aleo view key required when decrypt is true |

Configuration Examples

Single Program, Single Function

const config = {
  baseUrl: 'https://api.explorer.provable.com/v1/testnet',
  programs: [
    { programName: 'token_v1.aleo', functionNames: ['transfer_private'] }
  ],
  startBlockHeight: 0,
  pollingInterval: 5000,
  batchAmount: 50
};

Multiple Programs, Multiple Functions

const config = {
  baseUrl: 'https://api.explorer.provable.com/v1/mainnet',
  programs: [
    { programName: 'abc.aleo', functionNames: ['aaa', 'bbb'] },
    { programName: 'zyx.aleo', functionNames: ['xyz'] }
  ],
  startBlockHeight: 156000,
  pollingInterval: 10000,
  batchAmount: 100,
  maxRetries: 10,
  delayBetweenBatches: 500
};

Scan All Functions of a Program

// Omit functionNames to match every function in the program
const config = {
  baseUrl: 'https://api.explorer.provable.com/v1/testnet',
  programs: [
    { programName: 'my_program.aleo' }
  ],
  startBlockHeight: 0,
  pollingInterval: 3000,
  batchAmount: 25
};

With Record Decryption

When decrypt is true and a viewKey is provided, every encrypted record is decrypted before being emitted. The FoundRecord will contain both encryptedRecords and decryptedRecords.

const config = {
  baseUrl: 'https://api.explorer.provable.com/v1/testnet',
  programs: [
    { programName: 'token_v1.aleo', functionNames: ['transfer_private'] }
  ],
  startBlockHeight: 0,
  pollingInterval: 5000,
  batchAmount: 50,
  decrypt: true,
  viewKey: 'AViewKey1...'  // your Aleo view key
};

const scanner = new RecordScanner(config);

scanner.on('record', (record) => {
  console.log('Encrypted:', record.encryptedRecords);
  console.log('Decrypted:', record.decryptedRecords); // populated when decrypt: true
});

await scanner.start();

Note: If a record cannot be decrypted with the provided view key (i.e. it belongs to a different address), decryption fails silently for that record and an error event is emitted. The scanner continues processing.

Records

FoundRecord Shape

interface FoundRecord {
  /** All encrypted record ciphertexts from the transition */
  encryptedRecords: string[];

  /**
   * Decrypted plaintext records — only present when `decrypt: true`
   * and a valid `viewKey` is configured.
   */
  decryptedRecords?: Record<string, unknown>[];

  txHash: string;       // Transaction ID containing the record
  programId: string;    // Program ID the transition belongs to
  functionName: string; // Function name of the transition
  blockHeight: number;  // Block height where the record was found
}

Collecting Records

const foundRecords: FoundRecord[] = [];

scanner.on('record', (record) => {
  foundRecords.push(record);
  console.log(`Found ${foundRecords.length} record(s):`, record.txHash);
});

await scanner.start();
console.log(`Total records found: ${foundRecords.length}`);

Persisting Records

import * as fs from 'fs';

scanner.on('record', (record) => {
  fs.appendFileSync('records.jsonl', JSON.stringify(record) + '\n');
});

API Reference

RecordScanner Class

Constructor

constructor(config: ScannerConfig)

Methods

async start(): Promise<void>

Starts the scanner. Resolves only when stop() is called. Never rejects — errors are emitted via the "error" event. Throws if the scanner is already running.

stop(): void

Signals the scanner to stop after the current batch completes.

const scanTask = scanner.start();
setTimeout(() => scanner.stop(), 60_000);
await scanTask;

Events

record

Emitted for every encrypted record found in a matching transition.

scanner.on('record', (record: FoundRecord) => {
  console.log('Found record:', record);
});
progress

Emitted after each batch with the current scan progress.

scanner.on('progress', ({ currentBlock, latestBlock }: ScannerProgress) => {
  const pct = ((currentBlock / latestBlock) * 100).toFixed(2);
  console.log(`Progress: ${pct}% (Block ${currentBlock}/${latestBlock})`);
});
error

Emitted on non-fatal or fatal errors. The scanner attempts to recover and continue.

scanner.on('error', (err: Error) => {
  console.error('Scanner error:', err.message);
});

Usage Examples

Example 1: Multi-Program Scanner with Decryption

import { RecordScanner } from 'aleo-record-scanner';

const scanner = new RecordScanner({
  baseUrl: 'https://api.explorer.provable.com/v1/mainnet',
  programs: [
    { programName: 'abc.aleo', functionNames: ['aaa', 'bbb'] },
    { programName: 'zyx.aleo', functionNames: ['xyz'] }
  ],
  startBlockHeight: 100000,
  pollingInterval: 5000,
  batchAmount: 50,
  decrypt: true,
  viewKey: 'AViewKey1...'
});

scanner.on('record', (record) => {
  console.log(`[${record.programId}::${record.functionName}] tx: ${record.txHash}`);
  console.log('  Encrypted:', record.encryptedRecords);
  console.log('  Decrypted:', record.decryptedRecords);
});

scanner.on('progress', ({ currentBlock, latestBlock }) => {
  console.log(`${currentBlock}/${latestBlock}`);
});

scanner.on('error', (err) => {
  console.error('Error:', err.message);
});

await scanner.start();

Example 2: Resume from Checkpoint

import * as fs from 'fs';
import { RecordScanner } from 'aleo-record-scanner';

let lastBlock = 0;
if (fs.existsSync('checkpoint.json')) {
  const checkpoint = JSON.parse(fs.readFileSync('checkpoint.json', 'utf-8'));
  lastBlock = checkpoint.blockHeight;
  console.log(`Resuming from block ${lastBlock}`);
}

const scanner = new RecordScanner({
  baseUrl: 'https://api.explorer.provable.com/v1/testnet',
  programs: [{ programName: 'myprogram.aleo' }],
  startBlockHeight: lastBlock,
  pollingInterval: 5000,
  batchAmount: 100
});

scanner.on('record', (record) => {
  console.log('Found record at block', record.blockHeight);
});

scanner.on('progress', ({ currentBlock }) => {
  fs.writeFileSync('checkpoint.json', JSON.stringify({ blockHeight: currentBlock }, null, 2));
});

scanner.on('error', (err) => {
  console.error('Scan error:', err);
});

await scanner.start();

Error Handling

The scanner emits errors via the error event and continues scanning, making it resilient for long-running processes.

| Error | Cause | Recovery | |-------|-------|----------| | "Failed to fetch latest block height" | Network or API issue | Retries with backoff, then re-polls | | "Failed to fetch blocks X–Y" | Temporary API failure | Breaks inner loop, retries the range | | "Failed to decrypt record in tx …" | View key mismatch or corrupt ciphertext | Emits error, inserts {} placeholder, continues |

TypeScript Support

All public types are exported:

import {
  RecordScanner,
  RecordScannerEvents,
  ScannerConfig,
  ProgramFilter,
  FoundRecord,
  ScannerProgress
} from 'aleo-record-scanner';

Building from Source

npm install
npm run build
npm run build:watch  # rebuild on file changes
npm run clean

License

MIT © Prabin Baral

Support

For issues, questions, or feature requests, open an issue on GitHub.