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

@notemine/wrapper

v0.1.7

Published

Nostr note miner wrapper for notemine wasm-bindgen

Readme

@notemine/wrapper

npm License: MIT build docs

A TypeScript wrapper for @notemine/core that provides a high-level, user-friendly API for mining Nostr notes with proof-of-work.

Overview

@notemine/wrapper simplifies the process of mining Nostr notes by:

  • Managing Web Workers automatically for parallel mining
  • Providing RxJS observables for real-time progress tracking
  • Bundling WASM as inline base64 within inline workers for hassle-free deployment
  • Offering a clean, Promise-based API with TypeScript support
  • Tracking mining statistics and performance metrics

Features

  • Automatic Worker Management: Spawns and manages multiple Web Workers based on available CPU cores
  • Real-time Progress Tracking: RxJS observables for monitoring hash rate, best PoW, and mining progress
  • Pause & Resume: Pause mining operations and resume from the exact same state
  • State Persistence: Save and restore mining state across page refreshes or sessions
  • Dynamic Worker Scaling: Resume mining with a different number of workers
  • Zero Configuration: Works out of the box with modern bundlers (Vite, Webpack, Rollup, etc.)
  • TypeScript Support: Fully typed API with comprehensive interfaces
  • Cancellable Operations: Stop mining at any time with proper cleanup
  • Performance Metrics: Track hash rates, total hashes, and mining efficiency
  • Framework Agnostic: Works with React, Vue, Svelte, Angular, or vanilla JavaScript

Installation

npm install @notemine/wrapper @notemine/core rxjs
# or
pnpm install @notemine/wrapper @notemine/core rxjs
# or  
yarn add @notemine/wrapper @notemine/core rxjs

Peer Dependencies

  • @notemine/core: The WASM core mining module
  • rxjs: For reactive programming patterns

Basic Usage

import { Notemine } from '@notemine/wrapper';

// Create a new miner instance
const notemine = new Notemine({
  content: 'Hello, Nostr!',
  pubkey: 'your-public-key-here',
  difficulty: 21,
  numberOfWorkers: navigator.hardwareConcurrency || 4,
  tags: [['t', 'intro']]
});

// Subscribe to progress updates
const progressSub = notemine.progress$.subscribe(progress => {
  console.log(`Worker ${progress.workerId}: ${progress.hashRate} H/s`);
});

// Subscribe to success event
const successSub = notemine.success$.subscribe(({ result }) => {
  console.log('Mining completed!', result);
  console.log('Event ID:', result.event.id);
});

// Start mining
await notemine.mine();

// Cancel mining if needed
// notemine.cancel();

// Clean up subscriptions
progressSub.unsubscribe();
successSub.unsubscribe();

API Documentation

Constructor Options

interface MinerOptions {
  content?: string;          // The content to include in the mined event
  tags?: string[][];        // Tags for the event
  pubkey?: string;          // Public key for the event
  difficulty?: number;      // Target difficulty (default: 20)
  numberOfWorkers?: number; // Number of workers (default: CPU cores)
  kind?: number;            // Event kind (default: 1)
  debug?: boolean;          // Enable debug logging (default: false)
}

Properties

  • content: Get/set the event content
  • tags: Get/set the event tags
  • pubkey: Get/set the public key
  • difficulty: Get/set the mining difficulty
  • numberOfWorkers: Get/set the number of workers
  • totalHashRate: Get the combined hash rate of all workers

Methods

mine(): Promise<void>

Starts the mining process. Throws if pubkey or content is not set.

pause(): void

Pauses the mining process while preserving the current state (nonces, best PoW, etc.). Workers are terminated but state is maintained for resumption.

resume(workerNonces?: string[]): Promise<void>

Resumes mining from a paused state. Optionally accepts an array of worker nonces to resume from. If not provided, uses the tracked state from the last pause.

getState(): MiningState

Returns the current mining state as a serializable object. This can be saved to localStorage, IndexedDB, or any other storage mechanism for later restoration.

restoreState(state: MiningState): void

Restores the miner to a previously saved state. Must be called before resume(). Can be used to resume mining after a page refresh or across sessions.

cancel(): void

Stops the mining process and terminates all workers. State is not preserved.

stop(): void

Alias for cancel().

Observables

// Mining state
mining$: BehaviorSubject<boolean>
cancelled$: BehaviorSubject<boolean>
paused$: BehaviorSubject<boolean>

// Results
result$: BehaviorSubject<MinedResult | null>
success$: Observable<SuccessEvent>

// Progress tracking
progress$: Observable<ProgressEvent>
workersPow$: BehaviorSubject<Record<number, BestPowData>>
highestPow$: BehaviorSubject<WorkerPow | null>

