diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4c4ea6e01..7c61cee67 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,12 +34,6 @@ jobs: run: yarn install --immutable working-directory: ./packages/nextjs - - name: Install scarb - run: curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.5.4 - - - name: Check Code Format - run: npm run format:check - - name: Run Next.js lint run: yarn next:lint --max-warnings=0 working-directory: ./packages/nextjs @@ -50,4 +44,4 @@ jobs: - name: Build Next.js project run: yarn build - working-directory: ./packages/nextjs + working-directory: ./packages/nextjs \ No newline at end of file diff --git a/README.md b/README.md index 2c89b234c..99e34b4b4 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ Before you begin, you need to install the following tools: ### Compatible versions -- Scarb - v2.5.4 -- Snforge - v0.25.0 -- Cairo - v2.5.4 +- Scarb - v2.6.5 +- Snforge - v0.27.0 +- Cairo - v2.6.4 Make sure you have the compatible versions otherwise refer to [Scaffold-Stark Requirements](https://github.com/Quantum3-Labs/scaffold-stark-2?.tab=readme-ov-file#requirements) diff --git a/package.json b/package.json index e21e893ec..bfc7b50a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ss-2", - "version": "0.2.3", + "version": "0.2.6", "author": "Q3 Labs", "license": "MIT", "private": true, @@ -13,6 +13,7 @@ "scripts": { "chain": "yarn workspace @ss-2/snfoundry chain", "deploy": "yarn workspace @ss-2/snfoundry deploy", + "deploy:reset": "yarn workspace @ss-2/snfoundry deploy:reset", "test": "yarn workspace @ss-2/snfoundry test", "compile": "yarn workspace @ss-2/snfoundry compile", "start": "yarn workspace @ss-2/nextjs dev", diff --git a/packages/nextjs/app/debug/_components/contract/ContractUI.tsx b/packages/nextjs/app/debug/_components/contract/ContractUI.tsx index 3d6d10100..6b5e4bfbe 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractUI.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractUI.tsx @@ -5,11 +5,15 @@ import { useReducer } from "react"; import { ContractReadMethods } from "./ContractReadMethods"; // import { ContractWriteMethods } from "./ContractWriteMethods"; import { Address, Balance } from "~~/components/scaffold-stark"; -import { useDeployedContractInfo } from "~~/hooks/scaffold-stark"; +import { + useDeployedContractInfo, + useNetworkColor, +} from "~~/hooks/scaffold-stark"; import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork"; import { ContractName } from "~~/utils/scaffold-stark/contract"; import { ContractVariables } from "./ContractVariables"; import { ContractWriteMethods } from "./ContractWriteMethods"; +import { ClassHash } from "~~/components/scaffold-stark/ClassHash"; type ContractUIProps = { contractName: ContractName; @@ -51,13 +55,17 @@ export const ContractUI = ({
-
+
- {contractName} + {contractName}
+
Balance: )}
+
{ return ( <> + {/*
+

Debug Contracts

+

+ You can debug & interact with your deployed contracts here. +
Check{" "} + + packages / nextjs / app / debug / page.tsx + {" "} +

+
*/} ); }; diff --git a/packages/nextjs/components/Footer.tsx b/packages/nextjs/components/Footer.tsx index 31f75fea3..2b1d59907 100644 --- a/packages/nextjs/components/Footer.tsx +++ b/packages/nextjs/components/Footer.tsx @@ -1,15 +1,13 @@ import React from "react"; -import Link from "next/link"; -import { - CurrencyDollarIcon, - MagnifyingGlassIcon, -} from "@heroicons/react/24/outline"; +import { CurrencyDollarIcon } from "@heroicons/react/24/outline"; import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork"; import { useGlobalState } from "~~/services/store/store"; -import { devnet } from "@starknet-react/chains"; -import { Faucet } from "./scaffold-stark"; -import { getBlockExplorerLink } from "~~/utils/scaffold-stark"; +import { devnet, sepolia, mainnet } from "@starknet-react/chains"; +import { Faucet } from "~~/components/scaffold-stark/Faucet"; +import { FaucetSepolia } from "~~/components/scaffold-stark/FaucetSepolia"; +import { BlockExplorerSepolia } from "./scaffold-stark/BlockExplorerSepolia"; +import { BlockExplorer } from "./scaffold-stark/BlockExplorer"; /** * Site footer @@ -20,12 +18,14 @@ export const Footer = () => { ); const { targetNetwork } = useTargetNetwork(); const isLocalNetwork = targetNetwork.id === devnet.id; + const isSepoliaNetwork = targetNetwork.id === sepolia.id; + const isMainnetNetwork = targetNetwork.id === mainnet.id; return (
-
-
+
+
{nativeCurrencyPrice > 0 && (
@@ -34,27 +34,28 @@ export const Footer = () => {
)} + {isSepoliaNetwork && ( + <> + + + + )} {isLocalNetwork && ( <> - - - Block Explorer - + + )} + {isMainnetNetwork && ( + <> + )}
-
-
    -
    +
    +
      +
      · + {/*
      +

      + Built with by +

      + + + Q3 Labs + +

      at

      + + + BuidlGuidl + +
      + · */}
      { export const Header = () => { const [isDrawerOpen, setIsDrawerOpen] = useState(false); const burgerMenuRef = useRef(null); - useOutsideClick( burgerMenuRef, useCallback(() => setIsDrawerOpen(false), []), ); const { targetNetwork } = useTargetNetwork(); const isLocalNetwork = targetNetwork.id === devnet.id; + const { provider } = useProvider(); const { address, status } = useAccount(); const [isDeployed, setIsDeployed] = useState(true); diff --git a/packages/nextjs/components/MenuItem/MenuItem.tsx b/packages/nextjs/components/MenuItem/MenuItem.tsx deleted file mode 100644 index abc66fe50..000000000 --- a/packages/nextjs/components/MenuItem/MenuItem.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import Link from "next/link"; -import React from "react"; -import { HeaderMenuLink } from "~~/components/Header"; - -interface MenuItemProps { - link: HeaderMenuLink; - isActive: boolean; -} - -const MenuItem: React.FC = ({ link, isActive }) => { - //console.log({ isActive }, link.label); - return ( -
    • - - {link.icon} - {link.label} - -
    • - ); -}; - -export default MenuItem; diff --git a/packages/nextjs/components/SwitchTheme.tsx b/packages/nextjs/components/SwitchTheme.tsx index 1e080ea36..ff2c993c4 100644 --- a/packages/nextjs/components/SwitchTheme.tsx +++ b/packages/nextjs/components/SwitchTheme.tsx @@ -28,6 +28,13 @@ export const SwitchTheme = ({ className }: { className?: string }) => {
      + {/* */} {
      @@ -52,33 +68,49 @@ export const Balance = ({ address, className = "", usdMode }: BalanceProps) => { ); } - //const formattedBalance = balance ? Number(balance.formatted) : 0; + // Calculate the total balance in USD + const ethBalanceInUsd = parseFloat(formatted) * price; + const strkBalanceInUsd = parseFloat(strkFormatted) * strkPrice; + const totalBalanceInUsd = ethBalanceInUsd + strkBalanceInUsd; return ( - + <> + + ); }; diff --git a/packages/nextjs/components/scaffold-stark/BlockExplorer.tsx b/packages/nextjs/components/scaffold-stark/BlockExplorer.tsx new file mode 100644 index 000000000..68ca23c88 --- /dev/null +++ b/packages/nextjs/components/scaffold-stark/BlockExplorer.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { Address as AddressType, mainnet } from "@starknet-react/chains"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { useNetwork } from "@starknet-react/core"; +import Image from "next/image"; + +export const BlockExplorer = () => { + const { chain: ConnectedChain } = useNetwork(); + + const blockExplorers = [ + { + name: "Starkscan", + img: "/sn-symbol-gradient.png", + link: "https://starkscan.co/", + }, + { + name: "Voyager", + img: "/voyager-icon.svg", + link: "https://voyager.online/", + }, + { + name: "Stark Compass", + img: "/starkcompass-icon.svg", + link: "https://starkcompass.com/", + }, + ]; + + // Render only on mainnet chain + if (ConnectedChain?.id !== mainnet.id) { + return null; + } + + return ( +
      + ); +}; diff --git a/packages/nextjs/components/scaffold-stark/BlockExplorerSepolia.tsx b/packages/nextjs/components/scaffold-stark/BlockExplorerSepolia.tsx new file mode 100644 index 000000000..bad4d1019 --- /dev/null +++ b/packages/nextjs/components/scaffold-stark/BlockExplorerSepolia.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { Address as AddressType, sepolia } from "@starknet-react/chains"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { useNetwork } from "@starknet-react/core"; +import Image from "next/image"; + +export const BlockExplorerSepolia = () => { + const { chain: ConnectedChain } = useNetwork(); + + const sepoliaBlockExplorers = [ + { + name: "Starkscan", + img: "/sn-symbol-gradient.png", + link: "https://sepolia.starkscan.co/", + }, + { + name: "Voyager", + img: "/voyager-icon.svg", + link: "https://sepolia.voyager.online/", + }, + { + name: "Stark Compass", + img: "/starkcompass-icon.svg", + link: "https://starkcompass.com/sepolia/", + }, + ]; + + // Render only on sepolia chain + if (ConnectedChain?.id !== sepolia.id) { + return null; + } + + return ( +
      + + + +
      + ); +}; diff --git a/packages/nextjs/components/scaffold-stark/ClassHash.tsx b/packages/nextjs/components/scaffold-stark/ClassHash.tsx new file mode 100644 index 000000000..32b3e501d --- /dev/null +++ b/packages/nextjs/components/scaffold-stark/ClassHash.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { CopyToClipboard } from "react-copy-to-clipboard"; +import { Address as AddressType } from "@starknet-react/chains"; +import { devnet } from "@starknet-react/chains"; +import { + CheckCircleIcon, + DocumentDuplicateIcon, +} from "@heroicons/react/24/outline"; +import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork"; +import { getBlockExplorerClasshashLink } from "~~/utils/scaffold-stark"; + +type ClasshashProps = { + classHash: AddressType; + format?: "short" | "long"; + size?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl"; +}; + +/** + * Displays a Classhash and option to copy classHash. + */ +export const ClassHash = ({ + classHash, + format, + size = "xs", +}: ClasshashProps) => { + const [addressCopied, setAddressCopied] = useState(false); + const { targetNetwork } = useTargetNetwork(); + + const blockExplorerAddressLink = getBlockExplorerClasshashLink( + targetNetwork, + classHash, + ); + + let displayClasshash = classHash?.slice(0, 6) + "..." + classHash?.slice(-4); + + if (format === "long") { + displayClasshash = classHash; + } + + return ( +
      +
      + class hash: +
      + {targetNetwork.network === devnet.network ? ( + + {displayClasshash} + + ) : ( + + {displayClasshash} + + )} + {addressCopied ? ( +
      + ); +}; diff --git a/packages/nextjs/components/scaffold-stark/CustomConnectButton/AddressInfoDropdown.tsx b/packages/nextjs/components/scaffold-stark/CustomConnectButton/AddressInfoDropdown.tsx index ca122245c..7cf496903 100644 --- a/packages/nextjs/components/scaffold-stark/CustomConnectButton/AddressInfoDropdown.tsx +++ b/packages/nextjs/components/scaffold-stark/CustomConnectButton/AddressInfoDropdown.tsx @@ -188,7 +188,7 @@ export const AddressInfoDropdown = ({ <>
      -
      +

      diff --git a/packages/nextjs/components/scaffold-stark/Faucet.tsx b/packages/nextjs/components/scaffold-stark/Faucet.tsx index 05a3ff484..0abf9060b 100644 --- a/packages/nextjs/components/scaffold-stark/Faucet.tsx +++ b/packages/nextjs/components/scaffold-stark/Faucet.tsx @@ -41,8 +41,7 @@ export const Faucet = () => { useEffect(() => { const checkChain = async () => { try { - const providerInfo = await publicClient.getBlockWithTxHashes(); - console.log(providerInfo); + const providerInfo = await publicClient.getBlock(); } catch (error) { console.error("⚡️ ~ file: Faucet.tsx:checkChain ~ error", error); notification.error( @@ -83,16 +82,16 @@ export const Faucet = () => { return; } - try { - setLoading(true); - await mintEth(inputAddress, sendValue); - setLoading(false); - setInputAddress(undefined); - setSendValue(""); - } catch (error) { - console.error("⚡️ ~ file: Faucet.tsx:sendETH ~ error", error); + const res = await mintEth(inputAddress, sendValue); + if (!res.new_balance) { setLoading(false); + notification.error(`${res}`); + return; } + setLoading(false); + setInputAddress(undefined); + setSendValue(""); + notification.success("ETH sent successfully!"); }; // Render only on local chain @@ -121,17 +120,7 @@ export const Faucet = () => { > ✕ -
      -
      -
      - From: -
      -
      -
      - Available: - -
      -
      +
      { + const { chain: ConnectedChain } = useNetwork(); + const { targetNetwork } = useTargetNetwork(); + + const sepoliaFaucets = [ + { + name: "Starknet Foundation", + img: "/sn-symbol-gradient.png", + link: "https://starknet-faucet.vercel.app/", + }, + { + name: "Alchemy", + img: "/logo_alchemy.png", + link: "https://www.alchemy.com/faucets/starknet-sepolia", + }, + { + name: "Blast", + img: "/blast-icon-color.svg", + link: "https://blastapi.io/faucets/starknet-sepolia-eth", + }, + ]; + + const publicNodeUrl = targetNetwork.rpcUrls.public.http[0]; + + // Use useMemo to memoize the publicClient object + const publicClient = useMemo(() => { + return new RpcProvider({ + nodeUrl: publicNodeUrl, + }); + }, [publicNodeUrl]); + + useEffect(() => { + const checkChain = async () => { + try { + const providerInfo = await publicClient.getBlock(); + } catch (error) { + console.error("⚡️ ~ file: Faucet.tsx:checkChain ~ error", error); + notification.error( + <> +

      + Cannot connect to local provider +

      +

      + - Did you forget to run{" "} + + yarn chain + {" "} + ? +

      +

      + - Or you can change{" "} + + targetNetwork + {" "} + in{" "} + + scaffold.config.ts + +

      + , + { + duration: 5000, + }, + ); + } + }; + checkChain().then(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Render only on sepolia chain + if (ConnectedChain?.id !== sepolia.id) { + return null; + } + + return ( +
      + + + +
      + ); +}; diff --git a/packages/nextjs/contracts/predeployedContracts.ts b/packages/nextjs/contracts/predeployedContracts.ts index dd925ba51..4c3b211e3 100644 --- a/packages/nextjs/contracts/predeployedContracts.ts +++ b/packages/nextjs/contracts/predeployedContracts.ts @@ -5,6 +5,9 @@ const universalEthAddress = "0x49D36570D4E46F48E99674BD3FCC84644DDD6B96F7C741B1562B82F9E004DC7"; +const universalStrkAddress = + "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"; + const preDeployedContracts = { devnet: { Eth: { @@ -212,6 +215,216 @@ const preDeployedContracts = { ], }, ], + classHash: + "0x046ded64ae2dead6448e247234bab192a9c483644395b66f2155f2614e5804b0", + }, + Strk: { + address: universalStrkAddress, + abi: [ + { + type: "impl", + name: "ERC20Impl", + interface_name: "openzeppelin::token::erc20::interface::IERC20", + }, + { + name: "openzeppelin::token::erc20::interface::IERC20", + type: "interface", + items: [ + { + name: "name", + type: "function", + inputs: [], + outputs: [ + { + type: "core::felt252", + }, + ], + state_mutability: "view", + }, + { + name: "symbol", + type: "function", + inputs: [], + outputs: [ + { + type: "core::felt252", + }, + ], + state_mutability: "view", + }, + { + name: "decimals", + type: "function", + inputs: [], + outputs: [ + { + type: "core::integer::u8", + }, + ], + state_mutability: "view", + }, + { + name: "allowance", + type: "function", + inputs: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "spender", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + name: "transfer", + type: "function", + inputs: [ + { + name: "recipient", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "amount", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "external", + }, + { + name: "approve", + type: "function", + inputs: [ + { + name: "spender", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "amount", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "external", + }, + ], + }, + { + name: "ERC20CamelOnlyImpl", + type: "impl", + interface_name: + "openzeppelin::token::erc20::interface::IERC20CamelOnly", + }, + { + type: "interface", + name: "openzeppelin::token::erc20::interface::IERC20CamelOnly", + items: [ + { + name: "totalSupply", + type: "function", + inputs: [], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + name: "balanceOf", + type: "function", + inputs: [ + { + name: "account", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + name: "transferFrom", + type: "function", + inputs: [ + { + name: "sender", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "recipient", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "amount", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "external", + }, + ], + }, + { + kind: "struct", + name: "openzeppelin::token::erc20_v070::erc20::ERC20::Transfer", + type: "event", + members: [ + { + kind: "data", + name: "from", + type: "core::starknet::contract_address::ContractAddress", + }, + { + kind: "data", + name: "to", + type: "core::starknet::contract_address::ContractAddress", + }, + { + kind: "data", + name: "value", + type: "core::integer::u256", + }, + ], + }, + { + kind: "enum", + name: "openzeppelin::token::erc20_v070::erc20::ERC20::Event", + type: "event", + variants: [ + { + kind: "nested", + name: "Transfer", + type: "openzeppelin::token::erc20_v070::erc20::ERC20::Transfer", + }, + ], + }, + ], + classHash: + "0x046ded64ae2dead6448e247234bab192a9c483644395b66f2155f2614e5804b0", }, }, sepolia: { @@ -471,6 +684,267 @@ const preDeployedContracts = { ], }, ], + classHash: + "0x07f3777c99f3700505ea966676aac4a0d692c2a9f5e667f4c606b51ca1dd3420", + }, + Strk: { + address: universalStrkAddress, + abi: [ + { + type: "impl", + name: "ERC20Impl", + interface_name: "openzeppelin::token::erc20::interface::IERC20", + }, + { + name: "openzeppelin::token::erc20::interface::IERC20", + type: "interface", + items: [ + { + name: "name", + type: "function", + inputs: [], + outputs: [ + { + type: "core::felt252", + }, + ], + state_mutability: "view", + }, + { + name: "symbol", + type: "function", + inputs: [], + outputs: [ + { + type: "core::felt252", + }, + ], + state_mutability: "view", + }, + { + name: "decimals", + type: "function", + inputs: [], + outputs: [ + { + type: "core::integer::u8", + }, + ], + state_mutability: "view", + }, + { + name: "total_supply", + type: "function", + inputs: [], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + name: "balance_of", + type: "function", + inputs: [ + { + name: "account", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + name: "allowance", + type: "function", + inputs: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "spender", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + name: "transfer", + type: "function", + inputs: [ + { + name: "recipient", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "amount", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "external", + }, + { + name: "transfer_from", + type: "function", + inputs: [ + { + name: "sender", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "recipient", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "amount", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "external", + }, + { + name: "approve", + type: "function", + inputs: [ + { + name: "spender", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "amount", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "external", + }, + ], + }, + { + name: "ERC20CamelOnlyImpl", + type: "impl", + interface_name: + "openzeppelin::token::erc20::interface::IERC20CamelOnly", + }, + { + type: "interface", + name: "openzeppelin::token::erc20::interface::IERC20CamelOnly", + items: [ + { + name: "totalSupply", + type: "function", + inputs: [], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + name: "balanceOf", + type: "function", + inputs: [ + { + name: "account", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + name: "transferFrom", + type: "function", + inputs: [ + { + name: "sender", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "recipient", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "amount", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "external", + }, + ], + }, + { + kind: "struct", + name: "openzeppelin::token::erc20_v070::erc20::ERC20::Transfer", + type: "event", + members: [ + { + kind: "data", + name: "from", + type: "core::starknet::contract_address::ContractAddress", + }, + { + kind: "data", + name: "to", + type: "core::starknet::contract_address::ContractAddress", + }, + { + kind: "data", + name: "value", + type: "core::integer::u256", + }, + ], + }, + { + kind: "enum", + name: "openzeppelin::token::erc20_v070::erc20::ERC20::Event", + type: "event", + variants: [ + { + kind: "nested", + name: "Transfer", + type: "openzeppelin::token::erc20_v070::erc20::ERC20::Transfer", + }, + ], + }, + ], + classHash: + "0x04ad3c1dc8413453db314497945b6903e1c766495a1e60492d44da9c2a986e4b", }, }, } as const; diff --git a/packages/nextjs/hooks/scaffold-stark/useNativeCurrencyPrice.ts b/packages/nextjs/hooks/scaffold-stark/useNativeCurrencyPrice.ts index 1562dff9c..4a9fd14f3 100644 --- a/packages/nextjs/hooks/scaffold-stark/useNativeCurrencyPrice.ts +++ b/packages/nextjs/hooks/scaffold-stark/useNativeCurrencyPrice.ts @@ -13,23 +13,33 @@ export const useNativeCurrencyPrice = () => { const nativeCurrencyPrice = useGlobalState( (state) => state.nativeCurrencyPrice, ); + const strkCurrencyPrice = useGlobalState((state) => state.strkCurrencyPrice); const setNativeCurrencyPrice = useGlobalState( (state) => state.setNativeCurrencyPrice, ); - // Get the price of ETH from Coingecko on mount + const setStrkCurrencyPrice = useGlobalState( + (state) => state.setStrkCurrencyPrice, + ); + // Get the price of ETH & STRK from Coingecko on mount useEffect(() => { (async () => { if (nativeCurrencyPrice == 0) { - const price = await fetchPriceFromCoingecko(targetNetwork); + const price = await fetchPriceFromCoingecko("ETH"); setNativeCurrencyPrice(price); } + if (strkCurrencyPrice == 0) { + const strkPrice = await fetchPriceFromCoingecko("STRK"); + setStrkCurrencyPrice(strkPrice); + } })(); }, [targetNetwork]); - // Get the price of ETH from Coingecko at a given interval + // Get the price of ETH & STRK from Coingecko at a given interval useInterval(async () => { - const price = await fetchPriceFromCoingecko(targetNetwork); + const price = await fetchPriceFromCoingecko("ETH"); setNativeCurrencyPrice(price); + const strkPrice = await fetchPriceFromCoingecko("STRK"); + setStrkCurrencyPrice(strkPrice); }, scaffoldConfig.pollingInterval); //return nativeCurrencyPrice; diff --git a/packages/nextjs/hooks/scaffold-stark/useScaffoldStrkBalance.ts b/packages/nextjs/hooks/scaffold-stark/useScaffoldStrkBalance.ts new file mode 100644 index 000000000..ac80b742c --- /dev/null +++ b/packages/nextjs/hooks/scaffold-stark/useScaffoldStrkBalance.ts @@ -0,0 +1,34 @@ +import { Address } from "@starknet-react/chains"; +import { useDeployedContractInfo } from "./useDeployedContractInfo"; +import { useContractRead } from "@starknet-react/core"; +import { BlockNumber } from "starknet"; +import { Abi } from "abi-wan-kanabi"; +import { formatUnits } from "ethers"; + +type UseScaffoldStrkBalanceProps = { + address?: Address | string; +}; + +const useScaffoldStrkBalance = ({ address }: UseScaffoldStrkBalanceProps) => { + const { data: deployedContract } = useDeployedContractInfo("Strk"); + + const { data, ...props } = useContractRead({ + functionName: "balanceOf", + address: deployedContract?.address, + abi: deployedContract?.abi as Abi as any[], + watch: true, + enabled: true, + args: address ? [address] : [], + blockIdentifier: "pending" as BlockNumber, + }); + + return { + value: data as unknown as bigint, + decimals: 18, + symbol: "STRK", + formatted: data ? formatUnits(data as unknown as bigint) : "0", + ...props, + }; +}; + +export default useScaffoldStrkBalance; diff --git a/packages/nextjs/public/blast-icon-color.svg b/packages/nextjs/public/blast-icon-color.svg new file mode 100644 index 000000000..6de463649 --- /dev/null +++ b/packages/nextjs/public/blast-icon-color.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/nextjs/public/debug-image.png b/packages/nextjs/public/debug-image.png new file mode 100644 index 000000000..e6795e8aa Binary files /dev/null and b/packages/nextjs/public/debug-image.png differ diff --git a/packages/nextjs/public/logo_alchemy.png b/packages/nextjs/public/logo_alchemy.png new file mode 100644 index 000000000..9c4be1670 Binary files /dev/null and b/packages/nextjs/public/logo_alchemy.png differ diff --git a/packages/nextjs/public/sn-symbol-gradient.png b/packages/nextjs/public/sn-symbol-gradient.png new file mode 100644 index 000000000..c7856ba8e Binary files /dev/null and b/packages/nextjs/public/sn-symbol-gradient.png differ diff --git a/packages/nextjs/public/starkcompass-icon.svg b/packages/nextjs/public/starkcompass-icon.svg new file mode 100644 index 000000000..c6eee44b1 --- /dev/null +++ b/packages/nextjs/public/starkcompass-icon.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/packages/nextjs/public/voyager-icon.svg b/packages/nextjs/public/voyager-icon.svg new file mode 100644 index 000000000..6d4a76754 --- /dev/null +++ b/packages/nextjs/public/voyager-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nextjs/services/store/store.ts b/packages/nextjs/services/store/store.ts index 691b7e579..1137f3ea7 100644 --- a/packages/nextjs/services/store/store.ts +++ b/packages/nextjs/services/store/store.ts @@ -13,15 +13,20 @@ import { ChainWithAttributes } from "~~/utils/scaffold-stark"; type GlobalState = { nativeCurrencyPrice: number; + strkCurrencyPrice: number; setNativeCurrencyPrice: (newNativeCurrencyPriceState: number) => void; + setStrkCurrencyPrice: (newNativeCurrencyPriceState: number) => void; targetNetwork: ChainWithAttributes; setTargetNetwork: (newTargetNetwork: ChainWithAttributes) => void; }; export const useGlobalState = create((set) => ({ nativeCurrencyPrice: 0, + strkCurrencyPrice: 0, setNativeCurrencyPrice: (newValue: number): void => set(() => ({ nativeCurrencyPrice: newValue })), + setStrkCurrencyPrice: (newValue: number): void => + set(() => ({ strkCurrencyPrice: newValue })), targetNetwork: scaffoldConfig.targetNetworks[0], setTargetNetwork: (newTargetNetwork: ChainWithAttributes) => set(() => ({ targetNetwork: newTargetNetwork })), diff --git a/packages/nextjs/services/web3/faucet.ts b/packages/nextjs/services/web3/faucet.ts index cd61e2115..a4e4f620b 100644 --- a/packages/nextjs/services/web3/faucet.ts +++ b/packages/nextjs/services/web3/faucet.ts @@ -1,15 +1,25 @@ import { Address } from "@starknet-react/chains"; export async function mintEth(inputAddress: Address, eth: string) { - await fetch("http://0.0.0.0:5050/mint", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - address: inputAddress, - amount: parseFloat(eth) * 10 ** 18, - unit: "WEI", - }), - }); + try { + const response = await fetch("http://0.0.0.0:5050/mint", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + address: inputAddress, + amount: parseFloat(eth) * 10 ** 18, + unit: "WEI", + }), + }); + if (!response.ok) { + throw new Error(`${response.statusText}`); + } + const data = await response.json(); + return data; + } catch (error) { + console.error("There was a problem with the operation", error); + return error; + } } diff --git a/packages/nextjs/utils/scaffold-stark/contract.ts b/packages/nextjs/utils/scaffold-stark/contract.ts index ac9df3cc2..4f4d08908 100644 --- a/packages/nextjs/utils/scaffold-stark/contract.ts +++ b/packages/nextjs/utils/scaffold-stark/contract.ts @@ -63,7 +63,9 @@ export enum ContractCodeStatus { export type GenericContract = { address: Address; abi: Abi; + classHash: String; }; + export type GenericContractsDeclaration = { [network: string]: { [contractName: string]: GenericContract; diff --git a/packages/nextjs/utils/scaffold-stark/fetchPriceFromCoingecko.ts b/packages/nextjs/utils/scaffold-stark/fetchPriceFromCoingecko.ts index f4b5e345d..801258b6e 100644 --- a/packages/nextjs/utils/scaffold-stark/fetchPriceFromCoingecko.ts +++ b/packages/nextjs/utils/scaffold-stark/fetchPriceFromCoingecko.ts @@ -4,15 +4,10 @@ import { ChainWithAttributes } from "~~/utils/scaffold-stark"; const priceCache: Record = {}; export const fetchPriceFromCoingecko = async ( - targetNetwork: ChainWithAttributes, + symbol: string, retryCount = 3, ): Promise => { - const { symbol } = targetNetwork.nativeCurrency; - if ( - symbol !== "ETH" && - symbol !== "SEP" && - !targetNetwork.nativeCurrencyTokenAddress - ) { + if (symbol !== "ETH" && symbol !== "SEP" && symbol !== "STRK") { return 0; } @@ -32,11 +27,15 @@ const updatePriceCache = async ( let attempt = 0; while (attempt < retries) { try { - const response = await fetch( - `https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd`, - ); + 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`; + } + const response = await fetch(apiUrl); const data = await response.json(); - const price = data.ethereum.usd; + const price = symbol === "ETH" ? data.ethereum.usd : data.starknet.usd; priceCache[symbol] = price; console.log(`Price updated for ${symbol}: ${price}`); return price; diff --git a/packages/nextjs/utils/scaffold-stark/networks.ts b/packages/nextjs/utils/scaffold-stark/networks.ts index 4601bf92a..4ff4a9905 100644 --- a/packages/nextjs/utils/scaffold-stark/networks.ts +++ b/packages/nextjs/utils/scaffold-stark/networks.ts @@ -66,6 +66,26 @@ export function getBlockExplorerAddressLink( return `${blockExplorerBaseURL}/contract/${address}`; } +/** + * Gives the block explorer URL for a given classhash. + * Defaults to Etherscan if no (wagmi) block explorer is configured for the network. + */ +export function getBlockExplorerClasshashLink( + network: chains.Chain, + address: string, +) { + const blockExplorerBaseURL = network.explorers?.starkscan[0]; + if (network.network === chains.devnet.network) { + return `/blockexplorer/class/${address}`; + } + + if (!blockExplorerBaseURL) { + return `https://starkscan.co/class/${address}`; + } + + return `${blockExplorerBaseURL}/class/${address}`; +} + export function getBlockExplorerLink(network: chains.Chain) { switch (network) { case chains.mainnet: diff --git a/packages/snfoundry/contracts/src/YourContract.cairo b/packages/snfoundry/contracts/src/YourContract.cairo new file mode 100644 index 000000000..21e185095 --- /dev/null +++ b/packages/snfoundry/contracts/src/YourContract.cairo @@ -0,0 +1,103 @@ +#[starknet::interface] +pub trait IYourContract { + fn gretting(self: @TContractState) -> ByteArray; + fn set_gretting(ref self: TContractState, new_greeting: ByteArray, amount_eth: u256); + fn withdraw(ref self: TContractState); + fn premium(self: @TContractState) -> bool; +} + +#[starknet::contract] +mod YourContract { + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; + use starknet::{ContractAddress, contract_address_const}; + use starknet::{get_caller_address, get_contract_address}; + use super::{IYourContract}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + const ETH_CONTRACT_ADDRESS: felt252 = + 0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + GreetingChanged: GreetingChanged + } + + #[derive(Drop, starknet::Event)] + struct GreetingChanged { + #[key] + greeting_setter: ContractAddress, + #[key] + new_greeting: ByteArray, + premium: bool, + value: u256, + } + + #[storage] + struct Storage { + eth_token: IERC20CamelDispatcher, + greeting: ByteArray, + premium: bool, + total_counter: u256, + user_gretting_counter: LegacyMap, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + let eth_contract_address = contract_address_const::(); + self.eth_token.write(IERC20CamelDispatcher { contract_address: eth_contract_address }); + self.greeting.write("Building Unstoppable Apps!!!"); + self.ownable.initializer(owner); + } + + #[abi(embed_v0)] + impl YourContractImpl of IYourContract { + fn gretting(self: @ContractState) -> ByteArray { + self.greeting.read() + } + fn set_gretting(ref self: ContractState, new_greeting: ByteArray, amount_eth: u256) { + self.greeting.write(new_greeting); + self.total_counter.write(self.total_counter.read() + 1); + let user_counter = self.user_gretting_counter.read(get_caller_address()); + self.user_gretting_counter.write(get_caller_address(), user_counter + 1); + + if amount_eth > 0 { + // In `Debug Contract` or UI implementation call `approve` on ETH contract before invoke fn set_gretting() + self + .eth_token + .read() + .transferFrom(get_caller_address(), get_contract_address(), amount_eth); + self.premium.write(true); + } else { + self.premium.write(false); + } + self + .emit( + GreetingChanged { + greeting_setter: get_caller_address(), + new_greeting: self.greeting.read(), + premium: true, + value: 100 + } + ); + } + fn withdraw(ref self: ContractState) { + self.ownable.assert_only_owner(); + let balance = self.eth_token.read().balanceOf(get_contract_address()); + self.eth_token.read().transfer(self.ownable.owner(), balance); + } + fn premium(self: @ContractState) -> bool { + self.premium.read() + } + } +} diff --git a/packages/snfoundry/contracts/src/lib.cairo b/packages/snfoundry/contracts/src/lib.cairo index 853bd00aa..568f9f72a 100644 --- a/packages/snfoundry/contracts/src/lib.cairo +++ b/packages/snfoundry/contracts/src/lib.cairo @@ -1,3 +1,4 @@ +mod YourContract; #[cfg(test)] mod test { mod TestContract; diff --git a/packages/snfoundry/package.json b/packages/snfoundry/package.json index 17456ef37..06aa5cc1a 100644 --- a/packages/snfoundry/package.json +++ b/packages/snfoundry/package.json @@ -4,6 +4,7 @@ "scripts": { "chain": "starknet-devnet --seed 0 --account-class cairo1", "deploy": "ts-node scripts-ts/helpers/deploy-wrapper.ts", + "deploy:reset": "ts-node scripts-ts/helpers/deploy-wrapper.ts --reset", "test": "cd contracts && snforge test", "test-eslint": "node eslint-contract-name/eslint-plugin-contract-names.test.js", "compile": "cd contracts && scarb build", diff --git a/packages/snfoundry/scripts-ts/deploy-contract.ts b/packages/snfoundry/scripts-ts/deploy-contract.ts index a332ee8a3..dd9ed7c67 100644 --- a/packages/snfoundry/scripts-ts/deploy-contract.ts +++ b/packages/snfoundry/scripts-ts/deploy-contract.ts @@ -3,40 +3,63 @@ import path from "path"; import { networks } from "./helpers/networks"; import yargs from "yargs"; import { - BlockIdentifier, CallData, - hash, stark, RawArgs, - constants, - ec, - validateAndParseAddress, transaction, -} from "starknet"; -import { Network } from "./types"; -import { - LegacyContractClass, - CompiledSierra, extractContractHashes, + DeclareContractPayload, + UniversalDetails, } from "starknet"; +import { DeployContractParams, Network } from "./types"; +import { green, red, yellow } from "./helpers/colorize-log"; -const argv = yargs(process.argv.slice(2)).argv; -const networkName: string = argv["network"]; +interface Arguments { + network: string; + reset: boolean; + [x: string]: unknown; + _: (string | number)[]; + $0: string; +} -let deployments = {}; +const argv = yargs(process.argv.slice(2)) + .option("network", { + type: "string", + description: "Specify the network", + demandOption: true, + }) + .option("reset", { + alias: "r", + type: "boolean", + description: "Reset deployments", + default: false, + }) + .parseSync() as Arguments; + +const networkName: string = argv.network; +const resetDeployments: boolean = argv.reset; +let deployments = {}; let deployCalls = []; const { provider, deployer }: Network = networks[networkName]; -const declareIfNot_NotWait = async (payload: any) => { +const declareIfNot_NotWait = async ( + payload: DeclareContractPayload, + options?: UniversalDetails +) => { const declareContractPayload = extractContractHashes(payload); try { await provider.getClassByHash(declareContractPayload.classHash); } catch (error) { - let { transaction_hash } = await deployer.declare(payload); - if (networkName == "sepolia" || networkName == "mainnet") { - await provider.waitForTransaction(transaction_hash); + try { + const { transaction_hash } = await deployer.declare(payload, options); + if (networkName === "sepolia" || networkName === "mainnet") { + await provider.waitForTransaction(transaction_hash); + } + } catch (e) { + console.error(red("Error declaring contract:"), e); + throw e; } } return { @@ -49,38 +72,64 @@ const deployContract_NotWait = async (payload: { classHash: string; constructorCalldata: RawArgs; }) => { - let { calls, addresses } = transaction.buildUDCCall( - payload, - deployer.address - ); - deployCalls.push(...calls); - return { - contractAddress: addresses[0], - }; + try { + const { calls, addresses } = transaction.buildUDCCall( + payload, + deployer.address + ); + deployCalls.push(...calls); + return { + contractAddress: addresses[0], + }; + } catch (error) { + console.error(red("Error building UDC call:"), error); + throw error; + } }; +/** + * Deploy a contract using the specified parameters. + * + * @param {DeployContractParams} params - The parameters for deploying the contract. + * @param {string} params.contract - The name of the contract to deploy. + * @param {string} [params.contractName] - The name to export the contract as (optional). + * @param {RawArgs} [params.constructorArgs] - The constructor arguments for the contract (optional). + * @param {UniversalDetails} [params.options] - Additional deployment options (optional). + * + * @returns {Promise<{ classHash: string; address: string }>} The deployed contract's class hash and address. + * + * @example + * ///Example usage of deployContract function + * await deployContract({ + * contract: "YourContract", + * contractName: "YourContractExportName", + * constructorArgs: { owner: deployer.address }, + * options: { maxFee: BigInt(1000000000000) } + * }); + */ const deployContract = async ( - constructorArgs: RawArgs, - contractName: string, - exportContractName?: string, - options?: { - maxFee: bigint; - } + params: DeployContractParams ): Promise<{ classHash: string; address: string; }> => { + const { contract, constructorArgs, contractName, options } = params; + try { await deployer.getContractVersion(deployer.address); } catch (e) { if (e.toString().includes("Contract not found")) { - throw new Error( - `The wallet you're using to deploy the contract is not deployed in ${networkName} network` - ); + const errorMessage = `The wallet you're using to deploy the contract is not deployed in the ${networkName} network.`; + console.error(red(errorMessage)); + throw new Error(errorMessage); + } else { + console.error(red("Error getting contract version: "), e); + throw e; } } let compiledContractCasm; + let compiledContractSierra; try { compiledContractCasm = JSON.parse( @@ -88,7 +137,7 @@ const deployContract = async ( .readFileSync( path.resolve( __dirname, - `../contracts/target/dev/contracts_${contractName}.compiled_contract_class.json` + `../contracts/target/dev/contracts_${contract}.compiled_contract_class.json` ) ) .toString("ascii") @@ -102,12 +151,14 @@ const deployContract = async ( const match = error.message.match( /\/dev\/(.+?)\.compiled_contract_class/ ); - const contractName = match ? match[1].split("_").pop() : "Unknown"; + const missingContract = match ? match[1].split("_").pop() : "Unknown"; console.error( - `The contract "${contractName}" doesn't exist or is not compiled` + red( + `The contract "${missingContract}" doesn't exist or is not compiled` + ) ); } else { - console.error(error); + console.error(red("Error reading compiled contract class file: "), error); } return { classHash: "", @@ -115,27 +166,38 @@ const deployContract = async ( }; } - const compiledContractSierra = JSON.parse( - fs - .readFileSync( - path.resolve( - __dirname, - `../contracts/target/dev/contracts_${contractName}.contract_class.json` + try { + compiledContractSierra = JSON.parse( + fs + .readFileSync( + path.resolve( + __dirname, + `../contracts/target/dev/contracts_${contract}.contract_class.json` + ) ) - ) - .toString("ascii") - ); + .toString("ascii") + ); + } catch (error) { + console.error(red("Error reading contract class file: "), error); + return { + classHash: "", + address: "", + }; + } const contractCalldata = new CallData(compiledContractSierra.abi); const constructorCalldata = constructorArgs ? contractCalldata.compile("constructor", constructorArgs) : []; - console.log("Deploying Contract ", contractName); + console.log(yellow("Deploying Contract "), contract); - let { classHash } = await declareIfNot_NotWait({ - contract: compiledContractSierra, - casm: compiledContractCasm, - }); + let { classHash } = await declareIfNot_NotWait( + { + contract: compiledContractSierra, + casm: compiledContractCasm, + }, + options + ); let randomSalt = stark.randomAddress(); @@ -145,14 +207,14 @@ const deployContract = async ( constructorCalldata, }); - console.log("Contract Deployed at ", contractAddress); + console.log(green("Contract Deployed at "), contractAddress); - let finalContractName = exportContractName || contractName; + let finalContractName = contractName || contract; deployments[finalContractName] = { classHash: classHash, address: contractAddress, - contract: contractName, + contract: contract, }; return { @@ -161,26 +223,45 @@ const deployContract = async ( }; }; -const executeDeployCalls = async () => { +const executeDeployCalls = async (options?: UniversalDetails) => { + if (deployCalls.length < 1) { + throw new Error( + red( + "Aborted: No contract to deploy. Please prepare the contracts with `deployContract`" + ) + ); + } + try { - let { transaction_hash } = await deployer.execute(deployCalls); - console.log("Deploy Calls Executed at ", transaction_hash); - if (networkName == "sepolia" || networkName == "mainnet") { + let { transaction_hash } = await deployer.execute(deployCalls, options); + console.log(green("Deploy Calls Executed at "), transaction_hash); + if (networkName === "sepolia" || networkName === "mainnet") { await provider.waitForTransaction(transaction_hash); } } catch (error) { + console.error(red("Error executing deploy calls: "), error); // split the calls in half and try again recursively if (deployCalls.length > 1) { - let half = deployCalls.length / 2; + let half = Math.ceil(deployCalls.length / 2); let firstHalf = deployCalls.slice(0, half); - let secondHalf = deployCalls.slice(half, deployCalls.length); + let secondHalf = deployCalls.slice(half); deployCalls = firstHalf; - await executeDeployCalls(); + await executeDeployCalls(options); deployCalls = secondHalf; - await executeDeployCalls(); + await executeDeployCalls(options); } } }; +const loadExistingDeployments = () => { + const networkPath = path.resolve( + __dirname, + `../deployments/${networkName}_latest.json` + ); + if (fs.existsSync(networkPath)) { + return JSON.parse(fs.readFileSync(networkPath, "utf8")); + } + return {}; +}; const exportDeployments = () => { const networkPath = path.resolve( @@ -188,7 +269,11 @@ const exportDeployments = () => { `../deployments/${networkName}_latest.json` ); - if (fs.existsSync(networkPath)) { + let finalDeployments = resetDeployments + ? deployments + : { ...loadExistingDeployments(), ...deployments }; + + if (fs.existsSync(networkPath) && !resetDeployments) { const currentTimestamp = new Date().getTime(); fs.renameSync( networkPath, @@ -196,13 +281,15 @@ const exportDeployments = () => { ); } - fs.writeFileSync(networkPath, JSON.stringify(deployments, null, 2)); + fs.writeFileSync(networkPath, JSON.stringify(finalDeployments, null, 2)); }; export { deployContract, provider, deployer, + loadExistingDeployments, exportDeployments, executeDeployCalls, + resetDeployments, }; diff --git a/packages/snfoundry/scripts-ts/deploy.ts b/packages/snfoundry/scripts-ts/deploy.ts index 4489299b2..101aa7cfe 100644 --- a/packages/snfoundry/scripts-ts/deploy.ts +++ b/packages/snfoundry/scripts-ts/deploy.ts @@ -4,21 +4,57 @@ import { exportDeployments, deployer, } from "./deploy-contract"; +import { green } from "./helpers/colorize-log"; +/** + * Deploy a contract using the specified parameters. + * + * @example (deploy contract with contructorArgs) + * const deployScript = async (): Promise => { + * await deployContract( + * { + * contract: "YourContract", + * contractName: "YourContractExportName", + * constructorArgs: { + * owner: deployer.address, + * }, + * options: { + * maxFee: BigInt(1000000000000) + * } + * } + * ); + * }; + * + * @example (deploy contract without contructorArgs) + * const deployScript = async (): Promise => { + * await deployContract( + * { + * contract: "YourContract", + * contractName: "YourContractExportName", + * options: { + * maxFee: BigInt(1000000000000) + * } + * } + * ); + * }; + * + * + * @returns {Promise} + */ const deployScript = async (): Promise => { - await deployContract( - { - owner: deployer.address, // the deployer address is the owner of the contract + await deployContract({ + contract: "YourContract", + constructorArgs: { + owner: deployer.address, }, - "YourContract" - ); + }); }; deployScript() - .then(() => { - executeDeployCalls().then(() => { - exportDeployments(); - }); - console.log("All Setup Done"); + .then(async () => { + await executeDeployCalls(); + exportDeployments(); + + console.log(green("All Setup Done")); }) .catch(console.error); diff --git a/packages/snfoundry/scripts-ts/helpers/colorize-log.ts b/packages/snfoundry/scripts-ts/helpers/colorize-log.ts new file mode 100644 index 000000000..1e37792c2 --- /dev/null +++ b/packages/snfoundry/scripts-ts/helpers/colorize-log.ts @@ -0,0 +1,16 @@ +const colors = { + reset: "\x1b[0m", + red: "\x1b[31m", + green: "\x1b[32m", + yellow: "\x1b[33m", +}; + +const colorize = (color: string, message: string): string => { + return `${color}${message}${colors.reset}`; +}; + +export const red = (message: string): string => colorize(colors.red, message); +export const green = (message: string): string => + colorize(colors.green, message); +export const yellow = (message: string): string => + colorize(colors.yellow, message); diff --git a/packages/snfoundry/scripts-ts/helpers/deploy-wrapper.ts b/packages/snfoundry/scripts-ts/helpers/deploy-wrapper.ts index 59adce370..60ecb9450 100644 --- a/packages/snfoundry/scripts-ts/helpers/deploy-wrapper.ts +++ b/packages/snfoundry/scripts-ts/helpers/deploy-wrapper.ts @@ -6,22 +6,28 @@ interface CommandLineOptions { _: string[]; // Non-hyphenated arguments are usually under the `_` key $0: string; // The script name or path is under the `$0` key network?: string; // The --network option + reset?: boolean; } const argv = yargs(process.argv.slice(2)) .options({ network: { type: "string" }, + reset: { type: "boolean", default: false }, }) .parseSync() as CommandLineOptions; // Set the NETWORK environment variable based on the --network argument process.env.NETWORK = argv.network || "devnet"; +// Set the RESET environment variable based on the --reset flag + // Execute the deploy script execSync( - "cd contracts && scarb build && ts-node ../scripts-ts/deploy.ts --network " + + "cd contracts && scarb build && ts-node ../scripts-ts/deploy.ts" + + " --network " + process.env.NETWORK + + (argv.reset ? " --reset" : "") + " && ts-node ../scripts-ts/helpers/parse-deployments.ts" + - " && cd ../..", + " && cd ..", { stdio: "inherit" } ); diff --git a/packages/snfoundry/scripts-ts/helpers/parse-deployments.ts b/packages/snfoundry/scripts-ts/helpers/parse-deployments.ts index a64d12bdd..aa2490c50 100644 --- a/packages/snfoundry/scripts-ts/helpers/parse-deployments.ts +++ b/packages/snfoundry/scripts-ts/helpers/parse-deployments.ts @@ -14,11 +14,11 @@ const generatedContractComment = `/** const getContractDataFromDeployments = (): Record< string, - Record + Record > => { const allContractsData: Record< string, - Record + Record > = {}; files.forEach((file) => { @@ -49,6 +49,7 @@ const getContractDataFromDeployments = (): Record< [contractName]: { address: contractData.address, abi: abiContent.abi.filter((item) => item.type !== "l1_handler"), + classHash: contractData.classHash, }, }; } catch (e) {} diff --git a/packages/snfoundry/scripts-ts/types.ts b/packages/snfoundry/scripts-ts/types.ts index a5965a61b..eabc5c9f4 100644 --- a/packages/snfoundry/scripts-ts/types.ts +++ b/packages/snfoundry/scripts-ts/types.ts @@ -1,4 +1,4 @@ -import { Account, RpcProvider } from "starknet"; +import { Account, RawArgs, RpcProvider, UniversalDetails } from "starknet"; export type Networks = Record<"devnet" | "sepolia" | "mainnet", Network>; @@ -6,3 +6,10 @@ export type Network = { provider: RpcProvider; deployer: Account; }; + +export type DeployContractParams = { + contract: string; + contractName?: string; + constructorArgs?: RawArgs; + options?: UniversalDetails; +};