diff --git a/packages/nextjs/components/Footer.tsx b/packages/nextjs/components/Footer.tsx index 66feda99..b8eab224 100644 --- a/packages/nextjs/components/Footer.tsx +++ b/packages/nextjs/components/Footer.tsx @@ -8,6 +8,9 @@ import { import { HeartIcon } from "@heroicons/react/24/outline"; import { SwitchTheme } from "~~/components/SwitchTheme"; import { BuidlGuidlLogo } from "~~/components/assets/BuidlGuidlLogo"; +import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork"; +import { useGlobalState } from "~~/services/store/store"; +import { devnet } from "@starknet-react/chains"; // import { Faucet } from "~~/components/scaffold-eth"; // import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; // import { useGlobalState } from "~~/services/store/store"; @@ -16,18 +19,18 @@ import { BuidlGuidlLogo } from "~~/components/assets/BuidlGuidlLogo"; * Site footer */ export const Footer = () => { - // const nativeCurrencyPrice = useGlobalState( - // (state) => state.nativeCurrencyPrice - // ); - // const { targetNetwork } = useTargetNetwork(); - const isLocalNetwork = false; + const nativeCurrencyPrice = useGlobalState( + (state) => state.nativeCurrencyPrice, + ); + const { targetNetwork } = useTargetNetwork(); + const isLocalNetwork = targetNetwork.id === devnet.id; return (
- {/* {nativeCurrencyPrice > 0 && ( + {nativeCurrencyPrice > 0 && (
@@ -37,7 +40,7 @@ export const Footer = () => { )} {isLocalNetwork && ( <> - + {/**/} { Block Explorer - )} */} + )}
{ + useNativeCurrencyPrice(); + return ( <>
@@ -42,16 +42,6 @@ export const ScaffoldStarkAppWithProviders = ({ const isDarkMode = resolvedTheme === "dark"; const [mounted, setMounted] = useState(false); - const provider = - scaffoldConfig.rpcProviderUrl == "" - ? publicProvider() - : jsonRpcProvider({ - rpc: () => ({ - nodeUrl: scaffoldConfig.rpcProviderUrl, - chainId: starknetChainId(scaffoldConfig.targetNetworks[0].id), - }), - }); - useEffect(() => { setMounted(true); }, []); diff --git a/packages/nextjs/components/scaffold-stark/Balance.tsx b/packages/nextjs/components/scaffold-stark/Balance.tsx index f5fa9eef..cde2f5f4 100644 --- a/packages/nextjs/components/scaffold-stark/Balance.tsx +++ b/packages/nextjs/components/scaffold-stark/Balance.tsx @@ -70,7 +70,12 @@ export const Balance = ({ address, className = "", usdMode }: BalanceProps) => { {displayUsdMode ? ( <> $ - {(formattedBalance * price).toFixed(2)} + + {(formattedBalance * price).toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} + ) : ( <> diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index 25751cee..5052ea1d 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -3,6 +3,536 @@ * You should not edit it manually or your changes might be overwritten. */ -const deployedContracts = {} as const; +const deployedContracts = { + devnet: { + Challenge0: { + address: + "0x029de5578255560287ebdad126340555672f25ff9799e2e61e6c861fa2035cec", + abi: [ + { + type: "impl", + name: "WrappedIERC721MetadataImpl", + interface_name: + "openzeppelin::token::erc721::interface::IERC721Metadata", + }, + { + type: "struct", + name: "core::byte_array::ByteArray", + members: [ + { + name: "data", + type: "core::array::Array::", + }, + { + name: "pending_word", + type: "core::felt252", + }, + { + name: "pending_word_len", + type: "core::integer::u32", + }, + ], + }, + { + type: "struct", + name: "core::integer::u256", + members: [ + { + name: "low", + type: "core::integer::u128", + }, + { + name: "high", + type: "core::integer::u128", + }, + ], + }, + { + type: "interface", + name: "openzeppelin::token::erc721::interface::IERC721Metadata", + items: [ + { + type: "function", + name: "name", + inputs: [], + outputs: [ + { + type: "core::byte_array::ByteArray", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "symbol", + inputs: [], + outputs: [ + { + type: "core::byte_array::ByteArray", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "token_uri", + inputs: [ + { + name: "token_id", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::byte_array::ByteArray", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "Challenge0Impl", + interface_name: "contracts::challenge0::IChallenge0", + }, + { + type: "interface", + name: "contracts::challenge0::IChallenge0", + items: [ + { + type: "function", + name: "mint_item", + inputs: [ + { + name: "recipient", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "uri", + type: "core::byte_array::ByteArray", + }, + ], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "external", + }, + { + type: "function", + name: "token_id_counter", + inputs: [], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "ERC721Impl", + interface_name: "openzeppelin::token::erc721::interface::IERC721", + }, + { + type: "struct", + name: "core::array::Span::", + members: [ + { + name: "snapshot", + type: "@core::array::Array::", + }, + ], + }, + { + type: "enum", + name: "core::bool", + variants: [ + { + name: "False", + type: "()", + }, + { + name: "True", + type: "()", + }, + ], + }, + { + type: "interface", + name: "openzeppelin::token::erc721::interface::IERC721", + items: [ + { + type: "function", + name: "balance_of", + inputs: [ + { + name: "account", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "owner_of", + inputs: [ + { + name: "token_id", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::starknet::contract_address::ContractAddress", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "safe_transfer_from", + inputs: [ + { + name: "from", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "to", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "token_id", + type: "core::integer::u256", + }, + { + name: "data", + type: "core::array::Span::", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "transfer_from", + inputs: [ + { + name: "from", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "to", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "token_id", + type: "core::integer::u256", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "approve", + inputs: [ + { + name: "to", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "token_id", + type: "core::integer::u256", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "set_approval_for_all", + inputs: [ + { + name: "operator", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "approved", + type: "core::bool", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "get_approved", + inputs: [ + { + name: "token_id", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::starknet::contract_address::ContractAddress", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "is_approved_for_all", + inputs: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "operator", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "OwnableImpl", + interface_name: "openzeppelin::access::ownable::interface::IOwnable", + }, + { + type: "interface", + name: "openzeppelin::access::ownable::interface::IOwnable", + items: [ + { + type: "function", + name: "owner", + inputs: [], + outputs: [ + { + type: "core::starknet::contract_address::ContractAddress", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "transfer_ownership", + inputs: [ + { + name: "new_owner", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "renounce_ownership", + inputs: [], + outputs: [], + state_mutability: "external", + }, + ], + }, + { + type: "constructor", + name: "constructor", + inputs: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + }, + { + type: "event", + name: "openzeppelin::token::erc721::erc721::ERC721Component::Transfer", + kind: "struct", + members: [ + { + name: "from", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "to", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "token_id", + type: "core::integer::u256", + kind: "key", + }, + ], + }, + { + type: "event", + name: "openzeppelin::token::erc721::erc721::ERC721Component::Approval", + kind: "struct", + members: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "approved", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "token_id", + type: "core::integer::u256", + kind: "key", + }, + ], + }, + { + type: "event", + name: "openzeppelin::token::erc721::erc721::ERC721Component::ApprovalForAll", + kind: "struct", + members: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "operator", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "approved", + type: "core::bool", + kind: "data", + }, + ], + }, + { + type: "event", + name: "openzeppelin::token::erc721::erc721::ERC721Component::Event", + kind: "enum", + variants: [ + { + name: "Transfer", + type: "openzeppelin::token::erc721::erc721::ERC721Component::Transfer", + kind: "nested", + }, + { + name: "Approval", + type: "openzeppelin::token::erc721::erc721::ERC721Component::Approval", + kind: "nested", + }, + { + name: "ApprovalForAll", + type: "openzeppelin::token::erc721::erc721::ERC721Component::ApprovalForAll", + kind: "nested", + }, + ], + }, + { + type: "event", + name: "openzeppelin::introspection::src5::SRC5Component::Event", + kind: "enum", + variants: [], + }, + { + type: "event", + name: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferred", + kind: "struct", + members: [ + { + name: "previous_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "new_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + ], + }, + { + type: "event", + name: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferStarted", + kind: "struct", + members: [ + { + name: "previous_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "new_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + ], + }, + { + type: "event", + name: "openzeppelin::access::ownable::ownable::OwnableComponent::Event", + kind: "enum", + variants: [ + { + name: "OwnershipTransferred", + type: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferred", + kind: "nested", + }, + { + name: "OwnershipTransferStarted", + type: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferStarted", + kind: "nested", + }, + ], + }, + { + type: "event", + name: "contracts::challenge0::Challenge0::Event", + kind: "enum", + variants: [ + { + name: "ERC721Event", + type: "openzeppelin::token::erc721::erc721::ERC721Component::Event", + kind: "flat", + }, + { + name: "SRC5Event", + type: "openzeppelin::introspection::src5::SRC5Component::Event", + kind: "flat", + }, + { + name: "OwnableEvent", + type: "openzeppelin::access::ownable::ownable::OwnableComponent::Event", + kind: "flat", + }, + ], + }, + ], + }, + }, +} as const; export default deployedContracts; diff --git a/packages/nextjs/hooks/scaffold-stark/useNativeCurrencyPrice.ts b/packages/nextjs/hooks/scaffold-stark/useNativeCurrencyPrice.ts new file mode 100644 index 00000000..b117ba96 --- /dev/null +++ b/packages/nextjs/hooks/scaffold-stark/useNativeCurrencyPrice.ts @@ -0,0 +1,39 @@ +import { useEffect } from "react"; +import { useTargetNetwork } from "./useTargetNetwork"; +import { useInterval } from "usehooks-ts"; +import scaffoldConfig from "~~/scaffold.config"; +import { fetchPriceFromCoingecko } from "~~/utils/scaffold-stark"; +import { useGlobalState } from "~~/services/store/store"; + +/** + * Get the price of Native Currency based on Native Token/DAI trading pair from Uniswap SDK + */ +export const useNativeCurrencyPrice = () => { + const { targetNetwork } = useTargetNetwork(); + const nativeCurrencyPrice = useGlobalState( + (state) => state.nativeCurrencyPrice, + ); + const setNativeCurrencyPrice = useGlobalState( + (state) => state.setNativeCurrencyPrice, + ); + // Get the price of ETH from Coingecko on mount + useEffect(() => { + (async () => { + if (nativeCurrencyPrice == 0) { + const price = await fetchPriceFromCoingecko(targetNetwork); + setNativeCurrencyPrice(price); + } + })(); + }, [targetNetwork]); + + // Get the price of ETH from Coingecko at a given interval + useInterval( + async () => { + const price = await fetchPriceFromCoingecko(targetNetwork); + setNativeCurrencyPrice(price); + }, + scaffoldConfig.pollingInterval ? 4000 : scaffoldConfig.pollingInterval, + ); + + //return nativeCurrencyPrice; +}; diff --git a/packages/nextjs/scaffold.config.ts b/packages/nextjs/scaffold.config.ts index 9d010910..3daf09e4 100644 --- a/packages/nextjs/scaffold.config.ts +++ b/packages/nextjs/scaffold.config.ts @@ -2,6 +2,7 @@ import * as chains from "@starknet-react/chains"; export type ScaffoldConfig = { targetNetworks: readonly chains.Chain[]; + pollingInterval?: number | null; onlyLocalBurnerWallet: boolean; rpcProviderUrl: string; walletAutoConnect: boolean; @@ -12,6 +13,9 @@ const scaffoldConfig = { // Only show the Burner Wallet when running on devnet onlyLocalBurnerWallet: false, rpcProviderUrl: process.env.NEXT_PUBLIC_PROVIDER_URL || "", + // The interval at which your front-end polls the RPC servers for new data + // it has no effect if you only target the local network (default is 4000) + pollingInterval: null, /** * Auto connect: * 1. If the user was connected into a wallet before, on page reload reconnect automatically diff --git a/packages/nextjs/services/web3/provider.ts b/packages/nextjs/services/web3/provider.ts new file mode 100644 index 00000000..e155024a --- /dev/null +++ b/packages/nextjs/services/web3/provider.ts @@ -0,0 +1,24 @@ +import scaffoldConfig from "~~/scaffold.config"; +import { + jsonRpcProvider, + publicProvider, + starknetChainId, +} from "@starknet-react/core"; +import * as chains from "@starknet-react/chains"; + +const containsDevnet = (networks: readonly chains.Chain[]) => { + return networks.filter((it) => it.id == chains.devnet.id).length > 0; +}; + +const provider = + scaffoldConfig.rpcProviderUrl == "" || + containsDevnet(scaffoldConfig.targetNetworks) + ? publicProvider() + : jsonRpcProvider({ + rpc: () => ({ + nodeUrl: scaffoldConfig.rpcProviderUrl, + chainId: starknetChainId(scaffoldConfig.targetNetworks[0].id), + }), + }); + +export default provider; diff --git a/packages/nextjs/utils/scaffold-stark/fetchPriceFromCoingecko.ts b/packages/nextjs/utils/scaffold-stark/fetchPriceFromCoingecko.ts new file mode 100644 index 00000000..a0e42af7 --- /dev/null +++ b/packages/nextjs/utils/scaffold-stark/fetchPriceFromCoingecko.ts @@ -0,0 +1,27 @@ +import { ChainWithAttributes } from "~~/utils/scaffold-stark"; + +export const fetchPriceFromCoingecko = async ( + targetNetwork: ChainWithAttributes, +): Promise => { + if ( + targetNetwork.nativeCurrency.symbol !== "ETH" && + targetNetwork.nativeCurrency.symbol !== "SEP" && + !targetNetwork.nativeCurrencyTokenAddress + ) { + return 0; + } + + try { + const response = await fetch( + "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd", + ); + const data = await response.json(); + return data.ethereum.usd; + } catch (error) { + console.error( + `useNativeCurrencyPrice - Error fetching ${targetNetwork.nativeCurrency.symbol} price from Coingecko: `, + error, + ); + return 0; + } +}; diff --git a/packages/nextjs/utils/scaffold-stark/index.ts b/packages/nextjs/utils/scaffold-stark/index.ts index eaa4e9b4..1a102ac0 100644 --- a/packages/nextjs/utils/scaffold-stark/index.ts +++ b/packages/nextjs/utils/scaffold-stark/index.ts @@ -1,2 +1,3 @@ export * from "./networks"; export * from "./notification"; +export * from "./fetchPriceFromCoingecko"; diff --git a/packages/nextjs/utils/scaffold-stark/networks.ts b/packages/nextjs/utils/scaffold-stark/networks.ts index 2e5b71c6..4e44e005 100644 --- a/packages/nextjs/utils/scaffold-stark/networks.ts +++ b/packages/nextjs/utils/scaffold-stark/networks.ts @@ -4,6 +4,7 @@ import scaffoldConfig from "~~/scaffold.config"; type ChainAttributes = { // color | [lightThemeColor, darkThemeColor] color: string | [string, string]; + nativeCurrencyTokenAddress?: string; }; export type ChainWithAttributes = chains.Chain & Partial;