// Errors
error$: Observable<ErrorEvent>
cancelledEvent$: Observable<CancelledEvent>

// Worker management
workers$: BehaviorSubject<Worker[]>

Type Definitions

interface ProgressEvent {
  workerId: number;
  hashRate?: number;
  bestPowData?: BestPowData;
}

interface BestPowData {
  bestPow: number;
  nonce: string;
  hash: string;
}

interface MinedResult {
  event: any;           // The mined Nostr event
  totalTime: number;    // Total mining time in milliseconds
  hashRate: number;     // Average hash rate achieved
}

interface SuccessEvent {
  result: MinedResult | null;
}

interface ErrorEvent {
  error: any;
  message?: string;
}

interface MiningState {
  event: {
    pubkey: string;
    kind: number;
    tags: string[][];
    content: string;
    created_at: number;
  };
  workerNonces: string[];      // Array of current nonces for each worker
  bestPow: BestPowData | null; // Best proof-of-work found so far
  difficulty: number;          // Target difficulty
  numberOfWorkers: number;     // Number of workers when state was saved
}

Advanced Features

Debug Mode

Enable detailed console logging for development and troubleshooting:

const notemine = new Notemine({
  content: 'Hello, Nostr!',
  pubkey: 'your-public-key-here',
  difficulty: 21,
  debug: true  // Enable debug logging
});

Debug output includes:

  • Session Management: RunId generation for each mining session
  • Worker Progress: Per-worker nonce updates (rate-limited to every 2s)
  • Hash Rate: Total hash rate with delta changes (every 1s)
  • Ghost Updates: Detection and blocking of stale worker messages
  • State Persistence: Nonce arrays being saved/restored

Example console output:

[Notemine] Starting new mining session, runId: f3008079-ef2e-44c7-a282-24e851ccfe0c
[Notemine] Worker 0 currentNonce: 123456
[Notemine] totalHashRate: 6000.50 KH/s (Δ 150.25)
[Notemine] 🚫 GHOST UPDATE BLOCKED - Ignoring message from old session

Protocol v2 - Worker Message Format

The wrapper uses Protocol v2 for communication with workers, which includes:

Key Features:

  • RunId Gating: Each mining session has a unique UUID to prevent ghost updates
  • Current Nonce Tracking: Workers report their current nonce for accurate resume
  • Backward Compatibility: Protocol v1 messages (without runId) still work

Message Structure:

{
  type: 'progress',
  workerId: 0,
  runId: 'uuid-v4-string',     // Session identifier
  currentNonce: '123456',      // Current position for resume
  bestPowData: {
    bestPow: 21,
    nonce: '123456',
    hash: '000000abc...'
  },
  hashRate: 5000
}

Custom Worker Implementation:

// In your custom mine.worker.ts
let sessionRunId: string;

self.onmessage = (e) => {
  const { event, runId, workerId } = e.data;
  sessionRunId = runId;

  // Your mining loop
  while (mining) {
    // ... mining logic ...

    // Send progress with Protocol v2 format
    self.postMessage({
      type: 'progress',
      workerId,
      runId: sessionRunId,           // Include runId
      currentNonce: nonce.toString(), // Include current nonce
      hashRate: calculatedHashRate,
      bestPowData: bestPow ? { bestPow, nonce, hash } : undefined
    });
  }
};

Guarded Persistence

The wrapper implements "guarded persistence" to avoid storing useless default state:

// Only real progress is persisted
const state = notemine.getState();

// If mining just started, workerNonces is empty
// state.workerNonces = []  (defaults like ["0", "1", "2"] are filtered out)

// After real progress
// state.workerNonces = ["123456", "789012"]  (actual progress is saved)

This prevents cluttering localStorage with meaningless initial state.

State Persistence Throttling

State updates are automatically throttled to ~500ms to reduce I/O overhead:

// Updates are batched and throttled
const notemine = new Notemine({
  /* ... */
  onMiningStateUpdate: (state) => {
    // This callback is called at most every 500ms
    // Instead of 4-8 times per second
    localStorage.setItem('mining-state', JSON.stringify(state));
  }
});

Performance Optimizations

The wrapper includes several performance improvements:

WASM SIMD: 15-35% hash rate improvement

Baseline: 5.2-5.5 MH/s
With SIMD: 6.0 MH/s sustained, 7.0 MH/s burst

Adaptive Progress Reporting: Automatically adjusts reporting frequency to maintain ~250ms cadence

Efficient Cancel: Workers respond to cancel requests within 100ms typical

Lifecycle Semantics

