Observed since 20 Oct, 20:25 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Verifiable Attention Protocol SDK - Earn rewards for real, verified attention
Verifiable Attention Protocol SDK - Earn rewards for real, verified attention on any platform
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:
npm install @vap/sdk
# or
pnpm add @vap/sdk
# or
yarn add @vap/sdk
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();
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>
);
}
VapClientnew 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'
});
setSigner(fn: SignerFunction): voidConfigures 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): voidSets 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(): voidInitiates the VAP session and WebSocket connection.
Example:
client.startVap();
stop(): voidStops the session and closes WebSocket connection.
Example:
// When user navigates away
window.addEventListener('beforeunload', () => {
client.stop();
});
connect(): voidManually connect/reconnect to WebSocket (called automatically by startVap()).
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');
};
}
}
}
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)
};
}
}
// 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());
});
wss://vap.rendernet.work/session?uid=<userId>&cid=<channelId>
Query Parameters:
uid - Unique user identifiercid - Channel/content identifierServer → 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"
}
}
'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>
);
}
<!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>
# 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
Symptom: onclose fires immediately after connect()
Solutions:
Symptom: No rewards despite heartbeats
Solutions:
setSigner is called before startVap()console.log in signer functionSymptom: Connected but onReward never fires
Solutions:
Typical metrics:
See CONTRIBUTING.md
MIT © 555x402
Earn while you engage 🎯
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0Registry latest | 11 Nov 2025 | 2 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Public sponsor counts have not been collected. These signals belong to the repository owner.
github · 1 observations shown. Missing days are unknown.
| Date / source period | Value | Observed at |
|---|---|---|
| 2026-10-23 | 0 | 2026-10-23T14:41:15.2113728Z |
Observed since 20 Oct, 20:25 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:f35e7eb7301ee48404cc4597b73987a3c4cb6c593dc544efb35bc097c7be997a