diff --git a/cartesi-rollups/tutorials/eact-frontend-application.md b/cartesi-rollups/tutorials/eact-frontend-application.md new file mode 100644 index 00000000..0971bd6f --- /dev/null +++ b/cartesi-rollups/tutorials/eact-frontend-application.md @@ -0,0 +1,1400 @@ +--- +id: react-frontend-application +title: Build a React frontend for Cartesi dApps +resources: + - url: https://github.com/Mugen-Builders/cartesi-frontend-tutorial + title: Source code for the frontend application + - url: https://www.michaelasiedu.dev/posts/deploy-erc20-token-on-localhost/ + title: Deploying an ERC20 Token for Localhost Testing and Adding to MetaMask + - url: https://www.michaelasiedu.dev/posts/deploy-erc721-token-on-localhost/ + title: Deploying an ERC721 Token for Localhost Testing and Adding to MetaMask +--- + +This tutorial will focus on building a frontend for a Cartesi dApp using [React.js](https://create-react-app.dev/docs/getting-started). Our primary goal is to create a minimalistic frontend with all the functionalities for seamless interactions with any Cartesi backend. + +## Setting up the environment + +To build a frontend for Cartesi dApps, we'll use React.js along with [Wagmi](https://wagmi.sh/), a library that simplifies blockchain interactions in React applications. + +### Creating a new React project + +To get started quickly with a pre-configured React project that includes Wagmi, you can use the `create-wagmi` CLI command. For detailed instructions on setting up a Wagmi project, refer to [the official Wagmi documentation](https://wagmi.sh/react/getting-started). + +Once you've set up your project, you'll have a basic structure that includes: + +- A main configuration file for blockchain interactions +- A main App component +- An entry point file + +## Connecting wallet and chains + +The `wagmi.ts` file is the main configuration file for multiple chains and an `injected` connector. + +- Enhance the `wagmi.ts` configuration by adding all the chains supported by Cartesi Rollups. + +- Replace the transports property with a Viem Client integration via the `client` property to have finer control over Wagmi’s internal client creation. + +Edit the `src/wagmi.ts` file: + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + +

