Skip to content
Back to Projects
Dynexus logo

Dynexus

DeFi protocol on Aptos: token factory, constant-product AMM and ve-style gauge governance in Move, a Rust indexer, and a Next.js app. Live on testnet.

MoveAptosDeFiAMMGovernanceSmart ContractsRustIndexerPostgreSQLDieselNext.jsTypeScript

Problem

Build a complete DeFi protocol on Aptos rather than a single contract: launch a token, trade it, and let holders decide where liquidity incentives go, with an indexer and a frontend that make the on-chain state usable.

My role: Personal project. Designed and built all five repos: three Move packages, the Rust indexer and the Next.js app.

Constraints

  • Three Move packages depend on each other (Swap on Forge, Core on both) and are published from one account, so module boundaries and the minting permission have to be explicit.
  • Emissions, gauge rewards and bribes must stay exact over many weekly epochs without trusting a keeper.
  • History (swaps, volume, votes) only exists in events, and one view call per pool per page load does not scale.

Architecture

  1. Forge (token_factory)

    Creates fungible assets for a flat DYNEXUS fee; mints DYNEXUS only for allowlisted contracts

  2. Swap (dex)

    x*y=k pools, LP tokens, routes of up to 3 hops, swap fee for LPs plus a platform fee

  3. Core (voting_escrow, voter, gauge, minter, bribe)

    veDYNA locks, weekly gauge votes, decaying emissions, LP staking, per-epoch bribes

  4. Indexer (Rust)

    Aptos Transaction Stream, filtered to Dynexus events, into PostgreSQL; axum read API

  5. Web (Next.js)

    Swap, pools, tokens, create-token and governance pages; Aptos wallet adapter

What I Built

  • Forge: permissionless fungible-asset factory with a paginated token registry, a DYNEXUS faucet and a minter allowlist (add_dynexus_minter) so Core can mint emissions without holding the admin key.
  • Swap: constant-product AMM with 1,000 LP tokens locked on pool creation, a 10% cap on single-swap size, deadlines, slippage-aware quotes and swap_exact_in_route for multi-hop trades. Swap and platform fees are set in basis points and each capped at 1%.
  • Core voting_escrow: lock DYNEXUS for 1 week to 4 years; voting power is amount * remaining / 4 years, decaying to zero at unlock, with unlock times rounded to whole weeks.
  • Core voter and minter: weekly epochs, votes split across up to 10 gauges, and a permissionless distribute that mints each epoch's emission (decaying by a set percentage down to a tail floor) and sends it to gauges pro-rata to votes.
  • Core gauge and bribe: Synthetix-style staking that streams each gauge's emission over one week; anyone can bribe a pool's voters with any fungible asset, claimable pro-rata after the epoch.
  • Move unit tests across all three packages.
  • Indexer: Rust workspace (dynexus-db, dynexus-processor, dynexus-api) on the Aptos indexer SDK, Diesel and axum, serving tokens, pools, swaps, OHLC candles, gauges, epochs and per-user activity.
  • Web: Next.js App Router with the Aptos TS SDK, wallet adapter and TanStack Query, a strict CSP and security headers; falls back to on-chain view calls when no indexer URL is configured.

Technical Challenges

Minting without sharing the admin key
Emissions are minted by Core, but DYNEXUS belongs to Forge. Forge keeps an allowlist of minter addresses and exposes mint_dynexus only to them; Core's minter object is added once after deploy.
Keeper-free epochs
Anyone can call voter::distribute after an epoch ends. It catches up on missed epochs, at most 52 per call to bound gas, so emissions never depend on one operator.
Exactly-once indexing
Each batch is written in one database transaction. Every event is inserted into dynexus_events keyed by transaction version and event index with ON CONFLICT DO NOTHING, and typed tables update only when that insert succeeds, so replaying a batch after a crash is a no-op and running totals stay exact.
Less RPC load
The Transaction Stream filters on the server to transactions that emit Dynexus events, token metadata is read from the same transaction's write set instead of a follow-up call, and the web app fetches every pool in one GET /pools instead of a view call per pool.

Trade-offs & Limitations

  • Testnet only, with no external audit.
  • Constant-product curve only: simple and predictable, but less capital-efficient than concentrated liquidity.
  • One account publishes all three packages, which keeps addresses simple but makes that key the upgrade authority for everything.
  • The indexer is not hosted yet, so the live app reads chain state through view calls and history views are off.
  • On-chain integers are stored as NUMERIC and served as decimal strings, so clients must parse them rather than use JS numbers.