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 🙏

© 2025 – Pkg Stats / Ryan Hefner

x402-vap

v0.1.0

Published

Verifiable Attention Protocol SDK - Earn rewards for real, verified attention

Readme

@vap/sdk

Verifiable Attention Protocol SDK - Earn rewards for real, verified attention on any platform

npm version License: MIT

Overview

VAP (Verifiable Attention Protocol) enables platforms to reward users for genuine attention through cryptographic proof-of-attention. Users earn micro-rewards by proving they're actively engaged through WebSocket heartbeat challenges.

How it works:

  1. User connects to VAP session via WebSocket
  2. Server sends cryptographic challenges every 5 seconds
  3. User's wallet signs challenges proving presence
  4. Server validates signatures and accumulates attention credits
  5. Rewards paid out instantly or accumulated for batch settlement

Features

  • Cryptographic Proof: Ed25519 signature-based attention verification
  • Real-Time Rewards: Instant micro-payments for verified attention
  • State Channels: Off-chain accumulation with on-chain settlement
  • Wallet Agnostic: Works with any Solana wallet or embedded wallet
  • Framework Agnostic: Vanilla JS, React, Vue, Svelte
  • Auto-Reconnect: Handles disconnections gracefully
  • TypeScript: Full type safety

Installation

npm install x402-vap
# or
pnpm add @vap/sdk
# or  
yarn add @vap/sdk

Quick Start

Vanilla JavaScript

import { VapClient } from '@vap/sdk';

const client = new VapClient({
  wsUrl: 'wss://vap.rendernet.work/session?uid=user123&cid=channel456'
});

// Set up wallet signer
client.setSigner(async (message) => {
  // Sign with your wallet
  const signature = await wallet.signMessage(message);
  return signature;
});

// Handle rewards
client.onReward((amount, unit) => {
  console.log(`Earned ${amount} ${unit}!`);
  // Update UI, show notification, etc.
});

// Start session
client.startVap();

// Stop when user leaves
client.stop();

React Integration

import { VapClient } from '@vap/sdk';
import { useWallet } from '@solana/wallet-adapter-react';
import { useEffect, useState } from 'react';

export function useVapSession(userId: string, channelId: string) {
  const { signMessage, publicKey } = useWallet();
  const [totalRewards, setTotalRewards] = useState(0);
  const [client, setClient] = useState<VapClient | null>(null);
  
  useEffect(() => {
    if (!publicKey || !signMessage) return;
    
    const vapClient = new VapClient({
      wsUrl: `wss://vap.rendernet.work/session?uid=${userId}&cid=${channelId}`
    });
    
    // Configure signer
    vapClient.setSigner(async (msg) => {
      const sig = await signMessage(msg);
      return sig;
    });
    
    // Handle rewards
    vapClient.onReward((amount, unit) => {
      setTotalRewards(prev => prev + amount);
      console.log(`+${amount} ${unit}`);
    });
    
    // Start session
    vapClient.startVap();
    setClient(vapClient);
    
    // Cleanup on unmount
    return () => vapClient.stop();
  }, [publicKey, signMessage, userId, channelId]);
  
  return { totalRewards, isActive: !!client };
}

// Usage in component:
function VideoPlayer() {
  const { totalRewards } = useVapSession('user-123', 'video-456');
  
  return (
    <div>
      <video src="..." />
      <div className="rewards-badge">
        Earned: ${totalRewards.toFixed(4)} SOL
      </div>
    </div>
  );
}

API Reference

Class: VapClient

Constructor

new VapClient(options: VapOptions)

Options:

interface VapOptions {
  wsUrl: string;  // WebSocket URL with user/channel params
}

Example:

const client = new VapClient({
  wsUrl: 'wss://vap.rendernet.work/session?uid=alice&cid=stream-789'
});

Methods

setSigner(fn: SignerFunction): void

Configures the message signing function.

Parameters:

type SignerFunction = (message: Uint8Array) => Promise<Uint8Array>;

Example with Phantom:

client.setSigner(async (msg) => {
  const signature = await window.solana.signMessage(msg, 'utf8');
  return signature.signature;
});

Example with Privy:

import { useSolanaWallets } from '@privy-io/react-auth/solana';

