LiftUp
For integrators · aggregators · partner protocols

Integrate LiftUpin five minutes.

LiftUp is the stablecoin liquidity layer on Arc — Uniswap-V2- compatible AMM with a 0.05% fee tier across USDC / EURC / cirBTC pairs and a CCTP-native bridge. Any aggregator that speaks the V2 ABI can route through it — addresses, fee math, and integration samples below.

Testnet only for now — mainnet integration form opens with the audited v1.1 release.

Contracts

Addresses & network

Everything is on Arc Testnet (chainId 5042002). Click an address to view on ArcScan.

Network
Arc Testnet
Chain ID
5042002
RPC
https://rpc.testnet.arc.network
Explorer
testnet.arcscan.app

Core contracts

LiftupFactory

Deploys pairs via CREATE2 · constructor(feeToSetter)

0xAA0515a120F7fB
LiftupRouter

V2 router · swapExactTokensForTokens, addLiquidity, removeLiquidity

0x9bAca41238c240
LiftupRewardDistributor

Receives 100% of pool fees + Circle 10 bps surcharge · 5 buckets

0x1300c7acf80497
LiftupPair (USDC/EURC)

0.05% fee · standard V2 Swap/Mint/Burn events

0xc36B6B379aEBfF
LiftupPair (USDC/cirBTC)

0.05% fee · USDC-quoted BTC market via Circle Wrapped BTC

0x5A15E9D6Bfa9C5
LiftupPair (EURC/cirBTC)

0.05% fee · EURC-quoted BTC market via Circle Wrapped BTC

0xd86D2d9A4655f5

Token contracts

USDC

6 decimals · Arc native gas token

0x36000000000000
EURC

6 decimals · euro stablecoin

0x89B508F319D72a
cirBTC

8 decimals · Circle Wrapped Bitcoin

0xf0C4a4bBA432BF

Lift Engine v1.1

Open-source primitive, fully specified.

Pro-rata distribution layer with per-swap cashback + claim mechanism. Replaces v0 lottery-style distributor. Currently in audit scope ($15k grant). Migration preserves all LP positions — no remove + re-add required.

v0 lottery vs v1.1 Lift Engine
Aspectv0 (current)v1.1 (Lift Engine)
SelectionRandom recipient sampling (PRNG)Deterministic pro-rata by weight
Trader pathRandom lottery winnersInstant cashback per swap + weekly/monthly bonus
LP pathRandom + pro-rata splitPure pro-rata weighted by tier × tenure
Payout flowPushed by owner-gated distribute*User-pulled claim() — sub-cent gas
MultipliersSoft tier + tenure (off-chain)On-chain tier + tenure + bounded launch boost
Sub-buckets5 buckets8 (growth + bonus + 3 cadences × LP/Trader)

Fee math

Standard V2 formula, 5 bps fee tier

The K-invariant is unchanged from Uniswap V2 — only the numerator/denominator differ. Plug the constants below into your existing V2 quote helper and LiftUp slots in.

tsgetAmountOut
// V2 swap math with LiftUp's 0.05% fee tier
const FEE_NUMERATOR = 9995n;       // 10000 - 5 (5 bps fee)
const FEE_DENOMINATOR = 10000n;

function getAmountOut(amountIn, reserveIn, reserveOut) {
  const amountInWithFee = amountIn * FEE_NUMERATOR;
  const numerator = amountInWithFee * reserveOut;
  const denominator = reserveIn * FEE_DENOMINATOR + amountInWithFee;
  return numerator / denominator;
}
Fee tier
0.05%

5 basis points, matches Aerodrome / Velodrome stable tier

FEE_NUMERATOR
9995

amount in × (10000 − 5) / 10000 = amount after fee

FEE_DENOMINATOR
10000

basis-points base

Heads up

LiftupPair transfers 100% of the fee out of the pool to factory.feeTo() on every swap. K stays constant after the swap (instead of growing like vanilla V2). If your aggregator monitors K-growth as a sanity check, treat LiftUp as a special source where K-growth equals zero.

Pair address derivation

Standard V2 CREATE2. Sort tokens ascending, hash as the salt, combine with the factory address and LiftupPair init-code hash.

tsliftupPairAddress
// LiftupFactory uses standard V2 CREATE2 with sorted-token salt.
import { keccak256, encodePacked, getCreate2Address } from 'viem';

