Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Training n°1 for decentralized application

NotificationsYou must be signed in to change notification settings

marigold-dev/training-dapp-1

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

titleauthorslast_update
Create your minimum dapp on Tezos
Benjamin Fuentes
date
22 May 2024

dApp : A decentralized application is a type of distributed open source software application that runs on a peer-to-peer (P2P) blockchain network rather than on a single computer. DApps are visibly similar to other software applications that are supported on a website or mobile device.

This tutorial shows you how to create a Poke game as a smart contract.The game consists of poking the owner of a smart contract. The smart contract keeps track of user interactions and stores a trace.

Poke sequence diagram.

sequenceDiagram  Note left of User: Prepare poke transaction  User->>Smartcontract: poke()  Note right of Smartcontract: store(pokeTrace)
Loading

You will learn :

  • How to create a Tezos project with Taqueria.
  • How to create a smart contract in jsLigo.
  • How to deploy the smart contract a real testnet named Ghostnet.
  • How to create a frontend dApp using Taquito library and interact with a Tezos browser wallet.
  • How to use an indexer like TZKT.

This tutorial uses Typescript, so it will be easier if you are familiar with JavaScript.

  1. Make sure that you have installed these tools:

    • Node.JS and NPM: NPM is required to install the web application's dependencies.
    • Taqueria, version 0.45.0 or later: Taqueria is a platform that makes it easier to develop and test dApps.
    • Docker: Docker is required to run Taqueria.
    • jq: Some commands use thejq program to extract JSON data.
    • yarn: The frontend application uses yarn to build and run (see this article for details aboutdifferences betweennpm andyarn).
    • Any Tezos-compatible wallet that supports Ghostnet, such asTemple wallet.
  2. Optionally, you can installVS Code to edit your application code and theLIGO VS Code extension for LIGO editing features such as code highlighting and completion.Taqueria also provides aTaqueria VS Code extension that helps visualize your project and run tasks.

The tutorial application

In this tutorial, you create a simple game where the user is poking through a dApp. The user interacts with the smart contract through a web interface, where they can see the current state of the contract and send poke commands to it. The contract responds by updating its storage with the user's address. Alternatively, a user can also poke the contract deployed by other users.

The application looks like this:

Example of the table of addresses and which addresses poked them

The code for the completed application is in this GitHub repository:solution

When you're ready, move to the next sectionCreate your minimum dApp on Tezos to begin setting up the application.


title: 'Part 1: Create your minimum dApp on Tezos'authors: 'Benjamin Fuentes (Marigold)'last_update:date: 22 May 2024


To start working with the application, you create a Taqueria project and use it to deploy the Poke contract.Then you set up a web application to connect with a wallet, and then interact with your smart contract.

Before you begin, make sure that you have installed the tools in thePrerequisites section.

Creating a Taqueria project

Taqueria manages the project structure and keeps it up to date.For example, when you deploy a new smart contract, Taqueria automatically updates the web app to send transactions to that new smart contract.Follow these steps to set up a Taqueria project:

On the command-line terminal, run these commands to set up a Taqueria project and install the LIGO and Taquito plugins:

taq init trainingcd trainingtaq install @taqueria/plugin-ligotaq install @taqueria/plugin-taquitotaq create contract pokeGame.jsligo

Write the smart contract

  1. Edit thepokeGame.jsligo file. Remove the default code and paste this code instead.

    export type storage = unit;type return_ = [list<operation>, storage];@entryconst poke = (_: unit, store: storage): return_ => {  return [list([]), store];};

    Every contract has to follow these rules :

    • At least one entrypoint, annotated with@entry , with a mandatory signature taking 2 arguments*(parameter, storage) and a return type. An entrypoint is a function that is exposed as an external API.
      • parameter: the entrypoint parameter. Mandatory and can be of any type. For example: an (ignored) variable starting with_ here, and of typeunit (the type void on LIGO).
      • storage: the on-chain storage. Mandatory and can be of any type. For example, here we use the typeunit. It is recommended to add anexport keyword before the type definition as it is a good practice to export it when you are required to write unit tests from another LIGO file.
      • return_: a mandatory pair of list ofoperation and the storage type (defined earlier). Return type naming is free but don't use an existing keyword likereturn.

    Have a look at the Entrypoints contracts documentation>

    Note: Previous versions of LIGO used a single main function instead of a function for each entrypoint. This syntax is still valid, but it is harder to read and deprecated in LIGO V1.

    APoke variant parameter is generated from thepoke entrypoint function under the hood. A variant is more or less equivalent of the Enum type in Javascript. A default main function is generated and act like as a dispatcher for each of your entrypoints. It means that this painful boilerplate is no more needed on the new syntax.

    Have a look at the Variant type documentation

  2. Write the poke function.The objective is to store every user/caller addresses poking the contract.Rewrite the storage, and add the caller address to the set of traces.

    At line 1, replace the line with:

    export type storage = set<address>;
  3. Replace thepoke function with:

    @entryconst poke = (_: unit, store: storage): return_ => {  return [list([]), Set.add(Tezos.get_source(), store)]};

    Explanation:

    • The LIGO Set library has a functionadd to add one element to the Set of items. There is no concept of Class in LIGO, you use a library to apply functions on objects.
    • A list of operations is required to return. An empty list is returned here as there is no other contract to call.

    Have a look at the Set library documentation

    Have a look at the List library documentation

    Here, get the caller address usingTezos.get_source(). Tezos library provides useful functions for manipulating blockchain objects.

    Have a look at the Tezos library documentation

