Solidity

Solidity

Contract-oriented, high-level language for implementing smart contracts.
React Dev

React Dev

1642475800

How to Integrate React.js with Smart Contracts

In today's video we will learn how to Integrate React.js with Smart Contracts (read / write / events)

React JS is a JavaScript library, commonly used to develop software that is constantly refreshing data on its UI. This technology eliminates the need of reloading the whole screen and also avoids processing every single line of code. React JS allows you to create components actually made with JavaScript; the famous scripting language used to create interactive applications and interfaces. If you’re using an app that is constantly updating its data, then it was probably developed with JavaScript.

Smart contracts are computer programs that are hosted and executed on a blockchain network. Each smart contract consists of code specifying predetermined conditions that, when met, trigger outcomes. By running on a decentralized blockchain instead of a centralized server, smart contracts allow multiple parties to come to a shared result in an accurate, timely, and tamper-proof manner. 

Learn how to: 
- read and display data from smart contracts 
- issue or sign new transactions from React.js 
- listen to events emitted by ethereum smart contract.

0:00:00 - Intro
0:02:00 - ERC20 deploy
0:06:00 - What is ABI?
0:08:00 - Read data from smart contract (ethers)
0:15:00 - Run contract function from React.js app
0:20:10 - Listen to events emitted from contract
0:25:00 - Use same code for other contract (DAI)


In this video, I used ERC20 as an example. You can find code here:
https://codesandbox.io/s/react-write-read-smart-contracts-shtjf 

Subscribe: https://www.youtube.com/c/ArturChmaro/featured 

#react  #ethereum  #solidity 

How to Integrate React.js with Smart Contracts
Crypto  Fan

Crypto Fan

1642474162

How to Trade/sell NFTs Automatically from Solidity Smart Contract

In today's video tutorial we will learn how to trade/sell NFT automatically from Solidity Smart Contract

Solidity is an object-oriented, high-level programming language used to create smart contracts that automate transactions on the blockchain. After being proposed in 2014, the language was developed by contributors to the Ethereum project. The language is primarily used to create smart contracts on the Ethereum blockchain and create smart contracts on other blockchains.

NFT stands for a non-fungible token, which means that hidden in those quirky artworks, there is a totally unique and non-interchangeable unit of data stored on a digital ledger that uses blockchain technology to establish proof of ownership. NFTs are collectable digital assets that hold value, just like how physical art holds value, so do NFTs. 

Subscribe: https://www.youtube.com/c/ArturChmaro/featured 

#solidity  #nft  #ethereum 

How to Trade/sell NFTs Automatically from Solidity Smart Contract
Crypto  Fan

Crypto Fan

1642473078

How to Deploy Smart Contracts using Remix IDE

In this video, We will show you how to deploy smart contracts using Remix IDE (web app for Solidity development). As example, I created a simple contract that can receive and store ether. Owner of the contract can withdraw accumulated funds to any valid public address.

Smart contracts are computer programs that are hosted and executed on a blockchain network. Each smart contract consists of code specifying predetermined conditions that, when met, trigger outcomes. By running on a decentralized blockchain instead of a centralized server, smart contracts allow multiple parties to come to a shared result in an accurate, timely, and tamper-proof manner. 

Remix, more commonly known as Remix IDE, is an open-source Ethereum IDE you can use to write, compile and debug Solidity code. As such, Remix can be a hugely important tool in Web3 and dApps development. In short, if you’re looking for an answer to “what is Remix”, you’ve come to the right place! In the sections that follow, you will learn all you need to know about the basics of Remix. This information will help you decide whether or not it’s the proper tool you should take advantage of. On top of that, we’ll tell you about an amazing platform that enables you to skip time-consuming backend coding and still deploy excellent dApps.

Remix IDE: 
https://remix.ethereum.org/ 

Code from video:
https://gist.github.com/Chmarusso/d8d5349084b57c6f02c32ea30dbed79c 

Solidity official docs: 
https://docs.soliditylang.org/ 

Subscribe: https://www.youtube.com/c/ArturChmaro/featured 
 

#solidity  #ethereum  #smartcontract #remix 

How to Deploy Smart Contracts using Remix IDE

Cree Un Juego Blockchain Con Solidity, Web3 Y Vue.js

En este artículo, veremos el enfoque paso a paso para crear un juego descentralizado usando la cadena de bloques pública de Ethereum usando:

  • Casco de seguridad
  • Solidez
  • Vue.js

Nos centraremos más en el lado de la interfaz en la siguiente parte, pero primero, daré una breve explicación sobre el lado de Solidity y cómo implementarlo en Rinkeby testnet.

Este es mi primer artículo y sentí que falta información sobre Web3 y Vue.js, ya que también necesita atención.

 

Antes de comenzar, quiero dar crédito a buildspace por hacer este proyecto. Lo que agregué es la parte Vue.js. Si eres nuevo en este espacio, ¡no dudes en echarles un vistazo! ¡Tienen las mejores herramientas de aprendizaje y comunidad!

Entonces, antes de comenzar, hablemos de lo que realmente necesitará si recién está comenzando en este espacio:

  • Necesitas instalar MetaMask y habilitar las extensiones en Chrome
  • Conocimientos básicos sobre Metamask
  • Conocimientos básicos sobre Solidez
  • Conocimientos sobre JavaScript y Vue.js.

Lo que construiremos hoy

¡Construiremos un juego basado en blockchain (inspirado en buildspace) donde puedes crear tu personaje y luchar contra el jefe!

Solidez

Para empezar en Solidity, te recomiendo que sigas buildspace .

¡Nuestro contrato inteligente nos permitirá crear personajes, acuñar nuestro personaje seleccionado y luego luchar contra un jefe con él! ¿Simple verdad?

Aquí está nuestro MyEpicGame.solcontrato inteligente:

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

import "./libraries/Base64.sol";

import "hardhat/console.sol";

contract MyEpicGame is ERC721 {
    struct CharacterAttributes {
        uint256 characterIndex;
        string name;
        string imageURI;
        uint256 hp;
        uint256 maxHp;
        uint256 attackDamage;
    }

    struct BigBoss {
        string name;
        string imageURI;
        uint256 hp;
        uint256 maxHp;
        uint256 attackDamage;
    }

    BigBoss public bigBoss;

    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    CharacterAttributes[] defaultCharacters;

    mapping(uint256 => CharacterAttributes) public nftHolderAttributes;

    mapping(address => uint256) public nftHolders;

    event CharacterNFTMinted(
        address sender,
        uint256 tokenId,
        uint256 characterIndex
    );
    event AttackComplete(uint256 newBossHp, uint256 newPlayerHp);

    constructor(
        string[] memory characterNames,
        string[] memory characterImageURIs,
        uint256[] memory characterHp,
        uint256[] memory characterAttackDmg,
        string memory bossName,
        string memory bossImageURI,
        uint256 bossHp,
        uint256 bossAttackDamage
    ) ERC721("Heroes", "HERO") {
        for (uint256 i = 0; i < characterNames.length; i += 1) {
            defaultCharacters.push(
                CharacterAttributes({
                    characterIndex: i,
                    name: characterNames[i],
                    imageURI: characterImageURIs[i],
                    hp: characterHp[i],
                    maxHp: characterHp[i],
                    attackDamage: characterAttackDmg[i]
                })
            );

            CharacterAttributes memory c = defaultCharacters[i];
            console.log(
                "Done initializing %s w/ HP %s, img %s",
                c.name,
                c.hp,
                c.imageURI
            );
        }

        bigBoss = BigBoss({
            name: bossName,
            imageURI: bossImageURI,
            hp: bossHp,
            maxHp: bossHp,
            attackDamage: bossAttackDamage
        });

        console.log(
            "Done initializing boss %s w/ HP %s, img %s",
            bigBoss.name,
            bigBoss.hp,
            bigBoss.imageURI
        );

        _tokenIds.increment();
    }

    function mintCharacterNFT(uint256 _characterIndex) external {
        uint256 newItemId = _tokenIds.current();

        _safeMint(msg.sender, newItemId);

        nftHolderAttributes[newItemId] = CharacterAttributes({
            characterIndex: _characterIndex,
            name: defaultCharacters[_characterIndex].name,
            imageURI: defaultCharacters[_characterIndex].imageURI,
            hp: defaultCharacters[_characterIndex].hp,
            maxHp: defaultCharacters[_characterIndex].hp,
            attackDamage: defaultCharacters[_characterIndex].attackDamage
        });

        console.log(
            "Minted NFT w/ tokenId %s and characterIndex %s",
            newItemId,
            _characterIndex
        );

        nftHolders[msg.sender] = newItemId;

        _tokenIds.increment();
        emit CharacterNFTMinted(msg.sender, newItemId, _characterIndex);
    }

    function attackBoss() public {
        uint256 nftTokenIdOfPlayer = nftHolders[msg.sender];
        CharacterAttributes storage player = nftHolderAttributes[
            nftTokenIdOfPlayer
        ];
        console.log(
            "\nPlayer w/ character %s about to attack. Has %s HP and %s AD",
            player.name,
            player.hp,
            player.attackDamage
        );
        console.log(
            "Boss %s has %s HP and %s AD",
            bigBoss.name,
            bigBoss.hp,
            bigBoss.attackDamage
        );

        require(player.hp > 0, "Error: character must have HP to attack boss.");
        require(bigBoss.hp > 0, "Error: boss must have HP to attack boss.");

        if (bigBoss.hp < player.attackDamage) {
            bigBoss.hp = 0;
        } else {
            bigBoss.hp = bigBoss.hp - player.attackDamage;
        }

        if (player.hp < bigBoss.attackDamage) {
            player.hp = 0;
        } else {
            player.hp = player.hp - bigBoss.attackDamage;
        }

        console.log("Boss attacked player. New player hp: %s\n", player.hp);
        emit AttackComplete(bigBoss.hp, player.hp);
    }

    function checkIfUserHasNFT()
        public
        view
        returns (CharacterAttributes memory)
    {
        uint256 userNftTokenId = nftHolders[msg.sender];
        if (userNftTokenId > 0) {
            return nftHolderAttributes[userNftTokenId];
        } else {
            CharacterAttributes memory emptyStruct;
            return emptyStruct;
        }
    }

    function getAllDefaultCharacters()
        public
        view
        returns (CharacterAttributes[] memory)
    {
        return defaultCharacters;
    }

    function getBigBoss() public view returns (BigBoss memory) {
        return bigBoss;
    }

    function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
    {
        CharacterAttributes memory charAttributes = nftHolderAttributes[
            _tokenId
        ];

        string memory strHp = Strings.toString(charAttributes.hp);
        string memory strMaxHp = Strings.toString(charAttributes.maxHp);
        string memory strAttackDamage = Strings.toString(
            charAttributes.attackDamage
        );

        string memory json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "',
                        charAttributes.name,
                        " -- NFT #: ",
                        Strings.toString(_tokenId),
                        '", "description": "This is an NFT that lets people play in the game Metaverse Slayer!", "image": "',
                        charAttributes.imageURI,
                        '", "attributes": [ { "trait_type": "Health Points", "value": ',
                        strHp,
                        ', "max_value":',
                        strMaxHp,
                        '}, { "trait_type": "Attack Damage", "value": ',
                        strAttackDamage,
                        "} ]}"
                    )
                )
            )
        );

        string memory output = string(
            abi.encodePacked("data:application/json;base64,", json)
        );

        return output;
    }
}

