Skip to content

feat(puffer-plugin): Puffer Finance liquid restaking (v0.1.0)#30

Merged
plugin-store-bot[bot] merged 4 commits intomig-pre:mainfrom
GeoGu360:feat/puffer-plugin
Apr 29, 2026
Merged

feat(puffer-plugin): Puffer Finance liquid restaking (v0.1.0)#30
plugin-store-bot[bot] merged 4 commits intomig-pre:mainfrom
GeoGu360:feat/puffer-plugin

Conversation

@GeoGu360
Copy link
Copy Markdown
Owner

Summary

  • New plugin for Puffer Finance (pufETH) liquid restaking on Ethereum mainnet
  • 8 commands covering full lifecycle: stake ETH → pufETH, and two exit paths
    (1-step instant with live exit fee, 2-step queued fee-free ~14d)
  • Every write command emits structured JSON to stdout with a stable error_code
    and gas_check object so external agents can branch deterministically
  • Broadcast & confirmation fully delegated to onchainos (`wallet contract-call` +
    `wallet history` polling); gas price / gas-limit / native + ERC-20 balance for
    the connected wallet also go through `onchainos gateway gas` /
    `gateway gas-limit` / `wallet balance`. Custom contract view functions
    (convertToAssets, getMaxWithdrawalAmount, getWithdrawal, allowance, …) use
    direct eth_call since onchainos has no generic view channel.

Commands

Command Type Purpose
`positions` read pufETH balance, ETH equivalent, rate, exit-fee, APY
`rate` read live rate + queue stats (finalized batch, total requests)
`withdraw-options` read compare 1-step vs 2-step for a given amount
`withdraw-status --id` read PENDING / CLAIMABLE / ALREADY_CLAIMED / OUT_OF_RANGE
`stake --amount` write `PufferVault.depositETH(receiver)` payable
`request-withdraw --amount` write `PufferWithdrawalManager.requestWithdrawal` (auto-approve + wait)
`claim-withdraw --id` write `PufferWithdrawalManager.completeQueuedWithdrawal`
`instant-withdraw --amount` write `PufferVault.redeem` (pays live exit fee)

Pre-flight checks on every write command

  • Input asset balance (pufETH via `onchainos wallet balance --token-address`)
  • Vault liquidity (`maxRedeem`) for instant-withdraw
  • Per-request max (`getMaxWithdrawalAmount`) for request-withdraw
  • Minimum amount (0.01 pufETH) for request-withdraw
  • Gas budget: `onchainos gateway gas-limit` × `gateway gas` × 1.2 buffer, vs
    `onchainos wallet balance` native ETH. Bails with INSUFFICIENT_GAS before
    touching broadcast.
  • Revert simulation (onchainos surfaces the exact revert reason)

Error codes (stable for agents)

`INSUFFICIENT_BALANCE`, `INSUFFICIENT_GAS`, `WITHDRAWAL_AMOUNT_TOO_LOW`,
`WITHDRAWAL_AMOUNT_TOO_HIGH`, `WITHDRAWAL_NOT_FINALIZED`,
`WITHDRAWAL_ALREADY_CLAIMED`, `WITHDRAWAL_OUT_OF_RANGE`, `TX_WILL_REVERT`,
`TX_CONFIRMATION_TIMEOUT`, `RPC_ERROR`, `UNKNOWN_ERROR`.

Test plan

  • `cargo build` clean (zero warnings)
  • 8 commands `--help` + `--dry-run` all exercised against live mainnet RPC
    — rate 1.074330, exit_fee 100 bps, finalized_batch 1220, 12,283 requests
  • Read commands return accurate values: `positions` / `rate` /
    `withdraw-options` math checks (0.3 pufETH → 0.322299 gross, 0.319076 after 1% fee)
  • Error paths: insufficient balance, insufficient gas, below min, above max,
    already claimed, not finalized, out of range — each maps to the right
    `error_code` and `suggestion`
  • `wait_for_tx` placed after every broadcast (no stale post-tx reads)
  • `api_calls` whitelist covers all three external domains in use
  • SKILL.md / plugin.yaml / Cargo.toml / plugin.json version all `0.1.0`
  • Flag consistency: every `--flag` in SKILL.md exists in `--help`
  • Live mainnet broadcast (deferred to reviewer with funded wallet)

