Solidity for beginner

You can connect the Remix into your Ganache in your local machine:

Solidity principle

Solidity compile into byte code and then deploy on network

Solidity principle
solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.14;
contract MyContract {
  string public ourString = "Hello World";
}

Sample code of writing solidity

solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.13;
contract MyContract {
  string public myString = "Hello world";
  function updateOurString(string memory _myString) public {
    myString = _myString;
  }
}
Solidity principle
Solidity principle
Solidity principle
Solidity principle
Solidity principle

Variables in solidity

text
uint is similar tor unit256 // 0 - ( 2 ^ 256 ) -1 uint8 uint16int -2^128 to 2 ^ 128 -1

Be careful about overflow

Solidity 0.8 unchecked example

solidity dosen’t check the underflow and the value will be maximum of uint256

solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.15;
contract ExampleWrapAround {
  uint256 public myUint;
  function decrementUintUnchecked() public {
    unchecked {
      myUint--;
    }
  }
  function decrementUint() public {
    myUint--;
  }
}

Check two string in solidity

javascript
function compareTwoStrings(string memory _myString) public view returns(bool) {
  return keccak256(abi.encodePacked(myString)) == keccak256(abi.encodePacked(_myString));
}

bytes can concert into string

Address type

Default value is 0x00000000000000000 (14 zeros)

Address has a balance

javascript
function getAddressBalance() public view returns(uint) {
  return someAddress.balance;
}

1 ETH = 10¹⁸ wei

Account of message sender

solidity
msg.sender  //is address of account

View or Pure

View function can access global variable in contract and can’t write them. Pure function can’t call golbal variable. Cost of these function is nothing.

we can’t get or return value when writing value.

solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.15;
contract ExampleViewPure {
  uint public myStorageVariable;
  function getMyStorageVariable() public view returns(uint) {
    return myStorageVariable;
  }
  function getAddition(uint a, uint b) public pure returns(uint) {
    return a+b;
  }
  function setMyStorageVariable(uint _newVar) public returns(uint) {
    myStorageVariable = _newVar;
    return _newVar;
  }
}

Constructor

solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.15;
contract ExampleConstructor {
  address public myAddress;
  constructor(address _someAddress) {
    myAddress = _someAddress;
  }
  function setMyAddress(address _myAddress) public {
    myAddress = _myAddress;
  }
  function setMyAddressToMsgSender() public {
    myAddress = msg.sender;
  }
}

Variables

All variables has default value, fixed points not implemented yet in solidity.

String and bytes are expensive.

javascript
function updateTheMessage(string memory _newMessage) public {
  if(msg.sender == owner) {
    theMessage = _newMessage;
    changeCounter++;
  }
}

List of ETH Testnet Faucets

Sometimes Faucets don’t work as expected. Unfortunately there is nothing much that I can do about it. It is time intensive to run a faucet and usually it doesn’t pay off economically. Here is a list of Faucets in case the one here doesn’t work, you can probably switch to another one:

My current go-to Faucet I really like for all networks: https://faucet.paradigm.xyz

Ropsten: https://faucet.metamask.io

Rinkeby: https://faucet.rinkeby.io https://www.rinkebyfaucet.com https://app.mycrypto.com/faucet https://faucets.chain.link/rinkeby

Kovan: https://gitter.im/kovan-testnet/faucet basically post your eth address in the gitter chat

Görli: https://goerli-faucet.slock.it/index.html https://faucet.goerli.mudit.blog

Another “special edition” Faucet is maintained by Keir “Blockchain-Gandalf” Finlow-Bates, who also wrote a great book about Blockchains. He tries to maintain it as good as possible and it outputs Ropsten Ether: https://moonborrow.com

Kintsugi (Eth2.0): https://kintsugi.themerge.dev

Blockchain nodes

Blockchain nodes are the moderators that build the infrastructure of a decentralized network. Their primary function is to maintain the public ledger’s consensus, which varies according to the type of node. The architecture and design requirements of a particular blockchain protocol determine the types of nodes.

What is a Ethereum transaction?

Transactions are cryptographically signed instructions from accounts. An account will initiate a transaction to update the state of the Ethereum network. The simplest transaction is transferring ETH from one account to another.

An Ethereum transaction refers to an action initiated by an externally-owned account, in other words an account managed by a human, not a contract. For example, if Bob sends Alice 1 ETH, Bob’s account must be debited and Alice’s must be credited. This state-changing action takes place within a transaction.

Solidity principle

What is the Input Data Format for Ethereum Transaction?

