[EXTENSION] SIP-2: Implement a Samurai native OTC NFT solution

A 48 hour proposal voting extension + an inclusion of numerous changes and amendments to the proposed codebase of the smart contract(s).

Improvements/suggestions made over the proposed contract:

  • Included the usage of OpenZeppelin for ReentrancyGuard measures, Ownable, Pausable, SafeMath and standard ERC interfaces

  • Enhanced the overall security of proposed functions using custom modifiers to verify ownership

  • Improved gas optimisation of buy and sell transactions

  • Included events which are emitted upon Sell or Buy actions taking place

The contract requires the protocol holders to approve the Honour Nodes contract for the initial sell action. Yet, for the buy action, it will require the node holders to approve the xHNR contract, hence there are 2 approval actions in total required.

The sell function is lockable using the pauseable setter, and the buy function is guarded by a boolean variable, which will be set to true when the buy event will start to take place.

Upon interacting with the sell function, the contract, assuming that the contract has been approved by the user to use their Honour Nodes NFTs, will transfer the user NFTs from the origin wallet to the smart contract address. Afterwards, it will transfer the appropriate amount of xHNR governance tokens to the user’s wallet. All these actions will take place within a single transaction to ensure the atomic nature of the process.

Upon interacting with the buy function, the opposite actions will take place. Therefore, the NFTs stored in the smart contract will be transferred to the user and, assuming that the user has approved the xHNR contract, then the appropriate amount of xHNR tokens will be transferred from the user’s wallet to the smart contract.

Lastly, the contract also contains an emergency release function to send all the xHNR located on the smart contract to the deployer wallet. This function will be triggered in cases of a potential exploit or to withdraw the funds safely after the conclusion of the Sell/Buy events of SamuraiTrade.

Motivation

Currently, the only secondary NFT market that is compatible with Samurai Node NFTs is tofuNFT, where Samurai Node NFTs and all of its tiers (Buke, Musha, and Mononofu) can be acquired and exchanged. While utilizing tofuNFT was a wise decision at the specific point in time, there are a few problems that need to be fixed.

The inability to acquire or trade Samurai Node NFTs in large amounts is a major drawback and a demotivating issue for many of Samurai's active users who wish to be able to acquire, get rid of, or swap Samurai Node NFTs fast, effectively, and at scale.

Instead of using xHNR, the protocol's native governance token, the TofuNFT market and its collections use FTM (Fantom). The utility of the xHNR token would be increased by following this proposal as the Samurai Node NFTs would be acquired and disposed using the protocol's native xHNR governance token.

Because TofuNFT is an outside service provider, Samurai becomes more dependent on that party's technological capabilities, reputational standing, and terms of service. Samurai's goal is to be decentralized and function autonomously and independenťy, hence the project should support alternatives that promote no dependency on central middlemen and intermediaries.

Samurai Node NFTs are subject to a 3% royalty collection on TofuNFT. Samurai might have more control over its royalties by using its own, independently owned service.

Users are unable to conveniently and readily manage and control their Samurai Node NFTs within the primary point of engagement with the protocol (the Samurai dApp) because of the existing solution's inadequate integration capability.

Specification

Maintain and keep the Samurai Node NFT collection on tofuNFT while enhancing it with a second, independent, autonomous OTC NFT substitute that makes use of the xHNR governance token. All of the aforementioned pain issues would be addressed and resolved by the OTC NFT marketplace alternative, which would rely on a set of smart contracts that are described in more detail below.

With the help of the suggested smart contracts, OTC NFT events might happen inside of Samurai's dApp at a pre-set moment when the contract would be buying Samurai Node NFTs from its users at a set xHNR price. By selecting many Node NFTs at once, the solution would allow users to sell and dispose of their Node NFTs at scale within the dApp interface in exchange for xHNR tokens. The protocol would purchase Node NFTs using its treasury, and the treasury would then hold the purchased NFTs. To purchase Node NFTs and promote the suggested OTC NFT alternative, 3 000 000 xHNR tokens should be issued to the Treasury for the acquisition purposes.

The protocol should then sell the acquired Node NFTs that are held by the treasury to new or existing users within Samurai's dApp at a predetermined, scheduled time, at a fixed xHNR price, using the smart contracts. By choosing several NFTs at once, users would be able to quickly acquire Samurai's Node NFTs in exchange for xHNR tokens at scale. This could also potentially lead to a lower acquisition price by removing the need to pay fees associated with using the tofuNFT platform and would substantially increase the utility of the xHNR governance token.

