Verify wallet ownership through blockchain transactions. No signature popups, no signing required. Pure transaction-based authentication that's easy to integrate.
Simple 3-step authentication flow
Backend creates a unique session with verification details when user initiates authentication
// Backend creates session
const session = {
id: uuid(),
walletAddress: request.walletAddress,
receiverAddress: process.env.RECEIVER,
expectedAmount: 0.00001,
status: 'pending',
expiresAt: Date.now() + 900000
};
sessions.set(sessionId, session);
return session;
User sends a micro verification transaction (0.00001 SOL ≈ $0.002) from their wallet to prove ownership
Backend automatically monitors blockchain and detects matching transaction
// Backend polls blockchain for matching tx
const signatures = await connection
.getSignaturesForAddress(receiverPubkey);
for (const sig of signatures) {
const tx = await connection.getTransaction(sig);
// Check sender + amount match
if (tx.sender === session.walletAddress &&
tx.amount === session.expectedAmount) {
session.status = 'verified';
return { authenticated: true };
}
}
A non-profit, open-source mission to eliminate wallet drains on Solana. We're building this to protect the community, funded entirely by token creator rewards. No fees, no profit, just safer Web3 for everyone.
No blind signature approvals that drain wallets. Users see exactly what they're sending—attackers can't hide malicious transactions.
Authenticate in under 2 seconds on Solana's high-speed blockchain. No waiting, just seamless auth.
No central auth servers. Verification happens on-chain, ensuring true Web3 principles.
Simple REST API. Works with any stack. Get up and running in minutes, not days.
Run your own instance or use our hosted service. You control your infrastructure.
MIT licensed. Full transparency. Contribute, fork, or customize for your needs.
Implementation examples to get you started. Full setup guide available on GitHub.
// Step 1: Get wallet address from user
const walletAddress = prompt('Enter your Solana wallet address:');
// Step 2: Initiate authentication
const response = await fetch('https://your-api.com/api/auth/initiate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ walletAddress })
});
const data = await response.json();
// Step 3: Display instructions to user
alert(`Please send ${data.expectedAmount} SOL to: ${data.receiverAddress}`);
// Show these details in your UI with copy buttons
// Step 4: Poll for verification
const checkAuth = async () => {
const statusRes = await fetch(
`https://your-api.com/api/auth/status/${data.sessionId}`
);
const status = await statusRes.json();
if (status.status === 'verified') {
console.log('✅ Authenticated!', status);
localStorage.setItem('sessionId', data.sessionId);
// User is authenticated - proceed to app
} else if (status.status === 'pending') {
// Still waiting - check again in 5 seconds
setTimeout(checkAuth, 5000);
}
};
checkAuth(); // Start polling
import { useState, useEffect } from 'react';
function MyApp() {
const [walletAddress, setWalletAddress] = useState('');
const [authDetails, setAuthDetails] = useState(null);
const [authenticated, setAuthenticated] = useState(false);
const [polling, setPolling] = useState(false);
// Poll for authentication status
useEffect(() => {
if (!polling || !authDetails) return;
const interval = setInterval(async () => {
const res = await fetch(
`https://your-api.com/api/auth/status/${authDetails.sessionId}`
);
const status = await res.json();
if (status.status === 'verified') {
setAuthenticated(true);
setPolling(false);
localStorage.setItem('sessionId', authDetails.sessionId);
}
}, 5000); // Poll every 5 seconds
return () => clearInterval(interval);
}, [polling, authDetails]);
const startAuth = async () => {
// 1. Initiate authentication
const res = await fetch('https://your-api.com/api/auth/initiate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ walletAddress })
});
const data = await res.json();
// 2. Show user what to send (they send manually from wallet app)
setAuthDetails({
sessionId: data.sessionId,
receiverAddress: data.receiverAddress,
expectedAmount: data.expectedAmount
});
setPolling(true);
};
return (
<div>
{!authDetails ? (
<>
<input
value={walletAddress}
onChange={(e) => setWalletAddress(e.target.value)}
placeholder="Enter wallet address"
/>
<button onClick={startAuth}>Start Auth</button>
</>
) : !authenticated ? (
<div>
<p>Open your wallet app and send:</p>
<p>Amount: {authDetails.expectedAmount} SOL</p>
<p>To: <code>{authDetails.receiverAddress}</code></p>
<p>Waiting for transaction...</p>
</div>
) : (
<p>✅ Authenticated!</p>
)}
</div>
);
}
const express = require('express');
const axios = require('axios');
const API_URL = 'https://your-api.com';
// Middleware to protect routes
async function requireAuth(req, res, next) {
const sessionId = req.headers['x-session-id'];
if (!sessionId) {
return res.status(401).json({ error: 'No session ID provided' });
}
try {
const response = await axios.get(
`${API_URL}/api/auth/status/${sessionId}`
);
if (response.data.status === 'verified') {
req.user = {
walletAddress: response.data.walletAddress,
sessionId: sessionId
};
next();
} else {
res.status(401).json({ error: 'Not authenticated' });
}
} catch (error) {
res.status(401).json({ error: 'Authentication check failed' });
}
}
// Use on protected routes
const app = express();
app.get('/api/protected', requireAuth, (req, res) => {
res.json({
message: 'Welcome!',
wallet: req.user.walletAddress
});
});
app.listen(3000);
import requests
from functools import wraps
from flask import Flask, request, jsonify
app = Flask(__name__)
API_URL = 'https://your-api.com'
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
session_id = request.headers.get('X-Session-Id')
if not session_id:
return jsonify({'error': 'No session ID provided'}), 401
try:
response = requests.get(
f'{API_URL}/api/auth/status/{session_id}',
timeout=5
)
data = response.json()
if data.get('status') == 'verified':
request.user = {
'walletAddress': data['walletAddress'],
'sessionId': session_id
}
return f(*args, **kwargs)
else:
return jsonify({'error': 'Not authenticated'}), 401
except requests.RequestException as e:
return jsonify({'error': 'Authentication check failed'}), 401
return decorated
@app.route('/api/protected')
@require_auth
def protected_route():
return jsonify({
'message': 'Welcome!',
'wallet': request.user['walletAddress']
})
if __name__ == '__main__':
app.run()
Free forever. MIT licensed. Built for the community.
Complete authentication system. Deploy anywhere. Modify as needed. No strings attached.
Help us keep the lights on
SignLess is a nonprofit open-source project. We rely on creator rewards from our Solana token to maintain the infrastructure, keep the demo website online, and continue development.
By trading the SignLess token, you help us cover costs for Railway hosting, Solana RPC services, domain registration, and developer time.
Token Address: CDD7ZnFXaGH7h9xy2VYUaupsshpgfFQTzqLPcLi1pump
Note: This is not financial advice. The token is purely a way to support the project through creator rewards. SignLess will always remain free and open source.
Get started in 5 minutes with our quick start guide