StocksCLIDocs

@beegreat/sugar

This is an independent project and is not affiliated with, endorsed by, sponsored by, or maintained by Aerodrome Finance, Velodrome Finance, Dromos Labs, or Mellow Protocol. References to their names and protocols describe compatibility or source attribution only. All trademarks belong to their respective owners. Third-party code remains subject to its applicable licenses.

⚠️ Vibecoded & early beta — use at your own risk. This SDK and the aero CLI were vibecoded with AI agents and are in early beta. Expect rough edges and breaking changes. Always review unsigned plans (--dry-run) before signing, start with small amounts, and never risk funds you cannot afford to lose.

Native TypeScript port of Velodrome/Aerodrome's Sugar SDK. It uses Viem for RPC reads and ABI encoding and never signs or broadcasts transactions.

Standalone repository

This package is developed in the BeeGreat monorepo (packages/sugar) and mirrored to OxFrancesco/aerodrome-sdk-ts with an MIT license for original contributions. Third-party material retains its applicable terms; see the licensing review below before redistribution. The monorepo is the source of truth; mirror after landing changes with bun run sugar:mirror from the monorepo root. Standalone usage:

bun add github:OxFrancesco/aerodrome-sdk-ts   # as a dependency
# or work in a clone:
bun install && bun test && bun run typecheck
bun run cli -- pools --chain=8453 --pool-type=cl --limit=5

Licensing review 2026-09-06

This is a technical license review, not a legal opinion or a finding of non-infringement. Do not treat the MIT file or the non-affiliation notice as clearance for upstream material. The following issues remain open before further redistribution or production use of potentially restricted derivatives.

SourceVerified termsConsequence for this package
Python Sugar SDKApache-2.0. Metadata identifies copyright 2025 Velodrome Finance.Commercial use and ports are permitted subject to the license. Section 4 requires a license copy, notices on modified files, retained applicable attribution, and relevant NOTICE content if supplied. The package now includes LICENSE.Apache-2.0, NOTICE, and change notices on the identified ported TypeScript modules. Fifteen ABI files match the inspected Python reference byte-for-byte. Remaining ABI provenance and historical attribution checks are listed in NOTICE.
Official TypeScript SDKUNLICENSED in package and root metadata. No LICENSE or NOTICE file found in the main tree at review time.No general reuse grant was found. Do not copy or translate its implementation without permission. This package does not list it as a dependency, but references to its behavior are not proof of independent authorship. Review source history and ABI provenance.
Aerodrome contracts license and NOTICEMixed BSL-1.1, GPL-3.0, and MIT. The local Velodrome contracts reference has the same BSL parameters.Check each copied file, not just the repository license. Calling a deployed contract through RPC is distinct from distributing or deploying its implementation. Generated ABI artifacts still need provenance review; no blanket exemption is assumed.
Mellow PulseStrategyModuleBUSL-1.1; license parameters name February 15, 2028 as the change date. The inspected production-use grant list contains only a newline.src/alm/strategy.ts says it replicates this module. LICENSE.Mellow-BUSL-1.1 and NOTICE preserve the upstream terms without claiming production permission. ALM execution remains unchanged at the maintainer's request. Obtain permission or resolve derivative-work status before production use; dry-run mode is not legal clearance.

The contracts' BSL parameters name Perpetual Cyclist Services LLC and refer to v2-license-grants.velodrome.eth and v2-license-date.velodrome.eth. The license converts each version to GPL-2.0-or-later on its change date or the fourth anniversary of that version's first public BSL distribution, whichever is earlier. This review did not resolve those ENS records or establish version-specific first publication dates. Do not assume the whole repository has already converted. GPL-covered implementation redistribution has its own source and notice duties.

Apache section 6 and the contracts' BSL text do not grant general trademark or logo rights. New CLI WalletConnect pairings now use our own repository URL, an unofficial description, and no upstream logo. Existing sessions may cache old metadata until disconnected and paired again. Repository names, other UI assets, API terms, patents, and jurisdiction-specific trademark questions are not cleared by this review.