Para el Base64.solarchivo, puede encontrarlo aquí .

Básicamente, esto nos proporciona algunas funciones de ayuda que nos permiten codificar cualquier dato en una cadena Base64, que es una forma estándar de codificar algunos datos en una cadena.

Prueba

Antes de implementar. Deberíamos probar el contrato para asegurarnos de que podemos usarlo.

Cree una nueva carpeta llamada en el directorio raíz. Esta carpeta puede contener pruebas tanto del lado del cliente como de Ethereum.test

Dentro de la testcarpeta, agregue un nuevo archivo JS llamado test.js. Este archivo contendría las pruebas de los contratos en un solo archivo. Puedes crear el tuyo propio, creo un archivo de prueba simple:

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyEpicGame", function () {
  let gameContract;
  before(async () => {
    const gameContractFactory = await ethers.getContractFactory("MyEpicGame");
    gameContract = await gameContractFactory.deploy(
      ["Leo", "Aang", "Pikachu"],
      [
        "<https://i.imgur.com/pKd5Sdk.png>",
        "<https://i.imgur.com/xVu4vFL.png>",
        "<https://i.imgur.com/WMB6g9u.png>",
      ],
      [100, 200, 300],
      [100, 50, 25],
      "Elon Musk",
      "<https://i.imgur.com/AksR0tt.png>",
      10000,
      50
    );
    await gameContract.deployed();
  });
  it("Should have 3 default characters", async () => {
    let characters = await gameContract.getAllDefaultCharacters();
    expect(characters.length).to.equal(3);
  });
  it("Should have a boss", async () => {
		let boss = await gameContract.getBigBoss();
		expect(boss.name).to.equal("Elon Musk");
	});
});

Para ejecutar la prueba:

npx hardhat test

Implementar (a la red de prueba de Rinkeby)

Vamos a crear un nuevo archivo deploy.jsen la carpeta de scripts de nuestro proyecto de casco. Y tener este código en él.

Esto creará 3 personajes predeterminados y un jefe de nuestro constructor.

const main = async () => {
  const gameContractFactory = await hre.ethers.getContractFactory("MyEpicGame");
  const gameContract = await gameContractFactory.deploy(
    ["Leo", "Aang", "Pikachu"],
    [
      "<https://i.imgur.com/pKd5Sdk.png>",
      "<https://i.imgur.com/xVu4vFL.png>",
      "<https://i.imgur.com/u7T87A6.png>",
    ],
    [100, 200, 300],
    [100, 50, 25],
    "Elon Musk",
    "<https://i.imgur.com/AksR0tt.png>",
    10000,
    50
  );
  await gameContract.deployed();
  console.log("Contract deployed to:", gameContract.address);
};
const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};
runMain();

Para implementar el contrato, ejecute este comando:

npx hardhat run scripts/deploy.js --network rinkeby

Y hemos terminado con nuestra parte de Solidez. Ahora tenemos que hacer una interfaz frontend para interactuar con él.

Interfaz Vue.js

No compartiré el CSS aquí, no dude en consultarlo en mi repositorio de GitHub.

Comencemos creando nuestro proyecto:

vue create frontend
cd frontend

Usaremos ethers para nuestras interacciones Web3 y Vuex para nuestra gestión de estado. Aquí se explica cómo instalarlos:

npm install --save vuex ethers

Muy bien, ¡ahora el proyecto está listo para comenzar! Hablemos de los pasos que seguiremos para hacer nuestra aplicación frontend:

  • Conectar la billetera del usuario
  • elige un personaje
  • ataca al jefe

Conecte la billetera

Para que los usuarios interactúen con nuestra aplicación, deben tener Metamask instalado y la red Rinkeby seleccionada. Pero nos ocuparemos de ello en la última parte.

Nuestra App.vueplantilla debería verse así, con un botón de conexión que abrirá un aviso en Metamask para permitir que nuestra aplicación solicite transacciones para que el usuario las acepte:

<template>
  <div class="app" id="app">
    <div class="container mx-auto">
      <div class="header-container">
        <p class="header gradient-text">⚔️ Metaverse Slayer ⚔️</p>
        <p class="sub-text">Team up to protect the Metaverse!</p>
        <div class="connect-wallet-container">
          <img
            src="<https://64.media.tumblr.com/tumblr_mbia5vdmRd1r1mkubo1_500.gifv>"
            alt="Monty Python Gif"
          />
          <button class="cta-button connect-wallet-button" @click="connect">
            Connect Wallet To Get Started
          </button>
        </div>
      </div>
      <div class="footer-container">
        <img
          alt="Twitter Logo"
          class="twitter-logo"
          src="./assets/twitter-logo.svg"
        />
        <a
          class="footer-text"
          :href="twitter_link"
          target="_blank"
          rel="noreferrer"
          >built by @{{ twitter_handle }}</a
        >
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      twitter_handle: "zouln96",
      twitter_link: "<https://twitter.com/zouln96>",
    };
  },
	methods: {
    async connect() {
      await this.$store.dispatch("connect", true);
    },
  },
	async mounted() {
    await this.$store.dispatch("connect", false);
  },
};
</script>

El botón de conexión tiene un evento de clic que enviará una acción a nuestra tienda (Vuex), hablaremos de eso más adelante; por ahora, veamos la estructura de nuestra tienda:

import Vue from "vue";
import Vuex from "vuex";
import { ethers } from "ethers";
import MyEpicGame from "../utils/MyEpicGame.json";
Vue.use(Vuex);
export default new Vuex.Store({
  state: {
    account: null,
    error: null,
    mining: false,
    characterNFT: null,
    characters: [],
    boss: null,
    attackState: null,
    contract_address: "0x91b5483e35EC485C68FF33f0ACfD51a26F3F1EcA",
  },
  getters: {
    account: (state) => state.account,
    error: (state) => state.error,
    mining: (state) => state.mining,
    characterNFT: (state) => state.characterNFT,
    characters: (state) => state.characters,
    boss: (state) => state.boss,
    attackState: (state) => state.attackState,
  },
  mutations: {
    setAccount(state, account) {
      state.account = account;
    },
    setError(state, error) {
      state.error = error;
    },
    setMining(state, mining) {
      state.mining = mining;
    },
    setCharacterNFT(state, characterNFT) {
      state.characterNFT = characterNFT;
    },
    setCharacters(state, characters) {
      state.characters = characters;
    },
    setBoss(state, boss) {
      state.boss = boss;
    },
    setAttackState(state, attackState) {
      state.attackState = attackState;
    },
  },
  actions: {},
});

El objeto de estado tiene los siguientes atributos:

  • account: donde se guardará nuestra cuenta conectada
  • error: para mostrar errores
  • mining: un valor booleano para comprobar si se está minando una transacción
  • characterNFT: donde se guardará nuestro personaje seleccionado
  • characters: donde se guardarán los caracteres predeterminados
  • boss: el jefe que peleará con nuestro personaje
  • attackState: al atacar al jefe, el estado cambia mientras se extrae la transacción
  • contract_address: la dirección que se devolvió cuando implementamos el contrato en la red Rinkeby.

Y no olvide importar MyEpicGame.jsondesde la compilación después de implementar el contrato. Lo necesitaremos para nuestras llamadas web3 con el contrato en la cadena de bloques.

Creamos getters y setters (mutaciones) para los estados. Ahora vayamos a nuestras acciones.

Para empezar, tenemos la connectacción de la que hablábamos antes, la cual te desglosaré ahora:

actions: {	
  async connect({ commit, dispatch }, connect) {
      try {
        const { ethereum } = window;
        if (!ethereum) {
          commit("setError", "Metamask not installed!");
          return;
        }
        if (!(await dispatch("checkIfConnected")) && connect) {
          await dispatch("requestAccess");
        }
        await dispatch("checkNetwork");
      } catch (error) {
        console.log(error);
        commit("setError", "Account request refused.");
      }
    },
    async checkNetwork({ commit, dispatch }) {
      let chainId = await ethereum.request({ method: "eth_chainId" });
      const rinkebyChainId = "0x4";
      if (chainId !== rinkebyChainId) {
        if (!(await dispatch("switchNetwork"))) {
          commit(
            "setError",
            "You are not connected to the Rinkeby Test Network!"
          );
        }
      }
    },
    async switchNetwork() {
      try {
        await ethereum.request({
          method: "wallet_switchEthereumChain",
          params: [{ chainId: "0x4" }],
        });
        return 1;
      } catch (switchError) {
        return 0;
      }
    },
    async checkIfConnected({ commit }) {
      const { ethereum } = window;
      const accounts = await ethereum.request({ method: "eth_accounts" });
      if (accounts.length !== 0) {
        commit("setAccount", accounts[0]);
        return 1;
      } else {
        return 0;
      }
    },
    async requestAccess({ commit }) {
      const { ethereum } = window;
      const accounts = await ethereum.request({
        method: "eth_requestAccounts",
      });
      commit("setAccount", accounts[0]);
    },
}

Primero, verificamos aquí si Metamask está instalado:

const { ethereum } = window;
if (!ethereum) {
  commit("setError", "Metamask not installed!");
  return;
}

Si todo está bien, verificamos si el usuario ya ha otorgado acceso a nuestra aplicación a Metamask, luego solo tenemos que conectar la cuenta, si no, devuelve 0, la cantidad de cuentas encontradas. Eso significa que tendremos que solicitar acceso al usuario:

if (!(await dispatch("checkIfConnected")) && connect) {
	await dispatch("requestAccess");
}

Nota: la connectvariable nos ayuda a saber si se hace clic en un botón o si en realidad será la función montada la que lo está llamando

Después de verificar la red seleccionada, si no es la red Rinkeby, enviamos una solicitud para cambiarla:

await dispatch("checkNetwork");

Una vez que se encuentra la cuenta, asignamos la cuenta a la mutación para guardarla en nuestro estado:

// in checkIfConnected action
commit("setAccount", accounts[0]);

Y eso es todo para nuestra acción de conexión.

Ahora crearemos una acción para obtener los caracteres predeterminados para que nuestro usuario elija de nuestro contrato inteligente:

async getCharacters({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const charactersTxn = await connectedContract.getAllDefaultCharacters();
    const characters = charactersTxn.map((characterData) =>
      transformCharacterData(characterData)
    );
    commit("setCharacters", characters);
  } catch (error) {
    console.log(error);
  }
},

Para llamar a una función desde nuestro contrato, necesitamos buscar el contrato, creando una acción para eso también, y devolverlo. Proporcionamos un proveedor, el contrato abi y el firmante:

