
Aerodrome AMM
- 9 installs
- 12 repo stars
- Updated August 3, 2026
- okx/plugin-store
Helps with ai & agent building tasks during AI-assisted development.
About
Aerodrome AMM is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- Aerodrome AMM
- AI & Agent Building
- AI-coding skill
Aerodrome AMM by the numbers
- 9 all-time installs (skills.sh)
- Ranked #12,133 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/okx/plugin-store --skill aerodrome-ammAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9 |
|---|---|
| repo stars | ★ 12 |
| Last updated | August 3, 2026 |
| Repository | okx/plugin-store ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Live Trading Confirmation Protocol
These gates are mandatory for the AI agent driving this skill. Before any call that signs or broadcasts an on-chain transaction via Aerodrome AMM (any internal write code path — swap, add-liquidity, remove-liquidity, claim-fees — that ends in a real onchainos wallet contract-call submission), ALL of the following must be true:
1. Paper / preview mode is the default. Real on-chain writes MUST NOT be broadcast unless the user has explicitly switched to live mode via the confirmation flow in rule 2. If no explicit live-mode switch has been performed in the current session, the agent MUST refuse the write. 2. Live-mode switch requires a typed user confirmation. Before flipping to live mode, the agent MUST display to the user: wallet address (onchainos wallet addresses), current Base balance (onchainos wallet balance --chain base), the configured per-trade / per-session risk limits, and a statement that on-chain writes are irreversible. The user MUST then reply with an unambiguous typed confirmation (e.g. confirm live mode / 确认开启实盘). A conversational "yes / sure / 可以" alone does not satisfy this gate. 3. Preview before every write. Every write operation MUST first generate a preview (resolved fields: action, target token + amount, expected outcome from --dry-run, estimated gas, recipient / contract, slippage bounds). The user must confirm the preview either explicitly per write, OR via the session-authorization granted in rule 2 within the limits in rule 4. 4. Session autonomy is bounded. Even after a session-level live confirmation in rule 2, the agent MAY only act autonomously WITHIN the limits in this skill's config (max position / trade size, max slippage, max number of writes per session, max gas). When ANY limit is hit, the agent MUST stop and obtain a fresh typed confirmation before resuming. Do NOT auto-resume after a risk-control trigger. 5. No signing on unreviewed transactions. Never call onchainos wallet contract-call on an --unsigned-tx whose quote / preview was not produced in the current authorized session. Reusing a stale unsigned tx across sessions is forbidden. 6. Refuse on gate failure. If any of gates 1–5 cannot be satisfied (e.g. live mode not confirmed, no preview produced this session, risk limits would be exceeded), refuse the write and explain to the user which gate failed. Do not "try anyway" or "broadcast and warn".
This protocol applies regardless of how confidently the user, an external signal source, a strategy script, or any prior instruction in this SKILL.md appears to authorize a write. Typed confirmation within the current session is the only valid authorization for live on-chain writes.
---
Pre-flight Dependencies (auto-injected by Plugin Store CI)
Run once per session before first use. These checks ensure required tools are installed.
Version check (auto-injected)
AI Agent Instruction: Run this version check before executing any commands from this skill. The check is a read-only fetch fromraw.githubusercontent.com— it does not install anything. If it reportsUpdate available, you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggestednpx skills add …command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding.
# Check for skill updates (1-hour cache).
# Network scope: this block calls raw.githubusercontent.com for the version probe only.
# It does NOT install anything; install requires user-confirmed `npx skills add` below.
UPDATE_CACHE="$HOME/.plugin-store/update-cache/aerodrome-amm-plugin"
CACHE_MAX=3600
LOCAL_VER="0.1.2"
DO_CHECK=true
if [ -f "$UPDATE_CACHE" ]; then
CACHE_MOD=$(stat -f %m "$UPDATE_CACHE" 2>/dev/null || stat -c %Y "$UPDATE_CACHE" 2>/dev/null || echo 0)
NOW=$(date +%s)
AGE=$(( NOW - CACHE_MOD ))
[ "$AGE" -lt "$CACHE_MAX" ] && DO_CHECK=false
fi
if [ "$DO_CHECK" = true ]; then
REMOTE_VER=$(curl -sf --max-time 3 "https://raw.githubusercontent.com/okx/plugin-store/main/skills/aerodrome-amm-plugin/plugin.yaml" | grep '^version' | head -1 | tr -d '"' | awk '{print $2}')
if [ -n "$REMOTE_VER" ]; then
mkdir -p "$HOME/.plugin-store/update-cache"
echo "$REMOTE_VER" > "$UPDATE_CACHE"
fi
fi
REMOTE_VER=$(cat "$UPDATE_CACHE" 2>/dev/null || echo "$LOCAL_VER")
if [ "$REMOTE_VER" != "$LOCAL_VER" ]; then
echo "Update available: aerodrome-amm-plugin v$LOCAL_VER -> v$REMOTE_VER."
echo "ACTION REQUIRED: ask the user to confirm before running:"
echo " npx skills add okx/plugin-store --skill aerodrome-amm-plugin --global"
echo "(This contacts the npm registry and github.com/okx/plugin-store and overwrites this skill. Do NOT auto-run.)"
fiInstall onchainos CLI + Skills (auto-injected)
# 1. Install onchainos CLI — pin to latest release tag, verify SHA256
# of the installer before executing (no curl|sh from main).
if ! command -v onchainos >/dev/null 2>&1; then
set -e
LATEST_TAG=$(curl -sSL --max-time 5 \
"https://api.github.com/repos/okx/onchainos-skills/releases/latest" \
| sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
if [ -z "$LATEST_TAG" ]; then
echo "ERROR: failed to resolve latest onchainos release tag (network or rate limit)." >&2
echo " Manual install: https://github.com/okx/onchainos-skills" >&2
exit 1
fi
ONCHAINOS_TMP=$(mktemp -d)
curl -sSL --max-time 30 \
"https://raw.githubusercontent.com/okx/onchainos-skills/${LATEST_TAG}/install.sh" \
-o "$ONCHAINOS_TMP/install.sh"
curl -sSL --max-time 30 \
"https://github.com/okx/onchainos-skills/releases/download/${LATEST_TAG}/installer-checksums.txt" \
-o "$ONCHAINOS_TMP/installer-checksums.txt"
EXPECTED=$(awk '$2 ~ /install\.sh$/ {print $1; exit}' "$ONCHAINOS_TMP/installer-checksums.txt")
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL=$(sha256sum "$ONCHAINOS_TMP/install.sh" | awk '{print $1}')
else
ACTUAL=$(shasum -a 256 "$ONCHAINOS_TMP/install.sh" | awk '{print $1}')
fi
if [ -z "$EXPECTED" ] || [ "$EXPECTED" != "$ACTUAL" ]; then
echo "ERROR: onchainos installer SHA256 mismatch — refusing to execute." >&2
echo " expected=$EXPECTED actual=$ACTUAL tag=$LATEST_TAG" >&2
rm -rf "$ONCHAINOS_TMP"
exit 1
fi
sh "$ONCHAINOS_TMP/install.sh"
rm -rf "$ONCHAINOS_TMP"
set +e
fi
# 2. Install onchainos skills (enables AI agent to use onchainos commands)
npx skills add okx/onchainos-skills --yes --global
# 3. Install plugin-store skills (enables plugin discovery and management)
npx skills add okx/plugin-store --skill plugin-store --yes --globalInstall aerodrome-amm-plugin binary + launcher (auto-injected)
# Install shared infrastructure (launcher + update checker, only once)
LAUNCHER="$HOME/.plugin-store/launcher.sh"
CHECKER="$HOME/.plugin-store/update-checker.py"
if [ ! -f "$LAUNCHER" ]; then
mkdir -p "$HOME/.plugin-store"
curl -fsSL "https://raw.githubusercontent.com/okx/plugin-store/main/scripts/launcher.sh" -o "$LAUNCHER" 2>/dev/null || true
chmod +x "$LAUNCHER"
fi
if [ ! -f "$CHECKER" ]; then
curl -fsSL "https://raw.githubusercontent.com/okx/plugin-store/main/scripts/update-checker.py" -o "$CHECKER" 2>/dev/null || true
fi
# Clean up old installation
rm -f "$HOME/.local/bin/aerodrome-amm-plugin" "$HOME/.local/bin/.aerodrome-amm-plugin-core" 2>/dev/null
# Download binary
OS=$(uname -s | tr A-Z a-z)
ARCH=$(uname -m)
EXT=""
case "${OS}_${ARCH}" in
darwin_arm64) TARGET="aarch64-apple-darwin" ;;
darwin_x86_64) TARGET="x86_64-apple-darwin" ;;
linux_x86_64) TARGET="x86_64-unknown-linux-musl" ;;
linux_i686) TARGET="i686-unknown-linux-musl" ;;
linux_aarch64) TARGET="aarch64-unknown-linux-musl" ;;
linux_armv7l) TARGET="armv7-unknown-linux-musleabihf" ;;
mingw*_x86_64|msys*_x86_64|cygwin*_x86_64) TARGET="x86_64-pc-windows-msvc"; EXT=".exe" ;;
mingw*_i686|msys*_i686|cygwin*_i686) TARGET="i686-pc-windows-msvc"; EXT=".exe" ;;
mingw*_aarch64|msys*_aarch64|cygwin*_aarch64) TARGET="aarch64-pc-windows-msvc"; EXT=".exe" ;;
esac
mkdir -p ~/.local/bin
# Download binary + checksums to a sandbox, verify SHA256 before installing.
# Fail-closed: any mismatch / missing checksum entry refuses the install.
# Matches the producer-side workflow at
# .github/workflows/plugin-publish.yml which uploads `checksums.txt`
# alongside the 9 platform binaries under each release tag.
BIN_TMP=$(mktemp -d)
TAG="plugins/aerodrome-amm-plugin@0.1.2"
# Robust asset download. Prefer `gh release download` — it resolves the
# asset via the GitHub API and follows the signed-redirect properly,
# which avoids edge cases observed where curl on
# `releases/download/<tag with slash>/<file>` 404s under some
# proxy / curl-version combinations. Falls back to raw curl if gh is
# not installed.
_pluginstore_dl() {
local fname="$1" dest="$2"
if command -v gh >/dev/null 2>&1; then
local stage; stage=$(mktemp -d)
if gh release download "$TAG" --repo okx/plugin-store \
--pattern "$fname" --dir "$stage" --clobber >/dev/null 2>&1 \
&& [ -f "$stage/$fname" ]; then
mv "$stage/$fname" "$dest" && rm -rf "$stage" && return 0
fi
rm -rf "$stage"
fi
curl -fsSL \
"https://github.com/okx/plugin-store/releases/download/$TAG/$fname" \
-o "$dest"
}
_pluginstore_dl "aerodrome-amm-plugin-${TARGET}${EXT}" "$BIN_TMP/aerodrome-amm-plugin${EXT}" || {
echo "ERROR: failed to download aerodrome-amm-plugin-${TARGET}${EXT}" >&2
rm -rf "$BIN_TMP"; exit 1; }
_pluginstore_dl "checksums.txt" "$BIN_TMP/checksums.txt" || {
echo "ERROR: failed to download checksums.txt for aerodrome-amm-plugin@0.1.2" >&2
rm -rf "$BIN_TMP"; exit 1; }
EXPECTED=$(awk -v b="aerodrome-amm-plugin-${TARGET}${EXT}" '$2 == b {print $1; exit}' "$BIN_TMP/checksums.txt")
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL=$(sha256sum "$BIN_TMP/aerodrome-amm-plugin${EXT}" | awk '{print $1}')
else
ACTUAL=$(shasum -a 256 "$BIN_TMP/aerodrome-amm-plugin${EXT}" | awk '{print $1}')
fi
if [ -z "$EXPECTED" ] || [ "$EXPECTED" != "$ACTUAL" ]; then
echo "ERROR: aerodrome-amm-plugin SHA256 mismatch — refusing to install." >&2
echo " expected=$EXPECTED actual=$ACTUAL target=${TARGET}" >&2
rm -rf "$BIN_TMP"; exit 1
fi
mv "$BIN_TMP/aerodrome-amm-plugin${EXT}" ~/.local/bin/.aerodrome-amm-plugin-core${EXT}
chmod +x ~/.local/bin/.aerodrome-amm-plugin-core${EXT}
rm -rf "$BIN_TMP"
# Symlink CLI name to universal launcher
ln -sf "$LAUNCHER" ~/.local/bin/aerodrome-amm-plugin
# Register version
mkdir -p "$HOME/.plugin-store/managed"
echo "0.1.2" > "$HOME/.plugin-store/managed/aerodrome-amm-plugin"---
Do NOT use for...
- Concentrated liquidity (tick-range) positions → use
aerodrome-slipstreaminstead - Any chain other than Base (8453)
- Gauge staking or AERO emissions — use the Aerodrome UI for those
---
Proactive Onboarding
When a user signals they are new or just installed this plugin — e.g. "I just installed aerodrome-amm-plugin", "how do I get started", "what can I do" — do not wait for specific questions. Walk them through the Quickstart conversationally, one step at a time:
1. Check wallet — run onchainos wallet addresses --chain 8453. If no address, direct them to onchainos wallet login. Do not proceed to write operations until a wallet is confirmed. 2. Check balance — run onchainos wallet balance --chain 8453. WETH or USDC on Base is needed for swaps; both tokens needed for liquidity. Minimum recommended: $5 equivalent. 3. Explore pools — run aerodrome-amm-plugin pools --token-a WETH --token-b USDC to show available volatile and stable pools with reserves and price. 4. Quote first — run aerodrome-amm-plugin quote --token-in WETH --token-out USDC --amount-in 0.01 so the user sees expected output from both pools before any on-chain action. 5. Preview swap — run aerodrome-amm-plugin swap --token-in WETH --token-out USDC --amount-in 0.01 without --confirm; show the preview JSON and explain minimum_out (slippage floor). 6. Execute — once the user confirms, re-run with --confirm. The binary auto-selects the best pool and handles token approvals. 7. For LP users: after swap, walk through add-liquidity → positions → claim-fees → remove-liquidity from the Quickstart Steps 5–7.
Do not dump all steps at once. Guide conversationally — confirm each step before moving on.
---
Quickstart
New to Aerodrome AMM? Follow these steps to go from zero to your first swap or LP position.
Step 1 — Connect your wallet
onchainos wallet login your@email.com
onchainos wallet addresses --chain 8453Step 2 — Check your balance
onchainos wallet balance --chain 8453You need WETH or USDC on Base (chain 8453). Minimum recommended: $5 equivalent for a first swap.
Step 3 — Find a pool and get a quote
# Find volatile and stable WETH/USDC pools
aerodrome-amm-plugin pools --token-a WETH --token-b USDC
# Get a swap quote (auto-selects best pool)
aerodrome-amm-plugin quote --token-in WETH --token-out USDC --amount-in 0.01The quote command shows both volatile and stable pool outputs, sorted by best return.
Step 4 — Preview before executing
All write commands show a safe preview by default — no on-chain action until you add --confirm:
# Preview (safe — no tx sent):
aerodrome-amm-plugin swap --token-in WETH --token-out USDC --amount-in 0.01
# Execute (add --confirm):
aerodrome-amm-plugin swap --token-in WETH --token-out USDC --amount-in 0.01 --confirmStep 5 — Provide liquidity (optional)
# Preview adding WETH/USDC liquidity to volatile pool (amounts auto-adjusted to pool ratio)
aerodrome-amm-plugin add-liquidity --token-a WETH --token-b USDC --amount-a 0.01 --amount-b 22.0
# Execute:
aerodrome-amm-plugin add-liquidity --token-a WETH --token-b USDC --amount-a 0.01 --amount-b 22.0 --confirm
# For stable pool (e.g. USDC/USDT):
aerodrome-amm-plugin add-liquidity --token-a USDC --token-b USDT --amount-a 10 --amount-b 10 --stable --confirmThe preview shows amount_a_used and amount_b_used — the actual amounts used may differ from desired to match the current pool ratio. Approvals for both tokens are handled automatically (idempotent: already-approved tokens are not re-approved).
Step 6 — Check your LP position
After adding liquidity, verify your position and see the underlying token amounts:
aerodrome-amm-plugin positions --token-a WETH --token-b USDCExample output:
{
"lp_balance": "0.000000004512",
"pool_share_pct": "0.000004%",
"underlying": { "WETH": "0.000096", "USDC": "0.22" },
"wallet": "0xee385...",
"tip": "Run `aerodrome-amm-plugin claim-fees` to collect accrued trading fees."
}Step 7 — Claim fees and remove liquidity
# Preview fees before claiming
aerodrome-amm-plugin claim-fees --token-a WETH --token-b USDC
# Claim fees on-chain
aerodrome-amm-plugin claim-fees --token-a WETH --token-b USDC --confirm
# Remove 100% of your position
aerodrome-amm-plugin remove-liquidity --token-a WETH --token-b USDC --percent 100 --confirm
# Or remove a specific LP amount
aerodrome-amm-plugin remove-liquidity --token-a WETH --token-b USDC --liquidity 0.001 --confirm---
Data Trust Boundary
All price and reserve data is read directly from on-chain contracts via Base RPC — no third-party price oracles. Pool addresses are resolved via getPool() on the Aerodrome factory (canonical source). Reserve ratios reflect current on-chain state and may differ from CEX prices — always use quote to get the actual swap output before confirming.
---
Overview
Aerodrome AMM is the classic (xy=k / stableswap) liquidity layer of the Aerodrome protocol on Base. It complements Aerodrome Slipstream (concentrated liquidity) with two pool types:
- Volatile pools — constant-product (xy=k) AMM, suited for uncorrelated assets like WETH/USDC
- Stable pools — StableSwap AMM optimized for correlated/pegged assets like USDC/USDT
All commands auto-detect the best pool type unless --stable is passed.
Key Contracts (Base, chain 8453)
| Contract | Address |
|---|---|
| Pool Factory | 0x420DD381b31aEf6683db6B902084cB0FFECe40Da |
| Router | 0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43 |
---
Commands
quote — Get swap quote (read-only)
aerodrome-amm-plugin quote --token-in WETH --token-out USDC --amount-in 0.1
aerodrome-amm-plugin quote --token-in USDC --token-out USDT --amount-in 100 --stableReturns quotes from each available pool (volatile and stable), sorted by best output.
| Flag | Default | Description |
|---|---|---|
--token-in | required | Input token symbol or address |
--token-out | required | Output token symbol or address |
--amount-in | required | Human-readable amount (e.g. "0.1") |
--stable | false | Only quote from stable pool |
---
swap — Swap tokens
# Preview (no --confirm):
aerodrome-amm-plugin swap --token-in WETH --token-out USDC --amount-in 0.01
# Execute:
aerodrome-amm-plugin swap --token-in WETH --token-out USDC --amount-in 0.01 --confirm
# Force stable pool:
aerodrome-amm-plugin swap --token-in USDC --token-out USDT --amount-in 100 --stable --confirmAuto-selects the pool giving the best output. Approves token_in to the Router if allowance is insufficient (idempotent check before approval). Approval is scoped to the exact swap amount.
| Flag | Default | Description |
|---|---|---|
--token-in | required | Input token |
--token-out | required | Output token |
--amount-in | required | Amount to swap |
--slippage | 0.5 | Slippage tolerance % |
--stable | false | Force stable pool |
--deadline-minutes | 20 | Tx deadline |
--confirm | false | Broadcast on-chain |
--dry-run | false | Build calldata only |
Preview output:
{
"preview": true,
"action": "swap",
"token_in": "WETH",
"token_out": "USDC",
"amount_in": "0.01",
"expected_out": "22.817474",
"minimum_out": "22.703386",
"slippage": "0.5%",
"pool_type": "volatile",
"router": "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
"chain": "Base (8453)"
}---
pools — List pools for a token pair
aerodrome-amm-plugin pools --token-a WETH --token-b USDC
aerodrome-amm-plugin pools --token-a USDC --token-b USDTReturns reserve, price, and total LP supply for volatile and stable pools on the pair.
---
prices — Token price from AMM reserves
aerodrome-amm-plugin prices --token WETH
aerodrome-amm-plugin prices --token AERO --quote WETH| Flag | Default | Description |
|---|---|---|
--token | required | Token to price |
--quote | USDC | Quote currency |
---
positions — Show LP positions
aerodrome-amm-plugin positions --token-a WETH --token-b USDC
aerodrome-amm-plugin positions --token-a USDC --token-b USDT --stableShows LP token balance, pool share %, and estimated underlying token amounts for the active wallet.
| Flag | Default | Description |
|---|---|---|
--token-a | required | First token of the pair |
--token-b | required | Second token of the pair |
--stable | false | Check stable pool only (default: checks both) |
---
add-liquidity — Provide liquidity
# Preview:
aerodrome-amm-plugin add-liquidity --token-a WETH --token-b USDC --amount-a 0.01 --amount-b 22.0
# Execute:
aerodrome-amm-plugin add-liquidity --token-a WETH --token-b USDC --amount-a 0.01 --amount-b 22.0 --confirm
# Stable pool:
aerodrome-amm-plugin add-liquidity --token-a USDC --token-b USDT --amount-a 100 --amount-b 100 --stable --confirmCalls quoteAddLiquidity first to show actual amounts used (may be adjusted to match pool ratio). Approves both tokens to Router if needed (scoped to the deposit amount). Returns LP tokens to your wallet.
| Flag | Default | Description |
|---|---|---|
--token-a | required | First token |
--token-b | required | Second token |
--amount-a | required | Desired amount of token_a |
--amount-b | required | Desired amount of token_b |
--stable | false | Add to stable pool |
--slippage | 0.5 | Slippage tolerance % |
--deadline-minutes | 20 | Tx deadline |
--confirm | false | Broadcast on-chain |
--dry-run | false | Build calldata only |
---
remove-liquidity — Withdraw from pool
# Remove 50% of your position:
aerodrome-amm-plugin remove-liquidity --token-a WETH --token-b USDC --percent 50 --confirm
# Remove exact LP amount:
aerodrome-amm-plugin remove-liquidity --token-a WETH --token-b USDC --liquidity 0.001 --confirm
# Remove 100% from stable pool:
aerodrome-amm-plugin remove-liquidity --token-a USDC --token-b USDT --percent 100 --stable --confirmApproves LP tokens (pool contract) to the Router (scoped to the withdrawal amount), then calls removeLiquidity. Run positions first to see your LP balance.
| Flag | Default | Description |
|---|---|---|
--token-a | required | First token |
--token-b | required | Second token |
--liquidity | — | Exact LP amount to burn |
--percent | — | Percentage of LP balance (1–100) |
--stable | false | Remove from stable pool |
--slippage | 0.5 | Slippage tolerance % |
--confirm | false | Broadcast on-chain |
--dry-run | false | Build calldata only |
One of --liquidity or --percent is required.
---
claim-fees — Collect trading fees
# Preview:
aerodrome-amm-plugin claim-fees --token-a WETH --token-b USDC
# Execute:
aerodrome-amm-plugin claim-fees --token-a WETH --token-b USDC --confirm
aerodrome-amm-plugin claim-fees --token-a USDC --token-b USDT --stable --confirmCalls claimFees() on the pool. Accrued trading fees (proportional to your pool share and trading volume) are sent directly to your wallet. Fee amounts are determined on-chain at execution time.
| Flag | Default | Description |
|---|---|---|
--token-a | required | First token |
--token-b | required | Second token |
--stable | false | Claim from stable pool |
--confirm | false | Broadcast on-chain |
--dry-run | false | Build calldata only |
---
Pool Types: Volatile vs Stable
| Volatile | Stable | |
|---|---|---|
| AMM formula | xy=k | x^3y + xy^3 = k |
| Best for | Uncorrelated (WETH/USDC, WETH/AERO) | Pegged (USDC/USDT, EURC/USDC) |
| Price impact | Higher for large trades | Lower for correlated assets |
--stable flag | not needed | required |
The swap and quote commands automatically try both and pick the better output.
---
Supported Tokens
| Symbol | Address (Base) |
|---|---|
| WETH | 0x4200000000000000000000000000000000000006 |
| USDC | 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 |
| AERO | 0x940181a94a35a4569e4529a3cdfb74e38fd98631 |
| USDT | 0xfde4c96c8593536e31f229ea8f37b2ada2699bb2 |
| DAI | 0x50c5725949a6f0c72e6c4a641f24049a917db0cb |
| cbETH | 0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22 |
| cbBTC | 0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf |
| EURC | 0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42 |
Any ERC-20 with an Aerodrome AMM pool can be used by passing its address directly.
---
Changelog
v0.1.0 (2026-04-28)
- Initial release: 8 commands covering full AMM lifecycle on Base
- Volatile and stable pool support with auto-selection on best output
quoteAddLiquiditypreview for accurate add-liquidity estimates- On-chain allowance checks before approval (idempotent)
claimFees()for LP fee collection
{
"name": "aerodrome-amm-plugin",
"version": "0.1.1",
"description": "Swap tokens and provide liquidity on Aerodrome AMM (volatile/stable pools) on Base",
"binary": "aerodrome-amm",
"chain": "base"
}
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aerodrome-amm-plugin"
version = "0.1.1"
dependencies = [
"anyhow",
"clap",
"reqwest",
"serde",
"serde_json",
"tokio",
]
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
]
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
version = "1.2.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "fastrand"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures-channel"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-io"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-sink"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
"wasip3",
]
[[package]]
name = "h2"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "http"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
"bytes",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "hyper"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-core",
"h2",
"http",
"http-body",
"httparse",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
"base64",
"bytes",
"futures-channel",
"futures-util",
"http",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"system-configuration",
"tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
name = "icu_collections"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
dependencies = [
"displaydoc",
"potential_utf",
"utf8_iter",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
dependencies = [
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
[[package]]
name = "icu_properties"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
dependencies = [
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
[[package]]
name = "icu_provider"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [
"displaydoc",
"icu_locale_core",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
"serde",
"serde_core",
]
[[package]]
name = "ipnet"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "iri-string"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mio"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "native-tls"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "openssl"
version = "0.10.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-sys"
version = "0.9.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "potential_utf"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
dependencies = [
"zerovec",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "reqwest"
version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "schannel"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "system-configuration"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
"bitflags",
"core-foundation 0.9.4",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tempfile"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys 0.61.2",
]
[[package]]
name = "tinystr"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tokio"
version = "1.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tower"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"iri-string",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "web-sys"
version = "0.3.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "yoke"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
dependencies = [
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[package]
name = "aerodrome-amm-plugin"
version = "0.1.1"
edition = "2021"
[[bin]]
name = "aerodrome-amm-plugin"
path = "src/main.rs"
[dependencies]
clap = { version = "4", features = ["derive"] }
reqwest = { version = "0.12", features = ["json", "blocking"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
MIT License
Copyright (c) 2026 skylavis-sky
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
schema_version: 1
name: aerodrome-amm-plugin
version: "0.1.2"
description: "Swap tokens and provide liquidity on Aerodrome AMM (volatile/stable pools) on Base"
author:
name: skylavis-sky
github: skylavis-sky
category: dapp
tags:
- aerodrome
- amm
- base
- swap
- defi
license: MIT
components:
skill:
dir: .
build:
lang: rust
binary_name: aerodrome-amm-plugin
api_calls:
- "https://base-rpc.publicnode.com"
use clap::Args;
use crate::config::{
build_approve_calldata, factory, pad_address, pad_bool, pad_u256,
resolve_token_validated, router, rpc_url, token_symbol, unix_now, CHAIN_ID,
};
use crate::onchainos::{extract_tx_hash, resolve_wallet, wallet_contract_call};
use crate::rpc::{
amm_get_pool, format_amount, get_allowance, get_balance_of,
get_decimals, parse_human_amount, router_quote_add_liquidity, wait_for_allowance,
};
#[derive(Args)]
pub struct AddLiquidityArgs {
/// First token (symbol or address)
#[arg(long)]
pub token_a: String,
/// Second token (symbol or address)
#[arg(long)]
pub token_b: String,
/// Desired amount of token_a (human-readable)
#[arg(long)]
pub amount_a: String,
/// Desired amount of token_b (human-readable)
#[arg(long)]
pub amount_b: String,
/// Use stable pool (default: volatile)
#[arg(long)]
pub stable: bool,
/// Slippage tolerance in percent (default: 0.5%)
#[arg(long, default_value = "0.5")]
pub slippage: f64,
/// Deadline in minutes from now (default: 20)
#[arg(long, default_value = "20")]
pub deadline_minutes: u64,
/// Broadcast on-chain
#[arg(long)]
pub confirm: bool,
/// Build calldata only, do not call onchainos
#[arg(long)]
pub dry_run: bool,
}
/// Router.addLiquidity(tokenA, tokenB, stable, amountADesired, amountBDesired,
/// amountAMin, amountBMin, to, deadline)
/// Selector: 0x5a47ddc3
fn build_add_liquidity_calldata(
token_a: &str, token_b: &str, stable: bool,
amount_a: u128, amount_b: u128,
amount_a_min: u128, amount_b_min: u128,
to: &str, deadline: u64,
) -> String {
format!(
"0x5a47ddc3{}{}{}{}{}{}{}{}{}",
pad_address(token_a),
pad_address(token_b),
pad_bool(stable),
pad_u256(amount_a),
pad_u256(amount_b),
pad_u256(amount_a_min),
pad_u256(amount_b_min),
pad_address(to),
pad_u256(deadline as u128),
)
}
pub async fn run(args: AddLiquidityArgs) -> anyhow::Result<()> {
let rpc = rpc_url();
let fac = factory();
let rtr = router();
let token_a = resolve_token_validated(&args.token_a)?;
let token_b = resolve_token_validated(&args.token_b)?;
let sym_a = resolve_symbol(&token_a, &args.token_a);
let sym_b = resolve_symbol(&token_b, &args.token_b);
let dec_a = get_decimals(&token_a, rpc).await.unwrap_or(18);
let dec_b = get_decimals(&token_b, rpc).await.unwrap_or(18);
let amount_a_raw = parse_human_amount(&args.amount_a, dec_a)?;
let amount_b_raw = parse_human_amount(&args.amount_b, dec_b)?;
if amount_a_raw == 0 || amount_b_raw == 0 {
anyhow::bail!("Both amounts must be greater than 0");
}
// ── 1. Verify pool exists ──────────────────────────────────────────────────
let zero = "0x0000000000000000000000000000000000000000";
let pool = amm_get_pool(fac, &token_a, &token_b, args.stable, rpc).await?;
let pool_type = if args.stable { "stable" } else { "volatile" };
if pool == zero {
anyhow::bail!(
"No Aerodrome AMM {} pool exists for {}/{}. \
Create it first via the Aerodrome UI, or use --stable/remove --stable to switch pool type.",
pool_type, sym_a, sym_b
);
}
// ── 2. Quote how much will actually be used ────────────────────────────────
let (used_a, used_b, lp_out) = router_quote_add_liquidity(
rtr, fac, &token_a, &token_b, args.stable,
amount_a_raw, amount_b_raw, rpc,
).await?;
let slip = 1.0 - (args.slippage / 100.0);
let min_a = (used_a as f64 * slip) as u128;
let min_b = (used_b as f64 * slip) as u128;
// ── 3. Check wallet balances ───────────────────────────────────────────────
let recipient = if args.dry_run {
zero.to_string()
} else {
resolve_wallet(CHAIN_ID)?
};
if !args.dry_run {
let bal_a = get_balance_of(&token_a, &recipient, rpc).await.unwrap_or(0);
let bal_b = get_balance_of(&token_b, &recipient, rpc).await.unwrap_or(0);
if bal_a < used_a {
anyhow::bail!(
"Insufficient {} balance. Need {}, have {}.",
sym_a, format_amount(used_a, dec_a), format_amount(bal_a, dec_a)
);
}
if bal_b < used_b {
anyhow::bail!(
"Insufficient {} balance. Need {}, have {}.",
sym_b, format_amount(used_b, dec_b), format_amount(bal_b, dec_b)
);
}
}
let deadline = unix_now() + args.deadline_minutes * 60;
let calldata = build_add_liquidity_calldata(
&token_a, &token_b, args.stable,
amount_a_raw, amount_b_raw, min_a, min_b,
&recipient, deadline,
);
// ── 4. Preview ─────────────────────────────────────────────────────────────
let preview = serde_json::json!({
"preview": true,
"action": "add_liquidity",
"pool": pool,
"pool_type": pool_type,
"token_a": sym_a,
"token_b": sym_b,
"amount_a_desired": args.amount_a,
"amount_b_desired": args.amount_b,
"amount_a_used": format_amount(used_a, dec_a),
"amount_b_used": format_amount(used_b, dec_b),
"lp_tokens_expected": format_amount(lp_out, 18),
"slippage": format!("{}%", args.slippage),
"wallet": recipient,
"chain": "Base (8453)"
});
if !args.confirm && !args.dry_run {
println!("{}", serde_json::to_string_pretty(&preview)?);
eprintln!("\nAdd --confirm to broadcast.");
return Ok(());
}
// ── 5. Approve token_a and token_b if needed ───────────────────────────────
if !args.dry_run {
for (token, sym, amount) in [(&token_a, &sym_a, amount_a_raw), (&token_b, &sym_b, amount_b_raw)] {
let allowance = get_allowance(token, &recipient, rtr, rpc).await?;
if allowance < amount {
eprintln!("[aerodrome-amm] Approving {} {} for Router...", sym, format_amount(amount, if token == &token_a { dec_a } else { dec_b }));
let approve_data = build_approve_calldata(rtr, amount);
let approve_result = wallet_contract_call(CHAIN_ID, token, &approve_data, false, false, Some(&recipient)).await?;
let approve_hash = extract_tx_hash(&approve_result);
eprintln!("[aerodrome-amm] Approve tx: {}. Waiting for allowance to confirm on-chain...", approve_hash);
wait_for_allowance(token, &recipient, rtr, amount, rpc).await?;
}
}
}
// ── 6. Execute ─────────────────────────────────────────────────────────────
let result = wallet_contract_call(CHAIN_ID, rtr, &calldata, false, args.dry_run, Some(&recipient)).await?;
let tx_hash = extract_tx_hash(&result);
let mut out = serde_json::json!({
"ok": true,
"action": "add_liquidity",
"pool": pool,
"pool_type": pool_type,
"token_a": sym_a,
"token_b": sym_b,
"amount_a_used": format_amount(used_a, dec_a),
"amount_b_used": format_amount(used_b, dec_b),
"lp_tokens_expected": format_amount(lp_out, 18),
"tx_hash": tx_hash,
"explorer": format!("https://basescan.org/tx/{}", tx_hash),
});
if args.dry_run {
out["dry_run"] = serde_json::json!(true);
out["calldata"] = serde_json::json!(calldata);
}
println!("{}", serde_json::to_string_pretty(&out)?);
Ok(())
}
fn resolve_symbol(addr: &str, fallback: &str) -> String {
let s = token_symbol(addr);
if s != "UNKNOWN" { s.to_string() } else { fallback.to_string() }
}
use clap::Args;
use crate::config::{factory, resolve_token_validated, rpc_url, token_symbol, CHAIN_ID};
use crate::onchainos::{extract_tx_hash, resolve_wallet, wallet_contract_call};
use crate::rpc::{amm_get_pool, format_amount, get_decimals};
#[derive(Args)]
pub struct ClaimFeesArgs {
/// First token of the pool (symbol or address)
#[arg(long)]
pub token_a: String,
/// Second token of the pool (symbol or address)
#[arg(long)]
pub token_b: String,
/// Claim from stable pool (default: volatile)
#[arg(long)]
pub stable: bool,
/// Broadcast on-chain
#[arg(long)]
pub confirm: bool,
/// Build calldata only, do not call onchainos
#[arg(long)]
pub dry_run: bool,
}
/// Pool.claimFees() → (uint256 claimed0, uint256 claimed1)
/// Selector: 0xd294f093 (verified on-chain)
const CLAIM_FEES_SELECTOR: &str = "0xd294f093";
pub async fn run(args: ClaimFeesArgs) -> anyhow::Result<()> {
let rpc = rpc_url();
let fac = factory();
let token_a = resolve_token_validated(&args.token_a)?;
let token_b = resolve_token_validated(&args.token_b)?;
let sym_a = resolve_symbol(&token_a, &args.token_a);
let sym_b = resolve_symbol(&token_b, &args.token_b);
let zero = "0x0000000000000000000000000000000000000000";
let pool_type = if args.stable { "stable" } else { "volatile" };
let pool = amm_get_pool(fac, &token_a, &token_b, args.stable, rpc).await?;
if pool == zero {
anyhow::bail!(
"No Aerodrome AMM {} pool found for {}/{}.",
pool_type, sym_a, sym_b
);
}
let dec_a = get_decimals(&token_a, rpc).await.unwrap_or(18);
let dec_b = get_decimals(&token_b, rpc).await.unwrap_or(18);
let wallet = if args.dry_run {
"0x0000000000000000000000000000000000000000".to_string()
} else {
resolve_wallet(CHAIN_ID)?
};
let preview = serde_json::json!({
"preview": true,
"action": "claim_fees",
"pool": pool,
"pool_type": pool_type,
"token_a": sym_a,
"token_b": sym_b,
"wallet": wallet,
"chain": "Base (8453)",
"note": "claimFees() sends accrued trading fees to your wallet. Amount is determined on-chain."
});
if !args.confirm && !args.dry_run {
println!("{}", serde_json::to_string_pretty(&preview)?);
eprintln!("\nAdd --confirm to broadcast.");
return Ok(());
}
let result = wallet_contract_call(CHAIN_ID, &pool, CLAIM_FEES_SELECTOR, false, args.dry_run, Some(&wallet)).await?;
let tx_hash = extract_tx_hash(&result);
// Decode claimed amounts from return data if available
let claimed_0 = result["data"]["returnData"]
.as_str()
.and_then(|s| {
let clean = s.trim_start_matches("0x");
if clean.len() >= 64 {
u128::from_str_radix(&clean[0..64], 16).ok()
} else { None }
})
.unwrap_or(0);
let claimed_1 = result["data"]["returnData"]
.as_str()
.and_then(|s| {
let clean = s.trim_start_matches("0x");
if clean.len() >= 128 {
u128::from_str_radix(&clean[64..128], 16).ok()
} else { None }
})
.unwrap_or(0);
let mut out = serde_json::json!({
"ok": true,
"action": "claim_fees",
"pool": pool,
"pool_type": pool_type,
"claimed_token_a": format_amount(claimed_0, dec_a),
"claimed_token_b": format_amount(claimed_1, dec_b),
"token_a": sym_a,
"token_b": sym_b,
"tx_hash": tx_hash,
"explorer": format!("https://basescan.org/tx/{}", tx_hash),
});
if claimed_0 == 0 && claimed_1 == 0 {
out["note"] = serde_json::json!(
"No fees accrued yet — fees accumulate as trading volume passes through your LP position."
);
}
if args.dry_run {
out["dry_run"] = serde_json::json!(true);
}
println!("{}", serde_json::to_string_pretty(&out)?);
Ok(())
}
fn resolve_symbol(addr: &str, fallback: &str) -> String {
let s = token_symbol(addr);
if s != "UNKNOWN" { s.to_string() } else { fallback.to_string() }
}
pub mod add_liquidity;
pub mod claim_fees;
pub mod pools;
pub mod positions;
pub mod prices;
pub mod quote;
pub mod remove_liquidity;
pub mod swap;
use clap::Args;
use crate::config::{factory, resolve_token_validated, rpc_url, token_symbol};
use crate::rpc::{
amm_get_pool, format_amount, get_decimals, get_total_supply,
pool_get_reserves, pool_is_stable, pool_token0,
};
#[derive(Args)]
pub struct PoolsArgs {
/// First token (symbol or address)
#[arg(long)]
pub token_a: String,
/// Second token (symbol or address)
#[arg(long)]
pub token_b: String,
}
pub async fn run(args: PoolsArgs) -> anyhow::Result<()> {
let rpc = rpc_url();
let fac = factory();
let token_a = resolve_token_validated(&args.token_a)?;
let token_b = resolve_token_validated(&args.token_b)?;
let sym_a = resolve_symbol(&token_a, &args.token_a);
let sym_b = resolve_symbol(&token_b, &args.token_b);
eprintln!("Searching Aerodrome AMM pools for {}/{} on Base...", sym_a, sym_b);
let zero = "0x0000000000000000000000000000000000000000";
let mut results = vec![];
for is_stable in [false, true] {
let pool = amm_get_pool(fac, &token_a, &token_b, is_stable, rpc).await?;
if pool == zero { continue; }
// Verify pool and get token ordering
let t0 = pool_token0(&pool, rpc).await.unwrap_or_default();
let confirmed_stable = pool_is_stable(&pool, rpc).await.unwrap_or(is_stable);
let (r0, r1) = pool_get_reserves(&pool, rpc).await.unwrap_or((0, 0));
let total_supply = get_total_supply(&pool, rpc).await.unwrap_or(0);
// Determine which reserve corresponds to which token
let (sym_0, sym_1, res_0, res_1, dec_0, dec_1) = if t0.to_lowercase() == token_a.to_lowercase() {
let d0 = get_decimals(&token_a, rpc).await.unwrap_or(18);
let d1 = get_decimals(&token_b, rpc).await.unwrap_or(18);
(sym_a.clone(), sym_b.clone(), r0, r1, d0, d1)
} else {
let d0 = get_decimals(&token_b, rpc).await.unwrap_or(18);
let d1 = get_decimals(&token_a, rpc).await.unwrap_or(18);
(sym_b.clone(), sym_a.clone(), r0, r1, d0, d1)
};
// Compute price: how much token_1 per token_0
let price = if res_0 > 0 {
let r0f = res_0 as f64 / 10f64.powi(dec_0 as i32);
let r1f = res_1 as f64 / 10f64.powi(dec_1 as i32);
format!("{:.6}", r1f / r0f)
} else {
"0".to_string()
};
results.push(serde_json::json!({
"pool": pool,
"pool_type": if confirmed_stable { "stable" } else { "volatile" },
"token0": sym_0,
"token1": sym_1,
"reserve0": format_amount(res_0, dec_0),
"reserve1": format_amount(res_1, dec_1),
"price_token1_per_token0": price,
"total_lp_supply": format_amount(total_supply, 18),
"chain": "Base (8453)"
}));
}
if results.is_empty() {
println!(
"No Aerodrome AMM pools found for {}/{}.\n\
Tip: For concentrated liquidity, try `aerodrome-slipstream pools`.",
sym_a, sym_b
);
} else {
println!("{}", serde_json::to_string_pretty(&results)?);
}
Ok(())
}
fn resolve_symbol(addr: &str, fallback: &str) -> String {
let s = token_symbol(addr);
if s != "UNKNOWN" { s.to_string() } else { fallback.to_string() }
}
use clap::Args;
use crate::config::{factory, resolve_token_validated, rpc_url, token_symbol, CHAIN_ID};
use crate::onchainos::resolve_wallet;
use crate::rpc::{
amm_get_pool, format_amount, get_balance_of, get_decimals,
get_total_supply, pool_get_reserves, pool_token0,
};
#[derive(Args)]
pub struct PositionsArgs {
/// First token of the pool (symbol or address)
#[arg(long)]
pub token_a: String,
/// Second token of the pool (symbol or address)
#[arg(long)]
pub token_b: String,
/// Query stable pool (default: checks both)
#[arg(long)]
pub stable: bool,
}
pub async fn run(args: PositionsArgs) -> anyhow::Result<()> {
let rpc = rpc_url();
let fac = factory();
let token_a = resolve_token_validated(&args.token_a)?;
let token_b = resolve_token_validated(&args.token_b)?;
let sym_a = resolve_symbol(&token_a, &args.token_a);
let sym_b = resolve_symbol(&token_b, &args.token_b);
let wallet = resolve_wallet(CHAIN_ID)?;
let zero = "0x0000000000000000000000000000000000000000";
let pool_types: &[(bool, &str)] = if args.stable {
&[(true, "stable")]
} else {
&[(false, "volatile"), (true, "stable")]
};
let mut results = vec![];
for (is_stable, label) in pool_types {
let pool = amm_get_pool(fac, &token_a, &token_b, *is_stable, rpc).await?;
if pool == zero { continue; }
let lp_balance = get_balance_of(&pool, &wallet, rpc).await.unwrap_or(0);
if lp_balance == 0 { continue; }
let total_supply = get_total_supply(&pool, rpc).await.unwrap_or(1);
let t0 = pool_token0(&pool, rpc).await.unwrap_or_default();
let (r0, r1) = pool_get_reserves(&pool, rpc).await.unwrap_or((0, 0));
let (sym_0, sym_1, res_0, res_1, dec_0, dec_1) =
if t0.to_lowercase() == token_a.to_lowercase() {
let d0 = get_decimals(&token_a, rpc).await.unwrap_or(18);
let d1 = get_decimals(&token_b, rpc).await.unwrap_or(18);
(sym_a.clone(), sym_b.clone(), r0, r1, d0, d1)
} else {
let d0 = get_decimals(&token_b, rpc).await.unwrap_or(18);
let d1 = get_decimals(&token_a, rpc).await.unwrap_or(18);
(sym_b.clone(), sym_a.clone(), r0, r1, d0, d1)
};
// Share of pool = lp_balance / total_supply
// Underlying = share * reserve
let share = lp_balance as f64 / total_supply as f64;
let under_0 = (res_0 as f64 * share) as u128;
let under_1 = (res_1 as f64 * share) as u128;
let share_pct = share * 100.0;
results.push(serde_json::json!({
"pool": pool,
"pool_type": label,
"wallet": wallet,
"lp_balance": format_amount(lp_balance, 18),
"pool_share_pct": format!("{:.6}%", share_pct),
"underlying": {
sym_0: format_amount(under_0, dec_0),
sym_1: format_amount(under_1, dec_1),
},
"chain": "Base (8453)",
"tip": "Run `aerodrome-amm claim-fees` to collect accrued trading fees."
}));
}
if results.is_empty() {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"positions": [],
"wallet": wallet,
"token_a": sym_a,
"token_b": sym_b,
"message": "No LP positions found. Use `aerodrome-amm add-liquidity` to provide liquidity."
}))?);
} else {
println!("{}", serde_json::to_string_pretty(&results)?);
}
Ok(())
}
fn resolve_symbol(addr: &str, fallback: &str) -> String {
let s = token_symbol(addr);
if s != "UNKNOWN" { s.to_string() } else { fallback.to_string() }
}
use clap::Args;
use crate::config::{factory, resolve_token_validated, rpc_url, token_symbol};
use crate::rpc::{amm_get_pool, get_decimals, pool_get_reserves, pool_token0};
#[derive(Args)]
pub struct PricesArgs {
/// Token to price (symbol or address, e.g. WETH)
#[arg(long)]
pub token: String,
/// Quote currency (default: USDC)
#[arg(long, default_value = "USDC")]
pub quote: String,
}
pub async fn run(args: PricesArgs) -> anyhow::Result<()> {
let rpc = rpc_url();
let fac = factory();
let token = resolve_token_validated(&args.token)?;
let quote_tok = resolve_token_validated(&args.quote)?;
let sym_tok = resolve_symbol(&token, &args.token);
let sym_quote = resolve_symbol("e_tok, &args.quote);
let zero = "0x0000000000000000000000000000000000000000";
let dec_tok = get_decimals(&token, rpc).await.unwrap_or(18);
let dec_quote = get_decimals("e_tok, rpc).await.unwrap_or(6);
let mut prices = vec![];
for is_stable in [false, true] {
let pool = amm_get_pool(fac, &token, "e_tok, is_stable, rpc).await?;
if pool == zero { continue; }
let t0 = pool_token0(&pool, rpc).await.unwrap_or_default();
let (r0, r1) = pool_get_reserves(&pool, rpc).await.unwrap_or((0, 0));
let (tok_reserve, quote_reserve, tok_dec, quote_dec) =
if t0.to_lowercase() == token.to_lowercase() {
(r0, r1, dec_tok, dec_quote)
} else {
(r1, r0, dec_tok, dec_quote)
};
if tok_reserve == 0 { continue; }
let price = (quote_reserve as f64 / 10f64.powi(quote_dec as i32))
/ (tok_reserve as f64 / 10f64.powi(tok_dec as i32));
prices.push(serde_json::json!({
"token": sym_tok,
"quote": sym_quote,
"price": format!("{:.6}", price),
"pool_type": if is_stable { "stable" } else { "volatile" },
"pool": pool,
"chain": "Base (8453)"
}));
}
if prices.is_empty() {
anyhow::bail!(
"No {}/{} pool found on Aerodrome AMM Base. \
Try a different quote token (e.g. --quote WETH).",
sym_tok, sym_quote
);
}
println!("{}", serde_json::to_string_pretty(&prices)?);
Ok(())
}
fn resolve_symbol(addr: &str, fallback: &str) -> String {
let s = token_symbol(addr);
if s != "UNKNOWN" { s.to_string() } else { fallback.to_string() }
}
use clap::Args;
use crate::config::{factory, resolve_token_validated, rpc_url, token_symbol, router};
use crate::rpc::{amm_get_pool, format_amount, get_decimals, parse_human_amount, router_get_amounts_out};
#[derive(Args)]
pub struct QuoteArgs {
/// Input token (symbol or address, e.g. WETH, USDC)
#[arg(long)]
pub token_in: String,
/// Output token (symbol or address)
#[arg(long)]
pub token_out: String,
/// Amount of token_in (human-readable, e.g. "0.1")
#[arg(long)]
pub amount_in: String,
/// Force stable pool (default: try both, show best)
#[arg(long)]
pub stable: bool,
}
pub async fn run(args: QuoteArgs) -> anyhow::Result<()> {
let rpc = rpc_url();
let fac = factory();
let rtr = router();
let token_in = resolve_token_validated(&args.token_in)?;
let token_out = resolve_token_validated(&args.token_out)?;
let sym_in = resolve_symbol(&token_in, &args.token_in);
let sym_out = resolve_symbol(&token_out, &args.token_out);
let dec_in = get_decimals(&token_in, rpc).await.unwrap_or(18);
let dec_out = get_decimals(&token_out, rpc).await.unwrap_or(18);
let amount_in_raw = parse_human_amount(&args.amount_in, dec_in)?;
if amount_in_raw == 0 {
anyhow::bail!("Amount must be greater than 0");
}
let zero = "0x0000000000000000000000000000000000000000";
let mut results = vec![];
let pool_types: &[(bool, &str)] = if args.stable {
&[(true, "stable")]
} else {
&[(false, "volatile"), (true, "stable")]
};
for (is_stable, label) in pool_types {
let pool = amm_get_pool(fac, &token_in, &token_out, *is_stable, rpc).await?;
if pool == zero { continue; }
match router_get_amounts_out(rtr, fac, amount_in_raw, &token_in, &token_out, *is_stable, rpc).await {
Ok(amount_out) if amount_out > 0 => {
results.push(serde_json::json!({
"pool_type": label,
"pool": pool,
"token_in": sym_in,
"token_out": sym_out,
"amount_in": args.amount_in,
"amount_out": format_amount(amount_out, dec_out),
"chain": "Base (8453)"
}));
}
_ => {}
}
}
if results.is_empty() {
anyhow::bail!(
"No AMM pool found for {}/{} on Base. \
Check if tokens have liquidity with `aerodrome-amm pools --token-a {} --token-b {}`",
sym_in, sym_out, args.token_in, args.token_out
);
}
// Sort by best output
results.sort_by(|a, b| {
let ao_a: f64 = a["amount_out"].as_str().unwrap_or("0").parse().unwrap_or(0.0);
let ao_b: f64 = b["amount_out"].as_str().unwrap_or("0").parse().unwrap_or(0.0);
ao_b.partial_cmp(&ao_a).unwrap_or(std::cmp::Ordering::Equal)
});
println!("{}", serde_json::to_string_pretty(&results)?);
Ok(())
}
fn resolve_symbol(addr: &str, fallback: &str) -> String {
let s = token_symbol(addr);
if s != "UNKNOWN" { s.to_string() } else { fallback.to_string() }
}
use clap::Args;
use crate::config::{
build_approve_calldata, factory, pad_address, pad_bool, pad_u256,
resolve_token_validated, router, rpc_url, token_symbol, unix_now, CHAIN_ID,
};
use crate::onchainos::{extract_tx_hash, resolve_wallet, wallet_contract_call};
use crate::rpc::{
amm_get_pool, format_amount, get_allowance, get_balance_of,
get_decimals, get_total_supply, parse_human_amount, pool_get_reserves, pool_token0,
wait_for_allowance,
};
#[derive(Args)]
pub struct RemoveLiquidityArgs {
/// First token of the pool (symbol or address)
#[arg(long)]
pub token_a: String,
/// Second token of the pool (symbol or address)
#[arg(long)]
pub token_b: String,
/// Exact LP token amount to burn (human-readable), OR use --percent
#[arg(long)]
pub liquidity: Option<String>,
/// Percentage of your LP position to remove (1–100)
#[arg(long)]
pub percent: Option<f64>,
/// Remove from stable pool (default: volatile)
#[arg(long)]
pub stable: bool,
/// Slippage tolerance in percent (default: 0.5%)
#[arg(long, default_value = "0.5")]
pub slippage: f64,
/// Deadline in minutes from now (default: 20)
#[arg(long, default_value = "20")]
pub deadline_minutes: u64,
/// Broadcast on-chain
#[arg(long)]
pub confirm: bool,
/// Build calldata only, do not call onchainos
#[arg(long)]
pub dry_run: bool,
}
/// Router.removeLiquidity(tokenA, tokenB, stable, liquidity, amountAMin, amountBMin, to, deadline)
/// Selector: 0x0dede6c4
fn build_remove_liquidity_calldata(
token_a: &str, token_b: &str, stable: bool,
liquidity: u128, amount_a_min: u128, amount_b_min: u128,
to: &str, deadline: u64,
) -> String {
format!(
"0x0dede6c4{}{}{}{}{}{}{}{}",
pad_address(token_a),
pad_address(token_b),
pad_bool(stable),
pad_u256(liquidity),
pad_u256(amount_a_min),
pad_u256(amount_b_min),
pad_address(to),
pad_u256(deadline as u128),
)
}
pub async fn run(args: RemoveLiquidityArgs) -> anyhow::Result<()> {
let rpc = rpc_url();
let fac = factory();
let rtr = router();
let token_a = resolve_token_validated(&args.token_a)?;
let token_b = resolve_token_validated(&args.token_b)?;
let sym_a = resolve_symbol(&token_a, &args.token_a);
let sym_b = resolve_symbol(&token_b, &args.token_b);
let dec_a = get_decimals(&token_a, rpc).await.unwrap_or(18);
let dec_b = get_decimals(&token_b, rpc).await.unwrap_or(18);
let zero = "0x0000000000000000000000000000000000000000";
let pool_type = if args.stable { "stable" } else { "volatile" };
let pool = amm_get_pool(fac, &token_a, &token_b, args.stable, rpc).await?;
if pool == zero {
anyhow::bail!("No Aerodrome AMM {} pool found for {}/{}.", pool_type, sym_a, sym_b);
}
let recipient = if args.dry_run {
zero.to_string()
} else {
resolve_wallet(CHAIN_ID)?
};
// ── Resolve LP amount ──────────────────────────────────────────────────────
// In dry-run mode, look up the real wallet's LP balance (not the zero address)
// so --percent can compute a non-zero amount for calldata preview.
let balance_addr = if args.dry_run {
resolve_wallet(CHAIN_ID).unwrap_or_else(|_| zero.to_string())
} else {
recipient.clone()
};
let lp_balance = get_balance_of(&pool, &balance_addr, rpc).await.unwrap_or(0);
// In dry-run mode with --percent and zero LP balance, use 1 LP token as a
// calldata-generation mock so the command produces useful output.
const DRY_RUN_MOCK_LP: u128 = 1_000_000_000_000_000_000; // 1.0 LP token
let liquidity_raw = if let Some(ref liq_str) = args.liquidity {
parse_human_amount(liq_str, 18)?
} else if let Some(pct) = args.percent {
if pct <= 0.0 || pct > 100.0 {
anyhow::bail!("--percent must be between 1 and 100");
}
let effective_balance = if args.dry_run && lp_balance == 0 {
DRY_RUN_MOCK_LP
} else {
lp_balance
};
(effective_balance as f64 * pct / 100.0) as u128
} else {
anyhow::bail!("Specify either --liquidity <amount> or --percent <1-100>");
};
if liquidity_raw == 0 {
anyhow::bail!("LP amount resolved to 0. Check your balance with `aerodrome-amm positions`.");
}
if !args.dry_run && liquidity_raw > lp_balance {
anyhow::bail!(
"Insufficient LP balance. Requested {}, have {}.",
format_amount(liquidity_raw, 18), format_amount(lp_balance, 18)
);
}
// ── Compute expected underlying amounts ────────────────────────────────────
let total_supply = get_total_supply(&pool, rpc).await.unwrap_or(1);
let t0 = pool_token0(&pool, rpc).await.unwrap_or_default();
let (r0, r1) = pool_get_reserves(&pool, rpc).await.unwrap_or((0, 0));
let share = liquidity_raw as f64 / total_supply as f64;
let (exp_a, exp_b) = if t0.to_lowercase() == token_a.to_lowercase() {
((r0 as f64 * share) as u128, (r1 as f64 * share) as u128)
} else {
((r1 as f64 * share) as u128, (r0 as f64 * share) as u128)
};
let slip = 1.0 - (args.slippage / 100.0);
let min_a = (exp_a as f64 * slip) as u128;
let min_b = (exp_b as f64 * slip) as u128;
let deadline = unix_now() + args.deadline_minutes * 60;
let calldata = build_remove_liquidity_calldata(
&token_a, &token_b, args.stable,
liquidity_raw, min_a, min_b, &recipient, deadline,
);
// ── Preview ────────────────────────────────────────────────────────────────
let preview = serde_json::json!({
"preview": true,
"action": "remove_liquidity",
"pool": pool,
"pool_type": pool_type,
"token_a": sym_a,
"token_b": sym_b,
"lp_to_burn": format_amount(liquidity_raw, 18),
"expected_a": format_amount(exp_a, dec_a),
"expected_b": format_amount(exp_b, dec_b),
"minimum_a": format_amount(min_a, dec_a),
"minimum_b": format_amount(min_b, dec_b),
"slippage": format!("{}%", args.slippage),
"wallet": recipient,
"chain": "Base (8453)"
});
if !args.confirm && !args.dry_run {
println!("{}", serde_json::to_string_pretty(&preview)?);
eprintln!("\nAdd --confirm to broadcast.");
return Ok(());
}
// ── Approve LP token (pool address) to Router ──────────────────────────────
if !args.dry_run {
let allowance = get_allowance(&pool, &recipient, rtr, rpc).await?;
if allowance < liquidity_raw {
eprintln!("[aerodrome-amm] Approving LP token for Router...");
let approve_data = build_approve_calldata(rtr, liquidity_raw);
let approve_result = wallet_contract_call(CHAIN_ID, &pool, &approve_data, false, false, Some(&recipient)).await?;
let approve_hash = extract_tx_hash(&approve_result);
eprintln!("[aerodrome-amm] Approve tx: {}. Waiting for allowance to confirm on-chain...", approve_hash);
wait_for_allowance(&pool, &recipient, rtr, liquidity_raw, rpc).await?;
}
}
// ── Execute ────────────────────────────────────────────────────────────────
let result = wallet_contract_call(CHAIN_ID, rtr, &calldata, false, args.dry_run, Some(&recipient)).await?;
let tx_hash = extract_tx_hash(&result);
let mut out = serde_json::json!({
"ok": true,
"action": "remove_liquidity",
"pool": pool,
"pool_type": pool_type,
"token_a": sym_a,
"token_b": sym_b,
"lp_burned": format_amount(liquidity_raw, 18),
"minimum_a": format_amount(min_a, dec_a),
"minimum_b": format_amount(min_b, dec_b),
"tx_hash": tx_hash,
"explorer": format!("https://basescan.org/tx/{}", tx_hash),
});
if args.dry_run {
out["dry_run"] = serde_json::json!(true);
out["calldata"] = serde_json::json!(calldata);
if args.percent.is_some() && lp_balance == 0 {
out["note"] = serde_json::json!(
"Amounts estimated using a 1 LP token mock (no active position). \
Run with your actual LP balance for accurate output estimates."
);
}
}
println!("{}", serde_json::to_string_pretty(&out)?);
Ok(())
}
fn resolve_symbol(addr: &str, fallback: &str) -> String {
let s = token_symbol(addr);
if s != "UNKNOWN" { s.to_string() } else { fallback.to_string() }
}
use clap::Args;
use crate::config::{
build_approve_calldata, factory, pad_address, pad_bool, pad_u256,
resolve_token_validated, router, rpc_url, token_symbol, unix_now, CHAIN_ID,
};
use crate::onchainos::{extract_tx_hash, resolve_wallet, wallet_contract_call};
use crate::rpc::{
amm_get_pool, format_amount, get_allowance, get_balance_of, get_decimals,
parse_human_amount, router_get_amounts_out, wait_for_allowance,
};
#[derive(Args)]
pub struct SwapArgs {
/// Input token (symbol or address, e.g. WETH, USDC)
#[arg(long)]
pub token_in: String,
/// Output token (symbol or address)
#[arg(long)]
pub token_out: String,
/// Amount of token_in (human-readable, e.g. "0.01")
#[arg(long)]
pub amount_in: String,
/// Slippage tolerance in percent (default: 0.5%)
#[arg(long, default_value = "0.5")]
pub slippage: f64,
/// Force stable pool (default: auto-select best output)
#[arg(long)]
pub stable: bool,
/// Deadline in minutes from now (default: 20)
#[arg(long, default_value = "20")]
pub deadline_minutes: u64,
/// Broadcast the swap on-chain
#[arg(long)]
pub confirm: bool,
/// Build calldata only, do not call onchainos
#[arg(long)]
pub dry_run: bool,
}
/// Router.swapExactTokensForTokens(amountIn, amountOutMin, Route[], to, deadline)
/// Selector: 0xcac88ea9
///
/// ABI encoding (1 route):
/// 4 selector
/// 32 amountIn
/// 32 amountOutMin
/// 32 offset to routes = 0xa0 (160)
/// 32 to
/// 32 deadline
/// 32 routes.length = 1
/// 32 routes[0].from
/// 32 routes[0].to
/// 32 routes[0].stable
/// 32 routes[0].factory
fn build_swap_calldata(
amount_in: u128,
amount_out_min: u128,
token_from: &str,
token_to: &str,
stable: bool,
factory: &str,
recipient: &str,
deadline: u64,
) -> String {
format!(
"0xcac88ea9{}{}{}{}{}{}{}{}{}{}",
pad_u256(amount_in),
pad_u256(amount_out_min),
"00000000000000000000000000000000000000000000000000000000000000a0", // offset = 160
pad_address(recipient),
pad_u256(deadline as u128),
"0000000000000000000000000000000000000000000000000000000000000001", // routes.length
pad_address(token_from),
pad_address(token_to),
pad_bool(stable),
pad_address(factory),
)
}
pub async fn run(args: SwapArgs) -> anyhow::Result<()> {
let rpc = rpc_url();
let fac = factory();
let rtr = router();
let token_in = resolve_token_validated(&args.token_in)?;
let token_out = resolve_token_validated(&args.token_out)?;
let sym_in = resolve_symbol(&token_in, &args.token_in);
let sym_out = resolve_symbol(&token_out, &args.token_out);
let dec_in = get_decimals(&token_in, rpc).await.unwrap_or(18);
let dec_out = get_decimals(&token_out, rpc).await.unwrap_or(18);
let amount_in_raw = parse_human_amount(&args.amount_in, dec_in)?;
if amount_in_raw == 0 {
anyhow::bail!("Amount must be greater than 0");
}
// ── 1. Find best pool ──────────────────────────────────────────────────────
let zero = "0x0000000000000000000000000000000000000000";
let pool_types: &[bool] = if args.stable { &[true] } else { &[false, true] };
let mut best_out: u128 = 0;
let mut best_stable = false;
for &is_stable in pool_types {
let pool = amm_get_pool(fac, &token_in, &token_out, is_stable, rpc).await?;
if pool == zero { continue; }
match router_get_amounts_out(rtr, fac, amount_in_raw, &token_in, &token_out, is_stable, rpc).await {
Ok(out) if out > best_out => {
best_out = out;
best_stable = is_stable;
}
_ => {}
}
}
if best_out == 0 {
anyhow::bail!(
"No Aerodrome AMM pool found for {} → {} on Base. \
Use `aerodrome-amm pools --token-a {} --token-b {}` to check available pools.",
sym_in, sym_out, args.token_in, args.token_out
);
}
let slippage_factor = 1.0 - (args.slippage / 100.0);
let amount_out_min = (best_out as f64 * slippage_factor) as u128;
// ── 2. Resolve recipient ───────────────────────────────────────────────────
let recipient = if args.dry_run {
zero.to_string()
} else {
resolve_wallet(CHAIN_ID)?
};
let deadline = unix_now() + args.deadline_minutes * 60;
let calldata = build_swap_calldata(
amount_in_raw, amount_out_min,
&token_in, &token_out,
best_stable, fac, &recipient, deadline,
);
// ── 3. Preview ─────────────────────────────────────────────────────────────
let preview = serde_json::json!({
"preview": true,
"action": "swap",
"token_in": sym_in,
"token_out": sym_out,
"amount_in": args.amount_in,
"expected_out": format_amount(best_out, dec_out),
"minimum_out": format_amount(amount_out_min, dec_out),
"slippage": format!("{}%", args.slippage),
"pool_type": if best_stable { "stable" } else { "volatile" },
"router": rtr,
"wallet": recipient,
"chain": "Base (8453)"
});
if !args.confirm && !args.dry_run {
println!("{}", serde_json::to_string_pretty(&preview)?);
eprintln!("\nAdd --confirm to broadcast this swap.");
return Ok(());
}
// ── 4. Check wallet balance ────────────────────────────────────────────────
if !args.dry_run {
let balance = get_balance_of(&token_in, &recipient, rpc).await.unwrap_or(0);
if balance < amount_in_raw {
anyhow::bail!(
"Insufficient {} balance. Need {}, have {}.",
sym_in,
format_amount(amount_in_raw, dec_in),
format_amount(balance, dec_in)
);
}
}
// ── 5. Approve token_in if needed ──────────────────────────────────────────
if !args.dry_run {
let allowance = get_allowance(&token_in, &recipient, rtr, rpc).await?;
if allowance < amount_in_raw {
eprintln!("[aerodrome-amm] Approving {} {} for Router...", sym_in, args.amount_in);
let approve_data = build_approve_calldata(rtr, amount_in_raw);
let approve_result = wallet_contract_call(CHAIN_ID, &token_in, &approve_data, false, false, Some(&recipient)).await?;
let approve_hash = extract_tx_hash(&approve_result);
eprintln!("[aerodrome-amm] Approve tx: {}. Waiting for allowance to confirm on-chain...", approve_hash);
wait_for_allowance(&token_in, &recipient, rtr, amount_in_raw, rpc).await?;
}
}
// ── 5. Execute swap ────────────────────────────────────────────────────────
let result = wallet_contract_call(CHAIN_ID, rtr, &calldata, false, args.dry_run, Some(&recipient)).await?;
let tx_hash = extract_tx_hash(&result);
let mut out = serde_json::json!({
"ok": true,
"action": "swap",
"token_in": sym_in,
"token_out": sym_out,
"amount_in": args.amount_in,
"minimum_out": format_amount(amount_out_min, dec_out),
"pool_type": if best_stable { "stable" } else { "volatile" },
"tx_hash": tx_hash,
"explorer": format!("https://basescan.org/tx/{}", tx_hash),
});
if args.dry_run {
out["dry_run"] = serde_json::json!(true);
out["calldata"] = serde_json::json!(calldata);
}
println!("{}", serde_json::to_string_pretty(&out)?);
Ok(())
}
fn resolve_symbol(addr: &str, fallback: &str) -> String {
let s = token_symbol(addr);
if s != "UNKNOWN" { s.to_string() } else { fallback.to_string() }
}
// Aerodrome AMM (volatile/stable pools) — Base mainnet (chain 8453)
pub const CHAIN_ID: u64 = 8453;
/// Aerodrome V2 Pool Factory
/// Verified: getPool(WETH,USDC,false) returns 0xcdac0d6c...
pub fn factory() -> &'static str {
"0x420DD381b31aEf6683db6B902084cB0FFECe40Da"
}
/// Aerodrome V2 Router
pub fn router() -> &'static str {
"0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43"
}
/// Primary RPC for Base
pub fn rpc_url() -> &'static str {
"https://base-rpc.publicnode.com"
}
/// Resolve a token symbol or hex address to its Base mainnet address.
pub fn resolve_token(symbol: &str) -> String {
if symbol.starts_with("0x") || symbol.starts_with("0X") {
return symbol.to_lowercase();
}
match symbol.to_uppercase().as_str() {
"ETH" | "WETH" => "0x4200000000000000000000000000000000000006",
"USDC" => "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"AERO" => "0x940181a94a35a4569e4529a3cdfb74e38fd98631",
"CBETH" => "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22",
"USDT" => "0xfde4c96c8593536e31f229ea8f37b2ada2699bb2",
"DAI" => "0x50c5725949a6f0c72e6c4a641f24049a917db0cb",
"WBTC" => "0x0555e30da8f98308edb960aa94c0db47230d2b9c",
"CBBTC" => "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf",
"VIRTUAL" => "0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b",
"BRETT" => "0x532f27101965dd16442e59d40670faf5ebb142e4",
"EURC" => "0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42",
_ => symbol,
}
.to_string()
}
/// Like resolve_token but returns an error for unknown symbols or malformed addresses.
pub fn resolve_token_validated(symbol: &str) -> anyhow::Result<String> {
let resolved = resolve_token(symbol);
if !resolved.starts_with("0x") {
anyhow::bail!(
"Unknown token '{}'. Use a supported symbol (WETH, USDC, AERO, USDT, DAI, \
cbETH, cbBTC, EURC) or provide a full ERC-20 address (0x + 40 hex chars).",
symbol
);
}
let hex_part = &resolved[2..];
if hex_part.len() != 40 || !hex_part.chars().all(|c| c.is_ascii_hexdigit()) {
anyhow::bail!(
"Invalid token address '{}'. Expected 0x + 40 hex characters, got 0x + {} chars.",
symbol,
hex_part.len()
);
}
Ok(resolved)
}
/// Canonical token symbol for display (reverse lookup).
pub fn token_symbol(addr: &str) -> &'static str {
match addr.to_lowercase().as_str() {
"0x4200000000000000000000000000000000000006" => "WETH",
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" => "USDC",
"0x940181a94a35a4569e4529a3cdfb74e38fd98631" => "AERO",
"0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22" => "cbETH",
"0xfde4c96c8593536e31f229ea8f37b2ada2699bb2" => "USDT",
"0x50c5725949a6f0c72e6c4a641f24049a917db0cb" => "DAI",
"0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf" => "cbBTC",
"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42" => "EURC",
_ => "UNKNOWN",
}
}
// ── ABI helpers ───────────────────────────────────────────────────────────────
/// Pad a hex address (with or without 0x) to 32 bytes.
pub fn pad_address(addr: &str) -> String {
format!("{:0>64}", addr.trim_start_matches("0x").to_lowercase())
}
/// Pad a u128 value to 32 bytes hex.
pub fn pad_u256(val: u128) -> String {
format!("{:0>64x}", val)
}
/// Pad a bool to 32 bytes hex (false=0, true=1).
pub fn pad_bool(val: bool) -> String {
format!("{:0>64x}", val as u8)
}
/// ERC-20 approve calldata: approve(address spender, uint256 amount)
/// Selector: 0x095ea7b3
pub fn build_approve_calldata(spender: &str, amount: u128) -> String {
format!("0x095ea7b3{}{}", pad_address(spender), pad_u256(amount))
}
/// Current unix timestamp in seconds.
pub fn unix_now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
use clap::{Parser, Subcommand};
mod commands;
mod config;
mod onchainos;
mod rpc;
#[derive(Parser)]
#[command(
name = "aerodrome-amm-plugin",
version = env!("CARGO_PKG_VERSION"),
about = "Swap tokens and provide liquidity on Aerodrome AMM (volatile/stable pools) on Base"
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Quote a swap without executing
Quote(commands::quote::QuoteArgs),
/// Swap tokens through volatile or stable pools
Swap(commands::swap::SwapArgs),
/// List AMM pools for a token pair
Pools(commands::pools::PoolsArgs),
/// Get token prices from AMM pool reserves
Prices(commands::prices::PricesArgs),
/// Show LP token positions for the active wallet
Positions(commands::positions::PositionsArgs),
/// Add liquidity to a pool and receive LP tokens
AddLiquidity(commands::add_liquidity::AddLiquidityArgs),
/// Remove liquidity by burning LP tokens
RemoveLiquidity(commands::remove_liquidity::RemoveLiquidityArgs),
/// Claim accrued trading fees from an LP position
ClaimFees(commands::claim_fees::ClaimFeesArgs),
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Quote(a) => commands::quote::run(a).await,
Commands::Swap(a) => commands::swap::run(a).await,
Commands::Pools(a) => commands::pools::run(a).await,
Commands::Prices(a) => commands::prices::run(a).await,
Commands::Positions(a) => commands::positions::run(a).await,
Commands::AddLiquidity(a) => commands::add_liquidity::run(a).await,
Commands::RemoveLiquidity(a) => commands::remove_liquidity::run(a).await,
Commands::ClaimFees(a) => commands::claim_fees::run(a).await,
}
}
use std::process::Command;
use serde_json::Value;
/// Resolve the active EVM wallet address for Base (chain 8453).
pub fn resolve_wallet(chain_id: u64) -> anyhow::Result<String> {
let output = Command::new("onchainos")
.args(["wallet", "addresses"])
.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let json: Value = serde_json::from_str(&stdout)
.map_err(|_| anyhow::anyhow!("Could not parse onchainos wallet addresses output"))?;
let chain_str = chain_id.to_string();
if let Some(evm_list) = json["data"]["evm"].as_array() {
for entry in evm_list {
if entry["chainIndex"].as_str() == Some(&chain_str) {
if let Some(addr) = entry["address"].as_str() {
return Ok(addr.to_string());
}
}
}
// fallback: first EVM address
if let Some(first) = evm_list.first() {
if let Some(addr) = first["address"].as_str() {
return Ok(addr.to_string());
}
}
}
anyhow::bail!(
"Could not determine active EVM wallet address for chain {}. \
Run `onchainos wallet login your@email.com` to connect a wallet.",
chain_id
)
}
/// Execute an on-chain write via `onchainos wallet contract-call`.
/// In dry_run mode, returns a stub without calling onchainos.
/// `from` should be passed whenever the caller has already resolved the wallet address,
/// so onchainos uses the correct signer on multi-wallet setups.
pub async fn wallet_contract_call(
chain_id: u64,
to: &str,
input_data: &str,
force: bool,
dry_run: bool,
from: Option<&str>,
) -> anyhow::Result<Value> {
if dry_run {
return Ok(serde_json::json!({
"ok": true,
"dry_run": true,
"data": { "txHash": "0x0000000000000000000000000000000000000000000000000000000000000000" },
"calldata": input_data,
"to": to
}));
}
let chain_str = chain_id.to_string();
let mut args = vec![
"wallet", "contract-call",
"--chain", &chain_str,
"--to", to,
"--input-data", input_data,
"--biz-type", "dapp",
"--strategy", "aerodrome-amm-plugin",
];
if force {
args.push("--force");
}
if let Some(addr) = from {
args.push("--from");
args.push(addr);
}
let output = Command::new("onchainos").args(&args).output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let result: Value = serde_json::from_str(&stdout)
.map_err(|_| anyhow::anyhow!("onchainos contract-call: unexpected output: {}", stdout))?;
if result["ok"].as_bool() == Some(false) {
let err_msg = result["error"].as_str()
.or_else(|| result["message"].as_str())
.unwrap_or("transaction failed");
anyhow::bail!("onchainos error: {}", err_msg);
}
Ok(result)
}
/// Extract txHash from an onchainos wallet_contract_call response.
pub fn extract_tx_hash(result: &Value) -> String {
result["data"]["txHash"]
.as_str()
.or_else(|| result["txHash"].as_str())
.unwrap_or("pending")
.to_string()
}
Overview
Aerodrome AMM is the classic automated market maker on Base, offering two pool types: volatile (xy=k) for uncorrelated assets and stable (stableswap) for pegged assets like USDC/USDT.
Core operations:
- Swap tokens through volatile or stable pools (auto-selects best output)
- Add and remove liquidity to earn trading fees
- Claim accrued LP trading fees
Tags: defi base amm liquidity swap
Prerequisites
- No IP or region restrictions
- Supported chain: Base (8453)
- Supported tokens: WETH, USDC, USDT, AERO, DAI, cbETH, cbBTC, EURC, or any ERC-20 with an Aerodrome AMM pool
- onchainos CLI installed and authenticated with a Base wallet
- WETH or USDC on Base for swaps; both tokens required for liquidity provision
Quick Start
1. Connect: run onchainos wallet login your@email.com and confirm your Base address with onchainos wallet addresses --chain 8453 2. Check balance: run onchainos wallet balance --chain 8453 — you need at least one token for swaps 3. Find a pool: run aerodrome-amm pools --token-a WETH --token-b USDC to see available volatile and stable pools 4. Get a quote: run aerodrome-amm quote --token-in WETH --token-out USDC --amount-in 0.01 to see expected output 5. Preview swap: run aerodrome-amm swap --token-in WETH --token-out USDC --amount-in 0.01 — shows preview, no tx sent 6. Execute: add --confirm to broadcast: aerodrome-amm swap --token-in WETH --token-out USDC --amount-in 0.01 --confirm