Skip to content
Launch App >

The Turbine.cash API uses wallet signature authentication. This proves you control a Solana wallet without requiring account creation.

The message to sign is the current Unix timestamp (seconds since epoch):

const message = Math.floor(Date.now() / 1000);

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;
}
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;
}

Include the token in the x-api-auth header for authenticated endpoints:

const authToken = await login(wallet, signature, message);
// Use in subsequent requests
const response = await fetch('https://worker.turbine.cash/relay', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-auth': authToken,
},
body: JSON.stringify(relayParams),
});
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;
}
}
// Usage
const auth = new TurbineAuth();
await auth.authenticate(publicKey, signMessage);
// Now use for API calls
const response = await auth.fetch('/relay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(relayParams),
});

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;
}

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;
}
PracticeReason
Don’t store tokens permanentlyThey expire anyway
Use HTTPS onlyPrevent token interception
Re-auth on 401Handle token expiration gracefully
One token per sessionDon’t share tokens across tabs/devices