From 0cc3b9353f64f105ac501b2cdd731fa6dc4a6600 Mon Sep 17 00:00:00 2001 From: gconnect Date: Tue, 28 May 2024 16:55:49 +0100 Subject: [PATCH] made the portals code reuabale and modified the transfers component --- apps/frontend/next-app/.eslintrc.json | 5 +- apps/frontend/next-app/app/GraphqlClient.ts | 30 -- .../frontend/next-app/app/cartesi/GraphQL.tsx | 58 --- apps/frontend/next-app/app/cartesi/Input.tsx | 330 -------------- .../frontend/next-app/app/cartesi/Inspect.tsx | 53 +-- .../next-app/app/cartesi/Notices copy.tsx | 109 ----- .../frontend/next-app/app/cartesi/Notices.tsx | 72 +-- apps/frontend/next-app/app/cartesi/Portals.ts | 395 +++++++++++++++++ .../next-app/app/cartesi/Reports copy.tsx | 121 ----- .../frontend/next-app/app/cartesi/Reports.tsx | 61 +-- .../next-app/app/cartesi/Vouchers.tsx | 2 +- .../app/cartesi/hooks/useInspectCall.tsx | 94 ++++ .../next-app/app/cartesi/hooks/useNotices.tsx | 60 +++ .../next-app/app/cartesi/hooks/useReports.tsx | 59 +++ .../app/cartesi/{ => hooks}/useRollups.tsx | 24 +- apps/frontend/next-app/app/chakraProvider.tsx | 8 - .../next-app/app/component/Balance.tsx | 90 +--- .../next-app/app/component/FooterItems.tsx | 8 +- .../next-app/app/component/Header.tsx | 4 +- .../next-app/app/component/Transfers.tsx | 417 +++++++----------- .../next-app/app/component/footer.tsx | 4 +- apps/frontend/next-app/app/favicon.ico | Bin 50629 -> 17128 bytes apps/frontend/next-app/app/greetings/page.tsx | 75 ++-- apps/frontend/next-app/app/layout copy.tsx | 73 --- apps/frontend/next-app/app/layout.tsx | 41 +- apps/frontend/next-app/app/page.tsx | 11 +- .../next-app/app/utils/clientToProvider.ts | 30 -- apps/frontend/next-app/app/utils/constants.ts | 3 +- .../next-app/app/utils/customAlert.tsx | 21 + .../next-app/app/utils/rollupsProvider.tsx | 32 ++ apps/frontend/next-app/app/wallet/page.tsx | 5 +- .../next-app/public/images/CartDevKit.png | Bin 0 -> 3742 bytes .../public/images/cartesikit-logo.png | Bin 50629 -> 0 bytes .../next-app/public/images/devkit-logo.png | Bin 2508 -> 0 bytes apps/frontend/next-app/public/images/icon.png | Bin 1123 -> 0 bytes apps/frontend/next-app/tailwind.config.ts | 1 + 36 files changed, 911 insertions(+), 1385 deletions(-) delete mode 100644 apps/frontend/next-app/app/GraphqlClient.ts delete mode 100644 apps/frontend/next-app/app/cartesi/GraphQL.tsx delete mode 100644 apps/frontend/next-app/app/cartesi/Input.tsx delete mode 100644 apps/frontend/next-app/app/cartesi/Notices copy.tsx create mode 100644 apps/frontend/next-app/app/cartesi/Portals.ts delete mode 100644 apps/frontend/next-app/app/cartesi/Reports copy.tsx create mode 100644 apps/frontend/next-app/app/cartesi/hooks/useInspectCall.tsx create mode 100644 apps/frontend/next-app/app/cartesi/hooks/useNotices.tsx create mode 100644 apps/frontend/next-app/app/cartesi/hooks/useReports.tsx rename apps/frontend/next-app/app/cartesi/{ => hooks}/useRollups.tsx (85%) delete mode 100644 apps/frontend/next-app/app/chakraProvider.tsx delete mode 100644 apps/frontend/next-app/app/layout copy.tsx delete mode 100644 apps/frontend/next-app/app/utils/clientToProvider.ts create mode 100644 apps/frontend/next-app/app/utils/customAlert.tsx create mode 100644 apps/frontend/next-app/app/utils/rollupsProvider.tsx create mode 100644 apps/frontend/next-app/public/images/CartDevKit.png delete mode 100644 apps/frontend/next-app/public/images/cartesikit-logo.png delete mode 100644 apps/frontend/next-app/public/images/devkit-logo.png delete mode 100644 apps/frontend/next-app/public/images/icon.png diff --git a/apps/frontend/next-app/.eslintrc.json b/apps/frontend/next-app/.eslintrc.json index bffb357..eb82587 100644 --- a/apps/frontend/next-app/.eslintrc.json +++ b/apps/frontend/next-app/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": "next/core-web-vitals" + "extends": "next/core-web-vitals", + "rules": { + "react/display-name": "off" + } } diff --git a/apps/frontend/next-app/app/GraphqlClient.ts b/apps/frontend/next-app/app/GraphqlClient.ts deleted file mode 100644 index 63f1d56..0000000 --- a/apps/frontend/next-app/app/GraphqlClient.ts +++ /dev/null @@ -1,30 +0,0 @@ -"use client" -import { useMemo } from 'react'; -import { - UrqlProvider, - ssrExchange, - cacheExchange, - fetchExchange, - createClient, -} from '@urql/next'; - -// YOUR API URL -const API_BASE_URL = 'http://localhost:8080/graphql' - -export default function GraphQLClient({ children }: React.PropsWithChildren) { - const [client, ssr] = useMemo(() => { - const ssr = ssrExchange({ - isClient: typeof window !== 'undefined', - }); - const client = createClient({ - url: API_BASE_URL, - exchanges: [cacheExchange, ssr, fetchExchange], - suspense: true, - }); - - return [client, ssr]; - }, []); - - return {client, ssr} -} - diff --git a/apps/frontend/next-app/app/cartesi/GraphQL.tsx b/apps/frontend/next-app/app/cartesi/GraphQL.tsx deleted file mode 100644 index 89079fb..0000000 --- a/apps/frontend/next-app/app/cartesi/GraphQL.tsx +++ /dev/null @@ -1,58 +0,0 @@ -"use client" - -// Copyright 2022 Cartesi Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the license at http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -import { useSetChain } from "@web3-onboard/react"; -import React, { useMemo } from "react"; -import { Client, createClient, Provider } from "urql"; -import { Chain } from "viem"; -// import { Chain } from "@rainbow-me/rainbowkit"; -import configFile from "./config.json"; - -const config: any = configFile; - -const useGraphQL = () => { - const [{ connectedChain }] = useSetChain(); - return useMemo(() => { - if (!connectedChain) { - return null; - } - let url = ""; - - if(config[connectedChain.id]?.graphqlAPIURL) { - url = `${config[connectedChain.id].graphqlAPIURL}/graphql`; - } else { - console.error(`No GraphQL interface defined for chain ${connectedChain.id}`); - return null; - } - - if (!url) { - return null; - } - - return createClient({ - url, - exchanges: [] - }); - }, [connectedChain]); -}; - -export const GraphQLProvider: any = (props: any) => { - const client = useGraphQL(); - if (!client) { - return
; - } - - return {props.children}; -}; - diff --git a/apps/frontend/next-app/app/cartesi/Input.tsx b/apps/frontend/next-app/app/cartesi/Input.tsx deleted file mode 100644 index 1bf971a..0000000 --- a/apps/frontend/next-app/app/cartesi/Input.tsx +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright 2022 Cartesi Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the license at http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -import React, { useState } from "react"; -import { ethers } from "ethers"; -import { useRollups } from "./useRollups"; -import { useWallets } from "@web3-onboard/react"; -import { IERC1155__factory, IERC20__factory, IERC721__factory } from "../../src/generated/rollups"; - -interface IInputPropos { - dappAddress: string -} - -export const Input: React.FC = (propos) => { - const rollups = useRollups(propos.dappAddress); - const [connectedWallet] = useWallets(); - const provider = new ethers.providers.Web3Provider( - connectedWallet.provider - ); - - const sendAddress = async (str: string) => { - if (rollups) { - try { - await rollups.relayContract.relayDAppAddress(propos.dappAddress); - } catch (e) { - console.log(`${e}`); - } - } - }; - - const addInput = async (str: string) => { - if (rollups) { - try { - let payload = ethers.toUtf8Bytes(str); - if (hexInput) { - payload = ethers.utils.arrayify(str); - } - await rollups.inputContract.addInput(propos.dappAddress, payload); - } catch (e) { - console.log(`${e}`); - } - } - }; - - const depositErc20ToPortal = async (token: string,amount: number) => { - try { - if (rollups && provider) { - const data = ethers.toUtf8Bytes(`Deposited (${amount}) of ERC20 (${token}).`); - //const data = `Deposited ${args.amount} tokens (${args.token}) for DAppERC20Portal(${portalAddress}) (signer: ${address})`; - const signer = provider.getSigner(); - const signerAddress = await signer.getAddress() - - const erc20PortalAddress = rollups.erc20PortalContract.address; - const tokenContract = signer ? IERC20__factory.connect(token, signer) : IERC20__factory.connect(token, provider); - - // query current allowance - const currentAllowance = await tokenContract.allowance(signerAddress, erc20PortalAddress); - if (ethers.parseEther(`${amount}`) > currentAllowance) { - // Allow portal to withdraw `amount` tokens from signer - const tx = await tokenContract.approve(erc20PortalAddress, ethers.parseEther(`${amount}`)); - const receipt = await tx.wait(1); - const event = (await tokenContract.queryFilter(tokenContract.filters.Approval(), receipt.blockHash)).pop(); - if (!event) { - throw Error(`could not approve ${amount} tokens for DAppERC20Portal(${erc20PortalAddress}) (signer: ${signerAddress}, tx: ${tx.hash})`); - } - } - - await rollups.erc20PortalContract.depositERC20Tokens(token,propos.dappAddress,ethers.utils.parseEther(`${amount}`),data); - } - } catch (e) { - console.log(`${e}`); - } - }; - - const depositEtherToPortal = async (amount: number) => { - try { - if (rollups && provider) { - const data = ethers.toUtf8Bytes(`Deposited (${amount}) ether.`); - const txOverrides = {value: ethers.parseEther(`${amount}`)} - - // const tx = await ... - rollups.etherPortalContract.depositEther(propos.dappAddress,data,txOverrides); - } - } catch (e) { - console.log(`${e}`); - } - }; - - const transferNftToPortal = async (contractAddress: string,nftid: number) => { - try { - if (rollups && provider) { - const data = ethers.toUtf8Bytes(`Deposited (${nftid}) of ERC721 (${contractAddress}).`); - //const data = `Deposited ${args.amount} tokens (${args.token}) for DAppERC20Portal(${portalAddress}) (signer: ${address})`; - const signer = provider.getSigner(); - const signerAddress = await signer.getAddress() - - const erc721PortalAddress = rollups.erc721PortalContract.address; - - const tokenContract = signer ? IERC721__factory.connect(contractAddress, signer) : IERC721__factory.connect(contractAddress, provider); - - // query current approval - const currentApproval = await tokenContract.getApproved(nftid); - if (currentApproval !== erc721PortalAddress) { - // Allow portal to withdraw `amount` tokens from signer - const tx = await tokenContract.approve(erc721PortalAddress, nftid); - const receipt = await tx.wait(1); - const event = (await tokenContract.queryFilter(tokenContract.filters.Approval(), receipt.blockHash)).pop(); - if (!event) { - throw Error(`could not approve ${nftid} for DAppERC721Portal(${erc721PortalAddress}) (signer: ${signerAddress}, tx: ${tx.hash})`); - } - } - - // Transfer - rollups.erc721PortalContract.depositERC721Token(contractAddress,propos.dappAddress, nftid, "0x", data); - } - } catch (e) { - console.log(`${e}`); - } - }; - - const transferErc1155SingleToPortal = async (contractAddress: string, id: number, amount: number) => { - try { - if (rollups && provider) { - const data = ethers.toUtf8Bytes(`Deposited (${amount}) tokens from id (${id}) of ERC1155 (${contractAddress}).`); - //const data = `Deposited ${args.amount} tokens (${args.token}) for DAppERC20Portal(${portalAddress}) (signer: ${address})`; - const signer = provider.getSigner(); - const signerAddress = await signer.getAddress() - - const erc1155SinglePortalAddress = rollups.erc1155SinglePortalContract.address; - - const tokenContract = signer ? IERC1155__factory.connect(contractAddress, signer) : IERC1155__factory.connect(contractAddress, provider); - - // query current approval - const currentApproval = await tokenContract.isApprovedForAll(signerAddress,erc1155SinglePortalAddress); - if (!currentApproval) { - // Allow portal to withdraw `amount` tokens from signer - const tx = await tokenContract.setApprovalForAll(erc1155SinglePortalAddress,true); - const receipt = await tx.wait(1); - const event = (await tokenContract.queryFilter(tokenContract.filters.ApprovalForAll(), receipt.blockHash)).pop(); - if (!event) { - throw Error(`could set approval for DAppERC1155Portal(${erc1155SinglePortalAddress}) (signer: ${signerAddress}, tx: ${tx.hash})`); - } - } - - // Transfer - rollups.erc1155SinglePortalContract.depositSingleERC1155Token(contractAddress,propos.dappAddress, id, amount, "0x", data); - } - } catch (e) { - console.log(`${e}`); - } - }; - - const transferErc1155BatchToPortal = async (contractAddress: string, ids: number[], amounts: number[]) => { - try { - if (rollups && provider) { - const data = ethers.utils.toUtf8Bytes(`Deposited (${amounts}) tokens from ids (${ids}) of ERC1155 (${contractAddress}).`); - //const data = `Deposited ${args.amount} tokens (${args.token}) for DAppERC20Portal(${portalAddress}) (signer: ${address})`; - const signer = provider.getSigner(); - const signerAddress = await signer.getAddress() - - const erc1155BatchPortalAddress = rollups.erc1155BatchPortalContract.address; - - const tokenContract = signer ? IERC1155__factory.connect(contractAddress, signer) : IERC1155__factory.connect(contractAddress, provider); - - // query current approval - const currentApproval = await tokenContract.isApprovedForAll(signerAddress,erc1155BatchPortalAddress); - if (!currentApproval) { - // Allow portal to withdraw `amount` tokens from signer - const tx = await tokenContract.setApprovalForAll(erc1155BatchPortalAddress,true); - const receipt = await tx.wait(1); - const event = (await tokenContract.queryFilter(tokenContract.filters.ApprovalForAll(), receipt.blockHash)).pop(); - if (!event) { - throw Error(`could set approval for DAppERC1155Portal(${erc1155BatchPortalAddress}) (signer: ${signerAddress}, tx: ${tx.hash})`); - } - } - - // Transfer - rollups.erc1155BatchPortalContract.depositBatchERC1155Token(contractAddress,propos.dappAddress, ids, amounts, "0x", data); - } - } catch (e) { - console.log(`${e}`); - } - }; - - const AddTo1155Batch = () => { - const newIds = erc1155Ids; - newIds.push(erc1155Id); - setErc1155Ids(newIds); - const newAmounts = erc1155Amounts; - newAmounts.push(erc1155Amount); - setErc1155Amounts(newAmounts); - setErc1155IdsStr("["+erc1155Ids.join(',')+"]"); - setErc1155AmountsStr("["+erc1155Amounts.join(',')+"]"); - }; - - const Clear1155Batch = () => { - setErc1155IdsStr("[]"); - setErc1155AmountsStr("[]"); - setErc1155Ids([]); - setErc1155Amounts([]); - }; - - const [input, setInput] = useState(""); - const [hexInput, setHexInput] = useState(false); - const [erc20Amount, setErc20Amount] = useState(0); - const [erc20Token, setErc20Token] = useState(""); - const [erc721Id, setErc721Id] = useState(0); - const [erc721, setErc721] = useState(""); - const [etherAmount, setEtherAmount] = useState(0); - const [erc1155, setErc1155] = useState(""); - const [erc1155Id, setErc1155Id] = useState(0); - const [erc1155Amount, setErc1155Amount] = useState(0); - const [erc1155Ids, setErc1155Ids] = useState([]); - const [erc1155Amounts, setErc1155Amounts] = useState([]); - const [erc1155IdsStr, setErc1155IdsStr] = useState("[]"); - const [erc1155AmountsStr, setErc1155AmountsStr] = useState("[]"); - - return ( -
-
- Send Address (send relay dapp address)
- -

-
-
- Send Input
- Input: setInput(e.target.value)} - /> - setHexInput(!hexInput)}/>Raw Hex - -

-
-
- Deposit Ether
- Amount: setEtherAmount(Number(e.target.value))} - /> - -

-
-
- Deposit ERC20
- Address: setErc20Token(e.target.value)} - /> - Amount: setErc20Amount(Number(e.target.value))} - /> - -

