azimuth

agent
Guvenlik Denetimi
Uyari
Health Uyari
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 7 GitHub stars
Code Gecti
  • Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

Azimuth — multi-venue concentrated-liquidity LP bot. Automated pool screening, entry and exit for Meteora DLMM on Solana and Uniswap v3/v4 on Robinhood Chain.

README.md

Azimuth — Concentrated-Liquidity Pool Signal Daemon for AI Agents

Version
Go Version
License: MIT
Status
Chain
Chain

Azimuth is a Go daemon that watches concentrated-liquidity pools across two
venues — Meteora DLMM (Dynamic Liquidity Market Maker) on Solana and
Uniswap v3/v4 on Robinhood Chain — screens them through quality gates,
and hands an AI trading agent (built on
Hermes) a batch of vetted candidates
to pick from and deploy — instead of you babysitting a screener or grabbing the
first mediocre pool a dumb cron finds.

Each venue gets its own exit monitor. Solana is the mature path (screening,
entry and automated exits); the Robinhood Chain side screens and signals, and
its exit loop ships disabled until you deploy there yourself.

⚠️ This trades real funds. DYOR. NFA. Use at your own risk — see
Disclaimer.


Table of Contents

Why this exists

Most pool screeners run on a fixed schedule and deploy into whatever happens
to be trending at that moment, grabbing the first pool that clears their
filters. Both habits cost money: stale snapshots miss short-lived fee
opportunities, and first-match selection lets a mediocre pool take the slot a
stronger one deserved.

This daemon instead watches Meteora's pool-discovery API continuously and,
each cycle, emits every pool that crosses all quality gates as one batch. Your
AI agent sees the full set side by side and deploys only the strongest
candidate — always off fresh data.