function liftupPairAddress(factory, tokenA, tokenB, initCodeHash) {
  const [token0, token1] = tokenA.toLowerCase() < tokenB.toLowerCase()
    ? [tokenA, tokenB]
    : [tokenB, tokenA];

  const salt = keccak256(
    encodePacked(['address', 'address'], [token0, token1])
  );
  return getCreate2Address({ from: factory, salt, bytecodeHash: initCodeHash });
}

// initCodeHash for LiftupPair: keccak256(LiftupPair_creationCode + abi.encode(token0, token1))
// available via factory.allPairs(i) iteration or precomputed off-chain.

Sample code

Wire it in today.

TypeScript + viem snippets. The router exposes the same surface as Uniswap V2 — drop addresses in and ship.

ts1. Read pool reserves
// Read live reserves from a LiftupPair (viem)
import { createPublicClient, http } from 'viem';

const client = createPublicClient({
  chain: { id: 5042002, name: 'Arc Testnet' },
  transport: http('https://rpc.testnet.arc.network'),
});

const reserves = await client.readContract({
  address: '0xc36B6B7f9F35A145E2E34c9452E99E57379aEBfF', // USDC/EURC pair
  abi: [{
    type: 'function',
    name: 'getReserves',
    inputs: [],
    outputs: [
      { name: 'reserve0', type: 'uint112' },
      { name: 'reserve1', type: 'uint112' },
      { name: 'blockTimestampLast', type: 'uint32' },
    ],
    stateMutability: 'view',
  }],
  functionName: 'getReserves',
});
ts2. Quote via router (recommended)
// Quote a swap using LiftupRouter (recommended over manual math)
const amountOut = await client.readContract({
  address: '0x9bAca4056C278bA4f45bDd80B539322a1238c240', // Router
  abi: [{
    type: 'function',
    name: 'getAmountOut',
    inputs: [
      { name: 'amountIn',  type: 'uint256' },
      { name: 'tokenIn',   type: 'address' },
      { name: 'tokenOut',  type: 'address' },
    ],
    outputs: [{ name: 'amountOut', type: 'uint256' }],
    stateMutability: 'view',
  }],
  functionName: 'getAmountOut',
  args: [
    1_000_000n,                                    // 1 USDC (6 decimals)
    '0x3600000000000000000000000000000000000000', // USDC
    '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a', // EURC
  ],
});
ts3. Execute a swap
// Execute a swap (user must have approved Router to spend tokenIn)
import { encodeFunctionData } from 'viem';

const data = encodeFunctionData({
  abi: [{
    type: 'function',
    name: 'swapExactTokensForTokens',
    inputs: [
      { name: 'amountIn',     type: 'uint256' },
      { name: 'amountOutMin', type: 'uint256' },
      { name: 'path',         type: 'address[]' },
      { name: 'to',           type: 'address' },
      { name: 'deadline',     type: 'uint256' },
    ],
    outputs: [{ type: 'uint256[]' }],
    stateMutability: 'nonpayable',
  }],
  functionName: 'swapExactTokensForTokens',
  args: [
    1_000_000n,                                              // amountIn
    quotedOut * 9950n / 10000n,                              // 0.5% slippage
    [USDC, EURC],                                            // path
    userAddress,                                             // recipient
    BigInt(Math.floor(Date.now() / 1000) + 600),             // 10-min deadline
  ],
});

await wallet.sendTransaction({ to: '0x9bAca…', data });
solidity4. Events for your indexer / subgraph
// Standard V2 events emitted by LiftupPair
event Swap(
  address indexed sender,
  uint256 amount0In,
  uint256 amount1In,
  uint256 amount0Out,
  uint256 amount1Out,
  address indexed to
);
event Mint(address indexed sender, uint256 amount0, uint256 amount1);
event Burn(
  address indexed sender,
  uint256 amount0,
  uint256 amount1,
  address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
event Transfer(address indexed from, address indexed to, uint256 value);

// Bonus: LiftupRewardDistributor's Distributed event for indexing winners
event Distributed(
  bytes32 indexed bucket,    // "daily" / "weekly" / "monthly" / "bonus"
  address indexed token,
  address indexed recipient,
  uint256 amount
);

Partner with LiftUp

Aggregator? Front-end builder?

We'll co-promote any aggregator that lists LiftUp Pool as a routing source on Arc — protocol-level partnerships only. Reach out and we'll wire you into the next testnet snapshot + mainnet launch list.

Aggregator integrations (1inch · Paraswap · OpenOcean · Matcha) will be filed after the audited mainnet release.