Skip to content

Discord bot for faucet drips (anti-abuse via account-age + per-user rate limit) #2

@proofmancer

Description

@proofmancer

Why

Public HTTP faucets get abused. The current POST /v1/drip endpoint rate-limits per-address, but an attacker rotates addresses trivially: any cloud provider, any VPN, any throwaway keypair. Per-IP would hurt good actors behind shared NATs (corporate networks, university Wi-Fi) without slowing real abusers.

Discord-based faucets sidestep this entirely. The pattern across many testnets:

  1. User joins our Discord server
  2. User runs /drip lig1... in a faucet channel
  3. Bot validates: account meets minimum-age threshold, hasn't dripped in 24h, address is shaped correctly
  4. Bot hits POST /v1/drip against the api with a privileged token
  5. Bot replies with the tx hash + a link to explorer.ligate.io/tx/{hash}

Anti-abuse layers stack: Discord's own anti-spam (CAPTCHA on join, account-age checks, role gating), plus per-Discord-user cooldown enforced by the bot, plus the api's per-address cooldown as backstop. Sybil-resistance becomes "spin up N legitimate-looking Discord accounts and wait Y days each" instead of "rotate keypairs in a loop".

Scope (v0 of the bot)

  • Discord slash command /drip <address> — only callable in a designated faucet channel, by users with account_age >= 7 days and member_of_server >= 1 day.
  • Per-user cooldown — 24h, enforced via Discord-user-id keyed table. Persists in the same Postgres the api already uses (new table discord_drip_history).
  • Privileged drip path — bot calls POST /v1/drip with a Bearer token that bypasses the api's per-address cooldown (since Discord-user-id is the new rate-limit dimension). Token is a Discord-bot secret separate from the user-facing api.
  • Audit log — every drip emits a tracing event with discord_user_id, target_address, tx_hash, at. Goes to the same JSON logs the api emits; ops can grep for abuse patterns.
  • Failure modes — bot DMs the user with a clear "address rejected because X" / "you've dripped recently, try in N hours" / "address-only drips, paste the lig1..." rather than failing silently.

Architecture options

The bot is a small Rust process that connects to Discord via the gateway (serenity or twilight crate) and calls the api over loopback (deployed in the same Railway project) or HTTPS (if deployed elsewhere). Two options:

A. New crate in this repo (crates/discord-bot/)

  • Pro: shares the workspace's existing chain types + drip primitives + Postgres pool.
  • Pro: single Railway project, single Postgres, single deploy.
  • Con: Discord gateway runs forever; bot's failure modes become api-pod-restart triggers if not isolated.

B. New repo ligate-io/ligate-discord-bot

  • Pro: clean separation. Bot is a Discord-side concern; api is a chain-side concern.
  • Pro: independent deploy lifecycle.
  • Con: more repos to maintain. Same code-drift / context-switching tax we just folded the faucet to avoid.

Lean toward A unless the bot grows into other Discord features (mod tools, server stats, attestation lookups). Revisit if so.

Privileged-drip endpoint design

The api needs a way for the bot to drip without being subject to the per-address cooldown. Two options:

A. Bearer-auth path on /v1/drip — accept an Authorization: Bearer <token> header. If valid, skip the per-address rate limit; the bot does its own rate-limit-per-Discord-user instead.

B. Separate endpoint POST /internal/drip — same shape, no rate limit, locked to the bot's IP / Bearer token. Not exposed to public CORS.

Lean toward (B) — separate endpoint keeps the public /v1/drip semantics simple (what you see is what you get; rate limit always applies). Internal endpoint is invisible to humans.

Out of scope for v0 of the bot

  • Mod tools (kick, ban) — wrong concern; community can use stock Discord tools.
  • Attestation lookup commands (/attestation lat1...) — frontend's job (explorer.ligate.io).
  • Embedded captchas — Discord's join-server captcha is enough for v0.
  • Multi-language replies — English only.
  • Admin dashboard — journalctl -fu ligate-discord-bot is enough; build a real dashboard if abuse pressure surfaces real signal.

Pre-reqs (operator-side, when the bot is built)

  1. Create a Discord application at https://discord.com/developers/applications
  2. Add a bot user; copy the bot token to DISCORD_BOT_TOKEN in Railway secrets
  3. Invite the bot to the official Ligate Discord with applications.commands + bot scopes
  4. Create a #faucet channel; restrict /drip to that channel
  5. Set role requirements (e.g., @verified after Discord captcha pass)

When

Post-devnet. We're shipping the HTTP faucet first because partner integrations (Mneme browser wallet, Themisra demo flows) need programmatic access. The Discord bot is the human-facing alternative, important once devnet starts seeing real community traffic.

Tracking: ~Week 2-4 post-devnet, depending on abuse signal on the HTTP faucet.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions