APEXISAPEXIS
Developers

Build on ApexisRouter

One contract in front of every DEX on Arc. Send it a swap and it routes to the pool you name, takes the protocol fee inside the same transaction, and pays your wallet directly. No API key, no allowlist, no off-chain signature.

Contract addresses

Chain ID 5042 · compiler v0.8.26+commit.8a97fa7a · fee 30 bps. The router address can rotate; read it from the JSON endpoint at startup rather than hardcoding it.

ApexisRouter (fee router — call this)

0xed32e78cdcd587c90fa104e38209b4512d6d5fd5

Approve your tokens to THIS address, not to the DEX.

Retired revisions still on chain: 0xb0d65ccafa28385f81626ff6a7db05abf0959d1c — do not send new swaps to them.

Registered DEXes

Pass one of these as the router argument. Each DEX has its own WUSDC; the buy/sell paths must start or end with the WUSDC of the DEX you name.

DEXRouterWUSDCFlavourStatus
APEXISWAPid 10x1229377adE15278818cD50214456db4b469f6f2b0x1CCA7CA62bcBFdAB086765f451648d8C7591AECbETH_STYLEenabled
Uniswapid 20x1f7d7550b1b028f7571e69a784071f0205fd2efa0x3600000000000000000000000000000000000000NATIVE_ERC20enabled

How the fee works

Standard

30 bps

Taken on the USDC side of the trade, inside the swap. Max the owner can ever set: 300 bps. Default in the contract: 30 bps.

Graduated Pump token

100 bps

Instead of the standard rate, not on top. 80% of it accrues to the token creator. quoteFeeForPath returns the right rate for you.

Wrap / unwrap

0 bps

Native USDC to WUSDC 1:1 is not a swap. Call the WUSDC contract directly; the router is not involved.

Quote with the net amount. On a buy the fee leaves before the DEX sees your funds, so getAmountsOut(gross) promises more than arrives and the swap reverts with INSUFFICIENT_OUTPUT. Call quoteFeeForPath first and quote the DEX with net. On a sell, subtract the fee from the DEX output before setting minOut.

Functions you will call

viewquoteFee(uint256 amountIn)(uint256 fee, uint256 net)

Fee split at the standard rate. Quote the DEX with `net`, never the gross.

viewquoteFeeForPath(address[] path, uint256 amountIn)(uint256 fee, uint256 net, uint16 bps)

Same, for a specific route. Returns the graduated rate when the path contains a graduated Apexis Pump token. Use this one.

writeswapNativeForTokens(address router, address[] path, uint256 minOut, uint256 deadline)payable

BUY with native USDC. `path[0]` must be that DEX’s WUSDC. Fee skimmed from `msg.value`; the DEX pays you directly.

writeswapTokensForNative(address router, address[] path, uint256 amountIn, uint256 minOut, uint256 deadline)

SELL to native USDC. Last path element must be that DEX’s WUSDC. Fee taken from the output; `minOut` is checked on what you receive.

writeswapTokensForTokens(address router, address[] path, uint256 amountIn, uint256 minOut, uint256 deadline)

Token to token on one DEX. Fee is taken on the WUSDC leg if there is one, otherwise skimmed from the input.

writeswapTokensForTokensCrossDex((address routerA, address routerB, uint256 amountIn, uint256 minOutFinal, uint256 deadline) p, address[] pathA, address[] pathB)

Sell on DEX A into native, buy on DEX B. One fee on the native middle. Disabled unless `crossDexEnabled` is true.

viewswapInProgress(address token)bool

True only while this router is mid-swap on a path containing `token`. Graduated Pump tokens use it to gate pair transfers.

writeclaimCreatorFees(address creator, address token)

Pays out the creator share accrued on a graduated token. Anyone may call; funds go to `creator`.

Examples

Addresses below are the live ones. Amounts use 18 decimals because native USDC on Arc has 18 decimals; ERC-20 tokens launched on Apexis Pump also use 18.

Read the live config

config.ts
// The address can rotate. Read it at startup instead of hardcoding it.
const res = await fetch('https://www.apexiswap.com/api/developers/router');
const { address, abi, feeBps, dexes } = await res.json();

const enabledDexes = dexes.filter((d) => d.enabled);
// -> [{ id: 1, name: 'APEXISWAP', router: '0x…', wusdc: '0x…', flavour: 'ETH_STYLE', enabled: true }, …]