Release follow-up:

Client API

import { SugarClient, parseTokenUnits } from '@beegreat/sugar'

const sugar = new SugarClient(8453, {
  account: '0xYourPublicWalletAddress',
})

const eth = await sugar.getToken('ETH')
const usdc = await sugar.getToken('USDC')
if (!eth || !usdc) throw new Error('token unavailable')

const quote = await sugar.getQuote(eth, usdc, parseTokenUnits(eth, '0.01'))
const unsignedTransactions = quote
  ? await sugar.swapFromQuote(quote)
  : []

SugarClient ports the Python chain surface:

Every transaction result is { from, to, data, value }. value and other on-chain integers are bigint in the SDK and decimal strings after JSON serialization.

veNFTs and voting rewards

Native veNFT management is available on the two governance-root deployments: Optimism (Velodrome) and Base (Aerodrome). Superchain leaf deployments support pool gauges and voting incentives, but do not expose a local VotingEscrow; veNFT methods reject locally on those chains instead of returning unusable calldata.

const sugar = new SugarClient(8453, { account: wallet })

const locks = await sugar.getVeNfts()
const pools = await sugar.getPools()
if (!locks[0] || !pools[0]) throw new Error('veNFT or pool unavailable')
const create = await sugar.createVeNft(amount, 4 * 365 * 24 * 60 * 60)
const vote = await sugar.voteVeNft(locks[0].id, [
  { pool: pools[0].lp, weight: 1n },
])
const rewards = await sugar.getVeNftRewards(locks[0].id)
const claims = await sugar.claimVeNftRewards(locks[0].id)
const rebase = await sugar.claimVeNftRebase(locks[0].id)

The lifecycle surface also includes getVeNft, increaseVeNftAmount, extendVeNftLock, withdrawVeNft, mergeVeNfts, splitVeNft, setVeNftPermanent, delegateVeNft, resetVeNftVotes, pokeVeNftVotes, depositVeNftIntoManaged, withdrawVeNftFromManaged, and batched rebase claims.

Pool incentive funding is chain-agnostic. incentivizePool(pool, token, amount) resolves the correct BribeVotingReward/IncentiveVotingReward contract for the chain, adds an ERC-20 approval only when required, and returns the unsigned notifyRewardAmount transaction. Existing stake, unstake, claimEmissions, and claimFees methods continue to manage LP rewards.

CLI-compatible action seam

The Web3 power-up uses the same twelve action names as the former Python CLI:

import { executeSugarActionJson } from '@beegreat/sugar'

const output = await executeSugarActionJson('pools', {
  chain: 8453,
  pool_type: 'cl',
  limit: 10,
})

Supported actions are deposit, positions, pools, epochs_latest, epochs, withdraw, stake, unstake, claim_emissions, claim_fees, quote, and swap.

The same actions are available from the Bun CLI:

bun run --cwd packages/sugar cli -- pools --chain 1135 --limit 1
bun run --cwd packages/sugar cli -- quote --chain 1135 \
  --from-token ETH --to-token USDT --amount 0.001 --use-decimals

The sugar-ts package bin exposes that entrypoint to workspace consumers (aero is an alias). The SDK layer only returns JSON reads and unsigned transactions; signing lives exclusively in the CLI wallet flow below.

Analytics TUI (aero tui)

aero tuiAnalytics is a Dune Analytics dashboard for Aerodrome ve(3,3) mechanics. Charts mix two terminal renderers: dither-kit ordered dither (Bayer 8×8 fills: gradient, hatched, dotted, solid) and braille sub-pixel line charts — each cell packs a 2×4 dot grid, giving Dune-smooth multi-series curves. Also in the chart kit: donuts for composition, calendar heatmaps, waterfalls for epoch flow, and scatter quadrant maps.