+
+```javascript
+import { http, createConfig } from "wagmi";
+import {
+  anvil,
+  arbitrum,
+  arbitrumGoerli,
+  base,
+  baseSepolia,
+  mainnet,
+  optimism,
+  optimismGoerli,
+  sepolia,
+} from "wagmi/chains";
+import { coinbaseWallet, injected, walletConnect } from "wagmi/connectors";
+import { createClient } from "viem";
+
+export const config = createConfig({
+  chains: [
+    anvil,
+    mainnet,
+    sepolia,
+    arbitrum,
+    arbitrumGoerli,
+    optimismGoerli,
+    optimism,
+    base,
+    baseSepolia,
+  ],
+  connectors: [
+    injected(),
+    coinbaseWallet(),
+    walletConnect({ projectId: import.meta.env.VITE_WC_PROJECT_ID }),
+  ],
+  client({ chain }) {
+    return createClient({ chain, transport: http() });
+  },
+});
+
+declare module "wagmi" {
+  interface Register {
+    config: typeof config;
+  }
+}
+
+```
+
+
+
+
+ +:::note supported networks +You can find the list of supported chains and their IDs in the [deployment guide](../deployment/introduction.md/#supported-networks). +::: + +### Building the Account component + +Let's create an implementation for easy network switching and a comprehensive wallet management interface. + +Move the account connection and management logic to a separate component for a cleaner and more organized `App.tsx`. + +We will add [Tailwind CSS classes](https://tailwindcss.com/docs/guides/vite) ensure visual appeal. + +Create a new file `src/components/Account.tsx` and edit the `App.tsx`: + + +

+
+```javascript
+import { useAccount, useConnect, useDisconnect, useSwitchChain } from "wagmi";
+import { useState } from "react";
+
+const Account = () => {
+  const account = useAccount();
+  const { connectors, connect, status, error } = useConnect();
+  const { disconnect } = useDisconnect();
+  const { chains, switchChain } = useSwitchChain();
+  const [isChainDropdownOpen, setIsChainDropdownOpen] = useState(false);
+
+  return (
+    
+
+

Account

+ +
+

+ Status: {account.status.toLocaleUpperCase()} +

+

+ Address:{" "} + {account.addresses?.[0]} +

+

+ Chain ID: {account.chain?.name} | {account.chainId} +

+
+ + {/* Display chain switching and disconnect options when connected */} + {account.status === "connected" && ( +
+ {/* Chain switching dropdown */} +
+ + {/* Dropdown menu for chain options */} + {isChainDropdownOpen && ( +
+ {chains.map((chainOption) => ( + + ))} +
+ )} +
+ {/* Disconnect button */} + +
+ )} +
+ + {/* Connect section */} +
+

Connect

+
+ {connectors.map((connector) => ( + + ))} +
+
Status: {status.toLocaleUpperCase()}
+
{error?.message}
+
+
+ ); +}; + +export default Account; +``` + +
+
+ + +

+
+```javascript
+import Account from "./components/Account";
+
+function App() {
+  return (
+    <>
+      
+    
+  );
+}
+
+export default App;
+
+```
+
+
+
+ +
+ + +## Define the ABIs, contract addresses and hooks + +In a Cartesi dApp, the frontend sends inputs to the backend via the base layer chain using JSON-RPC transactions. + +Pre-deployed smart contracts on supported chains handle generic inputs and assets. + +We only need their ABIs and addresses to send transactions using Wagmi. + +However, manually specifying the ABIs and addresses for all the Cartesi Rollups contracts when making function calls can be a hassle. + +Thanks to [`@wagmi/cli`](https://wagmi.sh/cli/why), we can be more efficient by **autogenerating Cartesi-specific hooks**. + +:::note +These hooks come preconfigured with all the ABIs and addresses needed for any function calls to Cartesi. We just need to add the custom arguments for our specific use case. +::: + + +This will automate manual work so we can build faster! We simply import the hooks, call the functions, and pass in the custom arguments. + + +### Install the dependencies + +We will install the following dependencies to our project: + +- [`@wagmi/cli`](https://wagmi.sh/cli/why): The Wagmi CLI tool for ABI-specific hooks. +- [`@cartesi/rollups`](https://www.npmjs.com/package/@cartesi/rollups): The Cartesi Rollups contract implementations. +- [`sunodo/wagmi-plugin-hardhat-deploy`](https://www.npmjs.com/package/@sunodo/wagmi-plugin-hardhat-deploy): Wagmi CLI plugin that loads contracts and deployments from the `@cartesi/rollups` package. + +```bash +npm i @wagmi/cli @cartesi/rollups @sunodo/wagmi-plugin-hardhat-deploy +``` + +### Create a configuration file + +Creata a config file in the root of your project: `wagmi.config.ts` + +Then, add contracts and plugins for Cartesi Rollups: + + + + +

+
+```typescript
+import { defineConfig } from "@wagmi/cli";
+import { react } from "@wagmi/cli/plugins";
+import { erc20Abi, erc721Abi } from "viem";
+import hardhatDeploy from "@sunodo/wagmi-plugin-hardhat-deploy";
+
+export default defineConfig({
+  out: "src/hooks/generated.ts", // Specifies the output file for the hooks
+  contracts: [
+    {
+      abi: erc20Abi,
+      name: "erc20",
+    },
+    { abi: erc721Abi, name: "erc721" },
+  ],
+  plugins: [
+    hardhatDeploy({
+        directory: "node_modules/@cartesi/rollups/export/abi",
+    }),
+    react(),
+],
+});
+```
+
+
+
+ +
+ +The configuration sets up the Wagmi CLI to autogenerate TypeScript hooks for ERC20 and ERC721 contracts, as well as for any contracts in the specified Cartesi Rollups ABI directory. + +## Sending a generic input + +The [`InputBox`](../rollups-apis/json-rpc/input-box.md) contract is a trustless and permissionless contract that receives arbitrary blobs (called "inputs") from anyone. + +The `InputBox` contract is deployed on all supported chains. We will use a React hook to send an input to the backend via the `InputBox` contracct. + +Create a new file `src/components/SimpleInput.tsx` and follow the implementation below: + + +### Component setup and imports + +```javascript +import React, { useState } from "react"; +import { BaseError } from "wagmi"; +import { useWriteInputBoxAddInput } from "../hooks/generated"; +import { stringToHex } from "viem"; +``` +Here, we are importing the generated `useWriteInputBoxAddInput` hook and Viem's `stringToHex` function for data conversion. + + +### Component definition and state + +```javascript + +const SimpleInput = () => { + const dAppAddress = `0xab7528bb862fb57e8a2bcd567a2e929a0be56a5e`; + const [inputValue, setInputValue] = useState(""); + + // ... rest of the component +}; +``` + +We define the `dAppAddress` and create a state variable `inputValue` to manage the user's input. + +The `dAppAddress` is the address of the Cartesi backend that will receive the input. In this case, we are using a hardcoded address of a local dApp instance for demonstration purposes. + +### Using the Hook + +We'll use the `useWriteInputBoxAddInput` hook to interact with the `InputBox` contract: + +```javascript +const { isPending, isSuccess, error, writeContractAsync } = useWriteInputBoxAddInput(); +``` + +This hook provides us with state variables and a `writeContractAsync` function to write to the smart contract. + + +### Form submission and component rendering + +```typescript +async function submit(event: React.FormEvent) { + event.preventDefault(); + await writeContractAsync({ + args: [dAppAddress, stringToHex(inputValue)], + }); + } +``` + +The `submit` function is called when the form is submitted. + +It uses the `writeContractAsync` function to send the input to the [`addInput(address _dapp, bytes _input)`](../rollups-apis/json-rpc/input-box.md/#addinput) function of the `InputBox`. + +The `inputValue` will be received by the particular backend address is `dAppAddress`. + + +Now, let us build our component JSX with an input field and a submit button, styled with Tailwind CSS. It also includes conditional rendering for success and error messages. + +```javascript +return ( +
+

Send Generic Input

+
+
+ setInputValue(e.target.value)} + /> +
+ +
+ + {isSuccess && ( +

Transaction Sent

+ )} + + {error && ( +
+ Error: {(error as BaseError).shortMessage || error.message} +
+ )} +
+ ); +``` + +### Final Component + +Putting it all together, our complete `` component and `App.tsx` look like this: + + + +

+
+```typescript
+import React, { useState } from "react";
+import { BaseError } from "wagmi";
+import { useWriteInputBoxAddInput } from "../hooks/generated";
+import { stringToHex } from "viem";
+
+const SimpleInput = () => {
+  const dAppAddress = `0xab7528bb862fb57e8a2bcd567a2e929a0be56a5e`;
+  const [inputValue, setInputValue] = useState("");
+
+  const { isPending, isSuccess, error, writeContractAsync } =
+    useWriteInputBoxAddInput();
+
+  async function submit(event: React.FormEvent) {
+    event.preventDefault();
+    await writeContractAsync({
+      args: [dAppAddress, stringToHex(inputValue)],
+    });
+  }
+
+  return (
+    
+

Send Generic Input

+
+
+ setInputValue(e.target.value)} + /> +
+ +
+ + {isSuccess && ( +

Transaction Sent

+ )} + + {error && ( +
+ Error: {(error as BaseError).shortMessage || error.message} +
+ )} +
+ ); +}; + +export default SimpleInput; + + +``` + +
+
+ + +

+
+```typescript
+import Account from "./components/Account";
+import SimpleInput from "./components/SimpleInput";
+
+function App() {
+  return (
+    <>
+      
+      
+    
+  );
+}
+
+export default App;
+
+```
+
+
+
+ +
+ +## Depositing Ether + +The [`EtherPortal`](../rollups-apis/json-rpc/portals/EtherPortal.md) contract is a pre-deployed smart contract that allows users to deposit Ether to the Cartesi backend. + + +This implementation will be similar to the generic input, but with a few changes to handle Ether transactions. + +The key changes in this are: + + - The input field now will accept **Ether** values instead of generic text. + + - The `submit` function creates a data string representing the Ether deposit and uses `parseEther` to convert the input value. + + - We will use the `useWriteEtherPortalDepositEther` hook to send Ether. + + + + ```typescript + import { useWriteEtherPortalDepositEther } from "../hooks/generated"; + + // other imports here + + const [etherValue, setEtherValue] = useState(""); + + // rest of the code + + const {isPending,isSuccess,error,writeContractAsync: depositToken} = useWriteEtherPortalDepositEther(); + + async function submit(event: React.FormEvent) { + event.preventDefault(); + const data = stringToHex(`Deposited (${etherValue}) ether.`); + await depositToken({ + args: [dAppAddress, data], + value: parseEther(etherValue), + }); + } + + + // rest of the code + ``` + +### Final Component + +Create a new file `src/components/SendEther.tsx` and paste the complete code: + + + +

