DEVELOPER DOCS
No sections match that search. Try router, address, abi, quickstart.
Dev / Get started / Quickstart

Quickstart

Start with public market data, or use verified contract interfaces for your own wallet integration.

For integrators

The DLMM V2 ABI index contains copies of the application's canonical interface bundle. Contract sections below include historical deployments. Verify the target factory and current proxy implementation before signing; a published ABI alone does not establish deployment compatibility.

1 · Pick an RPC

Public RPC works for development. For production, use a dedicated provider (Alchemy / Infura / QuickNode) so you aren't rate-limited.

# Base mainnet (chain ID 8453)
RPC=https://mainnet.base.org

2 · Read a contract

Try the U1 token name with cast (Foundry):

cast call 0x14a4e80d633aF55Ace1160c320f5a36D41CCEd3E \
  'name()(string)' --rpc-url $RPC
# → "Umbrae One"

The same call with viem in TypeScript:

import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';

const client = createPublicClient({ chain: base, transport: http() });

const name = await client.readContract({
  address: '0x14a4e80d633aF55Ace1160c320f5a36D41CCEd3E',
  abi: [{ name: 'name', type: 'function', stateMutability: 'view',
          inputs: [], outputs: [{ type: 'string' }] }],
  functionName: 'name',
});
// "Umbrae One"

3 · Get the active DLMM bin

Reads the live U1/WETH DLMM pair (V2 stack, 100 bps, at 0xd288…5ff2) for the active bin ID:

cast call 0xd288889ef25ca7483277f39c262098f6ba615ff2 \
  'getActiveId()(uint24)' --rpc-url $RPC

4 · Find the contract you need

  • Address Book — every contract address with explorer link.
  • ABIs — downloadable JSON for every deployed contract.
  • Deploy blocks — useful for log-scan ranges.

5 · Pick your integration

  • DLMM Router — swap quotes and multi-hop on the concentrated-liquidity DEX.
  • DAMM Pair — the U1/WETH constant-product pool (simpler, single-pair).
  • Custody keeper — auto-rebalance a DLMM position; eligibility-gated.
  • Aggregator adapter pattern — surface our pools through 1inch / OpenOcean / Matcha.
Dev / Get started / Network & RPC

Network & RPC

Base mainnet endpoints, chain ID, and the explorer you'll use most.

Chain
Base mainnet
Optimistic rollup, OP Stack
Chain ID
8453
Block time
~2 s
Explorer

RPC endpoints

EndpointUse
https://mainnet.base.orgPublic RPC. Rate-limited — fine for development, not for production.
Alchemy / Infura / QuickNodeProduction. Most providers offer Base in their default plan.

WETH on Base

The canonical wrapped-ETH contract is at 0x4200000000000000000000000000000000000006. Read each pair's token addresses to determine its assets and direction; WETH is not always the Y side.

Dev / Get started / Authentication

Authentication & sessions

Only relevant if you're hitting Umbrae's authenticated HTTP API. Pure on-chain integration (reading contracts, signing transactions) needs nothing in this section.

On-chain reads = no auth

Every contract on Umbrae is public on Base. Reading state, scanning logs, and submitting wallet-signed transactions never requires an Umbrae session.

When authentication applies

The public market-data adapters require no authentication. App endpoints outside that surface have route-specific access rules: some reads are anonymous, while protected operations require sessions or scoped API keys. They are not a versioned external execution contract.

Sign-in flow

Sign-in is EVM wallet-signature based. Use the platform's sign-in flow for account access; the public market-data interface does not require signing in.

Session token shape

  • Sessions are stored server-side in auth_sessions with hashed tokens.
  • Each request validates: token exists, not expired, wallet matches.
  • Expired sessions are auto-deleted; the next request returns 401 and the client re-signs.
  • Scoped API keys are available through the account API-key flow. Creation requires an existing session and the account's email policy; an API key cannot issue another API key.
  • An account API key resolves to that account's primary wallet. The swap builder requires write scope and binds both caller and recipient to the resolved wallet; it is not an arbitrary-recipient routing interface.

Public HTTP API

Use the OpenAPI specification for supported anonymous market-data operations. Read the integration guide for units, retries and the boundary between discovery and execution.

Dev / Public API & agents

Public API & agents

Read Umbrae market data on Base without an API key, session or wallet connection.

First request

curl --fail --silent --show-error \
  https://www.umbrae.io/api/public/geckoterminal/dlmm/latest-block
OperationInputResult
GET /latest-blockNoneLast indexed block and timestamp
GET /assetToken address as idToken metadata
GET /pairPool address as idPool metadata
GET /eventsfromBlock and toBlockEvents in an inclusive block range

Choose a DAMM or DLMM base URL from the OpenAPI server list. GeckoTerminal and DEX Screener URLs are aliases for the same venue data. DLMM adapters expose curated pools and swap events; DAMM also exposes joins and exits.

Build a reliable reader

Preserve token-unit strings, deduplicate events by transaction hash and event index, and advance your cursor only after a successful response. Honor Retry-After on 429 and back off on upstream 502 responses. Indexed data can lag the chain; it is not an executable quote.

Trading and liquidity provision

This schema describes market data only. Use the DLMM V2 ABI bundle as an interface reference, verify the current deployment, quote and simulate from the actual caller, and sign with the caller's wallet. Multi-step swaps and LP exits need explicit completion tracking. Account keys do not grant authority over unrelated wallets.

Documentation prepared by GPT-6 via Codex, 2026-09-08.

Dev / Reference / Address Book

Address Book

DLMM V2 addresses are generated from the application configuration. Legacy deployments are identified separately. Other deployment notes are dated records; verify live state before signing.

Source of truth

Addresses here are mirrored from docs/CONTRACTS.md in the Umbrae monorepo. If anything on this page disagrees with that file, that file wins. Every address links to its Basescan / Etherscan #code page where you can read the verified Solidity source.

Token + migration

NameAddressState
U1Token
Umbrae One · U1
0x14a4e80d633aF55Ace1160c320f5a36D41CCEd3ESealed supply 5,000,000 × 1018. 18 decimals. No mint, no burn, no owner.
U1Migration0xb374f3bC4085dE6675AF44Fa81472A93402ad467Owner = TimelockController. Window OPEN until unix 1780388389 (≈ June 2026). 72h delay between entitlement set and claim().
U1ReserveLP0x9bb9961059f90041672AdE075450DDA398310fBdHolds 850,000 U1. Only outbound op: depositIntoLP to the DAMM pool.

Rewards

NameAddressState
U1TokenLockVault0xf771F202e8B49612e83f18B68D6b268765A40F725% protocol-fee payout vault for U1 lockers. Reward token = WETH. MIN_LOCK_DURATION = 7 days. Owner = U1/DAMM Timelock.

Current DLMM V2 trading stack — Base mainnet

Remediated deployment of 2026-08-24. Use these addresses with the V2 ABI bundle. Download the deployment registry. Implementation and extension addresses are source references; call the appropriate proxy or pair.

