Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"@openzeppelin/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"contracts/core/PriceOracle.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/IPriceOracle.sol";
interface IUniswapV2Pair {
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function token0() external view returns (address);
function token1() external view returns (address);
}
interface IUniswapV3Pool {
function slot0() external view returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint8 feeProtocol,
bool unlocked
);
function token0() external view returns (address);
function token1() external view returns (address);
function observe(uint32[] calldata secondsAgos) external view returns (
int56[] memory tickCumulatives,
uint160[] memory secondsPerLiquidityCumulativeX128s
);
}
/**
* @title PriceOracle
* @dev Multi-source price oracle for BWS token
* @notice Combines Chainlink, Uniswap V2, and Uniswap V3 for robust pricing
*/
contract PriceOracle is IPriceOracle, Ownable {
// Price sources
AggregatorV3Interface public chainlinkPriceFeed;
IUniswapV2Pair public uniswapV2Pair;
IUniswapV3Pool public uniswapV3Pool;
// Token addresses
address public immutable bwsToken;
address public immutable wethToken;
// Price configuration
uint256 public constant PRICE_STALENESS_THRESHOLD = 3600; // 1 hour
uint256 public constant TWAP_PERIOD = 600; // 10 minutes for TWAP
// Fallback price (last known good price)
uint256 public fallbackPrice;
uint256 public fallbackPriceTimestamp;
// Price source weights (out of 100)
uint8 public chainlinkWeight = 50;
uint8 public uniswapV2Weight = 25;
uint8 public uniswapV3Weight = 25;
// Events
event PriceSourceUpdated(string source, address indexed newAddress);
event PriceWeightsUpdated(uint8 chainlink, uint8 v2, uint8 v3);
event FallbackPriceUpdated(uint256 price, uint256 timestamp);
constructor(
address _bwsToken,
address _wethToken,
address _owner
) Ownable(_owner) {
require(_bwsToken != address(0), "Invalid BWS token");
require(_wethToken != address(0), "Invalid WETH token");
bwsToken = _bwsToken;
wethToken = _wethToken;
// Initialize with a default fallback price (0.001 ETH)
fallbackPrice = 1e15;
fallbackPriceTimestamp = block.timestamp;
}
/**
* @dev Set Chainlink price feed address
*/
function setChainlinkPriceFeed(address _priceFeed) external onlyOwner {
chainlinkPriceFeed = AggregatorV3Interface(_priceFeed);
emit PriceSourceUpdated("Chainlink", _priceFeed);
}
/**
* @dev Set Uniswap V2 pair address
*/
function setUniswapV2Pair(address _pair) external onlyOwner {
uniswapV2Pair = IUniswapV2Pair(_pair);
emit PriceSourceUpdated("UniswapV2", _pair);
}
/**
* @dev Set Uniswap V3 pool address
*/
function setUniswapV3Pool(address _pool) external onlyOwner {
uniswapV3Pool = IUniswapV3Pool(_pool);
emit PriceSourceUpdated("UniswapV3", _pool);
}
/**
* @dev Update price source weights
*/
function setPriceWeights(
uint8 _chainlinkWeight,
uint8 _uniswapV2Weight,
uint8 _uniswapV3Weight
) external onlyOwner {
require(
_chainlinkWeight + _uniswapV2Weight + _uniswapV3Weight == 100,
"Weights must sum to 100"
);
chainlinkWeight = _chainlinkWeight;
uniswapV2Weight = _uniswapV2Weight;
uniswapV3Weight = _uniswapV3Weight;
emit PriceWeightsUpdated(_chainlinkWeight, _uniswapV2Weight, _uniswapV3Weight);
}
/**
* @dev Get BWS price in ETH from all available sources
*/
function getBWSPriceInETH() external view override returns (uint256) {
uint256 weightedPrice = 0;
uint256 totalWeight = 0;
// Try to get price from each source
(uint256 chainlinkPrice, bool chainlinkValid) = _getChainlinkPrice();
(uint256 v2Price, bool v2Valid) = _getUniswapV2Price();
(uint256 v3Price, bool v3Valid) = _getUniswapV3Price();
// Calculate weighted average from valid sources
if (chainlinkValid) {
weightedPrice += chainlinkPrice * chainlinkWeight;
totalWeight += chainlinkWeight;
}
if (v2Valid) {
weightedPrice += v2Price * uniswapV2Weight;
totalWeight += uniswapV2Weight;
}
if (v3Valid) {
weightedPrice += v3Price * uniswapV3Weight;
totalWeight += uniswapV3Weight;
}
// If we have at least one valid price source
if (totalWeight > 0) {
return weightedPrice / totalWeight;
}
// If all sources fail, use fallback price
require(fallbackPrice > 0, "No valid price available");
return fallbackPrice;
}
/**
* @dev Get latest price data with metadata
*/
function getLatestPriceData() external view override returns (
uint256 price,
uint256 timestamp,
uint8 decimals
) {
price = this.getBWSPriceInETH();
timestamp = block.timestamp;
decimals = 18; // ETH has 18 decimals
}
/**
* @dev Check if price is stale
*/
function isPriceStale(uint256 maxAge) external view override returns (bool) {
// Check if we have fresh data from any source
if (address(chainlinkPriceFeed) != address(0)) {
(, , , uint256 updatedAt, ) = chainlinkPriceFeed.latestRoundData();
if (block.timestamp - updatedAt <= maxAge) {
return false;
}
}
// For Uniswap, we consider it fresh if a trade happened recently
if (address(uniswapV2Pair) != address(0)) {
(, , uint32 blockTimestampLast) = uniswapV2Pair.getReserves();
if (block.timestamp - blockTimestampLast <= maxAge) {
return false;
}
}
// Check fallback price age
return block.timestamp - fallbackPriceTimestamp > maxAge;
}
/**
* @dev Get price from Chainlink
*/
function _getChainlinkPrice() private view returns (uint256 price, bool valid) {
if (address(chainlinkPriceFeed) == address(0)) {
return (0, false);
}
try chainlinkPriceFeed.latestRoundData() returns (
uint80,
int256 _price,
uint256,
uint256 updatedAt,
uint80
) {
// Check if price is stale
if (block.timestamp - updatedAt > PRICE_STALENESS_THRESHOLD) {
return (0, false);
}
// Chainlink returns price with 8 decimals, convert to 18
if (_price > 0) {
return (uint256(_price) * 1e10, true);
}
} catch {
// Chainlink call failed
}
return (0, false);
}
/**
* @dev Get price from Uniswap V2
*/
function _getUniswapV2Price() private view returns (uint256 price, bool valid) {
if (address(uniswapV2Pair) == address(0)) {
return (0, false);
}
try uniswapV2Pair.getReserves() returns (
uint112 reserve0,
uint112 reserve1,
uint32 blockTimestampLast
) {
// Check if data is fresh
if (block.timestamp - blockTimestampLast > PRICE_STALENESS_THRESHOLD) {
return (0, false);
}
address token0 = uniswapV2Pair.token0();
// Determine which reserve is BWS and which is WETH
uint256 bwsReserve;
uint256 wethReserve;
if (token0 == bwsToken) {
bwsReserve = reserve0;
wethReserve = reserve1;
} else {
bwsReserve = reserve1;
wethReserve = reserve0;
}
if (bwsReserve > 0) {
// Price = WETH per BWS
price = (wethReserve * 1e18) / bwsReserve;
return (price, true);
}
} catch {
// V2 call failed
}
return (0, false);
}
/**
* @dev Get price from Uniswap V3 (using TWAP)
*/
function _getUniswapV3Price() private view returns (uint256 price, bool valid) {
if (address(uniswapV3Pool) == address(0)) {
return (0, false);
}
try uniswapV3Pool.slot0() returns (
uint160 sqrtPriceX96,
int24,
uint16,
uint16 observationCardinality,
uint16,
uint8,
bool
) {
if (observationCardinality == 0) {
return (0, false);
}
// Calculate price from sqrtPriceX96
// price = (sqrtPriceX96 / 2^96)^2
uint256 priceX192 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);
address token0 = uniswapV3Pool.token0();
// Adjust for token order
if (token0 == bwsToken) {
// Price is WETH/BWS
price = (priceX192 * 1e18) >> 192;
} else {
// Price is BWS/WETH, need to invert
// Use safe math to avoid overflow
if (priceX192 > 0) {
price = (1e18 * 1e18) / ((priceX192 * 1e18) >> 192);
}
}
return (price, true);
} catch {
// V3 call failed
}
return (0, false);
}
/**
* @dev Update fallback price (emergency use only)
*/
function updateFallbackPrice(uint256 _price) external onlyOwner {
require(_price > 0, "Invalid price");
fallbackPrice = _price;
fallbackPriceTimestamp = block.timestamp;
emit FallbackPriceUpdated(_price, block.timestamp);
}
/**
* @dev Get all price sources for debugging
*/
function getAllPrices() external view returns (
uint256 chainlinkPrice,
bool chainlinkValid,
uint256 v2Price,
bool v2Valid,
uint256 v3Price,
bool v3Valid,
uint256 weightedAverage
) {
(chainlinkPrice, chainlinkValid) = _getChainlinkPrice();
(v2Price, v2Valid) = _getUniswapV2Price();
(v3Price, v3Valid) = _getUniswapV3Price();
weightedAverage = this.getBWSPriceInETH();
}
}"
},
"contracts/interfaces/IPriceOracle.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
/**
* @title IPriceOracle
* @dev Interface for price oracle implementations
*/
interface IPriceOracle {
/**
* @dev Get the latest price of BWS token in ETH
* @return price The price of 1 BWS token in wei
*/
function getBWSPriceInETH() external view returns (uint256 price);
/**
* @dev Get the latest price with additional metadata
* @return price The price of 1 BWS token in wei
* @return timestamp The timestamp of the price update
* @return decimals The number of decimals in the price
*/
function getLatestPriceData() external view returns (
uint256 price,
uint256 timestamp,
uint8 decimals
);
/**
* @dev Check if the price feed is stale
* @param maxAge Maximum age in seconds for the price to be considered fresh
* @return isStale True if the price is older than maxAge
*/
function isPriceStale(uint256 maxAge) external view returns (bool isStale);
}
/**
* @title AggregatorV3Interface
* @dev Chainlink Price Feed Interface
*/
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(uint80 _roundId) external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData() external view returns (
uint80 roundId,
int256 price,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}
}}
Submitted on: 2025-09-21 16:22:16
Comments
Log in to comment.
No comments yet.