feat: recreate AgentServiceMarketplace.sol based on docs
This commit is contained in:
231
contracts/contracts/AgentServiceMarketplace.sol
Normal file
231
contracts/contracts/AgentServiceMarketplace.sol
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
// 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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title AgentServiceMarketplace
|
||||||
|
* @dev Advanced marketplace for AI agents to discover, offer, and monetize their capabilities and services.
|
||||||
|
*/
|
||||||
|
contract AgentServiceMarketplace is Ownable, ReentrancyGuard, Pausable {
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
IERC20 public aitbcToken;
|
||||||
|
|
||||||
|
uint256 public serviceCounter;
|
||||||
|
uint256 public subscriptionCounter;
|
||||||
|
uint256 public platformFeePercentage = 250; // 2.5% in basis points (10000 = 100%)
|
||||||
|
|
||||||
|
struct ServiceOffering {
|
||||||
|
uint256 serviceId;
|
||||||
|
address providerAgent;
|
||||||
|
string capabilityURI; // IPFS hash containing service metadata (inputs, outputs, SLA)
|
||||||
|
uint256 pricePerUse;
|
||||||
|
uint256 subscriptionPricePerMonth;
|
||||||
|
bool isSubscriptionAvailable;
|
||||||
|
bool isActive;
|
||||||
|
uint256 totalUses;
|
||||||
|
uint256 totalRevenue;
|
||||||
|
uint256 reputationScore; // Updated via cross-chain reputation or oracle
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Subscription {
|
||||||
|
uint256 subscriptionId;
|
||||||
|
uint256 serviceId;
|
||||||
|
address subscriberAgent;
|
||||||
|
uint256 expiryTimestamp;
|
||||||
|
bool isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping(uint256 => ServiceOffering) public services;
|
||||||
|
mapping(uint256 => Subscription) public subscriptions;
|
||||||
|
mapping(address => uint256[]) public providerServices;
|
||||||
|
mapping(address => uint256[]) public subscriberSubscriptions;
|
||||||
|
|
||||||
|
// Events
|
||||||
|
event ServiceRegistered(uint256 indexed serviceId, address indexed provider, string capabilityURI, uint256 pricePerUse);
|
||||||
|
event ServiceUpdated(uint256 indexed serviceId, uint256 pricePerUse, bool isActive);
|
||||||
|
event ServicePurchased(uint256 indexed serviceId, address indexed buyer, uint256 pricePaid);
|
||||||
|
event SubscriptionCreated(uint256 indexed subscriptionId, uint256 indexed serviceId, address indexed subscriber, uint256 expiryTimestamp);
|
||||||
|
event SubscriptionRenewed(uint256 indexed subscriptionId, uint256 newExpiryTimestamp);
|
||||||
|
event PlatformFeeUpdated(uint256 newFeePercentage);
|
||||||
|
event ServiceReputationUpdated(uint256 indexed serviceId, uint256 newScore);
|
||||||
|
|
||||||
|
modifier serviceExists(uint256 _serviceId) {
|
||||||
|
require(_serviceId < serviceCounter, "Service does not exist");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(address _aitbcToken) {
|
||||||
|
require(_aitbcToken != address(0), "Invalid token address");
|
||||||
|
aitbcToken = IERC20(_aitbcToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Register a new agent capability/service
|
||||||
|
* @param _capabilityURI IPFS hash for service metadata
|
||||||
|
* @param _pricePerUse Price to pay per individual API/capability call
|
||||||
|
* @param _subscriptionPricePerMonth Price for unlimited/tiered monthly access
|
||||||
|
* @param _isSubscriptionAvailable Boolean flag enabling subscriptions
|
||||||
|
*/
|
||||||
|
function registerService(
|
||||||
|
string calldata _capabilityURI,
|
||||||
|
uint256 _pricePerUse,
|
||||||
|
uint256 _subscriptionPricePerMonth,
|
||||||
|
bool _isSubscriptionAvailable
|
||||||
|
) external whenNotPaused returns (uint256) {
|
||||||
|
require(bytes(_capabilityURI).length > 0, "Invalid URI");
|
||||||
|
|
||||||
|
uint256 serviceId = serviceCounter++;
|
||||||
|
|
||||||
|
services[serviceId] = ServiceOffering({
|
||||||
|
serviceId: serviceId,
|
||||||
|
providerAgent: msg.sender,
|
||||||
|
capabilityURI: _capabilityURI,
|
||||||
|
pricePerUse: _pricePerUse,
|
||||||
|
subscriptionPricePerMonth: _subscriptionPricePerMonth,
|
||||||
|
isSubscriptionAvailable: _isSubscriptionAvailable,
|
||||||
|
isActive: true,
|
||||||
|
totalUses: 0,
|
||||||
|
totalRevenue: 0,
|
||||||
|
reputationScore: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
providerServices[msg.sender].push(serviceId);
|
||||||
|
|
||||||
|
emit ServiceRegistered(serviceId, msg.sender, _capabilityURI, _pricePerUse);
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Update an existing service offering
|
||||||
|
*/
|
||||||
|
function updateService(
|
||||||
|
uint256 _serviceId,
|
||||||
|
uint256 _pricePerUse,
|
||||||
|
uint256 _subscriptionPricePerMonth,
|
||||||
|
bool _isSubscriptionAvailable,
|
||||||
|
bool _isActive
|
||||||
|
) external serviceExists(_serviceId) {
|
||||||
|
ServiceOffering storage service = services[_serviceId];
|
||||||
|
require(service.providerAgent == msg.sender, "Not service provider");
|
||||||
|
|
||||||
|
service.pricePerUse = _pricePerUse;
|
||||||
|
service.subscriptionPricePerMonth = _subscriptionPricePerMonth;
|
||||||
|
service.isSubscriptionAvailable = _isSubscriptionAvailable;
|
||||||
|
service.isActive = _isActive;
|
||||||
|
|
||||||
|
emit ServiceUpdated(_serviceId, _pricePerUse, _isActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Purchase a single use of an agent service
|
||||||
|
*/
|
||||||
|
function purchaseService(uint256 _serviceId) external nonReentrant whenNotPaused serviceExists(_serviceId) {
|
||||||
|
ServiceOffering storage service = services[_serviceId];
|
||||||
|
require(service.isActive, "Service inactive");
|
||||||
|
require(service.pricePerUse > 0, "Not available for single use");
|
||||||
|
|
||||||
|
uint256 platformFee = (service.pricePerUse * platformFeePercentage) / 10000;
|
||||||
|
uint256 providerAmount = service.pricePerUse - platformFee;
|
||||||
|
|
||||||
|
// Transfer funds
|
||||||
|
aitbcToken.safeTransferFrom(msg.sender, address(this), service.pricePerUse);
|
||||||
|
|
||||||
|
// Pay provider
|
||||||
|
if (providerAmount > 0) {
|
||||||
|
aitbcToken.safeTransfer(service.providerAgent, providerAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retain platform fee in contract (owner can withdraw)
|
||||||
|
|
||||||
|
service.totalUses += 1;
|
||||||
|
service.totalRevenue += providerAmount;
|
||||||
|
|
||||||
|
emit ServicePurchased(_serviceId, msg.sender, service.pricePerUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Subscribe to an agent service for 30 days
|
||||||
|
*/
|
||||||
|
function subscribeToService(uint256 _serviceId) external nonReentrant whenNotPaused serviceExists(_serviceId) returns (uint256) {
|
||||||
|
ServiceOffering storage service = services[_serviceId];
|
||||||
|
require(service.isActive, "Service inactive");
|
||||||
|
require(service.isSubscriptionAvailable, "Subscriptions not enabled");
|
||||||
|
|
||||||
|
uint256 platformFee = (service.subscriptionPricePerMonth * platformFeePercentage) / 10000;
|
||||||
|
uint256 providerAmount = service.subscriptionPricePerMonth - platformFee;
|
||||||
|
|
||||||
|
// Transfer funds
|
||||||
|
aitbcToken.safeTransferFrom(msg.sender, address(this), service.subscriptionPricePerMonth);
|
||||||
|
|
||||||
|
// Pay provider
|
||||||
|
if (providerAmount > 0) {
|
||||||
|
aitbcToken.safeTransfer(service.providerAgent, providerAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
service.totalRevenue += providerAmount;
|
||||||
|
|
||||||
|
uint256 subId = subscriptionCounter++;
|
||||||
|
uint256 expiry = block.timestamp + 30 days;
|
||||||
|
|
||||||
|
subscriptions[subId] = Subscription({
|
||||||
|
subscriptionId: subId,
|
||||||
|
serviceId: _serviceId,
|
||||||
|
subscriberAgent: msg.sender,
|
||||||
|
expiryTimestamp: expiry,
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
subscriberSubscriptions[msg.sender].push(subId);
|
||||||
|
|
||||||
|
emit SubscriptionCreated(subId, _serviceId, msg.sender, expiry);
|
||||||
|
return subId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Check if a subscription is still active and valid
|
||||||
|
*/
|
||||||
|
function checkSubscription(uint256 _subscriptionId) external view returns (bool) {
|
||||||
|
Subscription memory sub = subscriptions[_subscriptionId];
|
||||||
|
return sub.isActive && (block.timestamp < sub.expiryTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Update the reputation score of a service (Oracle/Owner only)
|
||||||
|
*/
|
||||||
|
function updateServiceReputation(uint256 _serviceId, uint256 _newScore) external onlyOwner serviceExists(_serviceId) {
|
||||||
|
services[_serviceId].reputationScore = _newScore;
|
||||||
|
emit ServiceReputationUpdated(_serviceId, _newScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Update platform fee percentage
|
||||||
|
*/
|
||||||
|
function updatePlatformFee(uint256 _newFee) external onlyOwner {
|
||||||
|
require(_newFee <= 1000, "Fee too high"); // Max 10%
|
||||||
|
platformFeePercentage = _newFee;
|
||||||
|
emit PlatformFeeUpdated(_newFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Withdraw accumulated platform fees
|
||||||
|
*/
|
||||||
|
function withdrawPlatformFees() external onlyOwner {
|
||||||
|
uint256 balance = aitbcToken.balanceOf(address(this));
|
||||||
|
require(balance > 0, "No fees to withdraw");
|
||||||
|
aitbcToken.safeTransfer(owner(), balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pause() external onlyOwner {
|
||||||
|
_pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function unpause() external onlyOwner {
|
||||||
|
_unpause();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user