Configuration nameAddress
timelock0xFfdF1cc01BE7150a81E537Dca97364C7022c82EB
factory0x9DBB9289d0D75508b5D8EE01FfE4eb7c412F733b
router0x5A7673d413f510f4b5c191d96837694C5942bF38
beacon0xf74ca4e58DA6f4F8D1dc1D9fF087b1bEd1d9a573
positionNftManager0x362A4AD56E93B71b0dBB28f9Ba1e578E5b077134
pairViewer0xEaBC5b163bF79CdCE31027E722361802FAbBe8fD
positionNftManagerViewer0x9c37eD70cf46a732A9Ac60663C1811dAa866822C
pairExtension0x17BDe1AFdb7218347f422E1e0c26296F73799340
pairImplementation0x2d7C648f6D33A89550c72218003d0c683F25AF5a
pausedImplementation0x7f5464D476318D36177De9bFE80422Bd58E502Fe

Discover pools through the current factory's allPairsLength() and allPairs(i). Read the pair's tokens and bin step instead of reusing an old pair list.

Legacy DLMM — withdrawal-only

Paused for trading. Do not integrate, deposit, or grant new approvals to this retired deployment. These addresses are retained only to identify existing positions for withdrawal. The current application uses the V2 deployment above. Use the legacy interface only when withdrawing a verified legacy position.

NameAddressState
DLMM Router (proxy)0x4965DD6877ca9DE77caca2f57996651e7AF23c93Retired deployment; legacy withdrawals only.
DLMM Factory0x17Da44dcbdffD8c715be7A368E19F252C2940FeeRetired deployment; legacy withdrawals only.
DLMM Beacon0xa1BF1667bE1Dab835E224CDA666728f3e5194496Retired deployment; legacy withdrawals only.
Position NFT Manager (proxy)0x1a49ab1d93C269D22E98a8786823526813b04bD6Retired deployment; legacy withdrawals only.
Pair Viewer (proxy)0xbA3A5400A95b055b1412e18ad1978EdF32Fc3F05Retired deployment; legacy withdrawals only.
Position NFT Viewer (proxy)0x13302a890EaA061f6015d8ab2c08FaB28b1C5538Retired deployment; legacy withdrawals only.

Retired custody keeper stack (v8) — do not integrate

Historical addresses only. v8 was retired after a fee-attribution defect. These contracts are not the current V2 trading stack. Consult the canonical contract registry for keeper migration status.

Keeper v8 deployed 2026-06-23 (deploy block 47679950, owned by 48h timelock). v8 is a keeper-only upgrade: CustodyRebalanceKeeper + linked KeeperMath changed; peripherals (OrderManager / OrderExecutor / EligibilityManager / UmbraeSwapRouter / PositionNFTManager) reused from v7 unchanged.

ContractAddress
CustodyRebalanceKeeper (v8)0xa98D5e73D890A0A5C1db478a456F8d29B1581fE8
OrderManager0xA6861fD7a51f479dF84A300721222d350fFeC8c9
OrderExecutor0x8d28ce16cBf19F987A72B531b75756AA5f15C00e
EligibilityManager0x6AE6faf6F82cf2ccE8e40bF473c2000D1eBD4102
UmbraeSwapRouter0xA8F5f4DbdadD1de15fb9A77E0e53950a70186199
KeeperMath (linked library, v8)0x5B31C45d80EBD1b589AC5DE0f94755Ced3383275

DAMM V4 trading stack

NameAddress
DAMMPair (U1/WETH, DAMM U1-WETH LP)0x296964C34a571fCf85d3F74FB815ee871F5A08d4
DAMMFactory (proxy, ERC-1967)0xD14322b444415d78DBBF646BB369Ec325a1aCD5c
DAMMRouter0x71225FC9E1c522dC33E8f7CFA3E8dBCf2F3dF205
DAMMPairBeacon0xB6363F6091d185B431Ad51F7Cc0eF49797fa8569
DAMMPair impl0x1d8752A8533668FBC49E4721790592f3111A3D0A
DAMMPairPaused (emergency stub)0xEcEc2DCBE4a61De7A7f8530B510a801b0EA76B22
DAMMFactory impl0x5491E26dBFF761C10478e43d0D5c289CC8816630

Governance

RoleAddressDelay
U1 / DAMM Timelock0xC915dA2847A0640339D0071a0f92cC1562f8Eee448h
Legacy DLMM Timelock (withdrawal-only stack)0x8c1Ab74D8FDE9d5B860896da2A1057d59513E2CF10 min

External canonical

NameAddressNotes
WETH (Base canonical)0x4200000000000000000000000000000000000006Wrapped ETH on Base. The Y side of every Umbrae pair.

Ethereum mainnet

NameAddressState
IgnisNFTLockVault0x7c0A392fA177E5019FFd47f3daD5982BB1110D3CLocks an Ignis Elite NFT for 7/14/30 days. Eligibility for keeper access + platform rewards.
Dev / Reference / ABIs

ABIs

For DLMM V2, use the current application ABI bundle. The table below includes legacy DLMM and keeper interfaces; verify deployment compatibility before use. Bind a proxy's implementation ABI at the proxy address.

Naming

live-impl means the address you talk to is a proxy — the ABI is the implementation behind it. live means the address is the contract itself. pre-deploy means built and audited but not on-chain yet (skipped from this index).

Current DLMM V2 interfaces

Download canonical V2 interfaces and hashes, paired with the generated deployment registry. Use the proxy address from the Address Book, not an implementation address.

Historical interfaces

Legacy DLMM and keeper ABI downloads describe retired deployments. They are not compatible references for a new V2 integration. Use them only for a verified legacy withdrawal.

Bind at the proxy, not the implementation

For any proxy contract: bind the ABI at the proxy address you find in the Address Book, not at the implementation address. Calls to the implementation directly do not have state and may revert.

Dev / Reference / Deploy blocks & explorer

Deploy blocks & explorer

Useful for log-scan ranges and historical reads. All references are on Basescan (chain ID 8453).

Custody keeper stack v8 (active)

ContractDeploy blockNotes
CustodyRebalanceKeeper47679950v8 deploy 2026-06-23. Earliest block to scan for Rebalanced, FeesRealized, RetainedWithdrawn events.
KeeperMath (linked lib)47679950v8 redeploy alongside the keeper.

DLMM pairs

PairNotes
U1 / WETHAdded 2026-05-15 (verified via factory.allPairs(13)).

Block explorer

  • Basescan — basescan.org. Every contract on this site links to its #code page for verified source.
  • Etherscan — etherscan.io, used only for the Ethereum-side NFT Lock Vault.
Dev / Tokens / U1 token

U1 token

A standard ERC-20 with a sealed supply. No mint, no burn, no owner.

Name / Symbol
Umbrae One · U1
Decimals
18
Total supply
5,000,000
Sealed (no mint or burn function exists)

Reads