The Solana path runs entirely on Meteora's public pool-discovery API — no
third-party accounts, API keys, or scraping required to source signals. The
Robinhood Chain path is keyless for discovery too (GeckoTerminal's public tier
plus Uniswap's interface GraphQL gateway), though its token-safety gates want a
GMGN key and fail open without one.

Features

  • Continuous discovery, not polling snapshots — every POLL_INTERVAL
    cycle, not just on a fixed cron tick.
  • Batch signalling — one HMAC-signed webhook per cycle carries every
    qualifying pool, so your agent ranks the set instead of racing to grab the
    first one.
  • Four isolated Solana screening modescasual (30m, volume-spike plays),
    multiday (24h, quality holds), turnover (30m, fee-capture plays on
    small high-base-fee pools) and pulse (5m trending) with independent
    thresholds and position budgets. turnover and pulse share a band but
    sample it through different discovery windows, so enabling both makes entries
    the union of the two screens rather than either alone.
  • Four more on Robinhood Chainfresh (launch feed), mature (24h+ pools
    still printing an outsized fee pace), and two one-sided bid-wall modes,
    ladder (WETH-quoted) and usdg-ladder (USDG-quoted tokenized equities),
    which park resting quote-asset rungs below spot and never buy the token.
  • Layered risk gates — TVL, fee/TVL, market cap, holder count, organic
    score, top-10/dev supply concentration, mint/freeze authority, Jupiter
    shield status, a best-effort DexScreener downtrend filter, and a Jupiter
    token-audit gate (bot-holder %, global fees paid).
  • Conviction scoring — every signal carries a 0–100 Degen Score (balanced
    trading/LP/fee/liquidity efficiency); single-candidate cycles must clear a
    conviction floor, so "only option" never auto-reads as "good option".
  • PVP rival detection — candidates whose ticker is contested by an
    established same-symbol token with its own live DLMM pool are flagged with
    the rival's stats, so the agent avoids LPing the losing side of a ticker war.
  • Learning loop — deploys snapshot their entry signals, closes journal the
    outcome per pool (skip pools that already lost money, cool down churned
    pools), and a darwinian recalc boosts/decays signal weights the deploy agent
    reads on every pick.
  • Fail-open by design — gates with unreliable upstream data (verified
    status, momentum) default to pass instead of over-rejecting on missing
    fields.
  • Pluggable dedup store — in-memory for a single instance, or Redis to
    share "seen" pools across restarts/instances with a per-pool rolling TTL.
  • Exit management included — a companion dlmm_monitor.py cron owns all
    closes, applying stop-loss, trailing take-profit, out-of-range, and a
    "don't close a healthy winner" GUARD.
  • One install script — wires the skill, webhook subscription, and
    SOUL.md/cron templates into a Hermes
    profile and builds the daemon.

Architecture

flowchart LR
    A["azimuth (this Go daemon)<br/>poll → screen → dedup"] -->|"HMAC-signed POST /webhooks/dlmm-signal<br/>(batch array)"| B["Hermes agent<br/>ranks the batch,<br/>picks 1 + strategy<br/>→ dlmm_pipeline.py"]
    C[(Meteora discovery API)] --> A
    B --> D[(Meteora on-chain<br/>deploy/monitor)]

The daemon (internal/scanner) does one thing on a loop: poll the discovery
API, screen every candidate locally (the API's own filter is best-effort),
dedup against pools already signalled, and forward the batch. Your Hermes
agent owns the judgment call — which pool, which strategy, whether to reject
the whole batch.

Quick Start

Prerequisites

  • Go 1.22+ — builds the daemon.
  • Node.js (18+) and Python 3 — run the solana-dlmm skill (pipeline,
    monitor, on-chain executor) inside your Hermes profile.
  • A Hermes agent profile to install
    into.
  • Redis (optional) — only needed if you want the dedup set to survive
    restarts or run multiple instances; in-memory works fine for a single box.
  • A Solana RPC endpoint (Helius, QuickNode, or the public
    api.mainnet-beta.solana.com as a fallback) and a funded wallet.

Create a Hermes profile

If you don't have a Hermes profile yet, create a dedicated one for the trading
agent (full guide: Hermes docs — Profiles):

# Create a profile named "dlmm" — this also registers a `dlmm` command alias
hermes profile create dlmm

# Configure API keys and model settings interactively
dlmm setup

# Optional config tweaks
dlmm config set model.default anthropic/claude-sonnet-4

Key files under ~/.hermes/profiles/dlmm/:

  • .env — API keys and, for this project, your wallet (SOLANA_PUBLIC_KEY /
    SOLANA_PRIVATE_KEY, see Configuration below).
  • config.yaml — model, platforms, and the webhook port this daemon posts to.
  • SOUL.md — the agent's personality/policy document; install.sh merges the
    DLMM trading rules in as section 9.

Start the messaging gateway (Telegram delivery, webhook listener):

dlmm gateway start        # foreground
dlmm gateway install      # or persistent systemd/launchd service

Installation

git clone https://github.com/pgen0x/azimuth.git
cd azimuth

# Installs the skill (symlinked, not copied — edits here go live instantly),
# the webhook subscription, SOUL.md section + cron job templates, the 20s
# monitor-loop systemd service, and builds azimuth.
./install.sh ~/.hermes/profiles/<your-profile>

The install enables azimuth-sol-monitor.service (user-level systemd), which runs
dlmm_monitor.py every 20 seconds. This loop is the trader-side safety net
auto-close, auto-swap-to-SOL, out-of-range re-centering and cooldowns all fire
from it; the Hermes cron job is only the reporting/judgment layer. Make it
survive logout once per machine:

loginctl enable-linger $USER
systemctl --user status azimuth-sol-monitor   # verify it's running

Without user systemd (e.g. macOS), run the loop some other persistent way:
nohup bash <profile>/skills/solana-dlmm/scripts/dlmm_monitor_loop.sh &

Configuration

install.sh prints the exact next steps for your profile path, but in short,
create <profile>/.env:

SOLANA_PUBLIC_KEY=...
SOLANA_PRIVATE_KEY=...            # base58, used by dlmm_executor.js
SOLANA_RPC_URLS=https://mainnet.helius-rpc.com/?api-key=YOUR_KEY,https://api.mainnet-beta.solana.com
DLMM_ALERT_TARGET=telegram        # instant trade-event alerts (close/rebalance/compound)
                                  # sent script-side via `hermes send` — no LLM tokens.
                                  # "platform" or "platform:chat_id"; empty disables.
DLMM_TZ=Asia/Jakarta              # timezone for every report-card timestamp (IANA name,
                                  # e.g. America/New_York; empty/unset = system zone)
DLMM_STATS_HOUR=09                # daily scoreboard send hour in DLMM_TZ (00-23, default 09)

And the daemon's own .env (this repo's root):

cp .env.example .env        # set HERMES_WEBHOOK_SECRET to match the subscription

Launch

set -a && . ./.env && set +a
./azimuth

The daemon is stateless except for its dedup set (in-memory by default; point
REDIS_ADDR at a Redis instance to persist "seen" pools across restarts).

What gets screened

Four isolated modes, each with its own budget in the agent:

Mode Timeframe Min TVL Min fee/TVL (window) Min mcap Min holders Min fees/day
casual 30m $5k 0.1% $250k 10000 $20
multiday 24h $50k 1.0% $1M 5000 $150
turnover 30m $10k 0.15% $150k 500 $25
pulse 5m $10k $150k 500

pulse gates on fee/active-TVL (≥ 0.05 for the 5m window) rather than
fee/TVL, and has no absolute fees/day floor — see below.

Shared gates (all modes): SOL-paired · volatility > 0 · organic score
floor · top-10 ≤ 60% · dev ≤ 20% · no freeze/mint authority · no critical
warnings · (optional) not dumping (5m > −5%, 1h > −15%, 6h > −12%, 24h > −25%).
volatility ≤ 15, fee/TVL change ≥ −40%, the warning-severity flag gate and
is_verified not false apply to every mode except where noted below.

Turnover mode

While casual/multiday chase trending pools, turnover targets the niche
they never see: small pools (TVL $10k–$150k) with degen base fees (≥1%)
turning their TVL over fast
. The thesis is fee capture, not price — fee
income is fee_pct × turnover and isn't capped by the monitor's trailing
take-profit, so a $50k pool doing 5× volume/TVL at a 2% fee out-earns a
"better" trending pool.

Extra gates on top of the shared set: TVL ≤ $150k · pool base fee ≥ 1% ·
volume/TVL ≥ 3 per 30m window · ≥ 20 swaps and ≥ 15 unique traders in-window
(wash-trade guard — this is what lets the organic floor relax to 50) · fee/TVL
≥ 0.15% per 30m (~7.2%/day pace). Discovery queries category=all sorted by
fee:desc instead of trending.

Enable it in the daemon's .env (off by default):

ENABLE_TURNOVER=true

then restart ./azimuth. Signals arrive with "mode": "turnover"; the agent
prompt and dlmm_pipeline.py --mode turnover already handle the mode end to
end (2 position slots, tight-range custom_ratio_spot preferred).

Pulse mode

pulse is a port of a reference bot's own screen. It sits on the same
TVL/mcap/holder band as turnover ($10k–$150k TVL, $150k–$10M mcap, ≥500
holders, bin step 80–125, organic ≥60), and the point is not the band — it is
the window. turnover queries category=all on a 30m timeframe sorted by
fee:desc; pulse queries category=trending on a 5m timeframe. The
same universe sampled two ways yields largely disjoint pools, which is the
whole reason to run both: entries become the union of the screens.

What it drops relative to turnover: no base-fee, volume/TVL, swap-count or
unique-trader gate (it takes trending pools, not only high-fee oscillators);
no fee/TVL or fees/day floor; no volatility ceiling; no yield-decline gate; no
warning-severity gate (critical still hard-rejects). What it adds: fee/active-TVL
≥ 0.05 and window volume ≥ $500. Organic stays at 60, above turnover's
relaxed 50 — without the swap/trader wash-trade guards, organic score is the
only inorganic-volume defence left in the screen.

The daemon's own momentum / Jupiter-audit / GMGN / PVP gates still apply to
this mode, so it is strictly tighter here than in the bot it came from.

ENABLE_PULSE=true

Robinhood Chain modes

The second venue (Uniswap v3/v4, chain 4663) has four modes of its own. They
share one Screen and every safety gate, but not their discovery source — no
single feed spans all four theses:

Mode Toggle Discovery Thesis
fresh ROBINHOOD_ENABLED GeckoTerminal new_pools brand-new pools; a launch feed, so a pool scrolls off within minutes
mature ROBINHOOD_MATURE Uniswap interface GraphQL gateway (keyless) 24h+ pools still printing an outsized fee/TVL pace — the gateway indexes nothing younger than a day
ladder ROBINHOOD_LADDER gateway ∪ cached GeckoTerminal trending page weth_ladder: a one-sided WETH bid wall under an established pool
usdg-ladder ROBINHOOD_STOCK_LADDER gateway usdg_ladder: the same wall under USDG-quoted tokenized equities

Each mode is quote-pinned, so a batch never mixes WETH- and USDG-quoted pools:
a ladder's rungs and its sizing must be denominated in the same asset. USDG is
6 decimals, WETH is 18
— every amount in the executors goes through a
quote-aware parse/format, never parseEther.

The ladder shape

Both ladder modes mint the same thing: N contiguous rungs of the quote asset
only
, stacked on the bid side below spot, sized on a linear ramp so the rung
nearest spot is smallest, minted atomically — one multicall on v3, one
modifyLiquidities unlock on v4.

Two consequences worth understanding before enabling either:

  • It never buys the token. That is the entire edge — the failure mode of a
    two-sided range is holding a bag after a collapse. So its exits are re-pins
    (the wall is stale, a rung filled, or the wall has gone fee-dead), not
    stop-loss/take-profit, and a rung sitting out of range is by design — the
    fee-dead OOR timeout that governs other strategies must never apply to it.
  • Rung width quantizes to the pool's tick spacing, which differs per fee
    tier (0.05% → 10, 0.3% → 60, 1% → 200). The requested width rounds to whole
    spacings, so a 240-tick request stays 240 at spacings 10 and 60 but collapses
    to a single 200-tick spacing on the 1% tier. Covered drop per rung is
    therefore per-tier
    , and the executor's ladder log line — not the env var —
    is the honest source for how wide a wall actually is.

The equity universe in particular is not one fee tier: an underlying frequently
lists at two or three of them simultaneously, so ROBINHOOD_MAX_PER_TOKEN
(default 1) caps how many of the ROBINHOOD_MAX_OPEN_POSITIONS slots a single
underlying may hold. Without it one token takes the whole book — three walls
under one price, which all fill together.

Both feeds are also rate-limited in one place. GeckoTerminal's public tier allows
~30 requests/minute per IP, and a 429 on the enrich call costs a mode its entire
cycle, so every GT request in the package passes a shared gate that spaces
requests, backs off hard on a 429, and caches the per-candidate candle reads the
entry-timing gate would otherwise repeat every cycle.

Geometry (rung count, per-quote width, dust floor, size ramp, band layout) lives
in one shared module, assets/skill/scripts/uni_ladder.js, required by both the
v3 and v4 executors and mirrored by the exit monitor's stale-rung rule — those
numbers describe the thesis, not the protocol, so change them there and never in
one executor.

See docs/SIGNAL_SCHEMA.md for the exact webhook payload,
and docs/ROBINHOOD_CHAIN_PLAN.md for this venue's
full plan and phase status.

Configuration

Two .env files, two readers

This trips up most first-time setups. There are two env files and they are
not interchangeable:

<repo>/.env <profile>/.env
read by the Go daemon, via systemd EnvironmentFile= the Python/Node executors + exit monitors
holds what to screen, which venues, how to dispatch every secret — wallet keys, RPC URLs, alert targets
wallet keys none, ever SOLANA_PRIVATE_KEY, EVM_PRIVATE_KEY

install.sh never writes either one — you create both. The daemon's unit uses
the repo file as its EnvironmentFile, so the service cannot start until
<repo>/.env exists
.

⚠️ Duplicate keys silently win from the bottom. systemd's EnvironmentFile
applies the last assignment, so a stray second FOO=false further down the
file makes edits to the first one look like they do nothing. Check with:
grep -oE '^[A-Z_0-9]+=' .env | sort | uniq -d

Enabling a venue

Each venue is gated twice, on purpose — screening and spending are separate
switches, so you can watch a venue produce signals for days before it can touch
funds:

Solana (Meteora DLMM) Robinhood Chain (Uniswap v3/v4)
screen + signal ENABLE_CASUAL / ENABLE_MULTIDAY / ENABLE_TURNOVER / ENABLE_PULSE ROBINHOOD_ENABLED (fresh) / ROBINHOOD_MATURE / ROBINHOOD_LADDER / ROBINHOOD_STOCK_LADDER — independent, any subset
actually trade DEPLOY_CMD=dlmm_pipeline.py (omit for webhook mode) ROBINHOOD_DEPLOY_ENABLED=true, plus the mode allowlist ROBINHOOD_DEPLOY_MODES
secrets in <profile>/.env SOLANA_PUBLIC_KEY, SOLANA_PRIVATE_KEY (base58), SOLANA_RPC_URLS (comma-separated, failover in order) EVM_PRIVATE_KEY, ROBINHOOD_RPC_URL
executors ROBINHOOD_EXECUTOR_CMD (v3), ROBINHOOD_V4_EXECUTOR_CMD (v4)
sizing set in SOUL.md / the pipeline ROBINHOOD_DEPLOY_PCT, ..._FLOOR_WETH, ..._CEIL_WETH, ..._RESERVE_WETH, ..._MIN_GAS_ETH (+ _USDG variants)
exit monitor azimuth-sol-monitor.service (enabled by install.sh) azimuth-rh-monitor.serviceships disabled

⚠️ Enabling ROBINHOOD_DEPLOY_ENABLED without also enabling
azimuth-rh-monitor.service gives you positions with no automated exits
no stop-loss, no take-profit, no out-of-range close:
systemctl --user enable --now azimuth-rh-monitor

Set DRY_RUN=1 in <profile>/.env to exercise the whole path without
spending. Dry runs read your real wallet balance — it is a read-only RPC
call that spends nothing — so a soak reports the ticket sizes a live run would
actually deploy. Only the offline fallback (RPC unreachable) substitutes a
synthetic balance.

Daemon variables

All daemon config is via environment (see .env.example):

Variable Purpose
METEORA_DISCOVER_URL Base pool-discovery endpoint
POLL_INTERVAL How often to poll each enabled timeframe
HERMES_WEBHOOK_URL / HERMES_WEBHOOK_SECRET Where signals go, HMAC secret
REDIS_ADDR / REDIS_SEEN_KEY / SEEN_TTL Dedup store (empty REDIS_ADDR = in-memory)
ENABLE_CASUAL / ENABLE_MULTIDAY / ENABLE_TURNOVER / ENABLE_PULSE Toggle each Solana screening mode (turnover and pulse off by default)
ROBINHOOD_ENABLED / ROBINHOOD_MATURE / ROBINHOOD_LADDER / ROBINHOOD_STOCK_LADDER Toggle each Robinhood Chain mode (all off by default; independent of each other)
ROBINHOOD_DEPLOY_ENABLED / ROBINHOOD_DEPLOY_MODES Let this venue spend funds, and which modes may (others still screen + journal)
ROBINHOOD_MAX_OPEN_POSITIONS Position cap for the venue, counted in ladders (funded pools) across all modes
ROBINHOOD_MAX_PER_TOKEN How many of those slots one underlying may hold (default 1, 0 disables)
ENABLE_MOMENTUM_GATE DexScreener downtrend filter (fails open)
ENABLE_AUDIT_GATE Jupiter token-audit gate: rejects >30% bot holders, ships bot % + global fees in the signal (fails open)
ENABLE_PVP_CHECK same-symbol rival detection: flags candidates contested by an established token with its own live DLMM pool — advisory is_pvp + rival stats, never rejects (fails open)
LONE_MIN_SCORE Conviction floor for single-candidate batches (degen score 0–100, default 50, 0 disables)

Repo layout

main.go                     daemon entrypoint
internal/config             env config
internal/meteora            discovery client, screening gates, momentum
internal/scanner            poll ▸ screen ▸ dedup ▸ forward loop
internal/webhook            HMAC-signed forwarder
internal/store              seen-pool dedup (Redis or in-memory)
assets/skill                solana-dlmm skill (pipeline/monitor/executor) + safety scripts
assets/hermes               dlmm-signal webhook subscription + SOUL.md/cron templates
docs/SIGNAL_SCHEMA.md       webhook contract
install.sh                  wires assets into a Hermes profile + builds daemon

install.sh symlinks assets/skill/scripts/ and the DLMM-relevant
solana-web3 scripts into your profile instead of copying them — edits in
this repo take effect in every installed profile immediately, no reinstall
needed.

Performance scoreboard

python3 <profile>/skills/solana-dlmm/scripts/dlmm_stats.py [--hours 24] [--send]

Deterministic metlex-style card (closes, win rate, avg hold, realized PnL,
fees-vs-IL split, volume churned, per-mode breakdown, rebalance-chain PnL)
built from the Meteora portfolio API + close journal + Redis — no LLM. The
monitor loop auto-sends it daily at 09:00 WIB via hermes send when
DLMM_ALERT_TARGET is set.

Example output

The agent reports each deploy decision as a card like this (real values, not
placeholders — the prompt never lets it fabricate a size/entry/TX):

🤖 AI Pick — multiday · 14:32 WIB

Candidates screened: 5
Chose: world-SOL over 4 others — highest organic score, healthiest fee/TVL
Strategy: custom_ratio_spot — meme-pool volatility, symmetric range fits

`CQEYFv3KGnJ6xxRyrUNWbXjPHGnbyCbjuZDTGocV92ug`

| Metric | Value |
|---|---|
| Token | world |
| Decision | ✅ DEPLOYED (multiday) pos DoSj3Ga... |
| Size | 1.03 SOL |
| Range | 62↓ 62↑ bins |
| Fee/TVL | 10.8%/d |
| Score | 290.7 |

Project Status

Beta. The entry-signal daemon (this repo) and the Solana screening gates are
stable and running against real capital. Go tests cover the screening,
deploy-pick and indicator logic (go test ./internal/..., with go vet ./...
the other bar), but there is no CI and coverage is not uniform across packages.
The Robinhood Chain venue is newer than the Solana one and its ladder exits are
still being validated against live fills — if that's a blocker for your use case,
treat this as a reference implementation to adapt rather than a drop-in
production dependency.

Contributing

Issues and PRs welcome — see CONTRIBUTING.md for dev
setup, commit conventions, and the versioning policy. Keep changes scoped:
this is a small, single-purpose daemon by design. If you're proposing a new
screening gate or threshold change, explain the reasoning (what failure mode
it prevents) in the PR description.

See CHANGELOG.md for release history. This project follows
Semantic Versioning; the current version is reported
by ./azimuth -version.

Security

  • Wallet keys never live in this repo. The skill reads SOLANA_PUBLIC_KEY /
    SOLANA_PRIVATE_KEY from your profile .env at runtime.
  • Same for RPC endpoints: set SOLANA_RPC_URLS (comma-separated, tried in order
    with failover) in your profile .env. Never hardcode provider keys (Helius,
    QuickNode, etc.) into the scripts — this repo is public.
  • The webhook is HMAC-SHA256 signed; keep HERMES_WEBHOOK_SECRET secret and
    matched on both sides.
  • Found a vulnerability? Please open a private security advisory on GitHub
    rather than a public issue.

Disclaimer

DYOR. NFA (Not Financial Advice). This software trades real funds
autonomously on Solana. Meme-pool liquidity provision carries real, frequent
risk of loss (impermanent loss, rug pulls, thin-liquidity exits). Nothing
here is a guarantee of profit. Start with a small, disposable budget, read
the screening gates and exit rules before trusting it with real capital, and
never deploy more than you can afford to lose. No warranty, express or
implied — see LICENSE.

Yorumlar (0)

Sonuc bulunamadi