# Get Token by Address Source: https://docs.akka.finance/api-reference/get-token-by-address GET /{chainId}/tokens/{address} Retrieve detailed information about a specific token using its smart contract address. Returns comprehensive token data including symbol, name, decimals, logo URI, verification status, and trading metadata. Use this endpoint to look up a specific token's metadata when you already have its contract address. Returns the same data as the token list endpoint, but for a single token. # Get Token List Source: https://docs.akka.finance/api-reference/get-tokens GET /{chainId}/tokens Retrieve a paginated list of tokens available for swap in the AKKA Finance ecosystem. Supports filtering by limit and page parameters for efficient data retrieval. Returns comprehensive token information including metadata, pricing, and verification status. > [!NOTE] > > Token data is cached and refreshed periodically. Prices (`buyPriceUsd`, `sellPriceUsd`) are updated every few seconds. ## Filtering Use `verified=true` to return only tokens that have been verified by the AKKA team. This is recommended for user-facing token selectors to avoid showing low-quality or scam tokens. # Partner Chains Source: https://docs.akka.finance/api-reference/partner-chains GET /partner/v1/chains Returns only active chains with a deployed AKKA Router. Integrators should refresh this endpoint rather than treating documentation tables as a runtime registry. # Report Partner Execution Source: https://docs.akka.finance/api-reference/partner-executions POST /partner/v1/executions Best-effort telemetry only. AKKA does not trust this report as proof of on-chain state. Report the lifecycle of a transaction built from a partner quote. Submit `SUBMITTED` when it is broadcast, then `CONFIRMED` or `REVERTED` when its final outcome is known. This endpoint is best-effort telemetry. A `202` response only confirms receipt; AKKA independently treats the chain as the source of truth. Do not include calldata, signatures, API keys, or private user data in `reason`. Use the `quoteId` returned by the quote endpoint and the transaction hash produced by the wallet or executor. Reports improve per-partner execution monitoring and help identify unhealthy routes or sources. # Partner Executable Quote Source: https://docs.akka.finance/api-reference/partner-quote GET /partner/v1/{chainId}/quote Returns calldata, approval target, minimum output, lifecycle metadata, and structured payer issues in one response. > [!NOTE] > > This is the recommended endpoint for new wallet, router, and meta-aggregator > integrations. It returns an exact-input executable quote and does not > broadcast a transaction. `payer` supplies the input and sends the returned transaction. `recipient` receives the output and defaults to `payer`. A successful response can contain balance, allowance, or native gas entries in `issues`. Resolve them before broadcasting. A validation status of `UNKNOWN` means AKKA could not complete that RPC check; validate it independently. Use `simulation.status`, not HTTP status alone, to decide whether your own simulation is required. A payer that does not yet hold the sell amount or the router approval is still simulated: the estimate grants exactly what is missing through `eth_estimateGas` state overrides and lists it in `simulation.stateOverrides` (`balance`, `allowance`), while `issues` keeps reporting the shortfall. For native-ETH output to a contract recipient whose direct estimate reverts at the ETH transfer, the estimate runs with the recipient treated as a plain account and `simulation.stateOverrides` lists `recipientCode`: that gas leaves out the recipient's `receive()`, and the transaction succeeds only if the recipient accepts the ETH when it executes. `simulation.gasLimit` is the transaction gas limit to send: on `SUCCESS` it is the estimate +20%; on `INCOMPLETE` and `SKIPPED_METRIC_STATE` it is a static route plan, so never send less and simulate independently on `INCOMPLETE`. By default `simulation.gas`, `tx.gas` and the root `gas` are that same limit (`simulation.gasUsedMethod` is `limit`). `simulation.estimatedGasUsed` is the expected consumption to use for cost display. The payer gas checks (`validation.gas`, `INSUFFICIENT_GAS_BALANCE`, and the native-input balance) budget `simulation.gasLimit`, and `encodedTx` carries it. Refresh after `expiresAt`, any approval transaction, user delay, failed simulation, or nonce replacement. Executable calldata is single-use and must not be cached or retried after broadcast. Inspect `tx.type`: send only `gasPrice` for `legacy`, or only `maxFeePerGas` and `maxPriorityFeePerGas` for `eip1559`. Preserve `tx.to`, `tx.data`, and `tx.value` exactly. Set `includeRoutes=true` for the parallel route list, `includeGraph=true` for the flat on-chain hop list, or `includeCompare=true` for same-moment single-pool comparison quotes. All three views come from the same pathfinder invocation that produced `dstAmount` and `tx.data` — one call, one number, one route. `includeTokensInfo` and `includeGas` default to `true` on this endpoint. `gasPrice` and `gasLimit` override the RPC lookups. A `gasLimit` becomes the limit (on a route with a Metric leg the limit is never below the route floor) and skips the RPC estimate, so `simulation.status` is `INCOMPLETE` (`SKIPPED_METRIC_STATE` with a Metric leg). There is no `exactQuote` parameter: an executable quote is always exact. > [!NOTE] > > **Expected-gas reporting (a per-key option agreed with AKKA in writing).** For > a key with this option, `gas`, `tx.gas`, `simulation.gas` and > `estimatedGasUsed` carry the expected receipt gas: what `receipt.gasUsed` > shows, after the gas refund and with no buffer, for cost display and route > ranking. It is not a gas limit: a transaction sent with it runs out of gas. > Only `simulation.gasLimit` is the limit to send. `simulation.gasUsedMethod` > says how the expected value was made: > > * `simulateV1`: an `eth_simulateV1` of the exact transaction (the payer state > overrides of the estimate), accepted when the call succeeds and uses more > than 21,000 gas and at most the raw estimate. It is the receipt gas at the > state of the quote block. Replays of real swaps at their parent block > matched the receipt within 0.01% on 34 of 40 Ethereum swaps and exactly on > 14 of 18 HyperEVM swaps, and came out +0.004% to +1.8% on Arbitrum and up to > +532 gas on Robinhood (the L1 part included). Pool state that changes before your > transaction lands moves the receipt either way: the Ethereum replays moved > by -3.8% to +10.7% where earlier transactions of the same block touched the > pools. > * `refundRatio`: the raw `eth_estimateGas` times the chain's receipt/estimate > ratio (0.87 on Ethereum; 0.84 on Arbitrum and HyperEVM; 0.855 on Robinhood; > 0.80 on XDC), when no simulation is available. Each ratio is set above the > highest one observed on the chain, so this value errs high (typically 5–10% > over the receipt). The estimate is the gas before the refund, 1.15–1.30x the > receipt. > * `estimate`: the raw `eth_estimateGas` result, on a chain without a measured > ratio. > * `routeModel`: the gas model of the primary route, when no estimate ran. > * `limit`: no expected value exists; the limit stands in. > > More values may be added. An unknown value is opaque: it says how `gas` was > made and never changes what `gas` means for your key. Sending > `simulation.gasLimit` as the gas limit is correct in both modes. > [!WARNING] > > Keep the partner API key on a backend. Browser and mobile clients should call > an authenticated proxy owned by the integrator. # Partner Liquidity Sources Source: https://docs.akka.finance/api-reference/partner-sources GET /partner/v1/{chainId}/sources Returns protocol and pool-type coverage derived from currently active pools. Presence means the source is enabled on the chain; it does not guarantee a route for every token pair or amount. # Authentication Source: https://docs.akka.finance/authentication API keys, base URL, and rate limits ## Base URL All API requests are made to: ``` https://api.akka.finance ``` ## Try the API Every request requires an API key passed in the `apikey` header. ```bash theme={null} curl -H "apikey: basic-test-key" \ "https://api.akka.finance/partner/v1/chains" ``` > [!NOTE] > > `basic-test-key` is a shared evaluation credential for documentation examples > and initial testing. Its quota is shared by all testers, so it may return > `429 Too Many Requests`. Do not depend on it in production. ## Dedicated partner keys Production integrations receive a dedicated key and independent quota. Contact the AKKA team on [Telegram](https://t.me/akka_finance) to complete onboarding. > [!WARNING] > > Dedicated partner keys identify the partner and must remain in backend > infrastructure. Do not expose them in browser bundles, mobile applications, > public repositories, or client-visible environment variables. The shared > `basic-test-key` is intentionally public and is not a partner credential. ## Rate Limits | Tier | Limit | | - | - | | Shared `basic-test-key` | 5 requests per 60 seconds global | | Dedicated production key | Assigned during onboarding | When you exceed the rate limit, the API returns HTTP `429 Too Many Requests`. Wait for the window to reset before retrying. ## Swagger / OpenAPI The interactive Swagger UI is publicly available at: ``` https://api.akka.finance/docs ``` Viewing Swagger does not require an API key. Executing requests uses the shared `basic-test-key` by default; authorize with your dedicated key when validating a production integration. # Error Handling Source: https://docs.akka.finance/error-handling Error response format, common error codes, and troubleshooting ## Error Response Format All errors return a consistent JSON structure: ```json theme={null} { "statusCode": 400, "message": "Not enough balance. Current balance: 500000000000000000", "error": "Bad Request", "timestamp": "2025-01-15T12:00:00.000Z", "path": "/partner/v1/999/quote" } ``` | Field | Type | Description | | - | - | - | | `statusCode` | integer | HTTP status code | | `message` | string | Human-readable error description | | `error` | string | Error category | | `timestamp` | string | ISO 8601 timestamp | | `path` | string | Request path that produced the error | ## Common Errors ### 400 Bad Request | Message | Cause | Fix | | - | - | - | | `src must be an Ethereum address` | Invalid source token address | Use a valid `0x`-prefixed 40-character hex address | | `dst must be an Ethereum address` | Invalid destination token address | Same as above | | `amount should be positive integer string` | Amount is missing, negative, or not a number | Pass a positive integer string in wei | | `No route found for the given token pair` | No liquidity path exists between the two tokens | Verify both tokens exist on the chain and have liquidity | | Non-empty `issues` in a `200` response | Payer balance, gas, or allowance is insufficient | Resolve each issue and request a fresh quote | | A validation status is `UNKNOWN` | An RPC validation lookup failed | Validate that check independently before broadcasting | | `partner slippage must not exceed 5%` | Partner slippage exceeds the maximum | Use a value between 0 and 5 | ### 404 Not Found | Message | Cause | Fix | | - | - | - | | Chain not found | Unsupported chain ID | Use a [supported chain ID](/supported-chains) | | Token not found | Token does not exist on the specified chain | Verify the token address on a block explorer | | No spender contract found | No AKKA Router deployed on this chain | Use a chain with a deployed router | ### 429 Too Many Requests You exceeded the API-key or IP limit. Honor `Retry-After` when present and use bounded exponential backoff. Partner production limits are agreed during onboarding. ### 500 Internal Server Error An unexpected error occurred. If this persists, contact support on [Telegram](https://t.me/akka_finance). ## Troubleshooting > **My swap transaction reverted on-chain** > > Discard the calldata and request a fresh partner quote. Ask the user before changing slippage; never automatically retry a reverted transaction or reuse signed RFQ calldata. > **I get 'No route found' for a token I know exists** > > The token may exist on-chain but have no liquidity in any DEX pool that AKKA > indexes. Check that the token has active trading pairs on at least one > supported DEX. > **Allowance check returns 0 even after approving** > > Make sure the approval transaction was confirmed and targeted the quote's > `allowanceTarget`. Request a fresh partner quote after confirmation; the old > executable response is stale. > **Gas estimation seems too high** > > The gas limit (`simulation.gasLimit`, by default also `tx.gas` and `simulation.gas`) adds a safety buffer and may use a conservative route floor when RPC simulation cannot represent execution-time state. `simulation.estimatedGasUsed` is the expected consumption, for cost display. Inspect `simulation.status`; simulate independently when it is not `SUCCESS`. A wallet may raise the returned gas limit, but should not lower it. # FAQ Source: https://docs.akka.finance/faq Frequently asked questions about integrating with the AKKA API > **Why did my swap revert?** > > The most common reasons: > > 1. **Slippage exceeded** — The price moved between quoting and execution. Increase your slippage tolerance or retry with a fresh swap call. > 2. **Stale transaction** — Too much time passed between getting the swap data and broadcasting. Re-fetch from `/swap` and try again. > 3. **Insufficient allowance** — The token wasn't approved, or the approval hasn't confirmed yet. Check with `/approve/allowance` before swapping. > 4. **Insufficient balance** — The wallet doesn't have enough tokens to cover the swap amount plus gas. > > In all revert cases, **no tokens are lost** — you only pay the gas fee for the failed transaction. > **What is the native token address?** > > Use `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` to represent the chain's native token (HYPE on HyperEVM). This is a convention shared across most DEX aggregators. > > Native tokens don't need approval — skip the allowance check when swapping from HYPE. > **How fresh are token prices?** > > Prices in the token endpoints (`buyPriceUsd`, `sellPriceUsd`) are refreshed every **\~5 seconds** from actual DEX pool reserves. For most use cases, polling every 15–30 seconds is sufficient. > > See the [Price API guide](/guides/price-api) for a polling pattern with local caching. > **Do I need to approve tokens every time I swap?** > > No. The default approval sets an unlimited allowance for the AKKA Router contract. Once approved, you can swap that token as many times as you want without re-approving. > > Always call `/approve/allowance` first to check — if the allowance is already sufficient, skip the approval step. > **What is the rate limit?** > > **20 requests per 60 seconds** per IP address. If you exceed this, the API returns HTTP 429. > > Tips to stay within limits: > > * Debounce user input (400ms+) before fetching quotes > * Cache token lists — they don't change frequently > * Use `GET /{chainId}/tokens` to fetch all tokens in one call instead of individual lookups > **Which chains are supported?** > > Currently **HyperEVM (chain ID 999)** is the only supported chain. Ethereum, Arbitrum, and Base are coming soon. > > See [Supported Chains](/supported-chains) for details. > **What's the difference between /quote and /swap?** > > * **`/quote`** returns the expected output amount and optional route/gas info. It's read-only — use it to display prices to the user. > * **`/swap`** returns everything from `/quote` plus a ready-to-sign transaction object (`tx`). Use it when the user is ready to execute. > > Typical flow: show the user a quote as they type, then call `/swap` only when they click the swap button. > **What does the from parameter do on /swap?** > > The `from` parameter is the wallet address that will execute the transaction. It's optional — if omitted, the API builds the transaction without a `from` field, and your wallet/library will fill it in when signing. > > Providing `from` allows the API to do balance and allowance pre-checks and return more accurate gas estimates. > **Can I set a custom gas price?** > > Yes. The `/swap` endpoint accepts optional `gasPrice` and `gasLimit` parameters. If omitted, the API uses current network values. > > On HyperEVM, gas prices are low and stable — the API defaults are usually fine. Only override if you have a specific reason. > **What is encodedTx in the swap response?** > > `encodedTx` is the full transaction tuple (from, to, data, value, gasPrice, gas) ABI-encoded as a hex string. It allows frontends to decode and execute the swap without needing the router contract ABI. > > Most integrations use the `tx` object directly — `encodedTx` is an alternative for advanced use cases. > **How do I get an API key?** > > Contact the AKKA team on [Telegram](https://t.me/akka_finance) to request an API key. The key is passed as an `apikey` header on every request. > **Can I use AKKA for limit orders or DCA?** > > The API provides instant swap execution, not scheduled orders. However, you can build limit orders or DCA on top of the API: > > * **Limit orders**: Poll `/quote` at intervals and execute `/swap` when the rate hits your target. See the [Node.js guide](/guides/node-backend) for a price monitoring example. > * **DCA (Dollar Cost Averaging)**: Run a cron job or interval that executes a fixed swap at regular intervals. > **What pool types does AKKA aggregate?** > > AKKA indexes liquidity from 20+ DEX pool types on HyperEVM, including Uniswap V2/V3 forks, Algebra, Curve, Balancer, and more. The pathfinder algorithm evaluates all pools to find the optimal route — including splitting across multiple pools when that gives a better rate. > > Use `/dex-compare` to see how the aggregated rate compares to individual DEX pools. # Aggregator Integration Source: https://docs.akka.finance/guides/aggregator-integration Integrate AKKA as a liquidity source in a router, wallet, or meta-aggregator This guide is for server-side integrators such as wallets, cross-chain routers, and meta-aggregators. AKKA is an exact-input, same-chain EVM liquidity source. Use `GET /partner/v1/{chainId}/quote` as the integration contract: it combines exact pricing, executable calldata, payer checks, approval information, and lifecycle metadata in one response. ## Integration contract | Item | Value | | - | - | | Base URL | `https://api.akka.finance` | | Authentication | `apikey` request header | | Native-token sentinel | `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` | | Amount format | Base-10 integer string in the token's smallest unit | | Quote type | Exact input | | Chain scope | Same-chain swaps | | Transaction format | EVM `to`, `data`, `value`, `gasPrice`, and `gas` | | Gas limit to send | `simulation.gasLimit` | ## Partner discovery Use these endpoints instead of hardcoding availability: * `GET /partner/v1/chains` returns active chains that have a deployed AKKA Router. * `GET /partner/v1/{chainId}/sources` returns protocol and pool-type coverage derived from active pools. * `GET /{chainId}/tokens?verified=true` returns current verified token metadata. ## One-call executable quote ```bash theme={null} curl -G "https://api.akka.finance/partner/v1/1/quote" \ -H "apikey: YOUR_PARTNER_API_KEY" \ --data-urlencode "src=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" \ --data-urlencode "dst=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" \ --data-urlencode "amount=10000000000000000" \ --data-urlencode "payer=0xPARTNER_EXECUTOR_OR_USER" \ --data-urlencode "recipient=0xOUTPUT_RECIPIENT" \ --data-urlencode "slippage=0.5" ``` `payer` is the address that sends `tx` and supplies the input tokens. `recipient` receives the output and defaults to `payer`. This supports partner executor contracts without forcing output to remain in the executor. The response includes `quoteId`, `quotedAtBlock`, `generatedAt`, `expiresAt`, `validForSeconds`, `validitySource`, `buyAmount`, `minAmountOut`, `allowanceTarget`, structured `simulation` and `validation`, `issues`, and the transaction `tx` to broadcast with `simulation.gasLimit` as its gas limit. An HTTP `200` with non-empty `issues` is a valid quote that requires payer action before execution. A validation status of `UNKNOWN` means an RPC check failed and the partner must validate independently. ## Gas fields * `simulation.gasLimit` is the transaction gas limit to send: the RPC estimate +20% on `SUCCESS`, else a static route plan. A wallet may raise it but must not lower it; on `SKIPPED_METRIC_STATE` a lower limit can run out of gas. The payer gas checks (`validation.gas`, `INSUFFICIENT_GAS_BALANCE`, the native-input balance) and `encodedTx` use this limit. * `gas`, `tx.gas` and `simulation.gas` are one number. By default it is the same limit, and `simulation.gasUsedMethod` is `limit`. * `simulation.estimatedGasUsed` is the expected consumption with no buffer, for cost display: the RPC estimate on `SUCCESS`, else the primary route plan. ### Expected-gas reporting A partner that ranks AKKA net of gas and builds its own gas limit can agree expected-gas reporting for its key with the AKKA team. It is a per-key option: AKKA turns it on only with the partner's written agreement and an agreed effective date, and it changes the gas fields of that key only. With it, `gas`, `tx.gas`, `simulation.gas` and `estimatedGasUsed` carry the expected receipt gas: what `receipt.gasUsed` will show, after the gas refund, with no buffer, at the state of the quote block. It is not a gas limit. On `SUCCESS` it is an `eth_simulateV1` of the exact transaction (`simulation.gasUsedMethod` = `simulateV1`), else the raw estimate times the chain's receipt/estimate ratio, set to err high (`refundRatio`), or the raw estimate on a chain without a ratio (`estimate`). Without an estimate it is the gas model of the primary route without the margins that size the limit (`routeModel`). `limit` means no expected value existed and the limit stands in. `simulation.gasLimit` stays the limit to send. Code that sends `simulation.gasLimit` as the gas limit works in both modes. ## Transaction fee model Inspect `tx.type` before submitting: ```typescript theme={null} const feeFields = quote.tx.type === 'eip1559' ? { maxFeePerGas: BigInt(quote.tx.maxFeePerGas), maxPriorityFeePerGas: BigInt(quote.tx.maxPriorityFeePerGas), } : { gasPrice: BigInt(quote.tx.gasPrice) }; await walletClient.sendTransaction({ to: quote.tx.to, data: quote.tx.data, value: BigInt(quote.tx.value), // simulation.gasLimit: the limit to send. tx.gas is the same limit on a // response without it (an older deployment). gas: BigInt(quote.simulation.gasLimit ?? quote.tx.gas), ...feeFields, }); ``` Do not submit both legacy `gasPrice` and EIP-1559 fields in one transaction. ## Contract executor integration When a partner contract calls AKKA, that contract—not the end user—is the API `payer` and the router's `msg.sender`. It must hold the input tokens and approve the AKKA Router. The end user may remain the `recipient`. See the public [Partner Executor Contract](/guides/partner-executor-contract) for a complete, copyable Solidity reference. It demonstrates exact per-call approval, ERC-20 input collection, native-value forwarding, reentrancy protection, router allowlisting, and delivery to the recipient already encoded in `tx.data`. Partners must audit, adapt, deploy, and operate their own version; AKKA does not deploy it for partners. ## RFQ calldata safety Executable quotes may contain signed Bebop, Hashflow, Native, or other RFQ calldata. Treat every response as short-lived and single-use: * Never cache or persist executable calldata for later execution. * Never broadcast the same response concurrently from multiple workers. * Never automatically retry a reverted transaction with the old calldata. * Refresh after user delay, `expiresAt`, failed simulation, or nonce replacement. * Do not edit RFQ targets, offsets, amounts, signatures, or nested route data. * Use `quoteId` for logs and support correlation, not as an idempotency key. `validitySource=ADVISORY` means the API refresh window is conservative metadata rather than a universal on-chain deadline. Route-specific signatures may expire earlier; execute immediately or refresh. Keep the API key on your backend. Contact the [AKKA team](https://t.me/akka_finance) for a partner key and production rate limit. ## Recommended request flow **1. Discover support** Read `GET /partner/v1/chains` and `GET /partner/v1/{chainId}/sources`. Fetch token metadata from `GET /{chainId}/tokens?verified=true`. Cache discovery data, never executable quotes. **2. Request the partner quote** Call `GET /partner/v1/{chainId}/quote` with `payer`, `recipient`, and exact-input `amount`. Use `buyAmount` for ranking and keep the complete response together as one executable unit. **3. Resolve payer issues** Block execution on non-empty `issues`. For `INSUFFICIENT_ALLOWANCE`, approve the response's `allowanceTarget`, wait for confirmation, then request a new quote. Independently verify validation checks marked `UNKNOWN`. **4. Validate and broadcast immediately** Verify the chain, payer, recipient, expiry, and router allowlist. Simulate where supported, then broadcast `tx` without changing `to`, `data`, or `value`, with `simulation.gasLimit` as the gas limit. Never reuse or retry old calldata. **5. Report the receipt** Send `SUBMITTED`, followed by `CONFIRMED` or `REVERTED`, to `POST /partner/v1/executions`. On-chain receipts remain the source of truth. ## Minimal server-side adapter ```typescript akka-source.ts theme={null} const AKKA_API = 'https://api.akka.finance'; export class AkkaPartnerSource { constructor(private readonly apiKey: string) {} private async get( path: string, params: Record, ): Promise { const url = new URL(path, AKKA_API); for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value); const response = await fetch(url, { headers: { apikey: this.apiKey }, signal: AbortSignal.timeout(10_000), }); const body = await response.json(); if (!response.ok) { throw new Error( `AKKA ${response.status}: ${body.message ?? JSON.stringify(body)}`, ); } return body as T; } quote( chainId: number, src: string, dst: string, amount: bigint, payer: string, recipient: string, slippagePercent: number, ) { return this.get<{ quoteId: string; buyAmount: string; minAmountOut: string; expiresAt: string; issues: Array<{ code: string; message: string }>; allowanceTarget: string | null; simulation: { status: 'SUCCESS' | 'INCOMPLETE' | 'SKIPPED_METRIC_STATE'; gas: string; // equal to tx.gas: the limit unless expected-gas reporting gasLimit?: string; // the gas limit to send (absent before 2026-10: use tx.gas) gasUsedMethod?: string; // 'limit', 'simulateV1', 'refundRatio', 'estimate', 'routeModel', ... estimatedGasUsed?: string; // expected consumption }; tx: { from?: string; to: string; data: string; value: string; gasPrice: string; gas: string; // = simulation.gas; send simulation.gasLimit type?: 'legacy' | 'eip1559'; maxFeePerGas?: string; maxPriorityFeePerGas?: string; }; }>(`/partner/v1/${chainId}/quote`, { src, dst, amount: amount.toString(), payer, recipient, slippage: slippagePercent.toString(), }); } } ``` ## Production behavior * Set a client timeout and treat timeouts, `429`, and `5xx` as retryable for quote discovery. Use exponential backoff with jitter and do not retry a signed or broadcast transaction automatically. * Refresh the executable swap response after a user delay. Do not cache executable calldata. * Compare integer amounts with `bigint`; never use floating-point values for token quantities. * A successful HTTP response may include payer problems in `issues` or `UNKNOWN` validation states. Resolve or independently verify them before broadcast. * Simulate the returned transaction when your execution stack supports it. A simulation can still become stale as pool state changes. * Log partner ID, quote ID, chain ID, token addresses, input amount, returned output, latency, simulation status, issue codes, and transaction hash. Never log API keys, private keys, or full signed RFQ calldata. * After broadcasting, send `POST /partner/v1/executions` with the quote ID, chain ID, transaction hash, and `SUBMITTED`; follow with `CONFIRMED` or `REVERTED`. Reporting is best-effort telemetry and never replaces checking the chain receipt. ## Versioning contract `/partner/v1` is additive-only. AKKA may add optional fields or enum values but will not remove fields, change units, or change existing field meaning within v1. Breaking changes require a new versioned path, a migration guide, and advance notice under the partner agreement. Integrators must ignore unknown response fields and enum values safely. One exception is a per-key option agreed with AKKA in writing: [expected-gas reporting](#expected-gas-reporting) changes what `gas`, `tx.gas`, `simulation.gas` and `estimatedGasUsed` carry for that key only, from the agreed effective date. Every other key keeps the v1 meaning. `simulation.gasLimit` is the limit to send in both modes. ## Current limitations AKKA currently exposes exact-input, same-chain swaps. There is no exact-output quote, cross-chain status lifecycle, Permit2 flow, gasless transaction, integrator-fee parameter, source allow/deny filter, webhook, or trade-history endpoint. If your integration requires one of these, coordinate the adapter behavior with the AKKA team before launch. ## Launch checklist * Obtain a dedicated partner API key and agreed production limits. * Test native-to-ERC-20, ERC-20-to-native, and ERC-20-to-ERC-20 swaps on each enabled chain. * Confirm approval, insufficient-balance, no-route, timeout, rate-limit, stale-price, and on-chain-revert behavior. * Allowlist the active router contracts from [Smart Contracts](/smart-contracts), while still executing against `tx.to` returned by the API. * Agree on monitoring, incident escalation, and how AKKA should be named and displayed as a liquidity source. # Slippage & Best Practices Source: https://docs.akka.finance/guides/best-practices How to choose slippage, handle errors, and build reliable swap integrations ## Slippage Slippage is the maximum price difference you're willing to accept between the quoted rate and the executed rate. It protects against price movement during the time between quoting and on-chain execution. ### Choosing a slippage value | Slippage | When to use | | - | - | | **0.1–0.5%** | Stablecoins or high-liquidity pairs (e.g., USDC → USDT) | | **0.5–1%** | Standard pairs with good liquidity (e.g., HYPE → USDC) | | **1–3%** | Lower-liquidity tokens or volatile markets | | **3–5%** | Very low liquidity or fast-moving prices | | **> 5%** | Generally not recommended — consider splitting into smaller trades | Pass slippage as a percentage (not a fraction) to the partner quote endpoint: ```bash theme={null} # 1% slippage curl -H "apikey: YOUR_API_KEY" \ "https://api.akka.finance/partner/v1/999/quote?src=0x...&dst=0x...&amount=1000000000000000000&payer=0x...&recipient=0x...&slippage=1" ``` The partner API allows `0` to `5`. Up to 4 decimal places are supported (for example `0.5` or `1.25`). ### What happens when slippage is exceeded The on-chain transaction **reverts**. You pay gas but no tokens are swapped and no tokens are lost. The user keeps their full balance. *** ## Quote and execution timing The partner quote returns both the output and transaction. Pool and RFQ state can still change before confirmation. **Best practice:** A display quote may be shown while the user reviews, but request a fresh partner quote when they click "Swap" and execute it immediately. ```typescript theme={null} // Display response is informational and will not be reused. const displayQuote = await getPartnerQuote(input); // ... user reviews and clicks "Swap" ... const executableQuote = await getPartnerQuote(input); await sendQuote(executableQuote); ``` `sendQuote` sends `to`, `data` and `value` unchanged, with `simulation.gasLimit` as the gas limit. By default that is also `tx.gas`; with expected-gas reporting (a per-key option agreed with AKKA) `tx.gas` is the expected receipt gas (after the refund), and a transaction with that limit runs out of gas. Sending `simulation.gasLimit` is correct in both modes; `tx.gas` stands in only on a response without it. ```typescript theme={null} function sendQuote(quote: PartnerQuote) { const { tx, simulation } = quote; return walletClient.sendTransaction({ to: tx.to as `0x${string}`, data: tx.data as `0x${string}`, value: BigInt(tx.value), gas: BigInt(simulation.gasLimit ?? tx.gas), ...(tx.type === 'eip1559' ? { maxFeePerGas: BigInt(tx.maxFeePerGas), maxPriorityFeePerGas: BigInt(tx.maxPriorityFeePerGas), } : { gasPrice: BigInt(tx.gasPrice) }), }); } ``` *** ## Handling reverts If a swap transaction reverts on-chain, it's usually one of these: | Cause | Solution | | - | - | | Price moved beyond slippage tolerance | Ask the user whether to change slippage, then request a fresh quote | | Stale quote or RFQ signature | Request a completely new partner quote | | Insufficient gas | Send `simulation.gasLimit` as the gas limit, never lower | | Insufficient balance | Check the user's balance before initiating the swap | | Insufficient allowance | The token wasn't approved, or the approval transaction hasn't confirmed yet | ### Safe retry pattern ```typescript theme={null} async function requestAndSend(input: QuoteInput) { const quote = await getPartnerQuote(input); if (quote.issues.length) throw new Error(quote.issues[0].message); if (Date.now() >= Date.parse(quote.expiresAt)) { throw new Error('Refresh required'); } return sendQuote(quote); } ``` Retry network failures while requesting a quote with bounded backoff. After a transaction is signed, broadcast, or reverted, never reuse its calldata and never silently raise the user's slippage; obtain fresh user intent and a fresh quote. *** ## Gas on HyperEVM The partner response returns `simulation.gasLimit`, the gas limit to send (by default also `tx.gas`), plus either legacy or EIP-1559 fee fields. Inspect `tx.type` and never submit both fee models. ```typescript theme={null} const { tx, simulation } = await getPartnerQuote({ src, dst, amount, payer, slippage, }); const feeFields = tx.type === 'eip1559' ? { maxFeePerGas: BigInt(tx.maxFeePerGas), maxPriorityFeePerGas: BigInt(tx.maxPriorityFeePerGas), } : { gasPrice: BigInt(tx.gasPrice) }; await walletClient.sendTransaction({ to: tx.to as `0x${string}`, data: tx.data as `0x${string}`, value: BigInt(tx.value), gas: BigInt(simulation.gasLimit ?? tx.gas), ...feeFields, }); ``` If you need to override gas, set a higher limit than `simulation.gasLimit` — never lower: ```typescript theme={null} const gasWithBuffer = (BigInt(simulation.gasLimit ?? tx.gas) * 130n) / 100n; // 30% buffer ``` *** ## Rate limiting Partner limits are assigned with the API key. Confirm the production quota during onboarding. To stay within it: * **Debounce quote requests** — Don't re-fetch on every keystroke. Do not cache executable responses in a CDN, service worker, or persistent store. * **Poll at reasonable intervals** — For price feeds, 15–30 seconds is sufficient. Prices refresh every \~5 seconds on the backend. * **Batch token lookups** — Use `GET /{chainId}/tokens` to fetch all tokens in one call instead of fetching individually. If you hit the rate limit, the API returns HTTP 429. Back off and retry: ```typescript theme={null} async function fetchWithBackoff( url: string, headers: Record, ): Promise { const res = await fetch(url, { headers }); if (res.status === 429) { await new Promise((r) => setTimeout(r, 5000)); // wait 5 seconds return fetchWithBackoff(url, headers); } if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } ``` *** ## Approval best practices * **Use the returned target** — Approve only the `allowanceTarget` returned by the partner quote; never assume one router address applies forever. * **Prefer exact approval** — Approving `sellAmount` limits exposure. Partners may choose a larger allowance only after their own security review. * **Wait and refresh** — After approval confirmation, request a new partner quote before swapping. * **Native tokens skip approval** — HYPE (`0xeeee...eeee`) doesn't need approval. Skip the allowance check for native tokens. ```typescript theme={null} const NATIVE = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; if (src.toLowerCase() !== NATIVE) { const quote = await getPartnerQuote(input); if (quote.issues.some((issue) => issue.code === 'INSUFFICIENT_ALLOWANCE')) { const hash = await approve( src, quote.allowanceTarget, BigInt(quote.sellAmount), ); await waitForReceipt(hash); // wait before swapping // Request a new quote; do not use the pre-approval calldata. } } ``` *** ## Amounts and decimals All amounts in the API are in **wei** (the token's smallest unit). Each token has its own `decimals` value. | Token | Decimals | 1.0 in wei | | - | - | - | | HYPE | 18 | `1000000000000000000` | | USDC | 6 | `1000000` | | UBTC | 18 | `1000000000000000000` | Use viem's `parseUnits` and `formatUnits` to convert: ```typescript theme={null} import { parseUnits, formatUnits } from 'viem'; // Human → wei const amountWei = parseUnits('1.5', 18).toString(); // "1500000000000000000" // Wei → human const amountHuman = formatUnits(BigInt('1500000000000000000'), 18); // "1.5" ``` > [!WARNING] > > Always use the token's actual `decimals` value from the API — don't hardcode > 18\. Some tokens use 6, 8, or other values. # Complete Partner Swap Example Source: https://docs.akka.finance/guides/first-swap A copy-ready TypeScript example using the partner quote endpoint This server-side example requests one executable partner quote, resolves an ERC-20 approval when needed, refreshes the quote, broadcasts it, and reports the receipt. ```bash theme={null} npm install viem dotenv ``` ```bash .env theme={null} AKKA_API_KEY=your_partner_api_key PRIVATE_KEY=0xYourPrivateKey RPC_URL=https://rpc.hyperliquid.xyz/evm ``` > [!WARNING] > > 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. ```typescript swap.ts theme={null} 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; gas: string; // equal to tx.gas gasLimit?: string; // the gas limit to send (absent before 2026-10: use tx.gas) gasUsedMethod?: string; // 'limit' unless expected-gas reporting is on }; tx: { to: `0x${string}`; data: `0x${string}`; value: string; gas: string; // send simulation.gasLimit as the limit gasPrice: string; type?: 'legacy' | 'eip1559'; maxFeePerGas?: string; maxPriorityFeePerGas?: string; }; }; async function requestQuote(): Promise { 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 { 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.simulation.gasLimit ?? 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); ``` Run it with `npx tsx swap.ts`. For ERC-20 input, change `SRC` and use that token's actual decimals when calling `parseUnits`. # MCP Server (AI Agents) Source: https://docs.akka.finance/guides/mcp-server Connect AI agents like Claude, Cursor, and VS Code Copilot to AKKA's DEX aggregation via the Model Context Protocol AKKA provides an official [MCP server](https://github.com/Akka-Finance/akka-mcp-server) that lets AI agents get swap quotes, compare DEX prices, and build unsigned transactions — all through natural language. ## What is MCP? The [Model Context Protocol](https://modelcontextprotocol.io/) is an open standard that connects AI assistants to external tools. With AKKA's MCP server, an AI agent can: * Get the best swap quote across 25+ DEXes * Compare prices across individual liquidity pools * Build unsigned swap and approval transactions * Look up token details and supported chains The server is a thin client that calls the AKKA REST API. It does not hold private keys or execute transactions — it only returns unsigned transaction data. ## Prerequisites An AKKA API key is required. [Get your API key here](/authentication). ## Install **Claude Desktop** Add to your `claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "akka-dex": { "command": "npx", "args": ["-y", "@akka-finance/mcp-server"], "env": { "AKKA_API_KEY": "your-api-key" } } } } ``` **Claude Code** ```bash theme={null} claude mcp add akka-dex -e AKKA_API_KEY=your-api-key -- npx -y @akka-finance/mcp-server ``` **Cursor** Add to `.cursor/mcp.json`: ```json theme={null} { "mcpServers": { "akka-dex": { "command": "npx", "args": ["-y", "@akka-finance/mcp-server"], "env": { "AKKA_API_KEY": "your-api-key" } } } } ``` **VS Code** Add to `.vscode/mcp.json`: ```json theme={null} { "servers": { "akka-dex": { "command": "npx", "args": ["-y", "@akka-finance/mcp-server"], "env": { "AKKA_API_KEY": "your-api-key" } } } } ``` ## Available tools Once connected, your AI agent has access to these tools: | Tool | Description | | - | - | | `akka_get_quote` | Get the best swap quote across all supported DEXes | | `akka_get_swap` | Build an unsigned swap transaction | | `akka_dex_compare` | Compare quotes across individual DEX pools | | `akka_get_spender` | Get the router contract address for token approval | | `akka_get_approve_tx` | Build an ERC-20 approve transaction | | `akka_check_allowance` | Check current token spending allowance | | `akka_list_tokens` | List tradeable tokens on a chain | | `akka_get_token` | Get token details by address | | `akka_list_chains` | List all supported chains | ## Example conversations Once the MCP server is connected, you can ask your AI agent things like: > **Get a swap quote** > > **You:** "What's the best rate for swapping 1 ETH to USDC on Arbitrum?" > > The agent calls `akka_get_quote` with the correct chain ID, token addresses, and amount in wei, then shows you the expected output. > **Compare DEX prices** > > **You:** "Compare prices for 1000 USDC to WETH across all DEXes on Base" > > The agent calls `akka_dex_compare` and presents a table showing which DEX offers the best rate. > **Build a swap transaction** > > **You:** "Build a swap transaction for 0.5 HYPE to USDC on HyperEVM for wallet 0xabc..." > > The agent calls `akka_get_quote` first, confirms the rate with you, then calls `akka_get_swap` to return the unsigned transaction data you can sign and broadcast. ## Configuration The MCP server can be configured via environment variables: | Variable | Default | Description | | - | - | - | | `AKKA_API_BASE` | `https://api.akka.finance` | AKKA API base URL | | `AKKA_API_KEY` | **required** | API key for AKKA Finance API | | `AKKA_MCP_TRANSPORT` | `stdio` | Transport: `stdio` or `http` | | `AKKA_MCP_PORT` | `3100` | Port for HTTP transport | | `AKKA_TIMEOUT` | `15000` | Request timeout in ms | ### HTTP transport For remote or web-based agents, run the server with HTTP transport: ```bash theme={null} npx @akka-finance/mcp-server --transport=http --port=3100 ``` This exposes a Streamable HTTP endpoint at `http://localhost:3100/mcp`. ## Architecture ``` AI Agent (Claude, Cursor, VS Code, etc.) ↕ MCP Protocol (stdio or HTTP) AKKA MCP Server (@akka-finance/mcp-server) ↕ HTTP REST AKKA Finance API (api.akka.finance) ↕ On-chain 25+ DEXes across EVM chains ``` The MCP server translates natural language tool calls into AKKA API requests and formats the responses for the AI agent. All swap and approve tools return **unsigned transaction data** — signing and broadcasting is always the user's responsibility. ## Links * [npm package](https://www.npmjs.com/package/@akka-finance/mcp-server) * [GitHub repository](https://github.com/Akka-Finance/akka-mcp-server) * [MCP Registry](https://registry.modelcontextprotocol.io) * [Model Context Protocol](https://modelcontextprotocol.io/) # Node.js Partner Client Source: https://docs.akka.finance/guides/node-backend A reusable server-side client for executable AKKA partner quotes Use the partner API for bots, payment processors, wallets, and aggregators. It provides a one-call exact-input quote and transaction-building flow. ```bash theme={null} npm install viem ``` ## Reusable client ```typescript akka-client.ts theme={null} export type PartnerQuote = { quoteId: string; chainId: number; sellToken: `0x${string}`; buyToken: `0x${string}`; sellAmount: string; buyAmount: string; minAmountOut: string; allowanceTarget: `0x${string}` | null; expiresAt: string; issues: Array<{ code: string; message: string }>; validation: Record<'balance' | 'allowance' | 'gas', { status: string }>; simulation: { status: string; gas: string; // equal to tx.gas (the limit unless expected-gas reporting) gasLimit?: string; // the gas limit to send (absent before 2026-10: use tx.gas) gasUsedMethod?: string; // how gas was made: 'limit', 'simulateV1', ... estimatedGasUsed?: string; // expected consumption stateOverrides?: Array<'balance' | 'allowance' | 'recipientCode'>; }; tx: { to: `0x${string}`; data: `0x${string}`; value: string; gas: string; gasPrice: string; type?: 'legacy' | 'eip1559'; maxFeePerGas?: string; maxPriorityFeePerGas?: string; }; }; export class AkkaPartnerClient { constructor( private readonly apiKey: string, private readonly baseUrl = 'https://api.akka.finance', ) {} private async request(path: string, init?: RequestInit): Promise { const response = await fetch(`${this.baseUrl}${path}`, { ...init, headers: { apikey: this.apiKey, ...init?.headers }, signal: AbortSignal.timeout(10_000), }); const body = await response.json(); if (!response.ok) throw new Error(`AKKA ${response.status}: ${body.message}`); return body; } quote(input: { chainId: number; src: string; dst: string; amount: bigint; payer: string; recipient?: string; slippage?: number; }) { const params = new URLSearchParams({ src: input.src, dst: input.dst, amount: input.amount.toString(), payer: input.payer, recipient: input.recipient ?? input.payer, slippage: String(input.slippage ?? 1), }); return this.request( `/partner/v1/${input.chainId}/quote?${params}`, ); } chains() { return this.request< Array<{ chainId: number; routerAddress: string; routerCodeHash: string | null; operationalStatus: string; }> >('/partner/v1/chains'); } sources(chainId: number) { return this.request>( `/partner/v1/${chainId}/sources`, ); } async report(input: { quoteId: string; chainId: number; transactionHash: string; status: 'SUBMITTED' | 'CONFIRMED' | 'REVERTED'; reason?: string; }) { await this.request('/partner/v1/executions', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(input), }).catch(() => undefined); // Reporting must not block receipt processing. } } ``` ## Usage ```typescript theme={null} const akka = new AkkaPartnerClient(process.env.AKKA_API_KEY!); const quote = await akka.quote({ chainId: 999, src: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', dst: '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb', amount: 1_000_000_000_000_000_000n, payer: account.address, slippage: 1, }); if (quote.issues.length) throw new Error(quote.issues[0].message); if ( Object.values(quote.validation).some((check) => check.status === 'UNKNOWN') ) { throw new Error('Validate payer state through your RPC before execution'); } 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.simulation.gasLimit ?? quote.tx.gas), ...feeFields, }); await akka.report({ quoteId: quote.quoteId, chainId: 999, transactionHash: hash, status: 'SUBMITTED', }); ``` Refresh instead of executing whenever the quote expired, the user or scheduler delayed, simulation failed, or an approval was mined after the quote. Executable RFQ calldata is single-use and must not be cached or automatically retried. # Partner Executor Contract Source: https://docs.akka.finance/guides/partner-executor-contract A public Solidity reference for executing AKKA partner quotes through your own contract. Use this reference only when your integration executes the transaction returned by `GET /partner/v1/{chainId}/quote` through a partner-owned smart contract. If an EOA sends the returned transaction directly, you do not need an executor contract. This source is published here so integrators do not need access to AKKA's private contract repository. It is an integration example, not a production deployment or an audited library. Your team must review, test, deploy, own, and monitor its adapted version. ## Execution model * Set the quote request's `payer` to the executor contract address. * The executor must be the address that calls `quote.tx.to`. * For ERC-20 input, the user approves the executor; the executor pulls the exact input amount and grants the allowlisted AKKA Router an exact, per-call approval. * For native input, forward exactly `quote.tx.value`. * The output recipient is already encoded in `quote.tx.data`; do not modify the returned calldata. * Allowlist only active router addresses returned by `GET /partner/v1/chains`, and validate their published bytecode hashes before enabling them. ## Solidity reference The example targets Solidity `0.8.20` and OpenZeppelin Contracts 5.x. ```solidity AkkaPartnerExecutor.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.20; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; /// @notice Reference adapter for partners that execute AKKA calldata through /// their own contract. This is an integration example, not an AKKA deployment. /// The partner must audit it and configure the exact AKKA Router addresses it /// is willing to call. contract AkkaPartnerExecutor is Ownable, ReentrancyGuard { using SafeERC20 for IERC20; mapping(address router => bool allowed) public allowedRouter; error RouterNotAllowed(); error RouterCallFailed(bytes reason); error InvalidNativeValue(); event RouterPermissionSet(address indexed router, bool allowed); constructor(address owner_) Ownable(owner_) {} receive() external payable {} function setRouterAllowed(address router, bool allowed) external onlyOwner { allowedRouter[router] = allowed; emit RouterPermissionSet(router, allowed); } /// @notice Pull ERC-20 input from the user, approve the allowlisted AKKA /// Router for exactly this call, and execute partner API `tx.data`. /// The output recipient is already encoded in the calldata. function executeERC20( address router, IERC20 tokenIn, uint256 amountIn, bytes calldata routerCalldata ) external nonReentrant returns (bytes memory result) { if (!allowedRouter[router]) revert RouterNotAllowed(); uint256 balanceBefore = tokenIn.balanceOf(address(this)); tokenIn.safeTransferFrom(msg.sender, address(this), amountIn); tokenIn.forceApprove(router, amountIn); (bool ok, bytes memory returnData) = router.call(routerCalldata); tokenIn.forceApprove(router, 0); if (!ok) revert RouterCallFailed(returnData); uint256 balanceAfter = tokenIn.balanceOf(address(this)); if (balanceAfter > balanceBefore) { tokenIn.safeTransfer(msg.sender, balanceAfter - balanceBefore); } return returnData; } /// @notice Execute a native-input partner quote. `msg.value` must equal /// the `tx.value` returned by the partner API. function executeNative( address router, bytes calldata routerCalldata, uint256 expectedValue ) external payable nonReentrant returns (bytes memory result) { if (!allowedRouter[router]) revert RouterNotAllowed(); if (msg.value != expectedValue) revert InvalidNativeValue(); (bool ok, bytes memory returnData) = router.call{value: msg.value}(routerCalldata); if (!ok) revert RouterCallFailed(returnData); return returnData; } } ``` ## Before production At minimum, partners should add tests for router allowlisting, fee-on-transfer and non-standard tokens relevant to their product, native-value mismatches, refund behavior, malicious-token callbacks, paused execution, and router upgrades. Use a multisig or governed role for router permissions and define an emergency process to disable a router immediately. AKKA does not deploy this executor for partners. A contract is optional, and when a partner chooses this model, its deployment, audit, upgrades, and operational controls remain the partner's responsibility. # Partner Launch & Conformance Source: https://docs.akka.finance/guides/partner-launch Provision partner environments, validate known routes, and agree production service targets Use this checklist with the [Aggregator Integration](/guides/aggregator-integration) guide before enabling AKKA in production routing. ## Environment provisioning AKKA issues separate credentials per partner and environment. Request the following from the AKKA team: * A staging base URL and staging API key. * A production API key with an agreed request-rate and burst limit. * Source display name, icon, support channel, and incident escalation contacts. * Funded test payer addresses controlled by the partner. Never share private keys with AKKA. Keys must be supplied through the `apikey` header and stored only in a backend secret manager. Staging and production keys must not be interchangeable. The API gateway must authenticate the key, remove any client-supplied `x-akka-partner-id` and `x-akka-gateway-auth`, then inject the canonical partner ID and the backend-only gateway secret before forwarding. The backend ignores partner identity unless `x-akka-gateway-auth` matches `PARTNER_GATEWAY_SECRET` using a timing-safe comparison. ## Known conformance pairs These verified token pairs exercise the three transaction shapes. Amounts are examples in smallest units; the payer must also hold native gas. | Chain | Case | Source | Destination | Example amount | | - | - | - | - | - | | Ethereum (`1`) | ERC-20 → ERC-20 | WETH `0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2` | USDC `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` | `10000000000000000` | | HyperEVM (`999`) | Native → ERC-20 | `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` | USDC `0xb88339cb7199b77e23db6e890353e22632ba630f` | `100000000000000000` | | HyperEVM (`999`) | ERC-20 → native | WHYPE `0x5555555555555555555555555555555555555555` | `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` | `100000000000000000` | | Robinhood (`4663`) | ERC-20 → ERC-20 | WETH `0x0bd7d308f8e1639fab988df18a8011f41eacad73` | USDG `0x5fc5360d0400a0fd4f2af552add042d716f1d168` | `10000000000000000` | Route availability is dynamic. A no-route result is not a conformance failure unless AKKA and the partner have designated the case as funded and continuously monitored. ## Automated conformance runner Use the standalone Node.js script below; access to the private AKKA repository is not required. When manually run, it always validates the active-chain and liquidity-source discovery responses. It validates an executable quote only when every required quote variable is provided. The script requests transaction data but never signs or broadcasts a transaction. Copy this into a local file named `partner-conformance.js`: > **Show partner-conformance.js** > > ```javascript theme={null} > const assert = require('node:assert/strict'); > > const baseUrl = process.env.PARTNER_BASE_URL; > const apiKey = process.env.PARTNER_API_KEY; > > if (!baseUrl || !apiKey) { > throw new Error('Set PARTNER_BASE_URL and PARTNER_API_KEY'); > } > > async function get(path, params = {}) { > const url = new URL(path, baseUrl); > for (const [key, value] of Object.entries(params)) { > url.searchParams.set(key, value); > } > > const response = await fetch(url, { headers: { apikey: apiKey } }); > const body = await response.json(); > assert.equal( > response.ok, > true, > `${response.status}: ${JSON.stringify(body)}`, > ); > return body; > } > > async function main() { > const chains = await get('/partner/v1/chains'); > assert.ok(Array.isArray(chains) && chains.length > 0, 'no active chains'); > > for (const chain of chains) { > assert.equal(typeof chain.chainId, 'number'); > assert.match(chain.routerAddress, /^0x[0-9a-fA-F]{40}$/); > const sources = await get(`/partner/v1/${chain.chainId}/sources`); > assert.ok(Array.isArray(sources), 'sources must be an array'); > } > > const requiredQuoteVariables = [ > 'PARTNER_CHAIN_ID', > 'PARTNER_SRC', > 'PARTNER_DST', > 'PARTNER_AMOUNT', > 'PARTNER_PAYER', > ]; > > if (requiredQuoteVariables.every((name) => process.env[name])) { > const quote = await get( > `/partner/v1/${process.env.PARTNER_CHAIN_ID}/quote`, > { > src: process.env.PARTNER_SRC, > dst: process.env.PARTNER_DST, > amount: process.env.PARTNER_AMOUNT, > payer: process.env.PARTNER_PAYER, > recipient: process.env.PARTNER_RECIPIENT || process.env.PARTNER_PAYER, > slippage: process.env.PARTNER_SLIPPAGE || '1', > }, > ); > > assert.match(quote.quoteId, /^[0-9a-f-]{36}$/i); > assert.ok(BigInt(quote.buyAmount) > 0n); > assert.ok(BigInt(quote.minAmountOut) > 0n); > assert.match(quote.tx.to, /^0x[0-9a-fA-F]{40}$/); > assert.match(quote.tx.data, /^0x[0-9a-fA-F]+$/); > assert.ok(Array.isArray(quote.issues)); > assert.ok(quote.validation && quote.simulation); > > // simulation.gasLimit is the gas limit to send. tx.gas and > // simulation.gas are one figure: that limit by default > // (gasUsedMethod 'limit'), or the expected receipt gas (after the > // refund) when expected-gas reporting is agreed for the key. > assert.match(quote.simulation.gasLimit, /^[1-9]\d*$/); > assert.equal(quote.tx.gas, quote.simulation.gas); > assert.ok( > BigInt(quote.simulation.gasLimit) >= BigInt(quote.simulation.gas), > 'simulation.gasLimit is below simulation.gas', > ); > if (quote.simulation.gasUsedMethod === 'limit') { > assert.equal(quote.simulation.gas, quote.simulation.gasLimit); > } > } > > console.log('Partner API conformance checks passed'); > } > > main().catch((error) => { > console.error(error); > process.exitCode = 1; > }); > ``` Run discovery-only validation with Node.js 18 or newer: ```bash theme={null} PARTNER_BASE_URL=https://PARTNER_STAGING_URL \ PARTNER_API_KEY=YOUR_STAGING_KEY \ node partner-conformance.js ``` To validate a quote too, provide all required quote variables: ```bash theme={null} PARTNER_BASE_URL=https://PARTNER_STAGING_URL \ PARTNER_API_KEY=YOUR_STAGING_KEY \ PARTNER_CHAIN_ID=1 \ PARTNER_SRC=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 \ PARTNER_DST=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 \ PARTNER_AMOUNT=10000000000000000 \ PARTNER_PAYER=0xYOUR_FUNDED_TEST_PAYER \ PARTNER_RECIPIENT=0xYOUR_TEST_RECIPIENT \ node partner-conformance.js ``` Keep `PARTNER_API_KEY` in a backend environment or secret manager. Do not run this script in a browser or commit a real key to source control. ## Router verification `GET /partner/v1/chains` returns the router address, explorer URL, contract version, operational status, and deployed-bytecode hash where the chain RPC is available. Partners should pin both the allowlisted address and reviewed code hash. A changed hash or `DEGRADED`/`DISABLED` status must pause new execution until reviewed. Deployment block and audit URLs are nullable until deployment records are linked to the public registry. ## Durable telemetry The backend stores quote metadata in `partner_quote_telemetry` and partner-reported transaction lifecycle events in `partner_execution_telemetry`. By default it uses the `aggregator_health_monitor` schema on the `DATABASE_URL` server. Set `PARTNER_TELEMETRY_DATABASE_NAME` to change that schema, or `PARTNER_TELEMETRY_DATABASE_URL` when telemetry is hosted on a different MySQL server. Apply `database/migrations/20260814_partner_telemetry.sql` with `npm run db:migrate:partner-telemetry` before enabling partner traffic. Telemetry writes fail open and are bounded by `PARTNER_TELEMETRY_WRITE_TIMEOUT_MS` (500 ms by default), so storage downtime never blocks quoting indefinitely. Operations must alert on `PartnerTelemetryService` write errors. Stored fields support quote-to-execution analysis without retaining API keys, calldata, RFQ signatures, or complete responses. The migration includes a suggested 90-day cleanup policy; operations must schedule it according to the agreed privacy and retention policy. ## Pre-production test matrix * Distinct payer and recipient, including a smart-contract payer. * Native input, native output, and ERC-20-only routes. * Zero and insufficient balance. * Zero, exact, and unlimited allowance. * No route, invalid token, invalid chain, and unsupported exact-output request. * Expired advisory quote, price movement beyond slippage, and reverted simulation. * `429`, timeout, and `5xx` retry behavior with jittered exponential backoff. * Comparison of `buyAmount`, `minAmountOut`, simulated output, and actual receipt output. ## Service targets to agree before launch The following are recommended negotiation targets, not an SLA unless included in a signed partner agreement: | Metric | Recommended target | | - | - | | Discovery availability | 99.95% monthly | | Executable quote availability | 99.9% monthly, excluding no-route responses | | Quote latency | p95 ≤ 2 seconds; p99 ≤ 5 seconds per enabled chain | | Quote refresh window | Use or refresh within the response `expiresAt` | | Incident acknowledgement | 15 minutes for critical execution incidents | | Breaking API changes | 30 days' notice and a versioned migration path | | Per-key options (expected gas) | Only with written agreement and an effective date | Partners should monitor latency, HTTP status, issue codes, simulation completeness, quote-to-simulation variance, quote-to-execution variance, and on-chain revert rate by chain and token pair. # Price API Source: https://docs.akka.finance/guides/price-api Use AKKA as a real-time token price feed on HyperEVM AKKA aggregates liquidity across every DEX on HyperEVM. As a side effect, the token endpoints provide real-time USD prices derived from actual pool reserves — not a single source, but a volume-weighted view across all indexed markets. ## Get all token prices in one call Fetch the full token list to get prices for every token on the chain: ```bash cURL theme={null} curl -H "apikey: YOUR_API_KEY" \ "https://api.akka.finance/999/tokens?verified=true" ``` ```javascript JavaScript theme={null} const res = await fetch('https://api.akka.finance/999/tokens?verified=true', { headers: { apikey: 'YOUR_API_KEY' }, }); const { tokens } = await res.json(); // tokens is a map: { [address]: { symbol, buyPriceUsd, sellPriceUsd, ... } } for (const [address, token] of Object.entries(tokens)) { console.log(`${token.symbol}: $${token.buyPriceUsd}`); } ``` ```python Python theme={null} import requests res = requests.get( "https://api.akka.finance/999/tokens", params={"verified": "true"}, headers={"apikey": "YOUR_API_KEY"}, ) tokens = res.json()["tokens"] for address, token in tokens.items(): print(f"{token['symbol']}: ${token['buyPriceUsd']}") ``` Each token includes two price fields: | Field | Description | | - | - | | `buyPriceUsd` | Cost to buy 1 unit of this token (USD) | | `sellPriceUsd` | Value received when selling 1 unit of this token (USD) | The spread between `buyPriceUsd` and `sellPriceUsd` reflects real market liquidity — tighter spreads indicate deeper liquidity. ## Get a single token price If you only need the price for one token: ```bash theme={null} curl -H "apikey: YOUR_API_KEY" \ "https://api.akka.finance/999/tokens/0x5555555555555555555555555555555555555555" ``` ```json theme={null} { "symbol": "WHYPE", "name": "Wrapped HYPE", "decimals": 18, "address": "0x5555555555555555555555555555555555555555", "verified": true, "buyPriceUsd": 25.42, "sellPriceUsd": 25.38 } ``` ## Price polling pattern Prices are refreshed every \~5 seconds on the backend. For most use cases, polling every 15–30 seconds is sufficient and stays well within the rate limit. ```typescript priceService.ts theme={null} const API_BASE = 'https://api.akka.finance'; const API_KEY = process.env.AKKA_API_KEY!; const POLL_INTERVAL = 15_000; // 15 seconds interface TokenPrice { symbol: string; address: string; buyPriceUsd: number | null; sellPriceUsd: number | null; } let priceCache = new Map(); async function refreshPrices(): Promise { const res = await fetch(`${API_BASE}/999/tokens?verified=true`, { headers: { apikey: API_KEY }, }); const { tokens } = await res.json(); const updated = new Map(); for (const [address, token] of Object.entries(tokens) as [string, any][]) { updated.set(address.toLowerCase(), { symbol: token.symbol, address: token.address, buyPriceUsd: token.buyPriceUsd, sellPriceUsd: token.sellPriceUsd, }); } priceCache = updated; } // Start polling refreshPrices(); setInterval(refreshPrices, POLL_INTERVAL); // Read from cache (instant, no API call) export function getPrice(tokenAddress: string): TokenPrice | undefined { return priceCache.get(tokenAddress.toLowerCase()); } export function getAllPrices(): Map { return priceCache; } ``` ## React hook for prices The browser-facing example assumes your own `/api/akka` backend proxy attaches the API key. Never expose the partner key through `NEXT_PUBLIC_*`. ```typescript hooks/useTokenPrice.ts theme={null} import { useState, useEffect } from 'react'; const API_BASE = '/api/akka'; interface TokenPrice { symbol: string; buyPriceUsd: number | null; sellPriceUsd: number | null; } export function useTokenPrice(chainId: number, tokenAddress: string) { const [price, setPrice] = useState(null); useEffect(() => { if (!tokenAddress) return; let cancelled = false; async function fetchPrice() { const res = await fetch(`${API_BASE}/${chainId}/tokens/${tokenAddress}`, { cache: 'no-store', }); if (!res.ok || cancelled) return; const data = await res.json(); setPrice({ symbol: data.symbol, buyPriceUsd: data.buyPriceUsd, sellPriceUsd: data.sellPriceUsd, }); } fetchPrice(); const interval = setInterval(fetchPrice, 30_000); return () => { cancelled = true; clearInterval(interval); }; }, [chainId, tokenAddress]); return price; } ``` Usage: ```tsx theme={null} const whypePrice = useTokenPrice( 999, '0x5555555555555555555555555555555555555555', ); return WHYPE: ${whypePrice?.buyPriceUsd?.toFixed(2)}; ``` ## USD value helper Convert a token amount in wei to its USD value: ```typescript theme={null} import { formatUnits } from 'viem'; function toUsdValue( amountWei: string, decimals: number, priceUsd: number | null, ): string | null { if (!priceUsd) return null; const amount = Number(formatUnits(BigInt(amountWei), decimals)); return (amount * priceUsd).toFixed(2); } // Example: 1 WHYPE (18 decimals) at $25.42 toUsdValue('1000000000000000000', 18, 25.42); // → "25.42" ``` ## Use cases > **Swap UI** > > Show USD values next to token amounts so users know the dollar value of > their swap. > **Portfolio tracker** > > Display wallet holdings in USD by multiplying token balances by their > current prices. > **Price alerts** > > Poll prices and trigger notifications when a token crosses a threshold. > **Analytics** > > Track token price movements over time for dashboards and reporting. > [!NOTE] > > Prices are derived from real DEX pool reserves across all indexed markets on > HyperEVM. They reflect actual on-chain liquidity, not centralized exchange > prices. # Partner-ready React Hooks Source: https://docs.akka.finance/guides/react-hooks Drop-in React hooks backed by AKKA's partner quote endpoint These hooks use the partner quote as the only quote/build contract. The browser calls your backend proxy so the partner API key is never shipped in JavaScript. ## Backend proxy This Next.js route validates the fields your UI uses and forwards them to AKKA. Add your own session, origin, and rate-limit checks before production. ```typescript app/api/akka/quote/route.ts theme={null} import { NextRequest, NextResponse } from 'next/server'; const AKKA_API = 'https://api.akka.finance'; const ALLOWED = ['src', 'dst', 'amount', 'payer', 'recipient', 'slippage']; export async function GET(request: NextRequest) { const chainId = request.nextUrl.searchParams.get('chainId'); if (!chainId || !/^\d+$/.test(chainId)) { return NextResponse.json({ message: 'Invalid chainId' }, { status: 400 }); } const params = new URLSearchParams(); for (const key of ALLOWED) { const value = request.nextUrl.searchParams.get(key); if (value) params.set(key, value); } const response = await fetch( `${AKKA_API}/partner/v1/${chainId}/quote?${params}`, { headers: { apikey: process.env.AKKA_API_KEY! }, cache: 'no-store', signal: AbortSignal.timeout(10_000), }, ); return new NextResponse(await response.text(), { status: response.status, headers: { 'content-type': 'application/json' }, }); } ``` > [!WARNING] > > Never use `NEXT_PUBLIC_AKKA_API_KEY`. Executable quotes can contain signed RFQ > calldata; do not cache responses in your proxy, service worker, CDN, or > application state beyond their immediate UI lifecycle. ## Shared response type ```typescript lib/akka.ts theme={null} export type PartnerQuote = { quoteId: string; buyAmount: string; minAmountOut: string; sellToken: `0x${string}`; sellAmount: string; allowanceTarget: `0x${string}` | null; expiresAt: string; issues: Array<{ code: string; message: string }>; validation: Record<'balance' | 'allowance' | 'gas', { status: string }>; // gasLimit = the limit to send; gas = tx.gas (the same limit by default, // and the limit on a response without gasLimit). simulation: { status: string; gas: string; gasLimit?: string }; tx: { to: `0x${string}`; data: `0x${string}`; value: string; gas: string; gasPrice: string; type?: 'legacy' | 'eip1559'; maxFeePerGas?: string; maxPriorityFeePerGas?: string; }; }; export async function fetchPartnerQuote(input: { chainId: number; src: string; dst: string; amount: string; payer: string; recipient?: string; slippage: number; signal?: AbortSignal; }): Promise { const { chainId, signal, ...query } = input; const response = await fetch( `/api/akka/quote?${new URLSearchParams({ ...query, recipient: query.recipient ?? query.payer, slippage: String(query.slippage), })}`, { cache: 'no-store', signal }, ); const body = await response.json(); if (!response.ok) throw new Error(body.message ?? `AKKA ${response.status}`); return body; } ``` ## `useAkkaQuote` This hook debounces display quotes. Always request a fresh quote again when the user confirms execution. ```typescript hooks/useAkkaQuote.ts theme={null} import { useEffect, useState } from 'react'; import { fetchPartnerQuote, type PartnerQuote } from '../lib/akka'; export function useAkkaQuote(input: { chainId: number; src: string; dst: string; amount: string; payer?: string; slippage?: number; }) { const [quote, setQuote] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (!input.payer || !input.amount || input.amount === '0') { setQuote(null); return; } const controller = new AbortController(); const timer = setTimeout(async () => { setLoading(true); try { setQuote( await fetchPartnerQuote({ ...input, payer: input.payer!, slippage: input.slippage ?? 1, signal: controller.signal, }), ); setError(null); } catch (error) { if (!controller.signal.aborted) { setQuote(null); setError(error instanceof Error ? error.message : 'Quote failed'); } } finally { if (!controller.signal.aborted) setLoading(false); } }, 400); return () => { clearTimeout(timer); controller.abort(); }; }, [ input.chainId, input.src, input.dst, input.amount, input.payer, input.slippage, ]); return { quote, loading, error }; } ``` ## `useAkkaSwap` The execution hook requests a fresh quote on click, approves the exact AKKA `allowanceTarget` if required, then refreshes before broadcasting. ```typescript hooks/useAkkaSwap.ts theme={null} import { useCallback, useState } from 'react'; import { erc20Abi } from 'viem'; import { useAccount, usePublicClient, useSendTransaction, useWriteContract, } from 'wagmi'; import { fetchPartnerQuote } from '../lib/akka'; type Status = | 'idle' | 'quoting' | 'approving' | 'swapping' | 'confirming' | 'done' | 'error'; export function useAkkaSwap(input: { chainId: number; src: `0x${string}`; dst: `0x${string}`; amount: string; recipient?: `0x${string}`; slippage?: number; }) { const { address } = useAccount(); const publicClient = usePublicClient(); const { sendTransactionAsync } = useSendTransaction(); const { writeContractAsync } = useWriteContract(); const [status, setStatus] = useState('idle'); const [txHash, setTxHash] = useState<`0x${string}`>(); const [error, setError] = useState(); const execute = useCallback(async () => { if (!address || !publicClient || !input.amount || input.amount === '0') return; try { setError(undefined); setStatus('quoting'); const request = () => fetchPartnerQuote({ ...input, payer: address, recipient: input.recipient ?? address, slippage: input.slippage ?? 1, }); let quote = await request(); if ( quote.issues.some((issue) => issue.code === 'INSUFFICIENT_ALLOWANCE') ) { if (!quote.allowanceTarget) throw new Error('Missing allowance target'); setStatus('approving'); const approvalHash = await writeContractAsync({ address: quote.sellToken, abi: erc20Abi, functionName: 'approve', args: [quote.allowanceTarget, BigInt(quote.sellAmount)], }); await publicClient.waitForTransactionReceipt({ hash: approvalHash }); quote = await request(); } if (quote.issues.length) throw new Error(quote.issues[0].message); if ( Object.values(quote.validation).some( (check) => check.status === 'UNKNOWN', ) ) { throw new Error('AKKA could not complete payer validation'); } setStatus('swapping'); const feeFields = quote.tx.type === 'eip1559' ? { maxFeePerGas: BigInt(quote.tx.maxFeePerGas!), maxPriorityFeePerGas: BigInt(quote.tx.maxPriorityFeePerGas!), } : { gasPrice: BigInt(quote.tx.gasPrice) }; const hash = await sendTransactionAsync({ to: quote.tx.to, data: quote.tx.data, value: BigInt(quote.tx.value), gas: BigInt(quote.simulation.gasLimit ?? quote.tx.gas), ...feeFields, }); setTxHash(hash); setStatus('confirming'); await publicClient.waitForTransactionReceipt({ hash }); setStatus('done'); } catch (error) { setError(error instanceof Error ? error.message : 'Swap failed'); setStatus('error'); } }, [address, publicClient, sendTransactionAsync, writeContractAsync, input]); return { execute, status, txHash, error }; } ``` Use the quote hook for display and the swap hook for execution. Do not pass the display quote into the execution hook; refreshing on click protects against stale pool state and expired RFQ signatures. # Wagmi Partner Integration Source: https://docs.akka.finance/guides/wagmi-integration Execute AKKA partner quotes in React with wagmi and viem AKKA's recommended wagmi flow uses `GET /partner/v1/{chainId}/quote` for both price display and transaction construction. The endpoint returns the payer checks, approval target, expiry, simulation status, and transaction together. ## Install and configure ```bash theme={null} npm install wagmi viem @tanstack/react-query ``` ```typescript config.ts theme={null} import { defineChain } from 'viem'; import { createConfig, http } from 'wagmi'; export const hyperEVM = defineChain({ id: 999, name: 'HyperEVM', nativeCurrency: { name: 'HYPE', symbol: 'HYPE', decimals: 18 }, rpcUrls: { default: { http: ['https://rpc.hyperliquid.xyz/evm'] } }, blockExplorers: { default: { name: 'HyperEVM Scan', url: 'https://hyperevmscan.io' }, }, }); export const config = createConfig({ chains: [hyperEVM], transports: { [hyperEVM.id]: http() }, }); ``` ## Protect the API key Do not put the partner API key in `NEXT_PUBLIC_*` variables. Add the backend proxy and `fetchPartnerQuote` helper from the [React Hooks guide](/guides/react-hooks). Your UI calls `/api/akka/quote`; only your server calls AKKA with the `apikey` header. ## Complete swap component The reusable hook handles fresh quoting, exact-target approval, EIP-1559 versus legacy fees, and receipt confirmation. ```tsx SwapPage.tsx theme={null} import { useState } from 'react'; import { formatUnits, parseUnits } from 'viem'; import { useAccount, useConnect } from 'wagmi'; import { injected } from 'wagmi/connectors'; import { useAkkaQuote } from './hooks/useAkkaQuote'; import { useAkkaSwap } from './hooks/useAkkaSwap'; const CHAIN_ID = 999; const HYPE = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; const UBTC = '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb'; export function SwapPage() { const { address, isConnected } = useAccount(); const { connect } = useConnect(); const [input, setInput] = useState(''); const amount = input ? parseUnits(input, 18).toString() : '0'; const display = useAkkaQuote({ chainId: CHAIN_ID, src: HYPE, dst: UBTC, amount, payer: address, slippage: 1, }); const swap = useAkkaSwap({ chainId: CHAIN_ID, src: HYPE, dst: UBTC, amount, slippage: 1, }); if (!isConnected) { return ( ); } return (
setInput(event.target.value)} /> {display.loading &&

Finding route…

} {display.quote && (

Minimum received:{' '} {formatUnits(BigInt(display.quote.minAmountOut), 18)} UBTC

)} {swap.error &&

{swap.error}

} {swap.txHash && ( View transaction )}
); } ``` > [!NOTE] > > The execution hook intentionally obtains a new quote when the button is > clicked. Never broadcast a debounced display quote after user delay, and never > retry a reverted transaction with old calldata. ## Production checks * Confirm the connected wallet is on the requested chain. * Treat non-empty `issues` as blocking until resolved. * Independently validate any check whose status is `UNKNOWN`. * Preserve `tx.to`, `tx.data`, and `tx.value` exactly. * Send `simulation.gasLimit` as the gas limit (by default equal to `tx.gas`). * Report `SUBMITTED`, then `CONFIRMED` or `REVERTED`, through `POST /partner/v1/executions` from your backend. # Introduction Source: https://docs.akka.finance/introduction AKKA Finance — liquidity aggregation API for optimal token swaps AKKA Finance is a liquidity aggregation protocol that finds the best token swap rates by routing through multiple DEXes simultaneously. The Pathfinder algorithm splits your swap across protocols and market depths to minimize slippage and maximize output. ## What you can do with the API > **Get Quotes** > > Find the best swap rate for any token pair across all supported DEXes in a > single call. > **Execute Swaps** > > Generate ready-to-sign transaction data for token swaps with built-in > slippage protection. > **Validate Execution** > > Receive balance, allowance, gas, simulation, and quote-lifecycle status with > every executable quote. > **Browse Tokens** > > Get token metadata, verification status, and real-time USD pricing. ## How it works 1. **Discover support** — Read partner chains and sources instead of hardcoding availability. 2. **Request one executable quote** — `GET /partner/v1/{chainId}/quote` finds the route and returns payer validation, approval target, expiry, and transaction data together. 3. **Resolve issues and execute** — If required, approve the returned target and refresh the quote. Then submit its transaction unchanged and report the receipt. ## Next steps > **[Quickstart](/quickstart)** > > Go from zero to your first partner-ready swap in four stages. # Quickstart Source: https://docs.akka.finance/quickstart Build and execute your first partner-ready AKKA swap AKKA's partner API returns an exact-input quote, validation results, approval target, and the transaction to broadcast in one call: send its `to`, `data`, and `value` with `simulation.gasLimit` as the gas limit. This is the recommended flow for every new integration. > [!WARNING] > > The shared `basic-test-key` below is only for evaluation. Keep a dedicated > production `apikey` on a backend. Browser and mobile clients should call your > own authenticated API route, which then calls AKKA. ## Prerequisites * The shared `basic-test-key` for evaluation, or a dedicated partner key for production ([request one](https://t.me/akka_finance)) * A wallet with HYPE and gas on HyperEVM (`chainId=999`) * `viem` installed in your server-side project **1. Request an executable quote** Supply the address that pays for and sends the transaction as `payer`. `recipient` receives the output and can be a different address. ```bash theme={null} curl -G "https://api.akka.finance/partner/v1/999/quote" \ -H "apikey: basic-test-key" \ --data-urlencode "src=0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" \ --data-urlencode "dst=0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb" \ --data-urlencode "amount=1000000000000000000" \ --data-urlencode "payer=YOUR_WALLET_ADDRESS" \ --data-urlencode "recipient=YOUR_WALLET_ADDRESS" \ --data-urlencode "slippage=1" ``` The response includes `buyAmount`, `minAmountOut`, `issues`, `validation`, `allowanceTarget`, quote expiry, simulation status, and `tx`. `simulation.gasLimit` is the gas limit to send (by default also `tx.gas`); `simulation.estimatedGasUsed` is the expected consumption, for cost display. **2. Resolve validation issues** A `200` response may contain actionable `issues`. Native input requires no approval. For ERC-20 input, approve `allowanceTarget` when the response contains `INSUFFICIENT_ALLOWANCE`. ```typescript theme={null} import { erc20Abi } from 'viem'; const allowanceIssue = quote.issues.find( (issue) => issue.code === 'INSUFFICIENT_ALLOWANCE', ); if (allowanceIssue && quote.allowanceTarget) { const approvalHash = await walletClient.writeContract({ address: quote.sellToken, abi: erc20Abi, functionName: 'approve', args: [quote.allowanceTarget, BigInt(quote.sellAmount)], }); await publicClient.waitForTransactionReceipt({ hash: approvalHash }); // Approval changes state, so fetch a new executable quote. quote = await getPartnerQuote(); } ``` Do not broadcast while any validation check is `UNKNOWN`; verify it through your own RPC first. **3. Check freshness and submit** Execute immediately. Refresh if the current time is at or past `expiresAt`, or if simulation is not `SUCCESS` and your own simulation fails. ```typescript theme={null} 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), // The gas limit (by default equal to tx.gas, which also stands in // on a response without simulation.gasLimit). gas: BigInt(quote.simulation.gasLimit ?? quote.tx.gas), ...feeFields, }); const receipt = await publicClient.waitForTransactionReceipt({ hash }); console.log('Swap confirmed:', receipt.transactionHash); ``` Never edit `tx.to`, `tx.data`, or `tx.value`, and never reuse executable calldata. Never send a gas limit below `simulation.gasLimit`. **4. Report the outcome** Outcome reporting is best-effort telemetry and helps AKKA identify route health problems. ```bash theme={null} curl -X POST "https://api.akka.finance/partner/v1/executions" \ -H "apikey: basic-test-key" \ -H "content-type: application/json" \ -d '{ "quoteId": "QUOTE_ID", "chainId": 999, "transactionHash": "0xTRANSACTION_HASH", "status": "CONFIRMED" }' ``` ## Next steps > **[Complete TypeScript Example](/guides/first-swap)** > > Copy a complete server-side viem implementation. > **[Partner Quote Reference](/api-reference/partner-quote)** > > Explore the complete request and response contract. # Smart Contracts Source: https://docs.akka.finance/smart-contracts AKKA Router contract addresses ## Deployed Contracts All five active chains run the same AKKA Router build. The router is the `to` address of every transaction returned by the partner quote endpoint and the `allowanceTarget` for ERC-20 approvals. | Chain | Chain ID | AKKA Router Address | | - | - | - | | Ethereum | `1` | [`0x0B00004Ccc39408Fa289e9F6853BFE4eac3E52c8`](https://etherscan.io/address/0x0B00004Ccc39408Fa289e9F6853BFE4eac3E52c8) | | Arbitrum | `42161` | [`0xfB33E11e44547f31f55CAa5e77028b844a6d6fE3`](https://arbiscan.io/address/0xfB33E11e44547f31f55CAa5e77028b844a6d6fE3) | | HyperEVM (Hyperliquid) | `999` | [`0xcCe7452Db4392b40Aa0E1592A7C486e13Bf69654`](https://hyperevmscan.io/address/0xcCe7452Db4392b40Aa0E1592A7C486e13Bf69654) | | Robinhood Chain | `4663` | [`0x0B00004Ccc39408Fa289e9F6853BFE4eac3E52c8`](https://robinhoodchain.blockscout.com/address/0x0B00004Ccc39408Fa289e9F6853BFE4eac3E52c8) | | XDC Network | `50` | [`0x0B00004Ccc39408Fa289e9F6853BFE4eac3E52c8`](https://xdcscan.com/address/0x0B00004Ccc39408Fa289e9F6853BFE4eac3E52c8) | > [!NOTE] > > The router address is the same on Ethereum, Robinhood Chain and XDC Network > (deterministic deployment); Arbitrum and HyperEVM use chain-specific > addresses. Base will be listed here when it launches. ## What is the AKKA Router? The AKKA Router is the smart contract that executes token swaps on your behalf. The partner quote endpoint returns a transaction targeting the active router for the requested chain. Discover and verify router addresses with `GET /partner/v1/chains` instead of hardcoding this page. Before swapping ERC-20 tokens, approve the partner quote's `allowanceTarget` when it reports `INSUFFICIENT_ALLOWANCE`. Prefer an exact `sellAmount` approval, wait for confirmation, and request a fresh quote afterward. > [!WARNING] > > Native tokens (e.g. HYPE on HyperEVM, ETH on Robinhood Chain) do not require > approval. Only ERC-20 tokens need to be approved before swapping. # Supported Chains Source: https://docs.akka.finance/supported-chains Blockchain networks supported by AKKA Finance ## Live `GET /partner/v1/chains` is the runtime source of truth. It returns only active chains with an execution router, plus operational status and router verification metadata. The table below is a human-readable snapshot. | Chain | Chain ID | Native Token | Wrapped Native | | - | - | - | - | | Ethereum | `1` | ETH | WETH (`0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2`) | | Arbitrum | `42161` | ETH | WETH (`0x82af49447d8a07e3bd95bd0d56f35241523fbab1`) | | HyperEVM (Hyperliquid) | `999` | HYPE | WHYPE (`0x5555555555555555555555555555555555555555`) | | Robinhood Chain | `4663` | ETH | WETH (`0x0bd7d308f8e1639fab988df18a8011f41eacad73`) | | XDC Network | `50` | XDC | WXDC (`0x951857744785e80e2de051c32ee7b25f9c458c42`) | ## Coming Soon | Chain | Chain ID | Native Token | | - | - | - | | Base | `8453` | ETH | ## Native Token Address When swapping the chain's native token (e.g., HYPE on HyperEVM), use the sentinel address as the `src` or `dst` parameter: ``` 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee ``` The API automatically wraps/unwraps native tokens during the swap. You do not need to wrap manually.