name()        → "Umbrae One"
symbol()      → "U1"
decimals()    → 18
totalSupply() → 5000000 * 1e18
balanceOf(address) → uint256
allowance(owner, spender) → uint256

Writes

approve(spender, amount) → bool
transfer(to, amount) → bool
transferFrom(from, to, amount) → bool

Why "sealed"

The contract has no mint, burn, pause, or owner-only functions. Supply at the address above is the exhaustive supply of U1 that will ever exist. Verify on-chain:

cast call 0x14a4e80d633aF55Ace1160c320f5a36D41CCEd3E 'totalSupply()(uint256)' \
  --rpc-url https://mainnet.base.org
# → 5000000000000000000000000
Dev / Tokens / U1 lock vault

U1 lock vault (rewards)

Lock U1 to earn the protocol-fee payout in WETH. Pull-based rewards, monotonic lock extension, withdraw always available after the lock window.

Lock token
U1
Reward token
WETH
Min lock
7 days

Lifecycle

  1. Approve U1 to the vault, then lock(amount, duration).
  2. Rewards accumulate as protocol fees are deposited; query your share with pendingReward(holder).
  3. Claim WETH any time via claimReward() — works while still locked.
  4. Extend the lock by calling extendLock(newUnlockTime) — monotonic, new unlock time must be ≥ current.
  5. Withdraw the U1 after unlockTime via withdraw().

Surface

lock(uint256 amount, uint256 duration) external
extendLock(uint256 newUnlockTime) external
claimReward() external returns (uint256)
withdraw() external
pendingReward(address holder) view returns (uint256)
lockOf(address holder) view returns (uint256 amount, uint64 unlockTime)
totalLocked() view returns (uint256)
totalRewardsAccumulated() view returns (uint256)
notifyReward(uint256 amount) external onlyOwner

Operational notes

  • Never transfer the reward token directly to the vault — use notifyReward (owner-only) so the per-holder accounting updates.
  • Per the audit gate, the operator waits until totalLocked >= 1_000e18 before the first notifyReward.
  • The vault is pausable but unlock always works, even when paused.
Dev / Tokens / Migration

Migration (sunsetting)

Sunsetting

The U1 migration window is open until unix 1780388389 (≈ June 2026). Entitled-but-unclaimed entries stay claimable forever — the window controls new entitlement creation, not claims. Once the window closes the contract goes read-only.

Solana → Base U1 migration. A backend operator sets per-address entitlements after off-chain validation; a 72h security delay then opens the claim.

Reads

migrationWindowEnd() view returns (uint256)         // 1780388389
isWindowOpen() view returns (bool)
entitlementOf(address user) view returns (uint256)
claimableAt(address user) view returns (uint256)    // unix; claims OK when block.timestamp >= this
isClaimable(address user) view returns (bool)
totalEntitled() view returns (uint256)
totalClaimed() view returns (uint256)
unclaimedTotal() view returns (uint256)

Write (user-facing)

claim() external                                    // claims caller's full entitlement

UI states

StateDetection
No entitlemententitlementOf(user) == 0
Entitled, not yet maturedentitlement > 0 && now < claimableAt — show countdown
Claimable nowisClaimable(user) == true

Custom errors

NoEntitlement()
ClaimNotYetAvailable(uint256 claimableAt)
WindowAlreadyClosed()

DLMM V2 overview

Use the remediated deployment in the Address Book. Positions hold LP shares across discrete price bins and are represented by ERC-721 NFTs.

V2 uses declared-input swaps, a unified price domain, per-bin fee backing, up to 101 bins per position and fee-claim pages of at most 40 bins. Legacy balance-delta swap examples and legacy position interfaces do not describe this deployment.

Read current pool parameters, fees and permissions from the deployed contracts. The canonical deployment record is docs/CONTRACTS.md; integration behavior is documented in docs/reference/DLMM_V2_INTEGRATION_2026-08-20.md.

DLMM V2 Router

Use router from the current deployment registry with UmbraeLBRouterUpgradeable.json.

Read the exact argument types for swapExactTokensForTokens or swapTokensForExactTokens from that ABI. Obtain a fresh quote from the V2 Pair Viewer, set the caller's slippage and deadline, and simulate the exact calldata from the actual caller before requesting a signature.

Verify each pair belongs to the current factory. Token allowance must target the current router; confirm a successful approval receipt before submitting the swap.

DLMM V2 Factory

Use factory from the current deployment registry with UmbraeLBFactoryV2.json.

Enumerate pairs with allPairsLength() and allPairs(i). Use the full V2 getPair signature, including baseFactor, from the ABI. Do not use a legacy three-argument lookup.

Pair creation is permissioned. Check the caller's pool-creation permissions before building a transaction. Read current ownership from the contract: the deployment registry records a pending factory handover, so deployment into the timelock-governed stack alone is not proof that handover completed.

DLMM V2 Pair

Discover the pair through the current V2 factory and bind UmbraeLBPairUpgradeable.json at the pair address. Implementation and extension addresses in the registry are source references, not independent trading destinations.

Use getActiveId, getBin, getTokenX and getTokenY for current pool state. Bind overloaded swap functions by their full ABI signature. Do not transfer tokens to a pair using a legacy balance-delta swap recipe.

Fees are accounted separately from principal. Read the relevant fee getters; a reserve or token-balance difference is not a fee entitlement.

DLMM V2 Pair Viewer

Use pairViewer from the current deployment registry with UmbraeLBPairViewer.json.

The viewer exposes quoteSwap, quoteSwapDetailed, quoteSwapIn and quoteSwapInDetailed. Decode the exact return shape from the current ABI. A quote is a read; simulate the transaction with the caller's minimum output before signing.

DLMM V2 Position NFT Manager

Use positionNftManager from the current deployment registry with PositionNFTManagerV2.json.

The lifecycle uses createPositionWithStrategy, increasePositionWithStrategy, decreasePosition, decreasePositionByPercent and closePosition. Use claimFeesRange to page fee claims within the 40-bin limit.

For create or increase, simulate the exact deposit and derive minTotalLiquidity from the total LP shares returned by that simulation. Distribution weights are not minted shares. Preserve the user's active-bin and share-slippage limits.

ERC-20 allowances target the manager. Pair setApprovalForAll(manager, true) grants the manager operator rights over the wallet's LP shares in that pair, including fee settlement. Explain this grant before signing, and require a successful receipt before the deposit build.

DLMM V2 Position NFT Viewer

Use positionNftManagerViewer from the current deployment registry with PositionNFTManagerViewer.json.

Decode the viewer's position and fee reads using this ABI. Position identity includes the manager and pair, not just a token ID. Keep historical legacy positions distinct from current V2 positions.

Dev / DLMM / Subgraph

DLMM Subgraph

The Graph subgraph indexing DLMM Factory, Pair, Position NFT Manager, and UmbraeLBToken events for pair / position / volume queries.