Simulate a call on your smart contract

The LIGO command-line provides sub-commands to test your Ligo code.

Have a look at the Testing Framework documentation

  1. Compile the contract with Taqueria (Force to use a specific LIGO version withTAQ_LIGO_IMAGE Taqueria environment variable).
TAQ_LIGO_IMAGE=ligolang/ligo:1.6.0 taq compile pokeGame.jsligo

Taqueria is generating the.tz Michelson file in the `artifacts`` folder. The Michelson language is the default stack language used by the Michelson VM to run your code on a node. It is something similar to WASM.

Have a look on the Michelson documentation

  1. Taqueria is generating two additional files, edit the first filepokeGame.storageList.jsligo replacing the current code with:

    #import "pokeGame.jsligo" "Contract"const default_storage = Set.empty as set<address>;

    When you deploy a contract, you are required to initialize the default state of your smart contract. Taqueria allows you to declare different variables on this file, it is useful to use different initialized states per environment.

    Have a look at the Taqueria documentation

  2. Compile all (contract + initial storage)

    TAQ_LIGO_IMAGE=ligolang/ligo:1.6.0 taq compile pokeGame.jsligo

    It compiles both source code and storage.

    Before deployment, to simulate a call to our entrypointpoke, Taq has ataq simulate command.
    The contract parameterPoke() and the initial storage with the default empty set are passed to the execution.

  3. Edit the second filepokeGame.parameterList.jsligo

    #import "pokeGame.jsligo" "Contract"const default_parameter: parameter_of Contract = Poke();
  4. Run the simulation. First, install the Tezos client plugin, recompile it all, and then run the simulation.

    taq install @taqueria/plugin-octez-clientTAQ_LIGO_IMAGE=ligolang/ligo:1.6.0 taq compile pokeGame.jsligotaq simulate pokeGame.tz --param pokeGame.parameter.default_parameter.tz

    Output logs:

    ┌─────────────┬──────────────────────────────────────────────┐│ Contract    │ Result                                       │├─────────────┼──────────────────────────────────────────────┤│ pokeGame.tz │ storage                                      ││             │   { "tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" } ││             │ emitted operations                           ││             │                                              ││             │ big_map diff                                 ││             │                                              ││             │                                              │└─────────────┴──────────────────────────────────────────────┘

    You can notice that the instruction is storing the address of the caller in the storage set.

Configure your wallet and deploy

The default Tezos testing testnet is calledGhostnet.

⚠️ You need an account to deploy a contract with sometez (the Tezos native currency). The first time you deploy a contract with Taqueria, it is generating a new implicit account with0 tez.

  1. Deploy your contract to thetesting environment. Ut forces Taqueria to generate a default account on a testing config file.

    taq deploy pokeGame.tz -e"testing"

    You should get this kind of log.

    Warning: the faucet field in network configs has been deprecated and will be ignored.A keypair with public key hash tz1XXXXXXXXXXXXXXXXXXXXXX was generated for you.To fund this account:1. Go to https://teztnets.com and click "Faucet" of the target testnet.2. Copy and paste the above key into the 'wallet address field.3. Request some Tez (Note that you might need to wait for a few seconds for the network to register the funds).No operations performed.
    • Choice N°1 (Recommended): Use Alice wallet instead of the generated account. A common usage is to usealice account as Taqueria operator.alice is a commonly known address used on Tezos and she has always sometez. Replace the Taqueria config file fortesting env.taq/config.local.testing.json withalice settings:

      {"networkName":"ghostnet","accounts": {"taqOperatorAccount": {"publicKey":"edpkvGfYw3LyB1UcCahKQk4rF2tvbMUk8GFiTuMjL75uGXrpvKXhjn","publicKeyHash":"tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb","privateKey":"edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq"    }  }}
    • Choice N°2: use the Taqueria-generated account. Copy the accountprivateKey from the.taq/config.local.testing.json config file. Open your Temple browser extension on your computer or on your mobile phone and do theinitial setup. Once you are done, go to Settings (click on the avatar icon, or display Temple in full page) and click onImport account > Private key tab. Paste theprivateKey to Temple text input and confirm. Send free Tez to your new account via this web faucethere. Connect your wallet onGhostnet and ask for free tez.

    Now you have some money to play with.

  2. Deploy to Ghostnet testnet.

    taq deploy pokeGame.tz -e"testing"

    Your smart contract is deployed on the Ghostnet.

    ┌─────────────┬──────────────────────────────────────┬──────────┬──────────────────┬────────────────────────────────┐│ Contract    │ Address                              │ Alias    │ Balance In Mutez │ Destination                    │├─────────────┼──────────────────────────────────────┼──────────┼──────────────────┼────────────────────────────────┤│ pokeGame.tz │ KT1G8tx4qSeJmKRY1p2oxA6eYoCGc9Qi3Fky │ pokeGame │ 0                │ https://ghostnet.ecadinfra.com │└─────────────┴──────────────────────────────────────┴──────────┴──────────────────┴────────────────────────────────┘

Create the frontend

Create a React app

yarn create vite

Then follow the prompts. Choose React and then Typescript+SWC:

? Project name: › app#Enter your project name? Select a framework: › - Use arrow-keys. Return to submit.# Select React as framework    Vanilla    Vue❯   React    Preact    Lit    Svelte    Others? Select a variant: › - Use arrow-keys. Return to submit.#Both TypeScript variants are fine. Select TypeScript only.    TypeScript❯   TypeScript + SWC    JavaScript    JavaScript + SWC

More information about SWC here.

  1. Add taquito and tzkt indexer libraries.

    cd appyarn add @taquito/taquito @taquito/beacon-wallet @airgap/beacon-sdk  @tzkt/sdk-apiyarn add -D @airgap/beacon-types

    ⚠️ Before starting, add the following dependencies in order to resolve polyfill issues. Some dependencies are from NodeJs, thus not included in browsers.

  2. For example, in my case, I installed this:

    yarn add --dev process buffer crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url path-browserify
  3. Create a new filenodeSpecific.ts in the src folder of your project and edit with this content:

    touch src/nodeSpecific.ts
    import{Buffer}from"buffer";globalThis.Buffer=Buffer;
  4. Open theindex.html file and replace thebody with this one:

    <body><divid="root"></div><scripttype="module"src="/src/nodeSpecific.ts"></script><scripttype="module"src="/src/main.tsx"></script></body>
  5. Open thevite.config.ts file and replace it with:

    importreactfrom"@vitejs/plugin-react-swc";importpathfrom"path";import{defineConfig}from"vite";// https://vitejs.dev/config/exportdefault({ command})=>{constisBuild=command==="build";returndefineConfig({define:{},plugins:[react()],build:{commonjsOptions:{transformMixedEsModules:true,},},resolve:{alias:{// dedupe@airgap/beacon-sdk// I almost have no idea why it needs `cjs` on dev and `esm` on build, but this is how it works 🤷‍♂️"@airgap/beacon-sdk":path.resolve(path.resolve(),`./node_modules/@airgap/beacon-sdk/dist/${isBuild ?"esm" :"cjs"}/index.js`),stream:"stream-browserify",os:"os-browserify/browser",util:"util",process:"process/browser",buffer:"buffer",crypto:"crypto-browserify",assert:"assert",http:"stream-http",https:"https-browserify",url:"url",path:"path-browserify",},},});};

Generate the Typescript classes from Michelson code and run the server

Taqueria is able to generate Typescript classes for any frontend application. It takes the definition of your smart contract and generates the contract entrypoint functions, type definitions, etc ...

To get typescript classes from Taqueria plugin, on your project root folder run:

taq install @taqueria/plugin-contract-typestaq generate types ./app/src
  1. Back to your frontend app, and run the dev server.

    cd appyarn dev
  2. Open your browser at:http://localhost:5173/Your app should be running.

Connect / disconnect the wallet

Declare two React Button components and display the user's address and his balance.

Editsrc/App.tsx file.

import{NetworkType}from"@airgap/beacon-types";import{BeaconWallet}from"@taquito/beacon-wallet";import{TezosToolkit}from"@taquito/taquito";import*asapifrom"@tzkt/sdk-api";import{useEffect,useState}from"react";import"./App.css";importConnectButtonfrom"./ConnectWallet";importDisconnectButtonfrom"./DisconnectWallet";functionApp(){api.defaults.baseUrl="https://api.ghostnet.tzkt.io";const[Tezos,setTezos]=useState<TezosToolkit>(newTezosToolkit("https://ghostnet.ecadinfra.com"));const[wallet,setWallet]=useState<BeaconWallet>(newBeaconWallet({name:"Training",preferredNetwork:NetworkType.GHOSTNET,}));useEffect(()=>{(async()=>{constactiveAccount=awaitwallet.client.getActiveAccount();if(activeAccount){setUserAddress(activeAccount.address);constbalance=awaitTezos.tz.getBalance(activeAccount.address);setUserBalance(balance.toNumber());}})();},[]);const[userAddress,setUserAddress]=useState<string>("");const[userBalance,setUserBalance]=useState<number>(0);return(<divclassName="App"><headerclassName="App-header"><ConnectButtonTezos={Tezos}setTezos={setTezos}setUserAddress={setUserAddress}setUserBalance={setUserBalance}wallet={wallet}/><DisconnectButtonwallet={wallet}setUserAddress={setUserAddress}setUserBalance={setUserBalance}/><div>Iam{userAddress}with{userBalance}mutez</div></header></div>);}exportdefaultApp;
  1. Let's create the 2 missing src component files:

    touch src/ConnectWallet.tsxtouch src/DisconnectWallet.tsx

    ConnectWallet button creates an instance wallet, gets user permissions via a popup, and then retrieves the current account information.

  2. EditConnectWallet.tsx

    import{NetworkType}from"@airgap/beacon-sdk";import{BeaconWallet}from"@taquito/beacon-wallet";import{TezosToolkit}from"@taquito/taquito";import{Dispatch,SetStateAction}from"react";typeButtonProps={Tezos:TezosToolkit;setUserAddress:Dispatch<SetStateAction<string>>;setUserBalance:Dispatch<SetStateAction<number>>;wallet:BeaconWallet;setTezos:Dispatch<SetStateAction<TezosToolkit>>;};constConnectButton=({  Tezos,  setTezos,  setUserAddress,  setUserBalance,  wallet,}:ButtonProps):JSX.Element=>{constconnectWallet=async():Promise<void>=>{try{awaitwallet.requestPermissions({network:{type:NetworkType.GHOSTNET,rpcUrl:"https://ghostnet.ecadinfra.com",},});// gets user's addressconstuserAddress=awaitwallet.getPKH();constbalance=awaitTezos.tz.getBalance(userAddress);setUserBalance(balance.toNumber());setUserAddress(userAddress);Tezos.setWalletProvider(wallet);setTezos(Tezos);}catch(error){console.log(error);}};return(<divclassName="buttons"><buttonclassName="button"onClick={connectWallet}><span><iclassName="fas fa-wallet"></i>&nbsp;Connectwithwallet</span></button></div>);};exportdefaultConnectButton;
  3. EditDisconnectWallet.tsx

The button cleans the wallet instance and all linked objects.

import{BeaconWallet}from"@taquito/beacon-wallet";import{Dispatch,SetStateAction}from"react";interfaceButtonProps{wallet:BeaconWallet;setUserAddress:Dispatch<SetStateAction<string>>;setUserBalance:Dispatch<SetStateAction<number>>;}constDisconnectButton=({  wallet,  setUserAddress,  setUserBalance,}:ButtonProps):JSX.Element=>{constdisconnectWallet=async():Promise<void>=>{setUserAddress("");setUserBalance(0);console.log("disconnecting wallet");awaitwallet.clearActiveAccount();};return(<divclassName="buttons"><buttonclassName="button"onClick={disconnectWallet}><iclassName="fas fa-times"></i>&nbsp;Disconnectwallet</button></div>);};exportdefaultDisconnectButton;
  1. Save both files, the dev server should refresh the page.

    As Temple is configured, click on Connect button.

    On the popup, select your Temple wallet, then your account, and connect.

    The app after you have connected, showing your address and tex balance

    Your arelogged.

  2. Click on the Disconnect button to test the disconnection, and then reconnect.

List other poke contracts via an indexer

Instead of querying heavily the RPC node to search where are located all other similar contracts and retrieve each address, use an indexer. an indexer is a kind of enriched cache API on top of an RPC node. In this example, the TZKT indexer is used to find other similar contracts.

  1. You need to install jq to parse the Taqueria JSON configuration file.Install jq

  2. Onpackage.json, change thedev command onscripts configuration. Prefix it with ajq command to create an new environment variable pointing to your last smart contract address on testing env:

    "dev":"jq -r '\"VITE_CONTRACT_ADDRESS=\" + last(.tasks[]).output[0].address' ../.taq/testing-state.json > .env && vite",

    The last deployed contract address on Ghostnet is set now on our frontend.

  3. Add a button to fetch all similar contracts like yours, then display the list.EditApp.tsx and before thereturn of App function, add this section for the fetch function.

    const[contracts,setContracts]=useState<Array<api.Contract>>([]);constfetchContracts=()=>{(async()=>{setContracts(awaitapi.contractsGetSimilar(import.meta.env.VITE_CONTRACT_ADDRESS,{includeStorage:true,sort:{desc:"id"},}));})();};
  4. On the returnedhtml template section, after the display of the user balance divI am {userAddress} with {userBalance} mutez, append this:

    <br/><div><buttononClick={fetchContracts}>Fetch contracts</button>{contracts.map((contract)=><div>{contract.address}</div>)}</div>
  5. Save your file and restart your server.Now, the start script generates the .env file containing the last deployed contract address.

    yarn dev
  6. Go to your web browser and click onFetch contracts button.

    Congratulations, you are able to list all similar deployed contracts.

Poke your contract

  1. Import the Taqueria-generated types onapp/src/App.tsx.

    import{PokeGameWalletType}from"./pokeGame.types";
  2. Add this new function after the previous fetch function, it calls the entrypoint for poking.

    constpoke=async(contract:api.Contract)=>{letc:PokeGameWalletType=awaitTezos.wallet.at<PokeGameWalletType>(""+contract.address);try{constop=awaitc.methodsObject.default().send();awaitop.confirmation();alert("Tx done");}catch(error:any){console.table(`Error:${JSON.stringify(error,null,2)}`);}};

    ⚠️ Normally, a call toc.methods.poke() function is expected by convention, but with an unique entrypoint, Michelson generates a uniquedefault entrypoint name instead of having the name of the entrypoint function. Also, be careful because all entrypoints function names are in lowercase, and all parameter types are in uppercase.

  3. Replace the line displaying the contract address{contracts.map((contract) => <div>{contract.address}</div>)} with the one below, it adds a Poke button.

        {contracts.map((contract) =><div>{contract.address}<buttononClick={() =>poke(contract)}>Poke</button></div>)}
  4. Save and see the page refreshed, then click on the Poke button.

    It calls the contract and adds your public address tz1... to the set of traces.

  5. Display poke guys

To verify that on the page, we can display the list of poke people directly on the page

Replace again the html previous line{contracts ...} with this one

<table><thead><tr><th>address</th><th>people</th><th>action</th></tr></thead><tbody>    {contracts.map((contract) =><tr><tdstyle={{borderStyle: "dotted"}}>{contract.address}</td><tdstyle={{borderStyle: "dotted"}}>{contract.storage.join(", ")}</td><tdstyle={{borderStyle: "dotted"}}><buttononClick={() =>poke(contract)}>Poke</button></td></tr>)}</tbody></table>

Contracts are displaying their people now

ℹ️ Wait around few second for blockchain confirmation and click onfetch contracts to refresh the list

🎊 Congratulations, you have completed this first dapp training

Summary

Now, you can create any Smart Contract using Ligo and create a complete Dapp via Taqueria/Taquito.

In the next section, you will learn how to call a Smart contract from a Smart Contract using callbacks and also write unit and mutation tests.

When you are ready, continue toPart 2: Inter-contract calls and testing.

About

Training n°1 for decentralized application

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors3

  •  
  •  
  •  

[8]ページ先頭

©2009-2025 Movatter.jp