async getContract({ state }) {
  try {
    const { ethereum } = window;
    const provider = new ethers.providers.Web3Provider(ethereum);
    const signer = provider.getSigner();
    const connectedContract = new ethers.Contract(
      state.contract_address,
      MyEpicGame.abi,
      signer
    );
    return connectedContract;
  } catch (error) {
    console.log(error);
    console.log("connected contract not found");
    return null;
  }
},

Luego, podemos llamar a la función en nuestro contrato inteligente que devuelve los caracteres predeterminados y mapear cada uno con la ayuda de nuestra función que transforma los datos de los caracteres en un objeto utilizable por JavaScript:

const charactersTxn = await connectedContract.getAllDefaultCharacters();
const characters = charactersTxn.map((characterData) =>
  transformCharacterData(characterData)
);

La transformCharacterDatafunción se agrega encima de la inicialización de Vuex.Store . Transforma el hp, attackDamagede bigNumbera números legibles:

const transformCharacterData = (characterData) => {
  return {
    name: characterData.name,
    imageURI: characterData.imageURI,
    hp: characterData.hp.toNumber(),
    maxHp: characterData.maxHp.toNumber(),
    attackDamage: characterData.attackDamage.toNumber(),
  };
};

Ahora regresemos a nuestro App.vuepara configurar nuestras vistas y crear un componente llamado SelectCharacter.

Modifique nuestro App.vue, de modo que cuando el usuario conecte su billetera, tengamos una cuenta guardada en nuestra tienda y luego pueda elegir el personaje de los valores predeterminados que obtuvimos anteriormente.

Agregue v-ifa nuestro titular div de conexión y agregue nuestro componente de selección de personajes en la vista:

<div class="connect-wallet-container" v-if="!account">
  <img
    src="<https://64.media.tumblr.com/tumblr_mbia5vdmRd1r1mkubo1_500.gifv>"
    alt="Monty Python Gif"
  />
  <button class="cta-button connect-wallet-button" @click="connect">
    Connect Wallet To Get Started
  </button>
</div>
<select-character v-else-if="account" />

Y para la cuenta, en realidad es una variable calculada que se devuelve desde nuestra tienda:

computed: {
  account() {
    return this.$store.getters.account;
  },
}

Llegando a nuestro SelectCharactercomponente:

<template>
  <div class="select-character-container">
    <h2 class="mt-5">Mint Your Hero. Choose wisely.</h2>
    <div v-if="characters.length && !minting" class="character-grid">
      <div
        class="character-item cursor-pointer mt-10"
        :key="character.name"
        v-for="(character, index) in characters"
      >
        <div class="name-container">
          <p>{{ character.name }}</p>
        </div>
        <img :src="character.imageURI" :alt="character.name" />
        <button
          type="button"
          class="character-mint-button"
          @click="mintCharacterNFTAction(index)"
        >
          {{ `Mint ${character.name}` }}
        </button>
      </div>
    </div>
    <div class="loading" v-else>
      <div class="indicator">
        <loading-indicator />
        <p>Minting In Progress...</p>
      </div>
      <img
        src="<https://media2.giphy.com/media/61tYloUgq1eOk/giphy.gif?cid=ecf05e47dg95zbpabxhmhaksvoy8h526f96k4em0ndvx078s&rid=giphy.gif&ct=g>"
        alt="Minting loading indicator"
      />
    </div>
  </div>
</template>

<script>
import LoadingIndicator from "./LoadingIndicator.vue";
export default {
  data() {
    return {
      minting: false,
    };
  },
  components: {
    LoadingIndicator,
  },
  methods: {
    async mintCharacterNFTAction(index) {
      if (this.minting) return;
      this.minting = true;
      await this.$store.dispatch("mintCharacterNFT", index);
      this.minting = false;
    },
  },
  async mounted() {
    this.minting = true;
    await this.$store.dispatch("getCharacters");
    this.minting = false;
  },
  computed: {
    characters() {
      return this.$store.getters.characters;
    },
  },
};
</script>

Una vez que el componente está montado, tenemos que buscar defaultCharactersy mostrarlos en nuestra vista.

Para cada artículo, tenemos un evento de clic que enviará una acción de menta a nuestra tienda llamada mintCharacterNFTsegún el characterIdíndice seleccionado. Agreguemos esta acción a nuestra tienda:

async mintCharacterNFT({ commit, dispatch }, characterId) {
  try {
    const connectedContract = await dispatch("getContract");
    const mintTxn = await connectedContract.mintCharacterNFT(characterId);
    await mintTxn.wait();
  } catch (error) {
    console.log(error);
  }
},

Como antes, llamamos a nuestra función de contrato inteligente que es responsable de acuñar.

Pero hay un problema aquí, ¿no establecimos nuestro personaje acuñado en nuestro estado? No te preocupes, si recuerdas nuestra función en el contrato inteligente, tenemos un evento una vez acuñado el personaje CharacterNFTMinted.

Entonces, lo que tenemos que hacer ahora es escuchar ese evento y establecer el personaje a partir de él. Vamos a crear una acción para configurar nuestros detectores de eventos:

async setupEventListeners({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    if (!connectedContract) return;
    connectedContract.on(
      "CharacterNFTMinted",
      async (from, tokenId, characterIndex) => {
        console.log(
          `CharacterNFTMinted - sender: ${from} tokenId: ${tokenId.toNumber()} characterIndex: ${characterIndex.toNumber()}`
        );
        const characterNFT = await connectedContract.checkIfUserHasNFT();
        console.log(characterNFT);
        commit("setCharacterNFT", transformCharacterData(characterNFT));
        alert(
          `Your NFT is all done -- see it here: <https://testnets.opensea.io/assets/$>{
            state.contract_address
          }/${tokenId.toNumber()}`
        );
      }
    );

  } catch (error) {
    console.log(error);
  }
},

Para escuchar un evento en web3, solo usamos el contract.on("event_name", callback).

Dentro del evento, verificamos el NFT del usuario seleccionado con esta función checkIfUserHasNFTy lo asignamos a nuestro estado. La alerta es solo información adicional si el usuario desea ver el enlace NFT. Entonces, ¿dónde crees que debería llamarse esta acción?

Lo agregaremos a nuestra acción de conexión debajo del checkNetworkdespacho:

await dispatch("setupEventListeners");
await dispatch("fetchNFTMetadata");

Agreguemos también otra acción para verificar si el usuario ya tiene un NFT una vez que accede a nuestra aplicación:

async fetchNFTMetadata({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const txn = await connectedContract.checkIfUserHasNFT();
    if (txn.name) {
      commit("setCharacterNFT", transformCharacterData(txn));
    }
  } catch (error) {
    console.log(error);
  }
},

Esta acción es casi lo mismo que el evento, pero solo verifíquelo una vez que se llame.

Ahora que hemos terminado con nuestra selección de personajes, regresemos App.vuey configuremos nuestra Arena para luchar contra el jefe. Tenemos que modificar nuestro hijo de carácter seleccionado llamado App.vue, si el usuario ya tiene un NFT seleccionado, tenemos que ir directamente a la arena:

<select-character v-else-if="account && !characterNFT" />
<arena v-else-if="account && characterNFT" />

characterNFT variable es la variable calculada como cuenta:

characterNFT() {
  return this.$store.getters.characterNFT;
},

Creamos nuestro Arenacomponente:

<template>
  <div class="arena-container">
    <div class="boss-container" v-if="boss">
      <div :class="`boss-content ${attackState}`">
        <h2>🔥 {{ boss.name }} 🔥</h2>
        <div class="image-content">
          <img :src="boss.imageURI" :alt="`Boss ${boss.name}`" />
          <div class="health-bar">
            <progress :value="boss.hp" :max="boss.maxHp" />
            <p>{{ `${boss.hp} / ${boss.maxHp} HP` }}</p>
          </div>
        </div>
      </div>
      <div class="attack-container">
        <button class="cta-button" @click="attackAction">
          {{ `💥 Attack ${boss.name}` }}
        </button>
        <div class="loading-indicator" v-if="attackState === 'attacking'">
          <LoadingIndicator />
          <p>Attacking ⚔️</p>
        </div>
      </div>
    </div>
    <div class="players-container" v-if="characterNFT">
      <div class="player-container">
        <h2>Your Character</h2>
        <div class="player">
          <div class="image-content">
            <h2>{{ characterNFT.name }}</h2>
            <img
              :src="characterNFT.imageURI"
              :alt="`Character
            ${characterNFT.name}`"
            />
            <div class="health-bar">
              <progress :value="characterNFT.hp" :max="characterNFT.maxHp" />
              <p>{{ `${characterNFT.hp} / ${characterNFT.maxHp} HP` }}</p>
            </div>
          </div>
          <div class="stats">
            <h4>{{ `⚔️ Attack Damage: ${characterNFT.attackDamage}` }}</h4>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import LoadingIndicator from "./LoadingIndicator.vue";
export default {
  components: {
    LoadingIndicator,
  },
  methods: {
    async attackAction() {
      await this.$store.dispatch("attackBoss");
    },
  },
  async mounted() {
    await this.$store.dispatch("fetchBoss");
  },
  computed: {
    boss() {
      return this.$store.getters.boss;
    },
    characterNFT() {
      return this.$store.getters.characterNFT;
    },
    attackState() {
      return this.$store.getters.attackState;
    },
  },
};
</script>

Una vez que este componente está montado, llamamos a una acción para buscar al jefe y otra acción cuando se hace clic en el botón de ataque, y ahí es donde attackStatecambia entre (atacar/golpear):

async fetchBoss({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const bossTxn = await connectedContract.getBigBoss();
    commit("setBoss", transformCharacterData(bossTxn));
  } catch (error) {
    console.log(error);
  }
},
async attackBoss({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    commit("setAttackState", "attacking");
    console.log("Attacking boss...");
    const attackTxn = await connectedContract.attackBoss();
    await attackTxn.wait();
    console.log("attackTxn:", attackTxn);
    commit("setAttackState", "hit");
  } catch (error) {
    console.error("Error attacking boss:", error);
    setAttackState("");
  }
},

Y no olvidemos nuestro attackCompleteevento en nuestra setupEventListenersacción, esto actualiza al jefe y al jugador hp:

connectedContract.on(
  "AttackComplete",
  async (newBossHp, newPlayerHp) => {
    console.log(
      `AttackComplete: Boss Hp: ${newBossHp} Player Hp: ${newPlayerHp}`
    );
    let boss = state.boss;
    boss.hp = newBossHp;
    commit("setBoss", boss);    let character = state.characterNFT;
    character.hp = newPlayerHp;
    commit("setCharacterNFT", character);
  }
);

Puede agregar este componente de indicador de carga para una mejor UX:

<template>
  <div class="lds-ring">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</template><script>
export default {};
</script>

Ahora, ha completado su primer juego web3 usando Vue.js. Puede alojarlo vercelcomo lo hice yo, de forma gratuita.

Aquí está mi aplicación y el repositorio de GitHub para el código fuente completo.

¡ Una vez más, un gran agradecimiento a buildspace por ayudar a hacer este proyecto!

También obtuve este NFT por completar el proyecto:

¡Feliz codificación!

Enlace: https://betterprogramming.pub/create-a-blockchain-game-with-solidity-web3-and-vue-js-c75eed4b49a6

#vuejs  #web3  #solidity 

Cree Un Juego Blockchain Con Solidity, Web3 Y Vue.js
山本  洋介

山本 洋介

1642397640

Solidity、Web3、Vue.jsを使用してブロックチェーンゲームを作成する

この記事では、以下を使用してイーサリアムパブリックブロックチェーンを使用して分散型ゲームを作成するためのステップバイステップのアプローチを見ていきます。

  • ヘルメット
  • 堅牢性
  • Vue.js

次のパートではフロントエンド側に焦点を当てますが、最初に、Solidity側とそれをRinkebyにデプロイする方法について簡単に説明しますtestnet

これは私の最初の記事であり、Web3とVue.jsについても注意が必要なため、情報が不足しているように感じました。

 

始める前に、このプロジェクトを作成したビルドスペースの功績を称えたいと思います。追加したのはVue.jsの部分です。このスペースに不慣れな方は、ぜひチェックしてみてください!彼らは最高の学習ツールとコミュニティを持っています!

したがって、始める前に、このスペースで始めたばかりの場合に本当に必要になるものについて話しましょう。

  • MetaMaskをインストールし、Chromeで拡張機能を有効にする必要があります
  • Metamaskに関する基本的な知識
  • Solidityに関する基本的な知識
  • JavaScriptとVue.jsに関する知識。

今日構築するもの

buildspaceキャラクターをミントしてボスと戦うことができるブロックチェーンベースのゲーム(に触発された)を構築します!

堅牢性

Solidityの初心者の方は、buildspaceをフォローすることをお勧めします。

私たちのスマートコントラクトは、キャラクターを作成し、選択したキャラクターをミントし、それでボスと戦うことを可能にします!簡単でしょ?

これが私たちのMyEpicGame.solスマートコントラクトです:

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

import "./libraries/Base64.sol";

import "hardhat/console.sol";

contract MyEpicGame is ERC721 {
    struct CharacterAttributes {
        uint256 characterIndex;
        string name;
        string imageURI;
        uint256 hp;
        uint256 maxHp;
        uint256 attackDamage;
    }

    struct BigBoss {
        string name;
        string imageURI;
        uint256 hp;
        uint256 maxHp;
        uint256 attackDamage;
    }

    BigBoss public bigBoss;

    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    CharacterAttributes[] defaultCharacters;

    mapping(uint256 => CharacterAttributes) public nftHolderAttributes;

    mapping(address => uint256) public nftHolders;

    event CharacterNFTMinted(
        address sender,
        uint256 tokenId,
        uint256 characterIndex
    );
    event AttackComplete(uint256 newBossHp, uint256 newPlayerHp);

    constructor(
        string[] memory characterNames,
        string[] memory characterImageURIs,
        uint256[] memory characterHp,
        uint256[] memory characterAttackDmg,
        string memory bossName,
        string memory bossImageURI,
        uint256 bossHp,
        uint256 bossAttackDamage
    ) ERC721("Heroes", "HERO") {
        for (uint256 i = 0; i < characterNames.length; i += 1) {
            defaultCharacters.push(
                CharacterAttributes({
                    characterIndex: i,
                    name: characterNames[i],
                    imageURI: characterImageURIs[i],
                    hp: characterHp[i],
                    maxHp: characterHp[i],
                    attackDamage: characterAttackDmg[i]
                })
            );

            CharacterAttributes memory c = defaultCharacters[i];
            console.log(
                "Done initializing %s w/ HP %s, img %s",
                c.name,
                c.hp,
                c.imageURI
            );
        }

        bigBoss = BigBoss({
            name: bossName,
            imageURI: bossImageURI,
            hp: bossHp,
            maxHp: bossHp,
            attackDamage: bossAttackDamage
        });

        console.log(
            "Done initializing boss %s w/ HP %s, img %s",
            bigBoss.name,
            bigBoss.hp,
            bigBoss.imageURI
        );

        _tokenIds.increment();
    }

    function mintCharacterNFT(uint256 _characterIndex) external {
        uint256 newItemId = _tokenIds.current();

        _safeMint(msg.sender, newItemId);

        nftHolderAttributes[newItemId] = CharacterAttributes({
            characterIndex: _characterIndex,
            name: defaultCharacters[_characterIndex].name,
            imageURI: defaultCharacters[_characterIndex].imageURI,
            hp: defaultCharacters[_characterIndex].hp,
            maxHp: defaultCharacters[_characterIndex].hp,
            attackDamage: defaultCharacters[_characterIndex].attackDamage
        });

        console.log(
            "Minted NFT w/ tokenId %s and characterIndex %s",
            newItemId,
            _characterIndex
        );

        nftHolders[msg.sender] = newItemId;

        _tokenIds.increment();
        emit CharacterNFTMinted(msg.sender, newItemId, _characterIndex);
    }

    function attackBoss() public {
        uint256 nftTokenIdOfPlayer = nftHolders[msg.sender];
        CharacterAttributes storage player = nftHolderAttributes[
            nftTokenIdOfPlayer
        ];
        console.log(
            "\nPlayer w/ character %s about to attack. Has %s HP and %s AD",
            player.name,
            player.hp,
            player.attackDamage
        );
        console.log(
            "Boss %s has %s HP and %s AD",
            bigBoss.name,
            bigBoss.hp,
            bigBoss.attackDamage
        );

        require(player.hp > 0, "Error: character must have HP to attack boss.");
        require(bigBoss.hp > 0, "Error: boss must have HP to attack boss.");

        if (bigBoss.hp < player.attackDamage) {
            bigBoss.hp = 0;
        } else {
            bigBoss.hp = bigBoss.hp - player.attackDamage;
        }

        if (player.hp < bigBoss.attackDamage) {
            player.hp = 0;
        } else {
            player.hp = player.hp - bigBoss.attackDamage;
        }

        console.log("Boss attacked player. New player hp: %s\n", player.hp);
        emit AttackComplete(bigBoss.hp, player.hp);
    }

    function checkIfUserHasNFT()
        public
        view
        returns (CharacterAttributes memory)
    {
        uint256 userNftTokenId = nftHolders[msg.sender];
        if (userNftTokenId > 0) {
            return nftHolderAttributes[userNftTokenId];
        } else {
            CharacterAttributes memory emptyStruct;
            return emptyStruct;
        }
    }

    function getAllDefaultCharacters()
        public
        view
        returns (CharacterAttributes[] memory)
    {
        return defaultCharacters;
    }

    function getBigBoss() public view returns (BigBoss memory) {
        return bigBoss;
    }

    function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
    {
        CharacterAttributes memory charAttributes = nftHolderAttributes[
            _tokenId
        ];

        string memory strHp = Strings.toString(charAttributes.hp);
        string memory strMaxHp = Strings.toString(charAttributes.maxHp);
        string memory strAttackDamage = Strings.toString(
            charAttributes.attackDamage
        );

        string memory json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "',
                        charAttributes.name,
                        " -- NFT #: ",
                        Strings.toString(_tokenId),
                        '", "description": "This is an NFT that lets people play in the game Metaverse Slayer!", "image": "',
                        charAttributes.imageURI,
                        '", "attributes": [ { "trait_type": "Health Points", "value": ',
                        strHp,
                        ', "max_value":',
                        strMaxHp,
                        '}, { "trait_type": "Attack Damage", "value": ',
                        strAttackDamage,
                        "} ]}"
                    )
                )
            )
        );

        string memory output = string(
            abi.encodePacked("data:application/json;base64,", json)
        );

        return output;
    }
}

ファイルについては、ここでBase64.sol見つけることができます。

これは基本的に、任意のデータをBase64文字列にエンコードできるようにするいくつかのヘルパー関数を提供します。これは、データの一部を文字列にエンコードするための標準的な方法です。

テスト

デプロイする前。契約をテストして、使用できることを確認する必要があります。

ルートディレクトリにという新しいフォルダを作成します。このフォルダーには、クライアント側とイーサリアムの両方のテストを含めることができます。test

フォルダー内にtest、という名前の新しいJSファイルを追加しますtest.js。このファイルには、1つのファイルに契約テストが含まれます。あなたはあなた自身を作成することができます、私は簡単なテストファイルを作成します:

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyEpicGame", function () {
  let gameContract;
  before(async () => {
    const gameContractFactory = await ethers.getContractFactory("MyEpicGame");
    gameContract = await gameContractFactory.deploy(
      ["Leo", "Aang", "Pikachu"],
      [
        "<https://i.imgur.com/pKd5Sdk.png>",
        "<https://i.imgur.com/xVu4vFL.png>",
        "<https://i.imgur.com/WMB6g9u.png>",
      ],
      [100, 200, 300],
      [100, 50, 25],
      "Elon Musk",
      "<https://i.imgur.com/AksR0tt.png>",
      10000,
      50
    );
    await gameContract.deployed();
  });
  it("Should have 3 default characters", async () => {
    let characters = await gameContract.getAllDefaultCharacters();
    expect(characters.length).to.equal(3);
  });
  it("Should have a boss", async () => {
		let boss = await gameContract.getBigBoss();
		expect(boss.name).to.equal("Elon Musk");
	});
});

テストを実行するには:

npx hardhat test

デプロイ(Rinkebyテストネットワークに)

deploy.jsハードハットプロジェクトのscriptsフォルダーに新しいファイルを作成しましょう。そして、その中にこのコードを入れてください。

これにより、コンストラクターから3つのデフォルトキャラクターとボスが作成されます。

const main = async () => {
  const gameContractFactory = await hre.ethers.getContractFactory("MyEpicGame");
  const gameContract = await gameContractFactory.deploy(
    ["Leo", "Aang", "Pikachu"],
    [
      "<https://i.imgur.com/pKd5Sdk.png>",
      "<https://i.imgur.com/xVu4vFL.png>",
      "<https://i.imgur.com/u7T87A6.png>",
    ],
    [100, 200, 300],
    [100, 50, 25],
    "Elon Musk",
    "<https://i.imgur.com/AksR0tt.png>",
    10000,
    50
  );
  await gameContract.deployed();
  console.log("Contract deployed to:", gameContract.address);
};
const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};
runMain();

コントラクトをデプロイするには、次のコマンドを実行します。

npx hardhat run scripts/deploy.js --network rinkeby

そして、Solidityの部分は終わりです。次に、フロントエンドインターフェイスを作成して対話する必要があります。

フロントエンドVue.js

ここではCSSを共有しません。GitHubリポジトリで自由に確認してください。

プロジェクトを作成することから始めましょう:

vue create frontend
cd frontend

Web3の相互作用にはエーテルを使用し、状態管理にはVuexを使用します。それらをインストールする方法は次のとおりです。

npm install --save vuex ethers

これで、プロジェクトを開始する準備が整いました。フロントエンドアプリを作成するために実行する手順について説明しましょう。

  • ユーザーのウォレットを接続します
  • キャラクターを選ぶ
  • 上司を攻撃する

ウォレットを接続する

ユーザーがアプリを操作するには、Metamaskがインストールされ、Rinkebyネットワークが選択されている必要があります。しかし、最後の部分でそれを処理します。

テンプレートは次のようになりますApp.vue。接続ボタンを使用すると、メタマスクでプロンプトが開き、アプリがユーザーが受け入れるトランザクションを要求できるようになります。

<template>
  <div class="app" id="app">
    <div class="container mx-auto">
      <div class="header-container">
        <p class="header gradient-text">⚔️ Metaverse Slayer ⚔️</p>
        <p class="sub-text">Team up to protect the Metaverse!</p>
        <div class="connect-wallet-container">
          <img
            src="<https://64.media.tumblr.com/tumblr_mbia5vdmRd1r1mkubo1_500.gifv>"
            alt="Monty Python Gif"
          />
          <button class="cta-button connect-wallet-button" @click="connect">
            Connect Wallet To Get Started
          </button>
        </div>
      </div>
      <div class="footer-container">
        <img
          alt="Twitter Logo"
          class="twitter-logo"
          src="./assets/twitter-logo.svg"
        />
        <a
          class="footer-text"
          :href="twitter_link"
          target="_blank"
          rel="noreferrer"
          >built by @{{ twitter_handle }}</a
        >
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      twitter_handle: "zouln96",
      twitter_link: "<https://twitter.com/zouln96>",
    };
  },
	methods: {
    async connect() {
      await this.$store.dispatch("connect", true);
    },
  },
	async mounted() {
    await this.$store.dispatch("connect", false);
  },
};
</script>

接続ボタンには、ストア(Vuex)にアクションをディスパッチするクリックイベントがあります。これについては後で説明します。ここでは、ストアの構造を見てみましょう。

import Vue from "vue";
import Vuex from "vuex";
import { ethers } from "ethers";
import MyEpicGame from "../utils/MyEpicGame.json";
Vue.use(Vuex);
export default new Vuex.Store({
  state: {
    account: null,
    error: null,
    mining: false,
    characterNFT: null,
    characters: [],
    boss: null,
    attackState: null,
    contract_address: "0x91b5483e35EC485C68FF33f0ACfD51a26F3F1EcA",
  },
  getters: {
    account: (state) => state.account,
    error: (state) => state.error,
    mining: (state) => state.mining,
    characterNFT: (state) => state.characterNFT,
    characters: (state) => state.characters,
    boss: (state) => state.boss,
    attackState: (state) => state.attackState,
  },
  mutations: {
    setAccount(state, account) {
      state.account = account;
    },
    setError(state, error) {
      state.error = error;
    },
    setMining(state, mining) {
      state.mining = mining;
    },
    setCharacterNFT(state, characterNFT) {
      state.characterNFT = characterNFT;
    },
    setCharacters(state, characters) {
      state.characters = characters;
    },
    setBoss(state, boss) {
      state.boss = boss;
    },
    setAttackState(state, attackState) {
      state.attackState = attackState;
    },
  },
  actions: {},
});

状態オブジェクトには、次の属性があります。

  • account:接続されたアカウントが保存される場所
  • error:エラーを表示する
  • mining:トランザクションがマイニングされているかどうかを確認するブール値
  • characterNFT:選択した文字が保存される場所
  • characters:デフォルトの文字が保存される場所
  • boss:私たちのキャラクターと戦うボス
  • attackState:ボスを攻撃すると、トランザクションがマイニングされている間に状態が変化します
  • contract_address:契約をRinkebyネットワークにデプロイしたときに返されたアドレス。

MyEpicGame.jsonまた、コントラクトをデプロイした後、ビルドからインポートすることを忘れないでください。ブロックチェーン内のコントラクトを使用したweb3呼び出しに必要になります。

州のゲッターとセッター(ミューテーション)を作成しました。それでは、私たちの行動に取り掛かりましょう。

まず、前に説明したconnectアクションがあります。これについては、次のように説明します。

actions: {	
  async connect({ commit, dispatch }, connect) {
      try {
        const { ethereum } = window;
        if (!ethereum) {
          commit("setError", "Metamask not installed!");
          return;
        }
        if (!(await dispatch("checkIfConnected")) && connect) {
          await dispatch("requestAccess");
        }
        await dispatch("checkNetwork");
      } catch (error) {
        console.log(error);
        commit("setError", "Account request refused.");
      }
    },
    async checkNetwork({ commit, dispatch }) {
      let chainId = await ethereum.request({ method: "eth_chainId" });
      const rinkebyChainId = "0x4";
      if (chainId !== rinkebyChainId) {
        if (!(await dispatch("switchNetwork"))) {
          commit(
            "setError",
            "You are not connected to the Rinkeby Test Network!"
          );
        }
      }
    },
    async switchNetwork() {
      try {
        await ethereum.request({
          method: "wallet_switchEthereumChain",
          params: [{ chainId: "0x4" }],
        });
        return 1;
      } catch (switchError) {
        return 0;
      }
    },
    async checkIfConnected({ commit }) {
      const { ethereum } = window;
      const accounts = await ethereum.request({ method: "eth_accounts" });
      if (accounts.length !== 0) {
        commit("setAccount", accounts[0]);
        return 1;
      } else {
        return 0;
      }
    },
    async requestAccess({ commit }) {
      const { ethereum } = window;
      const accounts = await ethereum.request({
        method: "eth_requestAccounts",
      });
      commit("setAccount", accounts[0]);
    },
}

まず、Metamaskがインストールされているかどうかをここで確認します。

const { ethereum } = window;
if (!ethereum) {
  commit("setError", "Metamask not installed!");
  return;
}

すべて問題がない場合は、ユーザーがアプリにMetamaskへのアクセスを既に許可しているかどうかを確認し、アカウントを接続する必要があります。許可されていない場合は、見つかったアカウントの数である0が返されます。つまり、ユーザーにアクセスを要求する必要があります。

if (!(await dispatch("checkIfConnected")) && connect) {
	await dispatch("requestAccess");
}

注:connect変数は、クリックされたボタンであるか、実際にそれを呼び出しているマウントされた関数であるかを知るのに役立ちます

選択したネットワークを確認した後、それがRinkebyネットワークでない場合は、変更要求を送信します。

await dispatch("checkNetwork");

アカウントが見つかったら、アカウントをミューテーションにコミットして、次の状態で保存します。

// in checkIfConnected action
commit("setAccount", accounts[0]);

接続アクションは以上です。

次に、ユーザーがスマートコントラクトから選択できるデフォルトの文字を取得するアクションを作成します。

async getCharacters({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const charactersTxn = await connectedContract.getAllDefaultCharacters();
    const characters = charactersTxn.map((characterData) =>
      transformCharacterData(characterData)
    );
    commit("setCharacters", characters);
  } catch (error) {
    console.log(error);
  }
},

コントラクトから関数を呼び出すには、そのためのアクションも作成してコントラクトをフェッチし、それを返す必要があります。プロバイダー、契約abi、および署名者を提供します。

async getContract({ state }) {
  try {
    const { ethereum } = window;
    const provider = new ethers.providers.Web3Provider(ethereum);
    const signer = provider.getSigner();
    const connectedContract = new ethers.Contract(
      state.contract_address,
      MyEpicGame.abi,
      signer
    );
    return connectedContract;
  } catch (error) {
    console.log(error);
    console.log("connected contract not found");
    return null;
  }
},

次に、デフォルトの文字を返すスマートコントラクトの関数を呼び出し、文字データをJavaScriptで使用可能なオブジェクトに変換する関数を使用して、それぞれにマップします。

const charactersTxn = await connectedContract.getAllDefaultCharacters();
const characters = charactersTxn.map((characterData) =>
  transformCharacterData(characterData)
);

この関数は、 Vuex.StoretransformCharacterDataの初期化の上に追加されます。を読み取り可能な数値に変換します。hpattackDamagebigNumber

const transformCharacterData = (characterData) => {
  return {
    name: characterData.name,
    imageURI: characterData.imageURI,
    hp: characterData.hp.toNumber(),
    maxHp: characterData.maxHp.toNumber(),
    attackDamage: characterData.attackDamage.toNumber(),
  };
};

App.vue次に、ビューに戻ってビューを設定し、と呼ばれるコンポーネントを作成しましょうSelectCharacter

App.vueユーザーがウォレットに接続すると、ストアにアカウントが保存され、以前に取得したデフォルトからキャラクターを選択できるように、を変更します。

接続divホルダーにを追加v-ifし、ビューに文字選択コンポーネントを追加します。

<div class="connect-wallet-container" v-if="!account">
  <img
    src="<https://64.media.tumblr.com/tumblr_mbia5vdmRd1r1mkubo1_500.gifv>"
    alt="Monty Python Gif"
  />
  <button class="cta-button connect-wallet-button" @click="connect">
    Connect Wallet To Get Started
  </button>
</div>
<select-character v-else-if="account" />

アカウントの場合、実際にはストアから返されるのは計算変数です。

computed: {
  account() {
    return this.$store.getters.account;
  },
}

SelectCharacter私たちのコンポーネントに来る:

<template>
  <div class="select-character-container">
    <h2 class="mt-5">Mint Your Hero. Choose wisely.</h2>
    <div v-if="characters.length && !minting" class="character-grid">
      <div
        class="character-item cursor-pointer mt-10"
        :key="character.name"
        v-for="(character, index) in characters"
      >
        <div class="name-container">
          <p>{{ character.name }}</p>
        </div>
        <img :src="character.imageURI" :alt="character.name" />
        <button
          type="button"
          class="character-mint-button"
          @click="mintCharacterNFTAction(index)"
        >
          {{ `Mint ${character.name}` }}
        </button>
      </div>
    </div>
    <div class="loading" v-else>
      <div class="indicator">
        <loading-indicator />
        <p>Minting In Progress...</p>
      </div>
      <img
        src="<https://media2.giphy.com/media/61tYloUgq1eOk/giphy.gif?cid=ecf05e47dg95zbpabxhmhaksvoy8h526f96k4em0ndvx078s&rid=giphy.gif&ct=g>"
        alt="Minting loading indicator"
      />
    </div>
  </div>
</template>

<script>
import LoadingIndicator from "./LoadingIndicator.vue";
export default {
  data() {
    return {
      minting: false,
    };
  },
  components: {
    LoadingIndicator,
  },
  methods: {
    async mintCharacterNFTAction(index) {
      if (this.minting) return;
      this.minting = true;
      await this.$store.dispatch("mintCharacterNFT", index);
      this.minting = false;
    },
  },
  async mounted() {
    this.minting = true;
    await this.$store.dispatch("getCharacters");
    this.minting = false;
  },
  computed: {
    characters() {
      return this.$store.getters.characters;
    },
  },
};
</script>

コンポーネントがマウントされたら、をフェッチdefaultCharactersしてビューに表示する必要があります。

アイテムごとに、選択したまたはインデックスmintCharacterNFTに基づいて呼び出されるミントアクションをストアにディスパッチするクリックイベントがあります。characterIdこのアクションをストアに追加しましょう:

async mintCharacterNFT({ commit, dispatch }, characterId) {
  try {
    const connectedContract = await dispatch("getContract");
    const mintTxn = await connectedContract.mintCharacterNFT(characterId);
    await mintTxn.wait();
  } catch (error) {
    console.log(error);
  }
},

前と同じように、ミンティングを担当するスマートコントラクト関数を呼び出します。

しかし、ここに問題があります、私たちは私たちのミントキャラクターを私たちの状態に設定しませんでしたか?スマートコントラクトでの機能を覚えていれば、キャラクターがミントされたらイベントが発生しますので、ご安心くださいCharacterNFTMinted

ですから、私たちが今しなければならないことは、そのイベントを聞いて、そこからキャラクターを設定することです。イベントリスナーを設定するアクションを作成しましょう。

async setupEventListeners({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    if (!connectedContract) return;
    connectedContract.on(
      "CharacterNFTMinted",
      async (from, tokenId, characterIndex) => {
        console.log(
          `CharacterNFTMinted - sender: ${from} tokenId: ${tokenId.toNumber()} characterIndex: ${characterIndex.toNumber()}`
        );
        const characterNFT = await connectedContract.checkIfUserHasNFT();
        console.log(characterNFT);
        commit("setCharacterNFT", transformCharacterData(characterNFT));
        alert(
          `Your NFT is all done -- see it here: <https://testnets.opensea.io/assets/$>{
            state.contract_address
          }/${tokenId.toNumber()}`
        );
      }
    );

  } catch (error) {
    console.log(error);
  }
},

web3でイベントを聞くには、を使用しcontract.on("event_name", callback)ます。

イベント内で、この関数で選択されたユーザーNFTをチェックし、checkIfUserHasNFTそれを自分の状態にコミットします。ユーザーがNFTリンクを表示したい場合、アラートは単なる追加情報です。では、このアクションはどこで呼び出されるべきだと思いますか?

checkNetworkディスパッチの下の接続アクションに追加します。

await dispatch("setupEventListeners");
await dispatch("fetchNFTMetadata");

また、ユーザーがアプリにアクセスしたときに、ユーザーがすでにNFTを持っているかどうかを確認する別のアクションを追加しましょう。

async fetchNFTMetadata({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const txn = await connectedContract.checkIfUserHasNFT();
    if (txn.name) {
      commit("setCharacterNFT", transformCharacterData(txn));
    }
  } catch (error) {
    console.log(error);
  }
},

このアクションはイベントとほとんど同じですが、呼び出されたときにのみチェックしてください。

これでキャラクターの選択が完了しました。戻ってApp.vue、ボスと戦うためにアリーナをセットアップしましょう。で呼び出されたselect-characterの子を変更するApp.vue必要があります。ユーザーがすでにNFTを選択している場合は、アリーナに直接移動する必要があります。

<select-character v-else-if="account && !characterNFT" />
<arena v-else-if="account && characterNFT" />

characterNFT variableは、accountのような計算変数です。

characterNFT() {
  return this.$store.getters.characterNFT;
},

コンポーネントを作成しましょうArena

<template>
  <div class="arena-container">
    <div class="boss-container" v-if="boss">
      <div :class="`boss-content ${attackState}`">
        <h2>🔥 {{ boss.name }} 🔥</h2>
        <div class="image-content">
          <img :src="boss.imageURI" :alt="`Boss ${boss.name}`" />
          <div class="health-bar">
            <progress :value="boss.hp" :max="boss.maxHp" />
            <p>{{ `${boss.hp} / ${boss.maxHp} HP` }}</p>
          </div>
        </div>
      </div>
      <div class="attack-container">
        <button class="cta-button" @click="attackAction">
          {{ `💥 Attack ${boss.name}` }}
        </button>
        <div class="loading-indicator" v-if="attackState === 'attacking'">
          <LoadingIndicator />
          <p>Attacking ⚔️</p>
        </div>
      </div>
    </div>
    <div class="players-container" v-if="characterNFT">
      <div class="player-container">
        <h2>Your Character</h2>
        <div class="player">
          <div class="image-content">
            <h2>{{ characterNFT.name }}</h2>
            <img
              :src="characterNFT.imageURI"
              :alt="`Character
            ${characterNFT.name}`"
            />
            <div class="health-bar">
              <progress :value="characterNFT.hp" :max="characterNFT.maxHp" />
              <p>{{ `${characterNFT.hp} / ${characterNFT.maxHp} HP` }}</p>
            </div>
          </div>
          <div class="stats">
            <h4>{{ `⚔️ Attack Damage: ${characterNFT.attackDamage}` }}</h4>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import LoadingIndicator from "./LoadingIndicator.vue";
export default {
  components: {
    LoadingIndicator,
  },
  methods: {
    async attackAction() {
      await this.$store.dispatch("attackBoss");
    },
  },
  async mounted() {
    await this.$store.dispatch("fetchBoss");
  },
  computed: {
    boss() {
      return this.$store.getters.boss;
    },
    characterNFT() {
      return this.$store.getters.characterNFT;
    },
    attackState() {
      return this.$store.getters.attackState;
    },
  },
};
</script>

このコンポーネントがマウントされると、ボスをフェッチするアクションと、攻撃ボタンがクリックされたときに別のアクションを呼び出します。ここで、attackState(攻撃/ヒット)の間で変更が行われます。

async fetchBoss({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    const bossTxn = await connectedContract.getBigBoss();
    commit("setBoss", transformCharacterData(bossTxn));
  } catch (error) {
    console.log(error);
  }
},
async attackBoss({ state, commit, dispatch }) {
  try {
    const connectedContract = await dispatch("getContract");
    commit("setAttackState", "attacking");
    console.log("Attacking boss...");
    const attackTxn = await connectedContract.attackBoss();
    await attackTxn.wait();
    console.log("attackTxn:", attackTxn);
    commit("setAttackState", "hit");
  } catch (error) {
    console.error("Error attacking boss:", error);
    setAttackState("");
  }
},

attackCompleteそして、私たちの行動の中で私たちのイベントを忘れないでくださいsetupEventListeners、これは上司とプレーヤーを更新しますhp

connectedContract.on(
  "AttackComplete",
  async (newBossHp, newPlayerHp) => {
    console.log(
      `AttackComplete: Boss Hp: ${newBossHp} Player Hp: ${newPlayerHp}`
    );
    let boss = state.boss;
    boss.hp = newBossHp;
    commit("setBoss", boss);    let character = state.characterNFT;
    character.hp = newPlayerHp;
    commit("setCharacterNFT", character);
  }
);

この読み込みインジケーターコンポーネントを追加して、UXを向上させることができます。

<template>
  <div class="lds-ring">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</template><script>
export default {};
</script>

これで、Vue.jsを使用して最初のweb3ゲームが完了しました。vercel私と同じように無料でホストできます。

これが私のアプリと完全なソースコードのGitHubリポジトリです。

このプロジェクトの作成を支援するためのビルドスペースへの大きな叫び声!

プロジェクトを完了するために、このNFTも入手しました。

ハッピーコーディング!

リンク:https ://betterprogramming.pub/create-a-blockchain-game-with-solidity-web3-and-vue-js-c75eed4b49a6

#vuejs  #web3  #solidity 

Solidity、Web3、Vue.jsを使用してブロックチェーンゲームを作成する
Hong  Nhung

Hong Nhung

1642123641

Ethereum2.0 vs Polkadot vs Solana | Đâu mới là dự án tốt nhất?

Video đánh giá, phân tích và so sánh về tiềm năng của 3 dựa án Solana, Polkadot và Ethereum2.0 cung cấp cho mọi người những cái nhìn khách quan, và kiến thức sơ lược về các dự án này.

00:00 Giới thiệu 
01:52 Ethereum2.0
05:14 Tốc độ
10:00 Chức năng
17:40 Tiềm năng

#blockchain #solidity #smartcontract #polkadot #ethereum #solana 

Ethereum2.0 vs Polkadot vs Solana | Đâu mới là dự án tốt nhất?

How to Build Your Own ERC20 Token with Brownie, Python and Solidity

Build your own ERC20 token using Brownie, Python, and Solidity.

Using Brownie, Python, and Solidity, create a token that can be viewed, bought, and sold on the blockchain

The Ethereum and blockchain ecosystem as a whole do commerce by using ERC20 tokens. Anyone can create a token and use it as a cryptocurrency. We are going to be deploying an ERC20 token using Python, Brownie, and Solidity in this tutorial. You don’t need to be that familiar with any of these for this tutorial.

Github: https://github.com/PatrickAlphaC/erc20-brownie 
ERC20 Standard: https://ethereum.org/en/developers/docs/standards/tokens/erc-20/ 
Brownie: https://github.com/eth-brownie/brownie 
 

#blockchain #brownie #python #solidity #token #erc20 

How to Build Your Own ERC20 Token with Brownie, Python and Solidity
Blockchain Dev

Blockchain Dev

1641962338

Generate Random NFT ImageURIs and Metadata on-chain using SVGs

How to make NFT Art with On-Chain Metadata | FULL HARDHART / JS TUTORIAL! (w/ Polygon & Opensea)

We explore the world of using SVGs to generate random NFT ImageURIs and Metadata 100% on-chain. In combination with Chainlink VRF to create randomness and true scarcity. We use Hardhat, Javascript, and Solidity. These customized smart contracts were deployed to the Polygon chain, so you can see exactly how we did this!

0:00 | Intro
3:08 | Quickstart & Install Requirements
9:52 | View Static SVG Deployed NFT on Opensea
12:15 | View RandomSVG on Opensea
13:18 | Static SVG NFT Setup
16:25 | SVGNFT.sol & OpenZeppelin
21:19 | Creating an NFT and SVG Intro
28:10 | svgToImageURI (Base64 Encoding)
35:35 | formatTokenURI
44:10 | 01_deploy_svgnft.js
1:03:13 | Deploying to Rinkeby Testnet
1:14:50 | Viewing on Opensea

⛵️⛵️⛵️⛵️⛵️⛵️⛵️⛵️⛵️⛵️⛵️
1:16:00 | How can we make this WAY better?
🎲🎲🎲🎲🎲🎲🎲🎲🎲🎲🎲

1:18:32 | RandomSVG.sol
1:20:05 | Gameplan for Random SVGs
1:22:25 | Create Function & Chainlink VRF
1:38:40 | FulfillRandomness
1:43:00 | finishMint
1:49:29 | generateSvg
2:04:30 | generatePath
2:08:45 | generatePathCommand
2:12:35 | Grabbing the final functions
2:14:30 | 02_deploy_randomSVG.js
2:18:00 | Mocking Contracts
2:28:00 | Finishing the script
2:51:21 | Deploying to Rinkeby (& Opensea)

⬆️⬆️⬆️Level up AGAIN??⬆️⬆️⬆️
We also talk about minting 10,000!

2:55:15 | Deploy to Polygon Mainnet & Set Price
3:09:00 | End

💻💻💻💻💻💻
Code: https://github.com/PatrickAlphaC/all-on-chain-generated-nft 
💻💻💻💻💻💻

⛵️Opensea Example on Polygon: https://opensea.io/assets/matic/0x291ff90b9c410f56e047599bfee6b585c0c484d7/2

#nft #blockchain #javascript #nonfungibletoken #polygon #opensea #solidity #svg

Generate Random NFT ImageURIs and Metadata on-chain using SVGs
Hong  Nhung

Hong Nhung

1641961784

Học Web3.js từ A-Z: LoadProvider - Lấy và sử dụng dữ liệu từ Web3

Mastering Web3 | LoadProvider - Lấy và sử dụng dữ liệu từ Web3

Khoá học cung cấp toàn bộ kiến thức Web3js qua thực hành để mọi người có thể thực hiện vô các dự án của các bạn một cách hiệu quả hơn, Dự án được xây dựng trên Nextjs và Javascript, ứng dụng React và React Hook để tối ưu hoá code.

Source code: https://github.com/vugomars/web3-project-course/commit/9b0f1da5d85ac3d0977c6da31f75ae2fd60becdc 

#blockchain #solidity #smartcontract #web3 #react

Học Web3.js từ A-Z: LoadProvider - Lấy và sử dụng dữ liệu từ Web3
Hong  Nhung

Hong Nhung

1641961605

Học Web3.js từ A-Z: Web3 Provider - Sử dụng Hook | useContext

Mastering Web3 | Web3 Provider - Sử dụng Hook | useContext

Khoá học cung cấp toàn bộ kiến thức Web3js qua thực hành để mọi người có thể thực hiện vô các dự án của các bạn một cách hiệu quả hơn, Dự án được xây dựng trên Nextjs và Javascript, ứng dụng React và React Hook để tối ưu hoá code.

source code: https://github.com/vugomars/web3-project-course/commit/5e2b1f12588549844253c218de3ba3cc09c62e2c 

#blockchain #solidity #smartcontract #react #web3

Học Web3.js từ A-Z: Web3 Provider - Sử dụng Hook | useContext
Blockchain Dev

Blockchain Dev

1641952816

How to Create and Deploy an ERC-721 (NFT) with Solidity

Deploy an Advanced NFT in Solidity

A Glorious Guide to NFTs (ERC721) - deploying a simple NFT

What is an NFT (Non-Fungible Token)? What is the ERC-721 standard? What is this digital art stuff? We are going to use Openzepplin contracts to build a simple NFT that mom can be proud of :)

We use @Chainlink VRF to deploy a random breed of a dog, creating cryptographically guaranteed scarcity!

Code for this tutorial: https://github.com/PatrickAlphaC/nft-mix 

#nft #blockchain #solidity #nonfungibletoken

 

How to Create and Deploy an ERC-721 (NFT) with Solidity
Blockchain Dev

Blockchain Dev

1641952201

How to Build, Deploy, and Sell Your NFT on the OpenSea Marketplace

How to Make an NFT and Render it on the OpenSea Marketplace

In this article, I'll show you how to make an NFT without software engineering skills. Then we will learn how to make unlimited customizable NFTs with Brownie, Python, and Chainlink. And we'll see how to render and sell our creation on the OpenSea NFT marketplace.

If you're looking for a tutorial that uses Truffle, JavaScript, and fun medieval characters, check out how to Build, Deploy, and Sell your NFT here.

What is an NFT?

NFTs (Non-Fungible Tokens) can be summed up with one word: "unique". These are smart contracts deployed on a blockchain that represent something unique.

ERC20 vs ERC721

NFTs are a blockchain token standard similar to the ERC20, like AAVE, SNX, and LINK (technically a ERC677). ERC20s are "fungible" tokens, which means “replaceable” or “interchangeable.”

For example, your dollar bill is going to be worth $1 no matter what dollar bill you use. The serial number on the dollar bill might be different, but the bills are interchangeable and they’ll be worth $1 no matter what.

NFTs, on the other hand, are "non-fungible", and they follow their own token standard, the ERC721. For example, the Mona Lisa is "non-fungible". Even though someone can make a copy of it, there will always only be one Mona Lisa. If the Mona Lisa was created on a blockchain, it would be an NFT.

Make an NFT

Original Image from Wikipedia

What are NFTs for?

NFTs provide value to creators, artists, game designers and more by having a permanent history of deployment stored on-chain.

You'll always know who created the NFT, who owned the NFT, where it came from, and more, giving them a lot of value over traditional art. In traditional art, it can be tricky to understand what a "fake" is, whereas on-chain the history is easily traceable.

And since smart contracts and NFTs are 100% programmable, NFTs can also have added built-in royalties and any other functionality. Compensating artists has always been an issue, since often times an artist's work is spread around without any attribution.

More and more artists and engineers are jumping on this massive value add, because it's finally a great way for artists to be compensated for their work. And more than just that, NFTs are a fun way to show off your creativity and become a collector in a digital world.

The Value of NFTs

NFTs have come a long way, and we keep seeing record breaking NFT sales, like "Everydays: The First 5,000 Days” selling for $69.3 million.

Make an NFT

Image from Twitter

So there is a lot of value here, and it's also a fun, dynamic, and engaging way to create art in the digital world and learn about smart contract creation. So now I'll teach you everything you need to know about making NFTs.

How to Make an NFT

What we are not going to cover

Now, the easiest way to make an NFT is just to go to a platform like Opensea, Rarible, or Mintible and follow their step-by-step guide to deploying on their platform.

You can 100% take this route, however you could be bound to the platform, and you are shoehorned into the functionality the platform has. You can't achieve the unlimited customization, or really utilize any of the advantages NFTs have. But if you're a beginner software engineer, or not very technical, this is the route for you.

If you're looking to become a stronger software engineer, learn some solidity, and have the power to create something with unlimited creativity, then read on!

If you're new to solidity, don't worry, we will go over the basics there as well.

How to Make an NFT with Unlimited Customization

I'm going to get you jump started with this NFT Brownie Mix. This is a working repo with a lot of boilerplate code.

Prerequisites

We need a few things installed to get started:

If you're unfamiliar with Metamask, you can follow this tutorial to get it set up.

Rinkeby Testnet ETH and LINK

We will also be working on the Rinkeby Ethereum testnet, so we will be deploying our contracts to a real blockchain, for free!

Testnets are great ways to test how our smart contracts behave in the real world. We need Rinkeby ETH and Rinkeby LINK, which we can get for free from the links to the latest faucets from the Chainlink documentation.

We will also need to add the rinkeby LINK token to our metamask, which we can do by following the acquire LINK documentation.

If you're still confused, you can following along with this video, just be sure to use Rinkeby instead of Ropsten.

When working with a smart contract platform like Ethereum, we need to pay a little bit of ETH, and when getting data from off-chain, we have to pay a little bit of LINK. This is why we need the testnet LINK and ETH.

Awesome, let's dive in. This is the NFT we are going to deploy to OpenSea.

Quickstart

git clone https://github.com/PatrickAlphaC/nft-mix
cd nft-mix

Awesome! Now we need to install the ganache-cli and eth-brownie.

pip install eth-brownie
npm install -g ganache-cli

Now we can set our environment variables. If you're unfamiliar with environment variables, you can just add them into your .env file, and then run:

source .env

A sample .env should be in the repo you just cloned with the environment variables commented out. Uncomment them to use them!

You'll need a WEB3_INFURA_PROJECT_ID and a PRIVATE_KEY . The WEB3_INFURA_PROJECT_ID can be found be signing up for a free Infura account. This will give us a way to send transactions to the blockchain.

We will also need a private key, which you can get from your Metamask. Hit the 3 little dots, and click Account Details and Export Private Key. Please do NOT share this key with anyone if you put real money in it!

export PRIVATE_KEY=YOUR_KEY_HERE
export WEB3_INFURA_PROJECT_ID=YOUR_PROJECT_ID_HERE

.env

Now we can deploy our NFT contract and create our first collectible with the following two commands.

brownie run scripts/simple_collectible/deploy_simple.py --network rinkeby
brownie run scripts/simple_collectible/create_collectible.py --network rinkeby

The first script deploys our NFT contract to the Rinkeby blockchain, and the second one creates our first collectible.

You've just deployed your first smart contract!

It doesn't do much at all, but don't worry – I'll show you how to render it on OpenSea in the advanced part of this tutorial. But first, let's look at the ERC721 token standard.

The ERC721 Token Standard

Let's take a look at the contract that we just deployed, in the SimpleCollectible.sol file.

// SPDX-License-Identifier: MIT
pragma solidity 0.6.6;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract SimpleCollectible is ERC721 {
    uint256 public tokenCounter;
    constructor () public ERC721 ("Dogie", "DOG"){
        tokenCounter = 0;
    }

    function createCollectible(string memory tokenURI) public returns (uint256) {
        uint256 newItemId = tokenCounter;
        _safeMint(msg.sender, newItemId);
        _setTokenURI(newItemId, tokenURI);
        tokenCounter = tokenCounter + 1;
        return newItemId;
    }

}

We are using the OpenZepplin package for the ERC721 token. This package that we've imported allows us to use all the functions of a typical ERC721 token. This defines all the functionality that our tokens are going to have, like transfer which moves tokens to new users, safeMint which creates new tokens, and more.

You can find all the functions that are given to our contract by checking out the OpenZepplin ERC721 token contract. Our contract inherits these functions on this line:

contract SimpleCollectible is ERC721 {

This is how solidity does inheritance. When we deploy a contract, the constructor is automatically called, and it takes a few parameters.

constructor () public ERC721 ("Dogie", "DOG"){
        tokenCounter = 0;
    }

We also use the constructor of the ERC721, in our constructor, and we just have to give it a name and a symbol. In our case, it's "Dogie" and "DOG". This means that every NFT that we create will be of type Dogie/DOG.

This is like how every Pokemon card is still a pokemon, or every baseball player on a trading card is still a baseball player. Each baseball player is unique, but they are still all baseball players. We are just using type DOG.

We have tokenCounter at the top that counts how many NFTs we've created of this type. Each new token gets a tokenId based on the current tokenCounter.

We can actually create an NFT with the createCollectible function. This is what we call in our create_collectible.py script.

function createCollectible(string memory tokenURI) public returns (uint256) {
        uint256 newItemId = tokenCounter;
        _safeMint(msg.sender, newItemId);
        _setTokenURI(newItemId, tokenURI);
        tokenCounter = tokenCounter + 1;
        return newItemId;
    }

The _safeMint function creates the new NFT, and assigns it to whoever called createdCollectible , aka the msg.sender, with a newItemId derived from the tokenCounter. This is how we can keep track of who owns what, by checking the owner of the tokenId.

You'll notice that we also call _setTokenURI. Let's talk about that.

What are NFT Metadata and TokenURI?

When smart contracts were being created, and NFTs were being created, people quickly realized that it's reaaaally expensive to deploy a lot of data to the blockchain. Images as small as one KB can easily cost over $1M to store.

This is clearly an issue for NFTs, since having creative art means you have to store this information somewhere. They also wanted a lightweight way to store attributes about an NFT – and this is where the tokenURI and metadata come into play.

TokenURI

The tokenURI on an NFT is a unique identifier of what the token "looks" like. A URI could be an API call over HTTPS, an IPFS hash, or anything else unique.

They follow a standard of showing metadata that looks like this:

{
    "name": "name",
    "description": "description",
    "image": "https://ipfs.io/ipfs/QmTgqnhFBMkfT9s8PHKcdXBn1f5bG3Q5hmBaR4U6hoTvb1?filename=Chainlink_Elf.png",
    "attributes": [
        {
            "trait_type": "trait",
            "value": 100
        }
    ]
}

These show what an NFT looks like, and its attributes. The image section points to another URI of what the NFT looks like. This makes it easy for NFT platforms like Opensea, Rarible, and Mintable to render NFTs on their platforms, since they are all looking for this metadata.

Off-Chain Metadata vs On-Chain Metadata

Now you might be thinking "wait... if the metadata isn't on-chain, does that mean my NFT might go away at some point"? And you'd be correct.

You'd also be correct in thinking that off-chain metadata means that you can't use that metadata to have your smart contracts interact with each other.

This is why we want to focus on on-chain metadata, so that we can program our NFTs to interact with each other.

We still need the image part of the off-chain metadata, though, since we don't have a great way to store large images on-chain. But don't worry, we can do this for free on a decentralized network still by using IPFS.

Here's an example imageURI from IPFS that shows the Chainlink Elf created in the Dungeons and Dragons tutorial.

Make an NFT

The Chainlink Elf

We didn't set a tokenURI for the simple NFT because we wanted to just show a basic example.

Let's jump into the advanced NFT now, so we can see some of the amazing features we can do with on-chain metadata, have the NFT render on opeansea, and get our Dogie up!

If you want a refresher video on the section we just went over, follow along with the deploying a simple NFT video.

Dynamic and Advanced NFTs

Dynamic NFTs are NFTs that can change over time, or have on-chain features that we can use to interact with each other. These are the NFTs that have the unlimited customization for us to make entire games, worlds, or interactive art of some-kind. Let's jump into the advanced section.

Advanced Quickstart

Make sure you have enough testnet ETH and LINK in your metamask, then run the following:

brownie run scripts/advanced_collectible/deploy_advanced.py --network rinkeby
brownie run scripts/advanced_collectible/create_collectible.py --network rinkeby

Our collectible here is a random dog breed returned from the Chainlink VRF. Chainlink VRF is a way to get provable random numbers, and therefore true scarcity in our NFTs. We then want to create its metadata.

brownie run scripts/advanced_collectible/create_metadata.py --network rinkeby

We can then optionally upload this data to IPFS so that we can have a tokenURI. I'll show you how to do that later. For now, we are just going to use the sample tokenURI of:

https://ipfs.io/ipfs/Qmd9MCGtdVz2miNumBHDbvj8bigSgTwnr4SbyH6DNnpWdt?filename=1-PUG.json

If you download IPFS Companion into your browser you can use that URL to see what the URI returns. It'll look like this:

{
    "name": "PUG",
    "description": "An adorable PUG pup!",
    "image": "https://ipfs.io/ipfs/QmSsYRx3LpDAb1GZQm7zZ1AuHZjfbPkD6J7s9r41xu1mf8?filename=pug.png",
    "attributes": [
        {
            "trait_type": "cuteness",
            "value": 100
        }
    ]
}

Then we can run our set_tokenuri.py script:

brownie run scripts/advanced_collectible/set_tokenuri.py --network rinkeby

And we will get an output like this:

Running 'scripts/advanced_collectible/set_tokenuri.py::main'...
Working on rinkeby
Transaction sent: 0x8a83a446c306d6255952880c0ca35fa420248a84ba7484c3798d8bbad421f88e
  Gas price: 1.0 gwei   Gas limit: 44601   Nonce: 354
  AdvancedCollectible.setTokenURI confirmed - Block: 8331653   Gas used: 40547 (90.91%)

Awesome! You can view your NFT at https://testnets.opensea.io/assets/0x679c5f9adC630663a6e63Fa27153B215fe021b34/0
Please give up to 20 minutes, and hit the "refresh metadata" button

And we can hit the link given to see what it looks like on Opensea! You may have to hit the refresh metadata button and wait a few minutes.

Make an NFT

Refresh Metadata

The Random Breed

Let's talk about what we just did. Here is our AdvancedCollectible.sol:

pragma solidity 0.6.6;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";

contract AdvancedCollectible is ERC721, VRFConsumerBase {
    uint256 public tokenCounter;
    enum Breed{PUG, SHIBA_INU, BRENARD}
    // add other things
    mapping(bytes32 => address) public requestIdToSender;
    mapping(bytes32 => string) public requestIdToTokenURI;
    mapping(uint256 => Breed) public tokenIdToBreed;
    mapping(bytes32 => uint256) public requestIdToTokenId;
    event requestedCollectible(bytes32 indexed requestId); 


    bytes32 internal keyHash;
    uint256 internal fee;
    uint256 public randomResult;
    constructor(address _VRFCoordinator, address _LinkToken, bytes32 _keyhash)
    public 
    VRFConsumerBase(_VRFCoordinator, _LinkToken)
    ERC721("Dogie", "DOG")
    {
        tokenCounter = 0;
        keyHash = _keyhash;
        fee = 0.1 * 10 ** 18;
    }

    function createCollectible(string memory tokenURI, uint256 userProvidedSeed) 
        public returns (bytes32){
            bytes32 requestId = requestRandomness(keyHash, fee, userProvidedSeed);
            requestIdToSender[requestId] = msg.sender;
            requestIdToTokenURI[requestId] = tokenURI;
            emit requestedCollectible(requestId);
    }

    function fulfillRandomness(bytes32 requestId, uint256 randomNumber) internal override {
        address dogOwner = requestIdToSender[requestId];
        string memory tokenURI = requestIdToTokenURI[requestId];
        uint256 newItemId = tokenCounter;
        _safeMint(dogOwner, newItemId);
        _setTokenURI(newItemId, tokenURI);
        Breed breed = Breed(randomNumber % 3); 
        tokenIdToBreed[newItemId] = breed;
        requestIdToTokenId[requestId] = newItemId;
        tokenCounter = tokenCounter + 1;
    }

    function setTokenURI(uint256 tokenId, string memory _tokenURI) public {
        require(
            _isApprovedOrOwner(_msgSender(), tokenId),
            "ERC721: transfer caller is not owner nor approved"
        );
        _setTokenURI(tokenId, _tokenURI);
    }
}

We use the Chainlink VRF to create a random breed from a list of PUG, SHIBA_INU, BRENARD. When we call createCollectible this time, we actually kicked off a request to the Chainlink VRF node off-chain, and returned with a random number to create the NFT with one of those 3 breeds.

Using true randomness in your NFTs is a great way to create true scarcity, and using an Chainlink oracle random number means that your number is provably random, and can't be influenced by the miners.

You can learn more about Chainlink VRF in the documentation.

The Chainlink node responds by calling the fulfillRandomness function, and creates the collectible based on the random number. We then still have to call _setTokenURI to give our NFT the appearance that it needs.

We didn't give our NFT attributes here, but attributes are a great way to have our NFTs battle and interact. You can see a great example of NFTs with attributes in this Dungeons and Dragons example.

Metadata from IPFS

We are using IPFS to store two files:

  1. The image of the NFT (the pug image)
  2. The tokenURI file (the JSON file which also includes the link of the image)

We use IPFS because it's a free decentralized platform. We can add our tokenURIs and images to IPFS by downloading IPFS desktop, and hitting the import button.

Make an NFT

IPFS add a file

Then, we can share the URI by hitting the 3 dots next to the file we want to share, hitting share link and copying the link given. We can then add this link into our set_tokenuri.py file to change the token URI that we want to use.

Persistance

However, if the tokenURI is only on our node, this means when our node is down, no one else can view it. So we want others to pin our NFT. We can use a pinning service like Pinata to help keep our data alive even when our IPFS node is down.

I imagine in the future more and more metadata will be stored on IPFS and decentralized storage platforms. Centralized servers can go down, and would mean that the art on those NFTs is lost forever. Be sure to check where the tokenURI of the NFT you use is located!

I also expect down the line that more people will use dStorage platforms like Filecoin, as using a pinning service also isn't as decentralized as it should be.

Original article source at https://www.freecodecamp.org

#blockchain #nft #python #solidity #ethereum

How to Build, Deploy, and Sell Your NFT on the OpenSea Marketplace
Duong Tran

Duong Tran

1641876192

Top 3 Extension cho Rust: Rust-analyzer, Clippy & ErrroLens

Top 3 Extension dành cho lập trình viên Rust biến VSCode thành một IDE xịn xò hơn đối với Rust.
Video khoá học về ngôn ngữ lập trình RUST, để lập trình smart contract trên Solana, Near, substrate, game, websites, backend, front-end với Rust.

#blockchain #solidity #smartcontract #rust #vscode 

Top 3 Extension cho Rust: Rust-analyzer, Clippy & ErrroLens
Charles Cooper

Charles Cooper

1641875958

Top 10 Web3/Smart Contract Developer Tools you NEED for 2022

THE Top 10 Web3/Smart Contract Developer Tools you NEED for 2022

Web3, Smart Contract, Blockchain, Ethereum, Solana, and Solidity developers need to know about these tools to succeed this year! We go into each of these categories and the best tools for them.

0:00 | Introduction
0:47 | Smart Contract Languages
2:14 | Smart Contract Essentials
4:00 | Deployment & Testing Frameworks
8:40 | Wallets
10:32 | Block Explorers
11:09 | Layer 1 Connection
12:52 | Front End Tools
15:01 | Help & Support
16:49 | Security Analysis Tools
19:20 | Monitoring
19:40 | Sigh of relief 
19:41 | Summary
20:22 | NFTat Update

#web3 #blockchain #rust #ethereum #solana #solidity #javascript 

Top 10 Web3/Smart Contract Developer Tools you NEED for 2022
Hong  Nhung

Hong Nhung

1641807397

Học Web3.js từ A-Z | Thông tin Series và Khởi tạo dự án (UI Design)

Mastering Web3 | Thông tin Series và Khởi tạo dự án (UI Design)

Khoá học cung cấp toàn bộ kiến thức Web3js qua thực hành để mọi người có thể thực hiện vô các dự án của các bạn một cách hiệu quả hơn, Dự án được xây dựng trên Nextjs và Javascript, ứng dụng React và React Hook để tối ưu hoá code.

#blockchain #solidity #smartcontract #web3 #javascript #react 

Học Web3.js từ A-Z | Thông tin Series và Khởi tạo dự án (UI Design)