Ever wondered how to create your own blockchain application? When it comes to Ethereum, it starts with smart contracts.
In this tutorial, we’ll focus on smart contracts written in Solidity language. We’ll use the Truffle Suite to deploy a local version of the Ethereum blockchain and compile smart contracts using Ethereum Virtual Machine (EVM).
If you’re not familiar with the technologies described above, I would recommend first reading our guide to Ethereum blockchain development using Web3.js.
Ethereum is a blockchain that allows applications to run on it. The code is written in Solidity language in the form of smart contracts. To compile these contracts, we need an Ethereum compiler, which converts smart contracts to machine-readable code.
The Truffle Suite is a collection of tools made specifically for blockchain development on Ethereum. The suite includes three pieces of software:
For this tutorial, we need a few node packages and software, including the Ethereum blockchain, smart contract compiler, and a JavaScript library for communication.
Smart contracts run on the Ethereum blockchain, so we require one for deployment and testing. We could also deploy on a live chain, but that would cost us Ether as a gas fee. So let’s set up a local chain and do our testing there. When you’re sure about the code and ready to distribute your application then you can deploy on live chain.
Ganache is the local chain that gets installed on our computers and runs on localhost. Download and install Ganache from the Truffle Suite website.
You can see that Ganache provided 10 accounts with 100 ETH each. These are fake Ethers, so don’t get excited. Also, the chain is running on 127.0.0.1 at 7545 port. We will use these accounts to deploy our smart contracts on this chain. Ethers will help us pay gas fees.
We’ll need Node.js for this project, so make sure it’s installed on your system. You can download it from the Node.js official website.
Web3.js is a JavaScript library that enables communication with the Ethereum blockchain through JavaScript. It has a lot of inbuilt functions that are useful in development.
You can install Web3.js using npm
or yarn
:
npm install web3 -g
Truffle provides the compiler for smart contracts. We need it to convert the Solidity code into machine-readable code that can be deployed on Ganache blockchain.
Install Truffle using the following command:
npm install truffle -g
To create smart contracts, we first need to create a project directory where we will keep all the Solidity files. Let’s create one with the name solidity
and move to the directory in the terminal using cd solidity
.
Right now, our project is empty. To work with it, we need some boilerplate code. For example, if you wish to create the UI in React, you’ll need to install React.
Truffle already provides some packages, which are called boxes. These packages are bundles of different frameworks, such as Truffle, Ganache, React, Web3, and Redux, and there is one for Vue.js developers. Together, they complete the end-to-end application development, from client UI to blockchain smart contracts.
In this article, we’ll use the React box provided by Truffle.
To install the React box, run the following command:
truffle unbox react
This will install Web3.js, React, Ganache CLI, Truffle, and Ethereum. In a sense, we didn’t really need to install all these utilities in the first step.
For the purpose of this tutorial, we won’t focus on React or a browser-based UI. Instead, we’ll create the smart contracts and handle them with the terminal only.
The directory structure of our project will look like this:
Here, client
is a React project folder where we can create the UI of the application. There is a folder inside it (client
/src
/contracts
) that holds the compiled smart contracts in JSON format. These files are generated when we compile our smart contracts. They contain the ABI, bytecode, and other information.
From the image above, you can see that we have two JSON files. That’s because Truffle already provided two smart contracts when we unboxed the React bundle. If you open any of them, you will find code like this:
{ "contractName": "SimpleStorage", "abi": [ { "constant": false, "inputs": [ { "internalType": "uint256", "name": "x", "type": "uint256" } ], "name": "set", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "get", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" } ], "metadata": "{\"compiler\":{\"version\":\"0.5.16+commit.9c3226ce\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"project:/contracts/SimpleStorage.sol\":\"SimpleStorage\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"project:/contracts/SimpleStorage.sol\":{\"keccak256\":\"0x512df1603c5f878921707d236bc53d974afe05b4d9de4b6094249bac5ab60efe\",\"urls\":[\"bzz-raw://0d6de97971b1c387f984fa7ea1d9ec10f8a63d68cc63bf8bd00d8c3a7c9e3ee1\",\"dweb:/ipfs/Qmbt92T34sHzedfJjDsvbisvLhRtghNwS6VW8tqrGkrqTD\"]}},\"version\":1}", "bytecode": "0x608060405234801561001057600080fd5b5060c68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146062575b600080fd5b606060048036036020811015604b57600080fd5b8101908080359060200190929190505050607e565b005b60686088565b6040518082815260200191505060405180910390f35b8060008190555050565b6000805490509056fea265627a7a7231582044ccb2c2d46346d523107088f3e26a4c8a2ec3ec8b2e3a6edb1bc8574d5c5f5264736f6c63430005100032", "deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146062575b600080fd5b606060048036036020811015604b57600080fd5b8101908080359060200190929190505050607e565b005b60686088565b6040518082815260200191505060405180910390f35b8060008190555050565b6000805490509056fea265627a7a7231582044ccb2c2d46346d523107088f3e26a4c8a2ec3ec8b2e3a6edb1bc8574d5c5f5264736f6c63430005100032", "sourceMap": "66:176:1:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;66:176:1;;;;;;;", "deployedSourceMap": "66:176:1:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;66:176:1;;;;;;;;;;;;;;;;;;;;;;;;113:53;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;113:53:1;;;;;;;;;;;;;;;;;:::i;:::-;;170:70;;;:::i;:::-;;;;;;;;;;;;;;;;;;;113:53;160:1;147:10;:14;;;;113:53;:::o;170:70::-;206:4;225:10;;218:17;;170:70;:::o", "source": "// SPDX-License-Identifier: MIT\npragma solidity >=0.4.21 <0.7.0;\n\ncontract SimpleStorage {\n uint storedData;\n\n function set(uint x) public {\n storedData = x;\n }\n\n function get() public view returns (uint) {\n return storedData;\n }\n}\n", "sourcePath": "C:\\Users\\akash\\Desktop\\solidity\\contracts\\SimpleStorage.sol", "ast": { "absolutePath": "project:/contracts/SimpleStorage.sol", "exportedSymbols": { "SimpleStorage": [ 59 ] }, "id": 60, "nodeType": "SourceUnit", "nodes": [ { "id": 38, "literals": [ "solidity", ">=", "0.4", ".21", "<", "0.7", ".0" ], "nodeType": "PragmaDirective", "src": "32:32:1" }, { "baseContracts": [], "contractDependencies": [], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "id": 59, "linearizedBaseContracts": [ 59 ], "name": "SimpleStorage", "nodeType": "ContractDefinition", "nodes": [ { "constant": false, "id": 40, "name": "storedData", "nodeType": "VariableDeclaration", "scope": 59, "src": "93:15:1", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 39, "name": "uint", "nodeType": "ElementaryTypeName", "src": "93:4:1", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "body": { "id": 49, "nodeType": "Block", "src": "141:25:1", "statements": [ { "expression": { "argumentTypes": null, "id": 47, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 45, "name": "storedData", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 40, "src": "147:10:1", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 46, "name": "x", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 42, "src": "160:1:1", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "147:14:1", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 48, "nodeType": "ExpressionStatement", "src": "147:14:1" } ] }, "documentation": null, "id": 50, "implemented": true, "kind": "function", "modifiers": [], "name": "set", "nodeType": "FunctionDefinition", "parameters": { "id": 43, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 42, "name": "x", "nodeType": "VariableDeclaration", "scope": 50, "src": "126:6:1", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 41, "name": "uint", "nodeType": "ElementaryTypeName", "src": "126:4:1", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "125:8:1" }, "returnParameters": { "id": 44, "nodeType": "ParameterList", "parameters": [], "src": "141:0:1" }, "scope": 59, "src": "113:53:1", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" }, { "body": { "id": 57, "nodeType": "Block", "src": "212:28:1", "statements": [ { "expression": { "argumentTypes": null, "id": 55, "name": "storedData", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 40, "src": "225:10:1", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 54, "id": 56, "nodeType": "Return", "src": "218:17:1" } ] }, "documentation": null, "id": 58, "implemented": true, "kind": "function", "modifiers": [], "name": "get", "nodeType": "FunctionDefinition", "parameters": { "id": 51, "nodeType": "ParameterList", "parameters": [], "src": "182:2:1" }, "returnParameters": { "id": 54, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 53, "name": "", "nodeType": "VariableDeclaration", "scope": 58, "src": "206:4:1", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 52, "name": "uint", "nodeType": "ElementaryTypeName", "src": "206:4:1", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "205:6:1" }, "scope": 59, "src": "170:70:1", "stateMutability": "view", "superFunction": null, "visibility": "public" } ], "scope": 60, "src": "66:176:1" } ], "src": "32:211:1" }, "legacyAST": { "attributes": { "absolutePath": "project:/contracts/SimpleStorage.sol", "exportedSymbols": { "SimpleStorage": [ 59 ] } }, "children": [ { "attributes": { "literals": [ "solidity", ">=", "0.4", ".21", "<", "0.7", ".0" ] }, "id": 38, "name": "PragmaDirective", "src": "32:32:1" }, { "attributes": { "baseContracts": [ null ], "contractDependencies": [ null ], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "linearizedBaseContracts": [ 59 ], "name": "SimpleStorage", "scope": 60 }, "children": [ { "attributes": { "constant": false, "name": "storedData", "scope": 59, "stateVariable": true, "storageLocation": "default", "type": "uint256", "value": null, "visibility": "internal" }, "children": [ { "attributes": { "name": "uint", "type": "uint256" }, "id": 39, "name": "ElementaryTypeName", "src": "93:4:1" } ], "id": 40, "name": "VariableDeclaration", "src": "93:15:1" }, { "attributes": { "documentation": null, "implemented": true, "isConstructor": false, "kind": "function", "modifiers": [ null ], "name": "set", "scope": 59, "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" }, "children": [ { "children": [ { "attributes": { "constant": false, "name": "x", "scope": 50, "stateVariable": false, "storageLocation": "default", "type": "uint256", "value": null, "visibility": "internal" }, "children": [ { "attributes": { "name": "uint", "type": "uint256" }, "id": 41, "name": "ElementaryTypeName", "src": "126:4:1" } ], "id": 42, "name": "VariableDeclaration", "src": "126:6:1" } ], "id": 43, "name": "ParameterList", "src": "125:8:1" }, { "attributes": { "parameters": [ null ] }, "children": [], "id": 44, "name": "ParameterList", "src": "141:0:1" }, { "children": [ { "children": [ { "attributes": { "argumentTypes": null, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "operator": "=", "type": "uint256" }, "children": [ { "attributes": { "argumentTypes": null, "overloadedDeclarations": [ null ], "referencedDeclaration": 40, "type": "uint256", "value": "storedData" }, "id": 45, "name": "Identifier", "src": "147:10:1" }, { "attributes": { "argumentTypes": null, "overloadedDeclarations": [ null ], "referencedDeclaration": 42, "type": "uint256", "value": "x" }, "id": 46, "name": "Identifier", "src": "160:1:1" } ], "id": 47, "name": "Assignment", "src": "147:14:1" } ], "id": 48, "name": "ExpressionStatement", "src": "147:14:1" } ], "id": 49, "name": "Block", "src": "141:25:1" } ], "id": 50, "name": "FunctionDefinition", "src": "113:53:1" }, { "attributes": { "documentation": null, "implemented": true, "isConstructor": false, "kind": "function", "modifiers": [ null ], "name": "get", "scope": 59, "stateMutability": "view", "superFunction": null, "visibility": "public" }, "children": [ { "attributes": { "parameters": [ null ] }, "children": [], "id": 51, "name": "ParameterList", "src": "182:2:1" }, { "children": [ { "attributes": { "constant": false, "name": "", "scope": 58, "stateVariable": false, "storageLocation": "default", "type": "uint256", "value": null, "visibility": "internal" }, "children": [ { "attributes": { "name": "uint", "type": "uint256" }, "id": 52, "name": "ElementaryTypeName", "src": "206:4:1" } ], "id": 53, "name": "VariableDeclaration", "src": "206:4:1" } ], "id": 54, "name": "ParameterList", "src": "205:6:1" }, { "children": [ { "attributes": { "functionReturnParameters": 54 }, "children": [ { "attributes": { "argumentTypes": null, "overloadedDeclarations": [ null ], "referencedDeclaration": 40, "type": "uint256", "value": "storedData" }, "id": 55, "name": "Identifier", "src": "225:10:1" } ], "id": 56, "name": "Return", "src": "218:17:1" } ], "id": 57, "name": "Block", "src": "212:28:1" } ], "id": 58, "name": "FunctionDefinition", "src": "170:70:1" } ], "id": 59, "name": "ContractDefinition", "src": "66:176:1" } ], "id": 60, "name": "SourceUnit", "src": "32:211:1" }, "compiler": { "name": "solc", "version": "0.5.16+commit.9c3226ce.Emscripten.clang" }, "networks": { "5777": { "events": {}, "links": {}, "address": "0xc21DB75B9B17Cb43Cd983A16BaA6c73b314B1E8f", "transactionHash": "0xada7b561df968c3d23485d25cbd22a66d1d4b76a86137dbf1bef858e816ff0c2" } }, "schemaVersion": "3.4.3", "updatedAt": "2021-10-29T10:14:41.665Z", "networkType": "ethereum", "devdoc": { "methods": {} }, "userdoc": { "methods": {} } }
The massive code block above contains the ABI, bytecode, source code, and a ton of other information. From the ABI we can get the list of callable functions. This file contains all the information like network ID, contract address, transaction hash, compiler details, chain details, etc.
You might be wondering why this file is in the UI directory? That’s because Web3.js uses it for its operations. This file enables Web3.js to recognizes the smart contract on the chain. Also, the ABI helps in getting the list of functions.
contracts
is the directory that holds the Solidity files. You will put all the source code heremigrations
are the files that inform Truffle which Solidity files need to be migrated to the chaintest
is for creating test filestruffle-config.js
contains some configuration settings for Truffle.Here is the content of truffle-config.js
:
const path = require("path"); module.exports = { // See <http://truffleframework.com/docs/advanced/configuration> // to customize your Truffle configuration! contracts_build_directory: path.join(__dirname, "client/src/contracts"), networks: { develop: { port: 8545 } } };
As you can see, client/src/contracts
is set to hold compiled code. The development network is set at port 8545. This is the port where this box is running Ganache.
If you look at the top where we installed Ganache, you can see that it was running at 7545 port, but this one is running at 8545 because 7545 is already in use by our installed Ganache. If you wish, you can change this port to 7545 and Truffle will use the Ganache and the accounts we installed instead of the one provided by box. I am keeping it at 8545.
Now it’s time to write some code. We will do CRUD operations and manage a list of fruits.
Basically, our application will show a list of different fruits. You can add, update, and delete the fruits.
If you’ve developed apps before, we’ll follow a procedure you’re surely familiar with:
Now let’s write the code in Solidity.
Create a new file in contracts
directory and call it Fruits.sol
. We’ll start the file by indicating the license and solidity version we are supporting:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.21 <0.7.0;
Next, declare the contract
scope where we are going to write all the code:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.21 <0.7.0; contract Fruits { }
Create an array to hold the fruits:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.21 <0.7.0; contract Fruits { string[] myFruits; }
This is a string array with a private modifier, which means it can’t be accessed outside the contract and thus we can’t change the value directly.
Next, create a function to add a new value:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.21 <0.7.0; contract Fruits { string[] myFruits; function addFruit(string memory fruitName) public { myFruits.push(fruitName); } }
We created a function called addFruit
, which accepts a string as a parameter, fruitName
. This is declared public so it can be called by the UI or terminal. In the function body, we are simply pushing the value to the array.
Update the value:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.21 <0.7.0; contract Fruits { string[] myFruits; function addFruit(string memory fruitName) public { myFruits.push(fruitName); } function updateFruit(uint fruitIndex, string memory newFruitName) public returns (bool) { if(myFruits.length > fruitIndex){ myFruits[fruitIndex] = newFruitName; return true; } return false; } }
updateFruit
accepts two arguments, fruitIndex
and newFruitName
, and returns a boolean value. It works like this: if the index is out of the bounds of the array, it returns false
. Otherwise, it changes the value of the array with a new provided fruit name at the provided index and return true
.
The next steps is to create the delete function:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.21 <0.7.0; contract Fruits { string[] myFruits; function addFruit(string memory fruitName) public { myFruits.push(fruitName); } function updateFruit(uint fruitIndex, string memory newFruitName) public returns (bool) { if(myFruits.length > fruitIndex){ myFruits[fruitIndex] = newFruitName; return true; } return false; } function deleteFruit(uint fruitIndex) public returns (bool) { if(myFruits.length > fruitIndex){ for(uint i=fruitIndex; i < myFruits.length-1; i++){ myFruits[i] = myFruits[i+1]; } myFruits.pop(); return true; } return false; } }
Here we are checking the index out of bounds condition and then updating the array by replacing the value with the next value from the provided index. This way, the value at the provided index will be lost. In the end, we pop out the last value and return true
.
The last step is to return the array. To read all the values of the array:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.21 <0.7.0; contract Fruits { string[] myFruits; function addFruit(string memory fruitName) public { myFruits.push(fruitName); } function updateFruit(uint fruitIndex, string memory newFruitName) public returns (bool) { if(myFruits.length > fruitIndex){ myFruits[fruitIndex] = newFruitName; return true; } return false; } function deleteFruit(uint fruitIndex) public returns (bool) { if(myFruits.length > fruitIndex){ for(uint i=fruitIndex; i < myFruits.length-1; i++){ myFruits[i] = myFruits[i+1]; } myFruits.pop(); return true; } return false; } function getFruits() public view returns (string[] memory) { return myFruits; } }
Now that we’ve finished coding our smart contract, it’s time to compile it using Truffle. But first we need to create a migration file to indicate to Truffle that we want to migrate this to the chain.
If you check in the migration
folder, you’ll find two JavaScript files:
They each start with a number, so our third file will start with 3, and so on. The code is nearly standard. For 2_deploy_contracts
it is:
var SimpleStorage = artifacts.require("./SimpleStorage.sol"); module.exports = function(deployer) { deployer.deploy(SimpleStorage); };
Let’s add our migration file, 3_fruits_contracts
:
var FruitsList = artifacts.require("./Fruits.sol"); module.exports = function(deployer) { deployer.deploy(FruitsList); };
We’re ready to compile and migrate our fruits contract. Move to the terminal and run the following:
> truffle develop
This command will start the truffle console. It will also show some information such as the chain network, accounts, Mnemonic, etc.
Ganache provides 10 accounts by default. They will be different for you. Please don’t use any of these private keys on the live chain because they are visible to all the visitors of this article, meaning anybody can access these accounts.
Now compile the contracts using this command:
> compile
You can check in client/src/contracts
whether a Fruits.json
file is created or not.
We can migrate the compiled file to the chain using this command:
> migrate
This will migrate all three smart contracts to the chain.
Finally, our application is on the Ethereum chain. You can see that we spent 0.00153 Ethers in gas fees and the transactions happened from the first account. By default, it always takes the first account. We can perform various operations now.
We can use Web3.js to react and write various values. Let’s first store the instance of our contract in a variable:
let instance = await Fruits.deployed()
We are using await
because everything in blockchain is asynchronous and return a promise.
Now use this instance to get the array:
> let fruits = instance.getFruits() undefined > fruits []
It will return an empty array because, currently, our fruits array has no value.
Let’s add some fruits:
> let result = await instance.addFruit("Apple") undefined
The result
will hold the transaction. Remember, all the read operations are free but any operation that leads to a change in blockchain is subject to a gas fee. This operation is adding a value to the array and hence changing the data. It is, therefore, recorded as a transaction.
Now we can again read the array to check the content:
Let’s add a few more fruits to the list:
> await instance.addFruit("Mango") > await instance.addFruit("Banana") > await instance.addFruit("Orange") > await instance.addFruit("Guava") > await instance.addFruit("Pineapple") > await instance.addFruit("Water Melon") > await instance.addFruit("Papaya") > await instance.addFruit("Strawberry")
Remember, all these operations will cost you Ether. You can save the fee by creating a function in your contract for accepting multiple fruit values at a time.
Read the array now:
You can see in the above image that I misspelled “Guava” as “Guavva.” Let’s correct it using the updateFruit
function. It will accept the index and new value. The index is 4.
> await instance.updateFruit(4, "Guava")
Let’s read the array now:
The spelling is successfully fixed.
The last operation is to delete a value:
> await instance.deleteFruit(4)
Read the values:
As you can see, the “Guava” item is gone from the list.
Creating smart contracts and deploying on the blockchain is fun and powerful. It gives a new perspective from traditional programming. You can create all sorts of applications, such as online voting, digital bank, wallets, auctions etc., using these techniques.
In this tutorial, we demonstrated how to create a smart contract and deploy it on local chain. Now you can try deploying on test networks of Ethereum.
You can improve the current app by introducing more functionality, such as adding multiple fruits at a time to save transaction costs. You might also also put a check to only allow unique fruits. Instead of an array, try to use mappings.
Happy coding!
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — Start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
One Reply to "Truffle Suite tutorial: How to develop Ethereum smart contracts"
hi my smart contract refuse to deploy stating invalid opcode error. it compile successfully but refuse to migrate. I start the project with truffle init. please help.