Schema highlights

  • Pair — tokens, bin step, active id, reserves, volume, fees, swap count, price changes (5m, 15m, 1h, 4h, 8h, 12h, 24h, 7d, 30d).
  • Position — NFT positions: owner, pair, bin ranges, liquidity amounts, fees accrued.
  • Swap — individual swap events (amount in/out, active id, fee, timestamp).
  • PairHourlySnapshot / PairDailySnapshot — time-series rollups for charts and 24h stats.
  • Deposit / Withdrawal / FeeClaim — per-user history.
  • User — wallet with aggregated stats (position count, fees claimed, last active).

Example queries

{
  pairs(orderBy: swapCount, orderDirection: desc, first: 20) {
    id tokenXSymbol tokenYSymbol binStep activeId
    reserveX reserveY totalVolumeX totalVolumeY totalFeesX totalFeesY
    swapCount positionCount
  }
}

# 24h volume
{
  pairDailySnapshots(where: { dayStartUnix_gte: 1706745600 }, orderBy: volumeX, orderDirection: desc) {
    pair { id tokenXSymbol tokenYSymbol }
    volumeX volumeY feesX feesY swapCount reserveX reserveY
  }
}

# User positions
{
  user(id: "0xalice...") {
    positions { tokenId pair { id } isOpen strategy }
  }
}

What the Graph CANNOT compute

The subgraph indexes token amounts. USD values are computed by your client by combining the subgraph data with a USD price feed:

volumeUsd = volumeX * priceXUsd + volumeY * priceYUsd
apy       = (fees24hUsd / tvlUsd) * 365 * 100

Use the source-of-truth USD feeds you trust (CoinGecko / Birdeye / our /api/base/weth-usd); the subgraph stays USD-free for portability.

Endpoint

Production gateway URL is provisioned per integrator with a consumer API key (do not share an Umbrae-owned URL — that puts query load on our quota). Reach out for issuance. Local dev: http://localhost:8000/subgraphs/name/umbrae/dlmm.

Dev / DAMM / Overview

DAMM overview & architecture

Umbrae's constant-product DEX. Single live pair (U1/WETH) with dynamic fees, LP locking, and a 5% protocol-fee stream to U1 lockers.

Mental model

  • Constant-product — classic x*y=k with a base fee + a volatility-driven dynamic adjustment.
  • LP is an ERC-20 on the pair contract — no NFT, no per-bin shares. Simpler than DLMM.
  • Lockable LP — the pair has a built-in lockLP/unlockLP surface; locked LP keeps earning fees.
  • Protocol fee = 5% of swap fees, denominated in WETH, claimable to the protocol fee wallet.

Stack

DAMMFactory (ERC-1967 proxy)  ──creates──▶  DAMMPair instances (BeaconProxies)
                                                └─ DAMMPairBeacon (48h-timelocked)
                                                    └─ DAMMPair impl
DAMMRouter (stateless ETH→LP helper)
DAMMPairPaused (emergency stub; beacon swaps to this for incident response)

Token X vs Y

Same convention as DLMM: tokenX = U1, tokenY = WETH on the live pair because U1's address sorts lower.

Trust signals

  • All 7 DAMM V4 contracts are source-verified on Basescan.
  • Pair upgrades require 48h beacon timelock + 48h governance timelock (dual gate).
  • LP locked through 2031-05-07 by the LP-treasury Safe.
Dev / DAMM / Pair

DAMM Pair (U1/WETH)

The trading + LP + lock surface. Single contract for swap, add/remove liquidity, claim fees, lock LP.

tokenX / tokenY
U1 / WETH
Base fee
100 bps
+ dynamic volatility adjustment
Protocol fee
5%
of swap fees, in WETH

Reads — state

getReserves()         view returns (uint112 reserveX, uint112 reserveY)
getAmountOut(uint256 amountIn, bool xToY)  view returns (uint256 amountOut)
currentFeeBps()       view returns (uint16)
tokenX() / tokenY() / feeToken()  view returns (address)
protocolFeesAccumulated() / lpFeesAccumulated()  view returns (uint256)
pendingLPFees(address holder)  view returns (uint256)

Reads — LP token (ERC-20) + lock

name() / symbol() / decimals() / totalSupply() / balanceOf(addr) / allowance(o, s)
lockedBalance(holder) view returns (uint256)
lockOf(address) view returns (uint256 amount, uint64 unlockTime)

Writes

swap(uint256 amountIn, bool xToY, uint256 amountOutMin, address to)
  returns (uint256 amountOut)

addLiquidity(uint256 amountX, uint256 amountY, address to, uint256 minLiquidity)
  returns (uint256 lpTokens)

removeLiquidity(uint256 lpAmount, uint256 minAmountX, uint256 minAmountY, address to)
  returns (uint256 amountX, uint256 amountY)

lockLP(uint256 additionalAmount, uint64 newUnlockTime)
unlockLP()
claimLPFees(address recipient) returns (uint256 amount)

Direction (xToY)

User picksxToY
Sell U1 → get WETHtrue
Buy U1 with WETHfalse
Buy U1 with native ETHfalse (wrap ETH→WETH first via weth.deposit{value: ...})
No deadline parameter on swap

The pair contract has no deadline on swap. Slippage protection is the only floor — amountOutMin must be set tight enough to reject a stale quote. Compute as quotedOut * (10000 - slippageBps) / 10000.

Events

Swap(address sender, address recipient, uint256 amountXIn, uint256 amountYIn,
     uint256 amountXOut, uint256 amountYOut, uint16 feeBps)
LiquidityAdded(address provider, uint256 amountX, uint256 amountY, uint256 lpTokensMinted)
LiquidityRemoved(address provider, uint256 amountX, uint256 amountY, uint256 lpTokensBurned)
LPFeesClaimed(address holder, address recipient, uint256 amount)
LPLocked(address holder, uint256 totalAmount, uint64 unlockTime)
LPUnlocked(address holder)
Sync(uint112 reserveX, uint112 reserveY)

Custom errors

InsufficientLiquidity()   InsufficientOutput()   InsufficientInput()
SlippageExceeded()        InvalidRecipient()
LockedTransfer()          LockNotExpired()       LockMustExtend()
Dev / DAMM / Router

DAMM Router

Stateless helper. The router exposes one convenience function: an ETH→LP one-shot for users adding liquidity from native ETH.

What it does

  • addLiquidityETH(...) — wraps the user's ETH and adds liquidity to the pair in a single tx.
  • No swapETH on the current router. For pure swaps from ETH: wrap (weth.deposit) → approve → swap.
  • The router has no state, no admin functions, and no upgrade path. Safe to bind without timelock checks.

For LP management (lock / unlock / claim fees), interact with the Pair directly — the router is intentionally minimal.

Dev / DAMM / Factory & beacon

DAMM Factory & beacon

Pair deployment + upgrade plumbing. ERC-1967 proxy on the factory; BeaconProxy on every pair, with the beacon's implementation pointer behind a 48h timelock.

Addresses