Ethereum’s transaction input data format refers to the information that is included in a transaction when interacting with the Ethereum blockchain. It can contain various parameters and instructions for executing smart contracts, transferring tokens, or any other operation supported by the Ethereum network.

ETH.Build

ethereum sign transaction

To sign a transaction in Ethereum, the originator must: Create a transaction data structure, containing nine fields: nonce, gasPrice, gasLimit, to, value, data, chainID, 0, 0. Produce an RLP-encoded serialized message of the transaction data structure. Compute the Keccak-256 hash of this serialized message.

Elliptic curve digitial signature algorithm

The Elliptic Curve Digital Signature Algorithm (ECDSA) is a Digital Signature Algorithm (DSA) which uses keys derived from elliptic curve cryptography (ECC). It is a particularly efficient equation based on public key cryptography (PKC).

text
{    messageHash: '0x6893a6ee8df79b0f5d64a180cd1ef35d030f3e296a5361cf04d02ce720d32ec5',    r:
'0x9ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9c',    s:
'0x440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428',    v: '0x25',
rawTransaction:
'0xf86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a009ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9ca0440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428'    transactionHash: '0xd8f64a42b57be0d565f385378db2f6bf324ce14a594afc05de90436e9ce01f60'}

Public key has been made by private key.

In each write on blockchain transaction private key is used.

‍‍‍Cryptographic hashing

What is a digest in cryptography?

A message digest is a fixed size numeric representation of the contents of a message, computed by a hash function. A message digest can be encrypted, forming a digital signature. Messages are inherently variable in size. A message digest is a fixed size numeric representation of the contents of a message.

The digest is the output of the hash function.

For example, sha256 has a digest of 256 bits, i.e. its digest has a length of 32 bytes.

SHA256

Ethereum keccak256

SHA-256 stems from the SHA-2 standard with a 256 bit key, while Keccak-256 is a function within Solidity and stems from the SHA-3 family. Keccak-256 is stronger than SHA-256. SHA-256 generates a SHA-256 hash, while Keccak-256 generates a Keccak-256 hash.

The hash function has different results if a single character has changed in the blockchain.

Update transaction in Ethereum

Increase volume of gas fee without change a nonce.

“Replacing” vs “Canceling” Transactions

  • Canceling: This is when you want to ‘undo’ your transaction. You don’t want it to go through, so you generate a 0 ETH transaction to your own address with the purpose of preventing a previous transaction from “going through” / “being mined” / being included in the blockchain.
  • Replacing: This is when you want your same transaction to go through faster, or you want to replace it with another transaction. You generate a XX ETH transaction to someone else’s address with the purpose of doing something (i.e. sending funds, revealing an ENS bid, etc.) while simultaneously not having a previous transaction go through.

https://help.myetherwallet.com/en/articles/5461454-canceling-or-replacing-a-transaction-after-it-s-been-sent

Pending transactions

Solidity principle

Send transaction to your address with the same nonce it will cancel it.

Solidity principle
Solidity principle
Solidity principle
Solidity principle
Solidity principle

Payable function solidity

In Solidity, a payable function is a function that can accept ether as an input. When a contract is called with a payable function, the caller can send ether along with the function call. The ether is then stored in the contract’s balance, which can be accessed using the “address(this). balance” property.

msg.value => value that send to smart contract

javascript
function updateString(string memory _newString) public payable {
  if(msg.value == 1 ether) {
    myString = _newString;
  }
  else {
    payable(msg.sender).transfer(msg.value);
  }
}

Fallback function

In the Solidity programming language, a fallback function is a special type of function that is automatically called whenever a contract receives a message that is not handled by any of the contract’s other functions. The fallback function is defined using the fallback keyword, and it does not take any arguments.

Solidity principle
solidity
receive() external payable {        lastValueSent = msg.value;        lastFunctionCalled =
"receive";    }

Smart contract Wallet

Send value to the smart contract wallet and withdraw in different account

solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.16;
contract SendWithdrawMoney {
  uint public balanceReceived;
  function deposit() public payable {
    balanceReceived += msg.value;
  }
  function getContractBalance() public view returns(uint) {
    return address(this).balance;
  }
  function withdrawAll() public {
    address payable to = payable(msg.sender);
    to.transfer(getContractBalance());
  }
  function withdrawToAddress(address payable to) public {
    to.transfer(getContractBalance());
  }
}

Mapping and struct in solidity

solidity
//SPDX-License-Identifier: MITpragma solidity ^0.8.14;
contract SimpleMappingExample {
  mapping(uint => bool) public myMapping;
  mapping(address => bool) public myAddressMapping;
  function setValue(uint _index) public {
    myMapping[_index] = true;
  }
  function setMyAddressToTrue() public {
    myAddressMapping[msg.sender] = true;
  }
}
solidity
mapping (uint => mapping(uint => bool)) uintUintBoolMapping;