+
+```typescript
+import React, { useState } from "react";
+import { BaseError } from "wagmi";
+import { useWriteEtherPortalDepositEther } from "../hooks/generated";
+
+import { parseEther, stringToHex } from "viem";
+
+const SendEther = () => {
+  const dAppAddress = `0xab7528bb862fb57e8a2bcd567a2e929a0be56a5e`;
+  const [etherValue, setEtherValue] = useState("");
+
+  const {
+    isPending,
+    isSuccess,
+    error,
+    writeContractAsync: depositToken,
+  } = useWriteEtherPortalDepositEther();
+
+  async function submit(event: React.FormEvent) {
+    event.preventDefault();
+    const data = stringToHex(`Deposited (${etherValue}) ether.`);
+    await depositToken({
+      args: [dAppAddress, data],
+      value: parseEther(etherValue),
+    });
+  }
+
+  return (
+    
+

Deposit Ether

+
+
+ setEtherValue(e.target.value)} + /> +
+ +
+ + {isSuccess && ( +

{etherValue} ETH sent!

+ )} + + {error && ( +
+ Error: {(error as BaseError).shortMessage || error.message} +
+ )} +
+ ); +}; + +export default SendEther; + +``` + +
+
+ +
+ + +## Depositing ERC20 Tokens + +The [`ERC20Portal`](../rollups-apis/json-rpc/portals/ERC20Portal.md) contract is a pre-deployed smart contract that allows users to deposit ERC20 tokens to the Cartesi backend. + + +This implementation will be similar to the [depositing Ether](#depositing-ether), but with a few changes to handle ERC20 token transactions. + +Here are the key differences in depositing ERC20 tokens compared to Ether: + + - ERC20 deposits require both the **ERC20 token address** and **amounts**. + + - The `submit` function first calls [`approve()`](https://docs.openzeppelin.com/contracts/2.x/api/token/erc20#ERC20-approve-address-uint256-) before calling `depositERC20Tokens` on the ERC20Portal contract. + + :::note ERC Token Approval + For [**ERC20, ERC721, and ERC1155 token standards**](https://ethereum.org/en/developers/docs/standards/tokens/), an approval step is need. This ensures you grant explicit permission for a contract (like the Portals) to transfer tokens on your behalf. + + Without this approval, contracts like ERC20Portal cannot move your tokens to the Cartesi backend. + ::: + + - We will use the `useWriteErc20Approve` hook to approve the deposit and `useWriteErc20PortalDepositErc20Tokens` hook to make the deposit. + + ```typescript + import { + erc20PortalAddress, + useWriteErc20Approve, + useWriteErc20PortalDepositErc20Tokens, + } from "../hooks/generated"; + + import { Address, parseEther, stringToHex, Hex } from "viem"; + + // other imports here + + const [erc20Value, setErc20Value] = useState(""); + const [tokenAddress, setTokenAddress] = useState
(); + + const { writeContractAsync: approveToken } = useWriteErc20Approve(); + const { writeContractAsync: depositToken} = useWriteErc20PortalDepositErc20Token(); + + const approve = async (address: Address, amount: string) => { + try { + await approveToken({ + address, + args: [erc20PortalAddress, parseEther(amount)], + }); + console.log("ERC20 Approval successful"); + } catch (error) { + console.error("Error in approving ERC20:", error); + throw error; + } + }; + + async function submit(event: React.FormEvent) { + event.preventDefault(); + const data = stringToHex(`Deposited (${erc20Value}).`); + await approve(tokenAddress as Address, erc20Value); + await depositToken({ + args: [tokenAddress as Hex, dAppAddress, parseEther(erc20Value), data], + }); + } + + // rest of the code + + ``` + +For testing purposes, you'll need to deploy a test ERC20 token. Follow [this simple guide to deploy a test ERC20 token and add it to your Metamask wallet](https://www.michaelasiedu.dev/posts/deploy-erc20-token-on-localhost/). + + +### Final Component + +Create a new file `src/components/SendERC20.tsx` and paste the complete code: + + + +