RoleAddress
DAMMFactory (proxy)0xD14322b4…A1aCD5c
DAMMFactory impl0x5491E26d…816630
DAMMPairBeacon0xB6363F60…8569
DAMMPair impl0x1d8752A8…3D0A
DAMMPairPaused (emergency stub)0xEcEc2DCB…76B22

Upgrade discipline

Pair upgrades require both: a 48h governance timelock to propose, then a separate 48h beacon delay to land. The beacon can also be flipped to the paused stub (0xEcEc…76B22) for incident response — that swap is itself behind the timelock.

Reads

cast call <FACTORY> 'owner()(address)'        --rpc-url $RPC  # → U1/DAMM Timelock
cast call <FACTORY> 'pendingOwner()(address)' --rpc-url $RPC  # → 0x0 (no pending)
cast call <FACTORY> 'beacon()(address)'       --rpc-url $RPC  # → 0xB636…8569
cast call <BEACON>  'implementation()(address)' --rpc-url $RPC  # → 0x1d87…3D0A
Dev / DAMM / Subgraph

DAMM Subgraph

The Graph subgraph for the DAMM V4 constant-product DEX. Indexes pair creation, swaps, liquidity events, LP-fee + protocol-fee claims, LP lockup state, and time-series rollups.

Schema (Uniswap V2-compatible shape)

Mirrors Uniswap V2 entity shape so aggregator adapters that already parse V2 read this cleanly.

  • Factory — protocol-wide totals.
  • Bundle — single ETH/USD reference price (id "1"). USD math reads here.
  • Token — one per ERC-20 observed in any pair.
  • Pair — one per DAMM pair contract. Live reserves, prices, volumes.
  • Swap — one per Swap event (feeBps, USD value, direction).
  • LiquidityEvent — one per Add / Remove (kind enum).
  • FeeClaim — one per LP / Protocol fee claim.
  • LPLock — current lock state per holder per pair.
  • PairHourData / PairDayData / TokenDayData — time-series rollups.

Notes

  • DAMM V4 LP tokens are 18-decimal. Hardcoded in the subgraph mapping when converting lpAmount.
  • Fee token is WETH on the U1/WETH pair (per feeToken).
  • Bundle.ethPriceUSD is intentionally not populated by the subgraph for v1 — the consuming app already has a WETH/USD feed at /api/base/weth-usd; USD math at the app layer multiplies the subgraph's amountWETH by the live rate. Add a stablecoin-paired data source to make the subgraph self-contained (deferred).
  • tokenXPrice reads as "WETH per U1" for the U1/WETH pair (uses reserveY / reserveX). Multiply by ethPriceUSD for USD display.

Endpoints

Production gateway URL is issued per consumer. Local dev: http://localhost:8000/subgraphs/name/umbrae/damm. See Subgraph index for the cross-pool view.

Dev / Custody keeper / Overview

Custody keeper overview

Auto-rebalance + TP/SL orders for DLMM positions. v8 keeper-only stack (deployed 2026-06-23). Eligibility-gated: a wallet must lock an Ignis Elite NFT to use it.

Two modes, never both on one position

The keeper does two different jobs. A single position NFT is in exactly one mode at a time. The UI must treat them as mutually exclusive.

ModeWho holds the NFTWhat it doesContract the user calls
A — Auto-rebalance (custody)The keeper holds itKeeps the position centered on the active price as it driftsCustodyRebalanceKeeper
B — Take-profit / Stop-loss (non-custody)The user keeps itCloses the position when a price trigger firesOrderManager

What v8 adds (over v7)

  • Per-pool fee tracking — poolAccounting(depositor, pair) → (feesX, feesY, rebalanceCount, retainedX, retainedY) + the FeesRealized event.
  • Retained reuse-buffer — withdrawRetained(pair) reclaims the depositor's own capital the keeper holds back across rebalances. See Withdraw retained.
  • Fee modes — setFeeMode(pair, separate) / separateFees(depositor, pair) (Compound default vs Separate). See Fee modes.
  • Cooldown off-switch — minRebalanceInterval == 0 disables the per-position cooldown.

Operational addresses (NOT called by integrators — surface for verification)

AddressRoleCalls
0x5986Bb…Ac387Keeper operatorCustodyRebalanceKeeper.rebalance(...), OrderExecutor.executeOrder(...)
0x3489BD…1f6e907Sync operatorEligibilityManager.setEligible(...), batchSetEligible(...)

Both are whitelisted on-chain (operators(addr) / syncOperators(addr)). They are rotatable via the timelock — prefer reading the mapping live over hardcoding when you need a trust check.

Dev / Custody keeper / Deposit

Deposit (DepositorVault model)

How a user's position lands inside the keeper. v9 adds a per-depositor vault model: every depositor's NFT and LP shares live in their own deterministic CREATE2 clone.

Three deposit paths

First, one approval: positionManager.approve(<keeper>, tokenId) — or setApprovalForAll for repeat users.

The pair the position lives in must be governance-approved on the keeper (all 15 current DLMM pairs are approved at launch). If the pair isn't approved, deposit reverts — surface as "this pair isn't supported by the keeper yet."

(a) Preset config — easiest

keeper.depositWithPreset(uint256 tokenId, uint8 presetId)  // presetId 0..2

(b) Custom config — full control

keeper.depositWithCustom(
  uint256 tokenId,
  Strategy strategy,        // 0=Spot, 1=Curve, 2=BidAsk
  uint24  binRange,
  uint8   bidAskRatio,
  uint24  triggerThreshold,
  uint24  idSlippage,
  bool    skipActiveBin,
  uint8   balanceCapPct,
  bool    triggerFromEdge,
  uint32  minRebalanceInterval
)

(c) One-step safeTransferFrom — deposit + config in one tx, no approve

// preset:  data = abi.encode(uint8 presetId)             // presetId 0..2
// custom:  data = abi.encode(uint8(255), PositionConfig) // 255 = "custom follows"
positionManager.safeTransferFrom(user, <keeper>, tokenId, data)

DepositorVault (v9 model)

Every depositor's NFT and LP shares live in their own deterministic DepositorVault clone (EIP-1167 minimal proxy + CREATE2). Same depositor always maps to the same vault address — even before the vault is deployed. The vault is created lazily on the first deposit.

// Predict a depositor's vault address (always works, deploy or no deploy):
// salt   = bytes32(uint160(depositor))
// impl   = DepositorVault implementation
// deployer = keeper address
// returns the CREATE2-derived address

// Read whether the vault exists yet:
isVaultDeployed(address depositor) view returns (bool)
Reading LP shares

For per-bin LP, read from the depositor's vault address, not the keeper: pair.balanceOf(vault, binId). In v9 the keeper holds nothing — the vault is the holder.

Change config later

updateConfigWithPreset(uint256 tokenId, uint8 presetId)
updateConfigWithCustom(uint256 tokenId, /* same 9 fields as depositWithCustom */ ...)
Dev / Custody keeper / Status reads

Status & accounting reads

Everything an integrator needs to display a depositor's current state without a server-side index.

Per-depositor

