{"id":18623192,"url":"https://github.com/glottologist/tezos-dapp-training","last_synced_at":"2025-11-03T17:30:31.579Z","repository":{"id":42430655,"uuid":"478567487","full_name":"glottologist/tezos-dapp-training","owner":"glottologist","description":null,"archived":false,"fork":false,"pushed_at":"2022-04-08T09:12:30.000Z","size":729,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-27T05:23:12.440Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/glottologist.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-04-06T13:15:42.000Z","updated_at":"2022-04-08T09:12:33.000Z","dependencies_parsed_at":"2022-08-27T01:42:18.448Z","dependency_job_id":null,"html_url":"https://github.com/glottologist/tezos-dapp-training","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":"marigold-dev/training-dapp-1","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glottologist%2Ftezos-dapp-training","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glottologist%2Ftezos-dapp-training/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glottologist%2Ftezos-dapp-training/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glottologist%2Ftezos-dapp-training/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/glottologist","download_url":"https://codeload.github.com/glottologist/tezos-dapp-training/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239418544,"owners_count":19635203,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T04:21:54.734Z","updated_at":"2025-11-03T17:30:31.525Z","avatar_url":"https://github.com/glottologist.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"---\ntitle: Training dapp n°1\ntags: Training\ndescription: Training n°1 for decentralized application\n---\n\nTraining dapp n°1\n===\n\n[ToC]\n\n:::info\n:bulb: **Github repository :** get the complete solution [here](https://github.com/marigold-dev/training-dapp-1.git)\n:::\n:::info\n:bulb: **Hackmd original page** [here](https://hackmd.io/sJ2WKZlIRvO58w-YV1jf9A?view)\n:::\n\n# :point_up:  Poke game\n\n\u003e dapp : A decentralized application (dApp) 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 but are P2P supported\n\nGoal of this training is to develop a poke game with smart contract. You will learn : \n- create a smart contract in jsligo\n- deploy smart contract\n- create a dapp using taquito and interact with browser wallet\n- use an indexer\n\n\u003e :warning: This is not an HTML or REACT training, I will avoid asmuch of possible any complexity relative to these technologies \n\nThe game consists on poking the owner of a smart contract.  The smartcontract keeps a track of user interactions on the storage \n\nPoke sequence diagram\n```sequence\nNote left of User: Prepare poke\nUser-\u003eSM: poke owner\nNote right of SM: store poke trace\nSM-\u003eUser: \n```\n\n# :memo: Prerequisites\n\nPlease install this software first : \n\n- [ ] [VS Code](https://code.visualstudio.com/download) : as text editor\n- [ ] [npm](https://nodejs.org/en/download/) : we will use a typescript React client app\n- [ ] [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#windows-stable) : because yet another\n- [ ] [ligo](https://ligolang.org/docs/intro/installation/) : high level language that's transpile to michelson low level language and provide lot of development support for Tezos\n- [ ] [tezos-client (method 1)](https://tezos.gitlab.io/introduction/howtoget.html) or [tezos-client (method 2)](https://assets.tqtezos.com/docs/setup/1-tezos-client/#install) : the Tezos CLI\n- [ ] [Temple wallet](https://templewallet.com/) : an easy to use Tezos wallet as browser plugin \n\n# :scroll: Smart contract\n\n## Step 1 : Create folder \u0026 file\n\n```bash\nmkdir smartcontract\ntouch ./smartcontract/pokeGame.jsligo\n```\n\n## Step 2 : Edit pokeGame.jsligo\n\nAdd a main function\n\n```javascript=\ntype storage = unit;\n\ntype parameter =\n| [\"Poke\"];\n\ntype return_ = [list\u003coperation\u003e, storage];\n\nlet main = ([action, store] : [parameter, storage]) : return_ =\u003e {\n    return match (action, {\n        Poke: () =\u003e poke(store)\n    } \n    )\n};\n```\n\nEvery contract requires :\n- an entrypoint, **main** by default, with a mandatory signature taking 2 parameters and a return : \n    - **parameter** : the contract `parameter`\n    - **storage** : the on-chain storage (can be any type, here `unit` by default)\n    - **return_** : a list of `operation` and a storage\n\n\u003e Doc :  https://ligolang.org/docs/advanced/entrypoints-contracts\n\n\u003e:warning: You will notice that jsligo is a javascript-like language, multiple parameter declaration is a bit different.\nInstead of this declaration : `(action : parameter, store : storage)`\nYou have to separate variable name to its type declaration this way : `([action, store] : [parameter, storage])`\n\n\nPattern matching is an important feature in Ligo. We need a switch on the entrypoint function to manage different actions. We use `match` to evaluate the parameter and call the appropriated `poke` function\n\u003e Doc https://ligolang.org/docs/language-basics/unit-option-pattern-matching\n\n```javascript\nmatch (action, {\n        Poke: () =\u003e poke(store)\n    } \n```\n\n`Poke` is a `parameter` from `variant` type. It is the equivalent of Enum type in javascript\n\n```javascript\ntype parameter =\n| [\"Poke\"];\n```\n\n\u003e Doc https://ligolang.org/docs/language-basics/unit-option-pattern-matching#variant-types\n\n## Step 3 : Write the poke function\n\nWe want to store every caller address poking the contract. Let's redefine storage, and then add the caller to the set of poke guys\n\n```javascript\ntype storage = set\u003caddress\u003e;\n\nlet poke = (store : storage) : return_ =\u003e {\n    return [  list([]) as list\u003coperation\u003e, Set.add(Tezos.source, store)]; \n};\n```\n\nSet library has specific usage :\n\u003e Doc https://ligolang.org/docs/language-basics/sets-lists-tuples#sets\n\n\nHere, we get the caller address using `Tezos.source`. Tezos library provides useful function for manipulating blockchain objects\n\u003e Doc https://ligolang.org/docs/reference/current-reference\n\n## Step 4 : Try to poke\n\nThe LIGO command-line interpreter provides sub-commands to directly test your LIGO code\n\n\u003e Doc : https://ligolang.org/docs/advanced/testing\n\nCompile contract (to check any error, and prepare the michelson outputfile to deploy later) :\n\n```bash\nligo compile contract ./smartcontract/pokeGame.jsligo --output-file pokeGame.tz\n```\n\nCompile an initial storage (to pass later during deployment too)\n\n```\nligo compile storage ./smartcontract/pokeGame.jsligo 'Set.empty as set\u003caddress\u003e' --output-file pokeGameStorage.tz --entry-point main\n```\n\nDry run (i.e test an execution locally without deploying), pass the contract parameter `Poke()` and the initial on-chain storage with an empty set : \n\n```bash\nligo run dry-run ./smartcontract/pokeGame.jsligo 'Poke()' 'Set.empty as set\u003caddress\u003e' \n```\n\nOutput should give : \n\n```ocaml=\n( LIST_EMPTY() ,\n  SET_ADD(@\"tz1QL8xpMA9JwtUYXXwB6qnJTk8pkEakHpT4\" , SET_EMPTY()) )\n```\n\nYou can notice that the instruction will store the address of the caller into the traces storage\n\n## Step 5 : Configure your testnet local environment\n\nChoose a testnet to deploy\n\nFor hangzhounet :\n```\ntezos-client --endpoint https://hangzhounet.tezos.marigold.dev config update\n```\n\nYou will need an implicit account on your local wallet and get free Tz from a [faucet] and download the .json file locally (https://teztnets.xyz/)\n\n\u003e Doc : https://tezos.gitlab.io/introduction/howtouse.html#get-free-tez\n\nReplace \u003cACCOUNT_KEY_NAME\u003e by account key of your choice : \n\n```\ntezos-client activate account \u003cACCOUNT_KEY_NAME\u003e with \"tz1__xxxxxxxxx__.json\"\n```\n\nList all local accounts :\n\n```\ntezos-client list known addresses\n```\n\nYour account should appear on the list now\n\nCheck your balance\n\n```\ntezos-client get balance for \u003cACCOUNT_KEY_NAME\u003e\n```\n\n:rocket: You are ready to go :sunglasses: \n\n## Step 6 : Deploy to testnet\n\nUse the tezos-client to deploy the contract\n\n```\ntezos-client originate contract mycontract transferring 0 from \u003cACCOUNT_KEY_NAME\u003e running pokeGame.tz --init \"$(cat pokeGameStorage.tz)\" --burn-cap 1\n```\n\nVerify the output. a successful output display the address of the new created smart contract on the testnet\n\n```\nNew contract KT1M1sXXUYdLvow9J4tYcDDrYa6aKn3k1NT9 originated.\n```\n\nInteract now with it, poke it ! :face_with_hand_over_mouth: \n\n```\ntezos-client transfer 0 from \u003cACCOUNT_KEY_NAME\u003e to mycontract --burn-cap 0.01 \n```\n\nCheck that your address is registered on the storage\n\n```\ntezos-client get contract storage for mycontract \n```\n\nHOORAY :confetti_ball: your smart contract is ready !\n\n\n\n\n\n# :construction_worker:  Dapp \n\n## Step 1 : Create react app\n\n```bash\nyarn create react-app dapp --template typescript\n\ncd dapp\n```\n\nAdd taquito, tzkt indexer lib\n\n```\nyarn add @taquito/taquito @taquito/beacon-wallet\nyarn add @dipdup/tzkt-api\n```\n\n\u003e :warning: If you are using last version 5.x of react-script, follow these steps to rewire webpack for all encountered missing libraries : https://github.com/ChainSafe/web3.js#troubleshooting-and-known-issues\n\n\nStart the dev server\n\n```\nyarn run start\n```\n\nOpen your browser at : http://localhost:3000/\nYour app should be running\n\n## Step 2 : Connect / disconnect the wallet\n\nWe will declare 2 React Button components and a display of address and balance while connected\n\nEdit src/App.tsx file\n\n```typescript\nimport { useState } from 'react';\nimport './App.css';\nimport ConnectButton from './ConnectWallet';\nimport { TezosToolkit } from '@taquito/taquito';\nimport DisconnectButton from './DisconnectWallet';\n\nfunction App() {\n\n  const [Tezos, setTezos] = useState\u003cTezosToolkit\u003e(new TezosToolkit(\"https://hangzhounet.tezos.marigold.dev\"));\n  const [wallet, setWallet] = useState\u003cany\u003e(null);\n  const [userAddress, setUserAddress] = useState\u003cstring\u003e(\"\");\n  const [userBalance, setUserBalance] = useState\u003cnumber\u003e(0);\n\n  return (\n    \u003cdiv className=\"App\"\u003e\n      \u003cheader className=\"App-header\"\u003e\n        \u003cp\u003e\n        \n        \u003cConnectButton\n          Tezos={Tezos}\n          setWallet={setWallet}\n          setUserAddress={setUserAddress}\n          setUserBalance={setUserBalance}\n          wallet={wallet}\n        /\u003e\n        \n        \u003cDisconnectButton\n          wallet={wallet}\n          setUserAddress={setUserAddress}\n          setUserBalance={setUserBalance}\n          setWallet={setWallet}\n        /\u003e\n\n        \u003cdiv\u003e\n        I am {userAddress} with {userBalance} Tz\n        \u003c/div\u003e\n\n        \u003c/p\u003e\n\n      \u003c/header\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport default App;\n```\n\nLet's create the 2 missing src component files and put code in it\n\n```\ntouch ConnectWallet.tsx\ntouch DisconnectWallet.tsx\n```\n\nConnectWallet button will create an instance wallet, get user permissions via a popup and then retrieve account information\n\nEdit ConnectWallet.tsx\n\n```typescript\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport { TezosToolkit } from \"@taquito/taquito\";\nimport { BeaconWallet } from \"@taquito/beacon-wallet\";\nimport {\n  NetworkType\n} from \"@airgap/beacon-sdk\";\n\ntype ButtonProps = {\n  Tezos: TezosToolkit;\n  setWallet: Dispatch\u003cSetStateAction\u003cany\u003e\u003e;\n  setUserAddress: Dispatch\u003cSetStateAction\u003cstring\u003e\u003e;\n  setUserBalance: Dispatch\u003cSetStateAction\u003cnumber\u003e\u003e;\n  wallet: BeaconWallet;\n};\n\nconst ConnectButton = ({\n  Tezos,\n  setWallet,\n  setUserAddress,\n  setUserBalance,\n  wallet\n}: ButtonProps): JSX.Element =\u003e {\n\n  const setup = async (userAddress: string): Promise\u003cvoid\u003e =\u003e {\n    setUserAddress(userAddress);\n    // updates balance\n    const balance = await Tezos.tz.getBalance(userAddress);\n    setUserBalance(balance.toNumber());\n  };\n\n  const connectWallet = async (): Promise\u003cvoid\u003e =\u003e {\n    try {\n      if(!wallet) await createWallet();\n      await wallet.requestPermissions({\n        network: {\n          type: NetworkType.HANGZHOUNET,\n          rpcUrl: \"https://hangzhounet.tezos.marigold.dev\"\n        }\n      });\n      // gets user's address\n      const userAddress = await wallet.getPKH();\n      await setup(userAddress);\n    } catch (error) {\n      console.log(error);\n    }\n  };\n\n  const createWallet = async() =\u003e {\n    // creates a wallet instance if not exists\n    if(!wallet){\n      wallet = new BeaconWallet({\n      name: \"training\",\n      preferredNetwork: NetworkType.HANGZHOUNET\n    });}\n    Tezos.setWalletProvider(wallet);\n    setWallet(wallet);\n    // checks if wallet was connected before\n    const activeAccount = await wallet.client.getActiveAccount();\n    if (activeAccount) {\n      const userAddress = await wallet.getPKH();\n      await setup(userAddress);\n    }\n  }\n\n  useEffect(() =\u003e {\n    (async () =\u003e createWallet())();\n  }, []);\n\n  return (\n    \u003cdiv className=\"buttons\"\u003e\n      \u003cbutton className=\"button\" onClick={connectWallet}\u003e\n        \u003cspan\u003e\n          \u003ci className=\"fas fa-wallet\"\u003e\u003c/i\u003e\u0026nbsp; Connect with wallet\n        \u003c/span\u003e\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default ConnectButton;\n```\n\nDisconnectWallet button will clean wallet instance and all linked objects\n\n```typescript\nimport { Dispatch, SetStateAction } from \"react\";\nimport { BeaconWallet } from \"@taquito/beacon-wallet\";\n\ninterface ButtonProps {\n  wallet: BeaconWallet | null;\n  setUserAddress: Dispatch\u003cSetStateAction\u003cstring\u003e\u003e;\n  setUserBalance: Dispatch\u003cSetStateAction\u003cnumber\u003e\u003e;\n  setWallet: Dispatch\u003cSetStateAction\u003cany\u003e\u003e;\n}\n\nconst DisconnectButton = ({\n  wallet,\n  setUserAddress,\n  setUserBalance,\n  setWallet,\n}: ButtonProps): JSX.Element =\u003e {\n  const disconnectWallet = async (): Promise\u003cvoid\u003e =\u003e {\n    setUserAddress(\"\");\n    setUserBalance(0);\n    setWallet(null);\n    console.log(\"disconnecting wallet\");\n    if (wallet) {\n      await wallet.client.removeAllAccounts();\n      await wallet.client.removeAllPeers();\n      await wallet.client.destroy();\n    }\n  };\n\n  return (\n    \u003cdiv className=\"buttons\"\u003e\n      \u003cbutton className=\"button\" onClick={disconnectWallet}\u003e\n        \u003ci className=\"fas fa-times\"\u003e\u003c/i\u003e\u0026nbsp; Disconnect wallet\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default DisconnectButton;\n\n```\n\nSave both file, the dev server should refresh the page\n\n![](https://hackmd.io/_uploads/ryAnV4Pbq.png)\n\n\u003e Note on Temple wallet configuration :\n\u003e Go to your browser plugin and import an account \n\u003e ![](https://hackmd.io/_uploads/ByVDnFnb5.png)\n\u003e Choose the private key, copy/paste that is located here : ~/.tezos-client/secret_keys \n\u003e ![](https://hackmd.io/_uploads/Byv33Y3b5.png)\n\n\n\nOnce Temple is configured well, Click on Connect button\n\nOn the popup, select your Temple wallet, then your account and connect. :warning: Do not forget to stay on the \"Hangzhounet\" testnet\n\n![](https://hackmd.io/_uploads/ryn-HVw-9.png)\n\n:confetti_ball: your are *\"logged\"*\n\nClick on the Disconnect button to logout to test it\n\n## Step 3 : List poke contracts via an indexer\n\nRemember that you deployed your contract previously.\nInstead of querying heavily the rpc node to search where is located your contract and get back some information about it, we can use an indexer. We can consider it as an enriched cache API on top of rpc node. In this example, we will use the tzkt indexer\n\nAdd the library\n\n```\nyarn add @dipdup/tzkt-api\n```\n\nWe will add a button to fetch all similar contracts to the one you deployed, then we display the list\n\nNow, edit App.tsx to add 1 import on top of the file\n\n```typescript\nimport { Contract, ContractsService } from '@dipdup/tzkt-api';\n```\n\nBefore the return , add this section for the fetch\n\n```typescript\n  const contractsService = new ContractsService( {baseUrl: \"https://api.hangzhounet.tzkt.io\" , version : \"\", withCredentials : false});\n  const [contracts, setContracts] = useState\u003cArray\u003cContract\u003e\u003e([]);\n\n  const fetchContracts = () =\u003e {\n    (async () =\u003e {\n     setContracts((await contractsService.getSimilar({address:\"KT1M1sXXUYdLvow9J4tYcDDrYa6aKn3k1NT9\" , includeStorage:true, sort:{desc:\"id\"}})));\n    })();\n  }\n```\n\nOn the return 'html templating' section, add this after the display of the user balance div, add this : \n\n```html\n\u003cbr /\u003e\n\u003cdiv\u003e\n    \u003cbutton onClick={fetchContracts}\u003eFetch contracts\u003c/button\u003e\n    {contracts.map((contract) =\u003e \u003cdiv\u003e{contract.address}\u003c/div\u003e)}\n\u003c/div\u003e\n```\nSave your file and go to the browser. click on Fetch button\n\n![](https://hackmd.io/_uploads/H1oU34Pbc.png)\n\n:confetti_ball:  Congrats ! you are able to list all similar deployed contracts\n\n\n## Step 4 : Poke your contract\n\nAdd some import at the top\n\n```typescript\nimport { TezosToolkit, WalletContract } from '@taquito/taquito';\n```\n\nAdd this new function inside the App function, it will call the entrypoint to poke\n\n```typescript\n  const poke = async (contract : Contract) =\u003e {   \n    let c : WalletContract = await Tezos.wallet.at(\"\"+contract.address);\n    try {\n      const op = await c.methods.default().send();\n      await op.confirmation();\n    } catch (error : any) {\n      console.table(`Error: ${JSON.stringify(error, null, 2)}`);\n    }\n  };\n```\n\n\u003e :warning: Normally we should call `c.methods.poke()` function , but there is a bug while compiling ligo variant with one unique choice, then the `default` is generated instead of having the name of the function. Also be careful because all entrypoints naming are converting to lowercase whatever variant variable name you can have on source file. \n\nThen replace the line displaying the contract address by this one that will add a Poke button\n\n```html\n    {contracts.map((contract) =\u003e \u003cdiv\u003e{contract.address} \u003cbutton onClick={() =\u003epoke(contract)}\u003ePoke\u003c/button\u003e\u003c/div\u003e)}\n```\n\nSave and see the page refreshed, then click on Poke button\n\n![](https://hackmd.io/_uploads/ryk3qSv-5.png)\n\n:confetti_ball:  If you have enough Tz on your wallet for the gas, then it should have successfully call the contract and added you to the list of poke guyz\n\n## Step 5 : Display poke guys\n\nTo verify that on the page, we can display the list of poke guyz directly on the page\n\nReplace again the html contracts line by this one\n\n```html\n\u003ctable\u003e\u003cthead\u003e\u003ctr\u003e\u003cth\u003eaddress\u003c/th\u003e\u003cth\u003epeople\u003c/th\u003e\u003cth\u003eaction\u003c/th\u003e\u003c/tr\u003e\u003c/thead\u003e\u003ctbody\u003e\n    {contracts.map((contract) =\u003e \u003ctr\u003e\u003ctd style={{borderStyle: \"dotted\"}}\u003e{contract.address}\u003c/td\u003e\u003ctd style={{borderStyle: \"dotted\"}}\u003e{contract.storage.join(\", \")}\u003c/td\u003e\u003ctd style={{borderStyle: \"dotted\"}}\u003e\u003cbutton onClick={() =\u003epoke(contract)}\u003ePoke\u003c/button\u003e\u003c/td\u003e\u003c/tr\u003e)}\n    \u003c/tbody\u003e\u003c/table\u003e\n```\n\nContracts are displaying its people now \n\n![](https://hackmd.io/_uploads/HywGr92b9.png)\n\n\u003e :information_source: Wait around few second for blockchain confirmation and click on \"fetch contracts\" to refresh the list\n \n:confetti_ball: Congratulation, you have completed this first dapp training \n\n# :beach_with_umbrella: Conclusion\n\nNow, you are able to create any Smart Contract using Ligo and build a Dapp via Taquito to interact with it\n\nOn next training, you will learn how to call a Smart contract inside a Smart Contract and use the callback, write unit test, etc ...\n\n[:arrow_right: NEXT](https://hackmd.io/8N_Efu5VQWiVbehy9H18Xw)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglottologist%2Ftezos-dapp-training","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglottologist%2Ftezos-dapp-training","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglottologist%2Ftezos-dapp-training/lists"}