+
+```typescript
+import React, { useState } from "react";
+import { BaseError } from "wagmi";
+import {
+  erc20PortalAddress,
+  useWriteErc20Approve,
+  useWriteErc20PortalDepositErc20Tokens,
+} from "../hooks/generated";
+import { Address, parseEther, stringToHex, Hex } from "viem";
+
+const SendERC20 = () => {
+  const dAppAddress = `0xab7528bb862fb57e8a2bcd567a2e929a0be56a5e`;
+  const [erc20Value, setErc20Value] = useState("");
+  const [tokenAddress, setTokenAddress] = useState
(); + + const { + isPending, + isSuccess, + error, + writeContractAsync: depositToken, + } = useWriteErc20PortalDepositErc20Tokens(); + + const { writeContractAsync: approveToken } = useWriteErc20Approve(); + + const approve = async (address: Address, amount: string) => { + try { + await approveToken({ + address, + args: [erc20PortalAddress, parseEther(amount)], + }); + console.log("ERC20 Approval successful"); + } catch (error) { + console.error("Error in approving ERC20:", error); + throw error; + } + }; + + async function submit(event: React.FormEvent) { + event.preventDefault(); + const data = stringToHex(`Deposited (${erc20Value}).`); + await approve(tokenAddress as Address, erc20Value); + await depositToken({ + args: [tokenAddress as Hex, dAppAddress, parseEther(erc20Value), data], + }); + } + + return ( +
+

Deposit ERC20

+
+
+ setTokenAddress(e.target.value as Address)} + /> + setErc20Value(e.target.value)} + /> +
+ +
+ + {isSuccess && ( +

+ {erc20Value} tokens sent! +

+ )} + + {error && ( +
+ Error: {(error as BaseError).shortMessage || error.message} +
+ )} +
+ ); +}; + +export default SendERC20; + +``` + +
+
+ + +