Child Smart Contract

This method costs more than the Struct way.

solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.15;
contract PaymentReceived {
  address public from;
  uint public amount;
  constructor(address _from, uint _amount) {
    from = _from;
    amount = _amount;
  }
}
contract Wallet {
  PaymentReceived public payment;
  function payContract() public payable {
    payment = new PaymentReceived(msg.sender, msg.value);
  }
}
}

Assert

It is look like a require. It will show panic opcode.

The assert function should only be used to test for internal errors, and to check invariants. The require function should be used to ensure valid conditions, such as inputs, or contract state variables are met, or to validate return values from calls to external contracts.

If the condition evaluates to false, the contract will immediately stop executing and any state changes made so far will be rolled back. Unlike require and revert, assert is not used for checking user inputs or enforcing preconditions. In the above example, the assert checks if the integer has overflowed or not.

The bigger difference between the two keywords is that assert when the condition is false tends to consume all the remaining gas and reverts all the changes made. In reverse, require when the condition is false refund all the remaining gas fees we offered to pay beyond reverts all the changes.

Exactly for this last reason, require is recommended than assert.

https://stackoverflow.com/questions/71502322/difference-between-assert-and-require

solidity
assert(msg.value == uint8(msg.value));

Does solidity have a for loop?

Solidity supports for , while , and do while loops. Don’t write loops that are unbounded as this can hit the gas limit, causing your transaction to fail.

Yes, the EVM has a problem with unbounded loops. The gas consumed increases with each iteration until it hits the block gasLimit and the function can’t run at all. Consequently, unbounded iteration is an anti-pattern.

Consider inverting control so the clients call the contract one row at a time. The contract can expose helper functions to return the length of the list, the key (to a mapping) at a row in the list, and the details of a struct stored in a mapping for a given key.

There are some example implementations over here: Are there well-solved and simple storage patterns for Solidity?

The “Mapped Struct with Index” works as described above.

Hope it helps.

Try Catch

We need another contract to catch the error.

solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.14;
contract WillThrow {
  function aFunction() public pure {
    require(false, "Error message");
  }
}
contract ErrorHandling {
  event ErrorLogging(string reason);
  function catchError() public {
    WillThrow will = new WillThrow();
    try will.aFunction() {
      //here we could do something if it works
    }
    catch Error(string memory reason) {
      emit ErrorLogging(reason);
    }
  }
}

External Function Calls and Low-Level Calls In-Depth

In solidity transactions are atomic.

What is an ‘EOA’ account?

This is an Externally Owned Account, so your normal Ethereum address, not a wallet contract.

https://www.oreilly.com/library/view/mastering-blockchain-programming/9781839218262/00403615-d0ec-4c80-94a0-dc1d24fd3246.xhtml
https://www.oreilly.com/library/view/mastering-blockchain-programming/9781839218262/00403615-d0ec-4c80-94a0-dc1d24fd3246.xhtml

Input transaction data have contract payload data.

What is Transaction Output and Input in Blockchain Technology?

Inputs represent the source of funds being used in a transaction, while outputs indicate where these funds are going.

Transaction inputs and outputs refer to the transfer of digital assets between different stakeholders in a blockchain network, where inputs represent the sender’s address and outputs indicate the recipient’s address.

Wallet example

solidity
contract Consume{
  function getBalance() public view returns (uint) {
    return address(this).balance;
  }
  function deposit() payable public {
  }
}
Solidity principle

to => address of consumer deploy

payload => get from input after click on deposite

Web3.js in Remix

javascript
import { deploy } from './web3-lib'(async () => {
    let accounts = await web3.eth.getAccounts();
    console.log("Accounts: ", accounts);
    console.log(accounts.length)
    let balance = await web3.eth.getBalance(accounts[0])
    console.log(balance)
    console.log(web3.utils.fromwei(balance , "eth"))})();
text
Accounts:
["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"] 15100000000000000000000

ABI

solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.14;
contract MyContract {
  uint public myUint = 123;
  function setMyUint(uint newUint) public {
    myUint = newUint;
  }
}
Solidity principle
Solidity principle
Solidity principle
javascript
import { deploy } from './web3-lib'(async() => {
    const address = "0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8";
    const abi = [ {  "inputs": [],  "name": "myUint",  "outputs": [   {
    "internalType": "uint256",
    "name": "",
    "type": "uint256"   }  ],  "stateMutability": "view",  "type": "function" }, {  "inputs": [   {
    "internalType": "uint256",
    "name": "newUint",
    "type": "uint256"   }  ],  "name": "setMyUint",  "outputs": [],  "stateMutability":
    "nonpayable",  "type": "function" }];
    let contractInstance = new web3.eth.Contract(abi, address);
    console.log(await contractInstance.methods.myUint().call());
    let accounts = await web3.eth.getAccounts();
    await contractInstance.methods.setMyUint(345).send({from: accounts[0]});
    console.log(await contractInstance.methods.myUint().call());})()

Event

When write on blockchain , returns value didn’t work on real network and it only use when another contract call this function.

solidity
event TokensSent(address _from, address _to, uint _amount);
solidity
emit TokensSent(msg.sender, _to, _amount);
Solidity principle
solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.16;
contract EventExample {
  mapping(address => uint) public tokenBalance;
  event TokensSent(address _from, address _to, uint _amount);
  constructor() {
    tokenBalance[msg.sender] = 100;
  }
  function sendToken(address _to, uint _amount) public returns(bool) {
    require(tokenBalance[msg.sender] >= _amount, "Not enough tokens");
    tokenBalance[msg.sender] -= _amount;
    tokenBalance[_to] += _amount;
    emit TokensSent(msg.sender, _to, _amount);
    return true;
  }
  receive() external payable {
  }
}

Inheritance and Modifiers

‍‍‍

solidity
//SPDX-License-Identifier: MITpragma solidity ^0.8.16;
contract InheritanceModifierExample {
  mapping(address => uint) public tokenBalance;
  address owner;
  uint tokenPrice = 1 ether;
  constructor() {
    owner = msg.sender;
    tokenBalance[owner] = 100;
  }
  function createNewToken() public {
    require(msg.sender == owner, "You are not allowed");
    tokenBalance[owner]++;
  }
  function burnToken() public {
    require(msg.sender == owner, "You are not allowed");
    tokenBalance[owner]--;
  }
  function purchaseToken() public payable {
    require((tokenBalance[owner] * tokenPrice) / msg.value > 0, "not enough tokens");
    tokenBalance[owner] -= msg.value / tokenPrice;
    tokenBalance[msg.sender] += msg.value / tokenPrice;
  }
  function sendToken(address _to, uint _amount) public {
    require(tokenBalance[msg.sender] >= _amount, "Not enough tokens");
    tokenBalance[msg.sender] -= _amount;
    tokenBalance[_to] += _amount;
  }
}

Adding A Simple Solidity Modifier

solidity
modifier onlyOwner() {        require(msg.sender == owner, "You are not allowed");        _;    }

Inhertiance

solidity
contract Owned {    address owner;    constructor() {        owner = msg.sender;    }    modifier
onlyOwner() {        require(msg.sender == owner, "You are not allowed");        _;    }}
solidity
contract InheritanceModifierExample is Owned {

Importing Solidity Contracts

text
Did you know? In Remix you can also directly import contracts from github repositories
go
import "./Ownable.sol";

Destroying Smart Contracts using selfdestruct

The selfdestruct function takes one argument, an address. It will burn your eth.

javascript
function destroySmartContract() public {
  selfdestruct(payable(msg.sender));
}
solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.16;
contract StartStopUpdateExample {
  receive() external payable {
  }
  function destroySmartContract() public {
    selfdestruct(payable(msg.sender));
  }
  function Iamhere() public pure returns (string memory) {
    return "I am here";
  }
}
Solidity principle

https://www.youtube.com/watch?v=IMSmEAYGTp4

How to Verify A Smart Contract manually on Etherscan

solidity
//SPDX-License-Identifier: MITpragma solidity 0.8.16;
contract MyContract {
  mapping(address => uint) public balance;
  constructor() {
    balance[msg.sender] = 100;
  }
  function transfer(address to, uint amount) public {
    balance[msg.sender] -= amount;
    balance[to] += amount;
  }
  function someCrypticFunctionName(address _addr) public view returns(uint) {
    return balance[_addr];
  }
}

Sometimes Etherscan infers a function name. That is, for interfaces from known contracts, like the ERC20 contract. If you interact with this smart contract and use the transfer function, Etherscan would probably show you correctly that the function “transfer” was used. But if you use the function someCrypticFunctionName it would just show you the 4 byte hashed function signature.

Solidity principle
Solidity principle
Solidity principle

https://goerli.etherscan.io/address/0x2efa0b77f4850797f91e5ec1e30b0eec59fa7863#readContract

Solidity principle
Solidity principle

Ethereum Request for Comment ERC

ERC20 exist on etherium. token has been made by smart contracts.

ERC20 is a guide line for build your own token.

https://www.youtube.com/watch?v=cqZhNzZoMh8

list of ERC20 tokens

https://bloxy.info/list_tokens/ERC20

ERC-223 token is the expansion of the ERC-20 protocol. It was developed by an Ethereum community member known by the Reddit username “Dexaran” to fix a bug in ERC-20 tokens. ERC-223 tokens are powered by smart contracts. They allow users to transfer their tokens into digital wallets securely.

What’s a fungible token? That is, where each token doesn’t have any sort of unique serial number and they are all worth equally much. Like Euro or Dollar coins. You take out the coins in your pocket and 50 cents are 50 cents, doesn’t matter if the coin is old and used or new and shiny.

ERC20 Basic contract

solidity
// SPDX-License-Identifier: MITpragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
  constructor() ERC20("MyToken", "MYT") {
  }
}

