# Building a Telegram Game on Starknet with Argent's Wallet SDK: A Step-by-Step Guide First i want to thank you all for liking and interacting with the thread i wrote earlier on X, it is the main factor that motivated me to write this article with the aim of simplifiying the process for anyone who wants to integrate the argent's telegram wallet sdk into their telegram mini app. Secondly if you are a fan of "building while learning" this is the best article for you, as we will be building a simple word guessing game integrated to starknet through the argent telegram wallet before the end of this article. If you are already excited about the goodies at the end, join the train and lets roll ๐Ÿฅณ. ![argent](https://hackmd.io/_uploads/rkcN9eAJ1e.png) Now, Lets talk about the logic of our game. On start of every round, a set of 9 random Alphabets are generated and a random alphabet is also picked from these alphabets which is regarded as the right guess, the player is allowed to pick any of this alphabets, if the alphabet picked by the player is equal to the right guess, then the player earns 2-STRK and is allowed to cliam immediately, if the player doesnt guess right he is allowed to play again. we will be building this in 7 simple steps, but before then lets highlight all the technologies we will be using while building. For the frontend of our mini app we will be using react, for the game logic we will be using javascript and finally we will be deploying a simple smart contract on starknet written in cairo which will be a store to all of the rewards (STRK) to be disbursed to an eligible player's telegram wallet. ### Steps 1. Creating a telegram test Account 2. creating an Argent telegram wallet with the `@ArgentTestBot` on telegram 3. creating a telegram bot with `@BotFather` on telegram 4. creating our user interface with `react` and integrating the telegram wallet sdk. 5. writing and deploying the smart-contract on `starknet blockchain` 6. Integrating our telegram mini app with the smart-contract using the `Argent telegram sdk`. 7. creating a newapp with `@BotFather`. let's begin ๐Ÿ˜Š ### Step 1 - Creating a telegram test account You can create a telegram test account depending on the type of device you are using, for this article i will be demonstrating how you can do it on `ios`, `Telegram desktop` and `macos`. * **IOS:** tap 10 times on the Settings icon > Accounts > Login to another account > Test. * **Telegram Desktop:** open โ˜ฐ Settings > Shift + Alt + Right click โ€˜Add Accountโ€™ and select โ€˜Test Serverโ€™. * **MacOS:** click the Settings icon 10 times to open the Debug Menu, โŒ˜ + click โ€˜Add Accountโ€™ and log in via phone number. *Note: The test environment is completely separate from the main environment, so you will need to create a new user account and a new bot with @BotFather.* ### Step 2 - Creating an Argent telegram wallet You can open an argent wallet by interacting with the ` @ArgentTestBot` in your telegram test account, Press `start` and click the `open wallet` button to create a wallet. Now that you have an account, copy your account address and fund it with STRK using the **Starknet Sepolia Faucet** [https://starknet-faucet.vercel.app/](https://). The fastest way to activate you account is to send 1STRK to your newly created telegram wallet, this will automatically deploy the account. congratulations ๐ŸŽ‰ you now have an Argent telegram wallet and you are ready to start integrating with your mini app. ### Step 3 - Creating a telegram bot To create a telegram bot head over to your telegram test account and start a conversion with `@BotFather`. * send `/newbot` - this returns a prompt asking you to name your bot. * lastly provide a username for your bot congratulations ๐ŸŽ‰ you just created a telegram bot. Telegram provides you a token which you can used to interact with your bot in your code through the bot api. Here is a link to the bot api [https://core.telegram.org/api#bot-api](https://) learn more about telegram bots here ๐Ÿ‘‰๐Ÿผ [https://core.telegram.org/bots](https://) ### Step 4 - Creating our user interface and integrating Argent telegram wallet sdk You can use any framework or tool to create your frontend but for the sake of this article i will be using react + vite project with typescript. below is a screenshot of my already spinned projects using the command ; ``` yarn create vite ``` Insert a project name > select React as framework > select typescript or typescript + swc variant ![Screenshot 2024-10-15 at 16.16.17](https://hackmd.io/_uploads/rkyEdWnkye.png) Next we will install all dependencies needed for this project ``` yarn add @argent/tma-wallet react-icons tailwindcss ``` here is what each installed dependency will be doing * **@argent/tma-wallet:** This is the argent telegram wallet sdk that provides us with the interface to connect with and interact with the starknet blockchain via our mini app. you can read more about this amazing sdk here ๐Ÿ‘‰๐Ÿผ [https://www.npmjs.com/package/@argent/tma-wallet?activeTab=readme](https://) * **react-icons:** This library provides the icons we will be using within our mini app. * **tailwindcss:** This library will be used for styling our mini frontend Next we add the telegram script to the heading tag of the index.html file in the root of the frontend folder, this is will give us access to the telegram.window object which will regard our react app as a telegram mini app. ``` <script src="https://telegram.org/js/telegram-web-app.js"></script> ``` ![Screenshot 2024-10-15 at 17.08.39](https://hackmd.io/_uploads/SJ9iQGny1e.png) ### Step 5 - writing and deploying the smart-contract on starknet We will be writing a simple smart contract on starknet blockchain, to achieve this we will be using the `snforge` command from `snfoundry` to spin up a cairo project ``` snforge init <project_name> ``` ![Screenshot 2024-10-15 at 17.24.17](https://hackmd.io/_uploads/Syr8vf2Jyg.png) create new file in the ./src folder called `game.cairo` and add the following code ``` #[starknet::interface] pub trait IGame<TContractState> { fn claimPoints (ref self: TContractState, _points: u256); } #[starknet::contract] pub mod Game { use super::{IGame}; use starknet::{ContractAddress, get_caller_address}; use game_contract::erc20_interface::{IERC20Dispatcher, IERC20DispatcherTrait}; #[constructor] fn constructor(ref self: ContractState, _token_addr: ContractAddress) { self.token_address.write(_token_addr); } #[storage] struct Storage { token_address: ContractAddress, } #[event] #[derive(Drop, starknet::Event)] enum Event { RewardCliamed: RewardCliamed } #[derive(Drop, starknet::Event)] struct RewardCliamed { player: ContractAddress, amount: u256 } #[abi(embed_v0)] impl GameImpl of IGame<ContractState> { fn claimPoints (ref self: ContractState, _points: u256) { let caller = get_caller_address(); let strk_erc20_contract = IERC20Dispatcher { contract_address: self.token_address.read() }; strk_erc20_contract.transfer(caller, _points); self.emit( RewardCliamed {player: caller, amount: _points}); } } } ``` create a new file called `erc20_interface.cairo` in ./src folder and paste the following code in it ``` use starknet::ContractAddress; #[starknet::interface] pub trait IERC20<TContractState> { fn get_name(self: @TContractState) -> felt252; fn get_symbol(self: @TContractState) -> felt252; fn get_decimals(self: @TContractState) -> u8; fn get_total_supply(self: @TContractState) -> u256; fn balance_of(self: @TContractState, account: ContractAddress) -> u256; fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256); fn transfer_from( ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 ); fn approve(ref self: TContractState, spender: ContractAddress, amount: u256); fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: u256); fn decrease_allowance( ref self: TContractState, spender: ContractAddress, subtracted_value: u256 ); } ``` next we clear all the items in `lib.cairo` and paste the following code. ``` pub mod game; pub mod erc20_interface; ``` Finally we configure our `scarb.toml` file and deploy our smart contract, the smart contract has an argument in the constructor which is the contract address of the erc20 token we will be using as rewards to users. > In our case we are using the **STRK** token and here is the contract address on sepolia network`0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d` Follow this guide to let get started with starknet foundry ๐Ÿ‘‰๐Ÿผ [https://foundry-rs.github.io/starknet-foundry/getting-started/first-steps.html](https://) you can also learn how to declare and deploy smart contracts using `snforge` here ๐Ÿ‘‰๐Ÿผ [](https://) > Ensure you have you have scarb and starknet foundry installed on your device if you haven't make sure you install befor you proceed. ### Step 6 - Integrating our telegram mini app with the smart-contract using the argent telegram sdk Finally we have setup our guessing game, the last step is to integrate the telegram sdk and have interaction with our smart contract deployed on the starknet blockchain. This smart contract is a store for all our rewards ๐ŸŽ in STRK. To cliam this reward an elegible user has to interact with our smart contract. > if you where unable deploy a smart contract, you can use to this already deployed smart contract on sepolia test network `0x03891b46cdd780984a4954a3d54d00051ee761e068b56274c0762dfe80d7d4d9` make sure you fund the contract with strk to serve as the rewards for your users In the `App.tsx` page replace the content with this ``` import { useEffect, useState } from "react"; import { ArgentTMA, SessionAccountInterface } from "@argent/tma-wallet"; import { Account, Contract, AccountInterface } from "starknet"; import Game from '../components/Game'; const argentTMA = ArgentTMA.init({ environment: "sepolia", // "sepolia" | "mainnet" (not supperted yet) appName: "yor app name", // Your Telegram app name appTelegramUrl: "https://t.me/example_bot/example", // Your Telegram app URL sessionParams: { allowedMethods: [ // List of contracts/methods allowed to be called by the session key { contract: "contracts address", selector: "function name", } ], validityDays: 90 // session validity (in days) - default: 90 }, }); function App() { const [account, setAccount] = useState<SessionAccountInterface | undefined>(); const [isConnected, setIsConnected] = useState<boolean>(false); useEffect(() => { // Call connect() as soon as the app is loaded argentTMA .connect() .then((res) => { if (!res) { // Not connected setIsConnected(false); return; } // Connected const { account, callbackData } = res; if (account.getSessionStatus() !== "VALID") { // Session has expired or scope (allowed methods) has changed // A new connection request should be triggered // The account object is still available to get access to user's address // but transactions can't be executed const { account } = res; setAccount(account); setIsConnected(false); return; } // Connected // const { account, callbackData } = res; // The session account is returned and can be used to submit transactions setAccount(account); setIsConnected(true); // Custom data passed to the requestConnection() method is available here console.log("callback data:", callbackData); }) .catch((err) => { console.error("Failed to connect", err); }); }, []); const handleConnectButton = async () => { // If not connected, trigger a connection request // It will open the wallet and ask the user to approve the connection // The wallet will redirect back to the app and the account will be available // from the connect() method -- see above await argentTMA.requestConnection("custom_callback_data"); }; // useful for debugging const handleClearSessionButton = async () => { await argentTMA.clearSession(); setAccount(undefined); }; console.log(account) return ( <div className="h-auto"> {/* Navbar */} <div className="bg-gray-700 p-4 flex "> {!isConnected && <button className="py-2 px-4 rounded-lg bg-slate-300 text-gray-700" onClick={handleConnectButton}>Connect</button>} {isConnected && ( <div className="flex justify-between items-center w-full"> <p className="text-green-500 text-[12px]"> Account address: <code>{account?.address.slice(0, 12)} ...</code> </p> <button className="text-sm p-2 bg-white rounded-lg bg-tomato-300 " onClick={handleClearSessionButton}>Clear Session</button> </div> )} </div> {/* outlet */} <div className="grid grid-cols-1"> {isConnected && <Game context={account}/>} </div> </div> ); } export default App; ``` With the following code we just integrated the argent telegram wallet in our mini app, we successuful handled the sessions and set the functions we want our account to interact with during this sessions. The `handleConnectButton` and `handleClearSessionButton` are incharge of handling account connections and clearing of sessions whis is basically disconnecting the account. To add in the game logic and more styling to our frontend, we create a new file `./components/Game.tsx` and paste the code below ``` import React, { useEffect, useState } from 'react' import { TfiControlPlay, TfiFaceSad, TfiGift, TfiWallet } from 'react-icons/tfi'; import { toast } from 'react-toastify'; import {Contract, cairo, AccountInterface} from 'starknet' import { SessionAccountInterface } from "@argent/tma-wallet"; import gameAbi from '../utils/Abi/game.json'; import { useOutletContext } from 'react-router-dom'; import { FaSpinner } from 'react-icons/fa6'; function Game() { const [ind, setInd] = useState<any>(); const [ans, setAns] = useState<any>(); const [isModalVisible, setIsModalVisible] = useState(false); const [isUserCorrect, setIsUserCorrect] = useState(false); const [plays, setPlays] = useState<any>(0); const [points, setPoints] = useState<any>(0); const [pending, setPending] = useState<boolean>(false) const [error, setError] = useState<string>('') const account = useOutletContext(); const game_addr = "0x03891b46cdd780984a4954a3d54d00051ee761e068b56274c0762dfe80d7d4d9"; const gameContract = new Contract(gameAbi, game_addr, (account as AccountInterface)); const alphabets = 'abcdefghijklmnopqrstuvwxyz'.split(''); const start = () => { let indexed = [] for(let i: number = 0; i < 9; i++) { let num = Math.floor(Math.random() * 10) indexed.push(num) } setAns(alphabets[indexed[Math.floor(Math.random() * 10)]]) setInd(indexed) console.log(ans) } const compareAns = (a: String) => { setPlays(plays + 1) if(a == ans) { setIsUserCorrect(true) setIsModalVisible(true); setPlays(0) setPoints(points + 2) start() }else{ setIsUserCorrect(false) setIsModalVisible(true); } } const handleCliamRewards = async () => { setPending(true) try { await gameContract.claimPoints(Number(points)) setPending(false) setPoints(0) toast.success("reward cliamed"); } catch (error: any) { setPending(false) toast.error(error.message) setError(error.message) } } const closeModal = () => { setIsModalVisible(false); }; useEffect(() => { // Automatically close the modal after 1 second const timer = setTimeout(() => { closeModal(); }, 1000); // Clean up the timer return () => clearTimeout(timer); }, [compareAns]); if(!pending) { return ( <div className='h-inherit gap-y-4 mt-2 grid grid-cols-1'> {/* <span className='bg-slate-200 p-2 items-center rounded-lg flex gap-x-4 items-center mr-auto'> <TfiWallet /> <small className='text-blue-700'>STRK <small className='font-bold text-[15px] text-slate-700'>{4}</small></small> </span> */} <div className='flex border border-slate-300 p-2 justify-between'> <button onClick={start} className='py-2 px-4 text-3xl text-red-600 font-bold'>start</button> <div className='flex items-center gap-x-4 text-[22px]'> <div className='flex items-center gap-x-2'><small className='font-bold text-slate-600'>{plays}</small><TfiControlPlay /></div> <div className='flex items-center gap-x-2'><small className='font-bold text-green-600'>${points}</small><TfiGift /></div> </div> </div> {ind && ind.length > 0 && <div className='grid grid-cols-3 gap-6'> {ind.map((item: any) => ( <input type='button' onClick={(e) => {compareAns(e.target.value)}} value={alphabets[item]} className='text-3xl hover:bg-orange-200 text-center text-slate-700 bg-white rounded-[15px] shadow-md font-bold p-5'/> ))} </div>} <div className=' p-2 flex justify-center gap-x-8'> <button onClick={handleCliamRewards} className='border gap-y-2 p-5 flex flex-col items-center bg-lime-200 bg-opacity-50'> <TfiGift size={30} /> <small className='font-semibold'>cliam reward</small> </button> <button onClick={start} className='border w-[25%] gap-y-2 p-5 flex flex-col items-center'> <TfiFaceSad size={30} color=''/> <small className='font-semibold'>reshuffle</small> </button> </div> {/* loading state */} {error && error != '' &&<div> <p>{error}</p> </div>} {/* modal */} {isModalVisible && <div className='top-0 grid grid-cols-1 h-screen bg-black w-full fixed bg-opacity-60'> <div className='w-full m-auto flex flex-col'> {isUserCorrect ? <div className='flex flex-col justify-center gap-x-4'> <h4 className='text-center'> ๐ŸŽ‰</h4> <h4 className='text-white text-center text-bold'>GO CLIAM YOUR REWARD +2 !!!!</h4> </div> : <div className='flex flex-col justify-center gap-x-4'> <h4 className='text-center text-[40px]'>โŒ</h4> <h4 className='text-white text-center text-bold'>TRY AGAIN !!!!</h4> </div> } </div> </div>} </div> ) } else { return ( <div className='w-full flex flex-col text-green-600 font-bold'> <div className='flex flex-col gap-y-4 items-center p-5 m-auto w-full'> <FaSpinner size={40} className='animate-spin'/> <small className='m-auto text-slate-400'>Claiming Reward....</small> <small className='text-slate-400'>To Players wallet ๐Ÿ‘‡๐Ÿผ</small> <small className='m-auto text-[8px]'>{account?.address}</small> </div> </div> ) } } export default Game ``` ### Step 7 - creating a newapp with `@BotFather`: Now that our frontend is fully ready we can deploy it to any hosting service of our choice. Head over to telegram and interact with `@BotFather`, send `/newapp` for `@BotFather` to be able to create an app for you. fill in all required details like `title`, `description`, `bot`, `logo`, `gif`, `app url` etc. > The logo image must be of size (640x360) > your app url is your frontend deployment link After providing all neccessary details, telegram creates generates a link we can use to access our mini app. congratulations ๐ŸŽ‰ you just made it to the end of this article while building a word guessing game that distributes rewards to players in STRK. I hope you had a great read, and you find this article useful.