+
+```typescript
+import Account from "./components/Account";
+import SimpleInput from "./components/SimpleInput";
+import SendEther from "./components/SendEther";
+import SendERC20 from "./components/SendERC20";
+
+function App() {
+  return (
+    <>
+      
+      
+      
+      
+    
+  );
+}
+
+export default App;
+
+
+```
+
+
+
+ +
+ + +## Depositing ERC721 Tokens (NFTs) + +The [`ERC721Portal`](../rollups-apis/json-rpc/portals/ERC721Portal.md) contract is a pre-deployed smart contract that allows users to deposit ERC721 tokens to the Cartesi backend. + +This implementation will be similar to the [depositing ERC20 tokens](#depositing-erc20-tokens), but with a few changes to handle ERC721 token transactions. + +Here are the key differences in depositing ERC721 tokens: + + - ERC721 deposits require both the **ERC721 token address** and **token ID**. + + - We will use the `useWriteErc721Approve` hook to approve the deposit and `useWriteErc721PortalDepositErc721Tokens` hook to make the deposit. + + ```typescript + import { Address, erc721Abi, parseEther, stringToHex } from "viem"; + + // other imports here + + const [tokenId, setTokenId] = useState(""); + const [tokenAddress, setTokenAddress] = useState(""); + + const { + isPending, + isSuccess, + error, + writeContractAsync: depositToken, + } = useWriteErc721PortalDepositErc721Token(); + + const { writeContractAsync: approveToken } = useWriteErc721Approve(); + + const approve = async (address: Address, tokenId: bigint) => { + try { + await approveToken({ + address, + args: [erc721PortalAddress, tokenId], + }); + + console.log("Approval successful"); + } catch (error) { + console.error("Error in approving ERC721:", error); + throw error; // Re-throw the error to be handled by the caller + } + }; + + async function submit(event: React.FormEvent) { + event.preventDefault(); + + const bigIntTokenId = BigInt(tokenId); + const data = stringToHex(`Deposited NFT of token id:(${bigIntTokenId}).`); + + await approve(tokenAddress as Address, bigIntTokenId); + + depositToken({ + args: [tokenAddress as Hex, dAppAddress, bigIntTokenId, "0x", data], + }); + } + + // rest of the code + + ``` + +For testing purposes, you'll need to deploy a test ERC721 token. Follow [this simple guide to deploy and mint a test ERC721 token and add it to your Metamask wallet](https://www.michaelasiedu.dev/posts/deploy-erc721-token-on-localhost/). + + +### Final Component + +Create a new file `src/components/SendERC721.tsx` and paste the complete code: + + + +

