- portfel na kryptowaluty (np. Metamask),
- Remix (IDE do języka Solidty - LINK)
- npm (tworzenie projektu React, instalacja potrzebnych pakietów)
Metamask można zainstalować pod dwiema postaciami, jako rozszerzenie do przeglądarek opartych na Chromium (Chrome, Opera) lub razem z przeglądarką Brave Browser. Obie opcje dostępne są pod adresem https://metamask.io. Po założeniu konta ekran główny aplikacji powinien wyglądać tak jak na poniższym zrzucie ekranu (lub podobnie).
Jeżeli w lewym górnym rogu pojawiła się inna sieć należy ją zmienić na Rinkeby Test Net poprzez kliknięcie lewym przyciskiem myszy i wybranie odpowiedniej opcji z listy.
W celu uruchomienia naszego kontraktu na sieci Rinkeby będziemy potrzebowali Etheru, którym zapłacimy za deploy kontraktu. Darmowy Ether możemy uzyskać pod adresem https://faucet.rinkeby.io. Strona powinna wyglądać tak jak poniżej.
W celu uzyskania Etheru konieczne jest (w celach walidacyjnych) napisanie posta, w dowolnych mediach społecznościowych, który będzie zawierał adres naszego portfela. Sam adres możemy uzyskać z aplikacji Metamask poprzez kliknięcie trzech kropek przy nazwie konta i wybieraniu opcji Copy address to clipboard.
Adres publikujemy na dowolnym serwisie społecznościowym (np. Twitter) w poniższy sposób.
Następnie wybieramy opcję Copy link to Tweet i otrzymany adres wklejamy na stronie https://faucet.rinkeby.io jednocześnie wybierając dowolną z opcji po prawej stronie.
Po wybraniu jednej z opcji środki powinny zostać przelane na odpowiednie konto.
Każda operacja na blockchainie wykonywana jest poprzez wykorzystanie tzw. kontraktu, który definiuje wszystkie wykonywane akcje. Poniżej znajduje się kod pięciu kontraktów. Cztery z nich to kontrakty pomocnicze, natomiast ostatni jest kontraktem głównym, który zawiera wszelkie informacje o naszym tokenie.
Podstawowym celem tego kontraktu jest zapewnienie bezpiecznych operacji dodawania, odejmowania, mnożenia i dzielenia liczb z wykluczeniem możliwości wystąpienia przepełnienia.
// Safe Math - used to deal with overflows and divide_by_zero exceptionscontractSafeMath {
function add(uinta, uintb) publicpurereturns (uintc) {
c = a + b;
require(c >= a);
}
function sub(uinta, uintb) publicpurereturns (uintc) {
require(b <= a);
c = a - b;
}
function mult(uinta, uintb) publicpurereturns (uintc) {
c = a * b;
require(a ==0|| c / a == b);
}
function div(uinta, uintb) publicpurereturns (uintc) {
require(b >0);
c = a / b;
}
}Poniższy kontrakt definiuje standardowy interfejs dla tokenów opartych na Ethereum. Zawiera on podstawowe metody takie jak sprawdzenie stanu konta pod podanym adresem czy przelew środków z jednego portfela na drugi.
// ----------------------------------------------------------------------------// ERC Token Standard #20 Interface// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md// ----------------------------------------------------------------------------contractERC20Interface {
function totalSupply() publicviewreturns (uint);
function balanceOf(addresstokenOwner) publicviewreturns (uintbalance);
function allowance(addresstokenOwner, addressspender) publicviewreturns (uintremaining);
function transfer(addressto, uinttokens) publicreturns (boolsuccess);
function approve(addressspender, uinttokens) publicreturns (boolsuccess);
function transferFrom(addressfrom, addressto, uinttokens) publicreturns (boolsuccess);
event Transfer(addressindexedfrom, addressindexedto, uinttokens);
event Approval(addressindexedtokenOwner, addressindexedspender, uinttokens);
}Kontrakt ten zawiera funkcję wymaganą według standardu ERC20, pozwala ona aplikacjom na transfer środków z konta wydającego (po otrzymaniu zgody).
// ----------------------------------------------------------------------------// Contract function to receive approval and execute function in one call//// Source: MiniMeToken// ----------------------------------------------------------------------------contractApproveAndCallFallBack {
function receiveApproval(addressfrom, uint256tokens, addresstoken, bytesmemorydata) public;
}Kontrakt ten określa właściciela tokenu oraz jakie dodatkowe akcje na nim może on przeprowadzić.
contractOwned {
addresspublic owner;
addresspublic newOwner;
event OwnerChanged(addressindexed_from, addressindexed_to);
constructor() public {
owner =msg.sender;
}
modifier onlyOwner {
require(msg.sender== owner);
_;
}
function changeOwner(address_newOwner) public onlyOwner {
newOwner = _newOwner;
}
function acceptOwnership() public {
require(msg.sender== newOwner);
emitOwnerChanged(owner, newOwner);
owner = newOwner;
newOwner =address(0);
}
}Kontruktor kontraktu zapewnia, że pierwszym właścicielem kontraktu będzie osoba, która dokonała jego deployu (sender).
constructor() public {
owner =msg.sender;
}Poniżej przedstawiony jest kod głównego kontraktu, który będzie reprezentował nasz token. W polach <DEFAULT_ADDRESS> wpisujemy adres portfela na które mają zostać przelane wszystkie środki (konto główne). Z konta tego możemy rozdzielić tokeny użytkownikom serwisu. Można zauważyć, że TeacheCoin rozszerza zdefiniowane wcześniej kontrakty ERC20Interface, Owned, SafeMath.
// ----------------------------------------------------------------------------// Teache Coin - Code// Interfaces: ERC20Interface, Owned, SafeMath// ----------------------------------------------------------------------------contractTeacheCoinisERC20Interface, Owned, SafeMath {
stringpublic symbol;
stringpublic name;
uint8public decimals;
uintpublic _totalSupply;
mapping(address=>uint) userBalances;
mapping(address=>mapping(address=>uint)) allowed;
constructor() public {
symbol ="ThC";
name ="TeacheCoin";
decimals =18;
_totalSupply =100000000000000000000000000;
userBalances[<DEFAULT_ADDRESS>] = _totalSupply;
emitTransfer(address(0), <DEFAULT_ADDRESS>, _totalSupply);
}
// get totalSupplyfunction totalSupply() publicviewreturns (uint) {
return _totalSupply - userBalances[address(0)];
}
// check balance for account tokenOwnerfunction balanceOf(addresstokenOwner) publicviewreturns (uintbalance) {
return userBalances[tokenOwner];
}
// transfer the balance from token owner's account to the account// - Owner must have sufficient balance to transferfunction transfer(addressto, uinttokens) publicreturns (boolsuccess) {
userBalances[msg.sender] =sub(userBalances[msg.sender], tokens);
userBalances[to] =add(userBalances[to], tokens);
emitTransfer(msg.sender, to, tokens);
returntrue;
}
// Token owner can approve for spender to transferFrom(...) tokens from token owner's accountfunction approve(addressspender, uinttokens) publicreturns (boolsuccess) {
allowed[msg.sender][spender] = tokens;
emitApproval(msg.sender, spender, tokens);
returntrue;
}
// Transfer tokes from one account to the other// - Sender's account must have sufficient balance to transferfunction transferFrom(addressfrom, addressto, uinttokens) publicreturns (boolsuccess) {
userBalances[from] =sub(userBalances[from], tokens);
allowed[from][msg.sender] =sub(allowed[from][msg.sender], tokens);
userBalances[to] =add(userBalances[to], tokens);
emitTransfer(from, to, tokens);
returntrue;
} // Returns the amount of tokens approved by the owner that can be// transfered to the spender's accountfunction allowance(addresstokenOwner, addressspender) publicviewreturns (uintremaining) {
return allowed[tokenOwner][spender];
}
// Allows token owner to approve for spender to transferFrom(...) tokens// from the token owner's account// Utilizes receiveApproval function from ApproveAndCallFallBack contractfunction approveAndCall(addressspender, uinttokens, bytesmemorydata) publicreturns (boolsuccess) {
allowed[msg.sender][spender] = tokens;
emitApproval(msg.sender, spender, tokens);
ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, address(this), data);
returntrue;
}
// You can't buy token with Etherfunction () externalpayable {
revert();
}
// Allows owner to transfer out accidentally sent ERC20 Tokensfunction transferAnyERC20Token(addresstokenAddress, uinttokens) public onlyOwner returns (boolsuccess) {
returnERC20Interface(tokenAddress).transfer(owner, tokens);
}
}Dwie najważniejsze części kontraktu to przedstawione poniżej mapowanie adresów, które określa balans każdego konta oraz kontruktor, w którym definiujemy główne informacje o naszym kontrakcie.
Mapowanie stanów konta do adresów.
mapping(address=>uint) userBalances;Definiowanie podstawowych właśności kontraktu
constructor() public {
symbol ="ThC";
name ="TeacheCoin";
decimals =18;
_totalSupply =100000000000000000000000000;
userBalances[<DEFAULT_ADDRESS>] = _totalSupply;
emitTransfer(address(0), <DEFAULT_ADDRESS>, _totalSupply);
}W powyższym kodzie symbol oznacza skróconą nazwę naszego tokenu, name oznacza nazwę pod którą będzie on znany, natomiast pole decimals określa na jak małe części będzie można rozbić nasz token (standardową i zalecaną wartością jest 18). Kolejne pole _totalSupply określa ile tokenów jest w obiegu (nieużywane tokeny mogą być przetrzymywane na głównym koncie). W ostatnich dwóch linijkach ustawiamy stan naszego konta głównego tak, by zawierał wszystkie możliwe tokeny, a następnie wysyłamy wiadomość o transferze środków.
Na samym początku kodu definiujemy kompilator, z którego chcemy skorzystać.
pragma solidity^0.5.8;Na głównym ekranie Remix IDE wybieramy kompilator, który zostanie użyty do skompilowania kontraktów (po prawej stronie), a następnie wybieramy opcję Start to compile.
Przechodzimy do zakładki Run i wybieramy nasz główny kontrakt z listy (reszta kontraktów zostanie uruchomiona automatycznie). Następnie zmieniamy Environment na Injected Web3 (pozwoli to na wykrycie naszego konta Metamask). Następnie klikamy w przycisk Deploy i zatwierdzamy dokonanie transkacji.
Po dokonaniu tych czynności powinniśmy uzyskać adres dokonanej transkacji (znajduje się on na dole ekranu w jego centralnej części).
Na stronie transkacji klikamy w adres znajdujący się w polu To, a następnie przechodzimy do zakładki Code.
W celu weryfikacji i opublikowania naszego kontraktu klikamy w opcję Verify and Publish. Następnie wybieramy odpowiedni kompilator oraz jego wersję.
W polu Enter the Solidity Contract Code below wklejamy cały kod naszego kontraktu z Remix IDE, a następnie wybieramy opcję Verify and Publish na dole strony. Po udanej operacji zostanie wyświetlona odpowiednia strona, na dole której znajduje się pole ContractABI, którego zawartość pozwoli nam na oddziaływanie z kontraktem na poziomie języka JavaScript.
Zawartość pola ContractABI zostanie umieszczona w kodzie źródłowym naszej strony (src/tokens/TeacheCoin.js) w przedstawionej poniżej postaci.
constCoin={address: "ADRES TOKENU",decimal: LICZBAMIEJSCPOPRZECINKU,name: "NAZWA TOKENU",symbol: "SYMBOL TOKENU",abi: ZAWARTOŚĆContractABI}exportdefaultCoin;Przedstawiony powyżej kod pozwoli nam na odnoszenie się do naszego kontraktu z poziomu języka JavaScript.
Do implementacji aplikacji umożliwiającej przelew środków na dowolny portfel ETH wykorzystamy bibliotekę React.js. Aby rozpocząć projekt będziemy potrzebować npm (Link). Po zainstalowaniu menadżera pakietów przechodzimy do folderu, w którym chcemy rozpocząć projekt, uruchamiamy konsolę i wpisujemy polecenie:
npm init react-app eth-tokenSzczegółowe informacje dotyczące inicjacji projektu React.js znajdują się pod adresem https://github.com/facebook/create-react-app.
Po utworzeniu projektu kopiujemy plik TeacheCoin.js stworzony w poprzednim etapie do folderu src/tokens/. Następnie zamieniamy zawartość pliku App.js na poniższą
importReact,{Component}from'react';importTeacheCoinfrom'./tokens/TeacheCoin'import'./App.css';classSendTokensextendsComponent{constructor(){super();this.state={balance: 0,destWallet: "",destAmount: 0}this.isWeb3=false;this.isWeb3Locked=false;this.loadBalance=this.loadBalance.bind(this)this.checkWeb3Compatibility=this.checkWeb3Compatibility.bind(this)}componentDidMount(){window.addEventListener('load',this.checkWeb3Compatibility)if(window.web3){window.web3.currentProvider.publicConfigStore.on('update',()=>{this.checkWeb3Compatibility()})}}checkWeb3Compatibility(){if(window.web3){this.isWeb3=true;window.web3.eth.getCoinbase((error,coinbase)=>{if(error||coinbase===null){this.isWeb3Locked=true;}else{this.isWeb3Locked=false;this.setState({account: coinbase,token: window.web3.eth.contract(TeacheCoin.abi).at(TeacheCoin.address)},()=>{this.loadBalance()})}})}else{this.isWeb3=false;}}loadBalance(){if(this.isWeb3){window.web3.eth.getCoinbase((error,coinbase)=>{lettoken=this.state.tokentoken.balanceOf(coinbase,(error,response)=>{if(!error){letbalance=response.c[0]/10000balance=balance>=0 ? balance : 0this.setState({balance: balance,symbol: TeacheCoin.symbol,decimal: '1e'+TeacheCoin.decimal})}})})}}sendTokens=(event)=>{event.preventDefault()constbalance=this.state.balanceconstamount=this.state.destAmount;consttarget=this.state.destWalletconsttoken=this.state.tokenconstdecimals=this.state.decimal;if(amount<=balance&&amount>0&&token){token.transfer(target,amount*decimals,(error,response)=>{if(error||error!==null){alert(error);}else{alert('Pomyślnie wysłano '+amount+' '+this.state.symbol);this.loadBalance()}})}}handleAddressChange=(event)=>{this.setState({destWallet: event.target.value})}handleAmountChange=(event)=>{this.setState({destAmount: event.target.value})}render(){return(<divclassName="main-container"><pclassName="form-label">Stan konta</p><pclassName="form-balance">{this.state.balance}{this.state.symbol}</p><pclassName="form-label">Portfel odbiorcy</p><inputtype="text"className="usr-input"onChange={e=>this.handleAddressChange(e)}/><pclassName="form-label">Liczba tokenów do wysłania</p><inputtype="number"className="usr-input"min="0"max={this.state.balance}onChange={e=>this.handleAmountChange(e)}/><br/><inputtype="button"className="send-btn"onClick={e=>this.sendTokens(e)}value="Wyślij"/></div>)}}functionApp(){return(<divclassName="App"><SendTokensclassName="token-container"/></div>);}exportdefaultApp;Metoda checkWeb3Compatibility uruchamiana jest przy każdym załadowaniu strony, sprawdza ona czy użytkownik korzysta z przeglądarki obsługującej web3 oraz czy posiada on odblokowany portfel, a następnie ładuje stan konta użytkownika poprzez metodę loadBalance().
Pobrany w metodzie loadBalance() stan konta jest odpowiednio skalowany w linijce
letbalance=response.c[0]/10000W momencie kiedy użytkownik kliknie w przycisk Wyślij uruchamiana jest metoda SendTokens(), która po sprawdzeniu konta użytkownika wykonuje przy użyciu wcześniej zdefiniowanego ABI operację transfer na kontrakcie.
token.transfer(target,amount*decimals,(error,response)=>{if(error||error!==null){alert(error);}else{alert('Pomyślnie wysłano '+amount+' '+this.state.symbol);this.loadBalance();}})Następnie w pliku App.css dopisujemy poniższy kod
.token-container {
text-align: center;
}
.main-container {
display: block;
}
.form-label {
font-size:1.2rem;
}
.form-balance {
font-size:0.8rem;
}
.usr-input {
min-width:20vw;
min-height:3vh;
font-size:0.8rem;
background-color: white;
border: solid 1px lightgrey;
border-radius:5px;
}
.send-btn {
margin-top:0.9em;
min-width:5vw;
min-height:3vw;
background-color: white;
border: solid 1px grey;
border-radius:5px;
}
.send-btn:active {
background-color:lightgrey;
}Efekt końcowy powinien wyglądać następująco
Po kliknięciu w przycisk Wyślij rozszerzenie Metamask zapyta o zgodę na dokonanie transferu
Zatwierdzenie akcji przez naciśnięcie przycisku Submit powinno przelać tokeny na wybrane przez nas konto.

















