Skip to content

Commit

Permalink
Buildspace tutorial - Domain name service on Polygon
Browse files Browse the repository at this point in the history
  • Loading branch information
pdarden committed Feb 21, 2022
0 parents commit 978b904
Show file tree
Hide file tree
Showing 10 changed files with 28,015 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
.env
coverage
coverage.json
typechain

#Hardhat files
cache
artifacts
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Basic Sample Hardhat Project

This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, a sample script that deploys that contract, and an example of a task implementation, which simply lists the available accounts.

Try running some of the following tasks:

```shell
npx hardhat accounts
npx hardhat compile
npx hardhat clean
npx hardhat test
npx hardhat node
node scripts/sample-script.js
npx hardhat help
```
143 changes: 143 additions & 0 deletions contracts/Domains.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

import { StringUtils } from "./libraries/StringUtils.sol";
import {Base64} from "./libraries/Base64.sol";
import "hardhat/console.sol";

contract Domains is ERC721URIStorage {
// Magic given to us by OpenZeppelin to help us keep track of tokenIds.
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;

string public tld;

// We'll be storing our NFT images on chain as SVGs
string svgPartOne = '<svg xmlns="http://www.w3.org/2000/svg" width="270" height="270" fill="none"><path fill="url(#B)" d="M0 0h270v270H0z"/><defs><filter id="A" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="270" width="270"><feDropShadow dx="0" dy="1" stdDeviation="2" flood-opacity=".225" width="200%" height="200%"/></filter></defs><path d="M72.863 42.949c-.668-.387-1.426-.59-2.197-.59s-1.529.204-2.197.59l-10.081 6.032-6.85 3.934-10.081 6.032c-.668.387-1.426.59-2.197.59s-1.529-.204-2.197-.59l-8.013-4.721a4.52 4.52 0 0 1-1.589-1.616c-.384-.665-.594-1.418-.608-2.187v-9.31c-.013-.775.185-1.538.572-2.208a4.25 4.25 0 0 1 1.625-1.595l7.884-4.59c.668-.387 1.426-.59 2.197-.59s1.529.204 2.197.59l7.884 4.59a4.52 4.52 0 0 1 1.589 1.616c.384.665.594 1.418.608 2.187v6.032l6.85-4.065v-6.032c.013-.775-.185-1.538-.572-2.208a4.25 4.25 0 0 0-1.625-1.595L41.456 24.59c-.668-.387-1.426-.59-2.197-.59s-1.529.204-2.197.59l-14.864 8.655a4.25 4.25 0 0 0-1.625 1.595c-.387.67-.585 1.434-.572 2.208v17.441c-.013.775.185 1.538.572 2.208a4.25 4.25 0 0 0 1.625 1.595l14.864 8.655c.668.387 1.426.59 2.197.59s1.529-.204 2.197-.59l10.081-5.901 6.85-4.065 10.081-5.901c.668-.387 1.426-.59 2.197-.59s1.529.204 2.197.59l7.884 4.59a4.52 4.52 0 0 1 1.589 1.616c.384.665.594 1.418.608 2.187v9.311c.013.775-.185 1.538-.572 2.208a4.25 4.25 0 0 1-1.625 1.595l-7.884 4.721c-.668.387-1.426.59-2.197.59s-1.529-.204-2.197-.59l-7.884-4.59a4.52 4.52 0 0 1-1.589-1.616c-.385-.665-.594-1.418-.608-2.187v-6.032l-6.85 4.065v6.032c-.013.775.185 1.538.572 2.208a4.25 4.25 0 0 0 1.625 1.595l14.864 8.655c.668.387 1.426.59 2.197.59s1.529-.204 2.197-.59l14.864-8.655c.657-.394 1.204-.95 1.589-1.616s.594-1.418.609-2.187V55.538c.013-.775-.185-1.538-.572-2.208a4.25 4.25 0 0 0-1.625-1.595l-14.993-8.786z" fill="#fff"/><defs><linearGradient id="B" x1="0" y1="0" x2="270" y2="270" gradientUnits="userSpaceOnUse"><stop stop-color="#cb5eee"/><stop offset="1" stop-color="#0cd7e4" stop-opacity=".99"/></linearGradient></defs><text x="32.5" y="231" font-size="27" fill="#fff" filter="url(#A)" font-family="Plus Jakarta Sans,DejaVu Sans,Noto Color Emoji,Apple Color Emoji,sans-serif" font-weight="bold">';
string svgPartTwo = '</text></svg>';

address payable public owner;
mapping(string => address) public domains;
mapping(string => string) public records;
mapping (uint => string) public names;

error Unauthorized();
error AlreadyRegistered();
error InvalidName(string name);

constructor(string memory _tld) payable ERC721("Grid Name Service", "GNS") {
owner = payable(msg.sender);
tld = _tld;
console.log("%s name service deployed", _tld);
}

function register(string calldata name) public payable{
if (domains[name] != address(0)) revert AlreadyRegistered();
if (!valid(name)) revert InvalidName(name);

uint256 _price = price(name);
require(msg.value >= _price, "Not enough Matic paid");

// Combine the name passed into the function with the TLD
string memory _name = string(abi.encodePacked(name, ".", tld));
// Create the SVG (image) for the NFT with the name
string memory finalSvg = string(abi.encodePacked(svgPartOne, _name, svgPartTwo));
uint256 newRecordId = _tokenIds.current();
uint256 length = StringUtils.strlen(name);
string memory strLen = Strings.toString(length);

console.log("Registering %s.%s on the contract with tokenID %d", name, tld, newRecordId);

// Create the JSON metadata of our NFT. We do this by combining strings and encoding as base64
string memory json = Base64.encode(
bytes(
string(
abi.encodePacked(
'{"name": "',
_name,
'", "description": "A domain on the Grid name service", "image": "data:image/svg+xml;base64,',
Base64.encode(bytes(finalSvg)),
'","length":"',
strLen,
'"}'
)
)
)
);

string memory finalTokenUri = string( abi.encodePacked("data:application/json;base64,", json));

console.log("\n--------------------------------------------------------");
console.log("Final tokenURI", finalTokenUri);
console.log("--------------------------------------------------------\n");

_safeMint(msg.sender, newRecordId);
_setTokenURI(newRecordId, finalTokenUri);
domains[name] = msg.sender;

names[newRecordId] = name;
_tokenIds.increment();
}

// This function will give us the price of a domain based on length
function price(string calldata name) public pure returns(uint) {
uint len = StringUtils.strlen(name);
require(len > 0);
if (len == 3) {
return 5 * 10**17; // 5 MATIC = 5 000 000 000 000 000 000 (18 decimals). We're going with 0.5 Matic cause the faucets don't give a lot
} else if (len == 4) {
return 3 * 10**17; // To charge smaller amounts, reduce the decimals. This is 0.3
} else {
return 1 * 10**17;
}
}

function getAddress(string calldata name) public view returns (address) {
// Check that the owner is the transaction sender
return domains[name];
}

function setRecord(string calldata name, string calldata record) public {
// Check that the owner is the transaction sender
if (msg.sender != domains[name]) revert Unauthorized();
records[name] = record;
}

function getRecord(string calldata name) public view returns(string memory) {
return records[name];
}

modifier onlyOwner() {
require(isOwner());
_;
}

function isOwner() public view returns (bool) {
return msg.sender == owner;
}

function withdraw() public onlyOwner {
uint amount = address(this).balance;

(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Failed to withdraw Matic");
}

function getAllNames() public view returns (string[] memory) {
console.log("Getting all names from contract");
string[] memory allNames = new string[](_tokenIds.current());
for (uint i = 0; i < _tokenIds.current(); i++) {
allNames[i] = names[i];
console.log("Name for token %d is %s", i, allNames[i]);
}

return allNames;
}

function valid(string calldata name) public pure returns(bool) {
return StringUtils.strlen(name) >= 3 && StringUtils.strlen(name) <= 10;
}
}
78 changes: 78 additions & 0 deletions contracts/libraries/Base64.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
*Submitted for verification at Etherscan.io on 2021-09-05
*/

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// [MIT License]
/// @title Base64
/// @notice Provides a function for encoding some bytes in base64
/// @author Brecht Devos <[email protected]>
library Base64 {
bytes internal constant TABLE =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/// @notice Encodes some bytes to the base64 representation
function encode(bytes memory data) internal pure returns (string memory) {
uint256 len = data.length;
if (len == 0) return "";

// multiply by 4/3 rounded up
uint256 encodedLen = 4 * ((len + 2) / 3);

// Add some extra buffer at the end
bytes memory result = new bytes(encodedLen + 32);

bytes memory table = TABLE;

assembly {
let tablePtr := add(table, 1)
let resultPtr := add(result, 32)

for {
let i := 0
} lt(i, len) {

} {
i := add(i, 3)
let input := and(mload(add(data, i)), 0xffffff)

let out := mload(add(tablePtr, and(shr(18, input), 0x3F)))
out := shl(8, out)
out := add(
out,
and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF)
)
out := shl(8, out)
out := add(
out,
and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF)
)
out := shl(8, out)
out := add(
out,
and(mload(add(tablePtr, and(input, 0x3F))), 0xFF)
)
out := shl(224, out)

mstore(resultPtr, out)

resultPtr := add(resultPtr, 4)
}

switch mod(len, 3)
case 1 {
mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
}
case 2 {
mstore(sub(resultPtr, 1), shl(248, 0x3d))
}

mstore(result, encodedLen)
}

return string(result);
}
}
35 changes: 35 additions & 0 deletions contracts/libraries/StringUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
// Source:
// https://github.com/ensdomains/ens-contracts/blob/master/contracts/ethregistrar/StringUtils.sol
pragma solidity >=0.8.4;

library StringUtils {
/**
* @dev Returns the length of a given string
*
* @param s The string to measure the length of
* @return The length of the input string
*/
function strlen(string memory s) internal pure returns (uint) {
uint len;
uint i = 0;
uint bytelength = bytes(s).length;
for(len = 0; i < bytelength; len++) {
bytes1 b = bytes(s)[i];
if(b < 0x80) {
i += 1;
} else if (b < 0xE0) {
i += 2;
} else if (b < 0xF0) {
i += 3;
} else if (b < 0xF8) {
i += 4;
} else if (b < 0xFC) {
i += 5;
} else {
i += 6;
}
}
return len;
}
}
28 changes: 28 additions & 0 deletions hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require("@nomiclabs/hardhat-waffle");
require('dotenv').config();

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();

for (const account of accounts) {
console.log(account.address);
}
});

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.10",
networks: {
mumbai: {
url: process.env.DEV_ALCHEMY_KEY,
accounts: [process.env.PRIVATE_KEY],
}
}
};
Loading

0 comments on commit 978b904

Please sign in to comment.