getUserTokenIds(address user) view returns (uint256[])      // all currently-held positions
getUserPositionCount(address user) view returns (uint256)
getDepositor(uint256 tokenId) view returns (address)        // 0 = not in keeper
isEnabled(uint256 tokenId) view returns (bool)              // auto-rebalance on/off
getPositionConfig(uint256 tokenId) view returns (PositionConfig)

Per (depositor, pair) — v8 fee accounting

poolAccounting(address depositor, address pair) view returns (
  uint256 feesX,
  uint256 feesY,
  uint256 rebalanceCount,
  uint256 retainedX,
  uint256 retainedY
)
separateFees(address depositor, address pair) view returns (bool)  // fee mode

For per-chain (open chains only) attribution, scan the Rebalanced + FeesRealized + RetainedWithdrawn events from block 47679950; poolAccounting is the lifetime per-(depositor, pair) accumulator and inherits the running total across closed chains.

Events

Rebalanced(uint256 indexed oldTokenId, uint256 indexed newTokenId, address indexed depositor,
           address tokenX, address tokenY, uint256 amountX, uint256 amountY,
           uint256 deployX, uint256 deployY)

FeesRealized(uint256 indexed tokenId, address indexed depositor, address indexed pair,
             uint256 feeX, uint256 feeY)

RetainedWithdrawn(address indexed depositor, address indexed pair,
                  uint256 amountX, uint256 amountY)

FeeModeSet(address indexed depositor, address indexed pair, bool separate)

Presets

getPreset(uint8 presetId) view returns (Preset)  // 0..2; MAX_PRESET_ID = 2

PositionConfig fields: enabled, strategy, binRange, bidAskRatio, triggerThreshold, idSlippage, skipActiveBin, balanceCapPct, triggerFromEdge, minRebalanceInterval.

Dev / Custody keeper / Gas prepaid model

Gas prepaid model

The keeper bot pays gas to rebalance and is reimbursed from a per-user balance you keep on the keeper. Closing a position does not refund it — use withdrawGas.

The flow

keeper.depositGas{value: ...}()           // top up (payable)
keeper.withdrawGas(uint256 amount)        // pull unused gas back
keeper.gasBalances(address owner) view returns (uint256)  // wei

Surface in the UI

  • Always show the user's current gas balance.
  • Warn when low — a depleted balance means rebalances stop.
  • On an operator-funded revert, the operator pulls accumulated reimbursement via claimOperatorPayment(to); integrators do not call this.

Orders (TP/SL) have their own gas balance

Mode B (orders) uses a separate gas surface on OrderExecutor:

orderExecutor.depositGas{value: ...}()
orderExecutor.withdrawGas(uint256 amount)
orderExecutor.gasBalances(address owner) view returns (uint256)

An empty balance on a stop-loss reverts InsufficientGasBalance at the operator's pre-check; the order stays armed but will not fire. Prompt the user to fund gas in the order-setup flow.

Recovering gas from a retired keeper

Prepaid gas is per-contract, keyed by depositor. It is not migrated on an upgrade. If you funded a retired keeper (pre-v8), recover with the same selectors:

cast call <OLD_KEEPER> "gasBalances(address)(uint256)" <YOUR_WALLET> --rpc-url https://mainnet.base.org
cast send <OLD_KEEPER> "withdrawGas(uint256)" <WEI>   --rpc-url https://mainnet.base.org --account <wallet>

The most-funded retired keeper as of 2026-06-24 is 0xe259EA63…d2dB54e6 (~0.227 ETH stranded), Basescan-verified so the Read/Write tabs work without tooling.

Dev / Custody keeper / Fee modes

Fee modes — Compound vs Separate

v8 lets a depositor choose what happens to realized fees on rebalance: redeploy into the new position (Compound, default) or route to the wallet on each rebalance (Separate).

Set the mode

setFeeMode(address pair, bool separate)
// separate = false → Compound (default)
// separate = true  → Separate (fees pushed to wallet each rebalance)

Read the mode

separateFees(address depositor, address pair) view returns (bool)

Default false when never set. Switching modes mid-chain is supported — subsequent rebalances follow the new mode; previously-realized fees stay where they were (compounded or sent).

P&L implications for indexers

  • Compound mode: realized fees ride inside current value. Do NOT add them again to NET P&L — they're already in the position's reserves.
  • Separate mode: realized fees were pushed to the wallet. ADD them to NET P&L as "realized income."
  • The FeesRealized event fires in both modes — distinguish by reading the live separateFees flag at the event's block (or by tracking FeeModeSet events).

Event

FeeModeSet(address indexed depositor, address indexed pair, bool separate)
Dev / Custody keeper / Withdraw retained

Withdraw retained

v8 introduces a retained reuse-buffer: each rebalance, the keeper holds back some of the depositor's own capital instead of redeploying it. withdrawRetained reclaims it.

The model

On every rebalance, the keeper computes the new position's deploy amounts and the remainder. The remainder is the depositor's own capital, retained in the keeper for reuse on the next rebalance. It is always reclaimable on demand — nothing in the system can lock it away.

Read

// per-(depositor, pair) lifetime retained (uint256, base units)
poolAccounting(depositor, pair) view returns (..., uint256 retainedX, uint256 retainedY)

Withdraw

withdrawRetained(address pair) external
// Fully drains retainedX/retainedY for (msg.sender, pair) in one tx.
// Pair-wide drain: NOT per-position. Emits RetainedWithdrawn.
// No partial withdrawal API.

Event

RetainedWithdrawn(address indexed depositor, address indexed pair,
                  uint256 amountX, uint256 amountY)
Per-chain attribution requires a scan

poolAccounting.retainedX/Y is the per-(depositor, pair) lifetime accumulator. A brand-new chain (fresh deposit in a pair where the depositor had prior keeper history) inherits the running total. For per-open-chain attribution: scan Rebalanced events (retained delta = amountX - deployX, amountY - deployY per event) plus RetainedWithdrawn as a pair-wide barrier, attributing each delta to the chain its newTokenId belongs to (root via the Rebalanced lineage).

Dev / Custody keeper / Exit

Exit (claimWithdrawal flow)

Withdraw a custody position out of the keeper. Always available, even while the keeper is paused.

Withdraw the position

keeper.withdrawPosition(uint256 tokenId)
// Returns the NFT to the original depositor.
// Reverts if msg.sender is not the depositor.

Escrow fallback

If the depositor's address cannot receive a payout token (e.g. USDC/USDT blocklist, a contract that rejects the token), the payout is escrowed on the keeper instead of reverting the withdraw. Pull it from a fresh address:

keeper.claimWithdrawal(address token, address to)
// Call from the depositor's address; routes the escrowed token to `to`.

Auto-drain retained on last close

Closing the depositor's last custody position in a pair automatically drains retained for that pair — no separate withdrawRetained call needed. If the user has other custody positions in the same pair, retained stays in place.

Dev / Custody keeper / Orders

Orders — TP / SL

