Stakingverse Ethereum
v1.0.0Stake ETH on StakeWise (Ethereum liquid staking). Use when the user wants to stake ETH, unstake ETH, or check staked positions on StakeWise V3 vaults. Suppor...
Security Scan
OpenClaw
Suspicious
medium confidencePurpose & Capability
The stated purpose (stake/unstake/check positions on StakeWise V3) matches the code behavior: the scripts call a Vault contract, query a StakeWise subgraph, and sign transactions. However registry metadata claims no required environment variables while SKILL.md/README/scripts require a private key, address, RPC URL and vault address. That mismatch (registry vs docs/code) is suspicious and reduces trust.
Instruction Scope
Runtime instructions require a private key and address (expected for signing transactions) and tell the agent to run the included scripts. Problems: the README and SKILL.md reference different env var names (SKILL.md: PRIVATE_KEY, README: ETH_PRIVATE_KEY), the code uses process.env.ETH_PRIVATE_KEY (and falls back to literal placeholders), SKILL.md lists KEEPER but scripts do not use that env var, and Quick Start references scripts/unstake.js which is not present. There are also ABI / parameter ordering inconsistencies between the prose and the code. These inconsistencies could cause unexpected failures or misconfiguration and warrant manual inspection before running with real keys.
Install Mechanism
No install spec is provided (instruction-only skill with JS files). This is low-risk from an installer perspective because nothing is downloaded or installed automatically by the platform. However the package includes runnable scripts that will execute locally when invoked.
Credentials
Requesting a private key and RPC_URL is proportionate for software that must sign on-chain transactions. That said, the repository/doc inconsistencies around variable names (PRIVATE_KEY vs ETH_PRIVATE_KEY vs code fallbacks) and the presence of an unused KEEPER env variable reduce clarity. Ensure you do not paste real private keys into untrusted environments; consider using a signing service or hardware key instead of raw env vars.
Persistence & Privilege
The skill does not request always:true, does not attempt to modify other skills, and has no install script that would persist code outside the skill bundle. It runs only when invoked.
What to consider before installing
This skill contains runnable scripts that will use your Ethereum private key to sign transactions — that is expected for staking, but do not run these with your main wallet until you verify the code. Before installing/using: 1) Confirm the source repository and its integrity (the package has no homepage and the registry metadata is sparse). 2) Inspect stake.mjs, check-state.js, and position.js locally to confirm endpoints and contract addresses are correct. 3) Note env var name mismatches: README suggests ETH_PRIVATE_KEY, SKILL.md shows PRIVATE_KEY, and the code uses ETH_PRIVATE_KEY; set the correct variable or update the scripts. 4) There is no scripts/unstake.js though Quick Start mentions it — expect missing functionality. 5) Run with a throwaway wallet and tiny test amounts first. 6) Prefer using a remote signer/hardware wallet rather than placing a long-term private key in environment variables. If you are not comfortable auditing JavaScript code yourself, do not provide a live private key.Like a lobster shell, security has layers — review code before you run it.
latest
StakeWise Ethereum Staking Skill
Stake ETH on StakeWise V3 and receive osETH (liquid staking token). Earn staking rewards while keeping your ETH liquid.
What This Skill Does
- Stake ETH → Receive osETH tokens (handles state updates automatically)
- Unstake ETH → Burn osETH for ETH
- Check staked position → View vault shares and earned rewards
- Monitor vault state → Check if keeper state update is required
- Query harvest proofs → Get Merkle proofs from subgraph for deposits
Required Credentials
Set these environment variables or edit the scripts:
export STAKEWISE_VAULT="0x8A93A876912c9F03F88Bc9114847cf5b63c89f56"
export KEEPER="0x6B5815467da09DaA7DC83Db21c9239d98Bb487b5"
export PRIVATE_KEY="your_private_key"
export MY_ADDRESS="your_address"
export RPC_URL="https://ethereum-rpc.publicnode.com"
Quick Start
# Stake 0.1 ETH (auto-handles state updates)
node scripts/stake.mjs 0.1
# Check staked position
node scripts/position.js
# Unstake 0.05 osETH
node scripts/unstake.js 0.05
# Check if state update required
node scripts/check-state.js
How StakeWise V3 Works
Architecture Overview
StakeWise V3 uses a keeper-oracle pattern for state updates:
User (EOA/UP)
↓
Vault Contract
↓
Keeper (Oracle) - Validates and processes rewards
↓
osETH Token - Liquid staking token
Key Components
| Component | Address | Purpose |
|---|---|---|
| Vault | 0x8A93A876912c9F03F88Bc9114847cf5b63c89f56 | Staking/unstaking logic |
| Keeper | 0x6B5815467da09DaA7DC83Db21c9239d98Bb487b5 | Oracle for state updates |
| osETH Token | Dynamic per vault | Liquid staking token |
| Subgraph | https://graphs.stakewise.io/mainnet-a/subgraphs/name/stakewise/prod | Harvest proofs and data |
The State Update Mechanism
Why state updates?
- StakeWise accumulates rewards off-chain via validators
- Keeper periodically "harvests" and posts state on-chain
- Users can only deposit when state is current
When is state update required?
const vault = new ethers.Contract(vaultAddress, vaultAbi, provider);
const needsUpdate = await vault.isStateUpdateRequired();
// true = must update state before depositing
Staking Flow (With State Update)
Step 1: Check State
User
↓
vault.isStateUpdateRequired()
↓
Returns: true (update needed)
Step 2: Query Subgraph for Harvest Params
User
↓
POST to StakeWise subgraph
↓
Returns: rewardsRoot, reward, unlockedMevReward, proof[]
Step 3: Update State and Deposit
User
↓
vault.updateStateAndDeposit(harvestParams, receiver, referrer)
↓
Keeper validates harvest
↓
Vault mints osETH to receiver
↓
User receives osETH
Detailed Usage
Stake ETH (Full Flow with State Update)
import { ethers } from 'ethers';
import fetch from 'node-fetch';
// Setup
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
// Vault ABI (minimal)
const VAULT_ABI = [
'function isStateUpdateRequired() view returns (bool)',
'function updateStateAndDeposit(tuple(bytes32 rewardsRoot, uint256 reward, uint256 unlockedMevReward, bytes32[] proof) harvestParams, address receiver, address referrer) external payable',
'function deposit(address receiver, address referrer) external payable'
];
const vault = new ethers.Contract(
process.env.STAKEWISE_VAULT,
VAULT_ABI,
wallet
);
// Amount to stake
const stakeAmount = ethers.parseEther('0.1'); // 0.1 ETH
// Step 1: Check if state update required
const needsUpdate = await vault.isStateUpdateRequired();
console.log('State update required:', needsUpdate);
if (needsUpdate) {
// Step 2: Query subgraph for harvest params
const subgraphQuery = {
query: `
query getHarvestProofs($vault: String!) {
harvestProofs(
where: { vault: $vault }
orderBy: blockNumber
orderDirection: desc
first: 1
) {
rewardsRoot
reward
unlockedMevReward
proof
}
}
`,
variables: {
vault: process.env.STAKEWISE_VAULT.toLowerCase()
}
};
const response = await fetch('https://graphs.stakewise.io/mainnet-a/subgraphs/name/stakewise/prod', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subgraphQuery)
});
const data = await response.json();
const harvestProof = data.data.harvestProofs[0];
// Step 3: Call updateStateAndDeposit
const harvestParams = {
rewardsRoot: harvestProof.rewardsRoot,
reward: BigInt(harvestProof.reward),
unlockedMevReward: BigInt(harvestProof.unlockedMevReward),
proof: harvestProof.proof
};
const tx = await vault.updateStateAndDeposit(
harvestParams,
process.env.MY_ADDRESS, // receiver
ethers.ZeroAddress, // referrer (optional)
{ value: stakeAmount }
);
const receipt = await tx.wait();
console.log(`Staked ${ethers.formatEther(stakeAmount)} ETH with state update`);
console.log(`Transaction: ${receipt.hash}`);
} else {
// Simple deposit (no state update needed)
const tx = await vault.deposit(
process.env.MY_ADDRESS,
ethers.ZeroAddress,
{ value: stakeAmount }
);
const receipt = await tx.wait();
console.log(`Staked ${ethers.formatEther(stakeAmount)} ETH`);
console.log(`Transaction: ${receipt.hash}`);
}
Check Staked Position
const OSETH_ABI = [
'function balanceOf(address) view returns (uint256)',
'function convertToAssets(uint256 shares) view returns (uint256)'
];
// Get osETH token address from vault
const osEthAddress = await vault.osToken();
const osEth = new ethers.Contract(osEthAddress, OSETH_ABI, provider);
const osEthBalance = await osEth.balanceOf(process.env.MY_ADDRESS);
const underlyingEth = await osEth.convertToAssets(osEthBalance);
console.log(`osETH Balance: ${ethers.formatEther(osEthBalance)}`);
console.log(`Equivalent ETH: ${ethers.formatEther(underlyingEth)}`);
Unstake ETH
const VAULT_FULL_ABI = [
'function redeem(uint256 shares, address receiver, address owner) returns (uint256 assets)',
'function maxRedeem(address owner) view returns (uint256)'
];
const vaultFull = new ethers.Contract(
process.env.STAKEWISE_VAULT,
VAULT_FULL_ABI,
wallet
);
// Check max redeemable
const maxShares = await vaultFull.maxRedeem(process.env.MY_ADDRESS);
console.log(`Max redeemable: ${ethers.formatEther(maxShares)} osETH`);
// Redeem shares for ETH
const sharesToRedeem = ethers.parseEther('0.05');
const tx = await vaultFull.redeem(
sharesToRedeem,
process.env.MY_ADDRESS, // receiver
process.env.MY_ADDRESS // owner
);
const receipt = await tx.wait();
console.log(`Redeemed ${ethers.formatEther(sharesToRedeem)} osETH for ETH`);
console.log(`Transaction: ${receipt.hash}`);
Subgraph Queries
Get Latest Harvest Proof
const query = {
query: `
query {
harvestProofs(
orderBy: blockNumber
orderDirection: desc
first: 1
) {
id
vault
rewardsRoot
reward
unlockedMevReward
proof
blockNumber
}
}
`
};
const response = await fetch('https://graphs.stakewise.io/mainnet-a/subgraphs/name/stakewise/prod', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(query)
});
const data = await response.json();
console.log(data.data.harvestProofs[0]);
Get Vault State
const query = {
query: `
query {
vaults(first: 1) {
id
address
totalAssets
totalShares
apr
}
}
`
};
Common Issues
"State update required"
- The keeper hasn't posted recent rewards
- Query subgraph for latest harvest proof
- Use
updateStateAndDeposit()instead ofdeposit()
"Invalid harvest proof"
- Proof may be outdated
- Always query subgraph immediately before depositing
- Proofs are block-specific
"Insufficient shares"
- Trying to redeem more osETH than you have
- Check balance:
osETH.balanceOf(yourAddress)
"Vault is paused"
- Emergency pause may be active
- Check:
vault.paused() - Wait for StakeWise team to unpause
Important Notes
- APY varies: Based on Ethereum validator rewards, typically 3-5%
- osETH is rebasing: Balance increases automatically as rewards accrue
- Keeper dependency: Deposits require valid state (keeper must be active)
- Gas costs: State updates cost more gas than simple deposits
- MEV rewards: Part of harvest includes MEV extraction rewards
Resources
- StakeWise App: https://app.stakewise.io
- StakeWise Docs: https://docs.stakewise.io
- Subgraph: https://graphs.stakewise.io/mainnet-a/subgraphs/name/stakewise/prod
- Vault:
0x8A93A876912c9F03F88Bc9114847cf5b63c89f56 - Keeper:
0x6B5815467da09DaA7DC83Db21c9239d98Bb487b5
Comments
Loading comments...