TabWhat it shows
HealthKPI strip, braille volume + CL-vs-legacy fee lines, TVL-mix donut, 16-week activity heatmap
FlywheelRPV per 10k ve, bribe ROI, epoch waterfall (fees + bribes − emissions), hold vs LP vs lock+vote
TradeRanked pools with lens sorting, liquidity map (turnover × TVL scatter), weekly volume
TokenLocked-vs-liquid supply donut, real yield, P/S and P/F
ArenaSide-by-side vs Uniswap / Pancake on the same chain

Browse screens carry the same visual language: pools list TVL bars inline, and the epochs screen opens with a stacked vote-share banner. Analytics reports are cached session-wide (60s SWR) so tab flips and back-navigation replay instantly; ctrl+r forces a cold reload.

Pools, positions, epochs, the swap token catalog, and analytics persist to disk snapshots (~/.cache/aero/snapshots, override with AERO_CACHE_DIR), so a relaunch renders the last dataset instantly with a ◌ data from Xm ago — refreshing… badge while the live scan replaces it in the background. Snapshots feed browse/analytics screens only — quotes and transaction building always read live chain state. Loading spinners show live scan progress (N rpc reads). Pinning your own endpoint (SUGAR_RPC_URI_<chainId>) additionally raises the scan concurrency from 5 to 16 and parallelizes startup warming, since a dedicated RPC tolerates the fan-out that public endpoints rate-limit.

On-chain reads use the same Sugar client as the rest of the TUI. Each number is tagged with its source:

Set DUNE_API_KEY (or SUGAR_DUNE_API_KEY) from dune.com/settings/api. Without a key the screen still shows the live on-chain snapshot. aero guide analytics is the walkthrough.

Wallet-connected CLI (aero)

The interactive CLI is built on effect/unstable/cli: every action is a typed subcommand with described flags (aero <command> --help), any command can be filled in interactively with --wizard, shell completions are generated with aero --completions zsh|bash|fish, and aero guide <topic> prints in-terminal walkthroughs (getting-started, wallet, swap, liquidity, staking, rewards, venft, alm, chains, completions).

The CLI can connect a wallet and broadcast the plans it builds:

aero wallet connect      # WalletConnect: QR pairing with an extension/mobile wallet
aero wallet create       # new local wallet; mnemonic sealed with scrypt + AES-256-GCM
aero wallet restore      # import an existing mnemonic into the encrypted store
aero wallet status       # active wallet and source
aero wallet disconnect   # drop the WalletConnect session
aero wallet remove       # delete the local encrypted wallet (confirmed)

aero swap --from-token ETH --to-token USDC --amount 0.1 --use-decimals

Every command defaults --chain to Base (8453, Aerodrome). Transaction actions (swap, deposit, withdraw, stake, unstake, claim-emissions, claim-fees, create-venft) fill --wallet from the active wallet, print a human summary, and ask for confirmation before broadcasting each step (approvals first, receipts awaited). --yes skips the prompt; --dry-run always prints the unsigned plan. Without a wallet the CLI prints unsigned JSON.

Wallet security: WalletConnect wallets sign in-app, so no key material ever reaches the CLI. Local wallets keep the mnemonic sealed with scrypt + AES-256-GCM; the ciphertext lives in the macOS Keychain (generic password, iCloud Keychain syncable) with a 0600 file fallback elsewhere, and the plaintext mnemonic is shown once at creation and never written to disk. Environment: WALLETCONNECT_PROJECT_ID (optional override of the built-in public Reown project id), SUGAR_WALLET_PASSPHRASE (non-interactive local signing), SUGAR_WALLET_DIR / SUGAR_WALLET_NO_KEYCHAIN (storage overrides).

Self-hosted ALM (aero serve)

aero serve watches the configured concentrated positions and rebalances them like Aerodrome's ALM vaults — the strategy layer (src/alm/) is an off-chain reimplementation of Mellow's PulseStrategyModule (original, lazy-syncing, lazy-ascending, lazy-descending, and the Pulse V2 expand), with Mellow's production widths as defaults.

