Dapps
February 5, 2023

dApps | кнопка mint на сайте

Привет сегодня мы поговорим как сделать кнопку mint или просто списать деньги за покупку чего то на своем сайте через смарт-контракт.

Начну с небольших изменений к прошлой статье. Если не интересно, то можете скипнуть.

Я предлагаю создать компонент для кнопки check address, для более красивой записи и компактности кода.

export function CheckAddress({ funcAddress, checkAddr }) {  
  return (    
       <div>        
            <button onClick={funcAddress}> check address</button>        
            <p>{checkAddr}</p>      
        </div>  
  )  
}

Это наш компонент. И напомню, что в функцию CheckAddress мы передаем переменные props, то есть за переменную funcAddress будет отвечать наша функция, а за checkAddr будет отвечать наша глобальная переменная this.state.checkAddress.

Напомню, что компонент CheckAddress добавляем в нашу папку с компонентами.

И не забываем его импортировать в наш index.js

import { CheckAddress } from '../components/CheckAddress'

Добавляем эту строчку в начало index.js ко всем импортам.

Теперь этот компонент нужно объявить в нашей render функции. Удаляем старый код с кнопкой и пишем новый.

render() {  
  if(!this.state.selectedAccount) {           
      return <>             
          <div>       
               <ConnectWallet  connectWallet={this._connectWallet}
                networkError={this.state.networkError}
                 dismiss={this._dismissNetworkError}/>      
         </div >                           
      </>    
  }   
  return(           
      <>   
         {this.state.balance &&    
         <p className={styles.balance}>
         balance: 
         {ethers.utils.formatEther(this.state.balance).slice(0,10)}
          ETH</p>}         
         <div>    
            <CheckAddress funcAddress={this._checkAddress}
             checkAddr = {this.state.checkAddress}/>    
         </div>
     </>
  )                        
}

Вот так выглядит теперь функция render. Отдельное внимание уделим строчке:

<CheckAddress funcAddress={this._checkAddress}
 checkAddr = {this.state.checkAddress}/> 

Это и есть наш новый компонент, где funcAddress отвечает за функцию, а checkAddr за переменную, как я и говорил ранее.

Так же про компоненты: пишите с одинаковой буквы (заглавной или строчной) название файла и название функции, а то будет ругаться.

Так ну это было небольшое дополнение. Теперь к теме.

Изменение смарт контракта

Для начало добавим небольшие изменения в смарт-контракт.

// SPDX-License-Identifier: MIT 
pragma solidity ^0.8.9;    
contract Lock {  
   address[] public owner;     
   mapping (address => uint) public amount;    
   constructor() {      
       owner.push(msg.sender);     
   }     
   modifier onlyOwner() {    
       uint checkOwner = 0;        
       for(uint i = 0; i <= owner.length; i++){         
          if(owner[i] == msg.sender){             
             checkOwner = i;                
             break;            
          }        
       }        
       require(owner[checkOwner] == msg.sender, "not an owner!");       
       _;    
   }     
   function getAddress() public view onlyOwner returns(address){         
       return msg.sender;     
   }     
   function mint() public payable{        
       require(msg.value >= 2000, "not anouth funds");        
       amount[msg.sender] += 1;    
   }    
   function makeOwner() public{     
      require(amount[msg.sender] >= 3, "error");        
      owner.push(msg.sender);    
   }
   
}

Массив из owner. Теперь у нас owner будет не один а массив из овнеров. То есть можно будет добавлять овнеров по мере выполнения каких-либо условий.

mapping который будет считать сколько раз адрес вызовет функцию mint и соответственно успешно отправлять деньги на наш смарт-контракт.

Изменение в constructor. Теперь в конструкторе у нас будет не owner = msg.sender, а owner.push(msg.sender), потому что теперь массив из овнеров.

Модификатор onlyOwner конечно же тоже нужно поменять.
Так как нам нужно понять, есть ли тот или иной адрес в массиве овнеорв, нужно будет бежать по каждому из элементов этого массива и проверять на совпадение. Если такой адрес нашелся равный msg.sender, то все ок если нет, то require, откатит транзакции.

Новая функция mint. Она как раз отвечает за перевод средств с аккаунта на адрес смарт контракта. Чтоб это сделать просто помечаем функцию как payable, ну добавляем условия минимального перевода для выполнения функции и после добавляем в mapping +1 успешный mint.

Функция makeOwner, будет делать наш адрес овнеров контракта, при условии, что мы сделали 3 минта или больше. То есть пушим наш адрес в массив овнеров.

Теперь можно запустить ноду и деплой смарт-контракта. Как это делать показывал в прошлых статьях про кнопку на сайте или подключение смарт-контракта к сайту. Если вы забыли или не знаете.

Новые функции в index.js фронтенд

Функция _mint

_mint = async() =>{     
   const tx = await this.Lock.mint({        
       value: "5000"     
   })      
   await tx.wait()      
   let addrBought = Number(await this.Lock.amount(this.state.selectedAccount))      
   this.setState({        
       boughtPass: addrBought     
    })  
    await this.updateBalance()
}

Как и в других функциях, мы вызываем транзакцию при помощи this.Lock.mint и

