> For the complete documentation index, see [llms.txt](https://docs.arcadia.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.arcadia.finance/developers/integrations/wallets.md).

# Wallets & Indexers

Arcadia Finance is a DeFi yield-earning platform that lets users run leveraged automated-market-maker liquidity strategies on major DEXes (Uniswap, Aerodrome, Velodrome). Under the hood, the Arcadia Protocol provides user-owned, non-custodial smart-contract **Accounts** that come in two flavours: **Margin** Accounts (can borrow against deposited collateral) and **Spot** Accounts (pure asset holders for non-leveraged strategies). Arcadia supports both virtual and concentrated-liquidity positions and is live on Base, Optimism, and Unichain.

This page describes how to surface Arcadia positions in a wallet UI or indexer. End-users do not hold assets directly in their EOA; they hold them in **Arcadia Accounts**, which are proxy smart contracts owned by the EOA.

> All Arcadia contracts are deployed with **CREATE2**, so the core and periphery addresses are **identical on Base, Optimism, and Unichain**. The chain-specific differences are:
>
> * Lending pools available (Base: USDC, WETH, cbBTC; Optimism: USDC, WETH; Unichain: USDC, WETH but **margin is not yet enabled**; no Account versions are currently allowlisted to borrow on Unichain).
> * Slipstream V3 Asset Module and wrapper addresses (different on Optimism, which has a separate Velodrome v3 deployment; not deployed on Unichain).
> * Underlying DEX deployments (Uniswap V3/V4 position managers, Slipstream / Velodrome position managers).

## Chain Deployments

| Chain    | Chain ID | Block Explorer                                             |
| -------- | -------- | ---------------------------------------------------------- |
| Base     | 8453     | [basescan.org](https://basescan.org)                       |
| Optimism | 10       | [optimistic.etherscan.io](https://optimistic.etherscan.io) |
| Unichain | 130      | [uniscan.xyz](https://uniscan.xyz)                         |

## 1. The Factory

The **Factory** mints a non-transferable-by-itself ERC-721 (`ArcadiaAccount`) whose `tokenId == accountIndex`. Each token wraps an Account proxy.

```
Factory       0xDa14Fdd72345c4d2511357214c5B89A919768e59    (Base + Optimism + Unichain)
```

The Account NFT IS the right to control the underlying Account proxy. Transferring the NFT transfers the Account.

### Key Factory ABI

```solidity
// ERC-721 over Account ownership.
function ownerOf(uint256 tokenId) external view returns (address);
function balanceOf(address owner) external view returns (uint256);

// Map an Account proxy address to its NFT tokenId (returns 0 if not an Account).
function accountIndex(address account) external view returns (uint256);

// Reverse lookup: get the proxy address for a tokenId (storage array; 0-indexed in code, 1-indexed in NFT id).
function allAccounts(uint256 index) external view returns (address);

// Owner of an Account proxy.
function ownerOfAccount(address account) external view returns (address);

// Is the address an Arcadia Account?
function isAccount(address account) external view returns (bool);

// Total number of Accounts created.
function allAccountsLength() external view returns (uint256);

// Account creation.
function createAccount(uint32 userSalt, uint256 accountVersion, address creditor)
    external returns (address account);
```

### Key Factory events

```solidity
// ERC-721 mint/transfer, fired on every Account creation and transfer.
event Transfer(address indexed from, address indexed to, uint256 indexed id);

// Account upgraded to a new implementation version.
event AccountUpgraded(address indexed accountAddress, uint88 indexed newVersion);
```

The simplest way to index "Accounts owned by an EOA" is to subscribe to `Transfer` on the Factory.

## 2. Two Account Types: Margin (V3) and Spot (V4)

Each Account is a proxy. Its implementation determines its type. Because of CREATE2, the same implementation addresses are used across chains:

| Version | Type            | Implementation address                       |
| ------- | --------------- | -------------------------------------------- |
| 1       | Margin (legacy) | `0xbea2B6d45ACaF62385877D835970a0788719cAe1` |
| 2       | Spot (legacy)   | `0xd8AF1F1dEe6EA38f9c08b5cfa31e01ad2Bfbef28` |
| 3       | Margin          | `0x78Db6a136EdD0F70bEd7a6eb5ca2fDF6eE16E8D6` |
| 4       | Spot            | `0xe976BFb44f9322164ca6fdA6C5B84fBb6163D442` |

Per-chain availability:

| Version            | Base      | Optimism               | Unichain                            |
| ------------------ | --------- | ---------------------- | ----------------------------------- |
| 1 (Margin, legacy) | available | not used (placeholder) | not used (placeholder)              |
| 2 (Spot, legacy)   | available | not used (placeholder) | not used (placeholder)              |
| 3 (Margin)         | available | available              | available (no borrow allowlist yet) |
| 4 (Spot)           | available | available              | available                           |

> **Legacy caveat.** V1 (margin) and V2 (spot) Accounts only exist on Base and are considered legacy. The storage layout and most of the ABI (`creditor()`, `numeraire()`, `generateAssetData()`, `getUsedMargin()`, balance mappings, `Transfers` events) are the same as V3/V4, so the same indexing logic works. Newly-created Accounts on Base default to V3/V4; you may still encounter V1/V2 Accounts that were never upgraded.

To determine the type of an Account at runtime, call:

```solidity
function ACCOUNT_VERSION() external pure returns (uint256);
```

* `ACCOUNT_VERSION() ∈ {1, 3}` → **Margin Account** (can have debt).
* `ACCOUNT_VERSION() ∈ {2, 4}` → **Spot Account** (no debt; pure asset holder).

### Common state on every Account (from `AccountStorageV1`)

```solidity
function owner() external view returns (address);            // current owner (mirrors NFT owner)
function registry() external view returns (address);         // pricing/allowlist registry
function creditor() external view returns (address);         // 0x0 for Spot, LendingPool address for Margin
function numeraire() external view returns (address);        // the unit liabilities are denominated in
function minimumMargin() external view returns (uint96);     // gas-buffer margin (in numeraire decimals)
function liquidator() external view returns (address);       // 0x0 if no creditor

// Internal balance bookkeeping used by Margin (V3) Accounts; written only via routed
// deposit/withdraw flows. Not populated on Spot (V4) Accounts: do NOT use these for V4
// balance queries (see section 4).
function erc20Balances(address token) external view returns (uint256);
function erc1155Balances(address token, uint256 id) external view returns (uint256);
```

## 3. Margin Account (V3): Assets and Debt

A **Margin Account** is linked to exactly one Creditor (a LendingPool). Read it with:

```solidity
function creditor() external view returns (address);
```

### Possible creditors (LendingPools)

LendingPool addresses are identical on all three chains (CREATE2). The debt-asset address depends on the chain.

| Pool  | LendingPool address                          | Underlying (Base)                            | Underlying (Optimism)                        | Underlying (Unichain)                        |
| ----- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- |
| USDC  | `0x3ec4a293Fb906DD2Cd440c20dECB250DeF141dF1` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | `0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85` | `0x078D782b760474a361dDA0AF3839290b0EF57AD6` |
| WETH  | `0x803ea69c7e87D1d6C86adeB40CB636cC0E6B98E2` | `0x4200000000000000000000000000000000000006` | `0x4200000000000000000000000000000000000006` | `0x4200000000000000000000000000000000000006` |
| cbBTC | `0xa37E9b4369dc20940009030BfbC2088F09645e3B` | `0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf` | *not deployed*                               | *not deployed*                               |

> On **Unichain**, the USDC and WETH LendingPools exist but no Account versions are currently allowlisted to borrow against them. In practice, Unichain Accounts today are all Spot (V4).

(The numeraire of the Account equals the underlying asset of the Creditor.)

The LendingPool itself is an ERC-4626 with the debt token as the underlying. Each pool has a Tranche (an interest-bearing share) and a Wrapped Tranche (ERC-4626 wrapper of the Tranche).

### Reading assets in a Margin Account

```solidity
function generateAssetData()
    external
    view
    returns (
        address[] memory assetAddresses, // ERC-20 / ERC-721 / ERC-1155 contract addresses
        uint256[] memory assetIds,       // 0 for ERC-20; tokenId for ERC-721/1155
        uint256[] memory assetAmounts    // balance for ERC-20/1155; 1 for ERC-721
    );

function getAccountValue(address numeraire_) external view returns (uint256 accountValue);
function getCollateralValue() external view returns (uint256 collateralValue);
function getLiquidationValue() external view returns (uint256 liquidationValue);
```

`generateAssetData()` returns three parallel arrays of every asset held in the Account, including ERC-721 LP NFTs and ERC-1155 positions, with no off-chain indexing required.

### Reading debt and open liabilities

```solidity
function getUsedMargin() external view returns (uint256 usedMargin);
function getFreeMargin() external view returns (uint256 freeMargin);
function isAccountUnhealthy() external view returns (bool isUnhealthy);
```

> **Note.** `usedMargin = openDebt + minimumMargin` (the latter is a small fixed gas buffer). The bare open liability is:
>
> ```solidity
> // On the LendingPool / Creditor:
> function getOpenPosition(address account) external view returns (uint256 openPosition);
> // openPosition is denominated in the pool's underlying asset (with its native decimals).
> ```

Alternative: the user's "debt token" balance is also visible directly through the LendingPool's ERC-4626-like accounting:

```solidity
function maxWithdraw(address account) external view returns (uint256);  // same value as getOpenPosition
```

### Health metrics (for UI display)

Margin Accounts expose everything needed to render health, borrow capacity, and liquidation risk in the wallet UI. All values are denominated in the Account's `numeraire` (with that asset's native decimals).

```solidity
function getAccountValue(address numeraire_) external view returns (uint256 accountValue);   // mark-to-market total
function getCollateralValue() external view returns (uint256 collateralValue);               // MTM minus collateral-factor haircut
function getLiquidationValue() external view returns (uint256 liquidationValue);             // MTM minus liquidation-factor haircut (covers penalty + slippage + auction fees)
function getUsedMargin() external view returns (uint256 usedMargin);                         // openDebt + minimumMargin
function getFreeMargin() external view returns (uint256 freeMargin);                         // max(collateralValue - usedMargin, 0)
function isAccountUnhealthy() external view returns (bool);                                  // collateralValue < usedMargin
function isAccountLiquidatable() external view returns (bool);                               // liquidationValue < usedMargin
```

Suggested derived metrics for a wallet UI:

* **Total value:** `getAccountValue(numeraire)` (raw mark-to-market, ignoring haircuts).
* **Net equity:** `getAccountValue(numeraire) - getOpenPosition(account)`.
* **Borrow capacity remaining:** `getFreeMargin()`.
* **Health factor (collateral-side):** `getCollateralValue() / getUsedMargin()`. Below `1.0` the Account is unhealthy (cannot borrow more, owner must repay or add collateral).
* **Health factor (liquidation-side):** `getLiquidationValue() / getUsedMargin()`. Below `1.0` the Account is liquidatable and may be put up for Dutch auction.

If the Account has no debt (`creditor() == 0x0` or `getOpenPosition(account) == 0`), `usedMargin` collapses to just `minimumMargin` and health checks are trivially satisfied.

## 4. Spot Account (V4): Assets

Spot Accounts have **no creditor**, **no debt**, and **no `generateAssetData()`**. They are pure asset holders.

**Spot Account holdings must be indexed via the token contracts, not the Account itself.** V4 does not store any per-asset bookkeeping: `_deposit()` and `_withdraw()` only execute the token transfer and emit `Transfers`. They do **not** update `erc20Balances` / `erc1155Balances`. Those mappings are inherited from `AccountStorageV1` but are never written on V4, so they are permanently zero on Spot Accounts (any pre-existing values are cleared by `upgradeHook` when an Account is upgraded from V1/V2). Combined with the fact that anyone can send tokens directly to the Account proxy with a plain ERC-20 / ERC-721 / ERC-1155 transfer that bypasses the Account entirely, this means neither the Account's `Transfers` event nor its storage mappings are authoritative.

Index ERC-20 / ERC-721 / ERC-1155 `Transfer` events on the underlying token contracts, filtered by `to == accountProxy` (inflows) and `from == accountProxy` (outflows). For the current balance, call `balanceOf(accountProxy)` or `ownerOf(tokenId)` directly on the token contract.

```solidity
// On the ERC-20 / ERC-721 / ERC-1155 token contract:
event Transfer(address indexed from, address indexed to, uint256 valueOrTokenId);                       // ERC-20 / ERC-721
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);  // ERC-1155
event TransferBatch (address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);

function balanceOf(address account) external view returns (uint256);                                    // ERC-20 / ERC-1155 (with id)
function ownerOf(uint256 tokenId) external view returns (address);                                      // ERC-721
```

## 5. Arcadia Asset Addresses (LP positions, staked, wrapped)

These are the assets that Margin and Spot Accounts will most commonly hold (on top of plain ERC-20s).

### 5.1 CL position managers (the assets themselves)

| Protocol                                | Position Manager (Base)                      | Position Manager (Optimism)                  | Position Manager (Unichain)                  |
| --------------------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- |
| Uniswap V3                              | `0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1` | `0xC36442b4a4522E871399CD717aBDD847Ab11FE88` | `0x943e6e07a7E8E791dAFC44083e54041D743C46E9` |
| Uniswap V4                              | `0x7C5f5A4bBd8fD63184577525326123B519429bDc` | `0x3C3Ea4B57a46241e54610e5f022E5c45859A1017` | `0x4529A01c7A0410167c5740C487A8DE60232617bf` |
| Slipstream V1 (Aero v1 CL / Velo v1 CL) | `0x827922686190790b37229fd06084350E74485b72` | `0x416b433906b1B72FA758e166e239c43d68dC6F29` | `0x991d5546C4B442B4c5fdc4c8B8b8d131DEB24702` |
| Slipstream V2 (Aero v2 CL)              | `0xa990C6a764b73BF43cee5Bb40339c3322FB9D55F` | *(not deployed)*                             | *(not deployed)*                             |
| Slipstream V3 (Aero v3 CL / Velo v3 CL) | `0xe1f8cd9AC4e4A65F54f38a5CdAfCA44f6dD68b53` | `0xf7f8ccce99Ca2896eC75D3A399D152dB96808399` | *(not deployed)*                             |

These are standard Uniswap-V3-style ERC-721 position NFTs. An Arcadia Account can hold them directly (both Spot and Margin).

### 5.2 Staked Slipstream positions: Asset Modules (also ERC-721)

The Staked Slipstream Asset Modules are themselves the ERC-721 contracts that represent a Slipstream position that has been deposited into its gauge. **The position ID is preserved**: staked-position id `1000` corresponds to the underlying Slipstream position id `1000` on the relevant position manager.

| Asset                 | Address (Base)                               | Address (Optimism)                           | Address (Unichain)                           |
| --------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- |
| Staked Slipstream V1  | `0x1Dc7A0f5336F52724B650E39174cfcbbEdD67bF1` | `0x1Dc7A0f5336F52724B650E39174cfcbbEdD67bF1` | `0x1Dc7A0f5336F52724B650E39174cfcbbEdD67bF1` |
| Staked Slipstream V2  | `0xBed6C3E35B9B1e044b3Bc71465769EdFDC0FDD4c` | *(not deployed)*                             | *(not deployed)*                             |
| Staked Slipstream V3  | `0xE0F20BE5886F11CbcD2cb5bA9987Bcbbf1d8ca7b` | `0xF6a87d944204bb5Fdb9CF5534c03c46895f78eCd` | *(not deployed)*                             |
| Staked Aerodrome (LP) | `0x9f42361B7602Df1A8Ae28Bf63E6cb1883CD44C27` | n/a (Aerodrome is Base-only)                 | n/a                                          |
| Staked Stargate       | `0xae909e19fd13C01c28d5Ee439D403920CF7f9Eea` | n/a                                          | n/a                                          |

ABI for any Staked Slipstream Asset Module (same shape across V1/V2/V3):

```solidity
// ERC-721
function ownerOf(uint256 tokenId) external view returns (address);
function balanceOf(address owner) external view returns (uint256);

// The reward token (AERO on Base, VELO on Optimism, XVELO on Unichain).
function REWARD_TOKEN() external view returns (address);

// Pending rewards on a position (gauge-style "earned").
function rewardOf(uint256 positionId) external view returns (uint256 rewards);
```

### 5.3 Wrapped Staked Slipstream: for Spot Accounts (ERC-721 wrappers)

Margin Accounts can hold **Staked Slipstream** positions directly. Spot Accounts cannot (they do not price liabilities), so Arcadia exposes **Wrapped Staked Slipstream** wrappers; these are ERC-721s with the **same position id** as the underlying Slipstream NFT.

| Wrapped Asset                 | Address (Base)                               | Address (Optimism)                           | Address (Unichain)                           |
| ----------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- |
| Wrapped Staked Slipstream V1  | `0xD74339e0F10fcE96894916B93E5Cc7dE89C98272` | `0xD74339e0F10fcE96894916B93E5Cc7dE89C98272` | `0xD74339e0F10fcE96894916B93E5Cc7dE89C98272` |
| Wrapped Staked Slipstream V2  | `0x147a2CcbAF4521ad209A2875AE0B3c496f4B25a4` | *(not deployed)*                             | *(not deployed)*                             |
| Wrapped Staked Slipstream V3  | `0x9189BC25f8faC157B4D87b0b3c14F56bA1477d53` | `0xC4D3d804ed64C1f78097799208D46b1db4252749` | *(not deployed)*                             |
| Wrapped Staked Aerodrome (LP) | `0x17B5826382e3a5257b829cF0546A08Bd77409270` | n/a                                          | n/a                                          |

ABI (same `rewardOf` semantics as the Staked AM):

```solidity
function ownerOf(uint256 tokenId) external view returns (address);
function balanceOf(address owner) external view returns (uint256);

function REWARD_TOKEN() external view returns (ERC20);                    // AERO / VELO / XVELO
function POSITION_MANAGER() external view returns (address);              // the underlying Slipstream NPM
function idToGauge(uint256 positionId) external view returns (address);   // the CLGauge backing the wrapped position
function rewardOf(uint256 positionId) external view returns (uint256 rewards);

event RewardPaid(uint256 indexed positionId, address indexed reward, uint128 amount);
```

> **Position id transparency.** For every "staked" and "wrapped staked" wrapper, the tokenId equals the underlying Slipstream Position Manager tokenId. So position id `1000` on `0x9189BC25f8faC157B4D87b0b3c14F56bA1477d53` (Wrapped Staked Slipstream V1, Base) refers to the same underlying Slipstream V1 liquidity position with id `1000` on `0x827922686190790b37229fd06084350E74485b72`. The user's net exposure is: the underlying LP composition + accrued LP fees + accrued gauge rewards (`rewardOf(id)`).

### 5.4 Other assets that Arcadia Accounts may hold

| Asset                | Address (Base)                                                                                             | Notes                       |
| -------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------- |
| Stargate LP (ERC-20) | factory `0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6`, staking `0x06Eb48763f117c7Be887296CDcdfad2E4092739C` | Base only                   |
| Aerodrome v1 pools   | indexed via Aero factory `0x420DD381b31aEf6683db6B902084cB0FFECe40Da`                                      | LP tokens are plain ERC-20s |

### 5.5 Registry and Asset Modules (for advanced lookups)

For programmatic "is this address recognized by Arcadia, and what type is it?":

```
Registry                       0xd0690557600eb8Be8391D1d97346e2aab5300d5f   (all chains)
ERC20 Primary Asset Module     0xfBecEaFC96ed6fc800753d3eE6782b6F9a60Eed7   (all chains)
Uniswap V3 Asset Module        0x21bd524cC54CA78A7c48254d4676184f781667dC   (all chains)
Uniswap V4 Asset Module        0xb808971ea73341b0d7286B3D67F08De321f80465   (all chains)
Slipstream V1 AM               0xd3A7055bBcDA4F8F49e5c5dE7E83B09a33633F44   (all chains)
Slipstream V2 AM               0x3aDE1F1FdC666B1bFAd376345EA878D1c11EB73B   (Base only)
Slipstream V3 AM (Base)        0xcaf4167dE878Cfb23D9912b1ff5869F2b3527189
Slipstream V3 AM (Optimism)    0xb5bD6B1f9282328B4f21337Fd3BeFe49964627d7
Wrapped Aerodrome AM           0x17B5826382e3a5257b829cF0546A08Bd77409270   (Base only)
```

## 6. End-to-end Indexer Pseudocode

```ts
// 1) Find all Arcadia Accounts owned by an EOA.
const factory = "0xDa14Fdd72345c4d2511357214c5B89A919768e59";
const balance = await Factory.balanceOf(eoa);
// Either: enumerate via Factory `Transfer` event logs (recommended),
// or page over allAccountsLength() + allAccounts(i) + ownerOfAccount(account) === eoa.

for (const account of accounts) {
  const version = await IAccount(account).ACCOUNT_VERSION();
  const isMargin = version === 1n || version === 3n;
  const creditor = await IAccount(account).creditor();        // 0x0 if Spot

  if (isMargin) {
    // Assets.
    const [addrs, ids, amounts] = await IAccount(account).generateAssetData();
    // Debt.
    const debt = creditor !== ZERO
      ? await ICreditor(creditor).getOpenPosition(account)    // pool's underlying decimals
      : 0n;
    const usedMargin = await IAccount(account).getUsedMargin();   // = debt + minimumMargin
    const collateralValue = await IAccount(account).getCollateralValue();
  } else {
    // Spot: there is no on-chain enumerator AND the Account's own state is not authoritative
    // (direct transfers to the proxy bypass the Account's bookkeeping).
    // Discover holdings by indexing ERC-20/721/1155 `Transfer` events on the token contracts,
    // filtered by `to == account` (inflows) and `from == account` (outflows).
    // For the current balance, query the token contract directly:
    const usdcBal = await IERC20(USDC).balanceOf(account);
  }
}
```

For Slipstream / Staked Slipstream positions surfaced above:

* Compose the LP token0/token1 underlying via the position manager's `positions(id)` call.
* Add pending fees from `positions(id).tokensOwed0/1` (Uniswap V3 / Slipstream).
* Add pending gauge rewards via `rewardOf(id)` on the (Wrapped) Staked Slipstream contract.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.arcadia.finance/developers/integrations/wallets.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