Buy a token with native USDC (ethers v6)

buy.ts
import { ethers } from 'ethers';

const ROUTER = '0xed32e78cdcd587c90fa104e38209b4512d6d5fd5'; // ApexisRouter
const DEX_ROUTER = '0x1229377adE15278818cD50214456db4b469f6f2b'; // APEXISWAP
const WUSDC = '0x1CCA7CA62bcBFdAB086765f451648d8C7591AECb'; // APEXISWAP's wrapped USDC
const TOKEN = '0x0000000000000000000000000000000000000000'; // APXS

// Only the pieces you call. The full ABI is at https://www.apexiswap.com/api/developers/router
const abi = [
  'function quoteFeeForPath(address[] path, uint256 amountIn) view returns (uint256 fee, uint256 net, uint16 bps)',
  'function swapNativeForTokens(address router, address[] path, uint256 minOut, uint256 deadline) payable',
];
const dexAbi = ['function getAmountsOut(uint256 amountIn, address[] path) view returns (uint256[] amounts)'];

const provider = new ethers.JsonRpcProvider('https://rpc.mainnet.arc.io', 5042);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const router = new ethers.Contract(ROUTER, abi, wallet);
const dex = new ethers.Contract(DEX_ROUTER, dexAbi, provider);

const path = [WUSDC, TOKEN];
const amountIn = ethers.parseUnits('10', 18); // native USDC on Arc has 18 decimals

// 1. Ask the router what reaches the pool after its fee. Quote the DEX with NET,
//    not gross: quoting the gross promises more than arrives and the swap reverts.
const { net, bps } = await router.quoteFeeForPath(path, amountIn);
console.log('fee bps for this path:', bps); // 30 normally, 100 for a graduated Pump token

// 2. Quote the DEX with the net amount and apply your slippage tolerance.
const amounts = await dex.getAmountsOut(net, path);
const expectedOut = amounts[amounts.length - 1];
const minOut = (expectedOut * 995n) / 1000n; // 0.5% slippage

// 3. Swap. The fee is skimmed from msg.value; tokens go straight to your wallet.
const deadline = Math.floor(Date.now() / 1000) + 600;
const tx = await router.swapNativeForTokens(DEX_ROUTER, path, minOut, deadline, { value: amountIn });
console.log('sent', tx.hash);
await tx.wait();

Sell a token for native USDC (ethers v6)

sell.ts
import { ethers } from 'ethers';

const ROUTER = '0xed32e78cdcd587c90fa104e38209b4512d6d5fd5';
const DEX_ROUTER = '0x1229377adE15278818cD50214456db4b469f6f2b';
const WUSDC = '0x1CCA7CA62bcBFdAB086765f451648d8C7591AECb';
const TOKEN = '0x0000000000000000000000000000000000000000';

const abi = [
  'function swapTokensForNative(address router, address[] path, uint256 amountIn, uint256 minOut, uint256 deadline)',
];
const erc20 = ['function approve(address spender, uint256 value) returns (bool)'];
const dexAbi = ['function getAmountsOut(uint256 amountIn, address[] path) view returns (uint256[] amounts)'];

const provider = new ethers.JsonRpcProvider('https://rpc.mainnet.arc.io', 5042);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const router = new ethers.Contract(ROUTER, abi, wallet);
const token = new ethers.Contract(TOKEN, erc20, wallet);
const dex = new ethers.Contract(DEX_ROUTER, dexAbi, provider);

const path = [TOKEN, WUSDC];
const amountIn = ethers.parseUnits('1000', 18);

// Approve the FEE ROUTER, not the DEX. The router pulls your tokens, and the DEX
// pays the router so the fee can be taken on the USDC side.
await (await token.approve(ROUTER, amountIn)).wait();

// On a sell the fee comes off the OUTPUT, so quote the DEX with the full input and
// then subtract the fee from the expected output before setting minOut.
const amounts = await dex.getAmountsOut(amountIn, path);
const grossOut = amounts[amounts.length - 1];
const netOut = (grossOut * (10000n - 30n)) / 10000n; // 30 bps, or 100 for a graduated token
const minOut = (netOut * 995n) / 1000n;

const deadline = Math.floor(Date.now() / 1000) + 600;
// minOut is checked against what YOU receive, after the fee.
const tx = await router.swapTokensForNative(DEX_ROUTER, path, amountIn, minOut, deadline);
await tx.wait();

