Description:
Decentralized Finance (DeFi) protocol contract providing Factory functionality.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/* ---------- Minimal OpenZeppelin-style base contracts ---------- */
abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } }
contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) { _owner = initialOwner; emit OwnershipTransferred(address(0), initialOwner); }
modifier onlyOwner() { require(owner() == _msgSender(), "Ownable: caller is not the owner"); _; }
function owner() public view returns (address) { return _owner; }
}
contract ERC20 {
string public name; string public symbol; uint8 public constant decimals = 18;
uint256 public totalSupply;
mapping(address=>uint256) public balanceOf;
mapping(address=>mapping(address=>uint256)) public allowance;
event Transfer(address indexed from,address indexed to,uint256 value);
event Approval(address indexed owner,address indexed spender,uint256 value);
constructor(string memory n,string memory s){name=n;symbol=s;}
function _mint(address to,uint256 amount) internal { totalSupply+=amount; balanceOf[to]+=amount; emit Transfer(address(0),to,amount); }
function _burn(address from,uint256 amount) internal { require(balanceOf[from]>=amount,"ERC20: burn amount exceeds balance"); balanceOf[from]-=amount; totalSupply-=amount; emit Transfer(from,address(0),amount); }
function approve(address spender,uint256 amount) external returns(bool){ allowance[msg.sender][spender]=amount; emit Approval(msg.sender,spender,amount); return true; }
function transfer(address to,uint256 amount) external returns(bool){ require(balanceOf[msg.sender]>=amount,"ERC20: insufficient"); balanceOf[msg.sender]-=amount; balanceOf[to]+=amount; emit Transfer(msg.sender,to,amount); return true; }
function transferFrom(address from,address to,uint256 amount) external returns(bool){
uint256 a=allowance[from][msg.sender]; require(a>=amount,"ERC20: no allowance");
if(a!=type(uint256).max){ allowance[from][msg.sender]=a-amount; }
require(balanceOf[from]>=amount,"ERC20: insufficient"); balanceOf[from]-=amount; balanceOf[to]+=amount; emit Transfer(from,to,amount); return true;
}
}
/* ---------- Interfaces ---------- */
interface IGrowthVault {
function getBalance() external view returns (uint256); // USDC (6 dec)
function unlockTime() external view returns (uint256);
function owner() external view returns (address);
}
library PythStructs { struct Price { int64 price; uint64 conf; int32 expo; uint publishTime; } }
interface IPyth {
function getUpdateFee(bytes[] calldata updateData) external view returns (uint256);
function updatePriceFeeds(bytes[] calldata updateData) external payable;
function getPriceUnsafe(bytes32 id) external view returns (PythStructs.Price memory);
}
/* ---------- Contract ---------- */
contract pXRPVaultWrapper_Pyth is ERC20, Ownable {
IGrowthVault public immutable vault;
IPyth public immutable pyth;
bytes32 public immutable XRP_PRICE_ID; // Crypto.XRP/USD
uint256 public collateralUSDC; // notional USDC reserved (6 dec)
struct Position { uint256 pXRP; uint256 entryCostUSDC; }
mapping(address => Position) public positions;
event Minted(address indexed user, uint256 usdcIn, int64 px, int32 expo, uint256 pXRPMinted);
event Burned(address indexed user, uint256 pXRPBurned, uint256 usdcReleased);
event SettlementQuoted(address indexed user, uint256 pXRPBal, int64 px, int32 expo, uint256 usdcNow, int256 pnlUSDC);
constructor(address _vault, address _pythContract, bytes32 _xrpPriceId)
ERC20("Synthetic XRP","pXRP")
Ownable(msg.sender)
{
require(_vault!=address(0)&&_pythContract!=address(0),"zero addr");
vault = IGrowthVault(_vault);
pyth = IPyth(_pythContract);
XRP_PRICE_ID = _xrpPriceId;
require(vault.owner()==msg.sender,"deployer must own vault");
}
/* ---------- Price helpers ---------- */
function _getXrpPx8(uint256 maxAgeSec) internal view returns (uint256 px8){
PythStructs.Price memory p=pyth.getPriceUnsafe(XRP_PRICE_ID);
require(p.price>0,"bad price");
require(block.timestamp-p.publishTime<=maxAgeSec,"stale price");
int32 targetExpo=-8;
int32 delta=targetExpo-p.expo;
if(delta>=0){ px8=uint256(int256(p.price))*(10**uint32(uint256(int256(delta)))); }
else{ px8=uint256(int256(p.price))/(10**uint32(uint256(int256(-delta)))); }
}
function availableUSDC() public view returns(uint256){
uint256 bal=vault.getBalance();
return bal>collateralUSDC?bal-collateralUSDC:0;
}
function previewMint(uint256 usdcAmount,uint256 maxAgeSec) external view returns(uint256 pXRPOut){
uint256 px8=_getXrpPx8(maxAgeSec);
pXRPOut=(usdcAmount*1e20)/px8;
}
function previewBurn(uint256 pXRPAmount,uint256 maxAgeSec) external view returns(uint256 usdcOut){
uint256 px8=_getXrpPx8(maxAgeSec);
usdcOut=(pXRPAmount*px8)/1e20;
}
/* ---------- Mint / Burn ---------- */
function mintByUSDC(uint256 usdcAmount,bytes[] calldata updateData,uint256 maxAgeSec) external payable onlyOwner{
require(usdcAmount>0,"zero amount");
require(usdcAmount<=availableUSDC(),"exceeds collateral");
if(updateData.length>0){
uint256 fee=pyth.getUpdateFee(updateData);
require(msg.value>=fee,"insufficient fee");
pyth.updatePriceFeeds{value:fee}(updateData);
if(msg.value>fee)payable(msg.sender).transfer(msg.value-fee);
}
uint256 px8=_getXrpPx8(maxAgeSec);
uint256 mintAmount=(usdcAmount*1e20)/px8;
collateralUSDC+=usdcAmount;
Position storage pos=positions[msg.sender];
pos.pXRP+=mintAmount;
pos.entryCostUSDC+=usdcAmount;
_mint(msg.sender,mintAmount);
PythStructs.Price memory p=pyth.getPriceUnsafe(XRP_PRICE_ID);
emit Minted(msg.sender,usdcAmount,p.price,p.expo,mintAmount);
}
function burn(uint256 pXRPAmount,bytes[] calldata updateData,uint256 maxAgeSec) external payable{
require(pXRPAmount>0,"zero amount");
require(balanceOf[msg.sender]>=pXRPAmount,"insufficient pXRP");
if(updateData.length>0){
uint256 fee=pyth.getUpdateFee(updateData);
require(msg.value>=fee,"insufficient fee");
pyth.updatePriceFeeds{value:fee}(updateData);
if(msg.value>fee)payable(msg.sender).transfer(msg.value-fee);
}
_burn(msg.sender,pXRPAmount);
uint256 px8=_getXrpPx8(maxAgeSec);
uint256 usdcReleased=(pXRPAmount*px8)/1e20;
if(usdcReleased>collateralUSDC)usdcReleased=collateralUSDC;
collateralUSDC-=usdcReleased;
Position storage pos=positions[msg.sender];
uint256 prevPXRP=pos.pXRP+pXRPAmount;
if(prevPXRP>0){
uint256 costToRemove=(pos.entryCostUSDC*pXRPAmount)/prevPXRP;
if(costToRemove>pos.entryCostUSDC)costToRemove=pos.entryCostUSDC;
pos.entryCostUSDC-=costToRemove;
}
pos.pXRP=balanceOf[msg.sender];
emit Burned(msg.sender,pXRPAmount,usdcReleased);
}
/* ---------- Settlement (post-unlock) ---------- */
function quoteRedeemNow(bytes[] calldata updateData,uint256 maxAgeSec)
external payable returns(uint256 usdcNow,int256 pnlUSDC)
{
require(block.timestamp>=vault.unlockTime(),"vault locked");
if(updateData.length>0){
uint256 fee=pyth.getUpdateFee(updateData);
require(msg.value>=fee,"insufficient fee");
pyth.updatePriceFeeds{value:fee}(updateData);
if(msg.value>fee)payable(msg.sender).transfer(msg.value-fee);
}
uint256 pBal=balanceOf[msg.sender];
require(pBal>0,"no pXRP");
uint256 px8=_getXrpPx8(maxAgeSec);
usdcNow=(pBal*px8)/1e20;
uint256 entryCost=positions[msg.sender].entryCostUSDC;
pnlUSDC=int256(usdcNow)-int256(entryCost);
PythStructs.Price memory p=pyth.getPriceUnsafe(XRP_PRICE_ID);
emit SettlementQuoted(msg.sender,pBal,p.price,p.expo,usdcNow,pnlUSDC);
}
function averageEntryPrice8(address user) external view returns(uint256){
Position memory pos=positions[user];
if(pos.pXRP==0)return 0;
return (pos.entryCostUSDC*1e20)/pos.pXRP;
}
}
Submitted on: 2025-10-07 11:31:35
Comments
Log in to comment.
No comments yet.