-
-
- Transfer ERC721
- Address: setErc721(e.target.value)} - /> - id: setErc721Id(Number(e.target.value))} - /> - -

-
-
- Transfer Single ERC1155
- Address: setErc1155(e.target.value)} - /> - id: setErc1155Id(Number(e.target.value))} - /> - Amount: setErc1155Amount(Number(e.target.value))} - /> - - -
- Transfer ERC1155 Batch
- Ids: {erc1155IdsStr} - Amounts: {erc1155AmountsStr} - - -
-
- ); -}; diff --git a/apps/frontend/next-app/app/cartesi/Inspect.tsx b/apps/frontend/next-app/app/cartesi/Inspect.tsx index c2b0236..f3bd1a9 100644 --- a/apps/frontend/next-app/app/cartesi/Inspect.tsx +++ b/apps/frontend/next-app/app/cartesi/Inspect.tsx @@ -10,59 +10,14 @@ // License for the specific language governing permissions and limitations // under the License. -import React, { useState } from "react"; -import { useSetChain } from "@web3-onboard/react"; +import React from "react"; import { ethers } from "ethers"; -import { useRollups } from "./useRollups"; -import { useAccount } from "wagmi"; -import configFile from "../cartesi/config.json"; -import { DAPP_ADDRESS } from "../utils/constants"; -import { toHex } from "viem"; +import { useInspectCall } from "../cartesi/hooks/useInspectCall"; -const config: any = configFile; export const Inspect: React.FC = () => { - const rollups = useRollups(DAPP_ADDRESS); - const { chain } = useAccount(); - - const inspectCall = async (str: string) => { - let payload = str; - if (hexData) { - const uint8array = ethers.utils.arrayify(str); - payload = new TextDecoder().decode(uint8array); - } - if (!chain){ - return; - } - - let apiURL= "" - - if(config[toHex(chain.id)]?.inspectAPIURL) { - apiURL = `${config[toHex(chain.id)].inspectAPIURL}/inspect`; - } else { - console.error(`No inspect interface defined for chain ${toHex(chain.id)}`); - return; - } - - let fetchData: Promise; - if (postData) { - const payloadBlob = new TextEncoder().encode(payload); - fetchData = fetch(`${apiURL}`, { method: 'POST', body: payloadBlob }); - } else { - fetchData = fetch(`${apiURL}/${payload}`); - } - fetchData - .then(response => response.json()) - .then(data => { - setReports(data.reports); - setMetadata({metadata:data.metadata, status: data.status, exception_payload: data.exception_payload}); - }); - }; - const [inspectData, setInspectData] = useState(""); - const [reports, setReports] = useState([]); - const [metadata, setMetadata] = useState({}); - const [hexData, setHexData] = useState(false); - const [postData, setPostData] = useState(false); + const { setInspectData, inspectData, setHexData,hexData,postData, + setPostData,metadata, reports, inspectCall} = useInspectCall() return (
diff --git a/apps/frontend/next-app/app/cartesi/Notices copy.tsx b/apps/frontend/next-app/app/cartesi/Notices copy.tsx deleted file mode 100644 index 1124892..0000000 --- a/apps/frontend/next-app/app/cartesi/Notices copy.tsx +++ /dev/null @@ -1,109 +0,0 @@ -"use client" -// Copyright 2022 Cartesi Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the license at http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -import { ethers } from "ethers"; -import React, {useEffect} from "react"; - -import { useNoticesQuery } from "../cartesi/generated/graphql"; - -type Notice = { - id: string; - index: number; - input: any, //{index: number; epoch: {index: number; } - payload: string; -}; - -export const Notices: React.FC = () => { - const [result,reexecuteQuery] = useNoticesQuery(); - const { data, fetching, error } = result; - - useEffect(() => { - reexecuteQuery({ requestPolicy: 'network-only' }); - }, [reexecuteQuery]); - - if (fetching) return