Same buy with viem

buy-viem.ts
import { createPublicClient, createWalletClient, http, parseAbi, parseUnits } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const arc = {
  id: 5042,
  name: 'Arc',
  nativeCurrency: { name: 'USDC', symbol: 'USDC', decimals: 18 },
  rpcUrls: { default: { http: ['https://rpc.mainnet.arc.io'] } },
} as const;

const ROUTER = '0xed32e78cdcd587c90fa104e38209b4512d6d5fd5' as const;
const DEX_ROUTER = '0x1229377adE15278818cD50214456db4b469f6f2b' as const;
const WUSDC = '0x1CCA7CA62bcBFdAB086765f451648d8C7591AECb' as const;
const TOKEN = '0x0000000000000000000000000000000000000000' as const;

const routerAbi = parseAbi([
  'function quoteFeeForPath(address[] path, uint256 amountIn) view returns (uint256 fee, uint256 net, uint16 bps)',
  'function swapNativeForTokens(address router, address[] path, uint256 minOut, uint256 deadline) payable',
]);
const dexAbi = parseAbi(['function getAmountsOut(uint256 amountIn, address[] path) view returns (uint256[])']);

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const publicClient = createPublicClient({ chain: arc, transport: http() });
const walletClient = createWalletClient({ account, chain: arc, transport: http() });

const path = [WUSDC, TOKEN] as const;
const amountIn = parseUnits('10', 18);

const [, net] = await publicClient.readContract({
  address: ROUTER, abi: routerAbi, functionName: 'quoteFeeForPath', args: [path, amountIn],
});
const amounts = await publicClient.readContract({
  address: DEX_ROUTER, abi: dexAbi, functionName: 'getAmountsOut', args: [net, path],
});
const minOut = (amounts[amounts.length - 1] * 995n) / 1000n;
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);

const hash = await walletClient.writeContract({
  address: ROUTER, abi: routerAbi, functionName: 'swapNativeForTokens',
  args: [DEX_ROUTER, path, minOut, deadline], value: amountIn,
});
await publicClient.waitForTransactionReceipt({ hash });

Listen for swaps

events.ts
import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider('https://rpc.mainnet.arc.io', 5042);
const router = new ethers.Contract('0xed32e78cdcd587c90fa104e38209b4512d6d5fd5', [
  'event SwapExecuted(address indexed user, address indexed router, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut, address feeToken, uint256 feeAmount)',
], provider);

// amountOut is the recipient's measured balance delta, so it is truthful even for
// fee-on-transfer tokens. feeToken is address(0) when the fee was taken in native USDC.
router.on('SwapExecuted', (user, dexRouter, tokenIn, tokenOut, amountIn, amountOut, feeToken, feeAmount) => {
  console.log({ user, dexRouter, tokenIn, tokenOut, amountIn, amountOut, feeToken, feeAmount });
});

Revert reasons

The router uses short string reasons. If your call reverts with missing revert data and no reason, it is almost always the public RPC rate-limiting you (HTTP 429) rather than the contract — retry with backoff.

UNKNOWN_ROUTERThe `router` argument is not a registered DEX. Use one from the table above.
ROUTER_DISABLEDThat DEX is registered but switched off by the operator.
PATH_NOT_WUSDC_IN`swapNativeForTokens`: `path[0]` is not that DEX’s WUSDC. Each DEX has its own.
PATH_NOT_WUSDC_OUT`swapTokensForNative`: last path element is not that DEX’s WUSDC.
BAD_PATHPath shorter than 2 or with a zero address.
SAME_TOKENFirst and last path elements are the same token.
INSUFFICIENT_OUTPUTWhat you would receive, after the fee, is below `minOut`. Re-quote or widen slippage.
NOTHING_PULLED`transferFrom` moved zero tokens. Check the approval is to the router, not the DEX.
NO_VALUE / NO_AMOUNTZero `msg.value` or zero `amountIn`.
REENTRANTRe-entered during a swap. Do not call the router from a token hook.

Prices, pools and history

For quotes you should always ask the DEX router on chain. For everything else — pair lists, reserves, hourly candles, recent swaps, token metadata, Apexis Pump curves — use the public REST API. It is indexed from the same chain and needs no key.

Open the REST API reference