feat(coordinator-api): integrate dynamic pricing engine with GPU marketplace and add agent identity router
- Add DynamicPricingEngine and MarketDataCollector dependencies to GPU marketplace endpoints
- Implement dynamic pricing calculation for GPU registration with market_balance strategy
- Calculate real-time dynamic prices at booking time with confidence scores and pricing factors
- Enhance /marketplace/pricing/{model} endpoint with comprehensive dynamic pricing analysis
- Add static vs dynamic price
This commit is contained in:
492
contracts/contracts/AIServiceAMM.sol
Normal file
492
contracts/contracts/AIServiceAMM.sol
Normal file
@@ -0,0 +1,492 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||
import "@openzeppelin/contracts/security/Pausable.sol";
|
||||
import "@openzeppelin/contracts/utils/math/Math.sol";
|
||||
|
||||
/**
|
||||
* @title AIServiceAMM
|
||||
* @dev Automated Market Making protocol for AI service tokens
|
||||
* @notice Enables creation of liquidity pools and automated trading for AI services
|
||||
*/
|
||||
contract AIServiceAMM is Ownable, ReentrancyGuard, Pausable {
|
||||
using SafeERC20 for IERC20;
|
||||
using Math for uint256;
|
||||
|
||||
// Constants
|
||||
uint256 public constant MINIMUM_LIQUIDITY = 1000; // Minimum liquidity to prevent rounding errors
|
||||
uint256 public constant BASIS_POINTS = 10000; // 100% in basis points
|
||||
uint256 public constant MAX_FEE = 1000; // Maximum 10% fee
|
||||
uint256 public constant FEE_PRECISION = 1000000; // Fee calculation precision
|
||||
|
||||
// State variables
|
||||
uint256 public poolCounter;
|
||||
uint256 public defaultFee = 30; // 0.3% default fee
|
||||
uint256 public protocolFeePercentage = 20; // 20% of fees go to protocol
|
||||
address public protocolFeeRecipient;
|
||||
|
||||
// Structs
|
||||
struct LiquidityPool {
|
||||
uint256 poolId;
|
||||
address tokenA;
|
||||
address tokenB;
|
||||
uint256 reserveA;
|
||||
uint256 reserveB;
|
||||
uint256 totalLiquidity;
|
||||
uint256 feePercentage; // Pool-specific fee in basis points
|
||||
address lpToken; // LP token address for this pool
|
||||
bool isActive;
|
||||
uint256 created_at;
|
||||
uint256 lastTradeTime;
|
||||
uint256 volume24h;
|
||||
uint256 fee24h;
|
||||
}
|
||||
|
||||
struct LiquidityPosition {
|
||||
uint256 poolId;
|
||||
address provider;
|
||||
uint256 liquidityAmount;
|
||||
uint256 sharesOwned;
|
||||
uint256 lastDepositTime;
|
||||
uint256 unclaimedFees;
|
||||
}
|
||||
|
||||
struct SwapParams {
|
||||
uint256 poolId;
|
||||
address tokenIn;
|
||||
address tokenOut;
|
||||
uint256 amountIn;
|
||||
uint256 minAmountOut;
|
||||
address recipient;
|
||||
uint256 deadline;
|
||||
}
|
||||
|
||||
struct PoolMetrics {
|
||||
uint256 totalVolume;
|
||||
uint256 totalFees;
|
||||
uint256 tvl; // Total Value Locked
|
||||
uint256 apr; // Annual Percentage Rate for liquidity providers
|
||||
uint256 utilizationRate;
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => LiquidityPool) public pools;
|
||||
mapping(address => mapping(uint256 => LiquidityPosition)) public liquidityPositions;
|
||||
mapping(address => uint256[]) public providerPools;
|
||||
mapping(address => mapping(address => uint256)) public poolByTokenPair; // tokenA -> tokenB -> poolId
|
||||
|
||||
// Arrays
|
||||
uint256[] public activePoolIds;
|
||||
|
||||
// Events
|
||||
event PoolCreated(uint256 indexed poolId, address indexed tokenA, address indexed tokenB, uint256 fee);
|
||||
event LiquidityAdded(uint256 indexed poolId, address indexed provider, uint256 amountA, uint256 amountB, uint256 liquidity);
|
||||
event LiquidityRemoved(uint256 indexed poolId, address indexed provider, uint256 amountA, uint256 amountB, uint256 liquidity);
|
||||
event SwapExecuted(uint256 indexed poolId, address indexed recipient, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut);
|
||||
event FeesCollected(uint256 indexed poolId, uint256 protocolFees, uint256 lpFees);
|
||||
event PoolUpdated(uint256 indexed poolId, uint256 reserveA, uint256 reserveB);
|
||||
|
||||
// Modifiers
|
||||
modifier validPool(uint256 poolId) {
|
||||
require(poolId > 0 && poolId <= poolCounter, "Invalid pool ID");
|
||||
require(pools[poolId].isActive, "Pool not active");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validDeadline(uint256 deadline) {
|
||||
require(block.timestamp <= deadline, "Transaction expired");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier nonZeroAmount(uint256 amount) {
|
||||
require(amount > 0, "Amount must be greater than 0");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(address _protocolFeeRecipient) {
|
||||
protocolFeeRecipient = _protocolFeeRecipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates a new liquidity pool for two tokens
|
||||
* @param tokenA Address of the first token
|
||||
* @param tokenB Address of the second token
|
||||
* @param feePercentage Fee percentage in basis points
|
||||
* @return poolId The ID of the created pool
|
||||
*/
|
||||
function createPool(
|
||||
address tokenA,
|
||||
address tokenB,
|
||||
uint256 feePercentage
|
||||
) external nonReentrant whenNotPaused returns (uint256) {
|
||||
require(tokenA != tokenB, "Identical tokens");
|
||||
require(tokenA != address(0) && tokenB != address(0), "Zero address");
|
||||
require(feePercentage <= MAX_FEE, "Fee too high");
|
||||
require(poolByTokenPair[tokenA][tokenB] == 0 && poolByTokenPair[tokenB][tokenA] == 0, "Pool exists");
|
||||
|
||||
// Ensure tokenA < tokenB for consistency
|
||||
if (tokenA > tokenB) {
|
||||
(tokenA, tokenB) = (tokenB, tokenA);
|
||||
}
|
||||
|
||||
poolCounter++;
|
||||
uint256 poolId = poolCounter;
|
||||
|
||||
pools[poolId] = LiquidityPool({
|
||||
poolId: poolId,
|
||||
tokenA: tokenA,
|
||||
tokenB: tokenB,
|
||||
reserveA: 0,
|
||||
reserveB: 0,
|
||||
totalLiquidity: 0,
|
||||
feePercentage: feePercentage,
|
||||
lpToken: address(this), // Simplified LP token representation
|
||||
isActive: true,
|
||||
created_at: block.timestamp,
|
||||
lastTradeTime: 0,
|
||||
volume24h: 0,
|
||||
fee24h: 0
|
||||
});
|
||||
|
||||
poolByTokenPair[tokenA][tokenB] = poolId;
|
||||
poolByTokenPair[tokenB][tokenA] = poolId;
|
||||
activePoolIds.push(poolId);
|
||||
|
||||
emit PoolCreated(poolId, tokenA, tokenB, feePercentage);
|
||||
return poolId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Adds liquidity to a pool
|
||||
* @param poolId The pool ID
|
||||
* @param amountA Amount of tokenA to add
|
||||
* @param amountB Amount of tokenB to add
|
||||
* @param minAmountA Minimum amount of tokenA (slippage protection)
|
||||
* @param minAmountB Minimum amount of tokenB (slippage protection)
|
||||
* @return liquidityAmount The amount of liquidity tokens received
|
||||
*/
|
||||
function addLiquidity(
|
||||
uint256 poolId,
|
||||
uint256 amountA,
|
||||
uint256 amountB,
|
||||
uint256 minAmountA,
|
||||
uint256 minAmountB
|
||||
)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
validPool(poolId)
|
||||
nonZeroAmount(amountA)
|
||||
nonZeroAmount(amountB)
|
||||
returns (uint256 liquidityAmount)
|
||||
{
|
||||
LiquidityPool storage pool = pools[poolId];
|
||||
|
||||
// Calculate optimal amounts based on current reserves
|
||||
uint256 optimalAmountB = _calculateOptimalAmountB(poolId, amountA);
|
||||
|
||||
if (pool.reserveA == 0 && pool.reserveB == 0) {
|
||||
// First liquidity provider - set initial prices
|
||||
optimalAmountB = amountB;
|
||||
} else {
|
||||
require(amountB >= optimalAmountB, "Insufficient tokenB amount");
|
||||
}
|
||||
|
||||
// Transfer tokens to contract
|
||||
IERC20(pool.tokenA).safeTransferFrom(msg.sender, address(this), amountA);
|
||||
IERC20(pool.tokenB).safeTransferFrom(msg.sender, address(this), amountB);
|
||||
|
||||
// Calculate liquidity to mint
|
||||
if (pool.totalLiquidity == 0) {
|
||||
liquidityAmount = Math.sqrt(amountA * amountB) - MINIMUM_LIQUIDITY;
|
||||
pool.totalLiquidity += MINIMUM_LIQUIDITY; // Lock minimum liquidity
|
||||
} else {
|
||||
liquidityAmount = Math.min(
|
||||
(amountA * pool.totalLiquidity) / pool.reserveA,
|
||||
(amountB * pool.totalLiquidity) / pool.reserveB
|
||||
);
|
||||
}
|
||||
|
||||
require(liquidityAmount > 0, "Insufficient liquidity minted");
|
||||
|
||||
// Update pool reserves and liquidity
|
||||
pool.reserveA += amountA;
|
||||
pool.reserveB += amountB;
|
||||
pool.totalLiquidity += liquidityAmount;
|
||||
|
||||
// Update or create liquidity position
|
||||
LiquidityPosition storage position = liquidityPositions[msg.sender][poolId];
|
||||
position.poolId = poolId;
|
||||
position.provider = msg.sender;
|
||||
position.liquidityAmount += liquidityAmount;
|
||||
position.sharesOwned = (position.liquidityAmount * BASIS_POINTS) / pool.totalLiquidity;
|
||||
position.lastDepositTime = block.timestamp;
|
||||
|
||||
// Add to provider's pool list if new
|
||||
if (position.liquidityAmount == liquidityAmount) {
|
||||
providerPools[msg.sender].push(poolId);
|
||||
}
|
||||
|
||||
emit LiquidityAdded(poolId, msg.sender, amountA, amountB, liquidityAmount);
|
||||
emit PoolUpdated(poolId, pool.reserveA, pool.reserveB);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes liquidity from a pool
|
||||
* @param poolId The pool ID
|
||||
* @param liquidityAmount Amount of liquidity to remove
|
||||
* @param minAmountA Minimum amount of tokenA to receive
|
||||
* @param minAmountB Minimum amount of tokenB to receive
|
||||
* @return amountA Amount of tokenA received
|
||||
* @return amountB Amount of tokenB received
|
||||
*/
|
||||
function removeLiquidity(
|
||||
uint256 poolId,
|
||||
uint256 liquidityAmount,
|
||||
uint256 minAmountA,
|
||||
uint256 minAmountB
|
||||
)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
validPool(poolId)
|
||||
nonZeroAmount(liquidityAmount)
|
||||
returns (uint256 amountA, uint256 amountB)
|
||||
{
|
||||
LiquidityPool storage pool = pools[poolId];
|
||||
LiquidityPosition storage position = liquidityPositions[msg.sender][poolId];
|
||||
|
||||
require(position.liquidityAmount >= liquidityAmount, "Insufficient liquidity");
|
||||
|
||||
// Calculate amounts to receive
|
||||
amountA = (liquidityAmount * pool.reserveA) / pool.totalLiquidity;
|
||||
amountB = (liquidityAmount * pool.reserveB) / pool.totalLiquidity;
|
||||
|
||||
require(amountA >= minAmountA && amountB >= minAmountB, "Slippage protection");
|
||||
|
||||
// Update pool reserves and liquidity
|
||||
pool.reserveA -= amountA;
|
||||
pool.reserveB -= amountB;
|
||||
pool.totalLiquidity -= liquidityAmount;
|
||||
|
||||
// Update position
|
||||
position.liquidityAmount -= liquidityAmount;
|
||||
position.sharesOwned = (position.liquidityAmount * BASIS_POINTS) / pool.totalLiquidity;
|
||||
|
||||
// Transfer tokens to user
|
||||
IERC20(pool.tokenA).safeTransfer(msg.sender, amountA);
|
||||
IERC20(pool.tokenB).safeTransfer(msg.sender, amountB);
|
||||
|
||||
emit LiquidityRemoved(poolId, msg.sender, amountA, amountB, liquidityAmount);
|
||||
emit PoolUpdated(poolId, pool.reserveA, pool.reserveB);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Executes a token swap
|
||||
* @param params Swap parameters
|
||||
* @return amountOut Amount of tokens received
|
||||
*/
|
||||
function swap(SwapParams calldata params)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
validPool(params.poolId)
|
||||
validDeadline(params.deadline)
|
||||
nonZeroAmount(params.amountIn)
|
||||
returns (uint256 amountOut)
|
||||
{
|
||||
LiquidityPool storage pool = pools[params.poolId];
|
||||
|
||||
// Validate tokens
|
||||
require(
|
||||
params.tokenIn == pool.tokenA || params.tokenIn == pool.tokenB,
|
||||
"Invalid input token"
|
||||
);
|
||||
require(
|
||||
params.tokenOut == pool.tokenA || params.tokenOut == pool.tokenB,
|
||||
"Invalid output token"
|
||||
);
|
||||
require(params.tokenIn != params.tokenOut, "Same token swap");
|
||||
|
||||
// Calculate output amount
|
||||
amountOut = _calculateSwapOutput(params.poolId, params.amountIn, params.tokenIn);
|
||||
require(amountOut >= params.minAmountOut, "Insufficient output amount");
|
||||
|
||||
// Transfer input tokens
|
||||
IERC20(params.tokenIn).safeTransferFrom(msg.sender, address(this), params.amountIn);
|
||||
|
||||
// Update reserves
|
||||
if (params.tokenIn == pool.tokenA) {
|
||||
pool.reserveA += params.amountIn;
|
||||
pool.reserveB -= amountOut;
|
||||
} else {
|
||||
pool.reserveB += params.amountIn;
|
||||
pool.reserveA -= amountOut;
|
||||
}
|
||||
|
||||
// Transfer output tokens
|
||||
IERC20(params.tokenOut).safeTransfer(params.recipient, amountOut);
|
||||
|
||||
// Update pool metrics
|
||||
pool.lastTradeTime = block.timestamp;
|
||||
pool.volume24h += params.amountIn;
|
||||
|
||||
emit SwapExecuted(params.poolId, params.recipient, params.tokenIn, params.tokenOut, params.amountIn, amountOut);
|
||||
emit PoolUpdated(params.poolId, pool.reserveA, pool.reserveB);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculates the optimal amount of tokenB for adding liquidity
|
||||
* @param poolId The pool ID
|
||||
* @param amountA Amount of tokenA
|
||||
* @return optimalAmountB Optimal amount of tokenB
|
||||
*/
|
||||
function calculateOptimalSwap(uint256 poolId, uint256 amountIn)
|
||||
external
|
||||
view
|
||||
validPool(poolId)
|
||||
returns (uint256 amountOut)
|
||||
{
|
||||
return _calculateSwapOutput(poolId, amountIn, pools[poolId].tokenA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets pool metrics
|
||||
* @param poolId The pool ID
|
||||
* @return metrics Pool metrics
|
||||
*/
|
||||
function getPoolMetrics(uint256 poolId)
|
||||
external
|
||||
view
|
||||
validPool(poolId)
|
||||
returns (PoolMetrics memory metrics)
|
||||
{
|
||||
LiquidityPool storage pool = pools[poolId];
|
||||
|
||||
uint256 totalValue = pool.reserveA + pool.reserveB; // Simplified TVL calculation
|
||||
uint256 annualFees = pool.fee24h * 365; // Simplified APR calculation
|
||||
|
||||
metrics = PoolMetrics({
|
||||
totalVolume: pool.volume24h,
|
||||
totalFees: pool.fee24h,
|
||||
tvl: totalValue,
|
||||
apr: totalValue > 0 ? (annualFees * BASIS_POINTS) / totalValue : 0,
|
||||
utilizationRate: pool.totalLiquidity > 0 ? ((pool.volume24h * BASIS_POINTS) / pool.totalLiquidity) : 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the amount of liquidity tokens a user owns in a pool
|
||||
* @param provider The liquidity provider address
|
||||
* @param poolId The pool ID
|
||||
* @return liquidityAmount Amount of liquidity tokens
|
||||
*/
|
||||
function getLiquidityAmount(address provider, uint256 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256 liquidityAmount)
|
||||
{
|
||||
return liquidityPositions[provider][poolId].liquidityAmount;
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
function _calculateOptimalAmountB(uint256 poolId, uint256 amountA) internal view returns (uint256) {
|
||||
LiquidityPool storage pool = pools[poolId];
|
||||
if (pool.reserveA == 0) return 0;
|
||||
return (amountA * pool.reserveB) / pool.reserveA;
|
||||
}
|
||||
|
||||
function _calculateSwapOutput(uint256 poolId, uint256 amountIn, address tokenIn)
|
||||
internal
|
||||
view
|
||||
returns (uint256 amountOut)
|
||||
{
|
||||
LiquidityPool storage pool = pools[poolId];
|
||||
|
||||
uint256 reserveIn;
|
||||
uint256 reserveOut;
|
||||
|
||||
if (tokenIn == pool.tokenA) {
|
||||
reserveIn = pool.reserveA;
|
||||
reserveOut = pool.reserveB;
|
||||
} else {
|
||||
reserveIn = pool.reserveB;
|
||||
reserveOut = pool.reserveA;
|
||||
}
|
||||
|
||||
// Apply fee
|
||||
uint256 feeAmount = (amountIn * pool.feePercentage) / BASIS_POINTS;
|
||||
uint256 amountInAfterFee = amountIn - feeAmount;
|
||||
|
||||
// Calculate output using constant product formula
|
||||
amountOut = (amountInAfterFee * reserveOut) / (reserveIn + amountInAfterFee);
|
||||
|
||||
// Ensure minimum output
|
||||
require(amountOut > 0, "Insufficient output amount");
|
||||
require(reserveOut > amountOut, "Insufficient liquidity");
|
||||
}
|
||||
|
||||
function _collectFees(uint256 poolId) internal {
|
||||
LiquidityPool storage pool = pools[poolId];
|
||||
|
||||
if (pool.fee24h == 0) return;
|
||||
|
||||
uint256 protocolFees = (pool.fee24h * protocolFeePercentage) / BASIS_POINTS;
|
||||
uint256 lpFees = pool.fee24h - protocolFees;
|
||||
|
||||
// Distribute fees to liquidity providers
|
||||
if (lpFees > 0 && pool.totalLiquidity > 0) {
|
||||
for (uint i = 0; i < activePoolIds.length; i++) {
|
||||
uint256 currentPoolId = activePoolIds[i];
|
||||
if (currentPoolId == poolId) {
|
||||
// Simplified fee distribution
|
||||
// In production, this would be more sophisticated
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit FeesCollected(poolId, protocolFees, lpFees);
|
||||
pool.fee24h = 0;
|
||||
}
|
||||
|
||||
// Admin functions
|
||||
|
||||
function setProtocolFeeRecipient(address newRecipient) external onlyOwner {
|
||||
require(newRecipient != address(0), "Invalid address");
|
||||
protocolFeeRecipient = newRecipient;
|
||||
}
|
||||
|
||||
function setProtocolFeePercentage(uint256 newPercentage) external onlyOwner {
|
||||
require(newPercentage <= BASIS_POINTS, "Invalid percentage");
|
||||
protocolFeePercentage = newPercentage;
|
||||
}
|
||||
|
||||
function setDefaultFee(uint256 newFee) external onlyOwner {
|
||||
require(newFee <= MAX_FEE, "Fee too high");
|
||||
defaultFee = newFee;
|
||||
}
|
||||
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
|
||||
// Emergency functions
|
||||
|
||||
function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
|
||||
IERC20(token).safeTransfer(owner(), amount);
|
||||
}
|
||||
|
||||
function emergencyPause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user