Loading...

; - if (error) return

Oh no... {error.message}

; - - if (!data || !data.notices) return

No notices

; - - const notices: Notice[] = data.notices.edges.map((node: any) => { - const n = node.node; - let inputPayload = n?.input.payload; - if (inputPayload) { - try { - inputPayload = ethers.toUtf8String(inputPayload); - } catch (e) { - inputPayload = inputPayload + " (hex)"; - } - } else { - inputPayload = "(empty)"; - } - let payload = n?.payload; - if (payload) { - try { - payload = ethers.toUtf8String(payload); - } catch (e) { - payload = payload + " (hex)"; - } - } else { - payload = "(empty)"; - } - return { - id: `${n?.id}`, - index: parseInt(n?.index), - payload: `${payload}`, - input: n ? {index:n.input.index,payload: inputPayload} : {}, - }; - }).sort((b: any, a: any) => { - if (a.input.index === b.input.index) { - return b.index - a.index; - } else { - return b.input.index - a.input.index; - } - }); - - // const forceUpdate = useForceUpdate(); - return ( -
- - - - - - - {/* */} - - - - - {notices.length === 0 && ( - - - - )} - {notices.map((n: any) => ( - - - - {/* */} - - - ))} - -
Input IndexNotice IndexInput PayloadPayload
no notices
{n.input.index}{n.index}{n.input.payload}{n.payload}
- -
- ); -}; diff --git a/apps/frontend/next-app/app/cartesi/Notices.tsx b/apps/frontend/next-app/app/cartesi/Notices.tsx index 1124892..31a4c38 100644 --- a/apps/frontend/next-app/app/cartesi/Notices.tsx +++ b/apps/frontend/next-app/app/cartesi/Notices.tsx @@ -13,69 +13,25 @@ import { ethers } from "ethers"; import React, {useEffect} from "react"; - -import { useNoticesQuery } from "../cartesi/generated/graphql"; - -type Notice = { - id: string; - index: number; - input: any, //{index: number; epoch: {index: number; } - payload: string; -}; +import { useNotices } from "./hooks/useNotices"; +import { useRollups } from "./hooks/useRollups"; +import { DAPP_ADDRESS } from "../utils/constants"; export const Notices: React.FC = () => { - const [result,reexecuteQuery] = useNoticesQuery(); - const { data, fetching, error } = result; + const {loading, error, data, notices, refetch } = useNotices() useEffect(() => { - reexecuteQuery({ requestPolicy: 'network-only' }); - }, [reexecuteQuery]); - - if (fetching) return

Loading...

; - if (error) return

Oh no... {error.message}

; - - if (!data || !data.notices) return

No notices

; - - const notices: Notice[] = data.notices.edges.map((node: any) => { - const n = node.node; - let inputPayload = n?.input.payload; - if (inputPayload) { - try { - inputPayload = ethers.toUtf8String(inputPayload); - } catch (e) { - inputPayload = inputPayload + " (hex)"; - } - } else { - inputPayload = "(empty)"; - } - let payload = n?.payload; - if (payload) { - try { - payload = ethers.toUtf8String(payload); - } catch (e) { - payload = payload + " (hex)"; - } - } else { - payload = "(empty)"; - } - return { - id: `${n?.id}`, - index: parseInt(n?.index), - payload: `${payload}`, - input: n ? {index:n.input.index,payload: inputPayload} : {}, - }; - }).sort((b: any, a: any) => { - if (a.input.index === b.input.index) { - return b.index - a.index; - } else { - return b.input.index - a.input.index; - } - }); + refetch({ requestPolicy: 'network-only' }); + }, []); + + if (loading) return

Loading...

; + if (error) return

Oh no... {error.message}

; - // const forceUpdate = useForceUpdate(); + if (!data || !data.notices) return

No Notices