aero alm init            # scaffold ~/.config/sugar-ts/alm.json from your CL positions
aero serve               # dry-run daemon: logs/notifies what it WOULD do
aero serve --execute     # unlock the local wallet and rebalance for real
aero serve --once        # single pass, for cron
aero alm status          # tick, range, and gate status per managed position

Safety: dry-run is the default; --execute requires the local encrypted wallet (WalletConnect cannot approve unattended); every phase is simulated via eth_simulateV1 before signing (an RPC without it blocks broadcasting unless --allow-unsimulated); rebalances pass a Mellow-style TWAP deviation guard, a per-position cooldown, and a rolling daily cap persisted in alm-state.json; "telegram": true sends buddytg push notifications. AERO_ALM_CONFIG overrides the config path. See aero guide alm.

Safe execution and aero alm safe-setup are disabled for 0.1. Safe dry-run observation remains available. The previous role allowed unrestricted router command bytes. This update does not revoke deployed permissions: Safe owners must review and revoke old keeper roles or disable their Roles module. Do not treat the old role as protection against a compromised keeper.

EOA execution remains experimental pending fork tests. Rebalances and compounds check TWAP before every submission. Local fallback weights elapsed time and requires a full window without gaps longer than two poll intervals. It is a sampled estimate, not an on-chain oracle or protection against all MEV.

The daemon records the wallet, pool, original NFT, intended range, balance baselines and phase journal IDs before sending. Transaction journals preserve submitted hashes and receipt state. Rebalance attempts consume the daily cap and cooldown when the cycle starts; compound attempts start the 24-hour delay. Partial failures do not reset those limits. State writes are atomic and synced, and an exclusive state-file lock prevents overlapping local ALM passes.

ALM config entries accept positionId as a decimal string. aero alm init --position-id <id> selects one NFT when several share a pool. Each pool has one managed NFT; successful rebalances persist its replacement ID across restarts. Manual recovery accepts --position-id <repaired-id> and verifies a funded NFT owned by the same wallet in the same pool before updating that identity.

After interruption, the daemon reconciles known hashes but does not replay phases or start another cycle for that wallet and chain. Use:

aero alm status
aero alm recover --id <cycle-id>
aero alm resolve --id <cycle-id> --note "Verified receipts and repaired the position"

recover only reads receipts. Repair partial positions manually before confirming resolve, which cancels unsubmitted remainder and keeps attempt limits. Pending submissions or unknown outcomes without a hash block resolution. Those require operator investigation; the daemon never guesses that a send failed. Missing/corrupt journals and stale process locks also require review. Do not clear state or locks to retry blindly. Automatic phase resumption, multi-host coordination and concurrent external trading in the managed wallet are unsupported.

Chain clients

OPChain, BaseChain, LiskChain, UniChain, ModeChain, FraxtalChain, InkChain, SoneiumChain, SuperseedChain, and CeloChain mirror the Python chain-specific classes. getChain, token-based factories, async-name aliases, and Lisk/Uni Supersim clients are exported as well.

Superswap

Superswap ports the OP/Lisk/Uni cross-chain quote and unsigned transaction flow. If a destination ICA relay is required, the returned result includes swapData; pass the commitment transaction hash to result.relayArgs(...) and then to a SuperswapRelayer.

Configuration

The defaults and environment names match the Python SDK. Per-chain overrides use names such as SUGAR_RPC_URI_8453, SUGAR_SWAP_SLIPPAGE_8453, and SUGAR_CONNECTOR_TOKENS_ADDRS_8453; un-suffixed global values are also accepted. Constructor options take precedence over environment values.

Supported chain IDs: 10, 130, 252, 1135, 1868, 5330, 8453, 34443, 42220, and 57073. Local Supersim settings are available for Uni (130, port 4446) and Lisk (1135, port 4445).

RPC resilience

