diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 28d3f4e9..32e84eaf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,13 +26,13 @@ jobs: uses: actions/checkout@master - name: Install scarb - run: curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.8.2 + run: curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.8.5 - name: Install snfoundryup run: curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh - name: Install snforge - run: snfoundryup -v 0.30.0 + run: snfoundryup -v 0.33.0 - name: Run snforge tests run: snforge test 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/.tool-versions b/.tool-versions index 63a1f801..40fcaab2 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -scarb 2.8.3 -starknet-foundry 0.31.0 +scarb 2.8.5 +starknet-foundry 0.33.0 starknet-devnet 0.2.0 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/README.md b/README.md index 9de0608c..d69dcc99 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,9 @@ If your local starknet-devnet version is not `0.2.0`, you need to install it. ### Compatible versions - Starknet-devnet - v0.2.0 -- Scarb - v2.8.3 -- Snforge - v0.31.0 -- Cairo - v2.8.2 +- Scarb - v2.8.5 +- Snforge - v0.33.0 +- Cairo - v2.8.5 - RPC - v0.7.1 Make sure you have the compatible versions otherwise refer to [Scaffold-Stark Requirements](https://github.com/Scaffold-Stark/scaffold-stark-2?.tab=readme-ov-file#requirements) 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..76084c80 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,8 @@ type ArrayProps = { parentForm: Record | undefined; setParentForm: (form: Record) => void; parentStateObjectKey: string; - setFormErrorMessage: Dispatch>; + setFormErrorMessage: Dispatch>; + isDisabled?: boolean; }; export const ArrayInput = ({ @@ -26,6 +28,7 @@ export const ArrayInput = ({ parentStateObjectKey, abiParameter, setFormErrorMessage, + isDisabled, }: ArrayProps) => { // array in object representation const [inputArr, setInputArr] = useState({}); @@ -62,28 +65,27 @@ export const ArrayInput = ({ | ((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..846935b7 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx @@ -3,11 +3,19 @@ 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, + isCairoOption, + isCairoResult, + isCairoTuple, isCairoType, isCairoU256, } from "~~/utils/scaffold-stark"; @@ -21,7 +29,8 @@ type ContractInputProps = { form: Record | undefined; stateObjectKey: string; paramType: AbiParameter; - setFormErrorMessage: Dispatch>; + setFormErrorMessage: Dispatch>; + isDisabled?: boolean; }; export const ContractInput = ({ @@ -31,6 +40,7 @@ export const ContractInput = ({ stateObjectKey, paramType, setFormErrorMessage, + isDisabled, }: ContractInputProps) => { const inputProps = { name: stateObjectKey, @@ -56,8 +66,14 @@ export const ContractInput = ({ parentForm={form} setParentForm={setForm} setFormErrorMessage={setFormErrorMessage} + isDisabled={isDisabled} /> ); + } + + // we prio tuples here to avoid wrong input + else if (isCairoTuple(paramType.type)) { + return ; } else if ( isCairoInt(paramType.type) || isCairoBigInt(paramType.type) || @@ -67,13 +83,22 @@ export const ContractInput = ({ - setFormErrorMessage(errMessage) + setFormErrorMessage((prev) => { + if (!!errMessage) + return addError(prev, "intError" + stateObjectKey, errMessage); + return clearError(prev, "intError" + stateObjectKey); + }) } /> ); - } else if (isCairoType(paramType.type)) { - return ; + } else if ( + isCairoType(paramType.type) && + !isCairoResult(paramType.type) && + !isCairoOption(paramType.type) + ) { + return ; } else { return ( member.name === paramType.type, )} + isDisabled={isDisabled} /> ); } diff --git a/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx b/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx index d3f293ef..23f2107c 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: false, + })} 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 = ({