A self-hosted, config-branded Telegram + Discord trading bot for the MOON.lite router and aggregator on the Arc blockchain.
You run it on your own infra under your own brand. It holds all of your users' data and keys, signs every transaction locally, and reaches the MOON.lite real-time feed + execution plane over gRPC. MOON.lite never sees a private key — the bot is non-custodial to MOON.lite (you are the custodian for your users; the UI states this plainly and lets any user export their key).
- App: https://moonlite.so
- API: https://api.moonlite.so
- Chain: Arc testnet · chainId
5042002· nativeETH - Router (RouterV2):
0x37736562dd741A35291379cA3fa0EBb420DC4376·swapExactInselector0xb78f3852· 14-field EIP-712SwapAuthorization
This repository is the client only: the bot, the client-side signer, the P&L card renderer, and the gRPC
.proto+ client shim it needs. The MOON.lite hub server, routing engine, and transaction-relay path are not here and are not required to run the bot.
Everything else has sane defaults. Copy config.example.toml → config.toml, then set:
[brand] name·logo_path· colors — your identity (shown in-app and on the P&L card).[economics] partner_bps— your fee band:0(advertise 0% and keep positive slippage above the quote) or3–10bps off the top.1and2are rejected — RouterV2 has no valid regime there.[economics] fee_recipient— your payout wallet. The placeholder zero-address fails validation on purpose so you can't ship without setting it.ML_FEED_KEY(env) — your MOON.lite feed key (x-ml-key), needed only for the real-time gRPC feed + execution plane (the/quote+/swapHTTP plane is public). Register your integration on the MOON.lite partners page with your payout address — your feed key is minted instantly in the response (one per payout address; re-reveal it later by signing from your payout wallet). See Getting a feed key.TG_BOT_TOKEN(env) — from @BotFather. (Optional:DC_BOT_TOKENfor Discord.)
Then run it. The config is validated fail-fast — a bad value aborts the boot with a clear message, never a silent default:
cp config.example.toml config.toml && $EDITOR config.toml
export ML_FEED_KEY=... TG_BOT_TOKEN=... KEY_MASTER=<your key-encryption passphrase>
cargo run --release -- --config config.tomlNew to the bot? Read Quick start below, then docs/CONFIG.md for every field.
| Area | Capability |
|---|---|
| Manual trading | Buy / Sell by pasting a token address → live token card → amount menu → quote → sign → submit |
| Per-user settings | Slippage (→ minOut), priority/gas tier, effective fee within your band, default amounts, auto-approve |
| Strategies | Sniper, copy-trade, limit orders, positions, trailing stop, AFK, Twitter — each on the real-time feed |
| Real-time feed | gRPC price / wallet / launch streams, deduped into one upstream subscription for all your users |
| Referrals | First-touch attribution, self-funding payouts from realized fee revenue, deep-link + P&L-card QR |
| Withdrawals | Plain ERC-20 / native transfers to any address, signed with the user key, broadcast to the public RPC |
| Key export | Private key / seed shown once, auto-deletes after 30s (survives a restart mid-countdown) |
| P&L card | Branded, shareable win/loss image with weighted-average cost basis and a referral QR |
| Branding | Name, colors, logo, font, taglines, card backgrounds — all via config.toml |
- Rust (stable, 1.75+) and
cargo - A Telegram bot token from @BotFather and/or a Discord bot token
- A MOON.lite feed API key (
x-ml-key) for the real-time feed + execution plane. Register your integration on the partners page with your payout address — your feed key is minted instantly in the register response (one per payout address; reveal it again later by signing from your payout wallet). See Getting a feed key. - A payout wallet (your
[economics] fee_recipient) — where your fees land, and the address you register on the partner leaderboard.
cp config.example.toml config.toml
$EDITOR config.tomlFill in your brand, your x-ml-key, your fee partner_bps (see below), and your
fee_recipient wallet. Secrets (bot tokens, key-encryption passphrase) are read
from environment variables referenced as env:VAR_NAME — never write real
secrets into the file. See docs/CONFIG.md for every field.
export TG_BOT_TOKEN=... # from BotFather
export DC_BOT_TOKEN=... # optional, for Discord
export KEY_MASTER=... # your key-encryption passphrase (keep it safe)
cargo run --release -- --config config.tomlOn start the bot validates the config fail-fast, connects the feed, rehydrates
every user's strategies from its database, and re-subscribes. If
on_start_open_url = true (default) it opens your operator dashboard in a
browser — set it to false for headless/cron restarts.
The gRPC feed + execution plane (ml.px.v1.Fx) is key-gated; every call carries
your x-ml-key (see docs/FEED-API.md). The quote
& swap HTTP plane (/quote, /swap) is public and needs no key — only this real-time
feed + execution layer does.
Issuance is self-serve and instant — registering your integration mints your key on the spot. No waiting, no manual coordination.
-
Open the MOON.lite partners page and fill in Register your integration: your name, type (Web / Telegram / Discord), link, logo, and your payout address (the same wallet you put in
[economics] fee_recipient). ThisPOSTs to/partners/register. -
Your key is minted immediately. The response body returns your full feed key — an
ml_live_…string — exactly once, at mint time, so copy it right then. There is one key per payout address (idempotent: registering the same address again just returns the existing key, masked). The key is issuedprovisionaland works against the live feed straight away. It is delivered only in the HTTPS response body, never in a URL or query string. -
Set it as the
ML_FEED_KEYenvironment variable and you're live:
export ML_FEED_KEY=ml_live_xxxxxxxxxxxxxxxx # keep it in the env, never in config.tomlML_FEED_KEY authenticates your feed and execution calls. The config references it
as feed_key = "env:ML_FEED_KEY" (see
docs/CONFIG.md). Treat it like a password —
never commit it, never place it in a query string or URL.
Lost it, or setting up a second machine? The safe lookup (GET /partners/registration)
only ever returns the masked key. To recover the full key you prove you own the payout
address: request a challenge (POST /partners/key/challenge), sign it with that wallet
(EIP-191), and reveal (POST /partners/key/reveal). Knowing or typing an address is never
enough — only a signature from the registered wallet reveals the key.
Keep it earning. Keys are anti-Sybil and profit-gated: a blacklisted address is rejected at mint, and a key that routes no fees can be suspended. Run a real integration and route volume — your key stays active and your logo climbs the leaderboard.
Leaderboard vs. key. Registering also lists you on the partner leaderboard as
pendinguntil your integration is approved for public display. That approval gates only your listing, not your key — your feed key works the moment it's minted.
Your fee band is validated at load. 1 and 2 are rejected — they have no
valid regime on RouterV2 (protocolBps must be 0, or 3..=10).
0— advertise 0% fees and capture positive slippage above the signed quoted output (never the user'sminOutbuffer). RouterV2 CAPTURE mode.3..=10— an explicit fee band, taken off the top; the trader keeps any positive slippage. Paid to yourfee_recipient(partnerRecipienton-chain).
Referrals pay a configurable share (default 30%) of the realized fee revenue
you actually collected — never of volume — so payouts can never exceed fees
received. See docs/FEATURES.md.
Every trade — manual or strategy — takes the same path:
Quote (gRPC U1) → reconstruct the 14-field SwapAuthorization from MlQuoteReply.auth
→ verify trader/recipient=user, tokenOut, amountIn, fee band, net_out
→ RE-DERIVE the EIP-712 digest locally (never trust the hub's)
→ sign that digest with the user's key (client-side EIP-712)
→ build swapExactIn(auth, sig, hops) calldata + sign the eth tx
→ Submit (gRPC U2) → MOON.lite relays it to RouterV2
MOON.lite's Quote (U1) returns an unsigned plan whose money-critical
part is a structured MlAuth mirror — the exact 14-field
SwapAuthorization RouterV2 verifies — alongside the gross/net outputs, the
opaque hops_abi/candidates_abi route material, and the hub's digest. Your bot
reconstructs the auth from that mirror, verifies it (trader = user,
recipient = user, tokenOut/amountIn as requested, protocolBps == your
band, and net_out consistent with your band), re-tightens minOut from the
user's slippage, then re-derives the EIP-712 digest itself and signs that —
it never trusts MlQuoteReply.digest. Submit (U2) only accepts an
already-signed swapExactIn to the router — it cannot sign for a user.
Withdrawals and referral payouts are plain transfers signed locally and
broadcast directly to the public RPC (the source of truth for nonces).
See docs/FEED-API.md for the full gRPC surface.
- Each user has ≥1 EOA. Keys are generated in the bot, encrypted at rest
(
Argon2idmaster key → per-userHKDFDEK →XChaCha20-Poly1305), and never leave your host. The database alone is useless without the key-encryption passphrase. - Keys are decrypted only inside the signer and zeroized immediately after use.
- The bot re-checks every quote before signing, so a hostile or compromised
hub cannot redirect funds — the signed authorization pins
recipient = userandminOut. - You are the custodian for your users. The bot's UI states this and lets any user export their key or seed at any time.
Full threat model in docs/FEATURES.md.
docs/CONFIG.md— everyconfig.tomlfield, with defaults and validationdocs/FEED-API.md— the gRPC feed + execution plane (ml.px.v1.Fx)docs/FEATURES.md— manual trading, per-user settings, strategies, referrals, withdrawals, key export, P&L card, feedbackproto/moonlite_feed.proto— the wire contract
crates/
signer/ # client-side EIP-712 SwapAuthorization signing (no I/O, no hardcoded keys)
botcore/ # transport-agnostic core: config, subscription manager, key vault,
# strategies, referrals, withdrawals, P&L card (resvg), gRPC feed client
bot-tg/ # Telegram presenter (teloxide)
bot-dc/ # Discord presenter (serenity)
proto/ # the ml.px.v1.Fx .proto wire contract (client stubs generated at build)
docs/ # this documentation
config.example.toml
MIT — see LICENSE.
The P&L card renderer bundles the Inter typeface (© 2016 The Inter Project
Authors), distributed unmodified under the SIL Open Font License 1.1 — see
crates/botcore/assets/fonts/LICENSE-Inter.txt.
The OFL governs the font files only; all code remains MIT.
The bot is provided as-is; you are responsible for your users' funds and for complying with the law in your jurisdiction.