- Change SQLite database path from `/home/oib/windsurf/aitbc/data/` to `/opt/data/` - Fix foreign key references to use correct table names (users, wallets, gpu_registry) - Replace governance router with new governance and community routers - Add multi-modal RL router to main application - Simplify DEPLOYMENT_READINESS_REPORT.md to focus on production deployment status - Update governance router with decentralized DAO voting
697 lines
20 KiB
Solidity
697 lines
20 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
import "@openzeppelin/contracts/security/Pausable.sol";
|
|
import "./AIPowerRental.sol";
|
|
|
|
/**
|
|
* @title AITBC Payment Processor
|
|
* @dev Advanced payment processing contract with escrow, automated releases, and dispute resolution
|
|
* @notice Handles AITBC token payments for AI power rental services
|
|
*/
|
|
contract AITBCPaymentProcessor is Ownable, ReentrancyGuard, Pausable {
|
|
|
|
// State variables
|
|
IERC20 public aitbcToken;
|
|
AIPowerRental public aiPowerRental;
|
|
|
|
uint256 public paymentCounter;
|
|
uint256 public platformFeePercentage = 250; // 2.5% in basis points
|
|
uint256 public disputeResolutionFee = 100; // 1% in basis points
|
|
uint256 public minPaymentAmount = 1e15; // 0.001 AITBC minimum
|
|
uint256 public maxPaymentAmount = 1e22; // 10,000 AITBC maximum
|
|
|
|
// Structs
|
|
struct Payment {
|
|
uint256 paymentId;
|
|
address from;
|
|
address to;
|
|
uint256 amount;
|
|
uint256 platformFee;
|
|
uint256 disputeFee;
|
|
PaymentStatus status;
|
|
uint256 releaseTime;
|
|
uint256 createdTime;
|
|
uint256 confirmedTime;
|
|
bytes32 agreementId;
|
|
string paymentPurpose;
|
|
ReleaseCondition releaseCondition;
|
|
bytes32 conditionHash;
|
|
}
|
|
|
|
struct EscrowAccount {
|
|
uint256 escrowId;
|
|
address depositor;
|
|
address beneficiary;
|
|
uint256 amount;
|
|
uint256 releaseTime;
|
|
bool isReleased;
|
|
bool isRefunded;
|
|
bytes32 releaseCondition;
|
|
uint256 createdTime;
|
|
EscrowType escrowType;
|
|
}
|
|
|
|
struct ScheduledPayment {
|
|
uint256 scheduleId;
|
|
uint256 paymentId;
|
|
uint256 nextReleaseTime;
|
|
uint256 releaseInterval;
|
|
uint256 totalReleases;
|
|
uint256 releasedCount;
|
|
bool isActive;
|
|
}
|
|
|
|
// Enums
|
|
enum PaymentStatus {
|
|
Created,
|
|
Confirmed,
|
|
HeldInEscrow,
|
|
Released,
|
|
Refunded,
|
|
Disputed,
|
|
Cancelled
|
|
}
|
|
|
|
enum EscrowType {
|
|
Standard,
|
|
PerformanceBased,
|
|
TimeBased,
|
|
Conditional
|
|
}
|
|
|
|
enum ReleaseCondition {
|
|
Immediate,
|
|
Manual,
|
|
Performance,
|
|
TimeBased,
|
|
DisputeResolution
|
|
}
|
|
|
|
// Mappings
|
|
mapping(uint256 => Payment) public payments;
|
|
mapping(uint256 => EscrowAccount) public escrowAccounts;
|
|
mapping(uint256 => ScheduledPayment) public scheduledPayments;
|
|
mapping(address => uint256[]) public senderPayments;
|
|
mapping(address => uint256[]) public recipientPayments;
|
|
mapping(bytes32 => uint256) public agreementPayments;
|
|
mapping(address => uint256) public userEscrowBalance;
|
|
mapping(address => bool) public authorizedPayees;
|
|
mapping(address => bool) public authorizedPayers;
|
|
|
|
// Events
|
|
event PaymentCreated(
|
|
uint256 indexed paymentId,
|
|
address indexed from,
|
|
address indexed to,
|
|
uint256 amount,
|
|
bytes32 agreementId,
|
|
string paymentPurpose
|
|
);
|
|
|
|
event PaymentConfirmed(
|
|
uint256 indexed paymentId,
|
|
uint256 confirmedTime,
|
|
bytes32 transactionHash
|
|
);
|
|
|
|
event PaymentReleased(
|
|
uint256 indexed paymentId,
|
|
address indexed to,
|
|
uint256 amount,
|
|
uint256 platformFee
|
|
);
|
|
|
|
event PaymentRefunded(
|
|
uint256 indexed paymentId,
|
|
address indexed to,
|
|
uint256 amount,
|
|
string reason
|
|
);
|
|
|
|
event EscrowCreated(
|
|
uint256 indexed escrowId,
|
|
address indexed depositor,
|
|
address indexed beneficiary,
|
|
uint256 amount,
|
|
EscrowType escrowType
|
|
);
|
|
|
|
event EscrowReleased(
|
|
uint256 indexed escrowId,
|
|
uint256 amount,
|
|
bytes32 conditionHash
|
|
);
|
|
|
|
event EscrowRefunded(
|
|
uint256 indexed escrowId,
|
|
address indexed depositor,
|
|
uint256 amount,
|
|
string reason
|
|
);
|
|
|
|
event ScheduledPaymentCreated(
|
|
uint256 indexed scheduleId,
|
|
uint256 indexed paymentId,
|
|
uint256 nextReleaseTime,
|
|
uint256 releaseInterval
|
|
);
|
|
|
|
event ScheduledPaymentReleased(
|
|
uint256 indexed scheduleId,
|
|
uint256 indexed paymentId,
|
|
uint256 releaseCount
|
|
);
|
|
|
|
event DisputeInitiated(
|
|
uint256 indexed paymentId,
|
|
address indexed initiator,
|
|
string reason
|
|
);
|
|
|
|
event DisputeResolved(
|
|
uint256 indexed paymentId,
|
|
uint256 resolutionAmount,
|
|
bool resolvedInFavorOfPayer
|
|
);
|
|
|
|
event PlatformFeeCollected(
|
|
uint256 indexed paymentId,
|
|
uint256 feeAmount,
|
|
address indexed collector
|
|
);
|
|
|
|
// Modifiers
|
|
modifier onlyAuthorizedPayer() {
|
|
require(authorizedPayers[msg.sender], "Not authorized payer");
|
|
_;
|
|
}
|
|
|
|
modifier onlyAuthorizedPayee() {
|
|
require(authorizedPayees[msg.sender], "Not authorized payee");
|
|
_;
|
|
}
|
|
|
|
modifier paymentExists(uint256 _paymentId) {
|
|
require(_paymentId < paymentCounter, "Payment does not exist");
|
|
_;
|
|
}
|
|
|
|
modifier validStatus(uint256 _paymentId, PaymentStatus _requiredStatus) {
|
|
require(payments[_paymentId].status == _requiredStatus, "Invalid payment status");
|
|
_;
|
|
}
|
|
|
|
modifier sufficientBalance(address _user, uint256 _amount) {
|
|
require(aitbcToken.balanceOf(_user) >= _amount, "Insufficient balance");
|
|
_;
|
|
}
|
|
|
|
modifier sufficientAllowance(address _user, uint256 _amount) {
|
|
require(aitbcToken.allowance(_user, address(this)) >= _amount, "Insufficient allowance");
|
|
_;
|
|
}
|
|
|
|
// Constructor
|
|
constructor(address _aitbcToken, address _aiPowerRental) {
|
|
aitbcToken = IERC20(_aitbcToken);
|
|
aiPowerRental = AIPowerRental(_aiPowerRental);
|
|
paymentCounter = 0;
|
|
}
|
|
|
|
/**
|
|
* @dev Creates a new payment
|
|
* @param _to Recipient address
|
|
* @param _amount Payment amount
|
|
* @param _agreementId Associated agreement ID
|
|
* @param _paymentPurpose Purpose of the payment
|
|
* @param _releaseCondition Release condition
|
|
*/
|
|
function createPayment(
|
|
address _to,
|
|
uint256 _amount,
|
|
bytes32 _agreementId,
|
|
string memory _paymentPurpose,
|
|
ReleaseCondition _releaseCondition
|
|
) external onlyAuthorizedPayer sufficientBalance(msg.sender, _amount) sufficientAllowance(msg.sender, _amount) nonReentrant whenNotPaused returns (uint256) {
|
|
require(_amount >= minPaymentAmount, "Amount below minimum");
|
|
require(_amount <= maxPaymentAmount, "Amount above maximum");
|
|
require(_to != address(0), "Invalid recipient");
|
|
require(authorizedPayees[_to], "Recipient not authorized");
|
|
|
|
uint256 paymentId = paymentCounter++;
|
|
uint256 platformFee = (_amount * platformFeePercentage) / 10000;
|
|
uint256 disputeFee = (_amount * disputeResolutionFee) / 10000;
|
|
uint256 totalAmount = _amount + platformFee + disputeFee;
|
|
|
|
payments[paymentId] = Payment({
|
|
paymentId: paymentId,
|
|
from: msg.sender,
|
|
to: _to,
|
|
amount: _amount,
|
|
platformFee: platformFee,
|
|
disputeFee: disputeFee,
|
|
status: PaymentStatus.Created,
|
|
releaseTime: 0,
|
|
createdTime: block.timestamp,
|
|
confirmedTime: 0,
|
|
agreementId: _agreementId,
|
|
paymentPurpose: _paymentPurpose,
|
|
releaseCondition: _releaseCondition,
|
|
conditionHash: bytes32(0)
|
|
});
|
|
|
|
senderPayments[msg.sender].push(paymentId);
|
|
recipientPayments[_to].push(paymentId);
|
|
|
|
if (_agreementId != bytes32(0)) {
|
|
agreementPayments[_agreementId] = paymentId;
|
|
}
|
|
|
|
// Transfer tokens to contract
|
|
require(
|
|
aitbcToken.transferFrom(msg.sender, address(this), totalAmount),
|
|
"Payment transfer failed"
|
|
);
|
|
|
|
emit PaymentCreated(paymentId, msg.sender, _to, _amount, _agreementId, _paymentPurpose);
|
|
|
|
return paymentId;
|
|
}
|
|
|
|
/**
|
|
* @dev Confirms a payment with transaction hash
|
|
* @param _paymentId ID of the payment
|
|
* @param _transactionHash Blockchain transaction hash
|
|
*/
|
|
function confirmPayment(uint256 _paymentId, bytes32 _transactionHash)
|
|
external
|
|
paymentExists(_paymentId)
|
|
validStatus(_paymentId, PaymentStatus.Created)
|
|
nonReentrant
|
|
{
|
|
Payment storage payment = payments[_paymentId];
|
|
|
|
require(msg.sender == payment.from, "Only payer can confirm");
|
|
|
|
payment.status = PaymentStatus.Confirmed;
|
|
payment.confirmedTime = block.timestamp;
|
|
payment.conditionHash = _transactionHash;
|
|
|
|
// Handle immediate release
|
|
if (payment.releaseCondition == ReleaseCondition.Immediate) {
|
|
_releasePayment(_paymentId);
|
|
} else if (payment.releaseCondition == ReleaseCondition.TimeBased) {
|
|
payment.status = PaymentStatus.HeldInEscrow;
|
|
payment.releaseTime = block.timestamp + 1 hours; // Default 1 hour hold
|
|
} else {
|
|
payment.status = PaymentStatus.HeldInEscrow;
|
|
}
|
|
|
|
emit PaymentConfirmed(_paymentId, block.timestamp, _transactionHash);
|
|
}
|
|
|
|
/**
|
|
* @dev Releases a payment to the recipient
|
|
* @param _paymentId ID of the payment
|
|
*/
|
|
function releasePayment(uint256 _paymentId)
|
|
external
|
|
paymentExists(_paymentId)
|
|
nonReentrant
|
|
{
|
|
Payment storage payment = payments[_paymentId];
|
|
|
|
require(
|
|
payment.status == PaymentStatus.Confirmed ||
|
|
payment.status == PaymentStatus.HeldInEscrow,
|
|
"Payment not ready for release"
|
|
);
|
|
|
|
if (payment.releaseCondition == ReleaseCondition.Manual) {
|
|
require(msg.sender == payment.from, "Only payer can release manually");
|
|
} else if (payment.releaseCondition == ReleaseCondition.TimeBased) {
|
|
require(block.timestamp >= payment.releaseTime, "Release time not reached");
|
|
}
|
|
|
|
_releasePayment(_paymentId);
|
|
}
|
|
|
|
/**
|
|
* @dev Creates an escrow account
|
|
* @param _beneficiary Beneficiary address
|
|
* @param _amount Amount to lock in escrow
|
|
* @param _releaseTime Release time (0 for no time limit)
|
|
* @param _escrowType Type of escrow
|
|
* @param _releaseCondition Release condition hash
|
|
*/
|
|
function createEscrow(
|
|
address _beneficiary,
|
|
uint256 _amount,
|
|
uint256 _releaseTime,
|
|
EscrowType _escrowType,
|
|
bytes32 _releaseCondition
|
|
) external onlyAuthorizedPayer sufficientBalance(msg.sender, _amount) sufficientAllowance(msg.sender, _amount) nonReentrant whenNotPaused returns (uint256) {
|
|
require(_beneficiary != address(0), "Invalid beneficiary");
|
|
require(_amount >= minPaymentAmount, "Amount below minimum");
|
|
|
|
uint256 escrowId = paymentCounter++;
|
|
|
|
escrowAccounts[escrowId] = EscrowAccount({
|
|
escrowId: escrowId,
|
|
depositor: msg.sender,
|
|
beneficiary: _beneficiary,
|
|
amount: _amount,
|
|
releaseTime: _releaseTime,
|
|
isReleased: false,
|
|
isRefunded: false,
|
|
releaseCondition: _releaseCondition,
|
|
createdTime: block.timestamp,
|
|
escrowType: _escrowType
|
|
});
|
|
|
|
// Transfer tokens to contract
|
|
require(
|
|
aitbcToken.transferFrom(msg.sender, address(this), _amount),
|
|
"Escrow transfer failed"
|
|
);
|
|
|
|
userEscrowBalance[msg.sender] += _amount;
|
|
|
|
emit EscrowCreated(escrowId, msg.sender, _beneficiary, _amount, _escrowType);
|
|
|
|
return escrowId;
|
|
}
|
|
|
|
/**
|
|
* @dev Releases escrow to beneficiary
|
|
* @param _escrowId ID of the escrow account
|
|
*/
|
|
function releaseEscrow(uint256 _escrowId)
|
|
external
|
|
nonReentrant
|
|
{
|
|
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
|
|
|
require(!escrow.isReleased, "Escrow already released");
|
|
require(!escrow.isRefunded, "Escrow already refunded");
|
|
require(
|
|
escrow.releaseTime == 0 || block.timestamp >= escrow.releaseTime,
|
|
"Release time not reached"
|
|
);
|
|
|
|
escrow.isReleased = true;
|
|
userEscrowBalance[escrow.depositor] -= escrow.amount;
|
|
|
|
require(
|
|
aitbcToken.transfer(escrow.beneficiary, escrow.amount),
|
|
"Escrow release failed"
|
|
);
|
|
|
|
emit EscrowReleased(_escrowId, escrow.amount, escrow.releaseCondition);
|
|
}
|
|
|
|
/**
|
|
* @dev Refunds escrow to depositor
|
|
* @param _escrowId ID of the escrow account
|
|
* @param _reason Reason for refund
|
|
*/
|
|
function refundEscrow(uint256 _escrowId, string memory _reason)
|
|
external
|
|
nonReentrant
|
|
{
|
|
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
|
|
|
require(!escrow.isReleased, "Escrow already released");
|
|
require(!escrow.isRefunded, "Escrow already refunded");
|
|
require(
|
|
msg.sender == escrow.depositor || msg.sender == owner(),
|
|
"Only depositor or owner can refund"
|
|
);
|
|
|
|
escrow.isRefunded = true;
|
|
userEscrowBalance[escrow.depositor] -= escrow.amount;
|
|
|
|
require(
|
|
aitbcToken.transfer(escrow.depositor, escrow.amount),
|
|
"Escrow refund failed"
|
|
);
|
|
|
|
emit EscrowRefunded(_escrowId, escrow.depositor, escrow.amount, _reason);
|
|
}
|
|
|
|
/**
|
|
* @dev Initiates a dispute for a payment
|
|
* @param _paymentId ID of the payment
|
|
* @param _reason Reason for dispute
|
|
*/
|
|
function initiateDispute(uint256 _paymentId, string memory _reason)
|
|
external
|
|
paymentExists(_paymentId)
|
|
nonReentrant
|
|
{
|
|
Payment storage payment = payments[_paymentId];
|
|
|
|
require(
|
|
payment.status == PaymentStatus.Confirmed ||
|
|
payment.status == PaymentStatus.HeldInEscrow,
|
|
"Cannot dispute this payment"
|
|
);
|
|
|
|
require(
|
|
msg.sender == payment.from || msg.sender == payment.to,
|
|
"Only payment participants can dispute"
|
|
);
|
|
|
|
payment.status = PaymentStatus.Disputed;
|
|
|
|
emit DisputeInitiated(_paymentId, msg.sender, _reason);
|
|
}
|
|
|
|
/**
|
|
* @dev Resolves a dispute
|
|
* @param _paymentId ID of the disputed payment
|
|
* @param _resolutionAmount Amount to award to the winner
|
|
* @param _resolveInFavorOfPayer True if resolving in favor of payer
|
|
*/
|
|
function resolveDispute(
|
|
uint256 _paymentId,
|
|
uint256 _resolutionAmount,
|
|
bool _resolveInFavorOfPayer
|
|
) external onlyOwner paymentExists(_paymentId) nonReentrant {
|
|
Payment storage payment = payments[_paymentId];
|
|
|
|
require(payment.status == PaymentStatus.Disputed, "Payment not disputed");
|
|
require(_resolutionAmount <= payment.amount, "Resolution amount too high");
|
|
|
|
address winner = _resolveInFavorOfPayer ? payment.from : payment.to;
|
|
address loser = _resolveInFavorOfPayer ? payment.to : payment.from;
|
|
|
|
// Calculate refund for loser
|
|
uint256 refundAmount = payment.amount - _resolutionAmount;
|
|
|
|
// Transfer resolution amount to winner
|
|
if (_resolutionAmount > 0) {
|
|
require(
|
|
aitbcToken.transfer(winner, _resolutionAmount),
|
|
"Resolution payment failed"
|
|
);
|
|
}
|
|
|
|
// Refund remaining amount to loser
|
|
if (refundAmount > 0) {
|
|
require(
|
|
aitbcToken.transfer(loser, refundAmount),
|
|
"Refund payment failed"
|
|
);
|
|
}
|
|
|
|
payment.status = PaymentStatus.Released;
|
|
|
|
emit DisputeResolved(_paymentId, _resolutionAmount, _resolveInFavorOfPayer);
|
|
}
|
|
|
|
/**
|
|
* @dev Claims platform fees
|
|
* @param _paymentId ID of the payment
|
|
*/
|
|
function claimPlatformFee(uint256 _paymentId)
|
|
external
|
|
onlyOwner
|
|
paymentExists(_paymentId)
|
|
nonReentrant
|
|
{
|
|
Payment storage payment = payments[_paymentId];
|
|
|
|
require(payment.status == PaymentStatus.Released, "Payment not released");
|
|
require(payment.platformFee > 0, "No platform fee to claim");
|
|
|
|
uint256 feeAmount = payment.platformFee;
|
|
payment.platformFee = 0;
|
|
|
|
require(
|
|
aitbcToken.transfer(owner(), feeAmount),
|
|
"Platform fee transfer failed"
|
|
);
|
|
|
|
emit PlatformFeeCollected(_paymentId, feeAmount, owner());
|
|
}
|
|
|
|
/**
|
|
* @dev Authorizes a payee
|
|
* @param _payee Address to authorize
|
|
*/
|
|
function authorizePayee(address _payee) external onlyOwner {
|
|
authorizedPayees[_payee] = true;
|
|
}
|
|
|
|
/**
|
|
* @dev Revokes payee authorization
|
|
* @param _payee Address to revoke
|
|
*/
|
|
function revokePayee(address _payee) external onlyOwner {
|
|
authorizedPayees[_payee] = false;
|
|
}
|
|
|
|
/**
|
|
* @dev Authorizes a payer
|
|
* @param _payer Address to authorize
|
|
*/
|
|
function authorizePayer(address _payer) external onlyOwner {
|
|
authorizedPayers[_payer] = true;
|
|
}
|
|
|
|
/**
|
|
* @dev Revokes payer authorization
|
|
* @param _payer Address to revoke
|
|
*/
|
|
function revokePayer(address _payer) external onlyOwner {
|
|
authorizedPayers[_payer] = false;
|
|
}
|
|
|
|
/**
|
|
* @dev Updates platform fee percentage
|
|
* @param _newFee New fee percentage in basis points
|
|
*/
|
|
function updatePlatformFee(uint256 _newFee) external onlyOwner {
|
|
require(_newFee <= 1000, "Fee too high"); // Max 10%
|
|
platformFeePercentage = _newFee;
|
|
}
|
|
|
|
/**
|
|
* @dev Emergency pause function
|
|
*/
|
|
function pause() external onlyOwner {
|
|
_pause();
|
|
}
|
|
|
|
/**
|
|
* @dev Unpause function
|
|
*/
|
|
function unpause() external onlyOwner {
|
|
_unpause();
|
|
}
|
|
|
|
// Internal functions
|
|
|
|
function _releasePayment(uint256 _paymentId) internal {
|
|
Payment storage payment = payments[_paymentId];
|
|
|
|
payment.status = PaymentStatus.Released;
|
|
|
|
// Transfer amount to recipient
|
|
require(
|
|
aitbcToken.transfer(payment.to, payment.amount),
|
|
"Payment transfer failed"
|
|
);
|
|
|
|
// Transfer platform fee to owner
|
|
if (payment.platformFee > 0) {
|
|
require(
|
|
aitbcToken.transfer(owner(), payment.platformFee),
|
|
"Platform fee transfer failed"
|
|
);
|
|
}
|
|
|
|
emit PaymentReleased(_paymentId, payment.to, payment.amount, payment.platformFee);
|
|
}
|
|
|
|
// View functions
|
|
|
|
/**
|
|
* @dev Gets payment details
|
|
* @param _paymentId ID of the payment
|
|
*/
|
|
function getPayment(uint256 _paymentId)
|
|
external
|
|
view
|
|
paymentExists(_paymentId)
|
|
returns (Payment memory)
|
|
{
|
|
return payments[_paymentId];
|
|
}
|
|
|
|
/**
|
|
* @dev Gets escrow account details
|
|
* @param _escrowId ID of the escrow account
|
|
*/
|
|
function getEscrowAccount(uint256 _escrowId)
|
|
external
|
|
view
|
|
returns (EscrowAccount memory)
|
|
{
|
|
return escrowAccounts[_escrowId];
|
|
}
|
|
|
|
/**
|
|
* @dev Gets all payments for a sender
|
|
* @param _sender Address of the sender
|
|
*/
|
|
function getSenderPayments(address _sender)
|
|
external
|
|
view
|
|
returns (uint256[] memory)
|
|
{
|
|
return senderPayments[_sender];
|
|
}
|
|
|
|
/**
|
|
* @dev Gets all payments for a recipient
|
|
* @param _recipient Address of the recipient
|
|
*/
|
|
function getRecipientPayments(address _recipient)
|
|
external
|
|
view
|
|
returns (uint256[] memory)
|
|
{
|
|
return recipientPayments[_recipient];
|
|
}
|
|
|
|
/**
|
|
* @dev Gets payment associated with an agreement
|
|
* @param _agreementId ID of the agreement
|
|
*/
|
|
function getAgreementPayment(bytes32 _agreementId)
|
|
external
|
|
view
|
|
returns (uint256)
|
|
{
|
|
return agreementPayments[_agreementId];
|
|
}
|
|
|
|
/**
|
|
* @dev Gets user's escrow balance
|
|
* @param _user Address of the user
|
|
*/
|
|
function getUserEscrowBalance(address _user)
|
|
external
|
|
view
|
|
returns (uint256)
|
|
{
|
|
return userEscrowBalance[_user];
|
|
}
|
|
}
|