Mode B: the user keeps their NFT and authorizes the OrderExecutor to close it when a price trigger fires. Bin-based stop-loss and take-profit only — fee-target orders are unavailable on the current PM.

Two approvals (both required)

A missing second approval makes the SL silently not fire

setOrder fails closed unless both are in place. Prompt for both in the order-setup flow. Read pairAddress from the position via the Position NFT Manager.

// 1. Let the executor close (transfer/burn) the position NFT:
positionManager.setApprovalForAll(<orderExecutor>, true)
// or: positionManager.approve(<orderExecutor>, tokenId)

// 2. Let the position manager burn the pair's LP shares on close (ERC-1155):
IUmbraeLBToken(pairAddress).setApprovalForAll(<positionManager>, true)

If #1 is missing, setOrder reverts ExecutorNotApproved. If #2 is missing, it reverts PairNotApproved(pair).

Set the order

struct SetOrderParams {
  uint256 tokenId;
  uint256 feeTargetX;       // 0 = disabled
  uint256 feeTargetY;       // 0 = disabled
  uint24  slBinId;          // 0 = disabled
  uint24  tpBinId;          // 0 = disabled
  uint128 minAmountX;       // min output on close (0 = none)
  uint128 minAmountY;       // 0 = none
  uint256 minValueFloor;    // combined-value floor on close (0 = disabled)
  uint8   valueDenomToken;  // 0 = value in tokenX, 1 = tokenY
}
orderManager.setOrder(SetOrderParams calldata params)

Use minValueFloor as the crash-resistant floor. In a sharp drop a per-token minAmount on the wrong side becomes unsatisfiable exactly when the stop-loss needs to fire; the value floor avoids that.

Status & cancel

orderExecutor.checkOrder(uint256 tokenId) view returns (
  bool triggered, TriggerType triggerType, uint256 feeX, uint256 feeY
)
// TriggerType: 0 = FeeTarget, 1 = BinStopLoss, 2 = BinTakeProfit

orderManager.cancelOrder(uint256 tokenId)              // owner-only
orderManager.getOrder(uint256 tokenId) view returns (Order)
orderManager.isActive(uint256 tokenId) view returns (bool)

Width limit (OE-01)

A position spanning more than 41 price bins cannot be order-closed (reverts TooManyBins). Surface "split this position" if a user tries to set an order on a wider one.

Gas (owner-funded)

Orders are owner-funded for gas (KEEPER-054). Prompt the user to call orderExecutor.depositGas{value: ...}() in the setup flow. See Gas prepaid model.

Dev / Access control / NFT Lock Vault

NFT Lock Vault (Ethereum)

Locks an Ignis Elite NFT on Ethereum for a chosen window (7 / 14 / 30 days). While the lock is active and not expired, the user's address is "eligible" for keeper service + platform rewards on Base.

Chain
Ethereum mainnet
Lock windows
7 / 14 / 30 days

What eligibility unlocks (on Base)

  • Keeper auto-rebalance — gated on EligibilityManager.isEligible(addr) on Base.
  • Platform-reward eligibility — the 35% Elite-holder share of platform fees and the 35% Elite share of arb-bot profits.

Approved NFTs (constructor-set)

PhaseContractToken ID
Phase 10x42347DB4…6885b1
Phase 20x17A2b200…1712Ce0
Phase 30xab505a66…FeD1A0

The owner can add or remove approved NFTs. Removing a contract blocks new locks against it; existing locks remain valid.

Surface

lock(address nftContract, uint256 tokenId, uint8 durationDays)  // 7 / 14 / 30
extendLock(uint8 newDurationDays)                                // monotonic
unlock()                                                          // always works, even when paused
isApproved(address nftContract, uint256 tokenId) view returns (bool)
lockOf(address holder) view returns (uint256 tokenId, uint8 duration, uint64 unlockTime)

Sync to Base

A backend sync service watches the vault on Ethereum and mirrors eligibility onto Base's EligibilityManager. Expect ~5 min from lock → eligibility on Base.

Dev / Access control / Eligibility Manager

Eligibility Manager

Gates keeper service — not assets. An ineligible wallet can still deposit and can always withdraw; it just won't get rebalanced or have orders fire.

Reads

isEligible(address user) view returns (bool)
graceUntil(address user) view returns (uint64)   // 0 = no grace; nonzero = grace timestamp
syncOperators(address writer) view returns (bool)

Writes (depositor-facing)

renounceEligibility()  // self opt-out; clears grace on re-grant

Writes (operator-only)

setEligible(address user, bool eligible)         // sync operator
batchSetEligible(address[] users, bool[] flags)  // sync operator

UI rules

  • Gate "enable" actions on isEligible — the call-to-action that turns on auto-rebalance or sets an order.
  • Never gate withdraws on eligibility.
  • Explain the sync delay — "eligibility updates within a few minutes of locking your NFT on Ethereum."

Events

EligibilityUpdated(address indexed user, bool eligible, uint64 graceUntil)
SyncOperatorUpdated(address indexed writer, bool approved)
Dev / Off-chain / Subgraph index

Subgraph index

Umbrae publishes two subgraphs — one per trading stack — on The Graph hosted gateway. They're the primary data source for pair listings, position history, and chart rollups.

SubgraphWhat it indexesPage
umbrae-dlmm-baseDLMM Factory / Pair / PositionNFTManager / LBToken events — pairs, positions, swaps, hourly/daily snapshotsDLMM Subgraph
umbrae-damm-baseDAMM Factory / Pair events — Uniswap V2-compatible schema for aggregator parityDAMM Subgraph

When to use the subgraph vs RPC

Use caseRecommended source
Live state (active bin, reserves right now)RPC + pair viewer
Historical swaps / volume / feesSubgraph
User position listSubgraph (or PositionNFTManager + RPC)
Charts (hourly / daily rollups)Subgraph
USD-denominated TVL / volumeSubgraph token amounts × your USD price feed

Endpoint policy

Each consumer uses their own gateway API key. Do not share a URL with the Umbrae key in the path — that puts query load on our quota. Reach out for issuance.

Dev / Off-chain / Listings & data partners

Listings & data partners

External aggregators that index Umbrae pools.

Indexing status

ServiceStatusNotes
GeckoTerminalPending submissionSubmission is manual; auto-discovery does not index our custom factory + Swap event signature.
DEX ScreenerPending submissionSame auto-discovery limitation.
DEXToolsPending submissionSame.

What we provide to listings

  • Subgraph endpoint (Uniswap V2-compatible shape for DAMM, custom for DLMM).
  • Verified source on Basescan for every contract.
  • Token metadata (logos, descriptions, supply audit links).

Listing-data conventions

  • U1 token contract — 0x14a4e80d633aF55Ace1160c320f5a36D41CCEd3E.
  • Primary pair — DAMM V4 U1/WETH at 0x296964C34a571fCf85d3F74FB815ee871F5A08d4.
  • Secondary pair — DLMM U1/WETH at 0xd288889ef25ca7483277f39c262098f6ba615ff2 (100 bps, V2 stack).
  • USD price source for U1 = (WETH per U1 from the pool) × (WETH/USD). Use /api/base/weth-usd or your own oracle for WETH/USD.
