# StarkNet Frontends w/ Cairopal & Argent X Full-Stack Starknet: * **[Part 1]** 🚧 [Getting Started in Cairo & Deploying with Nile](https://hackmd.io/@sambarnes/BJvGs0JpK) * **[Part 2]** 🐍 [Contract Interaction with starknet.py](https://hackmd.io/@sambarnes/H1Fx7OMaF) * **[Part 3]** πŸ‘₯ [StarkNet Account Abstraction & Using Standard Contracts](https://hackmd.io/@sambarnes/rkGekNvAY) * **[Part 4]** πŸ’½ [Local Devnet & Starknet.py's Account Capabilities](https://hackmd.io/@sambarnes/By7kitOCt) * **[Part 5]** 🎨 [StarkNet Frontends w/ Cairopal & Argent X](https://hackmd.io/@sambarnes/HydPlH9CY) (***you are here***) * **[Notes]** πŸ’° [Contract Costs & Why Our Design Needs Work](https://hackmd.io/@sambarnes/SkxMZHhRK) *Completed code on GitHub [here](https://github.com/sambarnes/fullstack-starknet)*. We've built the cairo contracts and a raspberry pi server that will attest to a vehicle state periodically. Ideally though, a user shouldn't need to issue commands on the pi to register a vehicle or rotate the signing account. They should instead get a more modern experience: a webapp that connects to their [Argent X](https://github.com/argentlabs/argent-x) browser extension (current leading wallet provider). Not a huge fan of frontend work personally. Just never caught the JS bug 🀷 (I'm not bitter about it, I swear) Fortunately, a few community members have crafted [cairopal](https://github.com/abigger87/cairopal) -- a batteries-included frontend template for dapps on StarkNet. Thanks to their heavy lifting (seriously, thank you πŸ™ŒπŸ™) we have a massive head start in making a functional frontend with a smooth and clean UI. > πŸ’‘ If you're coming here just looking for React library to use, check out [starknet-react](https://github.com/auclantis/starknet-react) from [Francesco Ceccon](https://github.com/fracek). The template we'll use here is based on an early version of that work. Though it may be ported in the near future to use startknet-react now that it's an official package. We'll use this head start to quickly create a fontend that: * connects to the Argent X extension * sends transactions through a user's account in that extension * registers vehicles with the system * enables changing the vehicle signing authority ## Getting started Clone cairopal to be the base of our project: ``` sam@sam:~/fullstack-starknet/part5$ git clone https://github.com/abigger87/cairopal frontend sam@sam:~/fullstack-starknet/part5$ cd frontend ``` Install the dependencies with a `yarn install` and start it with a `yarn dev` -- then we'll be able to see the site at http://localhost:3000 ![template](https://i.imgur.com/bg1sf40.png) Looks pretty sleek without needing to modify much. Right out of the box, we can connect our account in the [Argent X](https://chrome.google.com/webstore/detail/argent-x-starknet-wallet/dlcobpjiigpikoobohmabehhmhfoodbb) browser extension. ![uses argent](https://i.imgur.com/2SrFI66.png) Two foundational components are included as well: * Transaction List - a view into a user's past actions with our contract * Mint Tokens Button - a way to invoke contract functions, automatically proxied through our connected account contract Between these, we should be able to re-use the same underlying mechanisms to build out our frontend. But before we move on, remember back to [Part 4](https://hackmd.io/@sambarnes/By7kitOCt) and try to setup our dev session. > ✨ Exercise: Start a local devnet and deploy our contract. Then, use the Argent X wallet to deploy a new account on that same local network. :::spoiler First, a `nile node` to bring up the local network: ``` (venv) sam@sam:~/fullstack-starknet/part4/starknet$ nile node * Running on http://localhost:5000/ (Press CTRL+C to quit) ``` Then, a compile & deploy: ``` (venv) sam@sam:~/fullstack-starknet/part4/starknet$ nile compile πŸ€– Compiling all Cairo contracts in the contracts directory πŸ”¨ Compiling contracts/IAccount.cairo πŸ”¨ Compiling contracts/ERC165_base.cairo πŸ”¨ Compiling contracts/Initializable.cairo πŸ”¨ Compiling contracts/Account.cairo πŸ”¨ Compiling contracts/contract.cairo πŸ”¨ Compiling contracts/utils/safemath.cairo πŸ”¨ Compiling contracts/utils/constants.cairo βœ… Done # NOTE: you may need to `rm localhost.deployments.txt` first to clear current aliases (venv) sam@sam:~/fullstack-starknet/part4/starknet$ nile deploy --network=localhost contract --alias blackbox πŸš€ Deploying contract ⏳ ️Deployment of contract successfully sent at 0x04f2d8ea9774229a040924c37b12a9244bae7451000502612340488e659206f2 🧾 Transaction hash: 0x01cdff319cdfaafa3caf419f62e02c9571e314f487401240b1e9d755aa3783c9 πŸ“¦ Registering deployment as blackbox in localhost.deployments.txt ``` To verify it was deployed, we can check with the CLI: ``` (venv) sam@sam:~/fullstack-starknet/part4/starknet$ starknet tx_status --hash 0x01cdff319cdfaafa3caf419f62e02c9571e314f487401240b1e9d755aa3783c9 --feeder_gateway_url http://localhost:5000 { "block_hash": "0x039ed1060ae667d44c93ea7f3f0c2de409768f13299eca7ccdc9cbbb25ef0c13", "tx_status": "ACCEPTED_ON_L2" } ``` > πŸ’‘ Tip: you can use this same command to display error messages from rejected transactions Finally, open the extension & select the localhost network. Clicking the + button will deploy to our local network. ![Add Account](https://i.imgur.com/kTGyv3l.png) ::: --- > NOTE: If you want to skip local devnet and just work against testnet, use this goerli contract I deployed: [0x0220d51c9f7ad564c4cc2fecfeeebb35f18646329c8f5cfcc5b6ec00cf31cbc0](https://goerli.voyager.online/contract/0x0220d51c9f7ad564c4cc2fecfeeebb35f18646329c8f5cfcc5b6ec00cf31cbc0) > You'll still need your own argent account deployed to goerli. ## Registration Button As we saw earlier, there's a Transactions List view and a Mint Tokens button to build off of. If ya go to the [`<MintTokens>` component](https://github.com/abigger87/cairopal/blob/af4d2c95e2f4552c564815fb9e7f72c9d3f62245/src/components/wallet/MintTokens.tsx), we can rename this file (and it's references) to `RegisterVehicle`. That will be the only responsibility of this component. The first obvious change is to set the contract address to what we've deployed. ```javascript const CONTRACT_ADDRESS = "0x04f2d8ea9774229a040924c37b12a9244bae7451000502612340488e659206f2"; ``` We're also going to need access to the currently connected account address. We can bring it into scope by modifying this line: ```javascript const { connected, library, account } = useStarknet(); ``` Then head down to the [`mintTokens` function](https://github.com/abigger87/cairopal/blob/af4d2c95e2f4552c564815fb9e7f72c9d3f62245/src/components/wallet/MintTokens.tsx#L28), where the contract interaction happens. Let's modify it to match the function signature we want to hit (`func register_vehicle(vehicle_id : felt, signer_address : felt):`). Overall it should feel pretty similar to how we've called our contracts from python. Just a slightly different layout. ```javascript const registerVehicle = async () => { // The account address is a large hex value, as a string. We need it as a felt. // A `parseInt(account, 16).toString()` might seem like it would work at first, // but it actually prints in scientific notation. A BigInt can show all digits. const account_big = BigInt(account!); const account_address_param = account_big.toString(10);; const registerVehicleResponse = await library.addTransaction({ type: "INVOKE_FUNCTION", contract_address: CONTRACT_ADDRESS, entry_point_selector: selector, calldata: [ "42", // vehicle_id account_address_param, // signer address (same as owner for simplicity) ], }); // eslint-disable-next-line no-console console.log(registerVehicleResponse); }; ``` Heading back to our refreshed site, we should be able to try it out: ![Sign Vehicle Registration](https://i.imgur.com/GVntdyv.png) Once sent & accepted, we can use nile to check if the owner of vehicle 42 was updated: ``` (venv) sam@sam:~/fullstack-starknet/part4/starknet$ nile call blackbox get_owner 42 0x466d9a1ecaab4d0e6cf9f791205cc82f05d1f5c77474314749f10cc2bcbc796 ``` If you were pointing your frontend at the testnet contract I mentioned earlier, you won't be able to see the transaction in the contract's explorer page (at least, not at time of writing). Rather, you'll see it show up on your account's contract page in the explorer. You can however query the testnet contract to see if the transaction updated the state: ![testnet explorer query](https://i.imgur.com/8ILOk7y.png) > ✨ Exercise: Can you figure out how to accept Vehicle ID using an input box? :::spoiler Never heard of `chakra ui` before, but a quick google came up with [an Input component](https://chakra-ui.com/docs/form/input). Using the docs I was able to come up with a quick and dirty implementation: ```jsx const [value, setValue] = React.useState(''); const handleChange = (event: any) => setValue(event.target.value); return ( <Box> <Text as="h2" marginTop={4} fontSize="2xl"> Register Vehicle </Text> <Box d="flex" flexDirection="column"> <Code marginTop={4} w="fit-content"> contract: {/* {`${CONTRACT_ADDRESS.substring(0, 4)}...${CONTRACT_ADDRESS.substring( CONTRACT_ADDRESS.length - 4 )}`} */} <Link isExternal textDecoration="none !important" outline="none !important" boxShadow="none !important" href={`https://voyager.online/contract/${CONTRACT_ADDRESS}`} > {CONTRACT_ADDRESS} </Link> </Code> // Show an input box that calls an event handler on change {connected && ( <Input my={4} variant="flushed" placeholder="Vehicle ID" onChange={handleChange} /> )} {connected && ( <Button my={4} w="fit-content" onClick={() => { registerVehicle(value); }} > Register Vehicle </Button> )} {!connected && ( <Box backgroundColor={colorMode === "light" ? "gray.200" : "gray.500"} padding={4} marginTop={4} borderRadius={4} > <Box fontSize={textSize}> Connect your wallet to register a vehicle. </Box> </Box> )} </Box> </Box> ); ``` It doesn't do any input validation like we'd want in prod, but good enough to demo. Try registering some arbitrary integer like `5`, then checking the owner. Reading from contract state shows us it went through! ``` (venv) sam@sam:~/fullstack-starknet/part4/starknet$ nile call blackbox get_owner 5 0x466d9a1ecaab4d0e6cf9f791205cc82f05d1f5c77474314749f10cc2bcbc796 ``` ::: ## Updating the signer So the frontend can be used to register a vehicle, and the rapberry pi can sign & commit state attestations on chain using the registered account. Though, we still need a way to send a `set_signer()` transaction to let the owner change what account has the signing rights. Doesn't really make much sense to put that logic in the onboard raspberry pi. Instead, it should be a new React component on the frontend of our management dashboard. Some quick copypasta from the last component brings us to this starter template: ```jsx= import { Box, Button, Input, Text, useBreakpointValue, useColorMode, } from "@chakra-ui/react"; import { stark } from "starknet"; import { CONTRACT_ADDRESS } from "./consts"; import { useStarknet } from "context"; import React from "react"; const UpdateSigner = () => { const { connected, library, account } = useStarknet(); const { colorMode } = useColorMode(); const textSize = useBreakpointValue({ base: "xs", sm: "md", }); // TODO: implement the contract interaction const updateSigner = async (vehicleId: string, newSignerAddress: string) => { }; return ( <Box> <Text as="h2" marginTop={4} fontSize="2xl"> Update Signing Authority </Text> <Text marginTop={4}> Vehicle signing key lost? Compromised and not able to be updated in the account? <br /> Use your vehicle owner's account to authorize a different account to sign state commitments. </Text> {connected && ( <Box d="flex" flexDirection="column"> // TODO: add the frontend inputs here </Box> )} {!connected && ( <Box d="flex" flexDirection="column"> <Box backgroundColor={colorMode === "light" ? "gray.200" : "gray.500"} padding={4} marginTop={4} borderRadius={4} > <Box fontSize={textSize}> Connect your wallet to change the authorized signer account. </Box> </Box> </Box> )} </Box> ); }; export default UpdateSigner; ``` > ✨ Exercise: Using the building blocks from the last section, add a `UpdateSigner` component below `RegisterVehicle`. It should take in a vehicle ID and a new signer account address as input, and invoke our `set_signer(vehicle_id, signer_address)` function. :::spoiler ```jsx= import { Box, Button, Input, Text, useBreakpointValue, useColorMode, } from "@chakra-ui/react"; import { stark } from "starknet"; import { CONTRACT_ADDRESS } from "./consts"; import { useStarknet } from "context"; import React from "react"; const UpdateSigner = () => { const { connected, library, account } = useStarknet(); const { colorMode } = useColorMode(); const textSize = useBreakpointValue({ base: "xs", sm: "md", }); const { getSelectorFromName } = stark; const selector = getSelectorFromName("set_signer"); // Similar to last time, we just invoke a different function const updateSigner = async (vehicleId: string, newSignerAddress: string) => { const accountAddressParam = BigInt(newSignerAddress!).toString(10); const setSignerResponse = await library.addTransaction({ type: "INVOKE_FUNCTION", contract_address: CONTRACT_ADDRESS, entry_point_selector: selector, calldata: [ vehicleId, accountAddressParam, ], }); // eslint-disable-next-line no-console console.log(setSignerResponse); }; // A couple state variables for our inputs & their setters. const [vehicleId, setVehicleId] = React.useState(''); const [vehicleSignerAddress, setSignerAddress] = React.useState(''); const handleVehicleIdChange = (event: any) => setVehicleId(event.target.value); const handleSignerAddressChange = (event: any) => setSignerAddress(event.target.value); return ( <Box> <Text as="h2" marginTop={4} fontSize="2xl"> Update Signing Authority </Text> <Text marginTop={4}> Vehicle signing key lost? Compromised and not able to be updated in the account? <br /> Use your vehicle owner's account to authorize a different account to sign state commitments. </Text> {connected && ( <Box d="flex" flexDirection="column"> // Same input types, just more of em <Input my={4} placeholder="Vehicle ID" onChange={handleVehicleIdChange} /> <Input my={4} placeholder="Signer Account Address (0x...)" onChange={handleSignerAddressChange} /> <Button my={4} w="fit-content" onClick={() => { updateSigner(vehicleId, vehicleSignerAddress); }} > Update </Button> </Box> )} {!connected && ( <Box d="flex" flexDirection="column"> <Box backgroundColor={colorMode === "light" ? "gray.200" : "gray.500"} padding={4} marginTop={4} borderRadius={4} > <Box fontSize={textSize}> Connect your wallet to change the authorized signer account. </Box> </Box> </Box> )} </Box> ); }; export default UpdateSigner; ``` ![update component](https://i.imgur.com/bxVGVU5.png) ::: ## Transaction List You may have noticed one thing that didn't work right away -- the Transaction List. I'm uhh still trying to figure that one out myself πŸ˜…. I was able to take a stab at porting cairopal to use [starknet-react](https://github.com/auclantis/starknet-react), in [this PR](https://github.com/abigger87/cairopal/pull/1). Though since you may read this before that change goes in, you can check out the starknet-react demo site in the meantime. The transaction manager is used here: https://github.com/auclantis/starknet-react/blob/main/website/src/components/Demo.tsx#L146-L178