+
+```typescript
+import React, { useState } from "react";
+import { BaseError } from "wagmi";
+import {
+  erc721PortalAddress,
+  useWriteErc721Approve,
+  useWriteErc721PortalDepositErc721Token,
+} from "../hooks/generated";
+import { stringToHex, Address, Hex } from "viem";
+
+const SendERC721 = () => {
+  const dAppAddress = `0xab7528bb862fb57e8a2bcd567a2e929a0be56a5e`;
+  const [tokenId, setTokenId] = useState("");
+  const [tokenAddress, setTokenAddress] = useState("");
+
+  const {
+    isPending,
+    isSuccess,
+    error,
+    writeContractAsync: depositToken,
+  } = useWriteErc721PortalDepositErc721Token();
+
+  const { writeContractAsync: approveToken } = useWriteErc721Approve();
+
+  const approve = async (address: Address, tokenId: bigint) => {
+    try {
+      await approveToken({
+        address,
+        args: [erc721PortalAddress, tokenId],
+      });
+
+      console.log("Approval successful");
+    } catch (error) {
+      console.error("Error in approving ERC721:", error);
+      throw error; // Re-throw the error to be handled by the caller
+    }
+  };
+
+  async function submit(event: React.FormEvent) {
+    event.preventDefault();
+
+    const bigIntTokenId = BigInt(tokenId);
+    const data = stringToHex(`Deposited NFT of token id:(${bigIntTokenId}).`);
+
+    await approve(tokenAddress as Address, bigIntTokenId);
+
+    depositToken({
+      args: [tokenAddress as Hex, dAppAddress, bigIntTokenId, "0x", data],
+    });
+  }
+
+  return (
+    
+

+ Deposit ERC721 Token +

+
+
+ setTokenAddress(e.target.value)} + /> +
+
+ setTokenId(e.target.value)} + /> +
+ +
+ + {isSuccess && ( +

+ NFT of Token number: {tokenId} sent! +

+ )} + + {error && ( +
+ Error: {(error as BaseError).shortMessage || error.message} +
+ )} +
+ ); +}; + +export default SendERC721; + +``` + +
+
+ + +

