npm install viem dotenv
.env
AKKA_API_KEY=your_partner_api_key
PRIVATE_KEY=0xYourPrivateKey
RPC_URL=https://rpc.hyperliquid.xyz/evm
This example loads a private key for simplicity. Production systems should use
a wallet service, HSM, or user wallet and must keep both the private key and
AKKA API key off the client.
swap.ts
import 'dotenv/config';
import {
createPublicClient,
createWalletClient,
defineChain,
erc20Abi,
http,
parseUnits,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
const API_BASE = 'https://api.akka.finance';
const API_KEY = process.env.AKKA_API_KEY!;
const CHAIN_ID = 999;
const NATIVE = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
const SRC = NATIVE;
const DST = '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb';
const AMOUNT = parseUnits('1', 18).toString();
const chain = defineChain({
id: CHAIN_ID,
name: 'HyperEVM',
nativeCurrency: { name: 'HYPE', symbol: 'HYPE', decimals: 18 },
rpcUrls: { default: { http: [process.env.RPC_URL!] } },
});
const account = privateKeyToAccount(process.env.PRIVATE_KEY! as `0x${string}`);
const publicClient = createPublicClient({ chain, transport: http() });
const walletClient = createWalletClient({ chain, account, transport: http() });
type PartnerQuote = {
quoteId: string;
chainId: number;
sellToken: `0x${string}`;
sellAmount: string;
buyAmount: string;
payer: `0x${string}`;
recipient: `0x${string}`;
allowanceTarget: `0x${string}` | null;
expiresAt: string;
issues: Array<{ code: string; message: string }>;
validation: {
balance: { status: string };
allowance: { status: string };
gas: { status: string };
};
simulation: { status: string };
tx: {
to: `0x${string}`;
data: `0x${string}`;
value: string;
gas: string;
gasPrice: string;
type?: 'legacy' | 'eip1559';
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
};
};
async function requestQuote(): Promise<PartnerQuote> {
const params = new URLSearchParams({
src: SRC,
dst: DST,
amount: AMOUNT,
payer: account.address,
recipient: account.address,
slippage: '1',
});
const response = await fetch(
`${API_BASE}/partner/v1/${CHAIN_ID}/quote?${params}`,
{ headers: { apikey: API_KEY } },
);
if (!response.ok)
throw new Error(`AKKA ${response.status}: ${await response.text()}`);
return response.json();
}
async function approveIfRequired(quote: PartnerQuote): Promise<boolean> {
if (SRC.toLowerCase() === NATIVE || !quote.allowanceTarget) return false;
if (!quote.issues.some((issue) => issue.code === 'INSUFFICIENT_ALLOWANCE')) {
return false;
}
const hash = await walletClient.writeContract({
address: quote.sellToken,
abi: erc20Abi,
functionName: 'approve',
args: [quote.allowanceTarget, BigInt(quote.sellAmount)],
});
await publicClient.waitForTransactionReceipt({ hash });
return true;
}
async function report(
quoteId: string,
transactionHash: `0x${string}`,
status: 'SUBMITTED' | 'CONFIRMED' | 'REVERTED',
) {
await fetch(`${API_BASE}/partner/v1/executions`, {
method: 'POST',
headers: { apikey: API_KEY, 'content-type': 'application/json' },
body: JSON.stringify({
quoteId,
chainId: CHAIN_ID,
transactionHash,
status,
}),
}).catch(() => undefined); // Best-effort telemetry.
}
async function main() {
let quote = await requestQuote();
if (await approveIfRequired(quote)) quote = await requestQuote();
const unknownValidation = Object.values(quote.validation).some(
(check) => check.status === 'UNKNOWN',
);
if (quote.issues.length || unknownValidation) {
throw new Error(`Quote is not executable: ${JSON.stringify(quote.issues)}`);
}
if (Date.now() >= Date.parse(quote.expiresAt)) quote = await requestQuote();
const feeFields =
quote.tx.type === 'eip1559'
? {
maxFeePerGas: BigInt(quote.tx.maxFeePerGas!),
maxPriorityFeePerGas: BigInt(quote.tx.maxPriorityFeePerGas!),
}
: { gasPrice: BigInt(quote.tx.gasPrice) };
const hash = await walletClient.sendTransaction({
to: quote.tx.to,
data: quote.tx.data,
value: BigInt(quote.tx.value),
gas: BigInt(quote.tx.gas),
...feeFields,
});
await report(quote.quoteId, hash, 'SUBMITTED');
const receipt = await publicClient.waitForTransactionReceipt({ hash });
await report(
quote.quoteId,
hash,
receipt.status === 'success' ? 'CONFIRMED' : 'REVERTED',
);
console.log(`Swap ${receipt.status}: ${hash}`);
}
main().catch(console.error);
npx tsx swap.ts. For ERC-20 input, change SRC and use that token’s actual decimals when calling parseUnits.