Future versions

  • v0.2.x: stETH / wstETH deposit via `PufferDepositor` (EIP-2612 permit);
    WETH deposit path on `stake`
  • v0.3.x: auto-enumerate a wallet's pending `withdrawal_id`s via event scan
  • v0.x.x: BNB Chain read-only `xPufETH` balance

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 23, 2026

🔨 Phase 2: Build Verification — ✅ PASSED

Plugin: puffer-plugin | Language: rust
Source: @

Compiled from developer source code by our CI. Users install our build artifacts.

Build succeeded. Compiled artifact uploaded as workflow artifact.


Source integrity: commit SHA `` is the content fingerprint.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 23, 2026

📋 Phase 3: AI Code Review Report — Score: N/A/100

Plugin: puffer-plugin | Recommendation: 👤 Manual review required

🔗 Reviewed against latest onchainos source code (live from main branch) | Model: unavailable via Anthropic API | Cost: N/A

This is an advisory report. It does NOT block merging. Final decision is made by human reviewers.


AI review FAILED (HTTP 400): Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.. Request size: 1069501 bytes, plugin content: 156445 bytes.


Generated by Claude AI via Anthropic API — review the full report before approving.

@github-actions
Copy link
Copy Markdown

🔨 Phase 2: Build Verification — ✅ PASSED

Plugin: puffer-plugin | Language: rust
Source: @

Compiled from developer source code by our CI. Users install our build artifacts.

Build succeeded. Compiled artifact uploaded as workflow artifact.


Source integrity: commit SHA `` is the content fingerprint.

GeoGu360 pushed a commit to GeoGu360/plugin-store that referenced this pull request Apr 28, 2026
… command (CI E151 + ONB-001)

CI Phase 1 reported 3 errors + 1 warning on PR mig-pre#30:
  - [E151] SUMMARY.md missing required section: '## Overview'
  - [E151] SUMMARY.md missing required section: '## Prerequisites'
  - [E151] SUMMARY.md missing required section: '## Quick Start'
  - [W010] description is 208 chars (recommended < 200)

Root cause: SUMMARY.md was using a non-standard `# puffer` + `## Highlights`
layout — pre-dates the now-mandatory three-section template. plugin.yaml
description was also too long.

While fixing, also discovered puffer-plugin was missing the `quickstart`
command altogether — a violation of the new ONB-001 三件套 mandatory
structure (quickstart command + SUMMARY.md + LICENSE). Without quickstart,
SUMMARY's "step 1 = quickstart" rule can't be satisfied either.

Changes:
1. Added src/commands/quickstart.rs — 4 status enum (rpc_degraded /
   no_funds / ready_to_stake / has_pufeth_earning); parallel reads of
   ETH balance + pufETH balance + rate + APY + ETH price; returns
   ready-to-run next_command per status.
2. Wired quickstart into commands/mod.rs and main.rs (with import,
   enum variant, dispatch arm).
3. Rewrote SUMMARY.md to three-section template (Overview /
   Prerequisites / Quick Start), with step 1 = quickstart and steps
   2..7 mapping to status enum values + the queued-withdrawal flow
   for completeness.
4. Shortened plugin.yaml description from 221 to 192 chars (W010 fix).

Verified locally:
- cargo build: clean
- puffer-plugin quickstart: returns status=ready_to_stake with
  ETH=0.0935 + APY=3.03% + next_command="puffer-plugin stake --amount
  0.05 --confirm"
- All Step 1.6 staleness checks pass: SUMMARY 三段式 ✓, 第 1 步
  quickstart ✓, status enum src↔doc 一致 ✓, LICENSE present ✓
- description 192 chars (< 200) ✓
- 9 commands now: quickstart + 8 existing

