fix(pendle): v0.2.3 — confirm gate, approval race, balance checks, output fix#185
Conversation
🔨 Phase 2: Build Verification — ✅ PASSED
Build succeeded. Compiled artifact uploaded as workflow artifact. Source integrity: commit SHA `` is the content fingerprint. |
✅ Phase 1: Structure Validation — PASSED→ Proceeding to Phase 2: Build Verification |
Phase 4: Summary + Pre-flight for
|
| Command | Purpose |
|---|---|
list-markets |
Browse available Pendle markets and pools |
get-market |
Get detailed market information and APY history |
get-positions |
View current Pendle token holdings and positions |
get-asset-price |
Check prices for PT, YT, LP, or SY tokens |
buy-pt |
Purchase Principal Tokens for fixed yield exposure |
sell-pt |
Sell Principal Tokens back to underlying assets |
buy-yt |
Buy Yield Tokens for floating yield speculation |
sell-yt |
Sell Yield Tokens back to underlying assets |
add-liquidity |
Provide single-token liquidity to Pendle AMM |
remove-liquidity |
Withdraw liquidity from Pendle AMM pools |
mint-py |
Mint PT+YT pairs from underlying tokens |
redeem-py |
Redeem PT+YT pairs back to underlying assets |
Triggers
Activate this skill when users mention Pendle-related operations like "buy PT", "sell YT", "Pendle fixed yield", "add Pendle liquidity", "mint PT YT", "Pendle positions", or "Pendle markets". The skill should also trigger for yield trading, fixed yield strategies, and liquidity provision discussions related to Pendle Finance.
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/pendle-plugin"
CACHE_MAX=3600
LOCAL_VER="0.2.3"
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/pendle-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: pendle-plugin v$LOCAL_VER -> v$REMOTE_VER. Updating..."
npx skills add okx/plugin-store --skill pendle-plugin --yes --global 2>/dev/null || true
echo "Updated pendle-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 pendle-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/pendle-plugin" "$HOME/.local/bin/.pendle-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/pendle-plugin@0.2.3/pendle-plugin-${TARGET}${EXT}" -o ~/.local/bin/.pendle-plugin-core${EXT}
chmod +x ~/.local/bin/.pendle-plugin-core${EXT}
# Symlink CLI name to universal launcher
ln -sf "$LAUNCHER" ~/.local/bin/pendle-plugin
# Register version
mkdir -p "$HOME/.plugin-store/managed"
echo "0.2.3" > "$HOME/.plugin-store/managed/pendle-plugin"Report install (auto-injected, runs once)
REPORT_FLAG="$HOME/.plugin-store/reported/pendle-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":"pendle-plugin","version":"0.2.3"}' >/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":"pendle-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: 78/100
1. Plugin Overview
Summary: This plugin provides a CLI interface to Pendle Finance's yield tokenization protocol. It enables users to buy/sell Principal Tokens (PT) and Yield Tokens (YT), add/remove AMM liquidity, and mint/redeem PT+YT pairs across Ethereum, Arbitrum, BSC, and Base. Write operations generate calldata via Pendle's hosted SDK API and submit transactions through onchainos wallet contract-call. Target Users: DeFi users who want to interact with Pendle Finance for yield trading, fixed-yield positions, and liquidity provisioning through an AI agent. 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 has significant on-chain write capabilities. It can execute ERC-20 token approvals and arbitrary contract calls via 4. onchainos API ComplianceDoes this plugin use onchainos CLI for all on-chain write operations?Yes — all on-chain write operations go through 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 onchainos CLI. Data queries appropriately use external Pendle APIs. 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)
Prompt Injection Scan
Result: ✅ Clean Dangerous Operations Check
Result: Data Exfiltration Risk
Result: ✅ No Risk Overall Security Rating: 🟡 Medium RiskThe primary risk factors are: (1) all write operations use 6. Source Code Security (if source code is included)Language & Build Config
Dependency AnalysisKey dependencies (all from crates.io):
No suspicious, unmaintained, or known-vulnerable dependencies detected. All are mainstream Rust ecosystem crates. Code Safety Audit
Does SKILL.md accurately describe what the source code does?Yes — the SKILL.md accurately describes:
Verdict: ✅ Source Safe7. Code ReviewQuality Score: 78/100
Strengths
Issues Found
8. Recommendations
9. Reviewer SummaryOne-line verdict: Well-implemented Pendle Finance DeFi plugin with proper onchainos integration and security-conscious design (exact approvals, dry-run flow, data trust boundaries), but the blanket use of Merge recommendation: The plugin is functional and follows most best practices. The following items should be noted for users:
Generated by Claude AI via Anthropic API — review the full report before approving. |
…s, approval race condition
- Add --confirm global flag to all 8 write commands (buy-pt, sell-pt, buy-yt, sell-yt,
add-liquidity, remove-liquidity, mint-py, redeem-py): without --confirm the command
calls the Pendle SDK for a real quote then returns {"preview":true} with no on-chain
action, preventing accidental transaction submission
- Fix approval race condition: wait_for_tx now called after each ERC-20 approve tx before
submitting the router call, ensuring the approval is mined first
- Fix get-market --time-frame values in SKILL.md: API accepts hour|day|week not 1D|1W|1M
- Update SKILL.md with three-mode execution table (preview / dry-run / --confirm live)
- Fix --dry-run docs: flag is global (before subcommand), not per-command
- Add ETH pool guidance: use WETH address for PT/YT pools, not native ETH
… validation Balance pre-checks (all 8 write commands): - Added erc20_balance_of() to onchainos.rs — direct eth_call balanceOf, avoids SDK round-trip - Each command checks wallet balance against required amount before calling Pendle SDK - redeem-py checks both PT and YT balances independently - Guard skips during --dry-run (offline mode); active for preview and --confirm SDK calldata validation (api.rs): - validate_sdk_calldata() called inside extract_sdk_calldata() on every write path - Rejects calldata shorter than 4 bytes or containing non-hex characters - Rejects router_to addresses not in Pendle Router v3 / known aggregator whitelist - Rejects selectors matching ERC-20/ERC-721 drain operations (transfer, transferFrom, approve, setApprovalForAll, safeTransferFrom) Addresses Phase 3 AI Code Review recommendations #1 and okx#6. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…h ETH hint sell-pt / sell-yt: - Extract priceImpact from Pendle SDK response (routes[0].data.priceImpact) - Surface as price_impact_pct in both preview and execution output - Add warning field when price impact exceeds 1% threshold - Addresses "磨损很大" report from live sell-pt testing list-markets --search: - New optional --search flag: filters results by market name, PT/YT/SY symbol - Fetches 100 results when search is active for wider client-side coverage - Empty result for ETH/WETH search surfaces hint to try liquid staking derivatives (weETH, wstETH, rETH, rsETH, ezETH, sfrxETH, cbETH) - Addresses "first search found nothing, second found pools" discovery gap Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…amounts M1 — list-markets ignores global --chain (all 8 write commands used it correctly, list-markets didn't): - Forward global --chain as default --chain-id in list-markets dispatch - pendle --chain 42161 list-markets now correctly returns Arbitrum markets M2 — --search eth emits no hint when results found: - Hint now fires when ETH/WETH search returns results (not just zero results) - Message clarifies these are ETH liquid staking/restaking derivative pools - Zero-result case still fires the "try weETH, wstETH, rETH..." discovery hint M3 — buy-pt (and all write commands) missing expected output amount: - Added extract_amount_out() to api.rs: tries netPtOut, netYtOut, netLpOut, netTokenOut, amountOut, outputAmount from routes[0].data - Added expected_*_out field to both preview and execution output for all 8 write commands: expected_pt_out, expected_yt_out, expected_lp_out, expected_token_out, expected_py_out Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sage
M1 — extract_amount_out always returned null:
Pendle SDK v3 places output amount at routes[0].outputs[0].amount, not
routes[0].data.*. The existing code only checked routes[0].data.{netPtOut,
netYtOut, ...} which are not present in v3 responses (data only contains
aggregatorType, priceImpact, fee). Fixed by checking outputs[0].amount
first, keeping data.* fields as fallback for older SDK shapes.
M2 — price impact warning fires misleadingly on small cross-asset sells:
The Pendle SDK priceImpact is a relative deviation vs the pool's theoretical
rate, not a USD value loss. For cross-asset routes (e.g. PT → WETH via
aggregator), small amounts can show 60%+ "impact" even when the trade is
profitable. Changes:
- Threshold raised 1% → 5% to reduce false-positive warnings.
- Warning message updated to clarify the metric is relative to pool rate,
and directs the user to verify expected_token_out before confirming.
- SKILL.md updated to match (threshold + context note).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a 5-step Quickstart (connect wallet → browse markets → buy PT → check positions → sell PT) so new users can get started without reading the full docs. Also adds inline note explaining price_impact_pct semantics in sell-pt Step 5. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sell-pt and sell-yt: "Insufficient balance" error now mentions --dry-run as a way to preview pricing without holding the token (new-user UX fix surfaced during explore-plugin testing). SKILL.md: - Fix stale `warning (if impact >1%)` → `>5%` in sell-pt/sell-yt output field lists (4 occurrences) - Add Key Concepts entries explaining price_impact_pct is a percentage value (not raw) and expected_*_out fields are in wei - Update troubleshooting table rows for PT/YT balance errors to mention --dry-run workaround Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Ten fixes addressing real-money failure modes, supply-chain risk, and UX gaps discovered during post-release testing, Phase 3 AI Code Review, and regression testing:
1. Critical: No confirmation gate — write commands executed immediately (all 8 write commands)
Bug: All 8 write commands called onchainos and submitted transactions immediately — no preview, no confirmation step.
Fix: Added
--confirmglobal flag. Without--confirm, the binary calls the Pendle Hosted SDK for a real quote and returns{"preview": true, ...}. Only with--confirmdoes it proceed to approvals and the router call."preview":true. No on-chain action.--dry-run(global flag)--confirm(global flag)2. Critical: Approval → main tx race condition (all 8 write commands)
Bug: After
erc20_approvebroadcast, the Pendle router tx fired immediately before the approval confirmed on-chain — reverted withERC20: transfer amount exceeds allowance.Fix: Added
wait_for_tx(tx_hash, rpc_url)— pollseth_getTransactionReceiptup to 20×2s. Called after everyerc20_approve.3. Major: Wrong
--time-framevalues in SKILL.md (get-market)Bug: SKILL.md documented
1D|1W|1M; Pendle API acceptshour|day|week.Fix: Updated all
get-marketexamples and docs.4. Dry-run documentation + ETH pool guidance (
SKILL.md)5. Wallet balance pre-checks before SDK call (all 8 write commands) — Phase 3 Rec #6
Added:
erc20_balance_of(chain_id, token, wallet)inonchainos.rs— directeth_callbefore calling the Pendle Hosted SDK. Each command checks the relevant token balance against the required amount.redeem-pychecks both PT and YT independently.Guard is active for preview and
--confirm; skipped during--dry-run.6. SDK calldata validation after
sdk_convertresponse — Phase 3 Rec #1Added:
validate_sdk_calldata(calldata, router_to)inapi.rs, called insideextract_sdk_calldata():router_tois in Pendle Router v3 / known DEX aggregator whitelist7.
list-markets --searchwith ETH/WETH discovery hintBug: Agent searching for "ETH" pools got no results (all ETH pools are named after derivatives), then had to retry with "weETH" etc.
Fix: Added
--searchflag tolist-markets. Client-side filters by market name, PT/YT/SY symbol. When search = "eth" or "weth" returns empty, surfaces an actionable hint:8.
expected_*_outalways null — SDK v3 response path fix (sell-pt,sell-yt,buy-pt,buy-yt,add-liquidity,remove-liquidity,mint-py,redeem-py)Bug:
expected_token_out/expected_pt_out/expected_lp_outwere alwaysnullin both preview and execution output.Root cause:
extract_amount_out()looked for the output amount inroutes[0].data.{netPtOut, netYtOut, ...}— but Pendle SDK v3 only puts{aggregatorType, priceImpact, priceImpactBreakDown, fee}underroutes[0].data. The actual output amount is atroutes[0].outputs[0].amount.Fix: Added primary check at
routes[0].outputs[0].amountinapi.rs. Old field names kept as fallback for older SDK shapes.9. Sell high-slippage warning — threshold fix + message rewrite (
sell-pt,sell-yt)Bug: A user reported seeing a large slippage warning ("磨损很大") when selling PT via a cross-asset route (PT → WETH via aggregator). The warning was a false positive — the trade was profitable but the SDK's
priceImpactfield appeared extremely high.Root cause: Pendle SDK's
priceImpactis a relative deviation vs the pool's theoretical exchange rate, not a USD loss metric. For cross-asset routes involving a DEX aggregator leg, the value can be 50%+ even on profitable trades with small amounts.Fixes:
10. Quickstart section added to SKILL.md
Added a 5-step onboarding guide (connect wallet → browse markets → buy PT → check positions → sell PT) with inline price impact explanation in Step 5 so new users can get started without reading the full docs.
Files Changed
src/main.rs--confirmglobal flag;--searchon list-marketssrc/onchainos.rswait_for_tx,default_rpc_url,erc20_balance_ofsrc/api.rsvalidate_sdk_calldata,extract_price_impact;extract_amount_outSDK v3 path fixsrc/commands/list_markets.rs--searchwith client-side filter + ETH/WETH hintsrc/commands/sell_pt.rsconfirmparam; preview gate; wait after approve; balance check; price impact (threshold 5%, semantic message)src/commands/sell_yt.rssrc/commands/buy_pt.rsconfirmparam; preview gate; wait after approve; balance checksrc/commands/buy_yt.rssrc/commands/add_liquidity.rssrc/commands/remove_liquidity.rssrc/commands/mint_py.rssrc/commands/redeem_py.rsSKILL.md--confirmdocs; three-mode table; time-frame fix; ETH pool guidance; Quickstart sectionLive Verification
The two user-reported issues (fix 8: expected output always null; fix 9: false-positive slippage warning) and the approval race condition fix (fix 2) were verified end-to-end with a live
--confirmtransaction on Arbitrum mainnet:Command:
Result:
{ "ok": true, "operation": "sell-pt", "chain_id": 42161, "approve_txs": ["0x7e91e51b60c3ee92af1774a1e26c27ffe02a8c7a9741509f98bfcb26247c6b55"], "expected_token_out": "8263775352966", "price_impact_pct": "0.01", "wallet": "0xee385ac7ac70b5e7f12aa49bf879a441bed0bae9", "tx_hash": "0x596016ceaa71760c7fd641d325f9742ea7d32833f6e0e7ce8a8d20ffd34d2551", "dry_run": false }approve_txspopulated; router tx succeeded (no allowance revert)expected_token_outnot null"8263775352966"present in execution outputwarningfield in output"preview":true;--confirmexecuted on-chainChecklist
skills/pendle-plugin/files in diff🤖 Generated with Claude Code