Two hooks that handle the entire swap lifecycle. Copy them into your project and you have a working swap UI.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.
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>
);
}
- 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 anexecute 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>
);
}
| Status | Meaning |
|---|---|
idle | Ready to swap |
checking | Checking current token allowance |
approving | Waiting for user to confirm approval in wallet |
waitingApproval | Approval tx submitted, waiting for on-chain confirmation |
swapping | Waiting for user to confirm swap in wallet |
waitingSwap | Swap tx submitted, waiting for on-chain confirmation |
done | Swap confirmed on-chain |
error | Something failed — check error for details |
Combining Both Hooks
For a complete swap UI, useuseAkkaQuote 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>
);
}
