Skip to content

Discovering Pools ​

POE supports an unbounded number of pools. Integrators do not have to hard-code each market: the Factory keeps a registry, so you can enumerate pools or look them up by pair on-chain.

The Factory address is the same on every supported chain (see POE Contracts):

Factory: 0x78120F2C0EBF0cc8B7E7749e62D36e6523dD711D

Factory Lookup API ​

solidity
interface IFactory {
    // Total number of pools ever created.
    function getPoolsLength() external view returns (uint256);

    // Index-based access. `poolId` is in [0, getPoolsLength()).
    function getPoolAt(uint256 poolId) external view returns (address);

    // Look up a pool by token pair. Returns address(0) if no pool exists.
    function getPool(address tokenX, address tokenY) external view returns (address);

    event PoolCreated(
        uint256 indexed poolId,
        address indexed pool,
        address tokenX,
        address tokenY,
        bytes data
    );
}

getPool sorts the two token addresses internally. getPool(tokenX, tokenY) and getPool(tokenY, tokenX) return the same pool.

WARNING

Do not use getPool lookup order to infer token roles. The pool's canonical ordering still comes from pool.getTokens(), and that ordering matters for oracle keys, swap direction, and amount interpretation.

Listing every pool ​

Enumerate [0, getPoolsLength()) and resolve each pool to its token pair:

solidity
IFactory factory = IFactory(0x78120F2C0EBF0cc8B7E7749e62D36e6523dD711D);

uint256 n = factory.getPoolsLength();
for (uint256 i = 0; i < n; ++i) {
    address pool = factory.getPoolAt(i);
    (address tokenX, address tokenY) = IBasePool(pool).getTokens();
    // ...
}

The pool exposes the pair via getTokens() (returns the same (tokenX, tokenY) tuple the pool was initialized with) and its current balances via getBalances(). See Tracking Liquidity for the full pool read API.

TypeScript ​

typescript
import { ethers } from 'ethers';

const FACTORY = '0x78120F2C0EBF0cc8B7E7749e62D36e6523dD711D';

const FACTORY_ABI = [
    'function getPoolsLength() view returns (uint256)',
    'function getPoolAt(uint256 poolId) view returns (address)',
    'function getPool(address tokenX, address tokenY) view returns (address)',
];

const POOL_ABI = [
    'function getTokens() view returns (address tokenX, address tokenY)',
    'function getBalances() view returns (uint256 totalX, uint256 totalY)',
];

async function listPools(provider: ethers.Provider) {
    const factory = new ethers.Contract(FACTORY, FACTORY_ABI, provider);
    const n: bigint = await factory.getPoolsLength();

    const pools = await Promise.all(
        Array.from({ length: Number(n) }, async (_, i) => {
            const address: string = await factory.getPoolAt(i);
            const pool = new ethers.Contract(address, POOL_ABI, provider);
            const { tokenX, totalX, tokenY, totalY } =
                Object.assign({}, await pool.getTokens(), await pool.getBalances());
            return { address, tokenX, tokenY, totalX, totalY };
        })
    );

    return pools;
}

Python ​

python
from web3 import Web3

FACTORY = '0x78120F2C0EBF0cc8B7E7749e62D36e6523dD711D'

factory_abi = [
    {"name": "getPoolsLength", "type": "function", "inputs": [],
     "outputs": [{"type": "uint256"}], "stateMutability": "view"},
    {"name": "getPoolAt", "type": "function",
     "inputs": [{"name": "poolId", "type": "uint256"}],
     "outputs": [{"type": "address"}], "stateMutability": "view"},
    {"name": "getPool", "type": "function",
     "inputs": [{"name": "tokenX", "type": "address"},
                {"name": "tokenY", "type": "address"}],
     "outputs": [{"type": "address"}], "stateMutability": "view"},
]

pool_abi = [
    {"name": "getTokens", "type": "function", "inputs": [],
     "outputs": [{"name": "tokenX", "type": "address"},
                 {"name": "tokenY", "type": "address"}],
     "stateMutability": "view"},
]

def list_pools(w3: Web3):
    factory = w3.eth.contract(address=w3.to_checksum_address(FACTORY), abi=factory_abi)
    n = factory.functions.getPoolsLength().call()
    pools = []
    for i in range(n):
        address = factory.functions.getPoolAt(i).call()
        pool = w3.eth.contract(address=address, abi=pool_abi)
        token_x, token_y = pool.functions.getTokens().call()
        pools.append({"address": address, "token_x": token_x, "token_y": token_y})
    return pools

Looking up a specific pair ​

If you already know the pair, skip enumeration:

solidity
address pool = factory.getPool(tokenX, tokenY);
require(pool != address(0), "No pool for this pair");

If getPool(a, b) returns address(0), getPool(b, a) will return the same result. A zero result means no pool exists for that token pair.

Reacting to new pools ​

Pool creation emits a PoolCreated event from the Factory. Indexers and integrators can subscribe to this to discover new markets without polling:

solidity
event PoolCreated(
    uint256 indexed poolId,
    address indexed pool,
    address tokenX,
    address tokenY,
    bytes data
);

poolId is the index used by getPoolAt; data is the per-pool initialization blob (currently maxValue (12 bytes) | oracle (20 bytes)).