const { wallets } = useSolanaWallets();
const wallet = wallets[0];

client.setSigner(async (msg) => {
  const sig = await wallet.signMessage(msg);
  return new Uint8Array(sig);
});
onReward(handler: RewardHandler): void

Sets the reward callback handler.

Parameters:

type RewardHandler = (amount: number, unit: string) => void;

Example:

client.onReward((amount, unit) => {
  // Update state
  setEarnings(prev => prev + amount);
  
  // Show toast notification
  toast.success(`+${amount} ${unit}`);
  
  // Track analytics
  analytics.track('reward_earned', { amount, unit });
});
startVap(): void

Initiates the VAP session and WebSocket connection.

Example:

client.startVap();
stop(): void

Stops the session and closes WebSocket connection.

Example:

// When user navigates away
window.addEventListener('beforeunload', () => {
  client.stop();
});
connect(): void

Manually connect/reconnect to WebSocket (called automatically by startVap()).

Advanced Usage

Auto-Reconnect with Exponential Backoff

import { VapClient } from '@vap/sdk';

class ResilientVapClient extends VapClient {
  private reconnectAttempts = 0;
  private maxReconnectDelay = 30000;
  
  connect() {
    super.connect();
    
    if (this.ws) {
      this.ws.onclose = () => {
        const delay = Math.min(
          1000 * Math.pow(2, this.reconnectAttempts),
          this.maxReconnectDelay
        );
        
        console.log(`Reconnecting in ${delay}ms...`);
        
        setTimeout(() => {
          this.reconnectAttempts++;
          this.connect();
        }, delay);
      };
      
      this.ws.onopen = () => {
        this.reconnectAttempts = 0;
        console.log('VAP connected');
      };
    }
  }
}

Session Analytics

class AnalyticsVapClient extends VapClient {
  private sessionStart = Date.now();
  private heartbeatsCount = 0;
  private rewardsCount = 0;
  
  constructor(opts: VapOptions) {
    super(opts);
    
    // Track heartbeats
    this.onMessage((data) => {
      if (data.nonce) {
        this.heartbeatsCount++;
      }
    });
    
    // Track rewards
    this.onReward((amount) => {
      this.rewardsCount++;
    });
  }
  
  getSessionStats() {
    return {
      duration: Date.now() - this.sessionStart,
      heartbeats: this.heartbeatsCount,
      rewards: this.rewardsCount,
      avgRewardInterval: this.heartbeatsCount / (this.rewardsCount || 1)
    };
  }
}

Multiple Concurrent Sessions

// User watching multiple streams simultaneously
const sessions = [
  new VapClient({ wsUrl: 'wss://vap.../session?cid=stream1' }),
  new VapClient({ wsUrl: 'wss://vap.../session?cid=stream2' }),
  new VapClient({ wsUrl: 'wss://vap.../session?cid=stream3' })
];

// Configure all with same signer
sessions.forEach(client => {
  client.setSigner(walletSigner);
  client.onReward((amt, unit) => {
    console.log(`Earned ${amt} ${unit} from ${client.opts.wsUrl}`);
  });
  client.startVap();
});

// Stop all on page unload
window.addEventListener('beforeunload', () => {
  sessions.forEach(c => c.stop());
});

WebSocket Protocol

Connection

wss://vap.rendernet.work/session?uid=<userId>&cid=<channelId>

Query Parameters:

  • uid - Unique user identifier
  • cid - Channel/content identifier

Message Flow

Server → Client (Challenge):

{
  "nonce": "abc123...",
  "seq": 1,
  "ts": 1699564800000
}

Client → Server (Response):

{
  "userId": "alice",
  "channelId": "stream-789",
  "seq": 1,
  "sig": "base64_signature",
  "pubkey": "base64_public_key",
  "cts": 1699564801000
}

Server → Client (Reward):

{
  "reward": {
    "amount": 0.0001,
    "unit": "SOL"
  }
}

Examples

Next.js App with Privy

'use client';

import { VapClient } from '@vap/sdk';
import { usePrivy, useSolanaWallets } from '@privy-io/react-auth';
import { useEffect, useState } from 'react';

