DATABASE QUEST API

Launch DB-Backed Campaigns

Verify if a user has swapped your custom token, how many times they swapped, and how much volume they traded. Backed by your own self-hosted real-time WebSocket indexer and MongoDB database. Faster and more reliable than subgraphs.

How it works under the hood

This API endpoint reads directly from your private MongoDB database which is kept in sync with the blockchain in real-time by a background indexer daemon:

  1. Your background daemon (scripts/event-indexer.mjs) connects to the RPC node via WebSockets.
  2. It listens to the AGGFlowSwap event from the aggregator router contract (0x5E19EB2A6BA30892CCe33f93D7Fb24D498512266).
  3. All swaps are recorded in your MongoDB transactions and user_stats collections immediately.
  4. The API route (/api/db-verify-swaps) queries MongoDB and returns cumulative swap stats, volumes, and tx hashes in milliseconds without any Goldsky or Graph protocol dependency.

API Endpoint Specs

Access the endpoint directly via HTTP. It supports both GET and POST requests, and has CORS enabled by default.

GET

Verify Swap & Volume Status

https://LitvmSwap.com/api/db-verify-swaps?address=[wallet_address]&token=[your_token_address]&minSwaps=[count]&limit=[limit]&counterToken=[counter_token_address]

Request Parameters

ParameterTypeRequirementDescription
addressStringRequiredThe user's EVM wallet address to verify (case-insensitive).
tokenStringRequiredYour token's deployed contract address on LitVM. Filters user swaps to this specific asset.
minSwapsNumberOptionalThe swap count threshold for the quest. Default is 1. Returns isEligible: true if cumulative swaps exceed this.
limitNumberOptionalLimits the size of the returned transaction history array (proofs.swaps). Default is 1000, hard-capped at 1000.
counterTokenStringOptionalPass a contract address to filter user swaps and volume specifically against this counter-token (e.g., pass the wrapped native address to verify swaps made specifically against native zkLTC).

Checking Volume & DIA Oracle Pricing

To check verification for your own token, replace the token parameter in the HTTP query with your token's deployed contract address.

💡 Pre-calculated volume: Unlike subgraphs, this API uses pre-computed USD pricing recorded during indexing. For newly launched custom tokens that do not have a DIA oracle price feed, the indexer defaults the USD price to $0. For custom token campaigns, clients must verify volume based on totalVolumeToken (raw tokens swapped) instead of USD.

JSON Response Structure

Below is a typical response payload returned by the endpoint:

API JSON Response Example
{
  "success": true,
  "address": "0x23d542dcefb00b1f4268e67a0ec1ef4de0a58fe2",
  "token": {
    "address": "0xdf69970b2fe416339187aa41d39882e864984ce9",
    "symbol": "ZKUSDC",
    "name": "ZKUSDC",
    "decimals": 18
  },
  "verification": {
    "totalSwaps": 16,
    "aggregatorSwaps": {
      "total": 16,
      "swappedIn": 2,
      "swappedOut": 14
    },
    "directPoolSwaps": {
      "total": 0,
      "v2": 0,
      "v3": 0
    },
    "totalVolumeToken": 10540.23,
    "totalVolumeUSD": 10540.23,
    "tokenPriceUSD": 1,
    "minSwapsRequired": 3,
    "isEligible": true,
    "remainingSwapsNeeded": 0
  },
  "proofs": {
    "swapCount": 16,
    "swaps": [
      {
        "id": "64b0f69a53d102e3b2e7a1b4",
        "txHash": "0x146402802f52410254d708619404d6cf5adef9ae1d9fbe7db80582a86d83586d",
        "type": "sell",
        "timestamp": 1778439711,
        "dateTime": "2026-05-10T19:01:51.000Z",
        "amountIn": "10.0",
        "amountInDecimal": 10.0,
        "amountOut": "2592.96",
        "amountOutDecimal": 0
      }
    ]
  },
  "timestamp": "2026-07-12T01:42:00.000Z"
}

Integration Example

Here is how you can fetch and verify a user's eligibility in your Node.js backend application:

Node.js Integration Code
// Example: Verifying quest eligibility in Node.js using Database indexer
const axios = require('axios');

async function verifyQuestWithDB(userAddress, customTokenAddress, requiredSwaps = 3) {
  try {
    const url = `${window.location.origin}/api/db-verify-swaps`;
    const response = await axios.get(url, {
      params: {
        address: userAddress,
        token: customTokenAddress, // Replace with your token contract address
        minSwaps: requiredSwaps
      }
    });

    const { success, verification, token } = response.data;
    
    if (success) {
      console.log(`Token ${token.symbol} database verification:`);
      console.log(`Total swaps user performed: ${verification.totalSwaps}`);
      console.log(`Total swap USD volume: $${verification.totalVolumeUSD.toFixed(2)}`);
      console.log(`Eligible for Quest: ${verification.isEligible ? 'YES' : 'NO'}`);
      
      return verification.isEligible;
    }
    return false;
  } catch (error) {
    console.error('Database quest verification failed:', error.message);
    return false;
  }
}

Frontend Integration (TypeScript & React)

Below is a beginner-friendly TypeScript guide showing how to implement this in a React frontend. The component handles state (loading, data, and errors) and provides the interfaces so you get strong type-checking when integrating with your wallet connection.

TypeScript React Component (DatabaseQuestVerifier.tsx)
import React, { useState } from 'react';

// Define TypeScript Interfaces for the API Response
interface TokenInfo {
  address: string;
  symbol: string;
  name: string;
  decimals: number;
}

interface SwapDetails {
  id: string;
  txHash: string;
  type: 'buy' | 'sell';
  timestamp: number;
  dateTime: string;
  amountIn: string;
  amountOut: string;
}

interface VerificationStats {
  totalSwaps: number;
  aggregatorSwaps: {
    total: number;
    swappedIn: number;
    swappedOut: number;
  };
  directPoolSwaps: {
    total: number;
    v2: number;
    v3: number;
  };
  totalVolumeToken: number;
  totalVolumeUSD: number;
  tokenPriceUSD: number;
  minSwapsRequired: number;
  isEligible: boolean;
  remainingSwapsNeeded: number;
}

interface VerifySwapsResponse {
  success: boolean;
  address: string;
  token: TokenInfo;
  verification: VerificationStats;
  proofs: {
    swapCount: number;
    swaps: SwapDetails[];
  };
  timestamp: string;
  error?: string;
}

export const DatabaseQuestVerifier: React.FC = () => {
  const [userAddress, setUserAddress] = useState<string>('');
  const [customToken, setCustomToken] = useState<string>('0xdf69970B2fE416339187aA41D39882e864984CE9');
  const [loading, setLoading] = useState<boolean>(false);
  const [data, setData] = useState<VerifySwapsResponse | null>(null);
  const [error, setError] = useState<string | null>(null);

  const checkQuestStatus = async () => {
    if (!userAddress.startsWith('0x') || userAddress.length !== 42) {
      setError('Please enter a valid EVM address starting with 0x.');
      return;
    }
    
    setLoading(true);
    setError(null);
    setData(null);

    try {
      const response = await fetch(
        `/api/db-verify-swaps?address=${userAddress}&token=${customToken}&minSwaps=1`
      );
      
      const json: VerifySwapsResponse = await response.json();
      
      if (json.success) {
        setData(json);
      } else {
        setError(json.error || 'Failed to verify swaps.');
      }
    } catch (err: any) {
      setError(err.message || 'Network error occurred.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{ padding: '24px', background: '#0d1117', borderRadius: '12px', border: '1px solid #30363d' }}>
      <h3 style={{ color: '#fff', marginTop: 0 }}>DEX Database Swap Verifier</h3>
      
      <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', marginBottom: '16px' }}>
        <label style={{ color: '#8b949e', fontSize: '14px' }}>User Wallet Address:</label>
        <input 
          type="text" 
          value={userAddress} 
          onChange={(e) => setUserAddress(e.target.value)} 
          placeholder="0x..." 
          style={{ padding: '8px 12px', background: '#161b22', border: '1px solid #30363d', borderRadius: '6px', color: '#fff' }}
        />

        <label style={{ color: '#8b949e', fontSize: '14px' }}>Custom Token Contract Address:</label>
        <input 
          type="text" 
          value={customToken} 
          onChange={(e) => setCustomToken(e.target.value)} 
          placeholder="0x..." 
          style={{ padding: '8px 12px', background: '#161b22', border: '1px solid #30363d', borderRadius: '6px', color: '#fff' }}
        />
      </div>

      <button 
        onClick={checkQuestStatus} 
        disabled={loading}
        style={{ padding: '10px 20px', background: '#238636', border: 'none', borderRadius: '6px', color: '#fff', cursor: 'pointer', fontWeight: 'bold' }}
      >
        {loading ? 'Verifying on Database...' : 'Verify User Quests'}
      </button>

      {error && <p style={{ color: '#f85149', marginTop: '16px' }}>⚠️ {error}</p>}

      {data && (
        <div style={{ marginTop: '20px', borderTop: '1px solid #30363d', paddingTop: '16px' }}>
          <h4 style={{ color: '#58a6ff', margin: '0 0 12px 0' }}>Result: {data.verification.totalSwaps > 0 ? '✅ Verified User!' : '❌ Not Swapped Yet'}</h4>
          
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px', fontSize: '14px', color: '#c9d1d9' }}>
            <div>Total Swaps: <strong>{data.verification.totalSwaps} swaps</strong></div>
            <div>Raw Token Volume: <strong>{data.verification.totalVolumeToken.toFixed(2)} {data.token.symbol}</strong></div>
            <div>Estimated USD Volume: <strong>${data.verification.totalVolumeUSD.toFixed(2)}</strong></div>
            <div>Oracle Price: <strong>${data.verification.tokenPriceUSD.toFixed(4)}</strong></div>
          </div>

          {data.proofs.swaps.length > 0 && (
            <div style={{ marginTop: '16px' }}>
              <h5 style={{ color: '#fff', margin: '0 0 8px 0' }}>Transaction Proofs:</h5>
              <ul style={{ margin: 0, paddingLeft: '20px', color: '#8b949e', fontSize: '13px' }}>
                {data.proofs.swaps.map((tx, idx) => (
                  <li key={idx} style={{ marginBottom: '6px' }}>
                    <a 
                      href={`https://liteforge.explorer.caldera.xyz/tx/${tx.txHash}`} 
                      target="_blank" 
                      rel="noreferrer"
                      style={{ color: '#58a6ff', textDecoration: 'none' }}
                    >
                      {tx.txHash.slice(0, 10)}...{tx.txHash.slice(-8)}
                    </a> (${tx.type === 'buy' ? 'Bought' : 'Sold'} on ${tx.dateTime ? tx.dateTime.slice(0, 10) : 'unknown'})
                  </li>
                ))}
              </ul>
            </div>
          )}
        </div>
      )}
    </div>
  );
};

LitvmSwap Aggregator Database Quest API Portal.