stPROS
Professional Liquid Staking for Pharos Network
What is stPROS?
stPROS is Bifrost's liquid staking token for the Pharos Network. By staking PHRS tokens through Bifrost, users receive stPROS tokens that represent their staked position while maintaining liquidity. Staking rewards are automatically reflected in stPROS's increasing exchange rate against PHRS.
Staking rewards
Staking rewards automatically increase the stPROS exchange rate. The longer you hold stPROS, the more PHRS you can redeem when unstaking.
Why stPROS?
Liquidity and Capital Efficiency
stPROS provides liquidity for your staked PHRS, allowing you to trade, transfer, or use it in DeFi protocols while still earning staking rewards.
Automatic Reward Compounding
Staking rewards are automatically compounded into the stPROS exchange rate. No manual claiming required.
No Technical Background Needed
Stake PHRS and receive stPROS with a simple deposit transaction. No need to run validators or manage complex staking infrastructure.
DeFi Composability
stPROS is an ERC-4626 compliant vault token, making it compatible with the broader DeFi ecosystem for lending, liquidity provision, and yield strategies.
Network Information
| Property | Value |
|---|---|
| Network | Pharos Atlantic Testnet |
| Chain ID | 688689 |
| Native Token | PHRS (18 decimals) |
| RPC Endpoint | https://atlantic.dplabs-internal.com/ |
| Block Explorer | PharosScan |
Contract Information
| Contract | Address |
|---|---|
| stPROS | 0xc9A0B63d91c2A808dD631d031f037944fedDaA12 |
Contract Architecture
The stPROS contract is part of Bifrost's SLPx V2 contracts, implementing a modular architecture for liquid staking with cross-chain capabilities.
Inheritance Hierarchy
┌─────────────────────────────────────────────────────────────┐
│ stPROS │
│ - depositWithPROS() │
│ - withdrawCompleteToPROS() │
│ - receive() payable │
└─────────────────────────────────────────────────────────────┘
│
extends │
▼
┌─────────────────────────────────────────────────────────────┐
│ VToken + ReentrancyGuardUpgradeable │
│ - initialize(asset, owner, name, symbol) │
└─────────────────────────────────────────────────────────────┘
│
extends │
▼
┌─────────────────────────────────────────────────────────────┐
│ VTokenBase │
│ - ERC4626 vault operations │
│ - Async cross-chain minting/redeeming │
│ - Withdrawal queue system │
│ - Oracle-based pricing │
│ - Pausable + Access Control │
└─────────────────────────────────────────────────────────────┘System Components
The stPROS system consists of several interconnected contracts:
| Contract | Purpose |
|---|---|
| stPROS | Main entry point for PHRS staking, handles native token wrapping |
| VToken | Initialization wrapper extending VTokenBase |
| VTokenBase | Core ERC4626 vault logic with async cross-chain operations |
| Oracle | Manages exchange rates between PHRS and stPROS |
| BridgeVault | Receives bridged assets from other chains |
| TokenGateway | Handles cross-chain token teleportation |
stPROS Contract
The stPROS contract extends VToken and adds functionality specific to handling PHRS (the native token of Pharos).
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;
import {VToken} from "./VToken.sol";
import {IWETH} from "./interfaces/IWETH.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
contract stPROS is VToken, ReentrancyGuardUpgradeable {
// Custom errors
error EthNotSent();
error EthTransferFailed();
// Events
event EthReceived(address indexed sender, uint256 amount);
// Accept native PHRS
receive() external payable {
emit EthReceived(msg.sender, msg.value);
}
// Deposit native PHRS to mint stPROS
function depositWithPROS(uint256 amount) external payable nonReentrant whenNotPaused {
if (msg.value == 0) revert EthNotSent();
// Wraps PHRS to V_PHRS, then mints stPROS shares
// ...
}
// Withdraw and receive native PHRS
function withdrawCompleteToPROS(uint256 withdrawId) external nonReentrant whenNotPaused {
// Burns stPROS, unwraps V_PHRS, sends native PHRS to caller
// ...
}
}stPROS-Specific Functions
| Function | Visibility | Description |
|---|---|---|
depositWithPROS(uint256 amount) | external payable | Accepts native PHRS, wraps to V_PHRS, mints stPROS shares |
withdrawCompleteToPROS(uint256 withdrawId) | external | Completes withdrawal, unwraps V_PHRS, returns native PHRS |
receive() | external payable | Fallback to accept native PHRS transfers |
VTokenBase (Core Vault Logic)
VTokenBase implements the ERC4626 vault standard with extensions for cross-chain operations.
State Variables
// Oracle for exchange rate calculations
IOracle public oracle;
// Bridge vault for cross-chain asset custody
address public bridgeVault;
// Token gateway for cross-chain teleportation
ITokenGateway public tokenGateway;
// Authorized address for triggering bridge operations
address public triggerAddress;
// Cycle-based tracking for batched operations
uint256 public currentCycle;
mapping(uint256 => CycleInfo) public cycleInfo;
// Per-user withdrawal queue
mapping(address => WithdrawalQueue) public withdrawalQueues;Core ERC4626 Functions
| Function | Description |
|---|---|
deposit(uint256 assets, address receiver) | Deposit assets, receive shares |
mint(uint256 shares, address receiver) | Mint exact shares by depositing assets |
withdraw(uint256 assets, address receiver, address owner) | Withdraw assets by burning shares |
redeem(uint256 shares, address receiver, address owner) | Redeem shares for assets |
convertToShares(uint256 assets) | Preview assets → shares conversion |
convertToAssets(uint256 shares) | Preview shares → assets conversion |
previewDeposit(uint256 assets) | Preview deposit result |
previewRedeem(uint256 shares) | Preview redeem result |
totalAssets() | Total assets managed by vault |
Async Cross-Chain Functions
These functions enable minting and redeeming across chains via the Bifrost bridge:
| Function | Description |
|---|---|
asyncMint(uint256 shares, address receiver) | Queue mint request for cross-chain execution |
asyncMintWithNativeCost(uint256 shares, address receiver) | Async mint paying relayer fees in native token |
asyncRedeem(uint256 shares, address receiver) | Queue redeem request for cross-chain execution |
asyncRedeemWithNativeCost(uint256 shares, address receiver) | Async redeem paying relayer fees in native token |
Withdrawal Queue Functions
| Function | Description |
|---|---|
getWithdrawalQueue(address user) | Get user's pending withdrawals |
withdrawComplete(uint256 withdrawId) | Complete a pending withdrawal |
getWithdrawableAmount(address user) | Get total amount available to withdraw |
Admin Functions
| Function | Description |
|---|---|
setOracle(address _oracle) | Set the oracle contract address |
setBridgeVault(address _bridgeVault) | Set the bridge vault address |
setTokenGateway(address _tokenGateway) | Set the token gateway address |
setTriggerAddress(address _triggerAddress) | Set authorized trigger address |
pause() | Pause user-facing operations |
unpause() | Resume operations |
Oracle Contract
The Oracle manages exchange rates between underlying assets and vTokens.
contract Oracle {
struct PoolInfo {
uint256 tokenAmount; // Total underlying tokens
uint256 vTokenAmount; // Total vToken supply
}
uint256 public constant FEE_DENOMINATOR = 10000;
uint256 public mintFeeRate; // Fee on minting (basis points)
uint256 public redeemFeeRate; // Fee on redeeming (basis points)
mapping(address => PoolInfo) public poolInfo;
}Oracle Functions
| Function | Description |
|---|---|
getVTokenAmountByToken(address token, uint256 amount) | Calculate vToken output for token input (with mint fee) |
getTokenAmountByVToken(address token, uint256 amount) | Calculate token output for vToken input (with redeem fee) |
setPoolInfo(address token, uint256 tokenAmount, uint256 vTokenAmount) | Update pool exchange rate |
setMintFeeRate(uint256 rate) | Set mint fee (max 10000 = 100%) |
setRedeemFeeRate(uint256 rate) | Set redeem fee (max 10000 = 100%) |
BridgeVault Contract
The BridgeVault receives and custodies assets bridged from other chains.
contract BridgeVault {
// Whitelist of authorized VToken contracts
mapping(address => bool) public vTokens;
// Only whitelisted VTokens can withdraw
modifier onlyVToken() {
require(vTokens[msg.sender], "Not authorized");
_;
}
}BridgeVault Functions
| Function | Description |
|---|---|
withdraw(address token, address to, uint256 amount) | Withdraw tokens (VToken only) |
withdrawETH(address to, uint256 amount) | Withdraw native currency (VToken only) |
addVToken(address vToken) | Whitelist a VToken contract |
removeVToken(address vToken) | Remove VToken from whitelist |
emergencyWithdraw(address token, uint256 amount) | Owner emergency withdrawal |
Security Features
The stPROS contract implements multiple security measures:
- Reentrancy Protection: All state-changing functions use
nonReentrantmodifier - Pausable: Operations can be paused in emergencies via
whenNotPaused - Access Control: Role-based permissions for admin functions
- Safe Transfers: Uses OpenZeppelin's SafeERC20 for token transfers
- Input Validation: Custom errors for invalid inputs (
EthNotSent,EthTransferFailed)
Events
// stPROS Events
event EthReceived(address indexed sender, uint256 amount);
// VTokenBase Events (inherited)
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
event AsyncMintQueued(address indexed receiver, uint256 shares, uint256 cycle);
event AsyncMintCompleted(address indexed receiver, uint256 shares);
event AsyncRedeemQueued(address indexed owner, uint256 shares, uint256 cycle);
event AsyncRedeemCompleted(address indexed owner, uint256 assets);
event BridgeVaultChanged(address indexed oldVault, address indexed newVault);
event OracleChanged(address indexed oldOracle, address indexed newOracle);Source Code
The complete contract source code is available at:
Integration Guide
Prerequisites
To integrate stPROS, you'll need:
- A wallet connected to Pharos Atlantic Testnet
- PHRS tokens for staking
- wagmi or viem for contract interactions
ABI
The stPROS contract implements the ERC-4626 vault standard with additional async operations for cross-chain functionality.
export const stProsAbi = [
// Core ERC-4626 Functions
{
name: "deposit",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "assets", type: "uint256" },
{ name: "receiver", type: "address" }
],
outputs: [{ name: "shares", type: "uint256" }]
},
{
name: "depositWithPROS",
type: "function",
stateMutability: "payable",
inputs: [{ name: "amount", type: "uint256" }],
outputs: []
},
{
name: "withdraw",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "assets", type: "uint256" },
{ name: "receiver", type: "address" },
{ name: "owner", type: "address" }
],
outputs: [{ name: "shares", type: "uint256" }]
},
{
name: "redeem",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "shares", type: "uint256" },
{ name: "receiver", type: "address" },
{ name: "owner", type: "address" }
],
outputs: [{ name: "assets", type: "uint256" }]
},
// Preview Functions
{
name: "previewDeposit",
type: "function",
stateMutability: "view",
inputs: [{ name: "assets", type: "uint256" }],
outputs: [{ name: "shares", type: "uint256" }]
},
{
name: "previewRedeem",
type: "function",
stateMutability: "view",
inputs: [{ name: "shares", type: "uint256" }],
outputs: [{ name: "assets", type: "uint256" }]
},
{
name: "convertToShares",
type: "function",
stateMutability: "view",
inputs: [{ name: "assets", type: "uint256" }],
outputs: [{ name: "shares", type: "uint256" }]
},
{
name: "convertToAssets",
type: "function",
stateMutability: "view",
inputs: [{ name: "shares", type: "uint256" }],
outputs: [{ name: "assets", type: "uint256" }]
},
// ERC-20 Functions
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "account", type: "address" }],
outputs: [{ name: "", type: "uint256" }]
},
{
name: "totalSupply",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ name: "", type: "uint256" }]
},
{
name: "totalAssets",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ name: "", type: "uint256" }]
},
{
name: "asset",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ name: "", type: "address" }]
},
// Async Operations (Cross-chain)
{
name: "asyncMint",
type: "function",
stateMutability: "payable",
inputs: [
{ name: "shares", type: "uint256" },
{ name: "receiver", type: "address" }
],
outputs: []
},
{
name: "asyncRedeem",
type: "function",
stateMutability: "payable",
inputs: [
{ name: "shares", type: "uint256" },
{ name: "receiver", type: "address" }
],
outputs: []
},
// Events
{
name: "Deposit",
type: "event",
inputs: [
{ name: "sender", type: "address", indexed: true },
{ name: "owner", type: "address", indexed: true },
{ name: "assets", type: "uint256", indexed: false },
{ name: "shares", type: "uint256", indexed: false }
]
},
{
name: "Withdraw",
type: "event",
inputs: [
{ name: "sender", type: "address", indexed: true },
{ name: "receiver", type: "address", indexed: true },
{ name: "owner", type: "address", indexed: true },
{ name: "assets", type: "uint256", indexed: false },
{ name: "shares", type: "uint256", indexed: false }
]
}
] as const;Minting stPROS (Staking PHRS)
Use the depositWithPROS function to stake PHRS and receive stPROS:
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { parseEther } from 'viem';
const STPROS_ADDRESS = '0xc9A0B63d91c2A808dD631d031f037944fedDaA12';
function MintStPROS() {
const { writeContract, data: hash } = useWriteContract();
const { isLoading, isSuccess } = useWaitForTransactionReceipt({
hash,
});
const handleMint = (amount: string) => {
writeContract({
address: STPROS_ADDRESS,
abi: stProsAbi,
functionName: 'depositWithPROS',
args: [parseEther(amount)],
value: parseEther(amount), // Send PHRS as native value
});
};
return (
<button onClick={() => handleMint('10')} disabled={isLoading}>
{isLoading ? 'Minting...' : 'Mint stPROS'}
</button>
);
}Reading stPROS Balance
import { useReadContract } from 'wagmi';
import { formatEther } from 'viem';
function StPROSBalance({ address }: { address: `0x${string}` }) {
const { data: balance } = useReadContract({
address: STPROS_ADDRESS,
abi: stProsAbi,
functionName: 'balanceOf',
args: [address],
});
return <p>stPROS Balance: {balance ? formatEther(balance) : '0'}</p>;
}Getting the Exchange Rate
Use convertToShares to preview how much stPROS you'll receive for a given amount of PHRS:
import { useReadContract } from 'wagmi';
import { parseEther, formatEther } from 'viem';
function ExchangeRate() {
const { data: shares } = useReadContract({
address: STPROS_ADDRESS,
abi: stProsAbi,
functionName: 'convertToShares',
args: [parseEther('1')], // 1 PHRS
});
return (
<p>
Exchange Rate: 1 PHRS = {shares ? formatEther(shares) : '...'} stPROS
</p>
);
}Redeeming stPROS (Unstaking)
Use the redeem function to convert stPROS back to PHRS:
import { useWriteContract } from 'wagmi';
import { parseEther } from 'viem';
function RedeemStPROS({ userAddress }: { userAddress: `0x${string}` }) {
const { writeContract } = useWriteContract();
const handleRedeem = (shares: string) => {
writeContract({
address: STPROS_ADDRESS,
abi: stProsAbi,
functionName: 'redeem',
args: [
parseEther(shares), // shares to redeem
userAddress, // receiver
userAddress, // owner
],
});
};
return (
<button onClick={() => handleRedeem('10')}>
Redeem 10 stPROS
</button>
);
}Complete Integration Example
import {
useAccount,
useBalance,
useReadContract,
useWriteContract,
useWaitForTransactionReceipt
} from 'wagmi';
import { parseEther, formatEther } from 'viem';
import { useState } from 'react';
const STPROS_ADDRESS = '0xc9A0B63d91c2A808dD631d031f037944fedDaA12';
export function StPROSStaking() {
const [amount, setAmount] = useState('');
const { address } = useAccount();
// Get native PHRS balance
const { data: phrsBalance } = useBalance({ address });
// Get stPROS balance
const { data: stProsBalance } = useReadContract({
address: STPROS_ADDRESS,
abi: stProsAbi,
functionName: 'balanceOf',
args: [address!],
query: { enabled: !!address },
});
// Get exchange rate
const { data: exchangeRate } = useReadContract({
address: STPROS_ADDRESS,
abi: stProsAbi,
functionName: 'convertToShares',
args: [parseEther('1')],
});
// Mint transaction
const { writeContract, data: hash } = useWriteContract();
const { isLoading, isSuccess } = useWaitForTransactionReceipt({ hash });
const handleMint = () => {
if (!amount) return;
writeContract({
address: STPROS_ADDRESS,
abi: stProsAbi,
functionName: 'depositWithPROS',
args: [parseEther(amount)],
value: parseEther(amount),
});
};
return (
<div>
<h2>stPROS Staking</h2>
<div>
<p>PHRS Balance: {phrsBalance ? formatEther(phrsBalance.value) : '0'}</p>
<p>stPROS Balance: {stProsBalance ? formatEther(stProsBalance) : '0'}</p>
<p>Rate: 1 PHRS = {exchangeRate ? formatEther(exchangeRate) : '...'} stPROS</p>
</div>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount of PHRS to stake"
/>
<button onClick={handleMint} disabled={isLoading || !amount}>
{isLoading ? 'Minting...' : 'Stake PHRS'}
</button>
{isSuccess && <p>Successfully minted stPROS!</p>}
</div>
);
}Key Functions Reference
| Function | Description | Parameters |
|---|---|---|
depositWithPROS | Stake PHRS to receive stPROS | amount: uint256 (wei) |
deposit | ERC-4626 deposit | assets: uint256, receiver: address |
withdraw | Withdraw PHRS from stPROS | assets: uint256, receiver: address, owner: address |
redeem | Redeem stPROS shares for PHRS | shares: uint256, receiver: address, owner: address |
convertToShares | Preview PHRS → stPROS conversion | assets: uint256 |
convertToAssets | Preview stPROS → PHRS conversion | shares: uint256 |
previewDeposit | Preview deposit result | assets: uint256 |
previewRedeem | Preview redeem result | shares: uint256 |
balanceOf | Get stPROS balance | account: address |
totalAssets | Get total PHRS in vault | - |
totalSupply | Get total stPROS supply | - |
FAQ
What is the minimum stake?
There is no enforced minimum, but gas costs should be considered for small amounts.
How do staking rewards work?
Staking rewards are automatically reflected in the exchange rate between PHRS and stPROS. As rewards accumulate, each stPROS becomes redeemable for more PHRS.
Is stPROS transferable?
Yes, stPROS is an ERC-20 compatible token that can be freely transferred between addresses.
What is the stPROS contract standard?
stPROS implements the ERC-4626 tokenized vault standard, ensuring compatibility with DeFi protocols that support this standard.