+
+```typescript
+import Account from "./components/Account";
+import SendERC20 from "./components/SendERC20";
+import SendERC721 from "./components/SendERC721";
+import SendEther from "./components/SendEther";
+import SimpleInput from "./components/SimpleInput";
+
+function App() {
+  return (
+    <>
+      
+      
+      
+      
+      
+    
+  );
+}
+
+export default App;
+
+
+
+```
+
+
+
+ +
+ + +## Listing Notices, Reports, and Vouchers + +All inputs sent to the Cartesi backend are processed by the Cartesi Machine. The Cartesi Machine produces three types of outputs: [Notices](../rollups-apis/backend/notices.md), [Reports](../rollups-apis/backend/reports.md), and [Vouchers](../rollups-apis/backend/vouchers.md). + +These outputs can be queried by the frontend using the [GraphQL API](../rollups-apis/graphql/basics.md) on `http://localhost:8080/graphql`. + +:::note GraphQL API Reference +Refer to the [GraphQL API documentation](../rollups-apis/graphql/basics.md) for all the queries and mutations available. +::: + +Let's move the GraphQL queries to an external file `src/utils/queries.ts` for better organization and reusability. + +Then, we will create a shared function `fetchGraphQLData` created in `src/utils/api.ts` to handle the GraphQL request. + + + + +

+
+```typescript
+// queries.ts
+
+export const NOTICES_QUERY = `
+  query notices {
+    notices {
+      edges {
+        node {
+          index
+          input {
+            index
+          }
+          payload
+        }
+      }
+    }
+  }
+`;
+
+export const REPORTS_QUERY = `
+  query reports {
+    reports {
+      edges {
+        node {
+          index
+          input {
+            index
+          }
+          payload
+        }
+      }
+    }
+  }
+`;
+
+export const VOUCHERS_QUERY = `
+  query vouchers {
+    vouchers {
+      edges {
+        node {
+          index
+          input {
+            index
+          }
+          destination
+          payload
+        }
+      }
+    }
+  }
+`;
+```
+
+
+
+ + +

+
+```typescript
+// types.ts
+
+export type Notice = {
+  index: number;
+  input: {
+    index: number;
+  };
+  payload: string;
+};
+
+export type Report = {
+  index: number;
+  input: {
+    index: number;
+  };
+  payload: string;
+};
+
+export type Voucher = {
+  index: number;
+  input: {
+    index: number;
+  };
+  destination: string;
+  payload: string;
+};
+
+export type GraphQLResponse = {
+  data: T;
+};
+```
+
+
+
+ + +

+
+```typescript
+// api.ts
+
+import axios from 'axios';  // install axios by running `npm i axios`
+import { GraphQLResponse } from './types';
+
+export const fetchGraphQLData = async (query: string) => {
+  const response = await axios.post>('http://localhost:8080/graphql', {
+    query,
+  });
+  return response.data.data;
+};
+```
+
+
+
+
+ +Let's have 3 components for Notices, Reports, and Vouchers that queries from the GraphQL API. + + + + + +

