Skip to main content

Custom Heuristics

Overview

Extend the scanner with protocol-specific privacy rules.

Use Cases

  • Privacy protocols detecting SDK misuse
  • DeFi protocols checking MEV exposure
  • DAOs detecting voting pattern leakage
  • Custom compliance policies

Write a Heuristic

import { ScanContext, PrivacySignal } from 'solana-privacy-scanner-core';

export function detectCustomPattern(context: ScanContext): PrivacySignal[] {
const signals: PrivacySignal[] = [];

// Your detection logic
if (/* condition */) {
signals.push({
id: 'custom-pattern',
name: 'Custom Pattern Detected',
severity: 'HIGH',
confidence: 0.85,
reason: 'What was detected',
impact: 'How this affects privacy',
evidence: [{
description: 'Transaction shows pattern X',
severity: 'HIGH',
reference: 'signature123...'
}],
mitigation: 'How to fix'
});
}

return signals;
}

PrivacySignal Fields

All fields required unless marked optional:

FieldTypeDescriptionExample
idstringUnique identifier for this signal type'custom-pattern'
namestringHuman-readable name'Custom Pattern Detected'
severity'LOW' | 'MEDIUM' | 'HIGH'Risk severity level'HIGH'
reasonstringWhy this signal was triggered'Detected repeated pattern X in 5 transactions'
impactstringWhat this means for privacy'Links all transactions to same user identity'
evidenceEvidence[]Supporting data (see below)[{description: '...', reference: 'sig1'}]
mitigationstringHow to fix or reduce this risk'Use separate wallets for different activities'
confidencenumber (optional)Confidence score 0-10.85
categorystring (optional)Signal category'linkability' | 'behavioral' | 'exposure'

Evidence Fields

FieldTypeDescriptionExample
descriptionstringHuman-readable explanation'Transaction A to B shows pattern'
severity'LOW' | 'MEDIUM' | 'HIGH' (optional)Evidence severity'HIGH'
referencestring (optional)Transaction signature, address, etc.'5x7Ym...'
typeEvidenceType (optional)Evidence category'transaction' | 'address' | 'amount' | 'timing' | 'pattern' | 'label'
dataRecord<string, unknown> (optional)Structured supporting data{count: 5, addresses: [...]}

ScanContext Fields

The ScanContext object contains all normalized blockchain data for analysis:

FieldTypeDescription
targetstringAddress/signature being scanned
targetType'wallet' | 'transaction' | 'program'Type of scan
transfersTransfer[]All SOL and SPL token transfers
instructionsNormalizedInstruction[]All program instructions
counterpartiesSet<string>Unique addresses interacted with
labelsMap<string, Label>Known entity labels (CEXs, etc.)
tokenAccountsArray<{mint, address, balance}>Token accounts owned
timeRange{earliest, latest}Time bounds of activity
transactionCountnumberTotal transactions analyzed
Core Fields:
transactionsTransactionMetadata[]Transaction metadata (fee payers, signers, memos)
tokenAccountEventsTokenAccountEvent[]Token account create/close events
pdaInteractionsPDAInteraction[]Program-Derived Address interactions
feePayersSet<string>Unique fee payer addresses
signersSet<string>Unique signer addresses
programsSet<string>Program IDs interacted with

Example: Access fee payers

export function detectCustomPattern(context: ScanContext): PrivacySignal[] {
const signals: PrivacySignal[] = [];

// Check if wallet never pays own fees
if (!context.feePayers.has(context.target)) {
signals.push({
id: 'custom-fee-pattern',
name: 'Custom Fee Analysis',
severity: 'HIGH',
reason: 'Wallet never pays own transaction fees',
impact: 'All transactions linked to external fee payer',
evidence: [{
description: `Fee paid by ${Array.from(context.feePayers).join(', ')}`,
severity: 'HIGH'
}],
mitigation: 'Pay own fees to avoid linkage'
});
}

return signals;
}

Integration

Option 1: Combine with Built-in Signals

import {
collectWalletData,
normalizeWalletData,
evaluateHeuristics,
generateReport,
createDefaultLabelProvider,
RPCClient
} from 'solana-privacy-scanner-core';
import { detectCustomPattern } from './my-heuristic';

// Collect and normalize data
const rpcClient = new RPCClient();
const rawData = await collectWalletData(rpcClient, 'wallet-address');
const labelProvider = createDefaultLabelProvider();
const context = normalizeWalletData(rawData, labelProvider);

// Evaluate built-in heuristics
const builtInSignals = evaluateHeuristics(context);

// Evaluate custom heuristics
const customSignals = detectCustomPattern(context);

// Combine signals
const allSignals = [...builtInSignals, ...customSignals];

// Generate report with combined signals
const report = {
...generateReport(context),
signals: allSignals
};

Option 2: Custom-Only Analysis

import {
collectWalletData,
normalizeWalletData,
createDefaultLabelProvider,
RPCClient
} from 'solana-privacy-scanner-core';
import { detectCustomPattern } from './my-heuristic';

const rpcClient = new RPCClient();
const rawData = await collectWalletData(rpcClient, 'wallet-address');
const labelProvider = createDefaultLabelProvider();
const context = normalizeWalletData(rawData, labelProvider);

// Only run your custom heuristic
const customSignals = detectCustomPattern(context);

// Build custom report
const report = {
version: '1.0.0',
timestamp: Date.now(),
target: context.target,
targetType: context.targetType,
signals: customSignals,
overallRisk: customSignals.some(s => s.severity === 'HIGH') ? 'HIGH' : 'LOW'
};

See Devtools for testing custom heuristics.