Dev / Off-chain / Aggregator adapter pattern

Aggregator adapter pattern

How to surface Umbrae's pools through 1inch / OpenOcean / Matcha / Paraswap / KyberSwap. The pattern below is verified against the KyberSwap dex-lib adapter Umbrae built for upstream submission.

Spec at a glance

SurfaceUse
SubgraphPool discovery + reserves snapshot.
Pair Viewer (DLMM)Per-bin reads for accurate quotes on partially-filled swaps.
DAMM Pair directgetAmountOut + getReserves for one-shot quotes.
Router (DLMM / DAMM)Submit the swap with amountOutMin at the aggregator's tolerance.

Adapter responsibilities

  1. Discover pools — query the subgraph for active pairs on each stack. Skip pairs with no liquidity.
  2. Reserve / bin state — for DAMM, single getReserves call per pair. For DLMM, multicall getBin(uint24) across the active bin and its 2–3 neighbors (the bins a small swap would touch).
  3. Quote — DAMM: getAmountOut(amountIn, xToY). DLMM: run the local bin-by-bin quote (fee + impact-aware) using the bin reserves and the pair's static + variable fee parameters.
  4. Encode the swap — emit the call the aggregator's swap executor will perform: pair.swap(...) for DAMM, router.swapExactTokensForTokens(...) for DLMM multi-hop.
  5. Verify on PROD before announcing — integrate against the live mainnet pair. Test pool listings exist before publishing the integration.

Test fixtures

Use the production mainnet pool for adapter validation — 0xd288889ef25ca7483277f39c262098f6ba615ff2 (DLMM U1/WETH, 100 bps, V2 stack) and 0x296964C34a571fCf85d3F74FB815ee871F5A08d4 (DAMM U1/WETH). Quote a small amount (~0.001 WETH) and compare your adapter's quote to pair.getAmountOut — they should match to the unit.

Dev / Resources / Source repos

Source repos

Where the code lives. All public-facing contracts are source-verified on Basescan; the repositories below host the canonical Solidity, scripts, and ABIs.

RepoWhat
github.com/Ignis-AI-LabsThe Ignis AI Labs organization — entry point.
Ignis-AI-Labs/umbraeTrading platform frontend + indexer + bots. Hosts these dev docs.
Ignis-AI-Labs/umbrae-sitePublic umbrae.io site. FAQ + landing.
Ignis-AI-Labs/evm-contractsAll deployed Solidity: DLMM, DAMM V4, U1 + lock vault, custody keeper stack, NFT lock vault.

Per-subproject highlights (inside evm-contracts)

  • base/dlmm — DLMM V2 pair, factory, router, position NFT manager, beacon, libraries.
  • base/dlmm/src/keepers — CustodyRebalanceKeeper, DepositorVault, OrderManager, OrderExecutor, EligibilityManager, KeeperMath.
  • base/damm — DAMM V4 pair, factory, beacon, router.
  • base/u1 — U1 token, migration, reserve LP, lock vault.
  • base/dlmm/subgraph, base/damm/subgraph — subgraph mappings.
  • ethereum/nft-lock-vault — the eligibility-gating vault on Ethereum mainnet.

Licensing

  • Custody keeper stack (v5+) — BUSL-1.1. Pre-v5 was MIT.
  • DLMM core — MIT.
  • DAMM V4 — see contract source for the canonical license header.
Dev / Resources / Audits

Audits

Every Umbrae contract on Base is source-verified on Basescan and has been audited or reviewed before deployment. Public audit reports are published when redaction policy permits.

Audit history (summary)

DateScopeOutcome
2026-05-12U1TokenLockVaultSigned off — 39/39 tests, no Critical/High.
2026-05-16Comprehensive (BlackBox)Multi-contract audit. Findings folded into the deploy pipeline; CSP report-only telemetry shipped 2026-05-18.
2026-06-09Custody keeper adversarial reviewFour findings (grace-timestamp opt-out bypass, executor PM-interface check, escrow fallback, interface-gated withdrawPosition) addressed in v5 (BUSL-1.1 cutover).
2026-06-16v6 + v7 keeper redeployFull KA3 swap-safety + KEEPER-054 owner-funded order gas + OE-01/OE-02 + KEEPER-055 (KeeperMath extraction).
2026-06-23v8 keeper redeployPer-pool fee accounting + retained reuse-buffer + Compound/Separate fee mode + cooldown off-switch.

How to verify on-chain

Every active contract address in the Address Book links to its Basescan #code page — verified Solidity source, optimizer settings, license header. Source verification is the strongest public artifact for any contract on Base.

Reach the team

For audit report PDFs and detailed findings, contact the team through the FAQ contact channels. Public reports will be linked from this page as they're published.

Dev / Resources / Changelog

Changelog

Cutover dates, deprecations, and version bumps. Use this to align integrations with the active stack.

Custody keeper

VersionDateChange
v8 (active)2026-06-23Keeper + KeeperMath redeploy. Per-pool fee accounting (poolAccounting + FeesRealized), withdrawRetained, Compound/Separate fee mode (setFeeMode/separateFees), cooldown off-switch (minRebalanceInterval == 0). Peripherals (OrderManager / OrderExecutor / EligibilityManager / UmbraeSwapRouter / PositionNFTManager) reused from v7 — eligibility and existing TP/SL orders preserved.
v72026-06-16Raised max position width to ±50 bins (101 total). MAX_BIN_RANGE 20→50, gas estimates 10M→30M. Replaced by v8.
v62026-06-16Full KA3 swap-safety + KEEPER-054 owner-funded order gas + OE-01/OE-02 + KEEPER-055. Same-day replaced by v7.
v52026-06-10First BUSL-1.1 keeper. KEEPER-052 stack + 2026-06-09 audit fixes (grace-timestamp, executor PM-interface, escrow fallback, withdrawPosition).
v4 and earlier2026-06-01 to 2026-06-08Pre-BUSL deploys. Retired. See Gas prepaid model § Recovering gas for funds recovery on retired keepers.

DAMM

VersionDateChange
V4 (active)2026-05-07Carries V-15 / V-16 / V-18 / V-19 audit fixes in deployed bytecode. BeaconProxy pattern.
V32026-05-07Retired same day. Audit re-review surfaced findings folded into V4.
V2 / V12026-05-03Retired. Pre-upgradeable era.

DLMM

DateChange
2026-05-15U1/WETH pair added (25 bps) — verified via factory.allPairs(13).

U1 token

DateChange
≈ June 2026 (unix 1780388389)Migration window closes for new entitlements. Entitled-but-unclaimed entries stay claimable forever — the close affects entitlement creation only.

Developer docs

DateChange
2026-06-29Phase 1 ships: Quickstart, Network, Address Book, ABIs, Deploy blocks at /dev/.
2026-06-29Phase 2–5 ships: every contract page, off-chain integration, resources.