From 9af45a906e1cd6a123768de91455c6e04a9938d8 Mon Sep 17 00:00:00 2001 From: Nadai2010 Date: Thu, 21 Nov 2024 13:44:26 +0000 Subject: [PATCH] Update and Sync ch-2 with last version Scaffold Stark --- .husky/pre-commit | 13 - CONTRIBUTING.md | 6 +- packages/nextjs/.env | 1 - .../nextjs/app/api/price/[symbol]/route.ts | 31 + .../app/debug/_components/contract/Array.tsx | 31 +- .../_components/contract/ContractInput.tsx | 21 +- .../contract/ContractReadMethods.tsx | 3 - .../_components/contract/DisplayVariable.tsx | 10 +- .../contract/ReadOnlyFunctionForm.tsx | 35 +- .../app/debug/_components/contract/Struct.tsx | 9 +- .../debug/_components/contract/TxReceipt.tsx | 20 +- .../contract/WriteOnlyFunctionForm.tsx | 43 +- .../contract/__test__/mock/mockABI.ts | 490 ++++ .../_components/contract/utilsContract.tsx | 163 +- .../_components/contract/utilsDisplay.tsx | 317 ++- packages/nextjs/app/layout.tsx | 1 - packages/nextjs/components/Footer.tsx | 1 + .../components/scaffold-stark/Address.tsx | 15 +- .../CustomConnectButton/ConnectModal.tsx | 1 + .../components/scaffold-stark/Input/utils.ts | 85 +- .../seed/mockDeployedContractData.ts | 302 +++ .../hooks/scaffold-stark/useAutoConnect.ts | 7 +- .../scaffold-stark/useNativeCurrencyPrice.ts | 45 +- .../scaffold-stark/useScaffoldEventHistory.ts | 136 +- .../useScaffoldMultiWriteContract.ts | 47 - .../useScaffoldWriteContract.ts | 51 +- packages/nextjs/package.json | 7 +- packages/nextjs/services/web3/PriceService.ts | 116 + .../web3/__test__/PriceService.test.ts | 344 +++ packages/nextjs/services/web3/connectors.tsx | 2 + .../nextjs/utils/scaffold-stark/common.ts | 12 +- .../nextjs/utils/scaffold-stark/contract.ts | 163 +- .../scaffold-stark/fetchPriceFromCoingecko.ts | 60 - packages/nextjs/utils/scaffold-stark/index.ts | 1 - .../nextjs/utils/scaffold-stark/networks.ts | 21 +- packages/nextjs/utils/scaffold-stark/types.ts | 4 +- packages/snfoundry/package.json | 2 +- .../snfoundry/scripts-ts/deploy-contract.ts | 35 +- .../scripts-ts/helpers/deploy-wrapper.ts | 33 +- yarn.lock | 2244 ++++++++--------- 40 files changed, 3004 insertions(+), 1924 deletions(-) delete mode 100644 packages/nextjs/.env create mode 100644 packages/nextjs/app/api/price/[symbol]/route.ts create mode 100644 packages/nextjs/app/debug/_components/contract/__test__/mock/mockABI.ts create mode 100644 packages/nextjs/hooks/scaffold-stark/__tests__/seed/mockDeployedContractData.ts create mode 100644 packages/nextjs/services/web3/PriceService.ts create mode 100644 packages/nextjs/services/web3/__test__/PriceService.test.ts delete mode 100644 packages/nextjs/utils/scaffold-stark/fetchPriceFromCoingecko.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index fa2cd8d7..de5163db 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,17 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -DESIRED_COMMIT="46e0ec032956f0e7cbe0330f32b6b31eff824087" - -cd packages/snfoundry/local-devnet - -LAST_COMMIT=$(git log -1 --format="%H") - -if [ "$LAST_COMMIT" != "$DESIRED_COMMIT" ]; then - echo "FAIL: Last local-devnet commit ($LAST_COMMIT) is not the desired ($DESIRED_COMMIT)." - exit 1 -fi - -cd - - npm run format:check diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a6e589a2..2da73951 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,14 @@ # Contributing to Scaffold-Stark -Thank you for your interest in contributing to Scaffold-Stark! Your support enhances this StarkNet-focused framework that bridges smart contract integration with web applications. +Thank you for your interest in contributing to Scaffold-Stark! Your support enhances this Starknet-focused framework that bridges smart contract integration with web applications. ## About the Project -**Scaffold-Stark** provides a full DApp development template, offering seamless integration of StarkNet smart contracts with web applications. **SpeedrunStark.com** offers interactive challenges that serve to practice your Cairo and StarkNet skills, learn how to use the provided hooks, and launch your applications swiftly with potential rewards. +**Scaffold-Stark** provides a full DApp development template, offering seamless integration of Starket smart contracts with web applications. **SpeedrunStark.com** offers interactive challenges that serve to practice your Cairo and Starknet skills, learn how to use the provided hooks, and launch your applications swiftly with potential rewards. ## Project Vision -Our goal is to simplify the DApp development and learning process, enabling developers to launch applications efficiently while understanding the intricacies of StarkNet. +Our goal is to simplify the DApp development and learning process, enabling developers to launch applications efficiently while understanding the intricacies of Starknet. ## How to Get Involved diff --git a/packages/nextjs/.env b/packages/nextjs/.env deleted file mode 100644 index 90efad63..00000000 --- a/packages/nextjs/.env +++ /dev/null @@ -1 +0,0 @@ -NEXT_PUBLIC_PROVIDER_URL=https://starknet-sepolia.infura.io/v3/47279f22f9954d6facd977b57c932b6a \ No newline at end of file diff --git a/packages/nextjs/app/api/price/[symbol]/route.ts b/packages/nextjs/app/api/price/[symbol]/route.ts new file mode 100644 index 00000000..1b4a3f94 --- /dev/null +++ b/packages/nextjs/app/api/price/[symbol]/route.ts @@ -0,0 +1,31 @@ +export async function GET( + _: Request, + { params: { symbol } }: { params: { symbol: string } }, +) { + let apiUrl = ""; + if (symbol === "ETH") { + apiUrl = + "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd"; + } else if (symbol === "STRK") { + apiUrl = + "https://api.coingecko.com/api/v3/simple/price?ids=starknet&vs_currencies=usd"; + } else { + return Response.json({ + ethereum: { usd: 0 }, + starknet: { usd: 0 }, + }); + } + try { + const response = await fetch(apiUrl); + if (!response.ok) { + throw new Error(`coingecko response status: ${response.status}`); + } + const json = await response.json(); + return Response.json(json); + } catch (e) { + return Response.json({ + ethereum: { usd: 0 }, + starknet: { usd: 0 }, + }); + } +} diff --git a/packages/nextjs/app/debug/_components/contract/Array.tsx b/packages/nextjs/app/debug/_components/contract/Array.tsx index d5803960..43a5d15f 100644 --- a/packages/nextjs/app/debug/_components/contract/Array.tsx +++ b/packages/nextjs/app/debug/_components/contract/Array.tsx @@ -9,6 +9,7 @@ import { replacer } from "~~/utils/scaffold-stark/common"; import { ContractInput } from "./ContractInput"; import { Abi } from "abi-wan-kanabi"; import { parseGenericType } from "~~/utils/scaffold-stark"; +import { FormErrorMessageState } from "./utilsDisplay"; type ArrayProps = { abi: Abi; @@ -16,7 +17,7 @@ type ArrayProps = { parentForm: Record | undefined; setParentForm: (form: Record) => void; parentStateObjectKey: string; - setFormErrorMessage: Dispatch>; + setFormErrorMessage: Dispatch>; }; export const ArrayInput = ({ @@ -67,23 +68,21 @@ export const ArrayInput = ({ | Record | ((arg: Record) => void), ) => { - let nextInputObject: Record = nextInputRecipe; + // if we find a function (a.k.a setState recipe), we run it to generate the next state based on recpe, else just use the object passed in + const nextInputObject: Record = + typeof nextInputRecipe === "function" + ? nextInputRecipe(parentForm!) + : nextInputRecipe; - // set state recipe function, handle - if (typeof nextInputRecipe === "function") { - nextInputObject = nextInputRecipe(parentForm!); - } - - const currentInputArray = { ...inputArr }; - - // we do some nasty workaround - currentInputArray[index] = - nextInputObject?.[`input_${index}`] || null; - - setInputArr(currentInputArray); + setInputArr((currentInputArray: any) => { + return { + ...currentInputArray, + [index]: nextInputObject?.[index] || null, + }; + }); }} - form={inputArr[index]} - stateObjectKey={`input_${index}`} + form={inputArr} + stateObjectKey={index} paramType={ { name: `${abiParameter.name}[${index}]`, diff --git a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx index 9cde96ed..4fa53bdf 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx @@ -3,11 +3,17 @@ import { Dispatch, SetStateAction } from "react"; import { InputBase, IntegerInput } from "~~/components/scaffold-stark"; import { AbiParameter } from "~~/utils/scaffold-stark/contract"; -import { displayType } from "./utilsDisplay"; +import { + addError, + clearError, + displayType, + FormErrorMessageState, +} from "./utilsDisplay"; import { isCairoArray, isCairoBigInt, isCairoInt, + isCairoTuple, isCairoType, isCairoU256, } from "~~/utils/scaffold-stark"; @@ -21,7 +27,7 @@ type ContractInputProps = { form: Record | undefined; stateObjectKey: string; paramType: AbiParameter; - setFormErrorMessage: Dispatch>; + setFormErrorMessage: Dispatch>; }; export const ContractInput = ({ @@ -58,6 +64,11 @@ export const ContractInput = ({ setFormErrorMessage={setFormErrorMessage} /> ); + } + + // we prio tuples here to avoid wrong input + else if (isCairoTuple(paramType.type)) { + return ; } else if ( isCairoInt(paramType.type) || isCairoBigInt(paramType.type) || @@ -68,7 +79,11 @@ export const ContractInput = ({ {...inputProps} variant={paramType.type} onError={(errMessage: string | null) => - setFormErrorMessage(errMessage) + setFormErrorMessage((prev) => { + if (!!errMessage) + return addError(prev, "intError" + stateObjectKey, errMessage); + return clearError(prev, "intError" + stateObjectKey); + }) } /> ); diff --git a/packages/nextjs/app/debug/_components/contract/ContractReadMethods.tsx b/packages/nextjs/app/debug/_components/contract/ContractReadMethods.tsx index e77bde50..dce6d0fc 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractReadMethods.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractReadMethods.tsx @@ -1,10 +1,7 @@ import { Abi } from "abi-wan-kanabi"; import { - AbiFunction, Contract, ContractName, - GenericContract, - InheritedFunctions, getFunctionsByStateMutability, } from "~~/utils/scaffold-stark/contract"; import { ReadOnlyFunctionForm } from "./ReadOnlyFunctionForm"; diff --git a/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx b/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx index d3f293ef..fa2849a8 100644 --- a/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx +++ b/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx @@ -9,7 +9,7 @@ import { Abi } from "abi-wan-kanabi"; import { Address } from "@starknet-react/chains"; import { useReadContract } from "@starknet-react/core"; import { BlockNumber } from "starknet"; -import { displayTxResult } from "./utilsDisplay"; +import { decodeContractResponse } from "./utilsDisplay"; import { useTheme } from "next-themes"; type DisplayVariableProps = { @@ -85,7 +85,13 @@ export const DisplayVariable = ({ showAnimation ? "bg-warning rounded-sm animate-pulse-fast" : "" }`} > - {displayTxResult(result, false, abiFunction?.outputs)} + {decodeContractResponse({ + resp: result, + abi, + functionOutputs: abiFunction?.outputs, + asText: true, + showAsString: true, + })} diff --git a/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx index ea641a83..ff5017f4 100644 --- a/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx +++ b/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx @@ -4,15 +4,18 @@ import { useState, useRef, useEffect } from "react"; import { Abi } from "abi-wan-kanabi"; import { Address } from "@starknet-react/chains"; import { - displayTxResult, getFunctionInputKey, getInitialFormState, - getParsedContractFunctionArgs, + getArgsAsStringInputFromForm, transformAbiFunction, + FormErrorMessageState, + isError, + getTopErrorMessage, + decodeContractResponse, } from "~~/app/debug/_components/contract"; import { AbiFunction } from "~~/utils/scaffold-stark/contract"; import { BlockNumber } from "starknet"; -import { useReadContract } from "@starknet-react/core"; +import { useContract, useReadContract } from "@starknet-react/core"; import { ContractInput } from "./ContractInput"; type ReadOnlyFunctionFormProps = { @@ -30,15 +33,21 @@ export const ReadOnlyFunctionForm = ({ getInitialFormState(abiFunction), ); const [inputValue, setInputValue] = useState(undefined); - const [formErrorMessage, setFormErrorMessage] = useState(null); + const [formErrorMessage, setFormErrorMessage] = + useState({}); const lastForm = useRef(form); + const { contract: contractInstance } = useContract({ + abi, + address: contractAddress, + }); + const { isFetching, data, refetch, error } = useReadContract({ address: contractAddress, functionName: abiFunction.name, abi: [...abi], args: inputValue || [], - enabled: !!inputValue, + enabled: !!inputValue && !!contractInstance, blockIdentifier: "pending" as BlockNumber, }); @@ -66,8 +75,7 @@ export const ReadOnlyFunctionForm = ({ }); const handleRead = () => { - const newInputValue = getParsedContractFunctionArgs(form, false, true); - + const newInputValue = getArgsAsStringInputFromForm(form); if (JSON.stringify(form) !== JSON.stringify(lastForm.current)) { setInputValue(newInputValue); lastForm.current = form; @@ -87,7 +95,12 @@ export const ReadOnlyFunctionForm = ({

