X402PaymentStream

Description:

ERC20 token contract with Factory capabilities. Standard implementation for fungible tokens on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "@openzeppelin/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}
"
    },
    "contracts/X402PaymentStream.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @title X402PaymentStream
 * @dev Payment streaming contract for CryptoVibe device control
 * Enables continuous micro-payments that control device vibration intensity
 */
contract X402PaymentStream is ReentrancyGuard {
    
    struct Stream {
        address sender;
        address recipient;
        uint256 ratePerSecond; // JJC tokens per second
        uint256 startTime;
        uint256 lastClaimTime;
        uint256 balance; // Prepaid balance
        bool active;
    }
    
    IERC20 public token;
    
    mapping(bytes32 => Stream) public streams;
    mapping(address => bytes32[]) public userStreams;
    
    // Device control thresholds (JJC per minute -> vibration level)
    uint256 public constant LOW_THRESHOLD = 10 * 10**18;      // 10 JJC/min
    uint256 public constant MEDIUM_THRESHOLD = 50 * 10**18;   // 50 JJC/min
    uint256 public constant HIGH_THRESHOLD = 100 * 10**18;    // 100 JJC/min
    
    event StreamCreated(
        bytes32 indexed streamId,
        address indexed sender,
        address indexed recipient,
        uint256 ratePerSecond,
        uint256 initialBalance
    );
    
    event StreamUpdated(
        bytes32 indexed streamId,
        uint256 newRatePerSecond,
        uint256 addedBalance
    );
    
    event StreamClaimed(
        bytes32 indexed streamId,
        address indexed recipient,
        uint256 amount
    );
    
    event StreamCancelled(
        bytes32 indexed streamId,
        uint256 refundAmount
    );
    
    event VibrationLevelChanged(
        bytes32 indexed streamId,
        uint8 level // 0=off, 1=low, 2=medium, 3=high, 4=max
    );
    
    constructor(address _tokenAddress) {
        require(_tokenAddress != address(0), "Invalid token address");
        token = IERC20(_tokenAddress);
    }
    
    /**
     * @dev Create a new payment stream
     * @param recipient Device wallet address
     * @param ratePerSecond JJC tokens per second
     * @param prepaidAmount Initial balance to lock
     */
    function createStream(
        address recipient,
        uint256 ratePerSecond,
        uint256 prepaidAmount
    ) external nonReentrant returns (bytes32) {
        require(recipient != address(0), "Invalid recipient");
        require(ratePerSecond > 0, "Rate must be > 0");
        require(prepaidAmount > 0, "Must prepay some amount");
        
        // Generate unique stream ID
        bytes32 streamId = keccak256(
            abi.encodePacked(msg.sender, recipient, block.timestamp, block.number)
        );
        
        require(!streams[streamId].active, "Stream already exists");
        
        // Transfer tokens to contract
        require(
            token.transferFrom(msg.sender, address(this), prepaidAmount),
            "Transfer failed"
        );
        
        // Create stream
        streams[streamId] = Stream({
            sender: msg.sender,
            recipient: recipient,
            ratePerSecond: ratePerSecond,
            startTime: block.timestamp,
            lastClaimTime: block.timestamp,
            balance: prepaidAmount,
            active: true
        });
        
        userStreams[msg.sender].push(streamId);
        userStreams[recipient].push(streamId);
        
        emit StreamCreated(streamId, msg.sender, recipient, ratePerSecond, prepaidAmount);
        emit VibrationLevelChanged(streamId, getVibrationLevel(ratePerSecond));
        
        return streamId;
    }
    
    /**
     * @dev Update stream rate (changes vibration intensity)
     * @param streamId Stream identifier
     * @param newRatePerSecond New rate in JJC per second
     * @param additionalBalance Additional tokens to add to balance
     */
    function updateStream(
        bytes32 streamId,
        uint256 newRatePerSecond,
        uint256 additionalBalance
    ) external nonReentrant {
        Stream storage stream = streams[streamId];
        require(stream.active, "Stream not active");
        require(stream.sender == msg.sender, "Not stream owner");
        
        // Claim pending amount first
        _claimStream(streamId);
        
        // Update rate
        if (newRatePerSecond > 0 && newRatePerSecond != stream.ratePerSecond) {
            stream.ratePerSecond = newRatePerSecond;
            emit VibrationLevelChanged(streamId, getVibrationLevel(newRatePerSecond));
        }
        
        // Add balance
        if (additionalBalance > 0) {
            require(
                token.transferFrom(msg.sender, address(this), additionalBalance),
                "Transfer failed"
            );
            stream.balance += additionalBalance;
        }
        
        emit StreamUpdated(streamId, newRatePerSecond, additionalBalance);
    }
    
    /**
     * @dev Claim accumulated tokens from stream
     * @param streamId Stream identifier
     */
    function claimStream(bytes32 streamId) external nonReentrant {
        _claimStream(streamId);
    }
    
    /**
     * @dev Internal claim function
     */
    function _claimStream(bytes32 streamId) internal {
        Stream storage stream = streams[streamId];
        require(stream.active, "Stream not active");
        
        uint256 claimable = getClaimableAmount(streamId);
        
        if (claimable > 0) {
            stream.lastClaimTime = block.timestamp;
            stream.balance -= claimable;
            
            require(token.transfer(stream.recipient, claimable), "Transfer failed");
            
            emit StreamClaimed(streamId, stream.recipient, claimable);
        }
        
        // Auto-cancel if balance depleted
        if (stream.balance == 0) {
            stream.active = false;
            emit VibrationLevelChanged(streamId, 0); // Turn off device
        }
    }
    
    /**
     * @dev Cancel stream and refund remaining balance
     * @param streamId Stream identifier
     */
    function cancelStream(bytes32 streamId) external nonReentrant {
        Stream storage stream = streams[streamId];
        require(stream.active, "Stream not active");
        require(stream.sender == msg.sender, "Not stream owner");
        
        // Claim pending amount for recipient
        uint256 claimable = getClaimableAmount(streamId);
        if (claimable > 0) {
            require(token.transfer(stream.recipient, claimable), "Transfer failed");
            emit StreamClaimed(streamId, stream.recipient, claimable);
        }
        
        // Refund remaining balance to sender
        uint256 refund = stream.balance - claimable;
        if (refund > 0) {
            require(token.transfer(stream.sender, refund), "Refund failed");
        }
        
        stream.active = false;
        stream.balance = 0;
        
        emit StreamCancelled(streamId, refund);
        emit VibrationLevelChanged(streamId, 0); // Turn off device
    }
    
    /**
     * @dev Get claimable amount for a stream
     * @param streamId Stream identifier
     */
    function getClaimableAmount(bytes32 streamId) public view returns (uint256) {
        Stream memory stream = streams[streamId];
        if (!stream.active) return 0;
        
        uint256 elapsed = block.timestamp - stream.lastClaimTime;
        uint256 accumulated = elapsed * stream.ratePerSecond;
        
        // Return minimum of accumulated and available balance
        return accumulated > stream.balance ? stream.balance : accumulated;
    }
    
    /**
     * @dev Get remaining time before stream depletes
     * @param streamId Stream identifier
     */
    function getTimeRemaining(bytes32 streamId) external view returns (uint256) {
        Stream memory stream = streams[streamId];
        if (!stream.active || stream.ratePerSecond == 0) return 0;
        
        return stream.balance / stream.ratePerSecond;
    }
    
    /**
     * @dev Calculate vibration level based on payment rate
     * @param ratePerSecond JJC tokens per second
     * @return level 0=off, 1=low(20%), 2=medium(50%), 3=high(80%), 4=max(100%)
     */
    function getVibrationLevel(uint256 ratePerSecond) public pure returns (uint8) {
        // Convert to per-minute rate for easier threshold comparison
        uint256 ratePerMinute = ratePerSecond * 60;
        
        if (ratePerMinute == 0) return 0;
        if (ratePerMinute <= LOW_THRESHOLD) return 1;      // Low (20%)
        if (ratePerMinute <= MEDIUM_THRESHOLD) return 2;   // Medium (50%)
        if (ratePerMinute <= HIGH_THRESHOLD) return 3;     // High (80%)
        return 4;                                          // Maximum (100%)
    }
    
    /**
     * @dev Get all streams for a user
     * @param user User address
     */
    function getUserStreams(address user) external view returns (bytes32[] memory) {
        return userStreams[user];
    }
    
    /**
     * @dev Get stream details
     */
    function getStreamDetails(bytes32 streamId) external view returns (
        address sender,
        address recipient,
        uint256 ratePerSecond,
        uint256 balance,
        uint256 claimable,
        uint8 vibrationLevel,
        bool active
    ) {
        Stream memory stream = streams[streamId];
        return (
            stream.sender,
            stream.recipient,
            stream.ratePerSecond,
            stream.balance,
            getClaimableAmount(streamId),
            getVibrationLevel(stream.ratePerSecond),
            stream.active
        );
    }
}

"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
ERC20, Token, Factory|addr:0xcc050167bf76b305c4d0e016a912ed8eca312547|verified:true|block:23673103|tx:0x55db31648d9308ec5d6f8e1176ba2666c145481d4182e600e62eccdfa40f9e04|first_check:1761647338

Submitted on: 2025-10-28 11:29:00

Comments

Log in to comment.

No comments yet.