export default function VideoWithRewards() {
  const { authenticated, login } = usePrivy();
  const { wallets } = useSolanaWallets();
  const [earnings, setEarnings] = useState(0);
  
  useEffect(() => {
    if (!authenticated || wallets.length === 0) return;
    
    const wallet = wallets[0];
    const client = new VapClient({
      wsUrl: `wss://vap.rendernet.work/session?uid=${wallet.address}&cid=video-123`
    });
    
    client.setSigner(async (msg) => {
      const sig = await wallet.signMessage(msg);
      return new Uint8Array(sig);
    });
    
    client.onReward((amount) => {
      setEarnings(prev => prev + amount);
    });
    
    client.startVap();
    
    return () => client.stop();
  }, [authenticated, wallets]);
  
  if (!authenticated) {
    return (
      <button onClick={login} className="...">
        Connect to Earn Rewards
      </button>
    );
  }
  
  return (
    <div>
      <video src="content.mp4" controls />
      <div className="earnings">
        You've earned: ${earnings.toFixed(4)}
      </div>
    </div>
  );
}

Vanilla JS (No Framework)

<!DOCTYPE html>
<html>
<head>
  <script type="module">
    import { VapClient } from 'https://esm.sh/@vap/sdk';
    
    const client = new VapClient({
      wsUrl: 'wss://vap.rendernet.work/session?uid=user&cid=page'
    });
    
    // Setup signer (Phantom example)
    client.setSigner(async (msg) => {
      const resp = await window.solana.signMessage(msg, 'utf8');
      return resp.signature;
    });
    
    // Handle rewards
    client.onReward((amount, unit) => {
      document.getElementById('earnings').textContent = 
        `Earned: ${amount} ${unit}`;
    });
    
    // Start on page load
    client.startVap();
    
    // Stop on page unload
    window.addEventListener('beforeunload', () => client.stop());
  </script>
</head>
<body>
  <div id="earnings">Earned: 0 SOL</div>
  <div>Watch content to earn...</div>
</body>
</html>

Configuration

Environment Variables

# WebSocket endpoint (optional, defaults to production)
NEXT_PUBLIC_VAP_WS_URL=wss://vap.rendernet.work/session

# User identifier
NEXT_PUBLIC_USER_ID=user-abc-123

# Channel identifier  
NEXT_PUBLIC_CHANNEL_ID=content-xyz-789

Troubleshooting

WebSocket Connection Fails

Symptom: onclose fires immediately after connect()

Solutions:

  • Check network connectivity
  • Verify WebSocket URL format
  • Check CORS headers (should allow your origin)
  • Ensure uid and cid params are present

Signature Verification Fails

Symptom: No rewards despite heartbeats

Solutions:

  • Verify setSigner is called before startVap()
  • Check wallet is connected and can sign messages
  • Ensure signature format is raw bytes (Uint8Array)
  • Test with console.log in signer function

Rewards Not Triggering

Symptom: Connected but onReward never fires

Solutions:

  • Verify server-side VAP session is active
  • Check minimum attention threshold (usually 30 seconds)
  • Ensure channel has rewards enabled
  • Check server logs for signature validation errors

Security

Message Signing

  • All heartbeat responses must be signed with user's private key
  • Server validates Ed25519 signatures on-chain public key
  • Replay attacks prevented via sequence numbers and timestamps
  • No private keys transmitted over WebSocket

Privacy

  • No PII transmitted (only wallet addresses)
  • Session data encrypted in transit (WSS)
  • Minimal fingerprinting (only UA for fraud detection)

Performance

Typical metrics:

  • WebSocket latency: <100ms
  • Heartbeat interval: 5 seconds
  • Signature verification: <10ms
  • Reward accumulation: off-chain (instant)
  • Settlement: batched every 5 minutes or on-demand

Changelog

v0.1.0 (2025-11-11)

  • Initial release
  • WebSocket client with heartbeat challenges
  • Reward handler
  • TypeScript support

Contributing

See CONTRIBUTING.md

License

MIT © 555x402

Support

  • Docs: https://docs.rendernet.work/vap
  • Issues: https://github.com/Render-Network-OS/555x402-vap-sdk/issues
  • Discord: https://discord.gg/555x402

Related Packages


Earn while you engage 🎯