Enhanced pause/resume/cancel behavior:

Grace Period: Workers get 200ms to respond to cancel before termination

Idempotent Operations: Safe to call pause/resume/cancel multiple times

Progress Gating: Progress messages are ignored after mining stops (prevents race conditions)

// All of these are safe
notemine.pause();
notemine.pause();  // No-op, already paused

notemine.resume();
notemine.resume(); // No-op, already mining

Framework Examples

<script lang="ts">
  import { onMount } from 'svelte';
  import { type Writable, writable } from 'svelte/store';
  import { type ProgressEvent, Notemine } from '@notemine/wrapper';

  const numberOfMiners = 8
  let notemine: Notemine;
  let progress: Writable<ProgressEvent[]> = new writable(new Array(numberOfMiners))
  let success: Writeable<SuccessEvent> = new writable(null)

  onMount(() => {
    notemine = new Notemine({ content: 'Hello, Nostr!', numberOfMiners  });

    const progress$ = miner.progress$.subscribe(progress_ => {
      progress.update( _progress => {
        _progress[progress_.workerId] = progress_
        return _progress
      })
    });

    const success$ = miner.progress$.subscribe(success_ => {
      const {event, totalTime, hashRate}
      success.update( _success => {
        _success = success_
        return _success
      })
      miner.cancel();
    });

    notemine.mine();

    return () => {
      progress$.unsubscribe();
      success$.unsubscribe();
      miner.cancel();
    };
  });
  $: miners = $progress
</script>


<div>
{#each $miners as miner}
<span>Miner #{miner.workerId}: {miner.hashRate}kH/s [Best PoW: ${miner.bestPowData}]
{/each}

{#if($success !== null)}
  <pre>
  {$success.event}
  </pre>
{/if}

</div>
  import React, { useEffect } from 'react';
  import { Notemine } from '@notemine/wrapper';

  const MyComponent = () => {
    const notemine = new Notemine({ content: 'Hello, Nostr!' });

    useEffect(() => {
      const subscription = notemine.progress$.subscribe(progress => {
        // Update progress bar or display notemine's progress
      });

      notemine.mine();

      return () => {
        subscription.unsubscribe();
        notemine.cancel();
      };
    }, []);

    return (
      <div>
        {/* Your UI components */}
      </div>
    );
  };
<template>
  <div>
    <!-- Your UI components -->
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, onUnmounted } from 'vue';
import { Notemine } from '@notemine/wrapper';

export default defineComponent({
  name: 'MinerComponent',
  setup() {
    const notemine = new Notemine({ content: 'Hello, Nostr!' });

    onMounted(() => {
      const subscription = notemine.progress$.subscribe(progress => {
        // Update progress bar or display notemine's progress
      });

      notemine.mine();

      onUnmounted(() => {
        subscription.unsubscribe();
        notemine.cancel();
      });
    });

    return {};
  },
});
</script>
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Notemine } from '@notemine/wrapper';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-notemine',
  templateUrl: './notemine.component.html',
})
export class MinerComponent implements OnInit, OnDestroy {
  notemine: Notemine;
  progressSubscription: Subscription;

  ngOnInit() {
    this.notemine = new Notemine({ content: 'Hello, Nostr!' });
    this.progressSubscription = this.notemine.progress$.subscribe(progress => {
      // Update progress bar or display notemine's progress
    });

    this.notemine.mine();
  }

  ngOnDestroy() {
    this.progressSubscription.unsubscribe();
    this.notemine.cancel();
  }
}

Advanced Usage

Pause and Resume Mining

import { Notemine } from '@notemine/wrapper';

const miner = new Notemine({
  content: 'Mining with pause/resume',
  pubkey: 'your-pubkey-here',
  difficulty: 21,
  numberOfWorkers: 4
});

// Start mining
await miner.mine();

// Pause after some time
setTimeout(() => {
  miner.pause();
  console.log('Mining paused');
}, 10000);

// Resume later
setTimeout(async () => {
  await miner.resume();
  console.log('Mining resumed');
}, 20000);

Persist State Across Page Refreshes

import { Notemine } from '@notemine/wrapper';

// Before page refresh - save state
const miner = new Notemine({
  content: 'Persistent mining',
  pubkey: 'your-pubkey-here',
  difficulty: 21
});

await miner.mine();

// User navigates away or refreshes
window.addEventListener('beforeunload', () => {
  miner.pause();
  const state = miner.getState();
  localStorage.setItem('mining_state', JSON.stringify(state));
});

