Compare commits

...

34 Commits

Author SHA1 Message Date
Moon Man 2eb54beec9 rm setting individual royalties to shrink contract size 2023-02-05 17:07:30 -05:00
Moon Man 7bc38210f6 gas saving, not enough yet 2023-02-05 17:06:29 -05:00
Moon Man da0a4a98f3 move setup to parms to try to save space 2023-02-05 16:58:31 -05:00
Moon Man 31aa432ed7 adjust config for smaller contract size 2023-02-05 16:35:55 -05:00
Moon Man 3a15bf1145 special handling for token 1 2023-02-05 16:35:43 -05:00
Moon Man f3e292bec2 bunch more scripts 2023-01-30 19:35:49 -05:00
Moon Man cb9898a4bd change xj9 url 2023-01-30 19:35:16 -05:00
Moon Man 791ca05333 override transfer from 2023-01-30 15:33:49 -05:00
Moon Man a5f9d9f139 script for supportsInterface 2023-01-30 14:56:42 -05:00
Moon Man bd2cb5aea9 xfer script 2023-01-30 14:33:15 -05:00
Moon Man 363d783f42 can specify different account 2023-01-30 14:32:53 -05:00
Moon Man a4fcf451f5 script to set default royalty info 2023-01-30 13:44:57 -05:00
Moon Man c2b3b04df7 better output 2023-01-30 13:44:41 -05:00
Moon Man 4b4922e40b updated scripts for everything almost 2023-01-30 12:47:43 -05:00
Moon Man 0677ee477d contract level metadata and individual royalty setting 2023-01-30 12:47:02 -05:00
Moon Man ad0266bc69 increase for fat contract 2023-01-30 12:46:34 -05:00
Moon Man be76f716b1 transferFrom so there's a transfer event 2023-01-30 09:09:02 -05:00
Moon Man 4d66247dd3 royalty reporting and default royalty, untested, may add specific royalties later 2023-01-30 01:09:51 -05:00
Moon Man abb21e0873 use openzeppelin ierc165 2023-01-30 00:45:26 -05:00
Moon Man 9625c4bdad add contract to metadata 2023-01-30 00:25:36 -05:00
Moon Man 01c8e2aefc working wrap and uwrap scripts 2023-01-30 00:00:04 -05:00
Moon Man 691c0c4279 add approve function 2023-01-29 23:20:44 -05:00
Moon Man 9a206ffbd3 pass id on command line for metadata query 2023-01-29 22:28:42 -05:00
Moon Man c29b0a0336 query metadata script 2023-01-29 22:21:37 -05:00
Moon Man c4d1ab0b7f function call instead of just function 2023-01-29 22:21:09 -05:00
Moon Man e430270130 fix network id for ubiq 2023-01-29 22:20:37 -05:00
Moon Man 31f0c6f84e pausable added, destroyable can be disabled 2023-01-29 17:50:26 -05:00
Moon Man 02a3164658 include erc1155 metadata supportsInterface 2023-01-29 17:22:15 -05:00
Moon Man e8936b07ab documentation improvement 2023-01-29 16:58:16 -05:00
Moon Man 6ede82efa8 rm safemath new solidity doesn't need it 2023-01-29 16:55:26 -05:00
Moon Man dcc29d5d4d documentation improvements 2023-01-29 16:44:25 -05:00
Moon Man 0f93eae7b0 dynamic metadata, external_url 2023-01-29 16:15:20 -05:00
Moon Man 1c58533f89 new interface, openzeppelin utils import 2023-01-29 14:46:02 -05:00
Moon Man ecaa83918f base64 encoder 2023-01-29 14:30:40 -05:00
25 changed files with 711 additions and 151 deletions

View File

