Skills bifrost-slpx-stake
install
source · Clone the upstream repo
git clone https://github.com/openclaw/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/ark930/bifrost-slpx-stake" ~/.claude/skills/openclaw-skills-bifrost-slpx-stake && rm -rf "$T"
OpenClaw · Install into ~/.openclaw/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.openclaw/skills && cp -r "$T/skills/ark930/bifrost-slpx-stake" ~/.openclaw/skills/openclaw-skills-bifrost-slpx-stake && rm -rf "$T"
manifest:
skills/ark930/bifrost-slpx-stake/SKILL.mdsource content
Bifrost SLPx Stake
Execute Bifrost vETH liquid staking operations: mint, redeem, and claim.
Contract & Network
vETH is deployed on Ethereum and three L2 networks. The same contract address is used across all chains.
| Chain | ChainId | VETH Contract | WETH (underlying) | Default RPC | Fallback RPC |
|---|---|---|---|---|---|
| Ethereum | 1 | | | | |
| Base | 8453 | | | | |
| Optimism | 10 | | | | |
| Arbitrum | 42161 | | | | |
Configuration
On first run, ask the user whether they want to configure custom settings. If not, use the defaults above.
Environment Variables
| Variable | Description | Default |
|---|---|---|
| Target chain name (, , , ) | |
| Custom RPC endpoint | Per-chain default from table above |
| VETH contract address (override) | |
| Private key for agent-side signing (hex, with or without 0x prefix) | Not set (manual signing mode) |
Wallet Setup
Two signing modes. Default is manual signing (no setup needed).
Default: Manual Signing
Output complete transaction details (to, value, data, gas, chainId). User signs with their own wallet (MetaMask, Ledger, CLI, etc.).
Option: Agent-Side Signing
Set
BIFROST_PRIVATE_KEY as an environment variable, or import via Foundry keystore:
cast wallet import bifrost-agent --interactive
When
BIFROST_PRIVATE_KEY is set, the agent can sign and broadcast transactions directly using cast send.
Quick Reference
Write Operations
| Operation | Function | Selector | Description |
|---|---|---|---|
| Mint vETH (via ETH) | | | Stake native ETH to mint vETH. ETH is sent as . The contract wraps ETH → WETH internally — no ERC-20 approval needed. Reverts if |
| Mint vETH (via WETH) | | | Deposit WETH directly to mint vETH for . Requires prior WETH approval to the VETH contract |
| Redeem vETH | | | Burn of vETH to initiate ETH withdrawal for . ETH enters a redemption queue and is NOT returned instantly. Requires or sufficient allowance |
| Claim as ETH | | | Claim ALL completed withdrawals as native ETH. Internally calls then unwraps WETH → ETH. Reverts if ETH transfer fails |
| Claim as WETH | | | Claim ALL completed withdrawals as WETH to . Use this if fails |
| Claim to address | | | Claim ALL completed withdrawals as WETH to a specified address |
Pre-Execution Query Functions
| Query | Function | Selector | Description |
|---|---|---|---|
| Preview deposit | | | Simulate deposit and return exact vETH shares to be minted |
| Preview redeem | | | Simulate redemption and return exact ETH to be returned |
| Fallback: shares calc | | | Convert ETH amount to vETH shares using current Oracle exchange rate |
| Fallback: assets calc | | | Convert vETH shares to ETH value using current Oracle exchange rate |
| vETH balance | | | Get vETH token balance of a specific address |
| Max redeemable | | | Maximum vETH shares the owner can redeem in a single tx |
| Claimable ETH | | | Returns . First value = ETH ready to claim |
How to Call
Read queries — use
eth_call (no gas):
# Method A: cast (preferred) cast call <VETH_CONTRACT> \ "<FUNCTION_SIGNATURE>(<ARG_TYPES>)(<RETURN_TYPES>)" <ARGS> \ --rpc-url <RPC_URL> # Method B: curl (if cast unavailable) curl -s -X POST <RPC_URL> \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"<VETH_CONTRACT>","data":"<SELECTOR><ENCODED_ARGS>"},"latest"]}'
If
previewDeposit or previewRedeem fails, fall back to convertToShares / convertToAssets (same encoding).
Write transactions — use
cast send (requires wallet):
# Mint vETH (stake native ETH) cast send <VETH_CONTRACT> \ "depositWithETH()" --value <AMOUNT_IN_WEI> \ --rpc-url <RPC_URL> --private-key <PRIVATE_KEY> # Redeem vETH (unstake) cast send <VETH_CONTRACT> \ "redeem(uint256,address,address)" <SHARES_IN_WEI> <USER_ADDR> <USER_ADDR> \ --rpc-url <RPC_URL> --private-key <PRIVATE_KEY> # Claim ETH (withdraw completed redemptions) cast send <VETH_CONTRACT> \ "withdrawCompleteToETH()" \ --rpc-url <RPC_URL> --private-key <PRIVATE_KEY>
Calldata Encoding (for manual signing output)
- uint256: convert wei to hex, left-pad to 64 chars
- address: remove 0x prefix, left-pad to 64 chars
returns 3 × uint256 (192 hex chars):canWithdrawalAmount
. First 64 chars = claimable ETH amount(totalAvailableAmount, pendingDeleteIndex, pendingDeleteAmount)
API 1: Mint vETH (Stake ETH)
Pre-Execution
- Query rate:
→ expected vETHpreviewDeposit(amount) - Check wallet:
env var or Foundry keystoreBIFROST_PRIVATE_KEYbifrost-agent - Display preview and wait for CONFIRM
Transaction
| Field | Value |
|---|---|
| To | |
| Value | User's ETH amount in wei |
| Data | |
| ChainId | Per selected chain |
Manual Signing Output
To: <VETH_CONTRACT> Value: {wei} ({amount} ETH) Data: 0x1166dab6 ChainId: {chainId}
API 2: Redeem vETH (Unstake)
Pre-Execution
- Check
≥ redeem amountbalanceOf(user) - Query
→ expected ETHpreviewRedeem(shares) - Check
maxRedeem(user) - Display preview (warn: ETH enters queue, NOT instant) and wait for CONFIRM
Transaction
| Field | Value |
|---|---|
| To | |
| Value | |
| Data | ABI-encoded |
| ChainId | Per selected chain |
Encode calldata:
cast calldata "redeem(uint256,address,address)" <SHARES> <ADDR> <ADDR>
API 3: Claim Redeemed ETH
Pre-Execution
- Check
— first return value = claimable amountcanWithdrawalAmount(user) - If 0: inform user redemption may still be processing
- If > 0: display claimable amount and wait for CONFIRM
Transaction
| Field | Value |
|---|---|
| To | |
| Value | |
| Data | |
| ChainId | Per selected chain |
Agent Behavior
- Environment check: on first interaction, ask user if they want to configure
,BIFROST_CHAIN
, orBIFROST_RPC_URL
. If not, use Ethereum Mainnet defaults with manual signing modeBIFROST_PRIVATE_KEY - RPC selection: use
if set; otherwise use per-chain default RPC. Fall back to per-chain fallback RPC on failureBIFROST_RPC_URL - Multi-chain awareness: when user specifies a chain (e.g. "on Base", "on Arbitrum"), switch to that chain's RPC, WETH address, and chainId accordingly
- Wallet detection: check
env var or Foundry keystoreBIFROST_PRIVATE_KEY
. If found, ask user whether to use it. If not, output tx data for manual signingbifrost-agent - CONFIRM required: display transaction preview (amount, rate, expected output, chain) and require user to type CONFIRM before any write
- Private key import requires CONFIRM: show security warning first, require CONFIRM before accepting key
- Key retention is user-controlled: after tx, ask user whether to keep or delete the key
- Balance pre-check: verify sufficient ETH/vETH before building tx
- Prefer cast, fall back to curl: use pre-computed calldata from selector table if cast fails
- No credential display: never echo private keys; truncate addresses (first 6 + last 4)
- Post-completion tip: if no wallet configured, suggest "set up wallet" after operation
- After successful tx, provide block explorer link:
(Ethereum),https://etherscan.io/tx/{hash}
(Base),https://basescan.org/tx/{hash}
(Optimism),https://optimistic.etherscan.io/tx/{hash}
(Arbitrum)https://arbiscan.io/tx/{hash} - Useful links: direct users to Bifrost vETH page or Bifrost App when relevant
Security
- Private keys are opt-in only — default outputs unsigned tx data
- Explicit CONFIRM for every write operation
- Validate amounts against balance and protocol limits
- Recommend dedicated wallet with limited funds for agent-side signing
Error Handling
| Error | User Message |
|---|---|
(0x8689d991) | "No ETH included. Please specify the amount." |
| "ETH transfer failed. Try claiming as WETH with withdrawComplete()." |
(0xd6d9e665) | "No claimable ETH. Your redemption may still be processing." |
(0xb94abeec) | "Redeem exceeds your maximum. Check balance." |
| "VETH contract is paused. Try again later." |
| Insufficient ETH | "Insufficient ETH. Balance: {bal}, Needed: {amount + gas}." |
| Insufficient vETH | "Insufficient vETH. Balance: {bal}, Requested: {amount}." |
| Max withdraw count exceeded | "Too many pending redemptions. Claim existing ones first." |
| RPC failure | "Unable to connect. Retrying with backup endpoint..." |
Notes
wraps ETH → WETH internally viadepositWithETH()
. No ERC-20 approval needed. For direct WETH deposits, useWETH.deposit()
instead (requires WETH approval)deposit(uint256,address)
internally callswithdrawCompleteToETH()
to receive WETH, then unwraps to ETH viawithdrawCompleteTo(address(this))
, then sends ETH to caller. If ETH transfer fails, useWETH.withdraw()
to receive WETH insteadwithdrawComplete()- Redemption is NOT instant —
/redeem()
add entries to the withdrawal queue, processed in batches via Bifrost cross-chain mechanismwithdraw() - All write functions are protected by
andwhenNotPaused
(ReentrancyGuardUpgradeable)nonReentrant - Gas estimates are approximate; use
for accuracycast estimate