There are two large groups of implementations for tokens:

  1. Fixed Supply Tokens
  2. Variable Supply Tokens

The difference is mainly in how the mint functionality is used. If you use OpenZeppelin smart contracts, then with fixed supply, the mint function is callable only in the constructor once. That means, once the token is deployed, there is no more access for the internal mint functionality, the supply of tokens remains fixed.

The Variable Supply Tokens implement some sort of functionality, so that its possible to mint more tokens after the contract is deployed.

You can define roles, such as a MINTER_ROLE or a BURNER_ROLE, which is basically just a keccak256 hash of the role name

typescript
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How

solidity
// SPDX-License-Identifier: MITpragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract MyToken is ERC20, AccessControl {
  bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
  bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
  constructor() ERC20("MyToken", "MYT") {
    _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    _grantRole(MINTER_ROLE, msg.sender);
    _grantRole(BURNER_ROLE, msg.sender);
  }
  function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
    _mint(to, amount);
  }
  function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) {
    _burn(from, amount);
  }
}
Solidity principle

Let’s use a sample contract from OpenZeppelin to deploy a token. This token could represent anything — for example a voucher for coffees.

  1. We can give users tokens for coffees
  2. The user can spend the coffee token in his own name, or give it to someone else
  3. Coffee tokens get burned when the user gets his coffee.
solidity
// SPDX-License-Identifier: MITpragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract CoffeeToken is ERC20, AccessControl {
  bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
  event CoffeePurchased(address indexed receiver, address indexed buyer);
  constructor() ERC20("CoffeeToken", "CFE") {
    _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    _grantRole(MINTER_ROLE, msg.sender);
  }
  function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
    _mint(to, amount);
  }
  function buyOneCoffee() public {
    _burn(_msgSender(), 1);
    emit CoffeePurchased(_msgSender(), _msgSender());
  }
  function buyOneCoffeeFrom(address account) public {
    _spendAllowance(account, _msgSender(), 1);
    _burn(account, 1);
    emit CoffeePurchased(_msgSender(), account);
  }
}
Solidity principle

ERC721

This contract has a token id for each token.

https://opensea.io/

https://opensea.io/assets/ethereum/0x5c1a0cc6dadf4d0fb31425461df35ba80fcbc110/3948

Solidity principle

https://mint.pixels.online/contracts/0x5c1a0cc6dadf4d0fb31425461df35ba80fcbc110/3948

https://hang.mypinata.cloud/ipfs/QmRJSjDZqQu5yoUbBtMXhLp4T9czmpUmfpBg6ntLzbjMUV/3948.png

https://boomland.io/

https://opensea.io/assets/matic/0x20b807b9af56977ef475c089a0e7977540743560/5208

https://hunt-nft.cdn.boombit.cloud/Recordings/024/024003020003.webm

Make NFT

Create wizard ERC721

solidity
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract Mobin is ERC721, ERC721URIStorage, Ownable {
  uint256 private _nextTokenId;
  constructor(address initialOwner) ERC721("mobin", "MOB") Ownable(initialOwner) {
  }
  function _baseURI() internal pure override returns (string memory) {
    return "https://mobinshaterian.medium.com";
  }
  function safeMint(address to, string memory uri) public onlyOwner {
    uint256 tokenId = _nextTokenId++;
    _safeMint(to, tokenId);
    _setTokenURI(tokenId, uri);
  }
  // The following
  functions are overrides required by Solidity.function tokenURI(uint256 tokenId) public view
  override(ERC721, ERC721URIStorage) returns (string memory) {
    return super.tokenURI(tokenId);
  }
  function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage)
  returns (bool) {
    return super.supportsInterface(interfaceId);
  }
}