Stake ETH on StakeWise V3 and receive osETH (liquid staking token). Earn staking rewards while keeping your ETH liquid.
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"
# 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
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
| 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 |
Why state updates?
When is state update required?
const vault = new ethers.Contract(vaultAddress, vaultAbi, provider);
const needsUpdate = await vault.isStateUpdateRequired();
// true = must update state before depositing
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
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}`);
}
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)}`);
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}`);
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]);
const query = {
query: `
query {
vaults(first: 1) {
id
address
totalAssets
totalShares
apr
}
}
`
};
updateStateAndDeposit() instead of deposit()osETH.balanceOf(yourAddress)vault.paused()0x8A93A876912c9F03F88Bc9114847cf5b63c89f560x6B5815467da09DaA7DC83Db21c9239d98Bb487b5共 1 个版本