> For the complete documentation index, see [llms.txt](https://npayme-reward.gitbook.io/npayme/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://npayme-reward.gitbook.io/npayme/technical-stuff/smart-contract.md).

# Smart Contract

Our smart contract is ERC721 compatible and has a couple of additional function to batch send prizes, here is a breakdown of our callable functions:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {Counters} from "@openzeppelin/contracts/utils/Counters.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {ERC721, ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";

contract npaymeRewardeLogicV2 is
    Multicall,
    Ownable2Step,
    ReentrancyGuard,
    EIP712,
    ERC721URIStorage
{
    using Counters for Counters.Counter;
    using ECDSA for bytes32;

    struct MintVoucher {
        string uuid;
        address to;
        string uri;
        bool soulbound;
    }
    struct Fees {
        uint256 disperseFungible;
        uint256 disperseNFT;
    }
    uint256 private _mintFee;
    mapping(address => Fees) private addressFees;
    mapping(uint256 => bool) public isSoulbound;
    Counters.Counter private _tokenIdCounter;
    mapping(uint256 => string) private _tokenURIs;
    mapping(bytes32 => bool) public minted;

    event Minted(address indexed to, string uri);

    constructor()
        EIP712("npaymeReward", "2")
        ERC721("npaymeReward", "npaymeReward-2")
    {}

    function setMintFee(uint256 fee) external onlyOwner {
        _mintFee = fee;
    }

    function setCustomerFees(
        address sender,
        Fees calldata fees
    ) external onlyOwner {
        addressFees[sender] = fees;
    }

    function typeHash() internal pure returns (bytes32) {
        return
            keccak256(
                "MintVoucher(string uuid,address to,string uri,bool soulbound)"
            );
    }

    function _hash(
        MintVoucher calldata voucher
    ) internal view returns (bytes32) {
        return
            _hashTypedDataV4(
                keccak256(
                    abi.encode(
                        typeHash(),
                        keccak256(bytes(voucher.uuid)),
                        voucher.to,
                        keccak256(bytes(voucher.uri)),
                        voucher.soulbound
                    )
                )
            );
    }

    /**
     * @notice Mint through voucher + signedMessage
     *
     */
    function mint(
        MintVoucher calldata voucher,
        bytes calldata signedMessage
    ) external payable nonReentrant {
        // Ensure not minted

        address signer = _hash(voucher).recover(signedMessage);
        require(signer == owner(), "INVALID_SIGNER");
        // If the receiver is `0x0`, send it to the sender.
        address to = voucher.to == address(0) ? _msgSender() : voucher.to;
        // fee
        payable(owner()).transfer(_mintFee);

        // check minted
        bytes32 uuid = keccak256(abi.encode(voucher.uuid));
        require(!minted[uuid], "DUPLICATE_USE");
        minted[uuid] = true;

        uint256 tokenId = _tokenIdCounter.current();
        _mint(to, tokenId);
        _setTokenURI(tokenId, voucher.uri);
        isSoulbound[tokenId] = voucher.soulbound;
        _tokenIdCounter.increment();

        emit Minted(voucher.to, voucher.uri);
    }

    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override {
        require(!isSoulbound[tokenId], "SOULBOUND");
        super._transfer(from, to, tokenId);
    }

    function disperseNative(
        address[] calldata recipients,
        uint256[] calldata values
    ) external payable nonReentrant {
        uint256 leftover = msg.value;
        uint256 totalSent;
        for (uint256 i = 0; i < recipients.length; i++) {
            leftover -= values[i];
            totalSent += values[i];
            payable(recipients[i]).transfer(values[i]);
        }
        // fee
        payable(owner()).transfer(
            (totalSent * addressFees[_msgSender()].disperseFungible) / 1e5
        );
        if (leftover > 0) {
            payable(_msgSender()).transfer(leftover);
        }
    }

    function disperseERC20(
        IERC20 token,
        address[] calldata recipients,
        uint256[] calldata values
    ) external nonReentrant {
        uint256 totalSent;
        for (uint256 i = 0; i < recipients.length; i++) {
            token.transferFrom(_msgSender(), recipients[i], values[i]);
            totalSent += values[i];
        }
        // fee
        token.transferFrom(
            _msgSender(),
            owner(),
            (totalSent * addressFees[_msgSender()].disperseFungible) / 1e5
        );
    }

    function disperseERC721(
        IERC721 nftContract,
        address[] calldata recipients,
        uint256[] calldata ids
    ) external nonReentrant {
        require(
            nftContract.isApprovedForAll(_msgSender(), address(this)),
            "MISSING_APPROVAL"
        );
        for (uint256 i = 0; i < recipients.length; i++) {
            nftContract.safeTransferFrom(_msgSender(), recipients[i], ids[i]);
        }
        // fee
        payable(owner()).transfer(
            addressFees[_msgSender()].disperseNFT * recipients.length
        );
    }
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://npayme-reward.gitbook.io/npayme/technical-stuff/smart-contract.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
