From c3ff555834e51dae3a1a613ae40104a9b5339fac Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 15 Apr 2024 12:33:09 -0500 Subject: [PATCH 1/3] fix: provider --- .../ScaffoldStarkAppWithProviders.tsx | 15 +- .../nextjs/contracts/deployedContracts.ts | 532 +++++++++++++++++- packages/nextjs/services/web3/provider.ts | 19 + 3 files changed, 551 insertions(+), 15 deletions(-) create mode 100644 packages/nextjs/services/web3/provider.ts diff --git a/packages/nextjs/components/ScaffoldStarkAppWithProviders.tsx b/packages/nextjs/components/ScaffoldStarkAppWithProviders.tsx index 4cee39ec..fa821d08 100644 --- a/packages/nextjs/components/ScaffoldStarkAppWithProviders.tsx +++ b/packages/nextjs/components/ScaffoldStarkAppWithProviders.tsx @@ -6,20 +6,17 @@ import { useTheme } from "next-themes"; import { Toaster } from "react-hot-toast"; import { StarknetConfig, - publicProvider, argent, braavos, useInjectedConnectors, starkscan, - jsonRpcProvider, - starknetChainId, } from "@starknet-react/core"; import { Header } from "~~/components/Header"; import { Footer } from "~~/components/Footer"; import { ProgressBar } from "~~/components/scaffold-stark/ProgressBar"; import { appChains } from "~~/services/web3/connectors"; import { BurnerConnector } from "~~/services/web3/stark-burner/BurnerConnector"; -import scaffoldConfig from "~~/scaffold.config"; +import provider from "~~/services/web3/provider"; const ScaffoldStarkApp = ({ children }: { children: React.ReactNode }) => { return ( @@ -42,16 +39,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/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/services/web3/provider.ts b/packages/nextjs/services/web3/provider.ts new file mode 100644 index 00000000..0c6f95b7 --- /dev/null +++ b/packages/nextjs/services/web3/provider.ts @@ -0,0 +1,19 @@ +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; \ No newline at end of file From 02b8c739d79c8cb89a793fcbbd5014b4cc246559 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 15 Apr 2024 12:39:52 -0500 Subject: [PATCH 2/3] fix: format --- packages/nextjs/services/web3/provider.ts | 25 ++++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/nextjs/services/web3/provider.ts b/packages/nextjs/services/web3/provider.ts index 0c6f95b7..e155024a 100644 --- a/packages/nextjs/services/web3/provider.ts +++ b/packages/nextjs/services/web3/provider.ts @@ -1,19 +1,24 @@ import scaffoldConfig from "~~/scaffold.config"; -import {jsonRpcProvider, publicProvider, starknetChainId} from "@starknet-react/core"; +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 -} + return networks.filter((it) => it.id == chains.devnet.id).length > 0; +}; const provider = - scaffoldConfig.rpcProviderUrl == "" || containsDevnet(scaffoldConfig.targetNetworks) + scaffoldConfig.rpcProviderUrl == "" || + containsDevnet(scaffoldConfig.targetNetworks) ? publicProvider() : jsonRpcProvider({ - rpc: () => ({ - nodeUrl: scaffoldConfig.rpcProviderUrl, - chainId: starknetChainId(scaffoldConfig.targetNetworks[0].id), - }), - }); + rpc: () => ({ + nodeUrl: scaffoldConfig.rpcProviderUrl, + chainId: starknetChainId(scaffoldConfig.targetNetworks[0].id), + }), + }); -export default provider; \ No newline at end of file +export default provider; From eb2de3a3fccc67933941f82f7c0610e7aabd821f Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 15 Apr 2024 15:03:52 -0500 Subject: [PATCH 3/3] feat: fetch eth price --- packages/nextjs/components/Footer.tsx | 19 +++++---- .../ScaffoldStarkAppWithProviders.tsx | 3 ++ .../components/scaffold-stark/Balance.tsx | 7 +++- .../scaffold-stark/useNativeCurrencyPrice.ts | 39 +++++++++++++++++++ packages/nextjs/scaffold.config.ts | 4 ++ .../scaffold-stark/fetchPriceFromCoingecko.ts | 27 +++++++++++++ packages/nextjs/utils/scaffold-stark/index.ts | 1 + .../nextjs/utils/scaffold-stark/networks.ts | 1 + 8 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 packages/nextjs/hooks/scaffold-stark/useNativeCurrencyPrice.ts create mode 100644 packages/nextjs/utils/scaffold-stark/fetchPriceFromCoingecko.ts 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 ( <>
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/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/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;