+
+```typescript
+
+import { useEffect, useState } from 'react';
+import { fetchGraphQLData } from '../utils/api';
+import { Notice } from '../utils/types';
+import { NOTICES_QUERY } from '../utils/queries';
+
+const Notices = () => {
+  const [notices, setNotices] = useState([]);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState(null);
+
+  useEffect(() => {
+    const fetchNotices = async () => {
+      try {
+        const data = await fetchGraphQLData<{ notices: { edges: { node: Notice }[] } }>(NOTICES_QUERY);
+        setNotices(data.notices.edges.map(edge => edge.node));
+      } catch (err) {
+        setError('Error fetching notices.');
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchNotices();
+  }, []);
+
+  if (loading) return 
Loading...
; + if (error) return
{error}
; + + return ( +
+

Notices

+ + + + + + + + + + {notices.map((notice, idx) => ( + + + + + + ))} + +
IndexInput IndexPayload
{notice.index}{notice.input.index}{notice.payload}
+
+ ); +}; + +export default Notices; +``` + +
+
+ + +

+
+```typescript
+
+
+import { useEffect, useState } from 'react';
+import { fetchGraphQLData } from '../utils/api';
+import { Report } from '../utils/types';
+import { REPORTS_QUERY } from '../utils/queries';
+
+
+const Reports = () => {
+  const [reports, setReports] = useState([]);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState(null);
+
+  useEffect(() => {
+    const fetchReports = async () => {
+      try {
+        const data = await fetchGraphQLData<{ reports: { edges: { node: Report }[] } }>(REPORTS_QUERY);
+        setReports(data.reports.edges.map(edge => edge.node));
+      } catch (err) {
+        setError('Error fetching reports.');
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchReports();
+  }, []);
+
+  if (loading) return 
Loading...
; + if (error) return
{error}
; + + return ( +
+

Reports

+ + + + + + + + + + {reports.map((report, idx) => ( + + + + + + ))} + +
IndexInput IndexPayload
{report.index}{report.input.index}{report.payload}
+
+ ); +}; + +export default Reports; +``` + +
+
+ + +

+
+```typescript
+
+
+import { useEffect, useState } from 'react';
+import { fetchGraphQLData } from '../utils/api';
+import { Voucher } from '../utils/types';
+import { VOUCHERS_QUERY } from '../utils/queries';
+
+const Vouchers = () => {
+  const [vouchers, setVouchers] = useState([]);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState(null);
+
+  useEffect(() => {
+    const fetchVouchers = async () => {
+      try {
+        const data = await fetchGraphQLData<{ vouchers: { edges: { node: Voucher }[] } }>(VOUCHERS_QUERY);
+        setVouchers(data.vouchers.edges.map(edge => edge.node));
+      } catch (err) {
+        setError('Error fetching vouchers.');
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchVouchers();
+  }, []);
+
+  if (loading) return 
Loading...
; + if (error) return
{error}
; + + return ( +
+

Vouchers

+ + + + + + + + + + + {vouchers.map((voucher, idx) => ( + + + + + + + ))} + +
IndexInput IndexDestinationPayload
{voucher.index}{voucher.input.index}{voucher.destination}{voucher.payload}
+
+ ); +}; + +export default Vouchers; +``` + +
+
+
+ + +### Executing vouchers + +Vouchers in Cartesi dApps authorize specific on-chain actions, such as token swaps or asset transfers, by encapsulating the details of these actions. + +They are validated and executed on the blockchain using the [`executeVoucher(address _destination, bytes _payload, struct Proof _proof)`](../rollups-apis/json-rpc/application.md#executevoucher) function in the [`CartesiDApp`](../rollups-apis/json-rpc/application.md/) contract, ensuring legitimacy and transparency. + +For example, users might generate vouchers to withdraw assets, which are executed on the base later. + +At this stage, you can now interact with the Cartesi backend using the frontend you've built. + diff --git a/sidebarsRollups.js b/sidebarsRollups.js index e0452b62..6b31aa37 100644 --- a/sidebarsRollups.js +++ b/sidebarsRollups.js @@ -150,6 +150,7 @@ module.exports = { collapsed: true, items: [ "tutorials/calculator", + "tutorials/react-frontend-application" ], },