Result:

-                {displayTxResult(data, false, abiFunction?.outputs)}
+                {decodeContractResponse({
+                  resp: data,
+                  abi,
+                  functionOutputs: abiFunction?.outputs,
+                  asText: true,
+                })}
               
)} @@ -95,15 +108,15 @@ export const ReadOnlyFunctionForm = ({
-
{displayTxResult(txResult, false)}
+
+            {decodeContractResponse({
+              resp: txResult,
+              abi: [],
+              functionOutputs: [],
+              asText: true,
+            })}
+          
diff --git a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx index 46459431..e765b71d 100644 --- a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx +++ b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx @@ -6,14 +6,18 @@ import { // TxReceipt, getFunctionInputKey, getInitialFormState, - getParsedContractFunctionArgs, + getArgsAsStringInputFromForm, transformAbiFunction, + FormErrorMessageState, + getTopErrorMessage, + isError, } from "~~/app/debug/_components/contract"; import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork"; import { useSendTransaction, useNetwork, useTransactionReceipt, + useContract, } from "@starknet-react/core"; import { Abi } from "abi-wan-kanabi"; import { AbiFunction } from "~~/utils/scaffold-stark/contract"; @@ -40,7 +44,8 @@ export const WriteOnlyFunctionForm = ({ const [form, setForm] = useState>(() => getInitialFormState(abiFunction), ); - const [formErrorMessage, setFormErrorMessage] = useState(null); + const [formErrorMessage, setFormErrorMessage] = + useState({}); const { status: walletStatus, isConnected, account, chainId } = useAccount(); const { chain } = useNetwork(); const writeTxn = useTransactor(); @@ -54,23 +59,17 @@ export const WriteOnlyFunctionForm = ({ [chain, targetNetwork.network, walletStatus], ); + const { contract: contractInstance } = useContract({ + abi, + address: contractAddress, + }); + const { data: result, isPending: isLoading, sendAsync, error, - } = useSendTransaction({ - calls: [ - { - contractAddress, - entrypoint: abiFunction.name, - - // use infinity to completely flatten array from n dimensions to 1 dimension - // writing in starknet next still needs rawArgs parsing, use v2 parsing - calldata: getParsedContractFunctionArgs(form, false).flat(Infinity), - }, - ], - }); + } = useSendTransaction({}); // side effect for error logging useEffect(() => { @@ -83,7 +82,17 @@ export const WriteOnlyFunctionForm = ({ const handleWrite = async () => { if (sendAsync) { try { - const makeWriteWithParams = () => sendAsync(); + const makeWriteWithParams = () => + sendAsync( + !!contractInstance + ? [ + contractInstance.populate( + abiFunction.name, + getArgsAsStringInputFromForm(form), + ), + ] + : [], + ); await writeTxn(makeWriteWithParams); onChange(); } catch (e: any) { @@ -131,7 +140,7 @@ export const WriteOnlyFunctionForm = ({ const errorMsg = (() => { if (writeDisabled) return "Wallet not connected or on wrong network"; - return formErrorMessage; + return getTopErrorMessage(formErrorMessage); })(); return ( @@ -163,7 +172,7 @@ export const WriteOnlyFunctionForm = ({ >