The pricing of the Node NFTs within the OTC NFT solution should be set in xHNR token terms and according to the floor price of the NFTs on TofuNFT -~ 15% to incentivise and encourage the use of Samurai’s OTC NFT alternative.

The tokens would be kept by the treasury if there is a positive price difference between the purchase price and the selling price of Node NFTs, which might be viewed as an xHNR-native alternative to a royalty collection.

Setting precise acquisition and selling goals, while taking into account the state of the treasury, current activity (volatility and volume) in the tofuNFT market, can all be used to predict the length of OTC NFT events and the numbers of Node NFTs attained.

I have developed the following smart contracts which take into account and are in line with the practices and technological stack observed in the protocol’s verified and publicly accessible smart contracts, codebase and governance proposals.

The proposed smart contract/Solidity code:

// SPDX-License-Identifier: MIT pragma solidity 0.8.13;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/interfaces/IERC721.sol"; import "@openzeppelin/contracts/interfaces/IERC20.sol"; import "@openzeppelin/contracts/utils/math/SafeMath.sol"; import "@openzeppelin/contracts/security/Pausable.sol";

contract SamuraiTrade is Ownable, ReentrancyGuard, Pausable { using SafeMath for uint256;

uint256 public buyPrice; uint256 public sellPrice; bool public isBuyEnabled;

IERC721 public hnrNodes; IERC20 public xHnr;

event BuyNode(address nodeBuyer, uint256[] tokenIds, uint256 amount); event SellNode(address nodeSeller, uint256[] tokenIds, uint256 amount);

constructor( uint256 _buyPrice, uint256 _sellPrice, address _hnrNodes, address _xHnr ) { buyPrice = _buyPrice; sellPrice = _sellPrice; hnrNodes = IERC721(_hnrNodes); xHnr = IERC20(_xHnr); isBuyEnabled = false; }

// we need to check if the seller actually owns all the tokens and if the contract has them to sell modifier ownsAll(uint256[] calldata _tokenIds, bool isContractOwner) { uint256 arrSize = _tokenIds.length; address tokenOwner = isContractOwner ? address(this) : msg.sender; for (uint256 i = 0; i < arrSize; i = uncheckedIncrement(i)) { require( hnrNodes.ownerOf(_tokenIds[i]) == tokenOwner, isContractOwner ? "Contact: token ID unavailable" : "Owner: not an owner!" ); } _; }

function sell(uint256[] calldata _tokenIds) external whenNotPaused ownsAll(_tokenIds, false) nonReentrant { address nodeSeller = msg.sender; uint256 amount = uint256(_tokenIds.length).mul(sellPrice); // transfer token ids to contract batchTransfer(_tokenIds, true); xHnr.transfer(nodeSeller, amount);

emit SellNode(nodeSeller, _tokenIds, amount);

}

function buy(uint256[] calldata _tokenIds) external ownsAll(_tokenIds, true) nonReentrant { require(isBuyEnabled, "Contract: Buy Not Enabled!");

address nodeBuyer = msg.sender;
uint256 quantity = _tokenIds.length;
uint256 amount = quantity.mul(buyPrice);
xHnr.transferFrom(nodeBuyer, address(this), amount);
// transfer out tokenIds to the buyer
batchTransfer(_tokenIds, false);

emit BuyNode(nodeBuyer, _tokenIds, amount);

}

function setPause(bool _isPaused) external onlyOwner { _isPaused ? _pause() : _unpause(); }

function setBuyEnabled(bool _isEnabled) external onlyOwner { isBuyEnabled = _isEnabled; }

function setBuyPrice(uint256 _buyPrice) external onlyOwner { buyPrice = _buyPrice; }

function setSellPrice(uint256 _sellPrice) external onlyOwner { sellPrice = _sellPrice; }

function release() external onlyOwner { uint256 totalBalance = xHnr.balanceOf(address(this)); xHnr.transfer(owner(), totalBalance); }

function batchTransfer(uint256[] calldata _tokenIds, bool isSell) internal { uint256 length = _tokenIds.length; address sender = msg.sender; address contractAddress = address(this);

for (uint256 i = 0; i < length; i = uncheckedIncrement(i)) {
  isSell
    ? hnrNodes.transferFrom(sender, contractAddress, _tokenIds[i])
    : hnrNodes.transferFrom(contractAddress, sender, _tokenIds[i]);
}

}

// gas optimisation function uncheckedIncrement(uint256 i) internal pure returns (uint256) { unchecked { return i + 1; } } }

Last updated