@ -2,95 +2,167 @@
pragma solidity ^0.8.17;
import "./ERC1155.sol";
import "./ICurio.sol";
import "./SafeMath.sol";
import "./IMastersFedi.sol";
import "./Address.sol";
import "./IERC1155Metadata.sol";
import "./ERC1155Metadata.sol";
import "./IERC223ReceivingContract.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";
contract CurioERC1155Wrapper is ERC1155, ERC1155Metadata_URI {
using SafeMath for uint256;
contract CurioERC1155Wrapper is ERC1155, ERC1155Metadata_URI, ERC1155Metadata, ERC2981, IERC223ReceivingContract, Ownable, Pausable {
using Address for address;
// nft id => curio contract address
mapping (uint256 => address) public contracts;
// nft id => nft metadata IPFS URI
mapping (uint256 => string) public metadatas;
mapping (uint256 => string) public urls;
string public name;
string public symbol;
uint96 internal defaultRoyalty;
struct InitPair {
address token;
string url;
}
/**
@notice Initialize an nft id's data.
@notice Initialize an nft id's data
*/
function create(uint256 _id, address _contract, string memory _uri) internal {
function create(uint256 _id, address _contract, string memory _url) internal {
require(contracts[_id] == address(0), "id already exists");
contracts[_id] = _contract;
urls[_id] = _url;
// mint 0 just to let explorers know it exists
emit TransferSingle(msg.sender, address(0), msg.sender, _id, 0);
metadatas[_id] = _uri;
emit URI(_uri, _id);
}
constructor() {
create(1, 0x6Aa2044C7A0f9e2758EdAE97247B03a0D7e73d6c, "ipfs://QmWHUnrdfA4w89TeepZqrvygbaF9wV48k97Wf27skL5cry");
create(2, 0xE9A6A26598B05dB855483fF5eCc5f1d0C81140c8, "ipfs://QmVJn6B289Xt3cq9evzubdyk4f1usPAu277SmUusmdYYWU");
create(3, 0x3f8131B6E62472CEea9cb8Aa67d87425248a3702, "ipfs://QmWBb6T4nviPWdAyqGJTki7VA6fpTmcYP37U9jpYAfhzPP");
create(4, 0x4F1694be039e447B729ab11653304232Ae143C69, "ipfs://Qmbcw8ix8xdK1reFpDEjKtk9EWuRwrbMKqvEvWkttNzXkH");
create(5, 0x5a3D4A8575a688b53E8b270b5C1f26fd63065219, "ipfs://QmXmj9YdsvBVddzC352Xsh7bmyJtfZvbVJeetK7PXW21p8");
create(6, 0x1Ca6AC0Ce771094F0F8a383D46BF3acC9a5BF27f, "ipfs://Qmdf16YMPM7zG5QkSYB4HjbxQPaStYazsL6d1npdJG8J7h");
create(7, 0x2647bd8777e0C66819D74aB3479372eA690912c3, "ipfs://QmUGmWwrNR7JKBCSu3CkGnTYSFat7y2AiUzACcbAoZcj2d");
create(8, 0x2FCE2713a561bB019BC5A110BE0A19d10581ee9e, "ipfs://QmXQfBgJRsUQbf8UkViATdpsySXzREsifegWzLvw5QsQPj");
create(9, 0xbf4Cc966F1e726087c5C55aac374E687000d4d45, "ipfs://Qmctv89ppbYTuwCWVFA9waVCeE8g6YM3Ah54bZW1WGmEHh");
create(10, 0x72b34d637C0d14acE58359Ef1bF472E4b4c57125, "ipfs://QmaSBVrCcBsYHjVuvTsj6ev4Pua7NYX7sDNzdAYwCdAAne");
create(11, 0xb36c87F1f1539c5FC6f6e7b1C632e1840C9B66b4, "ipfs://QmZjSs71uBYYdLx5Ju443KiSYjxQcJQLL5ZnhuzWX6nC19");
create(12, 0xD15af10A258432e7227367499E785C3532b50271, "ipfs://QmQqMKDMKiRhgbFBrmAJPknzYHEKuH7VrqPZ7NS5vFoy78");
create(13, 0x2d922712f5e99428c65b44f09Ea389373d185bB3, "ipfs://QmeShnRPe6uiRcBy81nQXDZ9TWUpFNQfiAThf9ruAQGcRa");
create(14, 0x0565ac44e5119a3224b897De761a46A92aA28ae8, "ipfs://Qmdi8vQuQQWksiM5HCCVXfzSzcaemzQwYkUe4Tb94DP6vK");
create(15, 0xdb7F262237Ad8acca8922aA2c693a34D0d13e8fe, "ipfs://QmS3UF256kWHbX8Wi7CYExyCxzLNX1nsaMwpaGBN73rr31");
create(16, 0x1b63532CcB1FeE0595c7fe2Cb35cFD70ddF862Cd, "ipfs://Qmbj1YCmQidTzxgjLmu1b99PPdXZLSgk72YZQSt9LEEe1R");
create(17, 0xF59536290906F204C3c7918D40C1Cc5f99643d0B, "ipfs://QmbDsZABRUPMcuoFWePRH7YiGyR64udWHc4u1mQPJYmB2c");
create(18, 0xA507D9d28bbca54cBCfFad4BB770C2EA0519F4F0, "ipfs://QmXafwRpoJPiiQ9TZihhbSsFmgKqKMqrHSRLkp1wyQ3jUU");
create(19, 0xf26BC97Aa8AFE176e275Cf3b08c363f09De371fA, "ipfs://QmTWJR1XJ2svexE2NT3A6cCtks8rgh6TKYaLYXwfHapNDN");
create(20, 0xD0ec99E99cE22f2487283A087614AEe37F6B1283, "ipfs://Qmd3HzUX52MmZcj1Se3ocgYWEJWSvzSceEqQFV1YL7LRWL");
create(21, 0xB7A5a84Ff90e8Ef91250fB56c50a7bB92a6306EE, "ipfs://QmX6stsihT3SNUakiFQLWU1cjvH7rC3pqtCnToxNn2T8JS");
create(22, 0x148fF761D16632da89F3D30eF3dFE34bc50CA765, "ipfs://Qmc1sj8LRdfbPinoqKMmAe6UvJUG33VMmSU3XzNK2GnjJB");
create(23, 0xCDE7185B5C3Ed9eA68605a960F6653AA1a5b5C6C, "ipfs://Qmdwh3S4imtE5RxZ4ddAzy3DMqNrD11JL6SATTyREuvrtN");
create(24, 0xE67dad99c44547B54367E3e60fc251fC45a145C6, "ipfs://QmbfTxH6XvbgGcyWWaygmPko6NQ6tKuT6dJj5WjnQGp5g8");
create(25, 0xC7f60C2b1DBDfd511685501EDEb05C4194D67018, "ipfs://QmXHyK19F4sMAUi6XYz1BJJYzxsdp8koVnL4BwsFA93Q47");
create(26, 0x1cB5BF4Be53eb141B56f7E4Bb36345a353B5488c, "ipfs://QmYK88qy84rcL46CZGPqpKRm4fE2PQYJ931pV69ZNi4J1D");
create(27, 0xFb9F3fa2502d01d43167A0A6E80bE03171DF407E, "ipfs://QmcUTEkPpmRPHCHiXskd9daQcEZwGzkHgybZmCWmFYha1T");
create(28, 0x59D190e8A2583C67E62eEc8dA5EA7f050d8BF27e, "ipfs://QmTmi8j5BBE5FWhEDAg1bTqpmkkEcaPgTUeYFJ4z3PxXqN");
create(29, 0xD3540bCD9c2819771F9D765Edc189cBD915FEAbd, "ipfs://QmVTGJtgnUgnMPttJV2VkfonCUYLRnJqX66gJLiig5QVgC");
create(30, 0x7F5B230Dc580d1e67DF6eD30dEe82684dD113D1F, "ipfs://QmQBu8jYC3vEGzx59BUW4knBdNRyFd8aTVLLFCEprdjZ5e");
constructor(string memory _name, string memory _symbol, InitPair[] memory _tokens, uint96 _defaultRoyalty) {
name = _name;
symbol = _symbol;
for(uint256 i = 1; i <= _tokens.length; i++) {
create(i, _tokens[i].token, _tokens[i].url);
}
_setDefaultRoyalty(msg.sender, _defaultRoyalty);
}
function setDefaultRoyalty(address receiver, uint96 feeNumerator) external onlyOwner {
_setDefaultRoyalty(receiver, feeNumerator);
}
function deleteDefaultRoyalty() external onlyOwner {
_deleteDefaultRoyalty();
}
/**
@dev override ERC1155 uri function to return IPFS ref.
@notice Returns URI of token metadata
@param _id NFT ID
@return IPFS URI pointing to NFT ID's metadata.
@return URI data URI of metadata JSON
*/
function uri(uint256 _id) public view returns (string memory) {
return metadatas[_id];
function uri(uint256 _id) external override view returns (string memory) {
IMastersFedi curio = IMastersFedi(contracts[_id]);
return string(abi.encodePacked("data:application/json;base64,", Base64.encode(abi.encodePacked(
'{',
'"name":"', curio.name(), '",',
'"description":"', curio.description(), '",',
'"image":"ipfs://', curio.ipfs_hash(), '",',
'"external_url":"', urls[_id], '",',
'"properties":{',
'"symbol":"', curio.symbol(), '",',
'"erc20_contract":"', Strings.toHexString(contracts[_id]), '"',
'}',
'}'
))));
}
/**
@dev helper function to see if NFT ID exists, makes OpenSea happy.
@notice Change the external URL for a token
@param _id NFT ID
@return if NFT ID exists.
@param _url URL pointing to user or site
*/
function setExternalUrl(uint256 _id, string memory _url) external onlyOwner {
require(contracts[_id] != address(0), "id must exist");
urls[_id] = _url;
}
/**
@notice Query if a contract implements an interface
@param _interfaceId The interface identifier, as specified in ERC-165
@return `true` if the contract implements `_interfaceId`
*/
function supportsInterface(bytes4 _interfaceId) public view override(ERC1155, ERC1155Metadata, ERC2981) returns (bool) {
return ERC1155.supportsInterface(_interfaceId)
|| ERC1155Metadata.supportsInterface(_interfaceId)
|| ERC2981.supportsInterface(_interfaceId)
;
}
/**
@notice If NFT ID exists
@dev Makes OpenSea happy
@param _id NFT ID
@return exists if NFT ID exists.
*/
function exists(uint256 _id) external view returns(bool) {
return contracts[_id] != address(0);
}
bool public destroyable = true;
/**
@dev for an NFT ID, queries and transfers tokens from the appropriate
curio contract to itself, and mints and transfers corresponding new
ERC-1155 tokens to caller.
@notice Destroy the contract
@dev Only if it hasn't been disabled
*/
function destroy() external onlyOwner whenPaused {
require(destroyable, "destroy() has been disabled");
selfdestruct(payable(msg.sender));
}
function disableDestroy() external onlyOwner {
destroyable = false;
}
/**
@dev called by token 1 contract after transfer, this is the only method to wrap 1.
*/
function wrap(uint256 _id, uint256 _quantity) external {
function tokenFallback(address _from, uint256 _value, bytes memory) override external {
require(_from == owner(), "Only owner can wrap token 1");
require(msg.sender == contracts[1] , "Can only transfer from token 1 owner");
IMastersFedi curio = IMastersFedi(contracts[1]);
require (curio.balanceOf(address(this)) <= 450, "Sanity transfer exceeded");
// no balance check is done because an overflow would fail the sanity check anyway
balances[1][_from] += _value;
// mint
emit TransferSingle(msg.sender, address(0), _from, 1, _value);
// safe transfer acceptance check not necessary because locked to a known contract
}
/**
@notice Converts old-style NFT to new-style
@dev For an NFT ID, queries and transfers tokens from the appropriate
curio contract to itself, and mints and transfers corresponding new
ERC-1155 tokens to caller
@param _id NFT ID
@param _quantity how many to wrap
*/
function wrap(uint256 _id, uint256 _quantity) external whenNotPaused {
address tokenContract = contracts[_id];
require(tokenContract != address(0), "invalid id");
ICurio curio = ICurio(tokenContract);
require(_id != 1, "cannot wrap token 1 this way");
IMastersFedi curio = IMastersFedi(tokenContract);
// these are here for convenience because curio contract doesn't throw meaningful exceptions
require(curio.balanceOf(msg.sender) >= _quantity, "insufficient curio balance");
@ -98,72 +170,79 @@ contract CurioERC1155Wrapper is ERC1155, ERC1155Metadata_URI {
curio.transferFrom(msg.sender, address(this), _quantity);
balances[_id][msg.sender] = balances[_id][msg.sender].add(_quantity);
balances[_id][msg.sender] += _quantity;
// mint
emit TransferSingle(msg.sender, address(0), msg.sender, _id, _quantity);
address _to = msg.sender;
if (_to.isContract()) {
if (msg.sender.isContract()) {
_doSafeTransferAcceptanceCheck(msg.sender, msg.sender, msg.sender, _id, _quantity, '');
}
}
/**
@dev batch version of wrap.
@notice Batch version of wrap function
@param _ids array of NFT IDs
@param _quantities how many to wrap of each
*/
function wrapBatch(uint256[] calldata _ids, uint256[] calldata _quantities) external {
function wrapBatch(uint256[] calldata _ids, uint256[] calldata _quantities) external whenNotPaused {
require(_ids.length == _quantities.length, "ids and quantities must match");
address _to = msg.sender;
bool callerIsContract = _to.isContract();
for (uint256 i=0; i < _ids.length; ++i) {
uint256 _id = _ids[i];
uint256 _quantity = _quantities[i];
address tokenContract = contracts[_id];
require(tokenContract != address(0), "invalid id");
ICurio curio = ICurio(tokenContract);
require(_id != 1, "cannot wrap token 1 this way");
IMastersFedi curio = IMastersFedi(tokenContract);
require(curio.balanceOf(msg.sender) >= _quantity, "insufficient curio balance");
require(curio.allowance(msg.sender, address(this)) >= _quantity, "insufficient curio allowance");
curio.transferFrom(msg.sender, address(this), _quantity);
balances[_id][msg.sender] = balances[_id][msg.sender].add(_quantity);
balances[_id][msg.sender] += _quantity;
if (callerIsContract) {
if (msg.sender.isContract()) {
_doSafeTransferAcceptanceCheck(msg.sender, msg.sender, msg.sender, _id, _quantity, '');
}
}
// mint
emit TransferBatch(msg.sender, address(0), _to, _ids, _quantities);
emit TransferBatch(msg.sender, address(0), msg.sender, _ids, _quantities);
}
/**
@dev for an NFT ID, burns ERC-1155 quantity and transfers curio ERC-20
tokens to caller.
@notice Unwrap new-style NFTs back to old-style
@dev For an NFT ID, burns ERC-1155 quantity and transfers curio ERC-20
tokens to caller
@param _id NFT ID
@param _quantity how many to unwrap
*/
function unwrap(uint256 _id, uint256 _quantity) external {
function unwrap(uint256 _id, uint256 _quantity) external whenNotPaused {
address tokenContract = contracts[_id];
require(tokenContract != address(0), "invalid id");
ICurio curio = ICurio(tokenContract);
require(_id != 1, "token 1 cannot be unwrapped");
IMastersFedi curio = IMastersFedi(tokenContract);
require(balances[_id][msg.sender] >= _quantity, "insufficient balance");
balances[_id][msg.sender] = balances[_id][msg.sender].sub(_quantity);
balances[_id][msg.sender] -= _quantity;
curio.transfer(msg.sender, _quantity);
// do it this way to make sure there's a transfer event
curio.approve(address(this), _quantity);
curio.transferFrom(address(this), msg.sender, _quantity);
// burn
emit TransferSingle(msg.sender, msg.sender, address(0), _id, _quantity);
}
/**
@dev batch version of unwrap.
@notice Batch version of unwrap
@param _ids array of NFT IDs
@param _quantities how many to unwrap of each
*/
function unwrapBatch(uint256[] calldata _ids, uint256[] calldata _quantities) external {
function unwrapBatch(uint256[] calldata _ids, uint256[] calldata _quantities) external whenNotPaused {
require(_ids.length == _quantities.length, "ids and quantities must match");
for (uint256 i=0; i < _ids.length; ++i) {
@ -172,12 +251,15 @@ contract CurioERC1155Wrapper is ERC1155, ERC1155Metadata_URI {
address tokenContract = contracts[_id];
require(tokenContract != address(0), "invalid id");
ICurio curio = ICurio(tokenContract);
require(_id != 1, "token 1 cannot be unwrapped");
IMastersFedi curio = IMastersFedi(tokenContract);
require(balances[_id][msg.sender] >= _quantity, "insufficient balance");
balances[_id][msg.sender] = balances[_id][msg.sender].sub(_quantity);
balances[_id][msg.sender] -= _quantity;
curio.transfer(msg.sender, _quantity);
// do it this way to make sure there's a transfer event
curio.approve(address(this), _quantity);
curio.transferFrom(address(this), msg.sender, _quantity);
}
// burn

View File

@ -1,16 +1,14 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.17;
import "./SafeMath.sol";
import "./Address.sol";
import "./Common.sol";
import "./IERC1155TokenReceiver.sol";
import "./IERC1155.sol";
import "@openzeppelin/contracts/interfaces/IERC165.sol";
// A sample implementation of core ERC1155 function.
contract ERC1155 is IERC1155, ERC165, CommonConstants
contract ERC1155 is IERC1155, IERC165, CommonConstants
{
using SafeMath for uint256;
using Address for address;
// id => (owner => balance)
@ -38,7 +36,8 @@ contract ERC1155 is IERC1155, ERC165, CommonConstants
function supportsInterface(bytes4 _interfaceId)
public
pure
view
virtual
returns (bool) {
if (_interfaceId == INTERFACE_SIGNATURE_ERC165 ||
_interfaceId == INTERFACE_SIGNATURE_ERC1155) {
@ -69,10 +68,8 @@ contract ERC1155 is IERC1155, ERC165, CommonConstants
require(_to != address(0x0), "_to must be non-zero.");
require(_from == msg.sender || operatorApproval[_from][msg.sender] == true, "Need operator approval for 3rd party transfers.");
// SafeMath will throw with insuficient funds _from
// or if _id is not valid (balance will be 0)
balances[_id][_from] = balances[_id][_from].sub(_value);
balances[_id][_to] = _value.add(balances[_id][_to]);
balances[_id][_from] -= _value;
balances[_id][_to] += _value;
// MUST emit event
emit TransferSingle(msg.sender, _from, _to, _id, _value);
@ -111,10 +108,8 @@ contract ERC1155 is IERC1155, ERC165, CommonConstants
uint256 id = _ids[i];
uint256 value = _values[i];
// SafeMath will throw with insuficient funds _from
// or if _id is not valid (balance will be 0)
balances[id][_from] = balances[id][_from].sub(value);
balances[id][_to] = value.add(balances[id][_to]);
balances[id][_from] -= value;
balances[id][_to] += value;
}
// Note: instead of the below batch versions of event and acceptance check you MAY have emitted a TransferSingle
@ -136,7 +131,7 @@ contract ERC1155 is IERC1155, ERC165, CommonConstants
@notice Get the balance of an account's Tokens.
@param _owner The address of the token holder
@param _id ID of the Token
@return The _owner's balance of the Token type requested
@return balance The _owner's balance of the Token type requested
*/
function balanceOf(address _owner, uint256 _id) external view returns (uint256) {
// The balance of any account can be calculated from the Transfer events history.
@ -150,7 +145,7 @@ contract ERC1155 is IERC1155, ERC165, CommonConstants
@notice Get the balance of multiple account/token pairs
@param _owners The addresses of the token holders
@param _ids ID of the Tokens
@return The _owner's balance of the Token types requested (i.e. balance for each (owner, id) pair)
@return balance The _owner's balance of the Token types requested (i.e. balance for each (owner, id) pair)
*/
function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory) {

View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.17;
import "./IERC1155Metadata.sol";
contract ERC1155Metadata {
/**
@notice Query if a contract implements an interface
@param _interfaceId The interface identifier, as specified in ERC-165
@return `true` if the contract implements `_interfaceID`
*/
function supportsInterface(bytes4 _interfaceId) public virtual view returns (bool) {
if (_interfaceId == type(ERC1155Metadata_URI).interfaceId) {
return true;
}
else return false;
}
}

View File

@ -1,8 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.17;
import "./ERC165.sol";
/**
@title ERC-1155 Multi Token Standard
@dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md

View File

@ -0,0 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.17;
interface IERC223ReceivingContract {
function tokenFallback(address _from, uint256 _value, bytes memory _data) external;
}

27
contracts/IERC2981.sol Normal file
View File

@ -0,0 +1,27 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/interfaces/IERC165.sol";
interface IERC2981 is IERC165 {
/// ERC165 bytes to add to interface array - set in parent contract
/// implementing this standard
///
/// bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a
/// bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a;
/// _registerInterface(_INTERFACE_ID_ERC2981);
/// @notice Called with the sale price to determine how much royalty
// is owed and to whom.
/// @param _tokenId - the NFT asset queried for royalty information
/// @param _salePrice - the sale price of the NFT asset specified by _tokenId
/// @return receiver - address of who should be sent the royalty payment
/// @return royaltyAmount - the royalty payment amount for _salePrice
function royaltyInfo(
uint256 _tokenId,
uint256 _salePrice
) external view returns (
address receiver,
uint256 royaltyAmount
);
}

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.17;
// Masters of the Fediverse is not quite ERC-20 compliant
interface IMastersFedi {
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address _spender, uint256 _value) external returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) external;
function transfer(address _to, uint256 _value) external;
function ipfs_hash() external view returns (string memory);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function description() external view returns (string memory);
}

View File

@ -1,53 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.17;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
// Gas optimization: this is cheaper than asserting 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
// uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return a / b;
}
/**
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
}

View File

@ -1,5 +1,23 @@
const CurioWrapper = artifacts.require("CurioERC1155Wrapper");
const tokens = [
{ token: "0x2f873FCc3F4B84E9A62AFf28E9a897ce1BC8814B", url: "https://shitposter.club" }, // 1 is the ERC-223
{ token: "0x13a9914Ad2e0be57eB2Abb3E159021Eab6D7a80E", url: "https://tuusin.misono-ya.info/users/hakui" },
{ token: "0xdeCAa5B6901dc465FBf90f9C0c70c96132aF51Db", url: "https://shitposter.club/users/augustus" },
{ token: "0xc1de7E95663FB3A0e8F8C6E6a64297d7AbcBF7f7", url: "https://xj-ix.luxe/bin/fedi" },
{ token: "0xB70F9A809693B8c6a4c331342B96F15252521dC7", url: "https://varishangout.net/users/nepfag" },
{ token: "0x3f2592136d90dE35615A409B4fe710B3764366F4", url: "https://shitposter.club/dokidoki@pl.smuglo.li" },
{ token: "0x5e7318f75b177a0F27A31CB20bB26bd0C049620c", url: "https://twitter.com/sonyasupposedly" },
{ token: "0x5539907D45a608828756765429f2B4e6311c295c", url: "https://shpposter.club/users/shpuld" },
{ token: "0x0a0e64067B1F7aDfbF876Dde4322633Ff7Df9702", url: "https://bbs.kawa-kun.com/users/tk" }
];
module.exports = function(deployer) {
deployer.deploy(CurioWrapper);
deployer.deploy(
CurioWrapper,
"Masters of the Fediverse",
"MASTERS",
tokens,
200 // default royalty 2%
);
};

13
package-lock.json generated
View File

@ -8,6 +8,9 @@
"name": "curio-wrapper",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@openzeppelin/contracts": "^4.8.1"
},
"devDependencies": {
"@truffle/hdwallet-provider": "^1.7.0",
"ganache": "^7.7.3",
@ -1278,6 +1281,11 @@
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"node_modules/@openzeppelin/contracts": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.1.tgz",
"integrity": "sha512-xQ6eUZl+RDyb/FiZe1h+U7qr/f4p/SrTSQcTPH2bjur3C5DbuW/zFgCU/b1P/xcIaEqJep+9ju4xDRi3rmChdQ=="
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@ -12336,6 +12344,11 @@
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"@openzeppelin/contracts": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.1.tgz",
"integrity": "sha512-xQ6eUZl+RDyb/FiZe1h+U7qr/f4p/SrTSQcTPH2bjur3C5DbuW/zFgCU/b1P/xcIaEqJep+9ju4xDRi3rmChdQ=="
},
"@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",

View File

@ -19,5 +19,8 @@
"@truffle/hdwallet-provider": "^1.7.0",
"ganache": "^7.7.3",
"truffle": "^5.7.4"
},
"dependencies": {
"@openzeppelin/contracts": "^4.8.1"
}
}

31
scripts/approve-set.js Normal file
View File

@ -0,0 +1,31 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
const IMastersFedi = artifacts.require("IMastersFedi");
const quantity = parseInt(process.argv[6]);
module.exports = async (callback) => {
if (quantity > 0) {
const wrapper = await Wrapper.deployed();
let approveSuccess = false;
console.log("Approving all contracts for transfer");
try {
for (let id = 2; id <= 9; ++id) {
const tokenContractAddress = await wrapper.contracts(id);
const tokenContract = await IMastersFedi.at(tokenContractAddress);
console.log(`Approving ID ${id}...`);
await tokenContract.approve(wrapper.address, quantity);
}
approveSuccess = true;
}
catch (e) {
console.error(`FAILED: ${e}`);
}
}
else {
console.error("Invalid quantity");
}
callback();
}

23
scripts/balance.js Normal file
View File

@ -0,0 +1,23 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
const IMastersFedi = artifacts.require("IMastersFedi");
module.exports = async (callback) => {
const nftId1 = parseInt(process.argv[6]);
const wrapper = await Wrapper.deployed();
const tokenContractAddress1 = await wrapper.contracts(nftId1);
const tokenContract1 = await IMastersFedi.at(tokenContractAddress1);
const tokenName1 = await tokenContract1.name();
console.log(`Token name: "${tokenName1}"`);
const [account,] = await web3.eth.getAccounts();
const whom = process.argv[7] ? process.argv[7] : account;
const wrappedBalance1 = await wrapper.balanceOf(whom, nftId1);
const tokenBalance1 = await tokenContract1.balanceOf(whom);
console.log(`Wrapped: ${wrappedBalance1}`);
console.log(`Unwrapped: ${tokenBalance1}`);
callback();
}

46
scripts/batch-unwrap.js Normal file
View File

@ -0,0 +1,46 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
const IMastersFedi = artifacts.require("IMastersFedi");
module.exports = async (callback) => {
const nftId1 = parseInt(process.argv[6]);
const quantity1 = parseInt(process.argv[7]);
const nftId2 = parseInt(process.argv[8]);
const quantity2 = parseInt(process.argv[9]);
const wrapper = await Wrapper.deployed();
const tokenContractAddress1 = await wrapper.contracts(nftId1);
const tokenContractAddress2 = await wrapper.contracts(nftId2);
const tokenContract1 = await IMastersFedi.at(tokenContractAddress1);
const tokenContract2 = await IMastersFedi.at(tokenContractAddress2);
const tokenName1 = await tokenContract1.name();
const tokenName2 = await tokenContract2.name();
console.log(`Token names: "${tokenName1}", "${tokenName2}"`);
const [account,] = await web3.eth.getAccounts();
try {
console.log("Batch unwrapping...");
const finalResult = await wrapper.unwrapBatch(
[nftId1, nftId2],
[quantity1, quantity2],
{ from: account }
);
console.log(`Tx: ${finalResult.tx}`);
const wrappedBalance1 = await wrapper.balanceOf(account, nftId1);
const wrappedBalance2 = await wrapper.balanceOf(account, nftId2);
console.log(`New wrapped balances: ${wrappedBalance1}, ${wrappedBalance2}`);
const tokenBalance1 = await tokenContract1.balanceOf(account);
const tokenBalance2 = await tokenContract2.balanceOf(account);
console.log(`New unwrapped balances: ${tokenBalance1}, ${tokenBalance2}`);
}
catch (e) {
console.error(`FAILED: ${e}`);
}
console.log("Done.");
callback();
}

46
scripts/batch-wrap.js Normal file
View File

@ -0,0 +1,46 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
const IMastersFedi = artifacts.require("IMastersFedi");
module.exports = async (callback) => {
const nftId1 = parseInt(process.argv[6]);
const quantity1 = parseInt(process.argv[7]);
const nftId2 = parseInt(process.argv[8]);
const quantity2 = parseInt(process.argv[9]);
const wrapper = await Wrapper.deployed();
const tokenContractAddress1 = await wrapper.contracts(nftId1);
const tokenContractAddress2 = await wrapper.contracts(nftId2);
const tokenContract1 = await IMastersFedi.at(tokenContractAddress1);
const tokenContract2 = await IMastersFedi.at(tokenContractAddress2);
const tokenName1 = await tokenContract1.name();
const tokenName2 = await tokenContract2.name();
console.log(`Token names: "${tokenName1}", "${tokenName2}"`);
const [account,] = await web3.eth.getAccounts();
const tokenBalance1 = await tokenContract1.balanceOf(account);
const tokenBalance2 = await tokenContract2.balanceOf(account);
console.log(`Unwrapped balances: ${tokenBalance1}, ${tokenBalance2}`);
try {
console.log(`Approving token 1...`);
const result1 = await tokenContract1.approve(wrapper.address, quantity1, { from: account });
console.log(`Approving token 2...`);
const result2 = await tokenContract2.approve(wrapper.address, quantity2, { from: account });
console.log("Batch wrapping...");
const finalResult = await wrapper.wrapBatch(
[nftId1, nftId2],
[quantity1, quantity2],
{ from: account }
);
console.log(`Tx: ${finalResult.tx}`);
}
catch (e) {
console.error(`FAILED: ${e}`);
}
console.log("Done.");
callback();
}

18
scripts/contract-info.js Normal file
View File

@ -0,0 +1,18 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
module.exports = async (callback) => {
const wrapper = await Wrapper.deployed();
const name = await wrapper.name();
const symbol = await wrapper.symbol();
console.log(`Name: "${name}", symbol: "${symbol}"`);
const { 0:receiver, 1:hexAmount} = await wrapper.royaltyInfo(1, "1000000000000000000");
console.log(`Default royalty recipient: ${receiver}`);
const ubiqAmount = web3.utils.fromWei(hexAmount, "ether");
console.log(`Royalty on a 1 Ubiq fee: ${ubiqAmount}`);
callback();
}

18
scripts/query-metadata.js Normal file
View File

@ -0,0 +1,18 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
module.exports = async (callback) => {
const nftId = parseInt(process.argv[6]);
console.log(`Querying metadata for ID: ${nftId}`);
const wrapper = await Wrapper.deployed();
const dataURI = await wrapper.uri(nftId);
console.log(`Data URI: ${dataURI}`);
const data = dataURI.split(',')[1];
const raw = Buffer.from(data, "base64").toString("utf8");
console.log(JSON.stringify(JSON.parse(raw), null, 4));
console.log("Done.");
callback();
}

32
scripts/set-royalties.js Normal file
View File

@ -0,0 +1,32 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
const MAX = 7.5;
module.exports = async (callback) => {
const receiver = process.argv[6];
if (/0x[0-9a-fA-F]{40}/.test(receiver)) {
const percentage = parseFloat(process.argv[7]);
if (percentage > 0 && percentage <= MAX) {
const numerator = Math.ceil(percentage * 100);
console.log(`Numerator to be used: ${numerator}`);
const wrapper = await Wrapper.deployed();
console.log("Exeuting transaction...");
try {
await wrapper.setDefaultRoyalty(receiver, numerator);
}
catch (e) {
console.error(`FAILED: ${e}`);
}
}
else {
console.error(`Royalty must be greater than zero and less than %${percentage}`);
}
}
else {
console.error("Invalid receiver address");
}
callback();
}

12
scripts/supports.js Normal file
View File

@ -0,0 +1,12 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
const interfaceId = process.argv[6];
module.exports = async (callback) => {
const wrapper = await Wrapper.deployed();
const supports = await wrapper.supportsInterface(interfaceId);
console.log(`Supports interface: ${supports}`);
callback();
}

View File

@ -0,0 +1,39 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
module.exports = async (callback) => {
const nftId1 = parseInt(process.argv[6]);
const quantity1 = BigInt(process.argv[7]);
const to = process.argv[8];
if (/0x[0-9a-fA-F]{40}/.test(to)) {
const wrapper = await Wrapper.deployed();
const [account,] = await web3.eth.getAccounts();
const from = process.argv[9] ? process.argv[9] : account;
if (from !== account) {
console.log(`Overrode from address`);
const approved = await wrapper.isApprovedForAll(from, account);
console.log(`Approved? ${approved ? "yes" : "no"}`);
}
const currentBalance = BigInt(await wrapper.balanceOf(from, nftId1));
console.log(`Current balance: ${currentBalance}`);
if (currentBalance >= quantity1) {
console.log("Executing transfer transaction...");
try {
const result = await wrapper.safeTransferFrom(from, to, nftId1, quantity1.toString(), []);
console.log(`Transaction: ${result.tx}`);
}
catch (e) {
console.error(`FAILED: ${e}`);
}
}
else {
console.error("Insufficient balance");
}
}
callback();
}

25
scripts/transfer1.js Normal file
View File

@ -0,0 +1,25 @@
const IMastersFedi = artifacts.require("IMastersFedi");
module.exports = async (callback) => {
const [account,] = await web3.eth.getAccounts();
const quantity = parseInt(process.argv[6]);
const to = process.argv[7];
const tokenContract = await IMastersFedi.at("0x2f873FCc3F4B84E9A62AFf28E9a897ce1BC8814B");
const currentBalance = await tokenContract.balanceOf(account);
console.log(`Current balance: ${currentBalance}`);
console.log("Executing transfer transaction...");
try {
const result = await tokenContract.transfer(to, quantity);
console.log(`Transaction: ${result.tx}`);
const newBalance = await tokenContract.balanceOf(account);
console.log(`New balance: ${newBalance}`);
}
catch (e) {
console.error(`FAILED: ${e}`);
}
callback();
}

35
scripts/unwrap.js Normal file
View File

@ -0,0 +1,35 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
const IMastersFedi = artifacts.require("IMastersFedi");
module.exports = async (callback) => {
const nftId = parseInt(process.argv[6]);
const quantity = parseInt(process.argv[7]);
console.log(`Quantity to unwrap: ${quantity}`);
const wrapper = await Wrapper.deployed();
const tokenContractAddress = await wrapper.contracts(nftId);
const tokenContract = await IMastersFedi.at(tokenContractAddress);
const tokenName = await tokenContract.name();
console.log(`Token contract address: ${tokenContractAddress}, name: "${tokenName}"`);
const [account,] = await web3.eth.getAccounts();
let wrappedQuantity = await wrapper.balanceOf(account, nftId);
console.log(`Wrapped quantity: ${wrappedQuantity}`);
let result1;
try {
console.log("Sending unwrap transaction...");
result1 = await wrapper.unwrap(nftId, quantity, { from: account });
wrappedQuantity = await wrapper.balanceOf(account, nftId);
console.log(`New wrapped balance: ${wrappedQuantity}`);
const unwrappedBalance = await tokenContract.balanceOf(account);
console.log(`New unwrapped balance: ${unwrappedBalance}`);
}
catch (e) {
console.error(`FAILED: ${e}`);
}
callback();
}

View File

@ -0,0 +1,58 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
const IMastersFedi = artifacts.require("IMastersFedi");
const seq = n => n < 1 ? [] : [...seq(n - 1), n];
const to = process.argv[6];
const quantity = parseInt(process.argv[7]);
module.exports = async (callback) => {
if (/0x[0-9a-fA-F]{40}/.test(to) && quantity > 0) {
const wrapper = await Wrapper.deployed();
const [account,] = await web3.eth.getAccounts();
const ids = seq(9);
const quantities = Array(9).fill(quantity);
let approveSuccess = false;
console.log("Approving all contracts for transfer");
try {
for (let id = 1; id <= 9; ++id) {
const tokenContractAddress = await wrapper.contracts(id);
const tokenContract = await IMastersFedi.at(tokenContractAddress);
console.log(`Approving ID ${id}...`);
await tokenContract.approve(wrapper.address, quantity);
}
approveSuccess = true;
}
catch (e) {
console.error(`FAILED: ${e}`);
}
if (approveSuccess) {
console.log("Executing wrap transaction...");
let wrapResult;
try {
wrapResult = await wrapper.wrapBatch(
ids,
quantities
);
console.log(`Wrap transaction: ${wrapResult.tx}`);
}
catch (e) {
console.error(`FAILED: ${e}`);
}
if (wrapResult) {
console.log("Executing batch transfer...");
const transferResult = await wrapper.safeBatchTransferFrom(account, to, ids, quantities, []);
console.log(`Transfer transaction: ${transferResult.tx}`);
}
}
}
else {
console.error("Invalid to address or quantity");
}
callback();
}

51
scripts/wrap.js Normal file
View File

@ -0,0 +1,51 @@
const Wrapper = artifacts.require("CurioERC1155Wrapper");
const IMastersFedi = artifacts.require("IMastersFedi");
module.exports = async (callback) => {
const nftId = parseInt(process.argv[6]);
const quantity = parseInt(process.argv[7]);
console.log(`Quantity to wrap: ${quantity}`);
const wrapper = await Wrapper.deployed();
const tokenContractAddress = await wrapper.contracts(nftId);
const tokenContract = await IMastersFedi.at(tokenContractAddress);
const tokenName = await tokenContract.name();
console.log(`Token contract address: ${tokenContractAddress}, name: "${tokenName}"`);
const [account,] = await web3.eth.getAccounts();
const tokenQuantity = await tokenContract.balanceOf(account);
console.log(`Unwrapped balance: ${tokenQuantity}`);
if (tokenQuantity >= quantity) {
console.log("First approving contract to withdraw token quantity...");
let result1;
try {
result1 = await tokenContract.approve(wrapper.address, quantity, { from: account });
}
catch (e) {
console.error(`FAILED: ${e}`);
}
if (result1) {
console.log("Now submitting wrap transaction...");
let result;
try {
result = await wrapper.wrap(nftId, quantity, { from: account });
console.log(`Transaction: ${result.tx}`);
const wrappedQuantity = await wrapper.balanceOf(account, nftId);
console.log(`New wrapped quantity: ${wrappedQuantity}`);
}
catch (e) {
console.error(`FAILED: ${e}`);
}
}
}
else {
console.error("Insufficient balance");
}
console.log("Done.");
callback();
}

View File

@ -54,8 +54,8 @@ module.exports = {
},
ubiq: {
provider: () => new HDWalletProvider(mnemonic, "https://rpc.octano.dev"),
gas: 5500000,
network_id: 88,
gas: 6150000,
network_id: 8,
confirmations: 2,
timeoutBlocks: 200,
skipDryRun: true
@ -80,7 +80,10 @@ module.exports = {
// settings: { // See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: true,
runs: 200
runs: 1
},
debug: {
revertStrings: "strip"
}
}
}