> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arc.io/llms.txt
> Use this file to discover all available pages before exploring further.

# How-to: Port an AMM or liquidity pool to Arc

> Migrate an AMM, liquidity pool, or DEX router to Arc by applying the correct USDC token address, decimal conversion factor, and Arc-specific EVM behaviors.

Use Arc's ERC-20 USDC interface exclusively for all pool accounting, token
addresses, and reserve math. The native interface (`address.balance`,
`msg.value`) uses 18 decimals and is for gas operations only; mixing it with the
6-decimal ERC-20 interface silently breaks constant-product calculations by a
factor of 10<sup>12</sup>. The following steps apply this principle and address
five other changes required before deploying to Arc.

## Prerequisites

Before you begin, ensure that you've:

* Read the [stablecoin-native model](/arc/concepts/stablecoin-native-model)
  concept page to understand Arc's dual USDC interface
* Reviewed the [EVM differences reference](/arc/references/evm-differences) for
  a complete list of protocol divergences
* Obtained Arc testnet RPC access at `https://rpc.testnet.arc.io` (chain ID
  `5042002`)

## Steps

### Step 1. Set the pool token to the ERC-20 USDC address

Use `0x3600000000000000000000000000000000000000` as the USDC pair address in all
pool and router configuration.

On other EVM blockchains, protocols use a WETH-style adapter contract to give
the native asset an ERC-20 interface. Arc's native USDC already has a built-in
ERC-20 interface, so no adapter is needed. The address
`0x3600000000000000000000000000000000000000` is that interface. It already
implements `transfer`, `approve`, and `transferFrom` over the native balance.

<Warning>
  Do not pair native USDC against the ERC-20 USDC interface as two separate pool
  tokens. Both interfaces draw from the same underlying balance, so pairing them
  is equivalent to pairing an asset with itself. A pool configured this way is
  immediately insolvent.
</Warning>

Two additional mistakes to avoid:

* The zero address (`0x0000000000000000000000000000000000000000`) is not USDC.
  Do not use it as a token address. Value-bearing transfers to it revert on Arc;
  see
  [Value transfer rules](/arc/references/evm-differences#value-transfer-rules).
* Any address other than `0x3600000000000000000000000000000000000000` used to
  represent USDC creates a separate, unrelated token that does not share the
  native balance.

Update your pool factory, router configuration, and any initialization scripts
to reference `0x3600000000000000000000000000000000000000` directly.

### Step 2. Account for the 10¹² decimal offset in offchain tooling

Arc's native USDC has 18 decimals (`address.balance`, `eth_getBalance`,
`msg.value`) and the ERC-20 interface has 6 decimals (`balanceOf`, `transfer`).
The conversion factor between them is 10<sup>12</sup>.

Pool contracts that follow the ERC-20 prescription do not encounter this offset:
deposits go through `transferFrom`, reserves are read from `balanceOf`, and
`nonpayable` functions block native USDC from entering pool logic. The offset
matters in offchain code: monitoring scripts, indexers, and SDK integrations
that read `eth_getBalance` or `address.balance` must convert to 6-decimal units
before comparing or displaying values.

Use these constants in your offchain scripts and SDK integrations:

```typescript theme={null}
const OFFSET: bigint = 12n; // native USDC decimals (18) minus ERC-20 USDC decimals (6)

// Normalize a value from eth_getBalance or address.balance to 6-decimal ERC-20 units
// before displaying or comparing with ERC-20 amounts
function toERC20Units(nativeAmount: bigint): bigint {
  return nativeAmount / 10n ** OFFSET;
}
```

### Step 3. Remove WETH-style wrap/unwrap code paths

On most EVM blockchains, the native asset has no ERC-20 interface, so pools use
a wrapper contract (WETH, WMATIC, and so on) with `deposit()` and `withdraw()`
functions. On Arc, native USDC already has a built-in ERC-20 interface. Any
WETH-style wrap/unwrap code paths are unnecessary and create double-accounting
bugs by crediting balances twice.

Replace the wrapped native address constant and remove all `deposit()` and
`withdraw()` call sites:

```solidity theme={null}
// Wrapped native token address on the source chain // [!code --]
address constant WRAPPED_NATIVE = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // [!code --]
// ERC-20 USDC address on Arc // [!code ++]
address constant USDC = 0x3600000000000000000000000000000000000000; // [!code ++]

// Wrap before interacting with the pool // [!code --]
IWETH(WRAPPED_NATIVE).deposit{value: msg.value}(); // [!code --]
IERC20(WRAPPED_NATIVE).approve(address(pool), msg.value); // [!code --]
pool.swap(WRAPPED_NATIVE, tokenOut, msg.value, minAmountOut); // [!code --]
// Use ERC-20 USDC directly; no wrapping needed // [!code ++]
IERC20(USDC).approve(address(pool), amount); // [!code ++]
pool.swap(USDC, tokenOut, amount, minAmountOut); // [!code ++]

// Unwrap after receiving wrapped native from the pool // [!code --]
pool.removeLiquidity(WRAPPED_NATIVE, lpAmount, minAmountOut); // [!code --]
IWETH(WRAPPED_NATIVE).withdraw(wethReceived); // [!code --]
// Remove liquidity directly; no unwrapping needed // [!code ++]
pool.removeLiquidity(USDC, lpAmount, minAmountOut); // [!code ++]
```

Note that internal-only wrapping that is not exposed to users (for example, an
internal accounting abstraction similar to what Chainlink uses) is acceptable.
User-facing WUSDC wrapper contracts are not.

### Step 4. Audit allowance-dependent sweep and security logic

ERC-20 allowances control transfers initiated through `transferFrom`. They do
not gate direct native transfers. A contract holding an `approve` from a user
can still have its native USDC balance moved by a direct native send
(`call{value: ...}`) without any allowance check.

Any sweep mechanism or authorization boundary that relies on ERC-20 allowance
limits to protect the contract's native USDC balance is ineffective. Audit your
pool's security logic for this pattern and apply one of the following
approaches:

* Mark the contract `nonpayable` on functions that should not receive native
  USDC, preventing accidental native deposits.
* Add an explicit `receive()` function that either reverts or records the
  incoming balance, so the contract behaves predictably on both the native and
  ERC-20 paths.

### Step 5. Replace onchain randomness and audit emergency-shutdown patterns

Two EVM opcodes behave differently on Arc and require review before deployment.

<Warning>
  **PREVRANDAO always returns `0` on Arc.** Any protocol that uses
  `block.difficulty` or `prevrandao` for randomness receives a constant value,
  making lottery mechanics, shuffle algorithms, and similar constructs
  predictable and exploitable. Replace these with a verifiable random function
  (VRF) or oracle-based randomness. See [Oracles](/arc/tools/oracles) for
  available providers on Arc.
</Warning>

Emergency-shutdown and escape-hatch patterns that use `SELFDESTRUCT` also
require review. On Arc, a contract's USDC balance is its native balance, so
`SELFDESTRUCT` transfers that USDC to the beneficiary address. On Ethereum, the
ERC-20 USDC balance lives in the token contract and is unaffected by
`SELFDESTRUCT`.

<Warning>
  **`SELFDESTRUCT` moves native USDC on Arc.** Protocols with emergency-shutdown
  patterns that call `SELFDESTRUCT` will transfer the contract's entire USDC
  balance to the beneficiary. Audit every code path that calls `SELFDESTRUCT`
  and verify that the USDC transfer is intentional.
</Warning>

### Step 6. Test against an Arc RPC endpoint, not a local Anvil fork

Local EVM simulators run a standard EVM and cannot reproduce the following
Arc-specific behaviors:

* Native-coin precompile behavior
* EIP-7708 Transfer events on native USDC movements
* Blocklist enforcement on value transfers
* Arc's native USDC value transfer rules

Run all pool invariant math and value transfer tests against
`https://rpc.testnet.arc.io` (chain ID `5042002`). For a complete porting
walkthrough covering contract deployment and verification, see
[Porting contracts to Arc](/arc/tutorials/porting-contracts-to-arc).
