Development Guide
Contributing to the Solana Privacy Scanner codebase.
Project Structure
solana-privacy-scanner/
├── docs/ # VitePress documentation (not published to npm)
├── packages/
│ ├── core/ # solana-privacy-scanner-core
│ │ ├── src/
│ │ │ ├── collectors/ # RPC data collection
│ │ │ ├── normalizer/ # Data transformation
│ │ │ ├── heuristics/ # Risk detection logic
│ │ │ ├── scanner/ # Report generation
│ │ │ ├── labels/ # Known entity database
│ │ │ ├── rpc/ # RPC client wrapper
│ │ │ └── types/ # TypeScript definitions
│ │ └── dist/ # Built output (published to npm)
│ │
│ ├── cli/ # @solana-privacy-scanner/cli
│ │ ├── src/
│ │ │ ├── commands/ # CLI command handlers
│ │ │ └── formatter.ts # Report formatting
│ │ └── dist/ # Built output (published to npm)
│ │
│ ├── server/ # HTTP API (future)
│ └── web/ # Web UI (future)
│
├── package.json # Root workspace config
└── vitest.config.ts # Test configuration
Setup
Prerequisites
- Node.js 20+
- npm 9+
- A Solana RPC endpoint (Helius recommended)
Installation
# Clone the repo
git clone https://github.com/yourusername/solana-privacy-scanner
cd solana-privacy-scanner
# Install dependencies
npm install
# Create .env.local
echo "SOLANA_RPC=https://your-rpc-url.com" > .env.local
# Build all packages
npm run build
# Run tests
npm test
Development Workflow
Make Changes
# Work on core library
cd packages/core
npm run dev # Watch mode
# Work on CLI
cd packages/cli
npm run dev # Watch mode
Run Tests
# Run all tests
npm test
# Run tests for specific package
cd packages/core
npm test
# Run tests in CI mode (no watch)
npm test -- --run
# Run tests with coverage
npm test -- --coverage
The project uses Vitest for testing. All core functionality has comprehensive test coverage including:
- Data Normalization Tests (
normalizer/index.test.ts) - Edge cases for handling undefined/null data - Data Collection Tests (
collectors/index.test.ts) - RPC failure scenarios and error handling - Heuristic Tests (
heuristics/index.test.ts) - Privacy pattern detection accuracy - Scanner Tests (
scanner/index.test.ts) - End-to-end report generation
Build for Production
# Build all packages
npm run build
# Build specific package
cd packages/core
npm run build
Adding a New Heuristic
- Create the heuristic file:
// packages/core/src/heuristics/my-heuristic.ts
import type { ScanContext, RiskSignal } from '../types/index.js';
export function detectMyPattern(context: ScanContext): RiskSignal | null {
// Your detection logic
if (patternDetected) {
return {
id: 'my-pattern-id',
name: 'My Pattern Name',
severity: 'HIGH',
reason: 'Brief explanation of what was detected',
impact: 'Why this matters for privacy',
evidence: [...],
mitigation: 'How to improve',
confidence: 0.85
};
}
return null;
}
- Export from index:
// packages/core/src/heuristics/index.ts
export { detectMyPattern } from './my-heuristic.js';
- Add to scanner:
// packages/core/src/scanner/index.ts
import { detectMyPattern } from '../heuristics/index.js';
const HEURISTICS = [
// ... existing heuristics
detectMyPattern,
];
- Write tests:
// packages/core/src/heuristics/index.test.ts
describe('detectMyPattern', () => {
it('should detect the pattern', () => {
const context = createMockContext();
const signal = detectMyPattern(context);
expect(signal).toBeTruthy();
expect(signal?.severity).toBe('HIGH');
});
it('should return null when pattern not present', () => {
const context = createCleanContext();
const signal = detectMyPattern(context);
expect(signal).toBeNull();
});
it('should handle edge cases gracefully', () => {
const context = createEdgeCaseContext();
expect(() => detectMyPattern(context)).not.toThrow();
});
});
- Run tests before committing:
npm test # Make sure all tests pass
Package Publishing
What Gets Published
Core package (solana-privacy-scanner-core):
"files": [
"dist/**/*", // Built JS/TS files
"README.md" // Package readme
]
CLI package (@solana-privacy-scanner/cli):
"files": [
"dist/index.js", // Single bundled CLI file
"README.md"
]
What Does NOT Get Published
- Source TypeScript files (
src/) - Tests (
*.test.ts) - Documentation (
docs/) - Build scripts (
build.js) - Development configs
Publishing Process
# 1. Ensure all tests pass
npm test -- --run
# 2. Update version in package.json
cd packages/core
npm version patch # or minor, major
# 3. Build
npm run build
# 4. Run tests again on built package
npm test -- --run
# 5. Test the package locally
npm pack
# Inspect the .tgz file contents
# 6. Publish
npm publish --access public
# Repeat for CLI
cd ../cli
npm version patch
npm run build
npm publish --access public
::: tip Always run tests before publishing to avoid publishing broken code. The test suite includes comprehensive checks for edge cases and error handling. :::
Testing
Unit Tests
Test individual functions and components:
# Run all tests
npm test
# Run tests for specific file
npm test -- heuristics
# Run in watch mode
npm test
# Run without watch (CI mode)
npm test -- --run
Writing Tests
The project uses Vitest with a focus on:
- Edge Case Coverage - Test undefined, null, empty arrays, malformed data
- Error Handling - Verify graceful failures (no crashes)
- Deterministic Output - Same input always produces same output
Example test structure:
import { describe, it, expect } from 'vitest';
import { myFunction } from './index.js';
describe('myFunction', () => {
it('should handle normal input', () => {
const result = myFunction(validInput);
expect(result).toBe(expectedOutput);
});
it('should handle empty input', () => {
const result = myFunction([]);
expect(result).toEqual([]);
});
it('should handle undefined gracefully', () => {
expect(() => myFunction(undefined as any)).not.toThrow();
});
it('should handle null gracefully', () => {
expect(() => myFunction(null as any)).not.toThrow();
});
});
Integration Tests
Test the full pipeline:
# Test with real data using examples
cd examples
npm install
npm run wallet # Test wallet scanning
npm run transaction # Test transaction scanning
npm run program # Test program scanning
Integration Tests
Test the full pipeline:
# Test with real data using examples
cd examples
npm install
npm run wallet # Test wallet scanning
npm run transaction # Test transaction scanning
npm run program # Test program scanning
Test Coverage
View test coverage reports:
npm test -- --coverage
Aim for:
- >80% line coverage for new code
- 100% coverage for critical paths (data normalization, error handling)
- Edge case tests for all public APIs
Mock RPC Client
For unit tests, use the mock RPC client:
import { vi } from 'vitest';
import type { RPCClient } from '../rpc/client.js';
function createMockRPCClient(overrides = {}): RPCClient {
return {
getSignaturesForAddress: vi.fn().mockResolvedValue([]),
getTransaction: vi.fn().mockResolvedValue(null),
getTransactions: vi.fn().mockResolvedValue([]),
...overrides,
} as unknown as RPCClient;
}
// Use in tests
const mockClient = createMockRPCClient({
getSignaturesForAddress: vi.fn().mockResolvedValue([
{ signature: 'sig1', slot: 100 }
]),
});
Test with Real Data
# Always test with real Solana data before releasing
export SOLANA_RPC=your-rpc
npm test
Code Style
- TypeScript strict mode - No
anytypes - ESM only - Use
.jsextensions in imports - Descriptive names -
detectCounterpartyReuse, notcheckCR - Comments - Explain "why", not "what"
- Error handling - Always handle RPC failures gracefully
Documentation
Update Docs
# Run docs dev server
npm run docs:dev
# Build docs
npm run docs:build
# Preview built docs
npm run docs:preview
Adding Docs Pages
- Create
.mdfile indocs/ - Add to sidebar in
docs/.vitepress/config.ts - Test locally with
npm run docs:dev
Git Workflow
# Create feature branch
git checkout -b feature/my-feature
# Make changes and commit
git add .
git commit -m "feat: add new heuristic for X"
# Push and create PR
git push origin feature/my-feature
Commit Message Format
type(scope): description
feat: new feature
fix: bug fix
docs: documentation changes
test: test additions/changes
chore: maintenance tasks
Troubleshooting
Build Failures
# Clean and rebuild
npm run clean
npm install
npm run build
Test Failures
# Check RPC connectivity
curl -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}' \
$SOLANA_RPC
Type Errors
# Run type checker
cd packages/core
npm run type-check
Resources
Getting Help
- Open an issue on GitHub
- Check existing documentation
- Review test files for examples