Verify Signature

Endpoint: POST /api/v1/auth/verify-signature

Description

This endpoint verifies a wallet's signature to authenticate users. It uses Solana's signature verification system and issues a JWT token upon successful verification. The endpoint also manages user records in the database, creating new users or updating login timestamps for existing users.

Authentication Required: No (This is the authentication endpoint)

Type: None

Request Headers

Content-Type: application/json

Body Parameters

Parameter
Type
Required
Description

walletAddress

string

Yes

The Solana wallet address of the user

signature

string

Yes

The base58-encoded signature of the message

message

string

Yes

The original message that was signed

Example Request Body:

{
  "walletAddress": "AaBbCc...",
  "signature": "5tBpQ...",
  "message": "Sign this message to authenticate with Pump Fun: 1234567890"
}

Response

Success Response

Status Code: 200 OK

Content-Type: application/json

{

"token": "eyJhbGciOiJIUzI1NiIs..." // JWT token valid for 24 hours

}

Error Responses

Invalid Signature

Status Code: 400 Bad Request

{

"error": "Verification failed. Invalid wallet address or signature."

}

Server Error

Status Code: 500 Internal Server Error

{

"error": "An internal error occurred. Please try again later."

}

Example Usage

Here's a complete example using JavaScript/TypeScript showing how to sign a message with a Solana wallet and verify it:

import { Connection, PublicKey, Keypair } from "@solana/web3.js";

import bs58 from "bs58";

import nacl from "tweetnacl";

import { encodeUTF8, decodeUTF8 } from "tweetnacl-util";

async function signAndVerify(wallet: any) { // wallet could be Phantom, Solflare, etc.

// 1. Generate a message

const message = `Sign this message to authenticate with Pump Fun: ${Date.now()}`;

try {

// 2. Request signature from wallet

const encodedMessage = decodeUTF8(message);

const signature = await wallet.signMessage(encodedMessage, "utf8");

// 3. Send to verification endpoint

const response = await fetch('https://api.cybers.app/api/v1/auth/verify-signature', {

method: 'POST',

headers: {

'Content-Type': 'application/json',

},

body: JSON.stringify({

walletAddress: wallet.publicKey.toString(),

signature: bs58.encode(signature),

message: message

})

});

if (!response.ok) {

throw new Error(`HTTP error! status: ${response.status}`);

}

const { token } = await response.json();

// Store the JWT token for future authenticated requests

localStorage.setItem('auth_token', token);

return token;

} catch (error) {

console.error('Authentication error:', error);

throw error;

}

}

// Example with Phantom Wallet

async function authenticateWithPhantom() {

try {

// Check if Phantom is installed

const { solana } = window as any;

if (!solana?.isPhantom) {

throw new Error('Phantom wallet is not installed!');

}

// Connect to wallet

const response = await solana.connect();

const wallet = response.publicKey;

// Sign and verify

const token = await signAndVerify(solana);

console.log('Authentication successful!', token);

return token;

} catch (error) {

console.error('Failed to authenticate:', error);

throw error;

}

}

Implementation Notes

Signature Verification

The signature is verified using tweetnacl's sign.detached.verify

The signature must be base58 decoded before verification

The wallet address is converted to bytes for verification

JWT Token

The issued JWT token is valid for 24 hours (expiresIn: "1d")

The token payload contains the wallet address

Use this token in the Authorization header for subsequent requests

Database Operations

New users are automatically created in the database

Existing users have their lastLogin timestamp updated

Initial users are created with an empty username and "user" role

Last updated