Reuses existing PR mig-pre#30 — no new PR opened.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GeoGu360 pushed a commit to GeoGu360/plugin-store that referenced this pull request Apr 29, 2026
Phase 3 local AI review (PR mig-pre#30) findings:

1. Non-ASCII chars in 4 metadata files where ASCII is mandated by
   skill-md-maintenance-guide §0.7 (CI lint.rs:946 panics on em-dash):
     plugin.yaml:                  1 em-dash
     .claude-plugin/plugin.json:   1 em-dash
     SKILL.md frontmatter:         2 (em-dash + LEFT-RIGHT-ARROW '<->')
     SUMMARY.md:                   6 em-dashes
   Replace U+2014 -> '-', U+2194 -> '<->', U+2192 -> '->', U+2265 -> '>=',
   U+2264 -> '<=', U+2026 -> '...'. SKILL.md body left untouched.

2. 7 hardcoded function selectors in src/calldata.rs are inlined as
   format!() literals (e.g. format!("0x6e553f65{}{}", ...)) instead of
   `pub const` -- no runtime verification existed. A typo would silently
   misroute calls. Add calldata::tests::selectors_match_keccak256 with
   keccak-recomputed assertions on all 7:
     - depositETH(address)
     - deposit(uint256,address) (ERC-4626, reserved for v0.2)
     - redeem(uint256,address,address)
     - withdraw(uint256,address,address)
     - requestWithdrawal(uint128,address)
     - completeQueuedWithdrawal(uint256)
     - approve(address,uint256)
   Required adding sha3 as a dev-dependency (hex was already a runtime
   dep). Pattern matches euler-v2 / aave-v2 / compound-v2 / dolomite /
   spark-savings / fourmeme.

Also added '.ai-review/' to '.gitignore'.

Tests: 0/0 -> 1/1.
ASCII compliance: 10 non-ASCII -> 0.
Static scan own-source findings: 0 -> 0 (no regressions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GeoGu360 pushed a commit to GeoGu360/plugin-store that referenced this pull request Apr 29, 2026
Phase 3 local AI review (PR mig-pre#30) findings:

1. Non-ASCII chars in 4 metadata files where ASCII is mandated by
   skill-md-maintenance-guide §0.7 (CI lint.rs:946 panics on em-dash):
     plugin.yaml:                  1 em-dash
     .claude-plugin/plugin.json:   1 em-dash
     SKILL.md frontmatter:         2 (em-dash + LEFT-RIGHT-ARROW '<->')
     SUMMARY.md:                   6 em-dashes
   Replace U+2014 -> '-', U+2194 -> '<->', U+2192 -> '->', U+2265 -> '>=',
   U+2264 -> '<=', U+2026 -> '...'. SKILL.md body left untouched.

2. 7 hardcoded function selectors in src/calldata.rs are inlined as
   format!() literals (e.g. format!("0x6e553f65{}{}", ...)) instead of
   `pub const` -- no runtime verification existed. A typo would silently
   misroute calls. Add calldata::tests::selectors_match_keccak256 with
   keccak-recomputed assertions on all 7:
     - depositETH(address)
     - deposit(uint256,address) (ERC-4626, reserved for v0.2)
     - redeem(uint256,address,address)
     - withdraw(uint256,address,address)
     - requestWithdrawal(uint128,address)
     - completeQueuedWithdrawal(uint256)
     - approve(address,uint256)
   Required adding sha3 as a dev-dependency (hex was already a runtime
   dep). Pattern matches euler-v2 / aave-v2 / compound-v2 / dolomite /
   spark-savings / fourmeme.

Also added '.ai-review/' to '.gitignore'.

Tests: 0/0 -> 1/1.
ASCII compliance: 10 non-ASCII -> 0.
Static scan own-source findings: 0 -> 0 (no regressions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@GeoGu360 GeoGu360 force-pushed the feat/puffer-plugin branch from 79d81c1 to 2e6af55 Compare April 29, 2026 08:15
@GeoGu360 GeoGu360 added the ci-approved Maintainer reviewed PR; allows Phase 1/2/3 CI to run label Apr 29, 2026
@github-actions
Copy link
Copy Markdown

✅ Phase 1: Structure Validation — PASSED

Linting skills/puffer-plugin...


✓ Plugin 'puffer-plugin' passed all checks!

→ Proceeding to Phase 2: Build Verification

@GeoGu360 GeoGu360 added the approved-for-publish Triggers Phase 4: compile + publish + merge label Apr 29, 2026
GeoGu360 pushed a commit to GeoGu360/plugin-store that referenced this pull request Apr 29, 2026
skill-md-maintenance-guide §0.6 explicitly forbids '## Roadmap' /
'## TODO' / '## Known Issues' as SKILL.md sections: 'these are GitHub
issue territory, not SKILL.md'. PR mig-pre#30 had a '## Roadmap (future
versions)' H2 with 4 v0.2.x / v0.3.x / v0.x.x bullets.

Removed the H2 + bullets. Future-version notes belong in PR
descriptions, GitHub issues, or the Recommendations section of
review reports -- not in user-facing SKILL.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Amos and others added 4 commits April 29, 2026 17:22
First release: deposit ETH to mint pufETH via PufferVault, and exit
through either the 1-step instant redeem (pays live exit fee from
getTotalExitFeeBasisPoints, default 1%) or the 2-step queued
withdraw (fee-free, ~14 days, 0.01 pufETH minimum).

8 commands:
  positions, rate, withdraw-options   — read-only
  withdraw-status                     — track a queued request
  stake                               — depositETH payable
  request-withdraw, claim-withdraw    — 2-step queued path
  instant-withdraw                    — 1-step redeem path

Each write command prints structured JSON to stdout so external
agents can branch on stable error_code (INSUFFICIENT_BALANCE,
INSUFFICIENT_GAS, WITHDRAWAL_AMOUNT_TOO_LOW / _TOO_HIGH /
_NOT_FINALIZED / _ALREADY_CLAIMED / _OUT_OF_RANGE,
TX_WILL_REVERT, TX_CONFIRMATION_TIMEOUT, RPC_ERROR).

Pre-flight checks before every broadcast:
  - input-asset balance via onchainos wallet balance
  - vault liquidity / per-request max / min amount
  - gas budget: onchainos gateway gas-limit + gas,
    wallet ETH balance must cover value + gas*1.2
  - revert simulation; onchainos surfaces the revert reason

Broadcast and confirmation use onchainos wallet contract-call and
wallet history polling (no sleep-based races; waits every tx until
SUCCESS before reading post-tx state). Custom contract view
functions (convertToAssets, getMaxWithdrawalAmount, getWithdrawal,
allowance, etc.) use direct eth_call — onchainos has no generic
view interface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… command (CI E151 + ONB-001)

CI Phase 1 reported 3 errors + 1 warning on PR mig-pre#30:
  - [E151] SUMMARY.md missing required section: '## Overview'
  - [E151] SUMMARY.md missing required section: '## Prerequisites'
  - [E151] SUMMARY.md missing required section: '## Quick Start'
  - [W010] description is 208 chars (recommended < 200)

Root cause: SUMMARY.md was using a non-standard `# puffer` + `## Highlights`
layout — pre-dates the now-mandatory three-section template. plugin.yaml
description was also too long.

While fixing, also discovered puffer-plugin was missing the `quickstart`
command altogether — a violation of the new ONB-001 三件套 mandatory
structure (quickstart command + SUMMARY.md + LICENSE). Without quickstart,
SUMMARY's "step 1 = quickstart" rule can't be satisfied either.

Changes:
1. Added src/commands/quickstart.rs — 4 status enum (rpc_degraded /
   no_funds / ready_to_stake / has_pufeth_earning); parallel reads of
   ETH balance + pufETH balance + rate + APY + ETH price; returns
   ready-to-run next_command per status.
2. Wired quickstart into commands/mod.rs and main.rs (with import,
   enum variant, dispatch arm).
3. Rewrote SUMMARY.md to three-section template (Overview /
   Prerequisites / Quick Start), with step 1 = quickstart and steps
   2..7 mapping to status enum values + the queued-withdrawal flow
   for completeness.
4. Shortened plugin.yaml description from 221 to 192 chars (W010 fix).

Verified locally:
- cargo build: clean
- puffer-plugin quickstart: returns status=ready_to_stake with
  ETH=0.0935 + APY=3.03% + next_command="puffer-plugin stake --amount
  0.05 --confirm"
- All Step 1.6 staleness checks pass: SUMMARY 三段式 ✓, 第 1 步
  quickstart ✓, status enum src↔doc 一致 ✓, LICENSE present ✓
- description 192 chars (< 200) ✓
- 9 commands now: quickstart + 8 existing

Reuses existing PR mig-pre#30 — no new PR opened.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 local AI review (PR mig-pre#30) findings:

1. Non-ASCII chars in 4 metadata files where ASCII is mandated by
   skill-md-maintenance-guide §0.7 (CI lint.rs:946 panics on em-dash):
     plugin.yaml:                  1 em-dash
     .claude-plugin/plugin.json:   1 em-dash
     SKILL.md frontmatter:         2 (em-dash + LEFT-RIGHT-ARROW '<->')
     SUMMARY.md:                   6 em-dashes
   Replace U+2014 -> '-', U+2194 -> '<->', U+2192 -> '->', U+2265 -> '>=',
   U+2264 -> '<=', U+2026 -> '...'. SKILL.md body left untouched.

2. 7 hardcoded function selectors in src/calldata.rs are inlined as
   format!() literals (e.g. format!("0x6e553f65{}{}", ...)) instead of
   `pub const` -- no runtime verification existed. A typo would silently
   misroute calls. Add calldata::tests::selectors_match_keccak256 with
   keccak-recomputed assertions on all 7:
     - depositETH(address)
     - deposit(uint256,address) (ERC-4626, reserved for v0.2)
     - redeem(uint256,address,address)
     - withdraw(uint256,address,address)
     - requestWithdrawal(uint128,address)
     - completeQueuedWithdrawal(uint256)
     - approve(address,uint256)
   Required adding sha3 as a dev-dependency (hex was already a runtime
   dep). Pattern matches euler-v2 / aave-v2 / compound-v2 / dolomite /
   spark-savings / fourmeme.

Also added '.ai-review/' to '.gitignore'.

Tests: 0/0 -> 1/1.
ASCII compliance: 10 non-ASCII -> 0.
Static scan own-source findings: 0 -> 0 (no regressions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
skill-md-maintenance-guide §0.6 explicitly forbids '## Roadmap' /
'## TODO' / '## Known Issues' as SKILL.md sections: 'these are GitHub
issue territory, not SKILL.md'. PR mig-pre#30 had a '## Roadmap (future
versions)' H2 with 4 v0.2.x / v0.3.x / v0.x.x bullets.

Removed the H2 + bullets. Future-version notes belong in PR
descriptions, GitHub issues, or the Recommendations section of
review reports -- not in user-facing SKILL.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@GeoGu360 GeoGu360 force-pushed the feat/puffer-plugin branch from b4b4636 to 74ff99c Compare April 29, 2026 09:22
@GeoGu360 GeoGu360 changed the base branch from test to main April 29, 2026 09:22
@GeoGu360 GeoGu360 added approved-for-publish Triggers Phase 4: compile + publish + merge and removed approved-for-publish Triggers Phase 4: compile + publish + merge labels Apr 29, 2026
@plugin-store-bot plugin-store-bot Bot merged commit 19dc9ad into mig-pre:main Apr 29, 2026
30 of 31 checks passed
@plugin-store-bot
Copy link
Copy Markdown

✅ Phase 4: Publish Complete

Plugins: puffer-plugin

  • ✅ Build: 9 architectures compiled
  • ✅ Release: GitHub Release created
  • ✅ Pre-flight: injected into SKILL.md
  • ✅ Registry: registry.json updated
  • ✅ Merged to main

View workflow run


Published by Plugin Store CI

GeoGu360 pushed a commit that referenced this pull request Apr 29, 2026
For new-plugin PRs, Phase 4 published the merge to main but never
created the GitHub release/tag. Symptom: PR shows merged, plugin files
on main, but `gh release list` has no entry and Create Release job
log says:

    ##[warning] skills/<plugin>/plugin.yaml not found,
    skipping release for <plugin>

Why: create-release runs `actions/checkout@v4` against main, then
loops over BUILD_PLUGINS and reads `skills/$NAME/plugin.yaml`. For new
plugins that file does not exist on main yet — the merge happens later
in the publish-registry job — so it bails with the "skipping" warning
while the workflow stays green.

publish-registry already has the right pattern (see step "Checkout PR
head" at L612). Mirroring it in create-release:

  - name: Checkout PR head
    if: inputs.pr_number != ''
    env:
      PR_NUM: ${{ github.event.pull_request.number || inputs.pr_number }}
    run: |
      git fetch origin "refs/pull/${PR_NUM}/head:pr-head"
      git checkout pr-head

Adds `fetch-depth: 0` to the initial checkout so the fetch above can
resolve refs/pull/N/head without a shallow-fetch error.

The conditional `inputs.pr_number != ''` skips this in
rebuild-all/manual-dispatch mode where main is already the source of
truth.

Build version is still pinned via BUILD_PLUGINS JSON, so the existing
"DO NOT git pull here" invariant from L480 is preserved — yq calls
read description/author from PR head, which matches the binaries that
were just compiled.

Concrete failures this fixes:
  - mig-pre PR #30 puffer-plugin v0.1.0 (manual release made post-hoc)
  - mig-pre PR #34 lifi-plugin v0.1.0 (still missing release as of writing)
  - mig-pre PR #37 spark-savings-plugin v0.1.0 (still missing release as of writing)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed approved-for-publish Triggers Phase 4: compile + publish + merge ci-approved Maintainer reviewed PR; allows Phase 1/2/3 CI to run new-plugin structure-validated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant