Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.akka.finance/llms.txt

Use this file to discover all available pages before exploring further.

Two hooks that handle the entire swap lifecycle. Copy them into your project and you have a working swap UI.

useAkkaQuote

Fetches a quote from AKKA with automatic debouncing. Returns the expected output amount, token info, and loading/error states.
hooks/useAkkaQuote.ts
import { useState, useEffect, useRef } from 'react';

const API_BASE = 'https://api.akka.finance';
const API_KEY = process.env.NEXT_PUBLIC_AKKA_API_KEY!;
const CHAIN_ID = 999;
const DEBOUNCE_MS = 400;

interface TokenInfo {
  address: string;
  symbol: string;
  name: string;
  decimals: number;
  logoUri: string | null;
}

interface QuoteResult {
  dstAmount: string;
  srcToken?: TokenInfo;
  dstToken?: TokenInfo;
  gas?: string;
}

interface UseAkkaQuoteReturn {
  quote: QuoteResult | null;
  loading: boolean;
  error: string | null;
}

export function useAkkaQuote(
  src: string,
  dst: string,
  amount: string,  // in wei
): UseAkkaQuoteReturn {
  const [quote, setQuote] = useState<QuoteResult | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const abortRef = useRef<AbortController>();

  useEffect(() => {
    // Don't fetch for zero/empty amounts
    if (!src || !dst || !amount || amount === '0') {
      setQuote(null);
      setError(null);
      return;
    }

    const timer = setTimeout(async () => {
      // Cancel previous in-flight request
      abortRef.current?.abort();
      const controller = new AbortController();
      abortRef.current = controller;

      setLoading(true);
      setError(null);

      try {
        const params = new URLSearchParams({
          src, dst, amount,
          includeTokensInfo: 'true',
          includeGas: 'true',
        });

        const res = await fetch(
          `${API_BASE}/swap/v1/${CHAIN_ID}/quote?${params}`,
          {
            headers: { apikey: API_KEY },
            signal: controller.signal,
          },
        );

        if (!res.ok) {
          const body = await res.json();
          throw new Error(body.message || `HTTP ${res.status}`);
        }

        const data: QuoteResult = await res.json();
        setQuote(data);
      } catch (err) {
        if (err instanceof DOMException && err.name === 'AbortError') return;
        setError(err instanceof Error ? err.message : 'Quote failed');
        setQuote(null);
      } finally {
        setLoading(false);
      }
    }, DEBOUNCE_MS);

    return () => {
      clearTimeout(timer);
      abortRef.current?.abort();
    };
  }, [src, dst, amount]);

  return { quote, loading, error };
}

Usage

import { parseUnits, formatUnits } from 'viem';
import { useAkkaQuote } from './hooks/useAkkaQuote';

const HYPE = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
const UBTC = '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb';

function QuoteDisplay() {
  const [input, setInput] = useState('');
  const amountWei = input ? parseUnits(input, 18).toString() : '0';

  const { quote, loading, error } = useAkkaQuote(HYPE, UBTC, amountWei);

  return (
    <div>
      <input
        type="number"
        placeholder="Amount in HYPE"
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />

      {loading && <span>Fetching best rate...</span>}
      {error && <span style={{ color: 'red' }}>{error}</span>}
      {quote && (
        <div>
          <p>You'll receive: {formatUnits(BigInt(quote.dstAmount), quote.dstToken?.decimals ?? 18)} {quote.dstToken?.symbol}</p>
          {quote.gas && <p>Estimated gas: {quote.gas}</p>}
        </div>
      )}
    </div>
  );
}
The hook automatically:
  • Debounces — waits 400ms after the user stops typing before fetching
  • Cancels stale requests — if the user types again, the previous request is aborted
  • Resets on zero input — clears the quote when the input is empty

useAkkaSwap

Handles the full approve-and-swap flow. Pass in the swap parameters, get back an execute function and status.
hooks/useAkkaSwap.ts
import { useState, useCallback } from 'react';
import { useSendTransaction, useAccount, usePublicClient } from 'wagmi';

const API_BASE = 'https://api.akka.finance';
const API_KEY = process.env.NEXT_PUBLIC_AKKA_API_KEY!;
const CHAIN_ID = 999;
const NATIVE_TOKEN = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';

type SwapStatus = 'idle' | 'checking' | 'approving' | 'waitingApproval' | 'swapping' | 'waitingSwap' | 'done' | 'error';

interface UseAkkaSwapReturn {
  execute: () => Promise<void>;
  status: SwapStatus;
  txHash: string | null;
  error: string | null;
  reset: () => void;
}