передаем туда в качестве аргумента value: 5000, что будет обозначать 5000 wei.

И соответственно передает эти деньги в смарт-контракт.

await tx.wait() - очень важная функция, которая дожидается, пока транзакция выполнится. И потом мы просто обращаемся к нашему mapping, чтоб вывести количество минтов нашего адреса.

в this.setState() мы передаем boughtPass - глобальная переменная, которую добавляем в конструктор (весь код конструктора ниже) и передаем ей amount - количество минтов.

Так же обязательно оборачиваем в Number наш вызов mapping, так как он будет выводиться как bigNumber и будет ошибка, если так не сделать.

Вызов updateBalance(). Чтоб наш баланс кошелка обновился на сайте. Нужно вызвать эту функции, которую мы уже написали в прошлых статьях.

Функция _makeOwner

_makeOwner = async() =>{  
  try{   
     let tx = await this.Lock.makeOwner()      
     this.setState({    
         messageError: null      
     }) 
     await this.updateBalance()   
  }catch{    
      this.setState({        
          messageError: "Now you can't stay owner"      
      })    
  }       
}

Тут мы вызываем транзакцию на функцию makeOwner, и если транзакция проходит, то мы обнуляем messageError. И вызываем снова updateBalance()

messageError - новая глобальная переменная в конструкторе. Добавляем ее

constructor(props) {    
    super(props)          
    this.initialState = {    
        selectedAccount: null,        
        networkError: null,        
        balance: null,        
        checkAddress: null,        
        boughtPass: null,        
        messageError: null      
    }     
      this.state = this.initialState          
}

Если же не прошла транзакция, то мы выводим на экран сообщение об ошибке.(То, что в catch)

Новые компоненты

Создаем два новых компонента. Mint.js и MakeOwner.js

Теперь код Mint.js

export function Mint({mintFunc, passId}) {  
    return (        
       <div>         
           <button onClick={mintFunc}>mint</button>        
           <p>{passId}</p>        
       </div>    
    )  
}

Тут все также, как и в начале статьи с компонентом checkAddress.

Если вкратце, то переменные mintFunc, passId - наши props, то есть в ниx передаем функцию _mint и this.state.boughtPass

Наш return в функции render()

return(          
  <>      
      {this.state.balance &&         
      <p className={styles.balance}>
      balance: {ethers.utils.formatEther(this.state.balance).slice(0,10)}
      ETH</p>}
      <div>       
       <CheckAddress 
       funcAddress={this._checkAddress} 
       checkAddr = {this.state.checkAddress}/>      
      </div>       
      <div>          
        <Mint 
        mintFunc= {this._mint} 
        passId = {this.state.boughtPass}/>        
      </div>             
  </> 
)                       

Тут мы должны обратить внимание на

 <Mint mintFunc= {this._mint} passId = {this.state.boughtPass}/> 

Как я и говорил, тут две переменные, одна отвечает за _mint(), другая за this.state.boughtPass.

Так же это делать не обязательно, если вам не нравится работать с компонентами. Просто все что в компоненте переходит в index.js в return.

И не забываем импортировать в начало index.js наш компонент

import { Mint } from '../components/Mint'

Теперь код MakeOwner.js

export function MakeOwner({ funcMakeOwn, ErrMsg }) {   
 return (        
  <div>         
     <button onClick={funcMakeOwn}> make owner </button>      
     <p> {ErrMsg}</p>     
  </div>    
  )  
}

Тут ровно так же как и в mint.js по логике.

Наш return теперь:

return(          
  <>      
      {this.state.balance &&         
      <p className={styles.balance}>
      balance: {ethers.utils.formatEther(this.state.balance).slice(0,10)}
      ETH</p>}
      <div>       
       <CheckAddress 
       funcAddress={this._checkAddress} 
       checkAddr = {this.state.checkAddress}/>      
      </div>       
      <div>          
        <Mint 
        mintFunc= {this._mint} 
        passId = {this.state.boughtPass}/>        
      </div>
      <div>         
       <MakeOwner 
       funcMakeOwn={this._makeOwner} 
       ErrMsg = {this.state.messageError} />       
      </div>             
  </> 
)  

Добавили так же как и в mint.js но для MakeOwner.js

<MakeOwner funcMakeOwn={this._makeOwner} ErrMsg={this.state.messageError} /> 

И не забываем про import.

import { MakeOwner } from '../components/MakeOwner'

По факту все готово. По хорошему добавить в наш смарт-контракт функцию, которая будет забирать все деньги из контракта на наш адрес . Но это можете придумать сами.

Смотрим результат

Подключаем кошелек.

Вызываем check Address и make owner

Как видно мы не овнеры контракта и не можем им стать пока что.

Теперь вызываем mint 3 раза, чтоб получить доступ к make owner.

Теперь мы вызвали check address и получили адрес аккаунта. Значит мы стали овнером контракта.

Если вы сделали все так же как я, то у вас тоже должно работать. Дальше больше. В целом с этими знаниями уже можно придумать кучу всяких идей и реализовать их. Удачи.

tg: мой телеграмчик)

github:
этот проект на гитхабе
все файлы из папки front в отдельном репозитории
репозиторий попки front