; + return (
- @@ -88,12 +44,12 @@ export const Notices: React.FC = () => { - {notices.length === 0 && ( + {notices && notices.length === 0 && ( )} - {notices.map((n: any) => ( + {notices && notices.map((n: any) => ( diff --git a/apps/frontend/next-app/app/cartesi/Portals.ts b/apps/frontend/next-app/app/cartesi/Portals.ts new file mode 100644 index 0000000..14537e5 --- /dev/null +++ b/apps/frontend/next-app/app/cartesi/Portals.ts @@ -0,0 +1,395 @@ +import { ethers, JsonRpcApiProvider, JsonRpcSigner, parseEther, Provider } from "ethers"; +import { RollupsContracts } from "../cartesi/hooks/useRollups"; +import { + IERC1155__factory, + IERC20__factory, + IERC721__factory, +} from "../cartesi/generated/rollups"; +import { successAlert, errorAlert } from "../utils/customAlert"; +import { DAPP_ADDRESS } from "../utils/constants"; + +export const sendAddress = async (rollups: RollupsContracts | undefined, signer: JsonRpcSigner | undefined, setDappRelayedAddress: Function) => { + if (rollups) { + try { + const relayTx = await rollups.relayContract.relayDAppAddress(DAPP_ADDRESS); + setDappRelayedAddress(true); + const trans = await signer!.sendTransaction(relayTx) + const tx = await trans?.wait(1) + successAlert(tx!.hash) + return tx?.hash + } catch (e) { + console.log(`${e}`); + errorAlert(e) + } + } +}; + +export const addInput = async ( + rollups: RollupsContracts | undefined, + signer: JsonRpcSigner | undefined, + setLoading: Function, + jsonPayload: string + ) => { + if (rollups) { + try { + setLoading(true) + const data = JSON.stringify(jsonPayload); + let payload = ethers.toUtf8Bytes(data); + const trans = await rollups.inputContract.addInput(DAPP_ADDRESS, payload); + const tx = await signer?.sendTransaction(trans) + const receipt = await tx?.wait() + setLoading(false) + successAlert(receipt?.hash) + return receipt?.hash + } catch (e) { + setLoading(false) + errorAlert(e) + console.log(`${e}`); + } + } +}; + +export const depositErc20ToPortal = async (rollups: RollupsContracts | undefined, + provider: JsonRpcApiProvider | undefined, setLoadERC20: Function, + token: string, amount: number) => { + try { + if (rollups && provider) { + setLoadERC20(true) + const data = ethers.toUtf8Bytes( + `Deposited (${amount}) of ERC20 (${token}).` + ); + const signer = await provider.getSigner(); + const signerAddress = await signer?.getAddress(); + + const erc20PortalAddress = rollups.erc20PortalContract.address; + const tokenContract = signer + ? IERC20__factory.connect(token, signer) + : IERC20__factory.connect(token, signer!); + + // query current allowance + const currentAllowance = await tokenContract.allowance( + signerAddress!, + erc20PortalAddress + ); + if (parseEther(`${amount}`) > currentAllowance) { + // Allow portal to withdraw `amount` tokens from signer + const tx = await tokenContract.approve( + erc20PortalAddress, + parseEther(`${amount}`) + ); + const trans = await signer.sendTransaction(tx) + const receipt = await trans.wait(1); + const event = ( + await tokenContract.queryFilter( + tokenContract.filters.Approval(), + receipt?.hash + ) + ).pop(); + if (!event) { + throw Error( + `could not approve ${amount} tokens for DAppERC20Portal(${erc20PortalAddress}) (signer: ${signerAddress}, tx: ${tx.hash})` + ); + } + } + + const deposit = await rollups.erc20PortalContract.depositERC20Tokens( + token, + DAPP_ADDRESS, + ethers.parseEther(`${amount}`), + data + ); + const depositTx = await signer.sendTransaction(deposit) + const transReceipt = await depositTx.wait(1); + setLoadERC20(false) + successAlert(transReceipt!.hash) + return transReceipt?.hash + } + } catch (e) { + setLoadERC20(false) + console.log(`${e}`); + errorAlert(e) + } +}; + +export const depositEtherToPortal = async (rollups: RollupsContracts | undefined, + provider: JsonRpcApiProvider | undefined, setLoadEther: Function, amount: number) => { + try { + if (rollups && provider) { + setLoadEther(true) + const data = ethers.toUtf8Bytes(`Deposited (${amount}) ether.`); + const txOverrides = { value: parseEther(`${amount}`) }; + console.log("Ether to deposit: ", txOverrides); + + const trans = await rollups.etherPortalContract.depositEther( + DAPP_ADDRESS, + data, + txOverrides + ); + const signer = await provider.getSigner(); + const tx = await signer.sendTransaction(trans) + setLoadEther(false) + const receipt = await tx.wait(1) + successAlert(receipt!.hash) + return receipt!.hash + } + } catch (e: any) { + setLoadEther(false) + console.log(`${e}`); + errorAlert(e) + } +}; + +export const withdrawEther = async (rollups: RollupsContracts | undefined, + provider: JsonRpcApiProvider | undefined, setLoadWithdrawEther: Function, amount: number) => { + try { + if (rollups && provider) { + setLoadWithdrawEther(true) + let ether_amount = parseEther(String(amount)).toString(); + console.log("ether after parsing: ", ether_amount); + const input_obj = { + method: "ether_withdraw", + args: { + amount: ether_amount, + }, + }; + const data = JSON.stringify(input_obj); + let payload = ethers.toUtf8Bytes(data); + const trans = await rollups.inputContract.addInput(DAPP_ADDRESS, payload); + const signer = await provider.getSigner(); + const tx = await signer.sendTransaction(trans) + const receipt = await tx.wait(1) + setLoadWithdrawEther(false) + successAlert(receipt!.hash) + return receipt!.hash + } + } catch (e) { + setLoadWithdrawEther(false) + console.log(e); + errorAlert(e) + } +}; + +export const withdrawErc20 = async (rollups: RollupsContracts | undefined, + provider: JsonRpcApiProvider | undefined, setLoadWithdrawERC20: Function, + amount: number, address: String) => { + try { + if (rollups && provider) { + setLoadWithdrawERC20(true) + let erc20_amount = parseEther(String(amount)).toString(); + console.log("erc20 after parsing: ", erc20_amount); + const input_obj = { + method: "erc20_withdraw", + args: { + erc20: address, + amount: erc20_amount, + }, + }; + const data = JSON.stringify(input_obj); + let payload = ethers.toUtf8Bytes(data); + const trans = await rollups.inputContract.addInput(DAPP_ADDRESS, payload); + const signer = await provider.getSigner(); + const tx = await signer.sendTransaction(trans) + const receipt = await tx.wait(1) + setLoadWithdrawERC20(false) + successAlert(receipt?.hash) + return receipt?.hash + } + } catch (e) { + setLoadWithdrawERC20(false) + console.log(e); + errorAlert(e) + } +}; + +export const withdrawErc721 = async (rollups: RollupsContracts | undefined, + provider: JsonRpcApiProvider | undefined, + setLoadWithdrawERC721: Function, address: String, id: number) => { + try { + if (rollups && provider) { + setLoadWithdrawERC721(true) + let erc721_id = String(id); + console.log("erc721 after parsing: ", erc721_id); + const input_obj = { + method: "erc721_withdrawal", + args: { + erc721: address, + token_id: id, + }, + }; + const data = JSON.stringify(input_obj); + let payload = ethers.toUtf8Bytes(data); + const trans = await rollups.inputContract.addInput(DAPP_ADDRESS, payload); + const signer = await provider.getSigner(); + const tx = await signer.sendTransaction(trans) + const receipt = await tx.wait(1) + setLoadWithdrawERC721(false) + successAlert(receipt?.hash) + return receipt?.hash + } + } catch (e) { + setLoadWithdrawERC721(false) + console.log(e); + errorAlert(e) + } +}; + +export const transferNftToPortal = async ( + rollups: RollupsContracts | undefined, + provider: JsonRpcApiProvider | undefined, + setLoadTransferNFT: Function, + contractAddress: string, + nftid: number +) => { + try { + if (rollups && provider) { + setLoadTransferNFT(true) + const data = ethers.toUtf8Bytes( + `Deposited (${nftid}) of ERC721 (${contractAddress}).` + ); + //const data = `Deposited ${args.amount} tokens (${args.token}) for DAppERC20Portal(${portalAddress}) (signer: ${address})`; + const signer = await provider.getSigner(); + const signerAddress = await signer.getAddress(); + + const erc721PortalAddress = rollups.erc721PortalContract.address; + + const tokenContract = signer + ? IERC721__factory.connect(contractAddress, await signer) + : IERC721__factory.connect(contractAddress, signer); + + // query current approval + const currentApproval = await tokenContract.getApproved(nftid); + if (currentApproval !== erc721PortalAddress) { + // Allow portal to withdraw `amount` tokens from signer + const tx = await tokenContract.approve(erc721PortalAddress, nftid); + const trans = await (await signer).sendTransaction(tx) + const receipt = await trans.wait(1); + const event = ( + await tokenContract.queryFilter( + tokenContract.filters.Approval(), + receipt?.hash + ) + ).pop(); + if (!event) { + throw Error( + `could not approve ${nftid} for DAppERC721Portal(${erc721PortalAddress}) (signer: ${signerAddress}, tx: ${tx.hash})` + ); + } + } + + // Transfer + const trans = await rollups.erc721PortalContract.depositERC721Token( + contractAddress, + DAPP_ADDRESS, + nftid, + "0x", + data + ); + + const tx = await (await signer).sendTransaction(trans) + const receipt = await tx.wait(1) + setLoadTransferNFT(false) + successAlert(receipt?.hash) + return receipt?.hash + } + } catch (e) { + setLoadTransferNFT(false) + console.log(`${e}`); + errorAlert(e) + } +}; + + +export const transferErc1155SingleToPortal = async ( + rollups: RollupsContracts | undefined, + provider: JsonRpcApiProvider | undefined, + setLoadERC1155: Function, + contractAddress: string, id: number, amount: number) => { + try { + if (rollups && provider) { + setLoadERC1155(true) + const data = ethers.toUtf8Bytes(`Deposited (${amount}) tokens from id (${id}) of ERC1155 (${contractAddress}).`); + //const data = `Deposited ${args.amount} tokens (${args.token}) for DAppERC20Portal(${portalAddress}) (signer: ${address})`; + const signer = await provider.getSigner(); + const signerAddress = await signer.getAddress() + + const erc1155SinglePortalAddress = await rollups.erc1155SinglePortalContract.getAddress(); + + const tokenContract = signer ? IERC1155__factory.connect(contractAddress, signer) : IERC1155__factory.connect(contractAddress, signer); + + // query current approval + const currentApproval = await tokenContract.isApprovedForAll(signerAddress,erc1155SinglePortalAddress); + if (!currentApproval) { + // Allow portal to withdraw `amount` tokens from signer + const trans = await tokenContract.setApprovalForAll(erc1155SinglePortalAddress,true); + const tx = await signer.sendTransaction(trans) + const receipt = await tx.wait(1); + const event = (await tokenContract.queryFilter(tokenContract.filters.ApprovalForAll(), receipt?.blockHash)).pop(); + if (!event) { + throw Error(`could set approval for DAppERC1155Portal(${erc1155SinglePortalAddress}) (signer: ${signerAddress}, tx: ${tx.hash})`); + } + } + + // Transfer + const depositTx = await rollups.erc1155SinglePortalContract.depositSingleERC1155Token(contractAddress,DAPP_ADDRESS, id, amount, "0x", data); + const tx = await signer.sendTransaction(depositTx) + const receipt = await tx.wait(1) + setLoadERC1155(false) + successAlert(receipt?.hash) + return receipt?.hash + } + } catch (e) { + setLoadERC1155(false) + errorAlert(e) + console.log(`${e}`); + } +}; + +export const transferErc1155BatchToPortal = async ( + rollups: RollupsContracts | undefined, + provider: JsonRpcApiProvider | undefined, + setLoadERC1155Batch: Function, + contractAddress: string, + ids: number[], + amounts: number[] +) => { + try { + if (rollups && provider) { + setLoadERC1155Batch(true) + const data = ethers.toUtf8Bytes(`Deposited (${amounts}) tokens from ids (${ids}) of ERC1155 (${contractAddress}).`); + //const data = `Deposited ${args.amount} tokens (${args.token}) for DAppERC20Portal(${portalAddress}) (signer: ${address})`; + const signer = await provider.getSigner(); + const signerAddress = await signer.getAddress() + + const erc1155BatchPortalAddress = await rollups.erc1155BatchPortalContract.getAddress(); + + const tokenContract = signer ? IERC1155__factory.connect(contractAddress, signer) : IERC1155__factory.connect(contractAddress, signer); + + // query current approval + const currentApproval = await tokenContract.isApprovedForAll(signerAddress,erc1155BatchPortalAddress); + if (!currentApproval) { + // Allow portal to withdraw `amount` tokens from signer + const trans = await tokenContract.setApprovalForAll(erc1155BatchPortalAddress,true); + const tx = await signer.sendTransaction(trans) + const receipt = await tx.wait(1); + const event = (await tokenContract.queryFilter(tokenContract.filters.ApprovalForAll(), receipt?.blockHash)).pop(); + if (!event) { + throw Error(`could set approval for DAppERC1155Portal(${erc1155BatchPortalAddress}) (signer: ${signerAddress}, tx: ${tx.hash})`); + } + } + + // Transfer + const depositTx = await rollups.erc1155BatchPortalContract.depositBatchERC1155Token(contractAddress,DAPP_ADDRESS, ids, amounts, "0x", data); + const tx = await signer.sendTransaction(depositTx) + const receipt = await tx.wait() + setLoadERC1155Batch(false) + successAlert(receipt?.hash) + return receipt?.hash + + } + } catch (e) { + setLoadERC1155Batch(false) + errorAlert(e) + console.log(`${e}`); + } +}; + diff --git a/apps/frontend/next-app/app/cartesi/Reports copy.tsx b/apps/frontend/next-app/app/cartesi/Reports copy.tsx deleted file mode 100644 index b17bdbf..0000000 --- a/apps/frontend/next-app/app/cartesi/Reports copy.tsx +++ /dev/null @@ -1,121 +0,0 @@ -"use client" - -// Copyright 2022 Cartesi Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the license at http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. -import { ethers } from "ethers"; -import React, { useEffect } from "react"; -import { useReportsQuery } from "../cartesi/generated/graphql"; -import { - Table, - Thead, - Tbody, - Tfoot, - Tr, - Th, - Td, - TableCaption, - TableContainer, - Button - } from '@chakra-ui/react' - -type Report = { - id: string; - index: number; - input: any, //{index: number; epoch: {index: number; } - payload: string; -}; - -export const Reports: React.FC = () => { - - const [result,reexecuteQuery] = useReportsQuery(); - const { data, fetching, error } = result; - - useEffect(() => { - reexecuteQuery({ requestPolicy: 'network-only' }); - }, [reexecuteQuery]); - - if (fetching) return

Loading...

; - if (error) return

Oh no... {error.message}

; - - if (!data || !data.reports) return

No reports

; - - - const reports: Report[] = data.reports.edges.map((node: any) => { - const n = node.node; - let inputPayload = n?.input.payload; - if (inputPayload) { - try { - inputPayload = ethers.toUtf8String(inputPayload); - } catch (e) { - inputPayload = inputPayload + " (hex)"; - } - } else { - inputPayload = "(empty)"; - } - let payload = n?.payload; - if (payload) { - try { - payload = ethers.toUtf8String(payload); - } catch (e) { - payload = payload + " (hex)"; - } - } else { - payload = "(empty)"; - } - return { - id: `${n?.id}`, - index: parseInt(n?.index), - payload: `${payload}`, - input: n ? {index:n.input.index,payload: inputPayload} : {}, - }; - }).sort((b: any, a: any) => { - if (a.input.index === b.input.index) { - return b.index - a.index; - } else { - return b.input.index - a.input.index; - } - }); - - // const forceUpdate = useForceUpdate(); - return ( -
-
no notices
{n.input.index} {n.index}
- - - {/* - */} - {/* */} - - - - - {reports.length === 0 && ( - - - - )} - {reports.map((n: any) => ( - - {/* - */} - {/* */} - - - ))} - -
Input IndexNotice IndexInput PayloadReports -
-
{n.input.index}{n.index}{n.input.payload}{n.payload}
- -
- ); -}; diff --git a/apps/frontend/next-app/app/cartesi/Reports.tsx b/apps/frontend/next-app/app/cartesi/Reports.tsx index b17bdbf..a91d469 100644 --- a/apps/frontend/next-app/app/cartesi/Reports.tsx +++ b/apps/frontend/next-app/app/cartesi/Reports.tsx @@ -13,7 +13,8 @@ // under the License. import { ethers } from "ethers"; import React, { useEffect } from "react"; -import { useReportsQuery } from "../cartesi/generated/graphql"; +import { useReports } from "./hooks/useReports"; + import { Table, Thead, @@ -27,65 +28,19 @@ import { Button } from '@chakra-ui/react' -type Report = { - id: string; - index: number; - input: any, //{index: number; epoch: {index: number; } - payload: string; -}; - export const Reports: React.FC = () => { - const [result,reexecuteQuery] = useReportsQuery(); - const { data, fetching, error } = result; + const {loading, error, data, reports, refetch,} = useReports(); useEffect(() => { - reexecuteQuery({ requestPolicy: 'network-only' }); - }, [reexecuteQuery]); + refetch({ requestPolicy: 'network-only' }); + }, []); - if (fetching) return

Loading...

; - if (error) return

Oh no... {error.message}

; - - if (!data || !data.reports) return

No reports

; - + if (loading) return

Loading...

; + if (error) return

Oh no... {error.message}

; - const reports: Report[] = data.reports.edges.map((node: any) => { - const n = node.node; - let inputPayload = n?.input.payload; - if (inputPayload) { - try { - inputPayload = ethers.toUtf8String(inputPayload); - } catch (e) { - inputPayload = inputPayload + " (hex)"; - } - } else { - inputPayload = "(empty)"; - } - let payload = n?.payload; - if (payload) { - try { - payload = ethers.toUtf8String(payload); - } catch (e) { - payload = payload + " (hex)"; - } - } else { - payload = "(empty)"; - } - return { - id: `${n?.id}`, - index: parseInt(n?.index), - payload: `${payload}`, - input: n ? {index:n.input.index,payload: inputPayload} : {}, - }; - }).sort((b: any, a: any) => { - if (a.input.index === b.input.index) { - return b.index - a.index; - } else { - return b.input.index - a.input.index; - } - }); + if (!data || !data.reports) return

No reports

; - // const forceUpdate = useForceUpdate(); return (
diff --git a/apps/frontend/next-app/app/cartesi/Vouchers.tsx b/apps/frontend/next-app/app/cartesi/Vouchers.tsx index af0b12d..94e81e8 100644 --- a/apps/frontend/next-app/app/cartesi/Vouchers.tsx +++ b/apps/frontend/next-app/app/cartesi/Vouchers.tsx @@ -14,7 +14,7 @@ import { ethers } from "ethers"; import React, { useEffect } from "react"; import { useVouchersQuery, useVoucherQuery } from "./generated/graphql"; -import { useRollups } from "./useRollups"; +import { useRollups } from "./hooks/useRollups"; import { Table, Thead, diff --git a/apps/frontend/next-app/app/cartesi/hooks/useInspectCall.tsx b/apps/frontend/next-app/app/cartesi/hooks/useInspectCall.tsx new file mode 100644 index 0000000..18404d9 --- /dev/null +++ b/apps/frontend/next-app/app/cartesi/hooks/useInspectCall.tsx @@ -0,0 +1,94 @@ +import { ethers } from 'ethers'; +import configFile from '../config.json'; +import { toHex } from 'viem'; +import { useState } from 'react'; +import { useAccount } from 'wagmi'; +import toast from 'react-hot-toast'; + +const config: any = configFile; + +interface Report { + payload: string; +} + +export const useInspectCall = () => { + const { chain } = useAccount(); + const [inspectData, setInspectData] = useState(""); + const [reports, setReports] = useState([]); + const [metadata, setMetadata] = useState({}); + const [hexData, setHexData] = useState(false); + const [postData, setPostData] = useState(false); + const [decodedReports, setDecodedReports] = useState({}); + + const inspectCall = async (payload: string) => { + try { + if (hexData) { + const uint8array = ethers.utils.arrayify(payload); + payload = new TextDecoder().decode(uint8array); + } + + if (!chain) { + return; + } + + let apiURL = ""; + + if (config[toHex(chain.id)]?.inspectAPIURL) { + apiURL = `${config[toHex(chain.id)].inspectAPIURL}/inspect`; + } else { + console.error(`No inspect interface defined for chain ${toHex(chain.id)}`); + return; + } + + let response; + if (postData) { + const payloadBlob = new TextEncoder().encode(payload); + response = await fetch(apiURL, { method: 'POST', body: payloadBlob }); + } else { + response = await fetch(`${apiURL}/${payload}`); + } + + const data = await response.json(); + setReports(data.reports); + setMetadata({ metadata: data.metadata, status: data.status, exception_payload: data.exception_payload }); + + // Decode payload from each report + const decode = data.reports.map((report: Report) => { + return ethers.toUtf8String(report.payload); + }); + + console.log("Decoded Reports:", decode); + const reportData: any = JSON.parse(decode); + console.log("Report data: ", reportData); + setDecodedReports(reportData); + + toast.success(reportData, { + position: 'bottom-right', + style: { + paddingRight: '40px', + }, + }) + } catch (error: any) { + console.error("Error fetching inspect data:", error) + toast.error(error.message, { + position: 'bottom-right', + style: { + paddingRight: '40px', + }, + }); + } + }; + + return { + reports, + metadata, + setInspectData, + inspectData, + setHexData, + hexData, + setPostData, + postData, + decodedReports, + inspectCall, + }; +}; diff --git a/apps/frontend/next-app/app/cartesi/hooks/useNotices.tsx b/apps/frontend/next-app/app/cartesi/hooks/useNotices.tsx new file mode 100644 index 0000000..c062686 --- /dev/null +++ b/apps/frontend/next-app/app/cartesi/hooks/useNotices.tsx @@ -0,0 +1,60 @@ +"use client" +import { useQuery } from '@apollo/client' +import { ethers } from 'ethers' +import { useState } from 'react' +import { NoticesByInputDocument } from '../generated/graphql' + +export type TNotice = { + id: string + index: number + input: any //{index: number; epoch: {index: number; } + payload: string +} + +export const useNotices = () => { + const [cursor] = useState(null) + const { loading, error, data, refetch } = useQuery(NoticesByInputDocument, { + variables: { cursor }, + pollInterval: 0, + }) + + const notices: TNotice[] = data?.notices.edges + .map((node: any) => { + const n = node.node + let inputPayload = n?.input.payload + if (inputPayload) { + try { + inputPayload = ethers.toUtf8String(inputPayload) + } catch (e) { + inputPayload = inputPayload + ' (hex)' + } + } else { + inputPayload = '(empty)' + } + let payload = n?.payload + if (payload) { + try { + payload = ethers.toUtf8String(payload) + } catch (e) { + payload = payload + ' (hex)' + } + } else { + payload = '(empty)' + } + return { + id: `${n?.id}`, + index: parseInt(n?.index), + payload: `${payload}`, + input: n ? { index: n.input.index, payload: inputPayload } : {}, + } + }) + .sort((b: any, a: any) => { + if (a.input.index === b.input.index) { + return b.index - a.index + } else { + return b.input.index - a.input.index + } + }) + + return { loading, error, data, notices, refetch } +} diff --git a/apps/frontend/next-app/app/cartesi/hooks/useReports.tsx b/apps/frontend/next-app/app/cartesi/hooks/useReports.tsx new file mode 100644 index 0000000..c2b4051 --- /dev/null +++ b/apps/frontend/next-app/app/cartesi/hooks/useReports.tsx @@ -0,0 +1,59 @@ +"use client" +import { useQuery } from '@apollo/client' +import { ethers } from 'ethers' +import { useState } from 'react' +import { gql } from 'urql' +import { ReportsByInputDocument } from '../generated/graphql' + +export type Report = { + id: string; + index: number; + input: any, //{index: number; epoch: {index: number; } + payload: string; +}; + +export const useReports = () => { + const [cursor] = useState(null) + const { loading, error, data, refetch } = useQuery(ReportsByInputDocument, { + variables: { cursor }, + pollInterval: 0, + }) + + const reports: Report[] = data && data.reports.edges.map((node: any) => { + const n = node.node; + let inputPayload = n?.input.payload; + if (inputPayload) { + try { + inputPayload = ethers.toUtf8String(inputPayload); + } catch (e) { + inputPayload = inputPayload + " (hex)"; + } + } else { + inputPayload = "(empty)"; + } + let payload = n?.payload; + if (payload) { + try { + payload = ethers.toUtf8String(payload); + } catch (e) { + payload = payload + " (hex)"; + } + } else { + payload = "(empty)"; + } + return { + id: `${n?.id}`, + index: parseInt(n?.index), + payload: `${payload}`, + input: n ? {index:n.input.index,payload: inputPayload} : {}, + }; +}).sort((b: any, a: any) => { + if (a.input.index === b.input.index) { + return b.index - a.index; + } else { + return b.input.index - a.input.index; + } +}); + + return { loading, error, data, reports, refetch } +} diff --git a/apps/frontend/next-app/app/cartesi/useRollups.tsx b/apps/frontend/next-app/app/cartesi/hooks/useRollups.tsx similarity index 85% rename from apps/frontend/next-app/app/cartesi/useRollups.tsx rename to apps/frontend/next-app/app/cartesi/hooks/useRollups.tsx index 7cda067..a5b6aed 100644 --- a/apps/frontend/next-app/app/cartesi/useRollups.tsx +++ b/apps/frontend/next-app/app/cartesi/hooks/useRollups.tsx @@ -1,23 +1,9 @@ "use client" -// Copyright 2022 Cartesi Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the license at http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - import { useEffect, useState } from "react"; -import { ethers } from "ethers"; -import { useSetChain, useWallets } from "@web3-onboard/react"; -import { useAccount, Config } from 'wagmi'; +import { useAccount } from 'wagmi'; import { Chain, toHex } from "viem"; -import { useEthersSigner } from "../utils/useEtherSigner"; +import { useEthersSigner } from "../../utils/useEtherSigner"; import { CartesiDApp, @@ -36,10 +22,8 @@ import { ERC1155SinglePortal__factory, ERC1155BatchPortal, ERC1155BatchPortal__factory -} from "../cartesi/generated/rollups"; -import configFile from "./config.json"; -import { JsonRpcSigner } from "@ethersproject/providers"; - +} from "../generated/rollups"; +import configFile from "../config.json"; const config: any = configFile; diff --git a/apps/frontend/next-app/app/chakraProvider.tsx b/apps/frontend/next-app/app/chakraProvider.tsx deleted file mode 100644 index aab79c2..0000000 --- a/apps/frontend/next-app/app/chakraProvider.tsx +++ /dev/null @@ -1,8 +0,0 @@ -// app/providers.tsx -'use client' - -import { ChakraProvider } from '@chakra-ui/react' - -export function ChakraProviders({ children }: { children: React.ReactNode }) { - return {children} -} \ No newline at end of file diff --git a/apps/frontend/next-app/app/component/Balance.tsx b/apps/frontend/next-app/app/component/Balance.tsx index 839f2b7..84ab7bd 100644 --- a/apps/frontend/next-app/app/component/Balance.tsx +++ b/apps/frontend/next-app/app/component/Balance.tsx @@ -1,101 +1,21 @@ -// Copyright 2022 Cartesi Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the license at http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -import React, { useState } from "react"; -import { useSetChain } from "@web3-onboard/react"; -import { ethers, parseEther } from "ethers"; -// import { useRollups } from "./useRollups"; -import { useAccount } from "wagmi"; -import configFile from "./../cartesi/config.json"; -//import "./App.css" - +import React from "react"; +import { ethers } from "ethers"; import { Table, Thead, Tbody, - Tfoot, Tr, Th, Td, - TableCaption, TableContainer, Button, Stack, Box, - Spacer } from '@chakra-ui/react' -import { useRollups } from "../cartesi/useRollups"; -import { toHex } from "viem"; - -const config: any = configFile; -interface Report { - payload: string; -} +import { useInspectCall } from "../cartesi/hooks/useInspectCall"; export const Balance: React.FC = () => { - const { chain } = useAccount(); - - // const rollups = useRollups("DApp Address"); - const inspectCall = async (str: string) => { - let payload = str; - if (hexData) { - const uint8array = ethers.getBytes(payload); - payload = new TextDecoder().decode(uint8array); - } - if (!chain){ - return; - } - - let apiURL= "" - - if(config[toHex(chain.id)]?.inspectAPIURL) { - apiURL = `${config[toHex(chain.id)].inspectAPIURL}/inspect`; - } else { - console.error(`No inspect interface defined for chain ${toHex(chain.id)}`); - return; - } - - let fetchData: Promise; - if (postData) { - const payloadBlob = new TextEncoder().encode(payload); - fetchData = fetch(`${apiURL}`, { method: 'POST', body: payloadBlob }); - } else { - fetchData = fetch(`${apiURL}/${payload}`); - } - fetchData - .then(response => response.json()) - .then(data => { - setReports(data.reports); - setMetadata({status: data.status, exception_payload: data.exception_payload}); - console.log("Metadata:", data.reports); - - // Decode payload from each report - const decode = data.reports.map((report: Report) => { - return ethers.toUtf8String(report.payload); - }); - console.log("Decoded Reports:", decode); - const reportData = JSON.parse(decode) - console.log("Report data: ", reportData) - setDecodedReports(reportData) - console.log("Erc20 : ", decodedReports.erc20) - //console.log(parseEther("1000000000000000000", "gwei")) - }); - }; - const [inspectData, setInspectData] = useState(""); - const [reports, setReports] = useState([]); - const [decodedReports, setDecodedReports] = useState({}); - const [metadata, setMetadata] = useState({}); - const [hexData, setHexData] = useState(false); - const [postData, setPostData] = useState(false); + const { decodedReports = {}, reports, inspectCall} = useInspectCall() return ( @@ -116,7 +36,7 @@ export const Balance: React.FC = () => { )} - { + { {decodedReports && decodedReports.ether && ( )} { decodedReports && decodedReports.erc20 && ( diff --git a/apps/frontend/next-app/app/component/FooterItems.tsx b/apps/frontend/next-app/app/component/FooterItems.tsx index 71bf721..92b5465 100644 --- a/apps/frontend/next-app/app/component/FooterItems.tsx +++ b/apps/frontend/next-app/app/component/FooterItems.tsx @@ -8,19 +8,13 @@ type FooterProps = { const footerItems : FooterProps[] = [ { - title: "Supported Templates", + title: "Templates", contentItems: [ "Reactjs & Nextjs", "Cartesify Reactjs & Nextjs", // "Javascript & Typescript" ] }, - // { - // title: "How to Use", - // contentItems: [ - // "Documentation", - // ] - // }, { title: "Follow Us", contentItems: [ diff --git a/apps/frontend/next-app/app/component/Header.tsx b/apps/frontend/next-app/app/component/Header.tsx index d7cc51e..81fbb86 100644 --- a/apps/frontend/next-app/app/component/Header.tsx +++ b/apps/frontend/next-app/app/component/Header.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { ConnectButton } from "@rainbow-me/rainbowkit"; import Link from "next/link"; import Image from "next/image"; -import devkit from "../../public/images/devkit-logo.png"; +import devkit from "../../public/images/CartDevKit.png"; import {FaBars} from 'react-icons/fa' import { IoCloseSharp } from "react-icons/io5"; import CustomButton from "./CustomButton"; @@ -19,7 +19,7 @@ const Header: React.FC = () => {

- +

diff --git a/apps/frontend/next-app/app/component/Transfers.tsx b/apps/frontend/next-app/app/component/Transfers.tsx index ffb2c92..550e895 100644 --- a/apps/frontend/next-app/app/component/Transfers.tsx +++ b/apps/frontend/next-app/app/component/Transfers.tsx @@ -1,28 +1,9 @@ "use client" -// Copyright 2022 Cartesi Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the license at http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - import React, { useState } from "react"; -import { ethers, parseEther, Provider } from "ethers"; -import { useRollups } from "../cartesi/useRollups"; -import { - IERC1155__factory, - IERC20__factory, - IERC721__factory, -} from "../cartesi/generated/rollups"; -import { Tabs, TabList, TabPanels, TabPanel, Tab, Card, CardBody } from "@chakra-ui/react"; -import { useToast } from "@chakra-ui/react"; +import { useRollups } from "../cartesi/hooks/useRollups"; +import { Tabs, TabList, TabPanels, TabPanel, Tab, Card, CardBody, Checkbox } from "@chakra-ui/react"; import { Button, Box } from "@chakra-ui/react"; -import { Input, Stack, Flex } from "@chakra-ui/react"; +import { Input, Stack } from "@chakra-ui/react"; import { Accordion, AccordionItem, @@ -35,237 +16,66 @@ import { Vouchers } from "./../cartesi/Vouchers"; import { Notices } from "./../cartesi/Notices"; import { Reports } from "./../cartesi/Reports"; import { useEthersSigner } from "../utils/useEtherSigner"; -import toast from "react-hot-toast"; -interface IInputPropos { +import { sendAddress, depositErc20ToPortal, depositEtherToPortal, + withdrawErc20, withdrawErc721, withdrawEther, transferNftToPortal, + transferErc1155SingleToPortal, + transferErc1155BatchToPortal} + from "../cartesi/Portals"; + +interface IInputProps { dappAddress: string; } -export const Transfers: React.FC = (propos) => { - const rollups = useRollups(propos.dappAddress); +const Transfers: React.FC = (props) => { + + const rollups = useRollups(props.dappAddress); const signer = useEthersSigner() const provider = signer?.provider - - const [input, setInput] = useState(""); const [dappRelayedAddress, setDappRelayedAddress] = useState(false) const [erc20Amount, setErc20Amount] = useState(0); const [erc20Token, setErc20Token] = useState(""); const [erc721Id, setErc721Id] = useState(0); const [erc721, setErc721] = useState(""); const [etherAmount, setEtherAmount] = useState(0); - const [loadEther, setLoadEther] = useState(false) - - const sendAddress = async (str: string) => { - if (rollups) { - try { - await rollups.relayContract.relayDAppAddress(propos.dappAddress); - setDappRelayedAddress(true); - } catch (e) { - console.log(`${e}`); - } - } - }; - - const depositErc20ToPortal = async (token: string, amount: number) => { - try { - if (rollups && provider) { - const data = ethers.toUtf8Bytes( - `Deposited (${amount}) of ERC20 (${token}).` - ); - //const data = `Deposited ${args.amount} tokens (${args.token}) for DAppERC20Portal(${portalAddress}) (signer: ${address})`; - // const signer = provider.getSigner(); - const signerAddress = await signer.getAddress(); - - const erc20PortalAddress = rollups.erc20PortalContract.address; - const tokenContract = signer - ? IERC20__factory.connect(token, signer) - : IERC20__factory.connect(token, signer); - // query current allowance - const currentAllowance = await tokenContract.allowance( - signerAddress, - erc20PortalAddress - ); - if (parseEther(`${amount}`) > currentAllowance) { - // Allow portal to withdraw `amount` tokens from signer - const tx = await tokenContract.approve( - erc20PortalAddress, - parseEther(`${amount}`) - ); - const trans = await signer.sendTransaction(tx) - const receipt = await trans.wait(); - const event = ( - await tokenContract.queryFilter( - tokenContract.filters.Approval(), - receipt?.hash - ) - ).pop(); - if (!event) { - throw Error( - `could not approve ${amount} tokens for DAppERC20Portal(${erc20PortalAddress}) (signer: ${signerAddress}, tx: ${tx.hash})` - ); - } - } + const [erc1155, setErc1155] = useState(""); + const [erc1155Id, setErc1155Id] = useState(); + const [erc1155Amount, setErc1155Amount] = useState(); + const [erc1155Ids, setErc1155Ids] = useState([]); + const [erc1155Amounts, setErc1155Amounts] = useState([]); + const [erc1155IdsStr, setErc1155IdsStr] = useState("[]"); + const [erc1155AmountsStr, setErc1155AmountsStr] = useState("[]"); - await rollups.erc20PortalContract.depositERC20Tokens( - token, - propos.dappAddress, - ethers.parseEther(`${amount}`), - data - ); - } - } catch (e) { - console.log(`${e}`); - } - }; - - const depositEtherToPortal = async (amount: number) => { - try { - if (rollups && provider) { - setLoadEther(true) - const data = ethers.toUtf8Bytes(`Deposited (${amount}) ether.`); - const txOverrides = { value: parseEther(`${amount}`) }; - console.log("Ether to deposit: ", txOverrides); - - // const tx = await ... - const trans = await rollups.etherPortalContract.depositEther( - propos.dappAddress, - data, - txOverrides - ); - const tx = await signer.sendTransaction(trans) - setLoadEther(false) - console.log(tx.hash) - toast(tx.hash, { - position: 'bottom-right', - }) - return tx.hash - } - } catch (e: any) { - setLoadEther(false) - console.log(`${e}`); - toast.error(e.message, { - position: 'bottom-right', - style: { - paddingRight: '40px', - }, - }) - } - }; - - const withdrawEther = async (amount: number) => { - try { - if (rollups && provider) { - let ether_amount = parseEther(String(amount)).toString(); - console.log("ether after parsing: ", ether_amount); - const input_obj = { - method: "ether_withdraw", - args: { - amount: ether_amount, - }, - }; - const data = JSON.stringify(input_obj); - let payload = ethers.toUtf8Bytes(data); - await rollups.inputContract.addInput(propos.dappAddress, payload); - } - } catch (e) { - console.log(e); - } - }; - - const withdrawErc20 = async (amount: number, address: String) => { - try { - if (rollups && provider) { - let erc20_amount = parseEther(String(amount)).toString(); - console.log("erc20 after parsing: ", erc20_amount); - const input_obj = { - method: "erc20_withdraw", - args: { - erc20: address, - amount: erc20_amount, - }, - }; - const data = JSON.stringify(input_obj); - let payload = ethers.toUtf8Bytes(data); - await rollups.inputContract.addInput(propos.dappAddress, payload); - } - } catch (e) { - console.log(e); - } - }; - - const withdrawErc721 = async (address: String, id: number) => { - try { - if (rollups && provider) { - let erc721_id = String(id); - console.log("erc721 after parsing: ", erc721_id); - const input_obj = { - method: "erc721_withdrawal", - args: { - erc721: address, - token_id: id, - }, - }; - const data = JSON.stringify(input_obj); - let payload = ethers.toUtf8Bytes(data); - await rollups.inputContract.addInput(propos.dappAddress, payload); - } - } catch (e) { - console.log(e); - } - }; - - const transferNftToPortal = async ( - contractAddress: string, - nftid: number - ) => { - try { - if (rollups && provider) { - const data = ethers.toUtf8Bytes( - `Deposited (${nftid}) of ERC721 (${contractAddress}).` - ); - //const data = `Deposited ${args.amount} tokens (${args.token}) for DAppERC20Portal(${portalAddress}) (signer: ${address})`; - const signer = provider.getSigner(); - const signerAddress = await (await signer).getAddress(); - - const erc721PortalAddress = rollups.erc721PortalContract.address; - - const tokenContract = signer - ? IERC721__factory.connect(contractAddress, await signer) - : IERC721__factory.connect(contractAddress, signer); + const [loadEther, setLoadEther] = useState(false) + const [loadERC20, setLoadERC20] = useState(false) + const [loadWithdrawEther, setLoadWithdrawEther] = useState(false) + const [loadWithdrawERC20, setLoadWithdrawERC20] = useState(false) + const [loadTransferNFT, setLoadTransferNFT] = useState(false) + const [loadWithdrawERC721, setLoadWithdrawERC721] = useState(false) + const [loadERC1155, setLoadERC1155] = useState(false) + const [loadERC1155Batch, setLoadERC1155Batch] = useState(false) + const [isChecked, setIsChecked] = useState(false) - // query current approval - const currentApproval = await tokenContract.getApproved(nftid); - if (currentApproval !== erc721PortalAddress) { - // Allow portal to withdraw `amount` tokens from signer - const tx = await tokenContract.approve(erc721PortalAddress, nftid); - const trans = await (await signer).sendTransaction(tx) - const receipt = await trans.wait(1); - const event = ( - await tokenContract.queryFilter( - tokenContract.filters.Approval(), - receipt?.hash - ) - ).pop(); - if (!event) { - throw Error( - `could not approve ${nftid} for DAppERC721Portal(${erc721PortalAddress}) (signer: ${signerAddress}, tx: ${tx.hash})` - ); - } - } + const handleCheckboxChange = (e: React.ChangeEvent) => { + setIsChecked(e.target.checked) + } + const addTo1155Batch = () => { + const newIds = erc1155Ids; + newIds.push(erc1155Id); + setErc1155Ids(newIds); + const newAmounts = erc1155Amounts; + newAmounts.push(erc1155Amount); + setErc1155Amounts(newAmounts); + setErc1155IdsStr("["+erc1155Ids.join(',')+"]"); + setErc1155AmountsStr("["+erc1155Amounts.join(',')+"]"); +}; - // Transfer - rollups.erc721PortalContract.depositERC721Token( - contractAddress, - propos.dappAddress, - nftid, - "0x", - data - ); - } - } catch (e) { - console.log(`${e}`); - } - }; +const clear1155Batch = () => { + setErc1155IdsStr("[]"); + setErc1155AmountsStr("[]"); + setErc1155Ids([]); + setErc1155Amounts([]); +}; return ( @@ -295,10 +105,10 @@ export const Transfers: React.FC = (propos) => { the base layer.
- - -

- + + +

+ Ether @@ -321,7 +131,7 @@ export const Transfers: React.FC = (propos) => { colorScheme="blue" size="sm" onClick={() => { - depositEtherToPortal(etherAmount); + depositEtherToPortal(rollups, provider, setLoadEther, etherAmount); }} disabled={!rollups} > {loadEther ? "Depositing please wait..🤑" :"Deposit"} @@ -329,11 +139,11 @@ export const Transfers: React.FC = (propos) => {
@@ -372,21 +182,18 @@ export const Transfers: React.FC = (propos) => {
@@ -425,25 +232,110 @@ export const Transfers: React.FC = (propos) => {

+ + +
+

+ + ERC1155 & ERC1155Batch + + +

+
+ Set Batch +
+
+ + + setErc1155(String(e.target.value))} + value={erc1155} + /> + setErc1155Id(Number(e.target.value))} + value={erc1155Id} + /> + setErc1155Amount(String(e.target.value))} + value={erc1155Amount} + /> + + + { !isChecked ? + : + +
+ Ids: {erc1155IdsStr} - Amounts: {erc1155AmountsStr} + + + + +
} +
+
+
+ @@ -459,7 +351,7 @@ export const Transfers: React.FC = (propos) => {

} - {dappRelayedAddress && } + {dappRelayedAddress && } -
- {/* */} +
+
- - //
+ ); }; + +export default Transfers \ No newline at end of file diff --git a/apps/frontend/next-app/app/component/footer.tsx b/apps/frontend/next-app/app/component/footer.tsx index 5c5c765..aa1b9fc 100644 --- a/apps/frontend/next-app/app/component/footer.tsx +++ b/apps/frontend/next-app/app/component/footer.tsx @@ -1,13 +1,13 @@ import Link from "next/link" import FooterItems from "./FooterItems" -import devkit from "../../public/images/devkit-logo.png"; +import devkit from "../../public/images/CartDevKit.png"; import Image from "next/image"; const Footer: React.FC = () => { return(
- + This template was built with 💟 for the Cartesi community by: Africinnovate Team diff --git a/apps/frontend/next-app/app/favicon.ico b/apps/frontend/next-app/app/favicon.ico index c040808be8d0ad83ac721ab8e7462f80dc1cf225..f8adb36235c4268e68dd004daeee069844806a16 100644 GIT binary patch literal 17128 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 literal 50629 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 diff --git a/apps/frontend/next-app/app/greetings/page.tsx b/apps/frontend/next-app/app/greetings/page.tsx index 0d5e7df..7d31277 100644 --- a/apps/frontend/next-app/app/greetings/page.tsx +++ b/apps/frontend/next-app/app/greetings/page.tsx @@ -2,70 +2,65 @@ import {useState, useEffect } from 'react' import { useAccount } from 'wagmi' import { useEthersSigner } from '../utils/useEtherSigner' -import { BASE_URL, DAPP_ADDRESS } from '@/app/utils/constants' +import { DAPP_ADDRESS } from '@/app/utils/constants' import { Box } from '@chakra-ui/react' -import { useRollups } from '../cartesi/useRollups' +import { useRollups } from '../cartesi/hooks/useRollups' import toast from 'react-hot-toast' +import { useNotices } from '../cartesi/hooks/useNotices' +import { Notices } from '../cartesi/Notices' +import { ethers } from 'ethers' +import { addInput } from '../cartesi/Portals' export default function Greetings() { - const [result, setResult] = useState("") - // const [signer, setSigner] = useState(undefined) - const [loading, setLoading] = useState(false) const [greeting, setGreeting] = useState('') - const [loadGreeting, setLoadGreeting] = useState(false) + const [loadGreeting, setLoading] = useState(false) const rollups = useRollups(DAPP_ADDRESS); const { isConnected } = useAccount(); const signer = useEthersSigner() - const provider = signer?.provider + const { refetch } = useNotices() const handleMessageChange = (e: React.ChangeEvent) =>{ console.log(e.target.value) setGreeting(e.target.value) } - const jsonPayload = JSON.stringify({ - method: 'sendgreeting', - data: greeting, - }) const sendGreeting = async () => { - if(!isConnected) return toast("Please connect your wallet") - if (rollups) { - try { - const trans = await rollups.inputContract.addInput(JSON.stringify(jsonPayload), DAPP_ADDRESS); - const tx = await signer?.sendTransaction(trans) - const receipt = await tx?.wait() - console.log("receipt", receipt?.hash) - return receipt?.hash - } catch (e) { - console.log(`${e}`); - } - } - }; + const jsonPayload = JSON.stringify({ + method: 'sendgreeting', + args: { + amount: greeting, + }, + }) - const getGreeting = async () => { if(!isConnected) return toast("Please connect your wallet") - console.log("Hello world") - } - + if(!greeting) return toast.error("Input field should not be empty") + + await addInput(rollups, signer, setLoading, jsonPayload) + }; + useEffect(() => { + const handleInputAdded = () => { + console.log('Input added, refetching notices') + refetch() + } + // Add event listener for inputAdded event + rollups?.inputContract.on('InputAdded', handleInputAdded) + // Cleanup function to remove event listener + return () => { + rollups?.inputContract.off('InputAdded', handleInputAdded) + } + }, [rollups, refetch]) - return ( + return (

Welcome! Say Hello 👋

- - - - -

- {greeting && greeting} -

-

{`Result: ${result}`}

+

Output

+
); } diff --git a/apps/frontend/next-app/app/layout copy.tsx b/apps/frontend/next-app/app/layout copy.tsx deleted file mode 100644 index 198ba72..0000000 --- a/apps/frontend/next-app/app/layout copy.tsx +++ /dev/null @@ -1,73 +0,0 @@ -"use client" -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; -import "./globals.css"; -import '@rainbow-me/rainbowkit/styles.css'; -import Header from "./component/Header"; -import { GraphQLProvider } from "./cartesi/GraphQL"; -import { useState } from "react"; -import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client' -import { Toaster } from 'react-hot-toast' -import { ChakraProviders } from './chakraProvider' -import { - ChakraBaseProvider, - extendBaseTheme, - theme as chakraTheme, -} from '@chakra-ui/react' - -const inter = Inter({ subsets: ["latin"] }); - -// export const metadata: Metadata = { -// title: "Create Next App", -// description: "Generated by create next app", -// }; - -import configFile from "./cartesi/config.json"; - -const config: any = configFile; - -import injectedModule from "@web3-onboard/injected-wallets"; -import { init } from "@web3-onboard/react"; - -const injected: any = injectedModule(); -init({ - wallets: [injected], - chains: Object.entries(config).map(([k, v]: [string, any], i) => ({id: k, token: v.token, label: v.label, rpcUrl: v.rpcUrl})), - appMetadata: { - name: "Cartesi Rollups Test DApp", - icon: "", - description: "Demo app for Cartesi Rollups", - recommendedInjectedWallets: [ - { name: "MetaMask", url: "https://metamask.io" }, - ], - }, -}); - -//Setup GraphQL Apollo client -// const URL_QUERY_GRAPHQL = 'http://localhost:8080/graphql' -const URL_QUERY_GRAPHQL = 'http://localhost:8080/host-runner' - -const client = new ApolloClient({ - uri: URL_QUERY_GRAPHQL, - cache: new InMemoryCache(), -}) - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>){ - return ( - - - - - {/*
*/} - {children} - - - - - - ); -} diff --git a/apps/frontend/next-app/app/layout.tsx b/apps/frontend/next-app/app/layout.tsx index b9b6172..51a35dd 100644 --- a/apps/frontend/next-app/app/layout.tsx +++ b/apps/frontend/next-app/app/layout.tsx @@ -2,39 +2,20 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; -import { useMemo, createContext, useContext, useState } from 'react'; import '@rainbow-me/rainbowkit/styles.css'; import { Providers } from './utils/providers'; import Header from "./component/Header"; import Footer from "./component/Footer"; import { ChakraProvider } from "@chakra-ui/react"; -// import { Provider as URQLProvider } from "urql"; import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client' import { Toaster } from 'react-hot-toast' -import { - UrqlProvider, - ssrExchange, - cacheExchange, - fetchExchange, - createClient, -} from '@urql/next'; -import GraphQLClient from "./GraphqlClient"; +import { API_BASE_URL } from "./utils/constants"; const inter = Inter({ subsets: ["latin"] }); // export const metadata: Metadata = { // title: "Create Next App", // description: "Generated by create next app", // }; -const API_BASE_URL = 'http://localhost:8080/graphql' - -//Setup GraphQL Apollo client -// const URL_QUERY_GRAPHQL = 'http://localhost:8080/graphql' -// const URL_QUERY_GRAPHQL = 'http://localhost:8080/host-runner' - -// const client = new ApolloClient({ -// uri: URL_QUERY_GRAPHQL, -// cache: new InMemoryCache(), -// }) export default function RootLayout({ children, @@ -42,32 +23,24 @@ export default function RootLayout({ children: React.ReactNode; }>) { - const [client, ssr] = useMemo(() => { - const ssr = ssrExchange({ - isClient: typeof window !== 'undefined', - }); - const client = createClient({ - url: API_BASE_URL, - exchanges: [cacheExchange, ssr, fetchExchange], - suspense: true, - }); - - return [client, ssr]; - }, []); + const apolloClient = new ApolloClient({ + uri: API_BASE_URL, + cache: new InMemoryCache(), + }) return ( - +
{ children }
{ethers.formatEther(decodedReports.ether)}