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 | 0xc3997ff81f2831929499c4eE4Ee4e0F08F42D4D8
| 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
| https://ethereum.publicnode.com
| |
| Base | 8453 | 0xc3997ff81f2831929499c4eE4Ee4e0F08F42D4D8
| 0x4200000000000000000000000000000000000006
| https://base.publicnode.com
| |
| Optimism | 10 | 0xc3997ff81f2831929499c4eE4Ee4e0F08F42D4D8
| 0x4200000000000000000000000000000000000006
| https://optimism.publicnode.com
| |
| Arbitrum | 42161 | 0xc3997ff81f2831929499c4eE4Ee4e0F08F42D4D8
| 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1
| https://arbitrum-one.publicnode.com
| |
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) | 0xc3997ff81f2831929499c4eE4Ee4e0F08F42D4D8
|
| 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
as an environment variable, or import via Foundry keystore:
bash
cast wallet import bifrost-agent --interactive
When
is set, the agent can sign and broadcast transactions directly using
.
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 | redeem(uint256,address,address)
| | 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 | withdrawCompleteTo(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 | canWithdrawalAmount(address)
| | Returns (totalAvailableAmount, pendingDeleteIndex, pendingDeleteAmount)
. First value = ETH ready to claim |
How to Call
Read queries — use
(no gas):
bash
# 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
or
fails, fall back to
/
(same encoding).
Write transactions — use
(requires wallet):
bash
# 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):
(totalAvailableAmount, pendingDeleteIndex, pendingDeleteAmount)
. First 64 chars = claimable ETH amount
API 1: Mint vETH (Stake ETH)
Pre-Execution
- Query rate: → expected vETH
- Check wallet: env var or Foundry keystore
- 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 amount
- Query → expected ETH
- Check
- Display preview (warn: ETH enters queue, NOT instant) and wait for CONFIRM
Transaction
| Field | Value |
|---|
| To | |
| Value | |
| Data | ABI-encoded redeem(shares, userAddr, userAddr)
|
| ChainId | Per selected chain |
Encode calldata:
cast calldata "redeem(uint256,address,address)" <SHARES> <ADDR> <ADDR>
API 3: Claim Redeemed ETH
Pre-Execution
- Check
canWithdrawalAmount(user)
— first return value = claimable amount
- 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 , , or . If not, use Ethereum Mainnet defaults with manual signing mode
- RPC selection: use if set; otherwise use per-chain default RPC. Fall back to per-chain fallback RPC on failure
- 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 keystore . If found, ask user whether to use it. If not, output tx data for manual signing
- 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:
https://etherscan.io/tx/{hash}
(Ethereum), https://basescan.org/tx/{hash}
(Base), https://optimistic.etherscan.io/tx/{hash}
(Optimism), https://arbiscan.io/tx/{hash}
(Arbitrum)
- 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 via . No ERC-20 approval needed. For direct WETH deposits, use instead (requires WETH approval)
- internally calls
withdrawCompleteTo(address(this))
to receive WETH, then unwraps to ETH via , then sends ETH to caller. If ETH transfer fails, use to receive WETH instead
- Redemption is NOT instant — / add entries to the withdrawal queue, processed in batches via Bifrost cross-chain mechanism
- All write functions are protected by and (ReentrancyGuardUpgradeable)
- Gas estimates are approximate; use for accuracy