// After page reload - restore state
const savedState = localStorage.getItem('mining_state');
if (savedState) {
  const state = JSON.parse(savedState);

  const miner = new Notemine({
    numberOfWorkers: state.numberOfWorkers
  });

  miner.restoreState(state);
  await miner.resume();

  console.log('Mining resumed from saved state!');
}

Resume with Different Worker Count

import { Notemine } from '@notemine/wrapper';

// Start mining with 4 workers
const miner = new Notemine({
  content: 'Scalable mining',
  pubkey: 'your-pubkey-here',
  difficulty: 21,
  numberOfWorkers: 4
});

await miner.mine();

// Pause and get state
miner.pause();
const state = miner.getState();

// Resume with 8 workers - nonces are automatically redistributed
const miner2 = new Notemine({
  numberOfWorkers: 8  // Different worker count!
});

miner2.restoreState(state);
await miner2.resume();

console.log('Mining resumed with 8 workers instead of 4!');

Track Pause/Resume State

import { Notemine } from '@notemine/wrapper';

const miner = new Notemine({ content: 'State tracking' });

// Subscribe to pause state
miner.paused$.subscribe(isPaused => {
  console.log(`Mining is ${isPaused ? 'paused' : 'active'}`);

  if (isPaused) {
    // Show resume button in UI
    // Display saved state info
  }
});

// Subscribe to mining state
miner.mining$.subscribe(isMining => {
  console.log(`Mining is ${isMining ? 'running' : 'stopped'}`);
});

Monitoring Mining Progress

import { Notemine } from '@notemine/wrapper';
import { combineLatest } from 'rxjs';

const miner = new Notemine({ content: 'Mining demo', difficulty: 25 });

// Combine multiple observables for comprehensive monitoring
combineLatest([
  miner.mining$,
  miner.highestPow$,
  miner.progress$
]).subscribe(([isMining, highestPow, progress]) => {
  if (isMining) {
    console.log('Mining in progress...');
    console.log(`Best PoW so far: ${highestPow?.bestPow || 0}`);
    console.log(`Worker ${progress.workerId} hash rate: ${progress.hashRate} H/s`);
  }
});

Custom Worker Configuration

// Use half of available CPU cores
const miner = new Notemine({
  content: 'Optimized mining',
  numberOfWorkers: Math.floor(navigator.hardwareConcurrency / 2)
});

// Monitor individual worker performance
miner.workersPow$.subscribe(workersPow => {
  Object.entries(workersPow).forEach(([workerId, powData]) => {
    console.log(`Worker ${workerId}: Best PoW = ${powData.bestPow}`);
  });
});

Error Handling

const miner = new Notemine({ content: 'Error handling demo' });

miner.error$.subscribe(error => {
  console.error('Mining error:', error);
  // Implement retry logic or user notification
});

try {
  await miner.mine();
} catch (error) {
  console.error('Failed to start mining:', error);
}

Building from Source

Prerequisites

  • Node.js 16+
  • pnpm (recommended) or npm
  • For WASM rebuilding: Rust toolchain and wasm-pack

Build Steps

# Clone the repository
git clone https://github.com/sandwichfarm/notemine.git
cd notemine/packages/wrapper

# Install dependencies
pnpm install

# Build the package
pnpm run build

# Run tests
pnpm test

Development Scripts

  • pnpm run clean - Remove build artifacts
  • pnpm run build - Build the package with TypeScript declarations
  • pnpm run build:types - Generate TypeScript declarations only
  • pnpm run dev - Run development build
  • pnpm test - Run test suite

Performance Tips

  1. Worker Count: More workers don't always mean better performance. Test different configurations for your use case.
  2. Difficulty: Higher difficulty exponentially increases mining time. Start with lower values for testing.
  3. Browser Considerations: Performance varies across browsers. Chrome and Firefox typically offer the best WASM performance.
  4. Memory Usage: Each worker maintains its own WASM instance. Monitor memory usage with many workers.

Troubleshooting

Common Issues

  1. "Public key is not set" error

    • Ensure you set the pubkey property before calling mine()
  2. Workers not starting

    • Check browser console for CSP (Content Security Policy) errors
    • Ensure your bundler properly handles Web Workers
  3. Low hash rates

    • Verify WASM is loading correctly
    • Check if browser throttling is active (background tabs)
    • Consider reducing the number of workers

Debug Mode

Enable detailed logging by checking the browser console for worker messages:

const miner = new Notemine({ content: 'Debug mode' });

// Monitor all worker messages
miner.workers$.subscribe(workers => {
  console.log(`Active workers: ${workers.length}`);
});

Related Packages

Demos

Contributing

Contributions are welcome! Please read our contributing guidelines and submit PRs to the main repository.

License

MIT License

See LICENSE for details.