Somnia 백서

Somnia

원문

bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 54쪽 중 22쪽

andards and best practices for gas optimization > Whether you’re launching a governance token, building an NFT marketplace, or gamifying user engagement, Somnia’s speed and scalability make asset creation seamless. # Create ERC20 Tokens The Somnia mission is to enable the development of mass-consumer real-time applications. To achieve this as a developer, you will need to build applications that are Token enabled, as this is a requirement for many Blockchain applications. This guide will teach you how to connect to and deploy your ERC20 Smart Contract to the Somia Network using the [Remix IDE](https://remix.ethereum.org/). {% hint style="success" %} Somnia Mainnet is LIVE. To deploy on Somnia Mainnet, you will need SOMI Tokens. Please refer to the [guide](/get-started/getting-started-for-mainnet) on Moving from Testnet to Mainnet. {% endhint %} {% embed url="<https://www.youtube.com/watch?v=EovEiYJNKTM>" %} ## Pre-requisite 1. This guide is not an introduction to Solidity Programming; you are expected to understand Basic Solidity Programming. 2. To complete this guide, you will need MetaMask installed and the Somnia Network added to the list of Networks. If you have yet to install MetaMask, please follow this guide to [Connect Your Wallet](/get-started/connect-your-wallet-to-mainnet). Somnia Network is an EVM-compatible Layer 1 Blockchain. This means that critical implementation on Ethereum is available on Somnia, with higher throughput and faster finality. Smart Contract that follows the [ERC-20 standard](https://eips.ethereum.org/EIPS/eip-20) is an ERC-20 token; these Smart Contracts are often referred to as Token Contracts. ERC-20 tokens provide functionality to * Transfer tokens * Allow others to transfer tokens on behalf of the token holder It is important to note that ERC20 Smart Contracts are different from the Native Somnia Contracts, which are used to pay gas fees when transacting on Somnia. In the following steps, we will create an ERC20 Token by following the EIP Standard and also demonstrate the option to use a Library to create the ERC20 Token. ## IERC-20 According to the EIP Standard, certain Smart Contract methods must be implemented adhering to the standard so that other Smart Contracts can interact with the deployed Smart Contracts and call the method. To achieve this, we will use the Solidity Interface Type to create the Smart Contract standard. \ In Solidity, an interface is a special contract that defines a set of function signatures without implementation. It acts as a "blueprint" for other contracts, ensuring they adhere to a specific structure. Interfaces are crucial for creating standards, such as the ERC-20 token standards, allowing different contracts to interact seamlessly within the EVM ecosystem. Create an Interface for the ERC20 Token. Copy and paste the code below into a file named `IERC20.sol` ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); } ``` ## ERC-20 Token Contract With the Interface created and all the Token standard methods implemented, the next step is to import the Interface into the ERC20 Smart Contract implementation. Create a file called `ERC20.sol` and paste the code below into it. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "./IERC20.sol"; contract ERC20 is IERC20 { event Transfer(address indexed from, address indexed to, uint256 value); event Approval( address indexed owner, address indexed spender, uint256 value ); uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; string public name; string public symbol; uint8 public decimals; constructor(string memory _name, string memory _symbol, uint8 _decimals) { name = _name; symbol = _symbol; decimals = _decimals; } function transfer(address recipient, uint256 amount) external returns (bool) { balanceOf[msg.sender] -= amount; balanceOf[recipient] += amount; emit Transfer(msg.sender, recipient, amount); return true; } function approve(address spender, uint256 amount) external returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) { allowance[sender][msg.sender] -= amount; balanceOf[sender] -= amount; balanceOf[recipient] += amount; emit Transfer(sender, recipient, amount); return true; } function _mint(address to, uint256 amount) internal { balanceOf[to] += amount; totalSupply += amount; emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal { balanceOf[from] -= amount; totalSupply -= amount; emit Transfer(from, address(0), amount); } function mint(address to, uint256 amount) external { _mint(to, amount); } function burn(address from, uint256 amount) external { _burn(from, amount); } } ``` We have implemented the various requirements for the ERC20 Token Standard: **`constructor`**: Initializes the token's basic properties. Accepts the token's `name`, `symbol`, and the number of `decimals` as parameters and sets these values as the token's immutable metadata. Example: `ERC20("MyToken", "MTK", 18)` initializes a token named `MyToken` with the symbol `MTK` and 18 decimal places (the standard for ERC-20). ### Functions **`transfer`**: Moves a specified `amount` of tokens from the *sender* to a *recipient*. It checks if the sender has enough balance and deducts the amount from the sender's balance, and adds it to the recipient's. It also **emits a Transfer event** to log the transaction and returns \`**true**\` if the transfer is successful. **`approve`**: It allows a *spender* to spend up to a certain amount of tokens on behalf of the *owner*. It sets the `allowance` for the *spender* to the specified `amount`. **The spender is usually another Smart Contract**. It then **emits an Approval event** to record the spender's `allowance`. It returns **true** if the `approval` is successful. A common use case is to enable spending through third-party contracts like decentralized exchanges (DEXs). **`transferFrom`**: It allows a *spender* to transfer tokens on behalf of another account. It checks if the *sender* has an approved `allowance` for the spender and ensures it is sufficient for the *amount* and deducts the amount from the `allowance` and the sender's *balance* and adds the *amount* to the recipient's balance. It also **emits a Transfer event** to log the transaction. It returns \`**true**\` if the transfer is successful. A common use case is DEXs or smart contracts to handle token transactions on behalf of users. **`_mint`**: Creates new tokens and adds them to a specified account's *balance*. Calling the method increases the recipient's **`balanceOf`** by the specified amount. It also increases the **`totalSupply`** of the ERC20 Tokens by the same amount. A transfer event with the from address as address(0) (indicating tokens are created). **`_burn`**: Destroys a specified number of *tokens* from an account's *balance*. It reduces the account's **`balanceOf`** and decreases the **`totalSupply`** by the amount. It emits a Transfer event with the to address as address(0) (indicating tokens are burned). It is typically implemented in token-burning mechanisms to reduce supply, increasing scarcity. **`mint`**: Public wrapper for **`_mint`**. It calls **`_mint`** to create new tokens for a specified to address. Allows the contract owner or authorized accounts to mint new tokens. **`burn`**: Public wrapper for **`_burn`**. Calls \_burn to destroy tokens from a specified from address. ### Events **`Transfer`**: Logs token transfers, including minting and burning events. Parameters: from (sender), to (recipient), value (amount). **`Approval`**: Logs approvals of allowances for spenders. Parameters: owner, spender, value (allowance amount). ## Compile Smart Contract The ERC20 Token is now ready to be deployed to the Somnia Blockchain. On the left tab, click the “Solidity Compiler” menu item and then the “ Compile ERC20.sol” button. This will compile the Solidity file and convert the Solidity code into machine-readable bytecode. <figure><img src="https://2122549367-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkYErT9t3BJtpPfejLO6I%2Fuploads%2FySYqHAeTuffcUc6GyICA%2FERC20-1.png?alt=media&amp;token=cafc024f-0b65-4a57-ad4b-a572e0f101f7" alt=""><figcaption></figcaption></figure> ## Deploy Smart Contract The Smart Contract has been created and compiled into ByteCode, and the ABI has also been created. The next step is to deploy the Smart Contract to the Somnia DevNet so that you can perform READ and WRITE operations. On the left tab, click the “Deploy and run transactions” menu item. To deploy the Smart Contract, we will require a wallet connection. In the Environment dropdown, select the option: “Injected Provider - MetaMask”. Then select the MetaMask account where you have STT Tokens. In the “DEPLOY” field, enter the property values for the ERC20 Token: * “\_NAME” - type string * “\_SYMBOL” - type string * “\_DECIMALS” - type uint8 Click Deploy. When prompted, approve the Contract deployment on your MetaMask.<br> <figure><img src="https://2122549367-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkYErT9t3BJtpPfejLO6I%2Fuploads%2F7Bnmh0xRi9fTFDuYwZ1u%2FERC20-2.png?alt=media&amp;token=68d16050-9d93-4da7-9581-6a9d5943c762" alt=""><figcaption></figcaption></figure> Look at the terminal for the response and the deployed Smart Contract address. You can interact with the Smart Contract via the Remix IDE. Send a transaction to change the name. <figure><img src="https://2122549367-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkYErT9t3BJtpPfejLO6I%2Fuploads%2FY6AD65aY4S9Sjhm1jMZX%2FERC20-3.png?alt=media&amp;token=056082b4-b772-4da9-b855-f07cf6c60a2b" alt=""><figcaption></figcaption></figure> Congratulations. 🎉 You have deployed an ERC20 Smart Contract to the Somnia Network. 🎉 *** ### OpenZeppelin As was mentioned at the beginning of this Tutorial, there is the option to use a Library to create the ERC20 Token. The OpenZeppelin Smart Contract Library can be used to create an ERC20 Token, and developers can rely on the Smart Contracts wizard to specify particular properties for the created Token. See an example below: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "@openzeppelin/contracts@4.0.0/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts@4.0.0/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/contracts@4.0.0/access/Ownable.sol"; contract MyToken is ERC20, ERC20Burnable, Ownable { constructor(address initialOwner) ERC20("MyToken", "MTK") Ownable() {} function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); } } ``` The process for deploying this Smart Contract implementation built with OpenZeppelin is the same as Steps 3 and 4 above. <br> # Create ERC721 NFT Collections ERC721 is the EVM compatible standard for Non Fungible Tokens, NFTs.\ \ NFTS are digital assets where each token is unique. Unlike ERC20 (fungible) tokens that are interchangeable, ERC721 tokens represent distinct items such as game assets, collectibles, tickets, certificates, or onchain identities. Somnia, being EVM-compatible, supports the ERC721 standard natively. ERC721 has the following functionalities: * Every token has a distinct `tokenId` and (optionally) distinct metadata, making it unique. * Wallets can own, transfer, and approve NFTs using a common interface. * NFTs are composable; therefore, Wallets, marketplaces, and dApps understand ERC-721 uniformly, enabling easy listing, trading, and display. * ERC721 has a clean base that can be extended with metadata, enumeration, royalties (ERC-2981), permit (EIP-4494), etc.<br> This guide will teach you how to connect to and deploy your ERC20 Smart Contract to the Somia Network using Hardhat. ## Pre-requisite * This guide is not an introduction to Solidity Programming; you are expected to have a basic understanding of Solidity Programming. * To complete this guide, you will need MetaMask installed and the Somnia Network added to the list of Networks. If you have yet to install MetaMask, please follow this guide to [Connect Your Wallet](/get-started/connect-your-wallet-to-mainnet).<br> The Smart Contract is minimal, production-friendly ERC-721 without royalties (no ERC-2981). Per-token tokenURI set during mint (works great with IPFS). It also demonstrates how to deploy, mint, and verify on Somnia networks. ## ERC721 Smart Contract <details> <summary><code>NFTTest.sol</code></summary> ```solidity // SPDX-License-Identifier: MIT // Compatible with OpenZeppelin Contracts ^5.4.0 pragma solidity ^0.8.27; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract NFTTest is ERC721, ERC721URIStorage, Ownable { uint256 private _nextTokenId; constructor(address initialOwner) ERC721("NFTTest", "NFTT") Ownable(initialOwner) {} function _baseURI() internal pure override returns (string memory) { return "https://ipfs.io"; } function safeMint(address to, string memory uri) public onlyOwner returns (uint256) { uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); _setTokenURI(tokenId, uri); return tokenId; } 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); } } ``` </details> ## Code Breakdown Below is a breakdown explanation of the code: ### Imports ```solidity import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; ``` `ERC721` is the Core NFT standard that has all the methods that control `ownership`, `transfers`, `approvals`, and `metadata` hook. `ERC721URIStorage` adds per-token URI storage via `_setTokenURI` and overrides `tokenURI`. ERC721URIStorage extends ERC721 to store URIs per token (costs storage gas, but very flexible). `Ownable` is a simple access control library that enables the Smart Contract owner to set one account as the “owner”.<br> ### Contract Declaration ```solidity contract NFTTest is ERC721, ERC721URIStorage, Ownable { uint256 private _nextTokenId; ... } ``` `_nextTokenId` is an Internal counter for new token IDs. Starts at `0` by default (since not set), so your first mint is `tokenId = 0`. ### Constructor ``` constructor(address initialOwner) ERC721("NFTTest", "NFTT") Ownable(initialOwner) {} ``` Calls `ERC721` constructor with collection name "NFTTest" and symbol "NFTT". Initializes Ownable with `initialOwner` the address allowed to mint tokens. In this instance, the body is empty because all setup is done via parent constructors. This ERC-721 contract follows a straightforward ownership and metadata model designed for simplicity and marketplace compatibility. The contract owner is the sole minter, and each mint assigns the next sequential identifier—beginning at 0—to the specified recipient. This ensures that token IDs remain predictable (0, 1, 2, …) and facilitates aligning your metadata files with minted tokens. ### Base URI ```solidity function _baseURI() internal pure override returns (string memory) { return "https://ipfs.io"; } ``` Returns a prefix used by `ERC721.tokenURI` when a token’s stored URI is relative (e.g., "/ipfs/\<CID>/1.json"). In this contract you set full URIs at mint (commonly ipfs\://...), which are returned as-is by `ERC721URIStorage`. So this Base URI only matters if you mint with relative paths. An example Base URI when using IPFS is then `https://ipfs.io/` ### Minting ```solidity function safeMint(address to, string memory uri) public onlyOwner returns (uint256) { uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); _setTokenURI(tokenId, uri); return tokenId; } ``` In this contract, minting is restricted by `onlyOwner`, ensuring that only the contract owner can create new tokens. Token IDs are assigned sequentially using `_nextTokenId++`, starting from 0 and incrementing by one with each mint. The `_safeMint` function adds an extra layer of safety by checking if the recipient is a contract and, if so, requiring it to implement `IERC721Receiver` to prevent tokens from being locked. For metadata, `_setTokenURI` stores the exact URI string for each token, such as `ipfs://<CID>/1.json`, making it easy to reference unique files. The function also emits and returns the new `tokenId` upon minting. However, it’s worth noting that storing a full string per token consumes more gas; for larger drops with sequential files, a cheaper alternative is to use a base URI combined with the token ID rather than storing individual URIs. ### Required overrides ```solidity function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } ``` Because both ERC721 and ERC721URIStorage define tokenURI, Solidity requires you to pick which implementation to use. `super.tokenURI(tokenId)` resolves to ERC721URIStorage’s version, which: * Returns the stored URI if present. * Otherwise falls back to ERC721’s baseURI and tokenId behavior. ```solidity function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) { return super.supportsInterface(interfaceId); } ``` Also resolves multiple inheritance for supportsInterface (ERC165) and ensures the contract correctly reports ERC721 and metadata support. Transfers and approvals behave exactly as the ERC721 standard specifies. Use `safeTransferFrom` by default, so the contract checks that recipients can handle NFTs, which prevents tokens from being sent to incompatible contracts. `transferFrom` is available when you are certain the recipient can accept NFTs without the receiver check, and approvals can be granted either per token with approve or globally with `setApprovalForAll`. Interface support is advertised through supportsInterface, allowing wallets, marketplaces, and indexers to recognize ERC721 core and metadata compatibility automatically. A few best practices will keep deployments robust. Favor the `ipfs://` scheme for `metadata` and media to avoid locking yourself to a single HTTP gateway. If you expect very large drops, remember that `ERC721URIStorage` stores a full string per token, which is convenient but more expensive at scale; collections with sequential filenames can reduce costs by adopting a `baseURI` and `tokenId` pattern instead of per token URI storage. ## Initialize Hardhat ```bash mkdir somnia-nft && cd somnia-nft npm init -y npm install --save-dev hardhat typescript ts-node @types/node npx hardhat npm install @openzeppelin/contracts npm install --save-dev @nomicfoundation/hardhat-verify @nomicfoundation/hardhat
프로젝트로 돌아가기