Sugar keeps its public interface Promise-based while using Effect internally for RPC deadlines, retry classification, and bounded read concurrency. The SDK-owned HTTP transport disables Viem retries so one policy owns all attempts. By default, idempotent reads receive up to three retries with 150ms exponential backoff inside a 120-second total deadline. Numeric and HTTP-date Retry-After headers are honored without extending that deadline. Multi-stage reads share one budget across pagination count/pages and quote multicall/fallback work; exhausted rate limits and transport failures are not amplified through the quote fallback.

Callers can tune this with plain TypeScript options (no Effect types cross the SDK interface):

const sugar = new SugarClient(8453, {
  onRpcEvent: (event) => telemetry.record(event),
  rpcPolicy: {
    maxRetries: 2,
    baseDelayMs: 250,
    deadlineMs: 60_000,
  },
})

onRpcEvent is optional and silent by default. It reports operation/phase, duration, aggregate attempts, and pagination item/page counts without wallet addresses or calldata. Pass the same callback in the options to createSugarFailoverTransport(rpcUrls, { onRpcEvent }) to record whether a backup RPC endpoint was used; endpoint URLs are not included.

Expected RPC failures reject with SugarRpcError, whose code is one of RPC_TIMEOUT, RPC_RATE_LIMITED, RPC_UNAVAILABLE, or RPC_READ_FAILED. The original Viem error remains available as cause.

Addressed pool and position reads can avoid a cold global pool scan by providing a durable poolLocatorStore. A stored offset is never trusted blindly: every new client verifies it with Sugar.all(1, offset) and deletes it before falling back to discovery when the pool no longer matches.

const sugar = new SugarClient(8453, {
  poolLocatorStore: {
    get: (key) => database.poolOffsets.get(key),
    set: (key, locator) => database.poolOffsets.set(key, locator),
    delete: (key) => database.poolOffsets.delete(key),
  },
})

The store is an optimization only. Store errors fall back to verified on-chain reads and cannot make Sugar unavailable.

When injecting a custom transport or publicClient, configure its own retry count to zero if Sugar should remain the sole retry owner. Effect can interrupt its wait and sibling retry fibers at the deadline, but the underlying transport may continue until its own timeout. An injected client is responsible for physically aborting its network request when it supports cancellation.

Headless verification

bun run --cwd packages/sugar test:headless

This runs the TypeScript CLI end to end against Lisk by default: a pool read, an ETH-to-USDT quote, and unsigned swap construction. It never broadcasts. Override SUGAR_HEADLESS_CHAIN_ID, SUGAR_HEADLESS_RPC_URL, token names, amount, or public wallet address as needed. The upstream SDK does not ship a public testnet deployment; its supported test environment is local Supersim, so point SUGAR_HEADLESS_RPC_URL at the appropriate fork when it is running.

CLI and TUI swap confirmations show both resolved asset addresses before signing. WalletConnect uses one client per process and checks the current session against the exact sender, chain and transaction permission before every submission. Session expiry, deletion and account changes invalidate the local connection.

Tokenized stocks and wallet indices

aero stocks list reads the ten Base stocks tracked by Dromos Kitchen. aero stocks buy --stock NVDAc --amount 25 spends 25 USDC; aero stocks sell --stock NVDAc --amount 0.1 sells 0.1 token units. Both use the existing review and wallet-signing flow. Add --dry-run to print the unsigned plan.

Create a local allocation with aero index create --name tech --allocations 'NVDAc=50,AAPLc=50', then preview aero index rebalance --name tech --cash 100 --dry-run. index list, show, update, and delete manage the saved definitions. Stocks and Indices are also available in aero tui, including weight bars, an editor, and a current-versus-target rebalance preview.

Indices use all wallet holdings of their named stocks. They are local recipes, not separate funds. Only the explicit --cash contribution spends existing USDC. Keep an exiting stock at 0% until sold. The basket executes atomically, and approvals account for the total spending across every leg.