diff --git a/AVALANCHE-L1.md b/AVALANCHE-L1.md index 9471067..4be92c1 100644 --- a/AVALANCHE-L1.md +++ b/AVALANCHE-L1.md @@ -21,7 +21,9 @@ It'll take some time to load, go take a walk or something 5. After it boots up the blockchain, for some reason port `9650` doesn't set itself automatically to public, so you gotta click `Ports` to the right of `Terminal`, find port 9650, right click it, Port visibility, Public -> NOTE: After 30 minutes (or if you close the tab/browser) your codespaces goes to sleep. To restart your blockchain after your codespace was sleep, run `avalanche network start`. Remember to set **Port 9650** to public again. +> NOTE: After 30 minutes (or if you close the tab/browser) your codespaces goes to sleep. To prevent this from happening, run this command: `while true; do echo "Ah ah ah ah, staying alive!"; sleep 900; done &` to ping the codespace every 15 minutes. + +> NOTE: Remember [GitHub will provide users in the free plan](https://docs.github.com/es/billing/managing-billing-for-your-products/managing-billing-for-github-codespaces/about-billing-for-github-codespaces) (boo!) 120 core hours or 60 hours of run time on a 2 core codespace, plus 15 GB of storage each month. So remember to shut it down when you're not using it. > NOTE for Ava Labs: Maybe a **function to set Port 9650 to public** could be a temporary improvement to open it until it's opened automatically by default. diff --git a/README.md b/README.md index 3bcc033..d174dbd 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ yarn install 4. Start a local Avalanche L1: -It'd be ideal to run it with one command like `yarn subnet`, but so far, **you gotta follow this instructions**. +It'd be ideal to run it with one command like `yarn subnet` with a config file, but so far, **you gotta follow this instructions**. You'll start a local Avalanche L1 using [Ava Labs avalanche-starter-kit](https://github.com/ava-labs/avalanche-starter-kit). The network runs on your local machine and can be used for testing and development. diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx index c36b9ca..f9dac23 100644 --- a/packages/nextjs/components/Header.tsx +++ b/packages/nextjs/components/Header.tsx @@ -4,18 +4,15 @@ import React from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { SwitchTheme } from "./SwitchTheme"; -import { PunkBalance } from "./punk-society/PunkBalance"; +import { ConfigMenu } from "./punk-society/ConfigMenu"; import { PunkConnectButton } from "./punk-society/PunkConnectButton"; import { FaucetButton } from "./scaffold-eth"; -import { useAccount } from "wagmi"; import { BellIcon, EnvelopeIcon, HomeIcon, MagnifyingGlassIcon } from "@heroicons/react/24/solid"; /** * Site header */ export const Header = () => { - const { address: connectedAddress } = useAccount(); - const pathname = usePathname(); return ( @@ -37,7 +34,7 @@ export const Header = () => {
*/} -
+
-
- -
-
-
+
@@ -121,9 +114,10 @@ export const Header = () => { {/*
*/}
-
- -
+ +
+
+
diff --git a/packages/nextjs/components/punk-society/ConfigMenu/AddressInfoDropdown.tsx b/packages/nextjs/components/punk-society/ConfigMenu/AddressInfoDropdown.tsx new file mode 100644 index 0000000..66f12eb --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/AddressInfoDropdown.tsx @@ -0,0 +1,128 @@ +import { useRef, useState } from "react"; +import { NetworkOptions } from "./NetworkOptions"; +import { Address } from "viem"; +import { useDisconnect } from "wagmi"; +import { Cog6ToothIcon } from "@heroicons/react/20/solid"; +import { KeyIcon as KeyIconOutline } from "@heroicons/react/24/outline"; +import { + ArrowLeftOnRectangleIcon, + ArrowTopRightOnSquareIcon, + ArrowsRightLeftIcon, + QrCodeIcon, +} from "@heroicons/react/24/outline"; +import { KeyIcon as KeyIconSolid, LanguageIcon } from "@heroicons/react/24/solid"; +import { useOutsideClick } from "~~/hooks/scaffold-eth"; +import { getTargetNetworks } from "~~/utils/scaffold-eth"; + +const allowedNetworks = getTargetNetworks(); + +type AddressInfoDropdownProps = { + address: Address; + blockExplorerAddressLink: string | undefined; + displayName: string; + ensAvatar?: string; +}; + +export const AddressInfoDropdown = ({ blockExplorerAddressLink }: AddressInfoDropdownProps) => { + const [selectingNetwork, setSelectingNetwork] = useState(false); + + const { disconnect } = useDisconnect(); + + const dropdownRef = useRef(null); + const closeDropdown = () => { + setSelectingNetwork(false); + dropdownRef.current?.removeAttribute("open"); + }; + useOutsideClick(dropdownRef, closeDropdown); + + return ( + <> +
+ + + +
    +
+
+ + ); +}; diff --git a/packages/nextjs/components/punk-society/ConfigMenu/AddressQRCodeModal.tsx b/packages/nextjs/components/punk-society/ConfigMenu/AddressQRCodeModal.tsx new file mode 100644 index 0000000..d256142 --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/AddressQRCodeModal.tsx @@ -0,0 +1,50 @@ +import React, { useState } from "react"; +import { QRCodeSVG } from "qrcode.react"; +import { CopyToClipboard } from "react-copy-to-clipboard"; +import { Address as AddressType } from "viem"; +import { notification } from "~~/utils/scaffold-eth"; + +type AddressQRCodeModalProps = { + address: AddressType; + modalId: string; +}; + +export const AddressQRCodeModal = ({ address, modalId }: AddressQRCodeModalProps) => { + const [copied, setCopied] = useState(false); + + const handleCopy = () => { + setCopied(true); + notification.success("Address copied to clipboard"); + setTimeout(() => setCopied(false), 2000); // Reset copied state after 2 seconds + }; + + return ( + <> +
+ + +
+ + ); +}; diff --git a/packages/nextjs/components/punk-society/ConfigMenu/Balance.tsx b/packages/nextjs/components/punk-society/ConfigMenu/Balance.tsx new file mode 100644 index 0000000..948fa0a --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/Balance.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { Address, formatEther } from "viem"; +import { useDisplayUsdMode } from "~~/hooks/scaffold-eth/useDisplayUsdMode"; +import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; +import { useWatchBalance } from "~~/hooks/scaffold-eth/useWatchBalance"; +import { useGlobalState } from "~~/services/store/store"; + +type BalanceProps = { + address?: Address; + className?: string; + usdMode?: boolean; +}; + +/** + * Display (ETH & USD) balance of an ETH address. + */ +export const Balance = ({ address, className = "", usdMode }: BalanceProps) => { + const { targetNetwork } = useTargetNetwork(); + const nativeCurrencyPrice = useGlobalState(state => state.nativeCurrency.price); + const isNativeCurrencyPriceFetching = useGlobalState(state => state.nativeCurrency.isFetching); + + const { + data: balance, + isError, + isLoading, + } = useWatchBalance({ + address, + }); + + const { displayUsdMode, toggleDisplayUsdMode } = useDisplayUsdMode({ defaultUsdMode: usdMode }); + + if (!address || isLoading || balance === null || (isNativeCurrencyPriceFetching && nativeCurrencyPrice === 0)) { + return ( +
+
+
+
+
+
+ ); + } + + if (isError) { + return ( +
+
Error
+
+ ); + } + + const formattedBalance = balance ? Number(formatEther(balance.value)) : 0; + + return ( + + ); +}; diff --git a/packages/nextjs/components/punk-society/ConfigMenu/BridgeUSDCModal.tsx b/packages/nextjs/components/punk-society/ConfigMenu/BridgeUSDCModal.tsx new file mode 100644 index 0000000..83b054a --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/BridgeUSDCModal.tsx @@ -0,0 +1,47 @@ +import Image from "next/image"; + +type BridgeUSDCModalProps = { + modalId: string; +}; + +export const BridgeUSDCModal = ({ modalId }: BridgeUSDCModalProps) => { + const handleBridgeUSDCClick = () => { + window.open("https://x.com/LuloxDev", "_blank"); + }; + return ( + <> +
+ + +
+ + ); +}; diff --git a/packages/nextjs/components/punk-society/ConfigMenu/LoadPrivateKeyModal.tsx b/packages/nextjs/components/punk-society/ConfigMenu/LoadPrivateKeyModal.tsx new file mode 100644 index 0000000..f4ca654 --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/LoadPrivateKeyModal.tsx @@ -0,0 +1,38 @@ +import Image from "next/image"; + +type LoadPrivateKeyModalProps = { + modalId: string; +}; + +export const LoadPrivateKeyModal = ({ modalId }: LoadPrivateKeyModalProps) => { + const handleLoadPrivateKeyClick = () => { + window.open("https://core.app/es/", "_blank"); + }; + return ( + <> +
+ + +
+ + ); +}; diff --git a/packages/nextjs/components/punk-society/ConfigMenu/NetworkOptions.tsx b/packages/nextjs/components/punk-society/ConfigMenu/NetworkOptions.tsx new file mode 100644 index 0000000..bf16a56 --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/NetworkOptions.tsx @@ -0,0 +1,48 @@ +import { useTheme } from "next-themes"; +import { useAccount, useSwitchChain } from "wagmi"; +import { ArrowsRightLeftIcon } from "@heroicons/react/24/solid"; +import { getNetworkColor } from "~~/hooks/scaffold-eth"; +import { getTargetNetworks } from "~~/utils/scaffold-eth"; + +const allowedNetworks = getTargetNetworks(); + +type NetworkOptionsProps = { + hidden?: boolean; +}; + +export const NetworkOptions = ({ hidden = false }: NetworkOptionsProps) => { + const { switchChain } = useSwitchChain(); + const { chain } = useAccount(); + const { resolvedTheme } = useTheme(); + const isDarkMode = resolvedTheme === "dark"; + + return ( + <> + {allowedNetworks + .filter(allowedNetwork => allowedNetwork.id !== chain?.id) + .map(allowedNetwork => ( +
  • + +
  • + ))} + + ); +}; diff --git a/packages/nextjs/components/punk-society/ConfigMenu/PrivateKeyModal.tsx b/packages/nextjs/components/punk-society/ConfigMenu/PrivateKeyModal.tsx new file mode 100644 index 0000000..2dfb57e --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/PrivateKeyModal.tsx @@ -0,0 +1,64 @@ +import { useEffect, useState } from "react"; +import { notification } from "~~/utils/scaffold-eth"; + +type AddressQRCodeModalProps = { + modalId: string; +}; + +export const PrivateKeyModal = ({ modalId }: AddressQRCodeModalProps) => { + const [privateKey, setPrivateKey] = useState(null); + + useEffect(() => { + const storedPrivateKey = localStorage.getItem("burnerWallet.pk"); + setPrivateKey(storedPrivateKey); + }, []); + + const handleCopy = () => { + if (privateKey) { + navigator.clipboard + .writeText(privateKey) + .then(() => { + notification.success("Private key copied to clipboard"); + // alert("Private key copied to clipboard"); + }) + .catch(err => { + notification.error("Failed to copy private key: ", err); + }); + } + }; + + return ( + <> +
    + + +
    + + ); +}; diff --git a/packages/nextjs/components/punk-society/ConfigMenu/SendUSDCModal.tsx b/packages/nextjs/components/punk-society/ConfigMenu/SendUSDCModal.tsx new file mode 100644 index 0000000..5a6e3eb --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/SendUSDCModal.tsx @@ -0,0 +1,41 @@ +import Image from "next/image"; + +type SendUSDCModalProps = { + modalId: string; +}; + +export const SendUSDCModal = ({ modalId }: SendUSDCModalProps) => { + const handleSendUSDCClick = () => { + window.open("https://core.app/es/", "_blank"); + }; + return ( + <> +
    + + +
    + + ); +}; diff --git a/packages/nextjs/components/punk-society/ConfigMenu/SwitchLanguageModal.tsx b/packages/nextjs/components/punk-society/ConfigMenu/SwitchLanguageModal.tsx new file mode 100644 index 0000000..6cf1460 --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/SwitchLanguageModal.tsx @@ -0,0 +1,38 @@ +import Image from "next/image"; + +type AddressQRCodeModalProps = { + modalId: string; +}; + +export const SwitchLanguageModal = ({ modalId }: AddressQRCodeModalProps) => { + const handleLearnEnglishClick = () => { + window.open("https://www.duolingo.com/", "_blank"); + }; + return ( + <> +
    + + +
    + + ); +}; diff --git a/packages/nextjs/components/punk-society/ConfigMenu/WrongNetworkDropdown.tsx b/packages/nextjs/components/punk-society/ConfigMenu/WrongNetworkDropdown.tsx new file mode 100644 index 0000000..f9f0fd8 --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/WrongNetworkDropdown.tsx @@ -0,0 +1,32 @@ +import { NetworkOptions } from "./NetworkOptions"; +import { useDisconnect } from "wagmi"; +import { ArrowLeftOnRectangleIcon, ChevronDownIcon } from "@heroicons/react/24/outline"; + +export const WrongNetworkDropdown = () => { + const { disconnect } = useDisconnect(); + + return ( +
    + +
      + +
    • + +
    • +
    +
    + ); +}; diff --git a/packages/nextjs/components/punk-society/ConfigMenu/index.tsx b/packages/nextjs/components/punk-society/ConfigMenu/index.tsx new file mode 100644 index 0000000..3e8e43c --- /dev/null +++ b/packages/nextjs/components/punk-society/ConfigMenu/index.tsx @@ -0,0 +1,75 @@ +"use client"; + +// @refresh reset +import { AddressInfoDropdown } from "./AddressInfoDropdown"; +import { AddressQRCodeModal } from "./AddressQRCodeModal"; +import { BridgeUSDCModal } from "./BridgeUSDCModal"; +import { LoadPrivateKeyModal } from "./LoadPrivateKeyModal"; +import { PrivateKeyModal } from "./PrivateKeyModal"; +import { SendUSDCModal } from "./SendUSDCModal"; +import { SwitchLanguageModal } from "./SwitchLanguageModal"; +import { WrongNetworkDropdown } from "./WrongNetworkDropdown"; +import { ConnectButton } from "@rainbow-me/rainbowkit"; +import { Address } from "viem"; +import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; +import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth"; + +/** + * Custom Wagmi Connect Button (watch balance + custom design) + */ +export const ConfigMenu = () => { + const { targetNetwork } = useTargetNetwork(); + + return ( + + {({ account, chain, openConnectModal, mounted }) => { + const connected = mounted && account && chain; + const blockExplorerAddressLink = account + ? getBlockExplorerAddressLink(targetNetwork, account.address) + : undefined; + + return ( + <> + {(() => { + if (!connected) { + return ( + + ); + } + + if (chain.unsupported || chain.id !== targetNetwork.id) { + return ; + } + + return ( + <> +
    + {/*
    + + + {chain.name} + +
    */} + + + + + + + + + ); + })()} + + ); + }} +
    + ); +}; diff --git a/packages/nextjs/components/punk-society/PunkBalance.tsx b/packages/nextjs/components/punk-society/PunkBalance.tsx index 9716d8d..1a4f535 100644 --- a/packages/nextjs/components/punk-society/PunkBalance.tsx +++ b/packages/nextjs/components/punk-society/PunkBalance.tsx @@ -67,7 +67,7 @@ export const PunkBalance = ({ address, className = "", usdMode }: BalanceProps) ) : ( <> - {formattedBalance.toFixed(6)} + {formattedBalance.toFixed(2)} {targetNetwork.nativeCurrency.symbol} )} diff --git a/packages/nextjs/components/punk-society/PunkConnectButton/AddressInfoDropdown.tsx b/packages/nextjs/components/punk-society/PunkConnectButton/AddressInfoDropdown.tsx index 907c82b..10676c4 100644 --- a/packages/nextjs/components/punk-society/PunkConnectButton/AddressInfoDropdown.tsx +++ b/packages/nextjs/components/punk-society/PunkConnectButton/AddressInfoDropdown.tsx @@ -1,24 +1,16 @@ import { useRef, useState } from "react"; import Link from "next/link"; +import { PunkBalance } from "../PunkBalance"; import { NetworkOptions } from "./NetworkOptions"; import { FundButton, getOnrampBuyUrl } from "@coinbase/onchainkit/fund"; import { getAddress } from "viem"; import { Address } from "viem"; import { useAccount, useDisconnect } from "wagmi"; -import { KeyIcon as KeyIconOutline, UserIcon } from "@heroicons/react/24/outline"; -import { - ArrowLeftOnRectangleIcon, - ArrowTopRightOnSquareIcon, - ArrowsRightLeftIcon, - ChevronDownIcon, - QrCodeIcon, -} from "@heroicons/react/24/outline"; -import { ArrowUpLeftIcon, KeyIcon as KeyIconSolid, LanguageIcon, LinkIcon } from "@heroicons/react/24/solid"; +import { UserIcon } from "@heroicons/react/24/outline"; +import { ArrowLeftOnRectangleIcon, ChevronDownIcon, QrCodeIcon } from "@heroicons/react/24/outline"; +import { ArrowUpLeftIcon, LinkIcon } from "@heroicons/react/24/solid"; import { isENS } from "~~/components/scaffold-eth"; import { useOutsideClick, useScaffoldReadContract } from "~~/hooks/scaffold-eth"; -import { getTargetNetworks } from "~~/utils/scaffold-eth"; - -const allowedNetworks = getTargetNetworks(); type AddressInfoDropdownProps = { address: Address; @@ -27,7 +19,7 @@ type AddressInfoDropdownProps = { ensAvatar?: string; }; -export const AddressInfoDropdown = ({ address, displayName, blockExplorerAddressLink }: AddressInfoDropdownProps) => { +export const AddressInfoDropdown = ({ address, displayName }: AddressInfoDropdownProps) => { const [selectingNetwork, setSelectingNetwork] = useState(false); const { disconnect } = useDisconnect(); @@ -88,35 +80,8 @@ export const AddressInfoDropdown = ({ address, displayName, blockExplorerAddress className="dropdown-content menu z-[2] p-2 mt-2 shadow-center shadow-accent bg-base-200 rounded-box gap-1" >