export function useAkkaSwap(
  src: string,
  dst: string,
  amount: string,
  slippage: number = 1,
): UseAkkaSwapReturn {
  const { address } = useAccount();
  const publicClient = usePublicClient();
  const { sendTransactionAsync } = useSendTransaction();

  const [status, setStatus] = useState<SwapStatus>('idle');
  const [txHash, setTxHash] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const reset = useCallback(() => {
    setStatus('idle');
    setTxHash(null);
    setError(null);
  }, []);

  const execute = useCallback(async () => {
    if (!address || !amount || amount === '0') return;

    setError(null);
    setTxHash(null);

    try {
      // Step 1: Check allowance (skip for native tokens)
      if (src.toLowerCase() !== NATIVE_TOKEN) {
        setStatus('checking');

        const allowanceRes = await fetch(
          `${API_BASE}/swap/v1/${CHAIN_ID}/approve/allowance?` +
          new URLSearchParams({ tokenAddress: src, walletAddress: address }),
          { headers: { apikey: API_KEY } },
        );
        const { allowance } = await allowanceRes.json();

        // Step 2: Approve if needed
        if (BigInt(allowance) < BigInt(amount)) {
          setStatus('approving');

          const approveRes = await fetch(
            `${API_BASE}/swap/v1/${CHAIN_ID}/approve/transaction?` +
            new URLSearchParams({ tokenAddress: src }),
            { headers: { apikey: API_KEY } },
          );
          const approveTx = await approveRes.json();

          const approveHash = await sendTransactionAsync({
            to: approveTx.to as `0x${string}`,
            data: approveTx.data as `0x${string}`,
            value: BigInt(approveTx.value),
          });

          setStatus('waitingApproval');
          await publicClient!.waitForTransactionReceipt({ hash: approveHash });
        }
      }

      // Step 3: Execute swap
      setStatus('swapping');

      const swapRes = await fetch(
        `${API_BASE}/swap/v1/${CHAIN_ID}/swap?` +
        new URLSearchParams({
          src, dst, amount, from: address,
          slippage: slippage.toString(),
        }),
        { headers: { apikey: API_KEY } },
      );

      if (!swapRes.ok) {
        const body = await swapRes.json();
        throw new Error(body.message || 'Swap failed');
      }

      const { tx } = await swapRes.json();

      const hash = await sendTransactionAsync({
        to: tx.to as `0x${string}`,
        data: tx.data as `0x${string}`,
        value: BigInt(tx.value),
        gasPrice: BigInt(tx.gasPrice),
        gas: BigInt(tx.gas),
      });

      setStatus('waitingSwap');
      await publicClient!.waitForTransactionReceipt({ hash });

      setTxHash(hash);
      setStatus('done');
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Swap failed');
      setStatus('error');
    }
  }, [address, src, dst, amount, slippage, sendTransactionAsync, publicClient]);

  return { execute, status, txHash, error, reset };
}

Usage

import { parseUnits } from 'viem';
import { useAkkaSwap } from './hooks/useAkkaSwap';

const HYPE = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
const UBTC = '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb';

function SwapForm() {
  const [input, setInput] = useState('1');
  const amountWei = parseUnits(input || '0', 18).toString();

  const { execute, status, txHash, error, reset } = useAkkaSwap(
    HYPE, UBTC, amountWei, 1, // 1% slippage
  );

  const statusLabels: Record<string, string> = {
    idle: 'Swap',
    checking: 'Checking allowance...',
    approving: 'Approve in wallet...',
    waitingApproval: 'Waiting for approval...',
    swapping: 'Confirm swap in wallet...',
    waitingSwap: 'Waiting for confirmation...',
    done: 'Swap complete!',
    error: 'Retry',
  };

  const isWorking = ['checking', 'approving', 'waitingApproval', 'swapping', 'waitingSwap'].includes(status);

  return (
    <div>
      <input
        type="number"
        value={input}
        onChange={(e) => { setInput(e.target.value); reset(); }}
      />

      <button onClick={execute} disabled={isWorking || !input}>
        {statusLabels[status]}
      </button>

      {error && <p style={{ color: 'red' }}>{error}</p>}
      {txHash && (
        <a href={`https://hyperevmscan.io/tx/${txHash}`} target="_blank" rel="noreferrer">
          View on explorer
        </a>
      )}
    </div>
  );
}
The hook provides granular status tracking so you can show the user exactly what’s happening:
StatusMeaning
idleReady to swap
checkingChecking current token allowance
approvingWaiting for user to confirm approval in wallet
waitingApprovalApproval tx submitted, waiting for on-chain confirmation
swappingWaiting for user to confirm swap in wallet
waitingSwapSwap tx submitted, waiting for on-chain confirmation
doneSwap confirmed on-chain
errorSomething failed — check error for details

Combining Both Hooks

For a complete swap UI, use useAkkaQuote for the live price preview and useAkkaSwap for execution:
function CompleteSwapUI() {
  const [input, setInput] = useState('');
  const amountWei = input ? parseUnits(input, 18).toString() : '0';

  // Live quote as user types
  const { quote, loading: quoteLoading } = useAkkaQuote(HYPE, UBTC, amountWei);

  // Swap execution
  const { execute, status, txHash, error } = useAkkaSwap(HYPE, UBTC, amountWei, 1);

  return (
    <div>
      <input type="number" value={input} onChange={(e) => setInput(e.target.value)} />

      {quoteLoading && <p>Finding best rate...</p>}
      {quote && <p>{formatUnits(BigInt(quote.dstAmount), 18)} UBTC</p>}

      <button onClick={execute} disabled={!quote}>
        {status === 'idle' ? 'Swap' : status}
      </button>

      {error && <p>{error}</p>}
      {txHash && <a href={`https://hyperevmscan.io/tx/${txHash}`}>View tx</a>}
    </div>
  );
}