Authentication
How to authenticate with the Turbine.cash API using wallet signatures
Authentication
Section titled “Authentication”The Turbine.cash API uses wallet signature authentication. This proves you control a Solana wallet without requiring account creation.
Authentication Flow
Section titled “Authentication Flow”Step 1: Generate Message
Section titled “Step 1: Generate Message”The message to sign is the current Unix timestamp (seconds since epoch):
const message = Math.floor(Date.now() / 1000);Step 2: Sign Message
Section titled “Step 2: Sign Message”Use your wallet to sign the timestamp:
import { useWallet } from '@solana/wallet-adapter-react';import bs58 from 'bs58';
const { publicKey, signMessage } = useWallet();
async function signAuthMessage(): Promise<string> { const message = Math.floor(Date.now() / 1000); const messageBytes = new TextEncoder().encode(message.toString());
const signatureBytes = await signMessage(messageBytes); const signature = bs58.encode(signatureBytes);
return signature;}Step 3: Call Login Endpoint
Section titled “Step 3: Call Login Endpoint”interface LoginRequest { wallet: string; // Base58 public key signature: string; // Base58 signature message: number; // Unix timestamp}
interface LoginResponse { id: string; // UUID auth token}
async function login(wallet: string, signature: string, message: number): Promise<string> { const response = await fetch('https://worker.turbine.cash/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ wallet, signature, message, }), });
if (!response.ok) { throw new Error(`Login failed: ${response.status}`); }
const data: LoginResponse = await response.json(); return data.id;}Step 4: Use Auth Token
Section titled “Step 4: Use Auth Token”Include the token in the x-api-auth header for authenticated endpoints:
const authToken = await login(wallet, signature, message);
// Use in subsequent requestsconst response = await fetch('https://worker.turbine.cash/relay', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-auth': authToken, }, body: JSON.stringify(relayParams),});Complete Example
Section titled “Complete Example”import { useWallet } from '@solana/wallet-adapter-react';import bs58 from 'bs58';
const API_BASE = 'https://worker.turbine.cash';
class TurbineAuth { private authToken: string | null = null;
async authenticate( publicKey: PublicKey, signMessage: (message: Uint8Array) => Promise<Uint8Array> ): Promise<string> { // 1. Generate message const timestamp = Math.floor(Date.now() / 1000); const messageBytes = new TextEncoder().encode(timestamp.toString());
// 2. Sign message const signatureBytes = await signMessage(messageBytes); const signature = bs58.encode(signatureBytes);
// 3. Login const response = await fetch(`${API_BASE}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ wallet: publicKey.toBase58(), signature, message: timestamp, }), });
if (!response.ok) { throw new Error(`Login failed: ${await response.text()}`); }
const { id } = await response.json(); this.authToken = id; return id; }
async fetch(endpoint: string, options: RequestInit = {}): Promise<Response> { if (!this.authToken) { throw new Error('Not authenticated'); }
return fetch(`${API_BASE}${endpoint}`, { ...options, headers: { ...options.headers, 'x-api-auth': this.authToken, }, }); }
isAuthenticated(): boolean { return this.authToken !== null; }}
// Usageconst auth = new TurbineAuth();await auth.authenticate(publicKey, signMessage);
// Now use for API callsconst response = await auth.fetch('/relay', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(relayParams),});Check Token Validity
Section titled “Check Token Validity”You can check if a token is still valid:
async function isTokenValid(authToken: string): Promise<boolean> { const response = await fetch(`${API_BASE}/is_logged/${authToken}`); return response.ok;}Token Expiration
Section titled “Token Expiration”Auth tokens have a limited lifetime. If a request returns 401 Unauthorized, re-authenticate:
async function fetchWithReauth(endpoint: string, options: RequestInit): Promise<Response> { let response = await auth.fetch(endpoint, options);
if (response.status === 401) { // Token expired, re-authenticate await auth.authenticate(publicKey, signMessage); response = await auth.fetch(endpoint, options); }
return response;}Security Best Practices
Section titled “Security Best Practices”| Practice | Reason |
|---|---|
| Don’t store tokens permanently | They expire anyway |
| Use HTTPS only | Prevent token interception |
| Re-auth on 401 | Handle token expiration gracefully |
| One token per session | Don’t share tokens across tabs/devices |