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

@neuraiproject/neurai-lock

v0.1.1

Published

Gated access for Neurai web apps: verify wallet ownership and on-chain balance from a Node.js backend

Readme

neurai-lock

Gate access to your web app using the Neurai blockchain. Users authenticate by proving ownership of a Neurai address via a digital signature, and your backend verifies they meet on-chain requirements (XNA balance, specific assets, coin age).

Verification runs on the Node.js backend, so it cannot be tampered with from the browser.

How it works

  1. The user clicks a "Connect" button on your web page
  2. The browser snippet asks the Neurai Sign Chrome extension for the wallet address
  3. The extension signs a one-time challenge nonce (user sees an approval popup)
  4. Your backend verifies the signature and checks the address meets your requirements via RPC
  5. Your backend returns OK or KO — you decide what to show next

Install

npm install @neuraiproject/neurai-lock

Quick start (Express.js)

Backend

const express = require('express');
const { NeuraiLock } = require('@neuraiproject/neurai-lock');

const app = express();
app.use(express.json());

const lock = new NeuraiLock({
  minXna: { amount: 1000, minAge: '1h' },  // must hold 1000 XNA for at least 1 hour
  assets: [{ name: 'MYTOKEN', minAmount: 1 }]
});

// Step 1: generate a one-time challenge for the address
app.post('/api/challenge', async (req, res) => {
  const challenge = await lock.createChallenge(req.body.address);
  res.json(challenge); // { nonce: '...' }
});

// Step 2: verify signature + on-chain requirements
app.post('/api/verify', async (req, res) => {
  const result = await lock.verifyChallenge(req.body);
  res.json(result);
  // { ok: true, address, balances } or { ok: false, reason, errorCode }
});

app.listen(3000);

Frontend HTML

Serve neurai-lock-client.js from the package and include it in your page:

<!-- Serve from your Express app or copy to your public folder -->
<script src="/neurai-lock-client.js"></script>
<script>
  document.getElementById('connectBtn').addEventListener('click', async () => {
    const result = await NeuraiLockClient.connect({
      challengeUrl: '/api/challenge',
      verifyUrl:    '/api/verify',
    });

    if (result.ok) {
      console.log('Address:', result.address);
      console.log('XNA balance:', result.balances.xna.total);
      console.log('Assets:', result.balances.assets);
      showProtectedContent();
    } else {
      console.log('Error:', result.reason);
    }
  });
</script>

To serve the browser snippet from Express:

const path = require('path');
app.get('/neurai-lock-client.js', (_req, res) => {
  res.sendFile(require.resolve('@neuraiproject/neurai-lock/client/neurai-lock-client.js'));
});

Configuration

const lock = new NeuraiLock({
  // RPC endpoint. Defaults to Neurai mainnet.
  rpcUrl: NeuraiLock.URL_MAINNET,

  // Minimum XNA balance requirement
  minXna: {
    amount: 1000,   // XNA (human units, not satoshis)
    minAge: '1h'    // optional: only UTXOs older than this count. Formats: '30m', '1h', '2d'
  },

  // Required assets — ALL must be satisfied
  assets: [
    { name: 'MYTOKEN',  minAmount: 1 },
    { name: 'OTHERNFT', minAmount: 1 },
  ],

  // How long a nonce stays valid before expiring (default: 300000 = 5 minutes)
  nonceTtl: 300_000,

  // Advanced: provide your own nonce store (e.g. Redis) for multi-process deployments
  // nonceStore: myRedisStore,
});

Built-in RPC URLs

NeuraiLock.URL_MAINNET  // 'https://rpc-main.neurai.org/rpc'
NeuraiLock.URL_TESTNET  // 'https://rpc-testnet.neurai.org/rpc'

API

new NeuraiLock(config)

Creates a new NeuraiLock instance with the given configuration.

lock.createChallenge(address: string): Promise<{ nonce: string }>

Generates a cryptographically random one-time nonce tied to the given address and stores it internally (with TTL). Call this from your challenge endpoint and return the nonce to the browser.

lock.verifyChallenge({ address, nonce, signature }): Promise<LockResult>

Verifies that:

  1. The nonce is valid and hasn't expired (consumed on first use — no replay)
  2. The signature proves the user controls the address
  3. The address meets all configured on-chain requirements

Returns a LockResult:

// Success
{
  ok: true,
  address: string,
  balances: {
    xna: {
      total: number,  // full confirmed XNA balance
      aged:  number   // XNA in UTXOs that meet minAge (equals total if no age req)
    },
    assets: Array<{ name: string, amount: number }>
  }
}

// Failure
{
  ok: false,
  address: string | null,
  reason:    string,     // human-readable explanation
  errorCode: string,     // see error codes below
  balances?: { ... }     // present if RPC succeeded before the check failed
}

Error codes

| Code | Meaning | |------|---------| | NONCE_INVALID | Nonce does not exist or has expired — request a new challenge | | VERIFY_FAILED | Signature does not match the address | | RPC_ERROR | Could not reach the Neurai node | | INSUFFICIENT_XNA | Address holds less XNA than required | | INSUFFICIENT_XNA_AGE | Not enough XNA in UTXOs older than minAge | | INSUFFICIENT_ASSET | A required asset is missing or below the minimum | | INVALID_CONFIG | Configuration error (thrown at construction time) |

Browser client API (NeuraiLockClient)

The client/neurai-lock-client.js file is a zero-dependency vanilla JS snippet for the browser.

NeuraiLockClient.connect(options): Promise<LockResult>

Runs the full connect flow. Options:

| Option | Type | Description | |--------|------|-------------| | challengeUrl | string | Your /api/challenge endpoint | | verifyUrl | string | Your /api/verify endpoint | | fetchOptions | object | Extra options merged into every fetch() call (e.g. credentials, custom headers) | | walletTimeout | number | Max ms to wait for the extension to inject (default: 3000) |

Additional browser-side error codes: NOT_INSTALLED, NO_WALLET, USER_REJECTED, CHALLENGE_ERROR, VERIFY_REQUEST_ERROR.

NeuraiLockClient.isWalletInstalled(): boolean

Synchronous check. Only reliable after waitForWallet() has resolved.

NeuraiLockClient.waitForWallet(timeoutMs?): Promise<boolean>

Waits for the Neurai Sign extension to inject window.neuraiWallet into the page. Resolves true if the extension is found within the timeout, false otherwise. Useful for updating UI on page load without triggering the full connect flow.

License

This project is licensed under the Apache License 2.0.

NeuraiLockClient.waitForWallet(3000).then(found => {
  if (!found) showInstallBanner();
});

Custom nonce store

The default nonce store is in-memory. For multi-process or multi-server deployments (e.g. behind a load balancer), provide a Redis-backed store:

const { NeuraiLock, createNonceStore } = require('@neuraiproject/neurai-lock');

// Example: Redis adapter
const myRedisStore = {
  save(nonce, address) {
    return redis.set(`nonce:${nonce}`, address, 'PX', 300_000);
  },
  async consume(nonce) {
    const address = await redis.get(`nonce:${nonce}`);
    if (address) await redis.del(`nonce:${nonce}`);
    return address;
  },
};

const lock = new NeuraiLock({ nonceStore: myRedisStore });

Running the example

A working Express + HTML example is included:

cd node_modules/@neuraiproject/neurai-lock/example
npm install
npm start
# Open http://localhost:4000 in Chrome with the Neurai Sign extension installed