# 🧪 Smart Contracts development on Osmosis Welcome to our "Smart Contracts development on Osmosis" course! In this course, you'll learn how to build secure, scalable, and reliable smart contracts using the CosmWasm platform on the Osmosis chain. You'll learn the fundamentals of CosmWasm development, including how to write and deploy contracts, interact with them, and implement a frontend to interact with your contracts. Plus, you'll get hands-on experience building your own real-world project: a To Do app! This will give you the opportunity to see how all the concepts and techniques you learn come together in a practical setting. We'll provide both text and video-based versions of the material, so you can learn at your own pace in a way that suits your learning style. So get ready to roll up your sleeves and start building your very own CosmWasm smart contracts – it's going to be a fun and rewarding journey. --- # 📝 Table of Contents - [🧪 Preparing Your Labs](https://hackmd.io/@laika-labs/osmosis-course#%F0%9F%A7%AA-Preparing-your-Labs) - [🧪 Initialize your smart contract](https://hackmd.io/@laika-labs/osmosis-course#%F0%9F%A7%AA-Initialize-your-smart-contract) - [📝 Create Todo List Smart Contract](https://hackmd.io/@laika-labs/osmosis-course#%F0%9F%93%9DCreate-Todo-List-Smart-Contract) - [🚀 Deploying smart contract](https://hackmd.io/@laika-labs/osmosis-course#%F0%9F%9A%80-Deploying-smart-contract) - [🧑‍💻 Implementing Frontend to Interact with Our Smart Contract](https://hackmd.io/@laika-labs/osmosis-course#%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB-Implementing-Frontend-to-Interact-with-Our-Smart-Contract) --- # 🧪 Preparing your Labs --- ## 🏁 Our Goal Okay! Now we know what Osmosis is and what we are going to build. Let's get start by preparing our workspace on local environment. Luckily, Osmosis Labs provide us with a wonderful tool called **LocalOsmosis** which already pre-configured standard environment for developing on Osmosis for us. For more information about LocalOsmosis please refer to its official documentation [here](https://docs.osmosis.zone/cosmwasm/local/localosmosis/) Also, We're going to install a tool call **Beaker** which is a tool that's going to help us scaffolding and do a lot of heavy tasks for us when developing smart contracts (think of hardhat for cosmwasm) --- ## 🪚 Let's Install it! Before installing LocalOsmosis you'll need to installed its prerequisites first. Let's look into it one-by-one now! #### 🐳 Docker & docker-compose So, In case you didn’t have Docker install in your local machine already. It's a tool that help containerize a lot of components together. thus, make it easier to work on things. You can install it by go to this [Link](https://www.docker.com/) Also, One more thing after you finished installed Docker. Its default resourses limit is pretty small. So it's not bad if we pump that number up a little bit. Here's what I using. ![](https://i.imgur.com/jcL3Dzy.png) #### 🤓 Node.js Next, is our beloved Node.js if you have no Node.js installed just head to their official website [here](https://nodejs.org/en/) Currently (As I writing this), The LTS version is on **NodeJS v16** just get that version if you didn't have it yet! ![](https://i.imgur.com/ToAKTOM.png) #### ⚙️ Rust And now, It's Rust which you may notice we're going to write it in the smart contract. (But it's also used in a lot of place) to install it is really easy. You can do it by using **rustup** ([docs](https://www.rust-lang.org/tools/install)) `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` then do ``` rustup default stable rustup target add wasm32-unknown-unknown ``` `` Also, you need to install the following cargo packages as well ``` cargo install cargo-generate --features vendored-openssl cargo install cargo-run-scrip ``` #### 🧪 LocalOsmosis Alright, Finally here's the time for LocalOsmosis. So actually there's number of ways to install LocalOsmosis in your local machine. The first one is you can use their official command line to install it. https://get.osmosis.zone/ Just check out the link on the top. You can install LocalOsmosis by choose choice 3. and it should be simple and straightforward just follow its instructions and you're good to go! Another way to do is to clone their repo into your local machine. `git clone https://github.com/osmosis-labs/osmosis/tree/main/tests/localosmosis` Then, You should be able to start, stop, and reset chain using this commands Start Env: `make start` or `make localnet-start` if you're using the git clone one Stop Env: `make stop` or `make localnet-stop` if you're using the git clone one Stop Env (And remove all data stored on the chain): `make restart` After your LocalOsmosis is run. You should be able to see the process on Docker Desktop with something similar to this. ![](https://i.imgur.com/3CswKGR.png) #### ⚗️ Beaker (I find no emoji for beaker please let me use alembic here 😂) As we discuss earlier **Beaker** is a tool to scaffold cosmwasm app. Which is really helpful for us. And make it really to get start developing it. Learn more about Beaker [here](https://github.com/osmosis-labs/beaker) Sooo, Let's just install it now! `cargo install -f beaker` After Beaker installed we should be able to see something like this. ![](https://i.imgur.com/oWCFaGg.png) --- ## Automatic install If the above installation process is too hard to follow along, you can also use the automatic setup option as well by doing the following command. ``` bash <(curl -sL https://get.osmosis.zone/run) ``` This command will setup everything you need for the smart contract development for you. Including Rust, Beaker, localosmosis command line tool, and additional configurations. --- # 🧪 Initialize your smart contract --- ## 🏁Our goal In this chapter, we'll scaffold the example project and take a quick tour of the project structure and it's files. --- ## 🚧Scaffolding your project After you finished the last chapter, you should have Beaker installed by now. To create a new project with Beaker, we'll use the following command. `beaker new <app-name>` for the `<app-name>` we'll use `counter-dapp` After that, we'll see the following choices. ![](https://i.imgur.com/Xllo2pk.png) For this tutorial, we want to see the example so we'll choose the `counter-example` choice. ![](https://i.imgur.com/N7YbaCT.png) After it has finished running, we'll see the result as follow and we'll also see the newly created project folder. Great! Now we can start the next part where it gonna be the project overview. Open the project in your favorite text editor or IDE, and proceed to the next part. --- ## 🕵️Project overview ![](https://i.imgur.com/os2L6pU.png) In my case, I'm using Visual Studio Code, and in the explorer view you we'll see the following folders and files. In this overview, we'll take a closer look into the `contracts` folder. For the `frontend` folder, we'll get into detail in the later chapter. ![](https://i.imgur.com/kknlfBR.png) Expanding the `contracts` folder, you'll see the `counter` subfolder which contains the files for example smart contract. Most of the files directly under this folder is irrelevant for this tutorial, but the `src` folder is what we're going to explore it in the detail. ![](https://i.imgur.com/DE4tQM5.png) After we've expanded the `src` folder, we'll see multiple files that related to the counter smart contract. The important files are: `contract.rs`, `msg.rs`, and `state.rs`. For other files, they're not that important for our tutorial so I'll skip it, but it's pretty much self explanatory by the name. ### contract.rs This file content is related to the main logic of the smart contract itself. ![](https://i.imgur.com/5VJxIyE.png) In this example, we'll see 3 important parts - `instantiate function`: This function will handle the instantiation of our smart contract state - `execute function` and `execute module`: These parts will process the execution message. - function: do the switch-case for the message type and call the corresponding function specify within the `execute module`. - module: implement the functios that will be called by `execute function`. - `query function` and `query module`: These parts will process the query message. - function: do the switch-case for the message type and call the corresponding function specify within the `query module`. - module: implement the functios that will be called by `query function`. The implementation example for `instantiate` part is as follow. ![](https://i.imgur.com/ZRH6hvO.png) The implementation for the `execute function` and `query function` is mostly identical, so I'll show only the execute function here. ![](https://i.imgur.com/ODA6TNS.png) And for the `execute module`, it will look something like this. ![](https://i.imgur.com/03kvJvK.png) For the `query module`, it will look a little bit different from the former. Since it also return data to the client, the implementation will be like this. ![](https://i.imgur.com/vSuhCWQ.png) ### msg.rs This file content is used to specify the contract message endpoint (which is similar to API specification or schema for the API call). ![](https://i.imgur.com/uPtSeut.png) The message types being shown here have 3 types. - `Instantiate` - `Execute` - `Query` There's also another type of message, the `Migration` message which we'll not cover in this beginner level tutorial. Every message types specify in this file here do the same thing, serve as an API specification for the message - what data to send in order to interact with the smart contract and what data the client should be receiving from the contract. ### state.rs This file content is used to specify the data (or state) that will be store within our smart contract. ![](https://i.imgur.com/CEHgX24.png) For this example, we'll see that the smart contract will store 2 information in the state - `count` data which is a 32-bits integer - `owner` data which is the wallet's address of the owner --- # 📝Create Todo List Smart Contract --- ## 🏁Our goal In this chapter, we'll edit the example project and initialize our own smart contract with "API Specification" --- ## Initialize your project You can scaffold the project by following our last tutorial. You could also choose the `minimal` option as well but we rather recommended you to choose the `counter-example` option to get some general idea of how you should implement specific part of the contract. After you have initialized your project, we'll also need a new contract to be created. So, you need to run the following command in order to get our Todo contract up and ready to be developed. `beaker new contract todo` The command will scaffold the new contract folder for you. At this point, you could also delete the `counter` folder under the `contracts` folder or leave it as a reference. --- ## Editing msg.rs file We'll start from `msg.rs` file since it's the most simple file to be edited and it's also the starting point for other part of the contract as well. First, we'll need to specify the struct for our Todo. This will be the representation of Todo data store within our contract. ```rust #[cw_serde] #[derive(Eq)] pub struct Todo { pub id: i32, pub title: String, pub due_date: String, pub is_done: bool } ``` As you can see here, our Todo contains the following data: - id - title - due_date - is_done Next part will be about the instantiate message. We want user to be able to input the initial list of todos into the contract. So the next part of the code will be like this. ```rust #[cw_serde] pub struct InstantiateMsg { pub todos: Vec<Todo> } ``` For the execute message, we will implement it like so. ```rust #[cw_serde] pub enum ExecuteMsg { Add {todo: Todo}, Remove {todo: Todo}, Update {todo: Todo}, Reset {}, } ``` As you can see, we have multiple execute messages here. - Add - Remove - Update - Reset For Add, Remove, and Update message, we'll also need to supply the todo as a parameter of the function as well. Finally for the query message, we'll have a single function called `GetTodos` and with this function, we'll also need to supply the return type as well. The implementation will be as followed. ```rust #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { #[returns(GetTodosResponse)] GetTodos {} } #[cw_serde] pub struct GetTodosResponse { pub todos: Vec<Todo>, } ``` And that's it. Your `msg.rs` file will look something like this. ```rust use cosmwasm_schema::{cw_serde, QueryResponses}; #[cw_serde] #[derive(Eq)] pub struct Todo { pub id: i32, pub title: String, pub due_date: String, pub is_done: bool } #[cw_serde] pub struct InstantiateMsg { pub todos: Vec<Todo> } #[cw_serde] pub enum ExecuteMsg { Add {todo: Todo}, Remove {todo: Todo}, Update {todo: Todo}, Reset {}, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { #[returns(GetTodosResponse)] GetTodos {} } #[cw_serde] pub struct GetTodosResponse { pub todos: Vec<Todo>, } ``` After you've successfully edited the file. You can proceed to the next chapter. --- # 📝Implementing Smart Contract Logic {%youtube zB6DP3ShA5w %} --- ## 🏁Our goal In this chapter, we'll continue the implementation from last chapter and edit the main logic part for our smart contract. Also, after everything is done, we'll also deploy the smart contract as well. --- ## Edit state.rs file Since we wanted to store the todos list within our smart contract, we'll need to modify the `state.rs` file as followed. ```rust #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct State { pub todos: Vec<Todo>, pub owner: Addr, } ``` As you can see here, we have 2 data to store: - todos: a vector (or list) that store your todo items - owner: your wallet address, we'll use this to validate user later on when we edit `contract.rs` file --- ## Edit contract.rs file ### Instantiate function This function will be called when instantiate message is sent to smart contract. We'll edit the function as followed. ```rust #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, _env: Env, info: MessageInfo, _msg: InstantiateMsg, ) -> Result<Response, ContractError> { let state = State{ todos: _msg.todos, owner: info.sender.clone() }; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; STATE.save(deps.storage, &state); Ok(Response::new() .add_attribute("method", "instantiate") .add_attribute("owner", info.sender) .add_attribute("todos",format!("{:?}",state.todos))) } ``` ### Execute function and it's related functions Now, to handle the execute message, we'll need an `execute` function. This function will handle different execute message type and call the function corresponding to it. The function will be as followed. ```rust #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result<Response, ContractError> { match msg { ExecuteMsg::Add { todo } => {try_add(deps, info, todo)} ExecuteMsg::Remove { todo } => {try_remove(deps, info, todo)} ExecuteMsg::Update { todo } => {try_update(deps, info, todo)} ExecuteMsg::Reset {  } => {try_reset(deps, info)} } } ``` And this part will be the functions. Note that the functions here can also be put within the `execute` module (you will see the example in the query function part). ```rust pub fn try_add(deps: DepsMut, info: MessageInfo, todo: Todo) -> Result<Response, ContractError>{ STATE.update(deps.storage, |mut state|->Result<_,ContractError>{ if info.sender != state.owner{ return Err(ContractError::Unauthorized {}); } state.todos.push(todo); Ok(state) })?; Ok(Response::new().add_attribute("method","try_add")) } pub fn try_remove(deps: DepsMut, info: MessageInfo, todo: Todo) -> Result<Response, ContractError>{ STATE.update(deps.storage, |mut state|->Result<_,ContractError>{ if info.sender != state.owner{ return Err(ContractError::Unauthorized {}); } state.todos.retain(|x| x.id != todo.id); Ok(state) })?; Ok(Response::new().add_attribute("method","try_remove")) } pub fn try_update(deps: DepsMut, info: MessageInfo, todo: Todo) -> Result<Response, ContractError>{ STATE.update(deps.storage, |mut state|->Result<_,ContractError>{ if info.sender != state.owner{ return Err(ContractError::Unauthorized {}); } state.todos.retain(|x| x.id != todo.id); // remove old one state.todos.push(todo); // push back new one Ok(state) })?; Ok(Response::new().add_attribute("method","try_update")) } pub fn try_reset(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError>{ STATE.update(deps.storage, |mut state|->Result<_,ContractError>{ if info.sender != state.owner{ return Err(ContractError::Unauthorized {}); } state.todos.clear(); Ok(state) })?; Ok(Response::new().add_attribute("method","try_update")) } ``` ### Query function and module Just like the `execute` function, the `query` function also do the same thing. Handle the query message and make the function call to the corresponding function. ```rust #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> { match msg { QueryMsg::GetTodos {} => to_binary(&query_todos(deps)?), } } ``` For this part, the function will be put within the `query` module. You can also put this outside of the module just like the execute part as well, but your code might be harder to understand. ```rust pub mod query { use super::*; pub fn query_todos(deps: Deps) -> StdResult<GetTodosResponse> { let state = STATE.load(deps.storage)?; Ok(GetTodosResponse { todos: state.todos }) } } ``` After you've completed all of this, you can deploy your smart contract on the blockchain! Proceed to the next part to learn how to do it. --- # 🚀 Deploying smart contract {%youtube J_zys5RNjJA %} We'll do it 2 ways: localosmosis and testnet with beaker. ## Localosmosis ### Setup This part we'll talk about how to deploy your contract on the localosmosis (AKA your local machine). You should have localosmosis installed within your computer before proceeding. If not, or you're not installing it with localosmosis option, you could simply do the following: `curl -sL https://get.osmosis.zone/run` then choose the option in the following order. `3, 1, 1, any name you wanted` When it finished running, reset your terminal. You should be able to use `osmosisd` commands. After the installation is completed, you need to start the localosmosis node by running these commands: ``` cd ~/osmosis make localnet-start-with-state ``` ### Deployment with osmosisd #### Compile Go to folder of your project you've developed earlier and open the terminal on the folder. Run the following command to compile and optimize the smart contract. ``` sudo docker run --rm -v "$(pwd)":/code \    --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \    --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \    cosmwasm/rust-optimizer:0.12.6 ``` After this command finished, you can go to `artifacts/` folder to see the compiled contract file. #### Initialize the wallet You need to pay the gas fee before you can deploy the smart contract on the blockchain. For the localosmosis, we will use the test wallet provided by the system. In this case, we'll use `test1` wallet's seed. To add the wallet, run the command: `osmosisd keys add test1 --recover` Then paste in the wallet's seed: `notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius` Now you can proceed to the actual deployment. #### Deployment To deploy, run the command: ``` cd artifacts osmosisd tx wasm store todo.wasm  --from test1 --chain-id=localosmosis --gas-prices 0.1uosmo --gas auto --gas-adjustment 1.3 -b block -y ``` And that's it for the localosmosis deployment. You can interact with the contract within your local machine via command line or locally-run application. Also, take note of your deployment address number. You'll need it later on. ### Deployment with Beaker If the above commands is hard to follow along, you could do the equivalent using the Beaker with the following command ``` beaker wasm deploy todo --signer-account test1 --raw '{ "todos": [] }' ``` ## Testnet with Beaker This part we'll talk about how to deploy your contract on the testnet with proposal using Beaker. * Disclaimer: The testnet DO NOT required the proposal, but the mainnet DOES required. To deploy to the testnet without proposal, you can check out the tutorial [here](https://docs.osmosis.zone/cosmwasm/testnet/cosmwasm-beaker). ### Preparing proposal file We'll create a proposal file, which normally will specify the data about what the contract name is, what does it do, the code repository, etc. So create the file called `prop.yml` as a file directly under your project folder. The content of the file here will be a placeholder value. Feel free to modify it to your liking. ```yaml title: Proposal to allow DappName to be enabled in Osmosis description: | A lengthy proposal description goes here  we expect this to be many lines... deposit: 500000000uosmo code: repo: https://github.com/osmosis-labs/beaker/ rust_flags: -C link-arg=-s roptimizer: workspace-optimizer:0.12.6 ``` ### Deployment After the file has been created and saved, you can run the actual deployment command: `beaker wasm proposal store-code --proposal prop.yml --signer-account test1 --network testnet counter --gas 25000000uosmo --gas-limit 25000000` After the deployment is completed. Take note of your `proposal_id`. We'll need it to vote the proposal. ### Voting To make the deployment is completed, you'll need to vote for it to become active. In the real world scenario, you need multiple people to vote for your smart contract and it take a really long time, but for the testnet, you can vote it yourself and the voting time is short. So you need to act fast. To vote for your contract, do the following command: `beaker wasm proposal vote --option yes counter --signer-account test1 --network testnet` After the timer has run out (5 minutes), you can check on the [testnet explorer](https://faucet.osmosis.zone/#/contracts) to validate your contract status. If you've done everything correctly, you will see that your smart contract is deployed successfully. --- # 🧑‍💻 Implementing Frontend to Interact with Our Smart Contract {%youtube X6A1XOyimM4 %} --- ## 🏁Our goal In this chapter, we'll taken starter files from GitHub for frontend and take a quick tour of it. Then, we'll edit it from there to create a working frontend that can interacted with our deployed Todo smart contract. --- ## Prerequisite ### Chrome browser and Keplr extension You should have Chrome web browser and Keplr extension installed on the browser. Note that Keplr can only be install on Chrome. If you saw it on other browser, it's probably a fake extension and you SHOULD NOT INSTALL IT. ### Get project starter files on GitHub [GitHub Repo](https://github.com/nonkung51/laika-osmosis-course/blob/main/05%20-%20Implement%20the%20Frontend/frontend) Copy the code from the repository and replace the `frontend` folder. Also, don't forget to do the `npm install` command to get the frontend ready to be run as well. After you've done the package installation, you can try running the frontend with the `npm run dev` command. --- ## Starter frontend overview Most of the files are completed, but some of them still need further edit. The lines that need to be edited are marked with `TODO` comment. (If you installed the Todo Tree for Visual Studio Code, you'll see it highlighted as well.) ### api folder This folder contains the smart contract interaction file. We'll have the `counter.ts` file to look as an example for our own contract. ### components folder This folder contains the component `AddTodoItem.tsx` and `TodoItem.tsx`. We'll edit these files later on to use our smart contract API. ### interfaces folder This folder contains a single file called `todo.ts` that specify the interface for a Todo item. ### pages folder This is a simple frontend page file with mock Todo data. We'll edit this file later on to use it with the actual smart contract interaction. --- ## Create smart contract API ### Learning from counter.ts example ```tsx import useSWR from "swr"; import { getAddress, getClient, getSigningClient } from "../lib/client"; import { getContractAddr } from "../lib/state"; export const getCount = async () => { const client = await getClient(); return await client.queryContractSmart(getContractAddr(), { get_count: {} }); }; export const increase = async () => { const client = await getSigningClient(); return await client.execute( await getAddress(), getContractAddr(), { increment: {} }, "auto" ); }; export const useCount = () => { const { data, error, mutate } = useSWR("/counter/count", getCount); return { count: data?.count, error, increase: () => mutate(increase), }; }; ``` From the example, you'll see 2 functions and 1 hook. We have `getCount` and `increase` function, these will be the main functions that interact with our smart contract. The `getCount` function is used to send the query message to the smart contract, and the `increase` function is used to send the execute message to the smart contract. Note that the execute function will need to supply the sender wallet address as a parameter as well. For the `useCount` hook, this is used to "pack" all the functions to export and utilizing the `useSWR` hook to handle the data fetching for us. ### Implementing todo.ts We'll create the file called `todo.ts` to handle the smart contract interaction. Most of the implementation here will follow the same idea of `counter.ts` file. ```tsx import useSWR from "swr"; import { Todo } from "../interfaces/todo"; import { getAddress, getClient, getSigningClient } from "../lib/client"; import { getContractAddr } from "../lib/state"; export const getTodos = async () => { const client = await getClient(); return await client.queryContractSmart(getContractAddr(), { get_todos: {} }); }; export const addTodo = async (todo: Todo) => { const client = await getSigningClient(); return await client.execute( await getAddress(), getContractAddr(), { "add": { "todo":todo } }, "auto" ); }; export const updateTodo = async (todo: Todo) => { const client = await getSigningClient(); return await client.execute( await getAddress(), getContractAddr(), { "update": { "todo":todo } }, "auto" ); }; export const resetTodo = async () => { const client = await getSigningClient(); return await client.execute( await getAddress(), getContractAddr(), { "reset": {} }, "auto" ); } export const useTodo = () => { const { data, error, mutate } = useSWR("/todos/todo", getTodos); return { todos: data?.todos as Todo[] | undefined, error, add: (todo: Todo) => mutate(()=>addTodo(todo)), update: (todo:Todo) => mutate(()=>updateTodo(todo)), reset: ()=>mutate(resetTodo) }; }; ``` As you can see, we have `getTodos` to send the query message, and we have `addTodo`, `updateTodo`, and `resetTodo` to send the execute message. Then, we pack up everything within the `useTodo` hook. ### Changing contract address Remember the contract address from the previous chapter? We'll need it in this part. We'll take the address and paste it into the environment file. So, you'll want to edit both `/.beaker/state.json` and `/.beaker/state.local.json` file to use the new contract endpoint. You'll add the contract address to the file like so. ```json { "testnet": { "counter": { "code_id": <YOUR CODE ID>, "addresses": { ..., "todos contract": "<YOUR SMART CONTRACT ADDRESS>" }, "proposal": { "store_code": <YOUR STORE CODE ID> } } } } ``` Don't worry about the `counter` key, it's used here so that you don't need to edit the `/lib/state.ts` file code. Also, since our contract run on the testnet-4, we'll need to edit the `.env.local` and `.env.production` file as well to change the endpoint. ``` NEXT_PUBLIC_NETWORK=testnet NEXT_PUBLIC_RPC_ENDPOINT="https://rpc-test.osmosis.zone/" NEXT_PUBLIC_CHAIN_ID=osmo-test-4 ``` --- ## Edit component to use newly implemented smart contract We'll be editing 2 files to use the newly implement ### pages/index.ts We'll edit the] file to use the `useTodo` hook we've created earlier. First, we'll want to import `useTodo` hook at the start of the file. ```tsx import { useTodo } from "../api/todo"; ``` Then, we'll change the `todos` constant to use the `useTodo` hook like so. ```tsx const { todos, add, error, reset, update } = useTodo(); ``` Next, we'll supply the function to our components. - AddTodoItem component ```tsx <AddTodoItem addTodo={add} setLoading={setLoading} /> ``` - TodoItem component ```tsx <TodoItem key={index} todo={todo} onClick={async () => { setLoading(true); try { await update({ ...todo, is_done: !todo.is_done, }); } catch (err) { console.log(err); } setLoading(false); }} /> ``` The end result should look something like this ```tsx import type { NextPage } from "next"; import Head from "next/head"; import { useState } from "react"; import { useTodo } from "../api/todo"; import AddTodoItem from "../components/AddTodoItem"; import TodoItem from "../components/TodoItem"; import { Todo } from "../interfaces/todo"; import styles from "../styles/Home.module.css"; const Home: NextPage = () => { const [isLoading, setLoading] = useState(false); const [isOpenAddTodo, setIsOpenAddTodo] = useState(false); const { todos, add, error, reset, update } = useTodo(); return ( <div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", }} > {todos === undefined || isLoading ? ( "Loading..." ) : ( <> <h1>Todos</h1> <button style={{ background: "#38bdf8", }} onClick={() => setIsOpenAddTodo(!isOpenAddTodo)} > {isOpenAddTodo ? "Close" : "Open"} </button> {isOpenAddTodo && ( <AddTodoItem addTodo={add} setLoading={setLoading} /> )} {todos.length > 0 ? ( <> <ul> {todos.map((todo, index) => ( <TodoItem key={index} todo={todo} onClick={async () => { setLoading(true); try { await update({ ...todo, is_done: !todo.is_done, }); } catch (err) { console.log(err); } setLoading(false); }} /> ))} </ul> <button style={{ backgroundColor: "#fca5a5", }} onClick={async () => { setLoading(true); try { await reset(); } catch (err) { console.log(err); } setLoading(false); }} > Remove All Todos </button> </> ) : ( <p>There is no Todo</p> )} </> )} </div> ); }; export default Home; ``` ### AddTodoItem.tsx Since we're using the actual function from `useTodo` hook, we'll also need to change the implementation inside this component as well. On the submit button, we'll modify it like so. ```tsx <button type='submit' style={{ backgroundColor: "#bae6fd", }} onClick={async (event) => { event.preventDefault(); setLoading(true); try { await addTodo({ ...todo, id: Math.floor(Math.random() * 9999), }); } catch (err) { console.log(err); } setLoading(false); }} > Add Todo </button> ``` And if you've complete this file successfully, you will be able to do the `npm run dev` again to validate the result.