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 = ({ >