fix(etherfi-plugin): fix approve race condition, JSON positions output, missing weETHExpected (v0.2.7)#212
Conversation
…t, missing weETHExpected (v0.2.7)
Root causes and fixes:
- EVM-006: wrap/unstake used sleep(3s/15s) after approve — insufficient for 12s avg Ethereum
block time. Replaced with wait_for_tx() polling loop (onchainos wallet history, up to 90s).
- EVM-002: positions printed a human text table — unreadable by agents. Replaced with
structured serde_json output (eeth_balance, weeth_balance, weeth_as_eeth, total_eeth,
total_usd, rate, apy_pct, tvl_usd, eth_price_usd).
- EVM-012: positions used unwrap_or(0) for balance/rate — silently returned 0 on RPC failure.
Changed to fail-fast with clear error messages.
- wrap: missing weETHExpected field in output — added via getRate() (weETH = eETH / rate).
- unwrap: eETHExpected showed too many decimals — changed to 6dp via format!("{:.6}", ...).
Affected commands: positions, wrap, unstake, unwrap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🔨 Phase 2: Build Verification — ✅ PASSED
Build succeeded. Compiled artifact uploaded as workflow artifact. Source integrity: commit SHA `` is the content fingerprint. |
Phase 4: Summary + Pre-flight for
|
| Command | Description |
|---|---|
etherfi positions [--owner ADDRESS] |
View eETH/weETH balances, total value, and current APY |
etherfi stake --amount ETH [--confirm] |
Deposit ETH to receive eETH (minimum 0.001 ETH) |
etherfi wrap --amount EETH [--confirm] |
Wrap eETH into weETH (ERC-4626 yield-bearing token) |
etherfi unwrap --amount WEETH [--confirm] |
Unwrap weETH back to eETH |
etherfi unstake --amount EETH [--confirm] |
Request eETH withdrawal (step 1: burns eETH, mints NFT) |
etherfi unstake --claim --token-id ID [--confirm] |
Claim ETH after withdrawal finalization (step 2: burns NFT) |
Triggers
AI agents should activate this skill when users want to participate in Ethereum liquid restaking, earn staking + EigenLayer rewards through weETH, or manage existing ether.fi positions. Use for ETH staking with liquidity, yield optimization through ERC-4626 tokens, or checking ether.fi portfolio balances and APY.
Auto-injected Pre-flight
## 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. If an update is applied, re-read this SKILL.md before proceeding — the instructions may have changed.
```bash
# Check for skill updates (1-hour cache)
UPDATE_CACHE="$HOME/.plugin-store/update-cache/etherfi-plugin"
CACHE_MAX=3600
LOCAL_VER="0.2.7"
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/etherfi-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: etherfi-plugin v$LOCAL_VER -> v$REMOTE_VER. Updating..."
npx skills add okx/plugin-store --skill etherfi-plugin --yes --global 2>/dev/null || true
echo "Updated etherfi-plugin to v$REMOTE_VER. Please re-read this SKILL.md."
fiInstall onchainos CLI + Skills (auto-injected)
# 1. Install onchainos CLI
onchainos --version 2>/dev/null || curl -fsSL https://raw.githubusercontent.com/okx/onchainos-skills/main/install.sh | sh
# 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 etherfi-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/etherfi-plugin" "$HOME/.local/bin/.etherfi-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
curl -fsSL "https://github.com/okx/plugin-store/releases/download/plugins/etherfi-plugin@0.2.7/etherfi-plugin-${TARGET}${EXT}" -o ~/.local/bin/.etherfi-plugin-core${EXT}
chmod +x ~/.local/bin/.etherfi-plugin-core${EXT}
# Symlink CLI name to universal launcher
ln -sf "$LAUNCHER" ~/.local/bin/etherfi-plugin
# Register version
mkdir -p "$HOME/.plugin-store/managed"
echo "0.2.7" > "$HOME/.plugin-store/managed/etherfi-plugin"Report install (auto-injected, runs once)
REPORT_FLAG="$HOME/.plugin-store/reported/etherfi-plugin"
if [ ! -f "$REPORT_FLAG" ]; then
mkdir -p "$HOME/.plugin-store/reported"
# Device fingerprint → SHA256 → 32-char device ID
DEV_RAW="$(hostname):$(uname -s):$(uname -m):$HOME"
DEV_ID=$(echo -n "$DEV_RAW" | shasum -a 256 | head -c 32)
# HMAC signature (obfuscated key, same as CLI binary)
_K=$(echo 'OE9nNWFRUFdfSVJkektrMExOV2RNeTIzV2JibXo3ZWNTbExJUDFIWnVoZw==' | base64 -d 2>/dev/null || echo 'OE9nNWFRUFdfSVJkektrMExOV2RNeTIzV2JibXo3ZWNTbExJUDFIWnVoZw==' | openssl base64 -d)
HMAC_SIG=$(echo -n "${_K}${DEV_ID}" | shasum -a 256 | head -c 8)
DIV_ID="${DEV_ID}${HMAC_SIG}"
unset _K
# Report to Vercel stats
curl -s -X POST "https://plugin-store-dun.vercel.app/install" \
-H "Content-Type: application/json" \
-d '{"name":"etherfi-plugin","version":"0.2.7"}' >/dev/null 2>&1 || true
# Report to OKX API (with HMAC-signed device token)
curl -s -X POST "https://www.okx.com/priapi/v1/wallet/plugins/download/report" \
-H "Content-Type: application/json" \
-d '{"pluginName":"etherfi-plugin","divId":"'"$DIV_ID"'"}' >/dev/null 2>&1 || true
touch "$REPORT_FLAG"
fi
</details>
---
*Generated by Plugin Store CI after maintainer approval.*
📋 Phase 3: AI Code Review Report — Score: 82/100
1. Plugin Overview
Summary: This plugin provides liquid restaking functionality for ether.fi on Ethereum mainnet. Users can deposit ETH to receive eETH, wrap/unwrap eETH↔weETH (ERC-4626), unstake eETH back to ETH via a two-step withdrawal process, and check positions with APY data. All write operations route through Target Users: DeFi users who want to participate in ether.fi's liquid restaking protocol to earn staking + EigenLayer restaking rewards, managed through an AI agent interface. 2. Architecture AnalysisComponents:
Skill Structure:
Data Flow:
Dependencies:
3. Auto-Detected Permissionsonchainos Commands Used
Wallet Operations
External APIs / URLs
Chains Operated On
Overall Permission SummaryThis plugin performs high-risk financial operations: depositing ETH, approving unlimited ERC-20 spending (u128::MAX), wrapping/unwrapping tokens, and requesting/claiming withdrawals. All write operations are routed through 4. onchainos API ComplianceDoes this plugin use onchainos CLI for all on-chain write operations?Yes — all write operations use On-Chain Write Operations (MUST use onchainos)
Data Queries (allowed to use external sources)
External APIs / Libraries Detected
Verdict: ✅ Fully CompliantAll on-chain write operations use 5. Security AssessmentStatic Rule Scan (C01-C09, H01-H09, M01-M08, L01-L02)
LLM Judge Analysis (L-PINJ, L-MALI, L-MEMA, L-IINJ, L-AEXE, L-FINA, L-FISO)
Toxic Flow Detection (TF001-TF006)No toxic flows detected:
Prompt Injection ScanNo instruction overrides, identity manipulation, hidden behavior, confirmation bypass, or unauthorized operations found. No base64-encoded payloads or invisible characters in SKILL.md or source code. The SKILL.md is clean technical documentation with appropriate security notices. Result: ✅ Clean Dangerous Operations CheckThe plugin involves:
All operations have explicit user confirmation via Result: Data Exfiltration RiskNo data exfiltration detected. The plugin:
Result: ✅ No Risk Overall Security Rating: 🟡 Medium RiskThe medium risk is due to: (1) high-value financial operations involving ETH deposits and token approvals, (2) 6. Source Code Security (if source code is included)Language & Build Config
Dependency Analysis
All dependencies are well-known, actively maintained Rust ecosystem crates. No suspicious or unmaintained packages detected. Code Safety Audit
Does SKILL.md accurately describe what the source code does?Yes — the SKILL.md accurately describes all 5 commands, their parameters, the two-step confirmation flow, the approval mechanism, error handling, and contract addresses. The ABI selectors in SKILL.md match those in Verdict: ✅ Source Safe7. Code ReviewQuality Score: 82/100
Strengths
Issues Found
8. Recommendations
9. Reviewer SummaryOne-line verdict: Well-architected DeFi plugin with proper onchainos integration, good confirmation gates, and solid error handling; main concern is u128::MAX token approvals and a minor documentation inconsistency. Merge recommendation: Caveats to address or accept:
Generated by Claude AI via Anthropic API — review the full report before approving. |
Summary
wrapandunstakecommands usedsleep(3s/15s)after the approve tx — not sufficient for Ethereum's ~12s average block time. Replaced withwait_for_tx()polling loop that checksonchainos wallet history --tx-hashevery 3s (up to 90s timeout) and only proceeds oncetxStatus: SUCCESS.positionsprinted a human-readable text table. Replaced with structuredserde_json::json!output:eeth_balance,weeth_balance,weeth_as_eeth,total_eeth,total_usd,rate,apy_pct,tvl_usd,eth_price_usd.positionsusedunwrap_or(0)for balance and exchange rate — returned0silently when RPC was down. Changed to fail-fast with explicit error messages.weETHExpectedfield in output — computed viagetRate()(weETH = eETH / rate).eETHExpectedshowed 18 decimal places. Changed to 6dp viaformat!("{:.6}", ...).Files changed
src/onchainos.rs— addedwait_for_tx()async +wait_for_tx_sync()polling implsrc/commands/wrap.rs— replaced sleep withwait_for_tx, addedweETHExpectedsrc/commands/unstake.rs— replaced sleep withwait_for_txsrc/commands/positions.rs— full rewrite: text table → JSON, fail-fast on balance/rate errorssrc/commands/unwrap.rs— fixedeETHExpecteddecimal precisionSKILL.md,plugin.yaml,Cargo.toml,.claude-plugin/plugin.json— bumped to v0.2.7Test plan
etherfi positionsreturns valid JSON with all 13 output fieldsetherfi wrap --amount 0.1 --confirmwaits for approve confirmation before calling wrapetherfi unstake --amount 0.1 --confirmwaits for approve confirmation before requesting withdrawaletherfi wrap --amount 0.1(preview) showsweETHExpectedfieldetherfi unwrap --amount 0.1(preview) showseETHExpectedwith 6 decimal placesetherfi positionswith RPC down returns clear error (not0balances)🤖 Generated with Claude Code