Skip to content

Instantly share code, notes, and snippets.

@CJ42
Last active September 29, 2023 14:16
Show Gist options
  • Save CJ42/d707ee4a884d69a9108e6e95d196c4bd to your computer and use it in GitHub Desktop.
Save CJ42/d707ee4a884d69a9108e6e95d196c4bd to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.17;
import {LSP7Mintable} from "./LSP7DigitalAsset/presets/LSP7Mintable.sol";
import {LSP7Burnable} from "./LSP7DigitalAsset/extensions/LSP7Burnable.sol";
// for overriding internal _mint function
import {
LSP7DigitalAsset,
LSP7DigitalAssetCore
} from "./LSP7DigitalAsset/LSP7DigitalAsset.sol";
contract LSP7DigitalStickers is
LSP7DigitalAsset,
// LSP7CappedSupply, // TODO: is this extension necessary if there is no way to mint after deployment?
LSP7Burnable
{
constructor(
string memory stickerName,
string memory stickerSymbol,
address contractOwner,
uint256 totalNumberOfStickersInCirculation
) LSP7DigitalAsset(stickerName, stickerSymbol, contractOwner, true) {
// mint the total number of stickers that the LSP8 Collection owns
_mint({
to: msg.sender,
amount: totalNumberOfStickersInCirculation,
force: true,
data: abi.encodePacked(
"creating ",
totalNumberOfStickersInCirculation,
" new stickers for this collection!"
)
});
}
}
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.17;
// modules
import {
LSP8IdentifiableDigitalAsset as LSP8
} from "./LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol";
import {LSP7DigitalStickers} from "./LSP7DigitalStickers.sol";
// libraries
import {
Strings as StringUtils
} from "@openzeppelin/contracts/utils/Strings.sol";
// List of LUKSO stickers collection (example):
//
// - LUKSO pink rocket --> 1_000_000 --> LSP7 contract 0x9760e8347D5E8f9042726E3B4DdE9Ae787476101
// - LUKSO silver --> 850_000 --> LSP7 contract 0xF21D47961A84D00E6d8fa8430dfC456D7be75547
// - LUKSO good --> 30_000 --> LSP7 contract 0x7B4d8090BB9cA1d8B081481C2a6b73b1a579C6eb
// - LUKSO logo --> 5_000_000 --> LSP7 contract ...
// - LUKSO pink pill --> 90_000 --> LSP7 contract ...
// - LUKSO UP blue square --> 30_000 --> LSP7 contract ...
// constants
import {
_LSP4_TOKEN_NAME_KEY,
_LSP4_METADATA_KEY
} from "./LSP4DigitalAssetMetadata/LSP4Constants.sol";
import {
_LSP8_TOKENID_TYPE_ADDRESS
} from "./LSP8IdentifiableDigitalAsset/LSP8Constants.sol";
// errors
import {
LSP8NonExistentTokenId
} from "./LSP8IdentifiableDigitalAsset/LSP8Errors.sol";
/**
* @dev LSP8 collection of digital stickers, where each sticker is a LSP7 contract with a limited supply available.
* WARNING!
* This contract is for testing purpose. It has not been audited and might contain vulnerabilities. DO NOT USE IN PRODUCTION!
*/
contract LSP8DigitalStickersCollection is
LSP8(
"Example Digital Stickers Collection",
"EDSC",
msg.sender,
_LSP8_TOKENID_TYPE_ADDRESS
)
{
using StringUtils for string;
// Constants ---
bytes32 internal constant _LSP8_REFERENCE_CONTRACT_DATA_KEY =
0x708e7b881795f2e6b6c2752108c177ec89248458de3bf69d0d43480b3e5034e6;
bytes32 internal constant _STICKER_PRICE_DATA_KEY =
keccak256("Price Per Sticker");
// Errors ---
error FailedDeployingLSP7StickerContract(string stickerName);
error CouldntFindStickerNameToBuyInCollection(string stickerName);
error InsufficientPricePaid(
uint256 msgValueSent,
uint256 paymentAmountRequired
);
error CouldntSendChangeBackToBuyer(address buyer, uint256 changeAmount);
function createNewStickerCollection(
string calldata stickerName,
string calldata stickerSymbol,
uint256 numberOfStickers,
uint256 pricePerSticker // in wei
) external onlyOwner {
try
new LSP7DigitalStickers(
stickerName,
stickerSymbol,
address(this),
numberOfStickers
)
returns (LSP7DigitalStickers newSticker) {
// example:
// newSticker (contract address)= 0x9760e8347D5E8f9042726E3B4DdE9Ae787476101
// 0x0000000000000000000000009760e8347D5E8f9042726E3B4DdE9Ae787476101
bytes32 stickerId = bytes32(abi.encode(address(newSticker)));
bytes32[] memory dataKeysToSet = new bytes32[](2);
bytes[] memory dataValuesToSet = new bytes[](2);
// Set Data Key "LSP8ReferenceContract",
dataKeysToSet[0] = _LSP8_REFERENCE_CONTRACT_DATA_KEY;
dataValuesToSet[0] = abi.encodePacked(address(this), stickerId); // "valueType": "(address,bytes32)"
// Set Data Key specific to the price per sticker
dataKeysToSet[1] = _STICKER_PRICE_DATA_KEY;
dataValuesToSet[1] = abi.encode(pricePerSticker);
newSticker.setDataBatch(dataKeysToSet, dataValuesToSet);
// need to pass `force = true` as will attempt to notify `universalReceiver` function of this contract
// which does not implement LSP1
_mint({
to: address(this),
tokenId: stickerId,
force: true,
data: ""
});
} catch (bytes memory) {
revert FailedDeployingLSP7StickerContract(stickerName);
}
}
function setStickerMetadata(
bytes calldata lsp4StickerMetadataJSONURL,
LSP7DigitalStickers stickerContract
) external onlyOwner {
// convert the sticker contract address into a tokenId and
// CHECK if the sticker we are trying to buy exists
_existsOrError(bytes32(abi.encode(stickerContract)));
stickerContract.setData(_LSP4_METADATA_KEY, lsp4StickerMetadataJSONURL);
}
function getAllStickersContracts() public view returns (address[] memory) {
bytes32[] memory allTokenIdsFromCollections = tokenIdsOf(address(this));
address[] memory stickersAddresses = new address[](
allTokenIdsFromCollections.length
);
for (uint256 ii; ii < stickersAddresses.length; ++ii) {
address stickerAddress = address(
bytes20(allTokenIdsFromCollections[ii] << 96)
);
stickersAddresses[ii] = stickerAddress;
}
return stickersAddresses;
}
/**
* @param stickerContract (e.g: 0x9760e8347D5E8f9042726E3B4DdE9Ae787476101)
*/
function buyStickersFromContractAddress(
LSP7DigitalStickers stickerContract,
uint256 numberOfStickersToBuy
) external payable {
// convert the sticker contract address into a tokenId and
// CHECK if the sticker we are trying to buy exists
_existsOrError(bytes32(abi.encode(stickerContract)));
uint256 pricePerSticker = abi.decode(
stickerContract.getData(_STICKER_PRICE_DATA_KEY),
(uint256)
);
uint256 minimumAmountToPay = numberOfStickersToBuy * pricePerSticker;
_checkPricePaid(minimumAmountToPay);
// give the change back
if (msg.value > minimumAmountToPay) {
_giveChangeBack(minimumAmountToPay);
}
stickerContract.transfer(
address(this),
msg.sender,
numberOfStickersToBuy,
false, // We require the buyer to be a UP or a contract that implements LSP1
""
);
}
/**
*
* @param stickerId (e.g: 0x0000000000000000000000009760e8347D5E8f9042726E3B4DdE9Ae787476101)
*/
function buyOneStickerFromId(
bytes32 stickerId,
uint256 numberOfStickersToBuy
) external payable {
// CHECK if the sticker we are trying to buy exists
_existsOrError(stickerId);
address payable stickerContractAddress = payable(
address(bytes20(stickerId << 96))
);
LSP7DigitalStickers stickerContract = LSP7DigitalStickers(
stickerContractAddress
);
uint256 pricePerSticker = abi.decode(
stickerContract.getData(_STICKER_PRICE_DATA_KEY),
(uint256)
);
uint256 minimumAmountToPay = numberOfStickersToBuy * pricePerSticker;
_checkPricePaid(minimumAmountToPay);
// give the change back
if (msg.value > minimumAmountToPay) {
_giveChangeBack(minimumAmountToPay);
}
// transfer the token to the buyer
LSP7DigitalStickers(stickerContractAddress).transfer(
address(this),
msg.sender,
numberOfStickersToBuy,
false, // We require the buyer to be a UP or a contract that implements LSP1
""
);
}
// Disabling this function for now as contract size is too big
/**
* @dev Requires to pay 1 LYX to buy 1 sticker :)
* @param stickerName e.g: "LUKSO Good"
*/
function buyOneStickerFromName(
string calldata stickerName,
uint256 numberOfStickersToBuy
) external payable {
address[] memory allStickersContracts = getAllStickersContracts();
// allocate memory arrays outside the loop to save in gas, and override the same memory address on each subsequent iteration
// instead of allocating new memory of each new iteration
bytes32[] memory dataKeysToFetch = new bytes32[](2);
bytes[] memory dataValuesRetrieved = new bytes[](2);
LSP7DigitalStickers stickerContract;
uint256 pricePerSticker;
uint256 minimumAmountToPay;
for (uint256 ii; ii < allStickersContracts.length; ++ii) {
stickerContract = LSP7DigitalStickers(
payable(allStickersContracts[ii])
);
dataKeysToFetch[0] = _LSP4_TOKEN_NAME_KEY;
dataKeysToFetch[1] = _STICKER_PRICE_DATA_KEY;
dataValuesRetrieved = stickerContract.getDataBatch(dataKeysToFetch);
string memory lsp4TokenName = string(dataValuesRetrieved[0]);
if (stickerName.equal(lsp4TokenName)) {
pricePerSticker = abi.decode(
dataValuesRetrieved[1],
(uint256)
);
minimumAmountToPay = numberOfStickersToBuy *
pricePerSticker;
_checkPricePaid(minimumAmountToPay);
// give the change back
if (msg.value > minimumAmountToPay) {
_giveChangeBack(minimumAmountToPay);
}
// transfer the token to the buyer
stickerContract.transfer(
address(this),
msg.sender,
numberOfStickersToBuy,
false, // We require the buyer to be a UP or a contract that implements LSP1
""
);
return;
}
}
revert CouldntFindStickerNameToBuyInCollection(stickerName);
}
function _checkPricePaid(uint256 minimumAmountToPay) internal {
if (msg.value < minimumAmountToPay) {
revert InsufficientPricePaid(msg.value, minimumAmountToPay);
} else {
return;
}
}
function _giveChangeBack(uint256 minimumAmountToPay) internal {
(bool success, ) = msg.sender.call{
value: msg.value - minimumAmountToPay
}("");
if (!success) {
revert CouldntSendChangeBackToBuyer(
msg.sender,
msg.value - minimumAmountToPay
);
}
}
}
{
"LSP4Metadata": {
"description": "Silver Shiny Dog",
"links": [
{ "title": "Twitter", "url": "https://x.com/JeanCavallera/status/1680501847482593280?s=20" },
{ "title": "Website", "url": "https://lukso.network" }
],
"attributes": [
{
"key": "Standard type",
"value": "LSP",
"type": "string"
},
{
"key": "Standard number",
"value": 4,
"type": "number"
},
{
"key": "🆙",
"value": false,
"type": "boolean"
},
{
"key": "sticker width",
"value": "5cm",
"type": "string"
},
{
"key": "sticker height",
"value": "10cm",
"type": "string"
},
{
"key": "number of stickers in existence",
"value": "30,000",
"type": "number"
}
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment