NFTGuessr is a game similar to GeoGuessr.
The idea is to find the location represented by NFT of a Google Street View.
This game operates on the EVM (Zama). Each location is associated with an NFT GeoSpace (GSP) encrypted with FHE. To
inquire if the found location is correct (if is within the 5 km² radius of the NFT location), it costs you 2 Zama (guess
fee) + winning fees (default is 0, but bet set by the woner and creator of nft GeoSpace).
If you have found it, you win the NFT and 1 ERC20 SpaceCoin (SPC).
Three options are available to you:
- 💼 just hold your GeoSpace in your wallet.
- 🎁 Re-engage your GeoSpace in game with a winning tax (ZAMA). Also, unlock the right to create other NFTs for the game and you can earn SpaceCoin.
- 🔓 Stake SpaceCoin to earn Zama when a player makes a guess request.
The smart contract has a fixed tax ZAMA (2 token by default) + winning fees. And a radius (5 km² by default) for location of nft. The contract owner can change this values and withdraw fees. When an NFT is held by a user, it is not possible for another user to find the NFT. If a user puts their NFT back into play, they will not be able to win it. If a user is the creator of an NFT, they can NEVER win that NFT.
- A total of 50 million SpaceCoin tokens are created. They are allocated among the team (10%) and the users (80%), with a portion reserved (10%). The distribution is carried out using the game.
- 1 SPC is minted when player guess and distribute with all creators of GeoSpace (the creators must call claimReward). 1 SPC is created when a player win a NFT. 1 SPC is burn when a player create a NFT.
- Guess fees: 2 Zama => 1 for teams, and 1 distribute with all staker of SpaceCoin.
- Win fees: a creator or player can set a winning tax. If the player wins, then the prize money will be distributed to the previous owner, and 3% will go to the creator of the NFT.
The spaceCoin staker will receive Zama tokens when a player makes a guess.
An NFT holder can put their NFT back into play with a winning tax. With this action the player can create other NFT for the game. Limited by lifePoint creation.
A user sends an NFT ID along with latitude and longitude (without decimal (1e15)), and a MINIMUM of 2 token + NFT winning fees to verify if their location is within a 5 km² radius of the specified NFT ID. If it is, the NFT is transferred to the user; otherwise, nothing happens.
If the user has access to NFT creation, they must have a valid location, meaning with a latitude and longitude and others with conversion (without decimal (1e15)) for which a Google Street View is available. It's cost 1 SPC. => is burn
- Libraries
- Structs
- Modifiers
- Owner Functions
- Fallback Functions
- Getter Functions
- Changer Functions
- Internal Functions
- Internal Functions Utils
- Gaming Functions
- Conclusion
External library for handling encrypted operations
import "fhevm/lib/TFHE.sol";
Provided by OpenZeppelin, used for managing token IDs.
import "@openzeppelin/contracts/utils/Counters.sol";
Provided by OpenZepplin, used for manage ownable of smart contract.
import "@openzeppelin/contracts/access/Ownable.sol";
Provided by OppenZepplin, used for math.
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
An extension of the ERC721 standard, allowing enumeration of all tokens.
"@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
External library for ERC20
"@openzeppelin/contracts/token/ERC20/ERC20.sol";
Represents the encrypted geographical coordinates of an NFT location.
struct Location {
euint32 northLat;
euint32 southLat;
euint32 eastLon;
euint32 westLon;
euint32 lat;
euint32 lng;
bool isValid;
}
Represents the decrypted geographical coordinates of an NFT location.
struct NFTLocation {
uint32 northLat;
uint32 southLat;
uint32 eastLon;
uint32 westLon;
uint lat;
uint lng;
}
Checks if the user has access to certain functionalities.
modifier isAccess() {
require(stakeNft[msg.sender].length >= 3, "The owner must stake 3 NFTs to create a new NFT");
_;
}
Allows the owner to withdraw Zama from the contract.
function withdraw() external onlyOwner {
// ... (Withdraw functionality)
}
Allows the owner to withdraw token SPC from the contract.
function withdrawToken() external onlyOwner {
// ... (Withdraw token functionality)
}
Function to reward the user with ERC-20 tokens script launch every 24 hours and check if user have receive reward in a same day.
function rewardUsersWithERC20() external onlyOwner {
// ... (reward and transfer functionality)
}
Fallback function to receive Ether.
receive() external payable {}
Gets the location of an NFT for the contract owner.
function getNFTLocation(uint256 tokenId) external view onlyOwner returns (NFTLocation memory) {
// ... (Get NFT location for owner)
}
Gets the location of an NFT for the owner.
function getNFTLocationForOwner(uint256 tokenId) external view returns (NFTLocation memory) {
// ... (Get NFT location for owner)
}
Gets the address associated with the reset of an NFT.
function getAddressResetWithToken(uint256 _tokenId) public view returns (address) {
// ... (Get address associated with NFT reset)
}
Gets the address associated with the creation of an NFT.
function getAddressCreationWithToken(uint256 _tokenId) public view returns (address) {
// ... (Get address associated with NFT creation)
}
Gets the address associated with the staking of an NFT.
function getAddressStakeWithToken(uint256 _tokenId) public view returns (address) {
// ... (Get address associated with NFT staking)
}
Gets the fee associated with a user and an NFT.
function getFee(address user, uint256 id) external view returns (uint256) {
// ... (Get fee associated with user and NFT)
}
Gets an array of NFTs owned by a user.
function getOwnedNFTs(address user) external view returns (uint256[] memory) {
// ... (Get array of owned NFTs)
}
Gets the creation IDs and fees of NFTs created by a user.
function getNftCreationAndFeesByUser(address user) public view returns (uint256[] memory, uint256[] memory) {
// ... (Get NFT creation IDs and fees by user)
}
Gets the IDs and fees of NFTs reset by a user.
function getResetNFTsAndFeesByOwner(address user) public view returns (uint256[] memory, uint256[] memory) {
// ... (Get reset NFT IDs and fees by owner)
}
Gets the IDs of NFTs staked by a user.
function getNFTsStakedByOwner(address _owner) public view returns (uint256[] memory) {
// ... (Get NFT IDs staked by owner)
}
Gets the IDs of NFTs reset by a user.
function getNFTsResetByOwner(address _owner) public view returns (uint256[] memory) {
// ... (Get NFT IDs reset by owner)
}
Gets the total number of NFTs in existence.
function getTotalNft() public view returns (uint256) {
// ... (Get total number of NFTs)
}
Check if the location is valid. (true or false) stake, own or not.
function isLocationValid(uint256 locationId) public view returns (bool) {
// ... (Check if location is valid functionality)
}
Check if user have access to creation gps (3 NFT GeoSpace stake).
function isAccessCreation(address user) public view returns (bool) {
// ... (Check if location access functionality)
}
Changes the fees required for NFT operations.
function changeFees(uint256 _fees) external onlyOwner {
// ... (Change fees functionality)
}
Changes the number of NFTs required to stake.
function changeNbNftStake(uint256 _nb) external onlyOwner {
// ... (Change number of NFTs required to stake functionality)
}
Changes the owner of the contract.
function changeOwner(address _newOwner) external onlyOwner {
// ... (Change owner functionality)
}
Change address tokenERC20.
function setAddressToken(address _tokenErc20) external onlyOwner {
// ... (Setter functionality)
}
Function to change the fees required for NFT creation.
function changeFeesCreation(uint256 _feesCreation) external onlyOwner {
// ... (Change fees)
}
Function to change reward user checkGps in SPC
function changeRewardUser(uint256 _amountReward) external onlyOwner {
// ... (Change reward)
}
Function to change reward user daily 24h in SPC
function changeRewardUsers(uint256 _amountReward) external onlyOwner {
// ... (Change reward)
}
// Function to change amount mint with function createGpsOwnerNft
function changeAmountMintErc20(uint256 _amountMintErc20) external onlyOwner {
// ... (Change reward)
}
Internal function to return the base URI for metadata.
function _baseURI() internal view virtual override(ERC721) returns (string memory) {
// ... (Get base URI for metadata)
}
Internal function to get the decrypted location.
- Return struct
Location
. - Use
TFHE.decrypt
to decrypt location.
function getLocation(Location memory _location) internal view returns (NFTLocation memory) {
// ... (Get decrypted location)
}
Internal function to check if the location is already used.
- Return true if location doesn't exist on smart contract
- Use
TFHE.optReq
to require ifTFHE.ne
value compare between location send by creator and smart contract
function isLocationAlreadyUsed(Location memory newLocation) internal view {
// ... (Check if location is already used)
}
Internal function to check if the user has enough funds to pay NFT tax.
function checkFees(uint256 _tokenId, address previous) internal view returns (uint256) {
// ... (Check user fees functionality)
}
Internal function to mint NFTs with location data and associated fees.
- Check if
data.length
is good. - Loop through the
data
array. - Increment counter
tokenId
- Create struct
Location
with encrypted value. (euint32
). - Check if location exist on smart contract with
isLocationAlreadyUsed
. - If ok,
locate
is save onlocations
. - Set mapping
userFees
withmsg.sender
. - Set mapping
isStake
to false. - Set mapping
creatorNft
savemsg.sender
withtokenId
. - Set mapping
tokenCreationAddress
to savemsg.sender
andtokenId
to access facilitate (no loop for). - Set mapping
ownerNft
to prevent owner indirectly. - call function
_mint
of oppenZepplin. - Emit event.
function mint(bytes[] calldata data, address _owner, uint256[] calldata feesData) internal {
// ... (Mint NFTs functionality)
}
Function to transfer reward the user if stake minimum 1 NFT GeoSpace with ERC-20 tokens SpaceCoin
function rewardUserWithERC20(address user) internal view returns (bool) {
// ... (transfer user reward)
}
Function internal to create object Location with conversion FHE bytes to euint
function createObjectLocation(bytes[] calldata data, uint256 baseIndex) internal pure returns (Location memory) {
// ... (create object Location)
}
Function to set data mapping and array for minting NFT GeoSpace function
function setDataForMinting(uint256 tokenId, uint256 feesToSet, Location memory locate) internal {
// ... (set data on variables)
}
Internal function to reset mappings.
function resetMapping(uint256 tokenId, address previous) internal {
// ... (Reset mapping functionality)
}
Internal function to remove an element from an array uint256.
function removeElement(uint256[] storage array, uint256 element) internal {
// ... (Remove element from array functionality)
}
Internal function to check if an element exists in an array.
function contains(uint256[] storage array, uint256 element) internal view returns (bool) {
// ... (Check if element exists in array functionality)
}
Internal function to check if a set of coordinates is within a location.
- Return true latitude and longitude is arround 5km2 else return false
- Use
TFHE.ge
to check if latitude and longitude is gretter or equal andTFHE.le
inverse. - Use
TFHE.decrypt
to decrypt boolean and use it on function checkGps check if location is good.
function isOnPoint(euint32 lat, euint32 lng, Location memory location) internal view returns (bool) {
// ... (Check if coordinates are within location functionality)
}
Internal function to remove an element from an array address.
function removeElementAddress(address[] storage array, address element) internal {
// ... (Remove element from array functionality)
}
Internal function to check if an element exists in an array.
function containsAddress(address[] storage array, address element) internal view returns (bool) {
// ... (return bool if success)
}
Internal function to create transaction from msg.sender to smart contract
function transactionCoinSpace() internal {
// ... (transfer erc20 to smart contract)
}
Creates one or more NFTs with taxes only for the owner smart contract (tax set on 0)
function createGpsOwner(bytes[] calldata data, uint256[] calldata feesData) external onlyOwner {
// ... (Create NFTs functionality for owner)
}
Creates one or more NFTs with taxes (for one round only) for the owner of the NFT.
function createGpsOwnerNft(bytes[] calldata data, uint256[] calldata feesData) external isAccess {
// ... (Create NFTs functionality for owner of the NFT)
}
Stakes one or more NFTs
function stakeNFT(uint256[] calldata nftIndices) external {
// ... (Stake NFTs functionality)
}
Unstakes one or more NFTs, deleting taxes.
function unstakeNFT(uint256[] calldata nftIndices) external {
// ... (Unstake NFTs functionality)
}
Checks GPS coordinates against a specified location's coordinates. This function allows determining whether a user finds the NFT located within a 5km² radius of the latitude and longitude of the GPS point.
-
Decrypt value send by
msg.sender
. -
Get
totalSupply
to check ifnftId
send bymsg.sender
is lower thantotalSupply
. -
Check if
locations[_tokenId]
is valid (if variable booleanisValid
set ontrue
). -
Check if latitude (
lat
) and longitude (lng
) send bymsg.sender
isonPoint
(check part Functions internals 8.4). -
Check if
msg.sender
is theownerOf(_tokenId)
. -
Before the function check if location is valid to check (if other user have nft).
-
The creator of nft cannot win.
-
To prevent, stake nft id cannot win (but check before (3)).
-
Check if
ownerNft
is owner, because the smart contract can hold an NFT owned by a user, as is the case with Staking or back in game of the NFT. -
Check if
msg.sender
is the creator of nft. -
Check fees (fees base + fees creator or back in game).
-
Transfer fund to smart contract and user owner correspondly fees.
-
Delete all mapping (fees, valid location true to false)
-
Transfer
ownerNft
tomsg.sender
-
Transfer NFT to winner.
-
Emit event.
function checkGps(
bytes calldata userLatitude,
bytes calldata userLongitude,
uint256 _tokenId
) external payable returns (bool) {
// ... (Check GPS functionality)
}
Resets one or more NFTs, putting them back into the game with tax just for one round
function resetNFT(uint256[] calldata tokenIds, uint256[] calldata taxes) external {
// ... (Reset NFTs functionality)
}
Cancels the reset of one or more NFTs.
function cancelResetNFT(uint256[] calldata tokenIds) external {
// ... (Cancel reset of NFTs functionality)
}
Function to burn (destroy) an NFT.
function burnNFT(uint256 tokenId) external onlyOwner {
// ... (Burn NFT functionality)
}
The NftGuessr smart contract provides a flexible and secure platform for a location-based NFT guessing game. Users can create, stake, transfer, reset, and interact with NFTs using encrypted GPS coordinates. The contract ensures ownership, fee management, and location validation while emitting events to track key activities.
[Jérémy Combe]
This contract is licensed under the MIT License.