├── nextjs ├── .npmrc ├── public │ ├── favicon.png │ ├── thumbnail.jpg │ ├── assets │ │ ├── delta.png │ │ ├── liquidity.png │ │ ├── gradient-bg.png │ │ ├── switch-button-off.png │ │ └── switch-button-on.png │ ├── font │ │ └── uniswap.ttf │ ├── manifest.json │ └── logo.svg ├── postcss.config.js ├── components │ ├── swap-ui │ │ ├── .swapComponent.tsx.swp │ │ ├── .InitializeComponent.tsx.swp │ │ ├── swapUI.tsx │ │ ├── PoolInit.tsx │ │ ├── LiquidityUI.tsx │ │ ├── helpers │ │ │ └── utils.tsx │ │ ├── assets │ │ │ ├── CopyIcon.tsx │ │ │ ├── HareIcon.tsx │ │ │ └── DiamondIcon.tsx │ │ └── ContractInteraction.tsx │ ├── scaffold-eth │ │ ├── Contract │ │ │ ├── .ContractEvents.tsx.swp │ │ │ ├── index.tsx │ │ │ ├── TxReceipt.tsx │ │ │ ├── ContractReadMethods.tsx │ │ │ ├── ContractWriteMethods.tsx │ │ │ ├── ContractVariables.tsx │ │ │ ├── ContractInput.tsx │ │ │ ├── utilsDisplay.tsx │ │ │ ├── ContractEvents.tsx │ │ │ ├── DisplayVariable.tsx │ │ │ ├── ReadOnlyFunctionForm.tsx │ │ │ ├── utilsContract.tsx │ │ │ └── WriteOnlyFunctionForm.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── BytesInput.tsx │ │ │ ├── Bytes32Input.tsx │ │ │ ├── InputBase.tsx │ │ │ ├── IntegerInput.tsx │ │ │ ├── AddressInput.tsx │ │ │ ├── utils.ts │ │ │ └── EtherInput.tsx │ │ ├── index.tsx │ │ ├── BlockieAvatar.tsx │ │ ├── Balance.tsx │ │ ├── NetworkSwitcher.tsx │ │ ├── FaucetButton.tsx │ │ └── Address.tsx │ ├── blockexplorer │ │ ├── index.tsx │ │ ├── AddressLogsTab.tsx │ │ ├── AddressCodeTab.tsx │ │ ├── TransactionHash.tsx │ │ ├── PaginationButton.tsx │ │ ├── SearchBar.tsx │ │ ├── AddressStorageTab.tsx │ │ └── TransactionsTable.tsx │ ├── ui │ │ ├── card.tsx │ │ ├── timeline.tsx │ │ └── text-hover-effect.tsx │ ├── assets │ │ ├── BuidlGuidlLogo.tsx │ │ └── Spinner.tsx │ ├── hookspage │ │ └── hooks-model.tsx │ ├── SwitchTheme.tsx │ ├── base │ │ ├── numeric-input.tsx │ │ ├── token-dropdown.tsx │ │ └── pool-key.tsx │ ├── Footer.tsx │ ├── primitives.ts │ └── MetaHeader.tsx ├── .eslintignore ├── utils │ ├── scaffold-eth │ │ ├── index.ts │ │ ├── common.ts │ │ ├── contractNames.ts │ │ ├── block.ts │ │ ├── decodeTxData.ts │ │ ├── fetchPriceFromUniswap.ts │ │ ├── notification.tsx │ │ └── networks.ts │ ├── tailwind.ts │ ├── config.ts │ ├── constants.ts │ └── v4helpers.ts ├── hooks │ └── scaffold-eth │ │ ├── scaffold-hook.code-workspace │ │ ├── useNetworkColor.ts │ │ ├── useAnimationConfig.ts │ │ ├── useOutsideClick.ts │ │ ├── index.ts │ │ ├── useNativeCurrencyPrice.ts │ │ ├── useAccountBalance.ts │ │ ├── useContractLogs.ts │ │ ├── useEventsWatch.ts │ │ ├── useScaffoldEventSubscriber.ts │ │ ├── useScaffoldContract.ts │ │ ├── useScaffoldContractRead.ts │ │ ├── useDeployedContractInfo.ts │ │ ├── useAutoConnect.ts │ │ ├── useScaffoldContractWrite.ts │ │ ├── useTransactor.tsx │ │ ├── useScaffoldEventHistory.ts │ │ └── useFetchBlocks.ts ├── .prettierrc.json ├── types │ └── abitype │ │ └── abi.d.ts ├── services │ ├── web3 │ │ ├── wagmiConfig.tsx │ │ ├── wagmi-burner │ │ │ ├── BurnerConnectorTypes.ts │ │ │ ├── BurnerConnectorErrors.ts │ │ │ └── burnerWalletConfig.ts │ │ └── wagmiConnectors.tsx │ └── store │ │ └── store.ts ├── .eslintrc.json ├── .gitignore ├── next.config.js ├── tsconfig.json ├── styles │ └── globals.css ├── .env.example ├── pages │ ├── swap-ui.tsx │ ├── _app.tsx │ ├── blockexplorer │ │ └── index.tsx │ ├── index.tsx │ └── debug.tsx ├── generated │ └── deployedContracts.ts ├── scaffold.config.ts └── package.json ├── .vscode └── settings.json ├── .gitmodules ├── foundry.toml ├── .env.example ├── remappings.txt ├── .gitignore ├── LICENSE ├── contracts ├── script │ └── DeployHook.s.sol ├── test │ ├── utils │ │ └── HookMiner.sol │ └── Counter.t.sol └── src │ └── Counter.sol └── package.json /nextjs/.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies = false 2 | -------------------------------------------------------------------------------- /nextjs/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/public/favicon.png -------------------------------------------------------------------------------- /nextjs/public/thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/public/thumbnail.jpg -------------------------------------------------------------------------------- /nextjs/public/assets/delta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/public/assets/delta.png -------------------------------------------------------------------------------- /nextjs/public/font/uniswap.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/public/font/uniswap.ttf -------------------------------------------------------------------------------- /nextjs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /nextjs/public/assets/liquidity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/public/assets/liquidity.png -------------------------------------------------------------------------------- /nextjs/public/assets/gradient-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/public/assets/gradient-bg.png -------------------------------------------------------------------------------- /nextjs/public/assets/switch-button-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/public/assets/switch-button-off.png -------------------------------------------------------------------------------- /nextjs/public/assets/switch-button-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/public/assets/switch-button-on.png -------------------------------------------------------------------------------- /nextjs/components/swap-ui/.swapComponent.tsx.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/components/swap-ui/.swapComponent.tsx.swp -------------------------------------------------------------------------------- /nextjs/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Scaffold-ETH 2 DApp", 3 | "description": "A DApp built with Scaffold-ETH", 4 | "iconPath": "logo.svg" 5 | } 6 | -------------------------------------------------------------------------------- /nextjs/components/swap-ui/.InitializeComponent.tsx.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/components/swap-ui/.InitializeComponent.tsx.swp -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.defaultCompiler": "localNodeModule", 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | } 6 | -------------------------------------------------------------------------------- /nextjs/.eslintignore: -------------------------------------------------------------------------------- 1 | # folders 2 | .next 3 | node_modules/ 4 | # files 5 | **/*.less 6 | **/*.css 7 | **/*.scss 8 | **/*.json 9 | **/*.png 10 | **/*.svg 11 | **/generated/**/* 12 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/.ContractEvents.tsx.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Attens1423/market-maker-hook/HEAD/nextjs/components/scaffold-eth/Contract/.ContractEvents.tsx.swp -------------------------------------------------------------------------------- /nextjs/utils/scaffold-eth/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./fetchPriceFromUniswap"; 2 | export * from "./networks"; 3 | export * from "./notification"; 4 | export * from "./block"; 5 | export * from "./decodeTxData"; 6 | -------------------------------------------------------------------------------- /nextjs/utils/tailwind.ts: -------------------------------------------------------------------------------- 1 | import { ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /nextjs/utils/scaffold-eth/common.ts: -------------------------------------------------------------------------------- 1 | // To be used in JSON.stringify when a field might be bigint 2 | // https://wagmi.sh/react/faq#bigint-serialization 3 | export const replacer = (key: string, value: unknown) => (typeof value === "bigint" ? value.toString() : value); 4 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Input/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AddressInput"; 2 | export * from "./Bytes32Input"; 3 | export * from "./BytesInput"; 4 | export * from "./EtherInput"; 5 | export * from "./InputBase"; 6 | export * from "./IntegerInput"; 7 | export * from "./utils"; 8 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/scaffold-hook.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "../../../../../scaffold-hook" 5 | }, 6 | { 7 | "path": "../../../.." 8 | } 9 | ], 10 | "settings": { 11 | "solidity.defaultCompiler": "localNodeModule" 12 | } 13 | } -------------------------------------------------------------------------------- /nextjs/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "printWidth": 120, 4 | "tabWidth": 2, 5 | "trailingComma": "all", 6 | "importOrder": ["^react$", "^next/(.*)$", "", "^@heroicons/(.*)$", "^~~/(.*)$"], 7 | "importOrderSortSpecifiers": true 8 | } 9 | -------------------------------------------------------------------------------- /nextjs/types/abitype/abi.d.ts: -------------------------------------------------------------------------------- 1 | import "abitype"; 2 | 3 | declare module "viem/node_modules/abitype" { 4 | export interface Config { 5 | AddressType: string; 6 | } 7 | } 8 | 9 | declare module "abitype" { 10 | export interface Config { 11 | AddressType: string; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /nextjs/components/blockexplorer/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./AddressCodeTab"; 2 | export * from "./AddressLogsTab"; 3 | export * from "./AddressStorageTab"; 4 | export * from "./PaginationButton"; 5 | export * from "./SearchBar"; 6 | export * from "./TransactionHash"; 7 | export * from "./TransactionsTable"; 8 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Address"; 2 | export * from "./Balance"; 3 | export * from "./BlockieAvatar"; 4 | export * from "./Contract"; 5 | export * from "./Faucet"; 6 | export * from "./FaucetButton"; 7 | export * from "./Input"; 8 | export * from "./RainbowKitCustomConnectButton"; 9 | -------------------------------------------------------------------------------- /nextjs/services/web3/wagmiConfig.tsx: -------------------------------------------------------------------------------- 1 | import { createConfig } from "wagmi"; 2 | import { appChains, wagmiConnectors } from "~~/services/web3/wagmiConnectors"; 3 | 4 | export const wagmiConfig = createConfig({ 5 | autoConnect: false, 6 | connectors: wagmiConnectors, 7 | publicClient: appChains.publicClient, 8 | }); 9 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./ContractInput"; 2 | export * from "./ContractUI"; 3 | export * from "./DisplayVariable"; 4 | export * from "./ReadOnlyFunctionForm"; 5 | export * from "./TxReceipt"; 6 | export * from "./utilsContract"; 7 | export * from "./utilsDisplay"; 8 | export * from "./WriteOnlyFunctionForm"; 9 | -------------------------------------------------------------------------------- /nextjs/services/web3/wagmi-burner/BurnerConnectorTypes.ts: -------------------------------------------------------------------------------- 1 | import { StaticJsonRpcProvider } from "@ethersproject/providers"; 2 | import { ConnectorData } from "wagmi"; 3 | 4 | export type BurnerConnectorOptions = { 5 | defaultChainId: number; 6 | }; 7 | 8 | export type BurnerConnectorData = ConnectorData & { 9 | provider: StaticJsonRpcProvider; 10 | }; 11 | -------------------------------------------------------------------------------- /nextjs/utils/scaffold-eth/contractNames.ts: -------------------------------------------------------------------------------- 1 | import scaffoldConfig from "~~/scaffold.config"; 2 | import { ContractName, contracts } from "~~/utils/scaffold-eth/contract"; 3 | 4 | export function getContractNames() { 5 | const contractsData = contracts?.[scaffoldConfig.targetNetwork.id]?.[0]?.contracts; 6 | return contractsData ? (Object.keys(contractsData) as ContractName[]) : []; 7 | } 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "contracts/lib/forge-std"] 2 | path = contracts/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/forge-std"] 5 | branch = v1.2.0 6 | [submodule "contracts/lib/v4-core"] 7 | path = contracts/lib/v4-core 8 | url = https://github.com/Uniswap/v4-core 9 | [submodule "contracts/lib/v4-periphery"] 10 | path = contracts/lib/v4-periphery 11 | url = https://github.com/uniswap/v4-periphery 12 | -------------------------------------------------------------------------------- /nextjs/components/swap-ui/swapUI.tsx: -------------------------------------------------------------------------------- 1 | import SwapComponent from "./swapComponent"; 2 | import { MetaHeader } from "~~/components/MetaHeader"; 3 | 4 | const SwapUI = () => { 5 | return ( 6 | <> 7 | 8 |
9 | 10 |
11 | 12 | ); 13 | }; 14 | 15 | export default SwapUI; 16 | -------------------------------------------------------------------------------- /nextjs/utils/scaffold-eth/block.ts: -------------------------------------------------------------------------------- 1 | import { Block, Transaction, TransactionReceipt } from "viem"; 2 | 3 | export type TransactionWithFunction = Transaction & { 4 | functionName?: string; 5 | functionArgs?: any[]; 6 | functionArgNames?: string[]; 7 | functionArgTypes?: string[]; 8 | }; 9 | 10 | interface TransactionReceipts { 11 | [key: string]: TransactionReceipt; 12 | } 13 | 14 | export interface TransactionsTableProps { 15 | blocks: Block[]; 16 | transactionReceipts: TransactionReceipts; 17 | } 18 | -------------------------------------------------------------------------------- /nextjs/components/swap-ui/PoolInit.tsx: -------------------------------------------------------------------------------- 1 | import InitializeComponent from "./InitializeComponent"; 2 | import { MetaHeader } from "~~/components/MetaHeader"; 3 | 4 | const PoolInit = () => { 5 | return ( 6 | <> 7 | 8 |
9 | 10 |
11 | 12 | ); 13 | }; 14 | 15 | export default PoolInit; 16 | -------------------------------------------------------------------------------- /nextjs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": ["next/core-web-vitals", "plugin:prettier/recommended", "plugin:@typescript-eslint/recommended"], 4 | "rules": { 5 | "@typescript-eslint/no-unused-vars": ["error"], 6 | "@typescript-eslint/no-explicit-any": ["off"], 7 | "@typescript-eslint/ban-ts-comment": ["off"], 8 | "prettier/prettier": [ 9 | "warn", 10 | { 11 | "endOfLine": "auto" 12 | } 13 | ], 14 | "@next/next/no-page-custom-font": ["off"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /nextjs/components/swap-ui/LiquidityUI.tsx: -------------------------------------------------------------------------------- 1 | import { MetaHeader } from "~~/components/MetaHeader"; 2 | import LiquidityComponent from "./LiquidityComponent"; 3 | 4 | const LiquidityUI = () => { 5 | 6 | return ( 7 | <> 8 | 9 |
10 | 11 |
12 | 13 | ); 14 | }; 15 | 16 | export default LiquidityUI; 17 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts/src' 3 | test = 'contracts/test' 4 | out = 'contracts/out' 5 | libs = ['contracts/lib'] 6 | fs_permissions = [{ access = "read", path = "./contracts/out"}, { access = "write", path = "genesis.json"}] 7 | cancun = true 8 | 9 | evm_version = "cancun" 10 | 11 | # For Linux/WSL2 systems, please change to `contracts/lib/v4-core/bin/solc-static-linux` 12 | # solc = "contracts/lib/v4-core/bin/solc-mac" 13 | # solc = "solc-mac" 14 | 15 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useNetworkColor.ts: -------------------------------------------------------------------------------- 1 | import { useDarkMode } from "usehooks-ts"; 2 | import { getTargetNetwork } from "~~/utils/scaffold-eth"; 3 | 4 | const DEFAULT_NETWORK_COLOR: [string, string] = ["#666666", "#bbbbbb"]; 5 | 6 | /** 7 | * Gets the color of the target network 8 | */ 9 | export const useNetworkColor = () => { 10 | const { isDarkMode } = useDarkMode(); 11 | const colorConfig = getTargetNetwork().color ?? DEFAULT_NETWORK_COLOR; 12 | 13 | return Array.isArray(colorConfig) ? (isDarkMode ? colorConfig[1] : colorConfig[0]) : colorConfig; 14 | }; 15 | -------------------------------------------------------------------------------- /nextjs/utils/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | counterConfig, 3 | poolManagerConfig, 4 | poolModifyLiquidityTestConfig, 5 | poolSwapTestConfig, 6 | token0Address, 7 | token1Address, 8 | } from "~~/generated/generated"; 9 | 10 | export const TOKEN_ADDRESSES = [token0Address, token1Address]; 11 | 12 | export const DEBUGGABLE_ADDRESSES = [ 13 | { ...counterConfig, name: "Counter" }, 14 | { ...poolManagerConfig, name: "PoolManager" }, 15 | { ...poolModifyLiquidityTestConfig, name: "PoolModifyLiquidityTest" }, 16 | { ...poolSwapTestConfig, name: "PoolSwapTest" }, 17 | ]; 18 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ANVIL_CHAIN_ID=31337 2 | ANVIL_FORK_URL=https://cloudflare-eth.com 3 | ETHERSCAN_API_KEY= 4 | FORGE_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 5 | 6 | # Hook Contract 7 | HOOK_CONTRACT="Counter.sol:Counter" 8 | 9 | # Hook Flags 10 | BEFORE_SWAP=true 11 | AFTER_SWAP=true 12 | BEFORE_ADD_LIQUIDITY=true 13 | AFTER_ADD_LIQUIDITY=true 14 | BEFORE_REMOVE_LIQUIDITY=false 15 | AFTER_REMOVE_LIQUIDITY=false 16 | BEFORE_INITIALIZE=false 17 | AFTER_INITIALIZE=false 18 | BEFORE_DONATE=false 19 | AFTER_DONATE=false 20 | NO_OP=false 21 | ACCESS_LOCK=false 22 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/BlockieAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { AvatarComponent } from "@rainbow-me/rainbowkit"; 2 | import { blo } from "blo"; 3 | 4 | // Custom Avatar for RainbowKit 5 | export const BlockieAvatar: AvatarComponent = ({ address, ensImage, size }) => ( 6 | // Don't want to use nextJS Image here (and adding remote patterns for the URL) 7 | // eslint-disable-next-line @next/next/no-img-element 8 | {`${address} 15 | ); 16 | -------------------------------------------------------------------------------- /nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @ensdomains/=contracts/lib/v4-core/node_modules/@ensdomains/ 2 | @openzeppelin/=contracts/lib/v4-core/lib/openzeppelin-contracts/ 3 | ds-test/=contracts/lib/forge-std/lib/ds-test/src/ 4 | forge-gas-snapshot/=contracts/lib/v4-core/lib/forge-gas-snapshot/src/ 5 | forge-std/=contracts/lib/v4-core/lib/forge-std/src/ 6 | hardhat/=contracts/lib/v4-core/node_modules/hardhat/ 7 | openzeppelin-contracts/=contracts/lib/v4-core/lib/openzeppelin-contracts/contracts/ 8 | solmate/=contracts/lib/v4-core/lib/solmate/ 9 | @uniswap/v4-core/=contracts/lib/v4-core/ 10 | @uniswap/v4-periphery/=contracts/lib/v4-periphery/ 11 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useAnimationConfig.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | const ANIMATION_TIME = 2000; 4 | 5 | export function useAnimationConfig(data: any) { 6 | const [showAnimation, setShowAnimation] = useState(false); 7 | const [prevData, setPrevData] = useState(); 8 | 9 | useEffect(() => { 10 | if (prevData !== undefined && prevData !== data) { 11 | setShowAnimation(true); 12 | setTimeout(() => setShowAnimation(false), ANIMATION_TIME); 13 | } 14 | setPrevData(data); 15 | }, [data, prevData]); 16 | 17 | return { 18 | showAnimation, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /nextjs/next.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | images: { 6 | domains: ["localhost", "res.cloudinary.com", "*", "uniswaphooks.com"], 7 | }, 8 | reactStrictMode: false, 9 | typescript: { 10 | ignoreBuildErrors: process.env.NEXT_PUBLIC_IGNORE_BUILD_ERROR === "true", 11 | }, 12 | eslint: { 13 | ignoreDuringBuilds: process.env.NEXT_PUBLIC_IGNORE_BUILD_ERROR === "true", 14 | }, 15 | webpack: config => { 16 | config.resolve.fallback = { fs: false, net: false, tls: false }; 17 | return config; 18 | }, 19 | }; 20 | 21 | module.exports = nextConfig; 22 | -------------------------------------------------------------------------------- /nextjs/services/store/store.ts: -------------------------------------------------------------------------------- 1 | import create from "zustand"; 2 | 3 | /** 4 | * Zustand Store 5 | * 6 | * You can add global state to the app using this useGlobalState, to get & set 7 | * values from anywhere in the app. 8 | * 9 | * Think about it as a global useState. 10 | */ 11 | 12 | type TGlobalState = { 13 | nativeCurrencyPrice: number; 14 | setNativeCurrencyPrice: (newNativeCurrencyPriceState: number) => void; 15 | }; 16 | 17 | export const useGlobalState = create(set => ({ 18 | nativeCurrencyPrice: 0, 19 | setNativeCurrencyPrice: (newValue: number): void => set(() => ({ nativeCurrencyPrice: newValue })), 20 | })); 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | 11 | /coverage 12 | /broadcast 13 | # next.js 14 | 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | 20 | /build 21 | 22 | # misc 23 | 24 | .DS_Store 25 | \*.pem 26 | 27 | # debug 28 | 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | .pnpm-debug.log* 33 | 34 | # local env files 35 | 36 | .env 37 | .env\*.local 38 | 39 | # vercel 40 | 41 | .vercel 42 | 43 | # typescript 44 | 45 | \*.tsbuildinfo 46 | next-env.d.ts 47 | 48 | # forge 49 | out 50 | cache -------------------------------------------------------------------------------- /nextjs/components/swap-ui/helpers/utils.tsx: -------------------------------------------------------------------------------- 1 | // Helper function to convert sqrtPriceX96 to a formatted price 2 | const convertSqrtPriceX96ToPrice = sqrtPriceX96 => { 3 | // Step 1: Convert fixed-point to floating-point 4 | const sqrtPriceX96ToFloat = sqrtPrice => sqrtPrice / Math.pow(2, 48); 5 | 6 | // Step 2: Square to get the actual price 7 | const actualPrice = sqrtPrice => Math.pow(sqrtPrice, 2); 8 | 9 | // Perform conversions 10 | const sqrtPriceFloat = sqrtPriceX96ToFloat(Number(sqrtPriceX96)); 11 | const price = actualPrice(sqrtPriceFloat); 12 | 13 | // Format to 2 decimal places 14 | return price.toFixed(6); 15 | }; 16 | 17 | export default convertSqrtPriceX96ToPrice; 18 | -------------------------------------------------------------------------------- /nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "paths": { 18 | "~~/*": ["./*"], 19 | "@/*": ["./*"] 20 | } 21 | }, 22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /nextjs/utils/constants.ts: -------------------------------------------------------------------------------- 1 | import { FetchTokenResult } from "wagmi/dist/actions"; 2 | 3 | export const MAX_UINT = 115792089237316195423570985008687907853269984665640564039457584007913129639935n; 4 | 5 | // allows for unlimited slippage 6 | export const MIN_SQRT_PRICE_LIMIT = 4295128739n + 1n; 7 | export const MAX_SQRT_PRICE_LIMIT = 1461446703485210103287273052203988822378723970342n - 1n; 8 | 9 | export const ZERO_ADDR = "0x0000000000000000000000000000000000000000"; 10 | 11 | export const BLANK_TOKEN: FetchTokenResult = { 12 | address: "0x000000000000000000000000000000000000dEaD", 13 | symbol: "BLANK", 14 | name: "BLANK", 15 | decimals: 18, 16 | totalSupply: { formatted: "0", value: 0n }, 17 | }; 18 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useOutsideClick.ts: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | 3 | /** 4 | * Check if a click was made outside the passed ref 5 | */ 6 | export const useOutsideClick = (ref: React.RefObject, callback: { (): void }) => { 7 | useEffect(() => { 8 | function handleOutsideClick(event: MouseEvent) { 9 | if (!(event.target instanceof Element)) { 10 | return; 11 | } 12 | 13 | if (ref.current && !ref.current.contains(event.target)) { 14 | callback(); 15 | } 16 | } 17 | 18 | document.addEventListener("click", handleOutsideClick); 19 | return () => document.removeEventListener("click", handleOutsideClick); 20 | }, [ref, callback]); 21 | }; 22 | -------------------------------------------------------------------------------- /nextjs/styles/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; 4 | 5 | @font-face { 6 | font-family: "UniswapFont"; 7 | src: url("/font/uniswap.ttf") format("ttf"); 8 | font-weight: normal; 9 | font-style: normal; 10 | font-display: swap; 11 | } 12 | 13 | :root, 14 | [data-theme] { 15 | background: none; 16 | } 17 | 18 | body { 19 | min-height: 100vh; 20 | font-family: "UniswapFont", sans-serif; 21 | } 22 | 23 | h1, 24 | h2, 25 | h3, 26 | h4 { 27 | margin-bottom: 0.5rem; 28 | line-height: 1; 29 | } 30 | 31 | p { 32 | margin: 1rem 0; 33 | } 34 | 35 | .btn { 36 | @apply shadow-md; 37 | } 38 | 39 | .btn.btn-ghost { 40 | @apply shadow-none; 41 | } 42 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./useAccountBalance"; 2 | export * from "./useAnimationConfig"; 3 | export * from "./useBurnerWallet"; 4 | export * from "./useDeployedContractInfo"; 5 | export * from "./useNativeCurrencyPrice"; 6 | export * from "./useNetworkColor"; 7 | export * from "./useOutsideClick"; 8 | export * from "./useScaffoldContract"; 9 | export * from "./useScaffoldContractRead"; 10 | export * from "./useScaffoldContractWrite"; 11 | export * from "./useScaffoldEventSubscriber"; 12 | export * from "./useScaffoldEventHistory"; 13 | export * from "./useTransactor"; 14 | export * from "./useFetchBlocks"; 15 | export * from "./useContractLogs"; 16 | export * from "./useAutoConnect"; 17 | export * from "./useEventsWatch"; 18 | -------------------------------------------------------------------------------- /nextjs/utils/v4helpers.ts: -------------------------------------------------------------------------------- 1 | import { encodeAbiParameters, keccak256 } from "viem"; 2 | 3 | export function getPoolId({ 4 | currency0, 5 | currency1, 6 | fee, 7 | tickSpacing, 8 | hooks, 9 | }: { 10 | currency0: string; 11 | currency1: string; 12 | fee: number; 13 | tickSpacing: number; 14 | hooks: string; 15 | }): `0x${string}` { 16 | return keccak256( 17 | encodeAbiParameters( 18 | [ 19 | { name: "currency0", type: "address" }, 20 | { name: "currency1", type: "address" }, 21 | { name: "fee", type: "uint24" }, 22 | { name: "tickSpacing", type: "int24" }, 23 | { name: "hooks", type: "address" }, 24 | ], 25 | [currency0, currency1, fee, tickSpacing, hooks], 26 | ), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /nextjs/components/blockexplorer/AddressLogsTab.tsx: -------------------------------------------------------------------------------- 1 | import { Address } from "viem"; 2 | import { useContractLogs } from "~~/hooks/scaffold-eth"; 3 | import { replacer } from "~~/utils/scaffold-eth/common"; 4 | 5 | export const AddressLogsTab = ({ address }: { address: Address }) => { 6 | const contractLogs = useContractLogs(address); 7 | 8 | return ( 9 |
10 |
11 |
12 |           {contractLogs.map((log, i) => (
13 |             
14 | Log: {JSON.stringify(log, replacer, 2)} 15 |
16 | ))} 17 |
18 |
19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /nextjs/.env.example: -------------------------------------------------------------------------------- 1 | # Template for NextJS environment variables. 2 | 3 | # For local development, copy this file, rename it to .env.local, and fill in the values. 4 | # When deploying live, you'll need to store the vars in Vercel/System config. 5 | 6 | # If not set, we provide default values (check `scaffold.config.ts`) so developers can start prototyping out of the box, 7 | # but we recommend getting your own API Keys for Production Apps. 8 | 9 | # To access the values stored in this env file you can use: process.env.VARIABLENAME 10 | # You'll need to prefix the variables names with NEXT_PUBLIC_ if you want to access them on the client side. 11 | # More info: https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables 12 | NEXT_PUBLIC_ALCHEMY_API_KEY= 13 | NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID= 14 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/TxReceipt.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionReceipt } from "viem"; 2 | import { displayTxResult } from "~~/components/scaffold-eth"; 3 | 4 | export const TxReceipt = ( 5 | txResult: string | number | bigint | Record | TransactionReceipt | undefined, 6 | ) => { 7 | return ( 8 |
9 | 10 |
11 | Transaction Receipt 12 |
13 |
14 |
{displayTxResult(txResult)}
15 |
16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /nextjs/components/blockexplorer/AddressCodeTab.tsx: -------------------------------------------------------------------------------- 1 | type AddressCodeTabProps = { 2 | bytecode: string; 3 | assembly: string; 4 | }; 5 | 6 | export const AddressCodeTab = ({ bytecode, assembly }: AddressCodeTabProps) => { 7 | const formattedAssembly = assembly.split(" ").join("\n"); 8 | 9 | return ( 10 |
11 | Bytecode 12 |
13 |
14 |           {bytecode}
15 |         
16 |
17 | Opcodes 18 |
19 |
20 |           {formattedAssembly}
21 |         
22 |
23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /nextjs/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { cn } from "@/utils/tailwind"; 5 | import { motion } from "framer-motion"; 6 | 7 | export const Card: React.FC<{ 8 | children: React.ReactNode; 9 | className?: string; 10 | draggable?: boolean; 11 | }> = ({ children, className, draggable = true }) => { 12 | return ( 13 | 29 | {children} 30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Input/BytesInput.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | import { bytesToString, isHex, toBytes, toHex } from "viem"; 3 | import { CommonInputProps, InputBase } from "~~/components/scaffold-eth"; 4 | 5 | export const BytesInput = ({ value, onChange, name, placeholder, disabled }: CommonInputProps) => { 6 | const convertStringToBytes = useCallback(() => { 7 | onChange(isHex(value) ? bytesToString(toBytes(value)) : toHex(toBytes(value))); 8 | }, [onChange, value]); 9 | 10 | return ( 11 | 22 | # 23 | 24 | } 25 | /> 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /nextjs/components/assets/BuidlGuidlLogo.tsx: -------------------------------------------------------------------------------- 1 | export const BuidlGuidlLogo = ({ className }: { className: string }) => { 2 | return ( 3 | 11 | 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /nextjs/services/web3/wagmi-burner/BurnerConnectorErrors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Error list used by {@link BurnerConnectorError} 3 | */ 4 | export const BurnerConnectorErrorList = { 5 | accountNotFound: "Account not found", 6 | couldNotConnect: "Could not connect to network", 7 | unsupportedBurnerChain: "This network is not supported for burner connector", 8 | chainIdNotResolved: "Cound not resolve chainId", 9 | signerNotResolved: "Cound not resolve signer", 10 | chainNotSupported: "Chain is not supported, check burner wallet config", 11 | } as const; 12 | 13 | /** 14 | * A union of all the BurnerConnectorErrorList 15 | */ 16 | export type BurnerConnectorErrorTypes = typeof BurnerConnectorErrorList[keyof typeof BurnerConnectorErrorList]; 17 | 18 | export class BurnerConnectorError extends Error { 19 | constructor(errorType: BurnerConnectorErrorTypes, message?: string) { 20 | const msg = `BurnerConnectorError ${errorType}: ${message ?? ""} `; 21 | super(msg); 22 | console.warn(msg); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Input/Bytes32Input.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | import { hexToString, isHex, stringToHex } from "viem"; 3 | import { CommonInputProps, InputBase } from "~~/components/scaffold-eth"; 4 | 5 | export const Bytes32Input = ({ value, onChange, name, placeholder, disabled }: CommonInputProps) => { 6 | const convertStringToBytes32 = useCallback(() => { 7 | if (!value) { 8 | return; 9 | } 10 | onChange(isHex(value) ? hexToString(value, { size: 32 }) : stringToHex(value, { size: 32 })); 11 | }, [onChange, value]); 12 | 13 | return ( 14 | 25 | # 26 | 27 | } 28 | /> 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /nextjs/pages/swap-ui.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { useRouter } from "next/router"; 3 | import type { NextPage } from "next"; 4 | import LiquidityUI from "~~/components/swap-ui/LiquidityUI"; 5 | import PoolInit from "~~/components/swap-ui/PoolInit"; 6 | import SwapUI from "~~/components/swap-ui/swapUI"; 7 | 8 | const SwapUIPage: NextPage = () => { 9 | const router = useRouter(); 10 | console.log(router, "router"); 11 | const { query } = router; 12 | const isSwap = useMemo(() => query.page === "swap", [query.page]); 13 | const isLiquidity = useMemo(() => query.page === "liquidity", [query.page]); 14 | const isInitialize = useMemo(() => query.page === "initialize", [query.page]); 15 | 16 | if (router.isFallback) { 17 | return
Loading...
; 18 | } else if (router.isReady && isSwap) { 19 | return ; 20 | } else if (router.isReady && isLiquidity) { 21 | return ; 22 | } else if (router.isReady && isInitialize) { 23 | return ; 24 | } 25 | 26 | return <>; 27 | }; 28 | 29 | export default SwapUIPage; 30 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx: -------------------------------------------------------------------------------- 1 | import { ReadOnlyFunctionForm } from "./ReadOnlyFunctionForm"; 2 | import { Abi, AbiFunction } from "abitype"; 3 | import { Contract, ContractName } from "~~/utils/scaffold-eth/contract"; 4 | 5 | export const ContractReadMethods = ({ deployedContractData }: { deployedContractData: Contract }) => { 6 | if (!deployedContractData) { 7 | return null; 8 | } 9 | 10 | const functionsToDisplay = ( 11 | ((deployedContractData.abi || []) as Abi).filter(part => part.type === "function") as AbiFunction[] 12 | ).filter(fn => { 13 | const isQueryableWithParams = 14 | (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length > 0; 15 | return isQueryableWithParams; 16 | }); 17 | 18 | if (!functionsToDisplay.length) { 19 | return <>No read methods; 20 | } 21 | 22 | return ( 23 | <> 24 | {functionsToDisplay.map(fn => ( 25 | 26 | ))} 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 saucepoint 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /nextjs/components/hookspage/hooks-model.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Card, CardBody, CardFooter, CardHeader, Divider, Image, Link } from "@nextui-org/react"; 3 | 4 | export default function Hook() { 5 | return ( 6 | 7 | 8 | nextui logo 15 |
16 |

NextUI

17 |

nextui.org

18 |
19 |
20 | 21 | 22 |

Make beautiful websites regardless of your design experience.

23 |
24 | 25 | 26 | 27 | Visit source code on GitHub. 28 | 29 | 30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useNativeCurrencyPrice.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useInterval } from "usehooks-ts"; 3 | import scaffoldConfig from "~~/scaffold.config"; 4 | import { fetchPriceFromUniswap } from "~~/utils/scaffold-eth"; 5 | 6 | const enablePolling = false; 7 | 8 | /** 9 | * Get the price of Native Currency based on Native Token/DAI trading pair from Uniswap SDK 10 | * @returns nativeCurrencyPrice: number 11 | */ 12 | export const useNativeCurrencyPrice = () => { 13 | const [nativeCurrencyPrice, setNativeCurrencyPrice] = useState(0); 14 | 15 | // Get the price of ETH from Uniswap on mount 16 | useEffect(() => { 17 | (async () => { 18 | const price = await fetchPriceFromUniswap(); 19 | setNativeCurrencyPrice(price); 20 | })(); 21 | }, []); 22 | 23 | // Get the price of ETH from Uniswap at a given interval 24 | useInterval( 25 | async () => { 26 | const price = await fetchPriceFromUniswap(); 27 | setNativeCurrencyPrice(price); 28 | }, 29 | enablePolling ? scaffoldConfig.pollingInterval : null, 30 | ); 31 | 32 | return nativeCurrencyPrice; 33 | }; 34 | -------------------------------------------------------------------------------- /nextjs/components/SwitchTheme.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDarkMode, useIsMounted } from "usehooks-ts"; 3 | import { MoonIcon, SunIcon } from "@heroicons/react/24/outline"; 4 | 5 | export const SwitchTheme = ({ className }: { className?: string }) => { 6 | const { isDarkMode, toggle } = useDarkMode(); 7 | const isMounted = useIsMounted(); 8 | 9 | useEffect(() => { 10 | const body = document.body; 11 | body.setAttribute("data-theme", isDarkMode ? "scaffoldEthDark" : "scaffoldEth"); 12 | }, [isDarkMode]); 13 | 14 | return ( 15 |
16 | 23 | {isMounted() && ( 24 | 28 | )} 29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /nextjs/components/base/numeric-input.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent, HTMLInputTypeAttribute } from "react"; 2 | import { Tooltip } from "@nextui-org/react"; 3 | import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; 4 | 5 | export function NumericInput({ 6 | type, 7 | placeholder, 8 | tooltipText, 9 | value, 10 | onChange, 11 | }: { 12 | type: HTMLInputTypeAttribute; 13 | placeholder: string; 14 | tooltipText: string; 15 | value: string | number; 16 | onChange: (e: ChangeEvent) => void; 17 | }) { 18 | return ( 19 |
20 | 26 | 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useAccountBalance.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | import { useBalance } from "wagmi"; 3 | import { useGlobalState } from "~~/services/store/store"; 4 | import { getTargetNetwork } from "~~/utils/scaffold-eth"; 5 | 6 | export function useAccountBalance(address?: string) { 7 | const [isEthBalance, setIsEthBalance] = useState(true); 8 | const [balance, setBalance] = useState(null); 9 | const price = useGlobalState(state => state.nativeCurrencyPrice); 10 | 11 | const { 12 | data: fetchedBalanceData, 13 | isError, 14 | isLoading, 15 | } = useBalance({ 16 | address, 17 | watch: true, 18 | chainId: getTargetNetwork().id, 19 | }); 20 | 21 | const onToggleBalance = useCallback(() => { 22 | if (price > 0) { 23 | setIsEthBalance(!isEthBalance); 24 | } 25 | }, [isEthBalance, price]); 26 | 27 | useEffect(() => { 28 | if (fetchedBalanceData?.formatted) { 29 | setBalance(Number(fetchedBalanceData.formatted)); 30 | } 31 | }, [fetchedBalanceData]); 32 | 33 | return { balance, price, isError, isLoading, onToggleBalance, isEthBalance }; 34 | } 35 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useContractLogs.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { Address, Log } from "viem"; 3 | import { usePublicClient } from "wagmi"; 4 | 5 | export const useContractLogs = (address: Address) => { 6 | const [logs, setLogs] = useState([]); 7 | const client = usePublicClient(); 8 | 9 | useEffect(() => { 10 | const fetchLogs = async () => { 11 | try { 12 | const existingLogs = await client.getLogs({ 13 | address: address, 14 | fromBlock: 0n, 15 | toBlock: "latest", 16 | }); 17 | setLogs(existingLogs); 18 | } catch (error) { 19 | console.error("Failed to fetch logs:", error); 20 | } 21 | }; 22 | fetchLogs(); 23 | 24 | return client.watchBlockNumber({ 25 | onBlockNumber: async (blockNumber, prevBlockNumber) => { 26 | const newLogs = await client.getLogs({ 27 | address: address, 28 | fromBlock: prevBlockNumber, 29 | toBlock: "latest", 30 | }); 31 | setLogs(prevLogs => [...prevLogs, ...newLogs]); 32 | }, 33 | }); 34 | }, [address, client]); 35 | 36 | return logs; 37 | }; 38 | -------------------------------------------------------------------------------- /nextjs/components/swap-ui/assets/CopyIcon.tsx: -------------------------------------------------------------------------------- 1 | export const CopyIcon = ({ className }: { className: string }) => { 2 | return ( 3 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /nextjs/generated/deployedContracts.ts: -------------------------------------------------------------------------------- 1 | import { counterABI, hookMinerABI, poolManagerABI, poolModifyLiquidityTestABI, poolSwapTestABI } from "./generated"; 2 | import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; 3 | 4 | export const deployedContracts: GenericContractsDeclaration = { 5 | 31337: [ 6 | { 7 | name: "Anvil", 8 | chainId: "31337", 9 | contracts: { 10 | PoolManager: { 11 | address: "0x5FbDB2315678afecb367f032d93F642f64180aa3", 12 | abi: poolManagerABI, 13 | }, 14 | Counter: { 15 | address: "0x3Ce72a2059524eC26219E6a7f9dBe387370ac1D8", 16 | abi: counterABI, 17 | }, 18 | HookMiner: { 19 | address: "0x32cd5ecdA7f2B8633C00A0434DE28Db111E60636", 20 | abi: hookMinerABI, 21 | }, 22 | PoolModifyLiquityTest: { 23 | address: "0x3079c0319f8734239eb06765666468f7b76eb505", 24 | abi: poolModifyLiquidityTestABI, 25 | }, 26 | PoolSwapTest: { 27 | address: "0xaf7ccf0ff7ef054a1db43330f5431958ab4a9441", 28 | abi: poolSwapTestABI, 29 | }, 30 | }, 31 | }, 32 | ], 33 | }; 34 | 35 | export default deployedContracts; 36 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx: -------------------------------------------------------------------------------- 1 | import { WriteOnlyFunctionForm } from "./WriteOnlyFunctionForm"; 2 | import { Abi, AbiFunction } from "abitype"; 3 | import { Contract, ContractName } from "~~/utils/scaffold-eth/contract"; 4 | 5 | export const ContractWriteMethods = ({ 6 | onChange, 7 | deployedContractData, 8 | }: { 9 | onChange: () => void; 10 | deployedContractData: Contract; 11 | }) => { 12 | if (!deployedContractData) { 13 | return null; 14 | } 15 | 16 | const functionsToDisplay = ( 17 | (deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[] 18 | ).filter(fn => { 19 | const isWriteableFunction = fn.stateMutability !== "view" && fn.stateMutability !== "pure"; 20 | return isWriteableFunction; 21 | }); 22 | 23 | if (!functionsToDisplay.length) { 24 | return <>No write methods; 25 | } 26 | 27 | return ( 28 | <> 29 | {functionsToDisplay.map((fn, idx) => ( 30 | 36 | ))} 37 | 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/ContractVariables.tsx: -------------------------------------------------------------------------------- 1 | import { DisplayVariable } from "./DisplayVariable"; 2 | import { Abi, AbiFunction } from "abitype"; 3 | import { Contract, ContractName } from "~~/utils/scaffold-eth/contract"; 4 | 5 | export const ContractVariables = ({ 6 | refreshDisplayVariables, 7 | deployedContractData, 8 | }: { 9 | refreshDisplayVariables: boolean; 10 | deployedContractData: Contract; 11 | }) => { 12 | if (!deployedContractData) { 13 | return null; 14 | } 15 | 16 | const functionsToDisplay = ( 17 | (deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[] 18 | ).filter(fn => { 19 | const isQueryableWithNoParams = 20 | (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0; 21 | return isQueryableWithNoParams; 22 | }); 23 | 24 | if (!functionsToDisplay.length) { 25 | return <>No contract variables; 26 | } 27 | 28 | return ( 29 | <> 30 | {functionsToDisplay.map(fn => ( 31 | 37 | ))} 38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /nextjs/components/base/token-dropdown.tsx: -------------------------------------------------------------------------------- 1 | import { Select, SelectItem, Tooltip } from "@nextui-org/react"; 2 | import { FetchTokenResult } from "wagmi/dist/actions"; 3 | import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; 4 | 5 | export function TokenDropdown({ 6 | label, 7 | tooltipText, 8 | options, 9 | onChange, 10 | }: { 11 | label: string; 12 | tooltipText: string; 13 | options: FetchTokenResult[]; 14 | onChange: (e: React.ChangeEvent) => void; 15 | }) { 16 | return ( 17 |
18 | 24 | 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /nextjs/components/blockexplorer/TransactionHash.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Link from "next/link"; 3 | import { CopyToClipboard } from "react-copy-to-clipboard"; 4 | import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline"; 5 | 6 | export const TransactionHash = ({ hash }: { hash: string }) => { 7 | const [addressCopied, setAddressCopied] = useState(false); 8 | 9 | return ( 10 |
11 | 12 | {hash?.substring(0, 6)}...{hash?.substring(hash.length - 4)} 13 | 14 | {addressCopied ? ( 15 |
36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /nextjs/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Site footer 3 | */ 4 | export const Footer = () => { 5 | return ( 6 |
7 |
8 |
9 | 36 |
37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /nextjs/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useEventsWatch.ts: -------------------------------------------------------------------------------- 1 | 2 | import { useEffect, useRef } from "react"; 3 | import { usePublicClient } from "wagmi"; 4 | import scaffoldConfig from "~~/scaffold.config"; 5 | import { Contract, ContractName } from "~~/utils/scaffold-eth/contract"; 6 | 7 | type UseEventWatchParams = { 8 | deployedContractData?: Contract; 9 | onEvent: (e: any) => void; 10 | chainId?: number; 11 | }; 12 | 13 | export const useEventWatch = ({ 14 | deployedContractData, 15 | onEvent, 16 | chainId = scaffoldConfig.targetNetwork.id, 17 | }: UseEventWatchParams) => { 18 | const publicClient = usePublicClient({ chainId }); 19 | const unwatchHandlerRef = useRef<() => void>(() => undefined); 20 | 21 | useEffect(() => { 22 | const { address, abi } = deployedContractData ?? { address: "", abi: [] }; 23 | console.log("useEventWatch", { address, abi }); 24 | if (!address || !abi) return; 25 | publicClient.watchContractEvent({ 26 | address, 27 | abi, 28 | onLogs: logs => onEvent(logs), 29 | }); 30 | unwatchHandlerRef.current = publicClient.watchContractEvent({ 31 | address, 32 | abi, 33 | onLogs: logs => onEvent(logs), 34 | }); 35 | 36 | return () => { 37 | if (!!unwatchHandlerRef.current) unwatchHandlerRef.current(); 38 | }; 39 | }, [deployedContractData, onEvent, publicClient]); 40 | 41 | return { unwatch: unwatchHandlerRef.current }; 42 | }; -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Input/InputBase.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent, ReactNode, useCallback } from "react"; 2 | import { CommonInputProps } from "~~/components/scaffold-eth"; 3 | 4 | type InputBaseProps = CommonInputProps & { 5 | error?: boolean; 6 | prefix?: ReactNode; 7 | suffix?: ReactNode; 8 | }; 9 | 10 | export const InputBase = string } | undefined = string>({ 11 | name, 12 | value, 13 | onChange, 14 | placeholder, 15 | error, 16 | disabled, 17 | prefix, 18 | suffix, 19 | }: InputBaseProps) => { 20 | let modifier = ""; 21 | if (error) { 22 | modifier = "border-error"; 23 | } else if (disabled) { 24 | modifier = "border-disabled bg-base-300"; 25 | } 26 | 27 | const handleChange = useCallback( 28 | (e: ChangeEvent) => { 29 | onChange(e.target.value as unknown as T); 30 | }, 31 | [onChange], 32 | ); 33 | 34 | return ( 35 |
36 | {prefix} 37 | 46 | {suffix} 47 |
48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /nextjs/components/assets/Spinner.tsx: -------------------------------------------------------------------------------- 1 | export const Spinner = ({ width, height }: { width?: string; height?: string }) => { 2 | return ( 3 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /nextjs/components/blockexplorer/PaginationButton.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/outline"; 2 | 3 | interface PaginationButtonProps { 4 | currentPage: number; 5 | totalItems: number; 6 | setCurrentPage: (page: number) => void; 7 | } 8 | 9 | const ITEMS_PER_PAGE = 20; 10 | 11 | export const PaginationButton = ({ currentPage, totalItems, setCurrentPage }: PaginationButtonProps) => { 12 | const isPrevButtonDisabled = currentPage === 0; 13 | const isNextButtonDisabled = currentPage + 1 >= Math.ceil(totalItems / ITEMS_PER_PAGE); 14 | 15 | const prevButtonClass = isPrevButtonDisabled ? "bg-gray-200 cursor-default" : "btn btn-primary"; 16 | const nextButtonClass = isNextButtonDisabled ? "bg-gray-200 cursor-default" : "btn btn-primary"; 17 | 18 | if (isNextButtonDisabled && isPrevButtonDisabled) return null; 19 | 20 | return ( 21 |
22 | 29 | Page {currentPage + 1} 30 | 37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /nextjs/components/primitives.ts: -------------------------------------------------------------------------------- 1 | import { tv } from "tailwind-variants"; 2 | 3 | export const title = tv({ 4 | base: "tracking-tight inline font-semibold", 5 | variants: { 6 | color: { 7 | violet: "from-[#FF1CF7] to-[#b249f8]", 8 | yellow: "from-[#FF705B] to-[#FFB457]", 9 | blue: "from-[#5EA2EF] to-[#0072F5]", 10 | cyan: "from-[#00b7fa] to-[#01cfea]", 11 | green: "from-[#6FEE8D] to-[#17c964]", 12 | pink: "from-[#FF72E1] to-[#F54C7A]", 13 | foreground: "dark:from-[#FFFFFF] dark:to-[#4B4B4B]", 14 | }, 15 | size: { 16 | sm: "text-3xl lg:text-4xl", 17 | md: "text-[2.3rem] lg:text-5xl leading-9", 18 | lg: "text-4xl lg:text-6xl", 19 | }, 20 | fullWidth: { 21 | true: "w-full block", 22 | }, 23 | }, 24 | defaultVariants: { 25 | size: "md", 26 | }, 27 | compoundVariants: [ 28 | { 29 | color: [ 30 | "violet", 31 | "yellow", 32 | "blue", 33 | "cyan", 34 | "green", 35 | "pink", 36 | "foreground", 37 | ], 38 | class: "bg-clip-text text-transparent bg-gradient-to-b", 39 | }, 40 | ], 41 | }); 42 | 43 | export const subtitle = tv({ 44 | base: "w-full md:w-1/2 my-2 text-lg lg:text-xl text-default-600 block max-w-full", 45 | variants: { 46 | fullWidth: { 47 | true: "!w-full", 48 | }, 49 | }, 50 | defaultVariants: { 51 | fullWidth: true 52 | } 53 | }); -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useScaffoldEventSubscriber.ts: -------------------------------------------------------------------------------- 1 | import { Abi, ExtractAbiEventNames } from "abitype"; 2 | import { Log } from "viem"; 3 | import { useContractEvent } from "wagmi"; 4 | import { addIndexedArgsToEvent, useDeployedContractInfo } from "~~/hooks/scaffold-eth"; 5 | import { getTargetNetwork } from "~~/utils/scaffold-eth"; 6 | import { ContractAbi, ContractName, UseScaffoldEventConfig } from "~~/utils/scaffold-eth/contract"; 7 | 8 | /** 9 | * @dev wrapper for wagmi's useContractEvent 10 | * @param config - The config settings 11 | * @param config.contractName - deployed contract name 12 | * @param config.eventName - name of the event to listen for 13 | * @param config.listener - the callback that receives events. If only interested in 1 event, call `unwatch` inside of the listener 14 | */ 15 | export const useScaffoldEventSubscriber = < 16 | TContractName extends ContractName, 17 | TEventName extends ExtractAbiEventNames>, 18 | >({ 19 | contractName, 20 | eventName, 21 | listener, 22 | }: UseScaffoldEventConfig) => { 23 | const { data: deployedContractData } = useDeployedContractInfo(contractName); 24 | 25 | const addInexedArgsToLogs = (logs: Log[]) => logs.map(addIndexedArgsToEvent); 26 | const listenerWithIndexedArgs = (logs: Log[]) => 27 | listener(addInexedArgsToLogs(logs) as Parameters[0]); 28 | 29 | return useContractEvent({ 30 | address: deployedContractData?.address, 31 | abi: deployedContractData?.abi as Abi, 32 | chainId: getTargetNetwork().id, 33 | listener: listenerWithIndexedArgs, 34 | eventName, 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/ContractInput.tsx: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction } from "react"; 2 | import { AbiParameter } from "abitype"; 3 | import { 4 | AddressInput, 5 | Bytes32Input, 6 | BytesInput, 7 | InputBase, 8 | IntegerInput, 9 | IntegerVariant, 10 | } from "~~/components/scaffold-eth"; 11 | 12 | type ContractInputProps = { 13 | setForm: Dispatch>>; 14 | form: Record | undefined; 15 | stateObjectKey: string; 16 | paramType: AbiParameter; 17 | }; 18 | 19 | /** 20 | * Generic Input component to handle input's based on their function param type 21 | */ 22 | export const ContractInput = ({ setForm, form, stateObjectKey, paramType }: ContractInputProps) => { 23 | const inputProps = { 24 | name: stateObjectKey, 25 | value: form?.[stateObjectKey], 26 | placeholder: paramType.name ? `${paramType.type} ${paramType.name}` : paramType.type, 27 | onChange: (value: any) => { 28 | setForm(form => ({ ...form, [stateObjectKey]: value })); 29 | }, 30 | }; 31 | 32 | if (paramType.type === "address") { 33 | return ; 34 | } else if (paramType.type === "bytes32") { 35 | return ; 36 | } else if (paramType.type === "bytes") { 37 | return ; 38 | } else if (paramType.type === "string") { 39 | return ; 40 | } else if (paramType.type.includes("int") && !paramType.type.includes("[")) { 41 | return ; 42 | } 43 | 44 | return ; 45 | }; 46 | -------------------------------------------------------------------------------- /nextjs/components/blockexplorer/SearchBar.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useRouter } from "next/router"; 3 | import { isAddress, isHex } from "viem"; 4 | import { usePublicClient } from "wagmi"; 5 | import { hardhat } from "wagmi/chains"; 6 | 7 | export const SearchBar = () => { 8 | const [searchInput, setSearchInput] = useState(""); 9 | const router = useRouter(); 10 | 11 | const client = usePublicClient({ chainId: hardhat.id }); 12 | 13 | const handleSearch = async (event: React.FormEvent) => { 14 | event.preventDefault(); 15 | if (isHex(searchInput)) { 16 | try { 17 | const tx = await client.getTransaction({ hash: searchInput }); 18 | if (tx) { 19 | router.push(`/blockexplorer/transaction/${searchInput}`); 20 | return; 21 | } 22 | } catch (error) { 23 | console.error("Failed to fetch transaction:", error); 24 | } 25 | } 26 | 27 | if (isAddress(searchInput)) { 28 | router.push(`/blockexplorer/address/${searchInput}`); 29 | return; 30 | } 31 | }; 32 | 33 | return ( 34 |
35 | setSearchInput(e.target.value)} 41 | /> 42 | 45 |
46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useScaffoldContract.ts: -------------------------------------------------------------------------------- 1 | import { Account, Address, Transport, getContract } from "viem"; 2 | import { Chain, PublicClient, usePublicClient } from "wagmi"; 3 | import { GetWalletClientResult } from "wagmi/actions"; 4 | import { useDeployedContractInfo } from "~~/hooks/scaffold-eth"; 5 | import { Contract, ContractName } from "~~/utils/scaffold-eth/contract"; 6 | 7 | /** 8 | * Gets a deployed contract by contract name and returns a contract instance 9 | * @param config - The config settings 10 | * @param config.contractName - Deployed contract name 11 | * @param config.walletClient - An viem wallet client instance (optional) 12 | */ 13 | export const useScaffoldContract = < 14 | TContractName extends ContractName, 15 | TWalletClient extends Exclude | undefined, 16 | >({ 17 | contractName, 18 | walletClient, 19 | }: { 20 | contractName: TContractName; 21 | walletClient?: TWalletClient | null; 22 | }) => { 23 | const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo(contractName); 24 | const publicClient = usePublicClient(); 25 | 26 | let contract = undefined; 27 | if (deployedContractData) { 28 | contract = getContract< 29 | Transport, 30 | Address, 31 | Contract["abi"], 32 | Chain, 33 | Account, 34 | PublicClient, 35 | TWalletClient 36 | >({ 37 | address: deployedContractData.address, 38 | abi: deployedContractData.abi as Contract["abi"], 39 | walletClient: walletClient ? walletClient : undefined, 40 | publicClient, 41 | }); 42 | } 43 | 44 | return { 45 | data: contract, 46 | isLoading: deployedContractLoading, 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /nextjs/components/MetaHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Head from "next/head"; 3 | 4 | type MetaHeaderProps = { 5 | title?: string; 6 | description?: string; 7 | image?: string; 8 | twitterCard?: string; 9 | children?: React.ReactNode; 10 | }; 11 | 12 | // Images must have an absolute path to work properly on Twitter. 13 | // We try to get it dynamically from Vercel, but we default to relative path. 14 | const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}/` : "/"; 15 | 16 | export const MetaHeader = ({ 17 | title = "scaffold-hook v4 App", 18 | description = "Built with 🏗 scaffold-hook", 19 | image = "thumbnail.jpg", 20 | twitterCard = "summary_large_image", 21 | children, 22 | }: MetaHeaderProps) => { 23 | const imageUrl = baseUrl + image; 24 | 25 | return ( 26 | 27 | {title && ( 28 | <> 29 | {title} 30 | 31 | 32 | 33 | )} 34 | {description && ( 35 | <> 36 | 37 | 38 | 39 | 40 | )} 41 | {image && ( 42 | <> 43 | 44 | 45 | 46 | )} 47 | {twitterCard && } 48 | 49 | {children} 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /nextjs/components/blockexplorer/AddressStorageTab.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { createPublicClient, http, toHex } from "viem"; 3 | import { hardhat } from "wagmi/chains"; 4 | 5 | const publicClient = createPublicClient({ 6 | chain: hardhat, 7 | transport: http(), 8 | }); 9 | 10 | export const AddressStorageTab = ({ address }: { address: string }) => { 11 | const [storage, setStorage] = useState([]); 12 | 13 | useEffect(() => { 14 | const fetchStorage = async () => { 15 | try { 16 | const storageData = []; 17 | let idx = 0; 18 | 19 | while (true) { 20 | const storageAtPosition = await publicClient.getStorageAt({ 21 | address: address, 22 | slot: toHex(idx), 23 | }); 24 | 25 | if (storageAtPosition === "0x" + "0".repeat(64)) break; 26 | 27 | if (storageAtPosition) { 28 | storageData.push(storageAtPosition); 29 | } 30 | 31 | idx++; 32 | } 33 | setStorage(storageData); 34 | } catch (error) { 35 | console.error("Failed to fetch storage:", error); 36 | } 37 | }; 38 | 39 | fetchStorage(); 40 | }, [address]); 41 | 42 | return ( 43 |
44 | {storage.length > 0 ? ( 45 |
46 |
47 |             {storage.map((data, i) => (
48 |               
49 | Storage Slot {i}: {data} 50 |
51 | ))} 52 |
53 |
54 | ) : ( 55 |
This contract does not have any variables.
56 | )} 57 |
58 | ); 59 | }; 60 | -------------------------------------------------------------------------------- /nextjs/scaffold.config.ts: -------------------------------------------------------------------------------- 1 | import * as chains from "wagmi/chains"; 2 | 3 | export type ScaffoldConfig = { 4 | targetNetwork: chains.Chain; 5 | pollingInterval: number; 6 | alchemyApiKey: string; 7 | walletConnectProjectId: string; 8 | onlyLocalBurnerWallet: boolean; 9 | walletAutoConnect: boolean; 10 | }; 11 | 12 | const scaffoldConfig = { 13 | // The network where your DApp lives in 14 | targetNetwork: chains.foundry, 15 | 16 | // The interval at which your front-end polls the RPC servers for new data 17 | // it has no effect on the local network 18 | pollingInterval: 30000, 19 | 20 | // This is ours Alchemy's default API key. 21 | // You can get your own at https://dashboard.alchemyapi.io 22 | // It's recommended to store it in an env variable: 23 | // .env.local for local testing, and in the Vercel/system env config for live apps. 24 | alchemyApiKey: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY || "oKxs-03sij-U_N0iOlrSsZFr29-IqbuF", 25 | 26 | // This is ours WalletConnect's default project ID. 27 | // You can get your own at https://cloud.walletconnect.com 28 | // It's recommended to store it in an env variable: 29 | // .env.local for local testing, and in the Vercel/system env config for live apps. 30 | walletConnectProjectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID || "3a8170812b534d0ff9d794f19a901d64", 31 | 32 | // Only show the Burner Wallet when running on hardhat network 33 | onlyLocalBurnerWallet: true, 34 | 35 | /** 36 | * Auto connect: 37 | * 1. If the user was connected into a wallet before, on page reload reconnect automatically 38 | * 2. If user is not connected to any wallet: On reload, connect to burner wallet if burnerWallet.enabled is true && burnerWallet.onlyLocal is false 39 | */ 40 | walletAutoConnect: true, 41 | } satisfies ScaffoldConfig; 42 | 43 | export default scaffoldConfig; 44 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Balance.tsx: -------------------------------------------------------------------------------- 1 | import { useBalance } from "wagmi"; 2 | 3 | type TBalanceProps = { 4 | address?: string; 5 | className?: string; 6 | }; 7 | 8 | /** 9 | * Display (ETH & USD) balance of an ETH address. 10 | */ 11 | export const Balance = ({ address, className = "" }: TBalanceProps) => { 12 | const { 13 | data: balance, 14 | isError, 15 | isLoading, 16 | } = useBalance({ 17 | address, 18 | }); 19 | 20 | // disable USD conversion for now 21 | const isEthBalance = true; 22 | 23 | if (!address || isLoading || balance === null) { 24 | return ( 25 |
26 |
27 |
28 |
29 |
30 |
31 | ); 32 | } 33 | 34 | if (isError) { 35 | return ( 36 |
37 |
Error
38 |
39 | ); 40 | } 41 | 42 | return ( 43 | 61 | ); 62 | }; 63 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useScaffoldContractRead.ts: -------------------------------------------------------------------------------- 1 | import type { ExtractAbiFunctionNames } from "abitype"; 2 | import { useContractRead } from "wagmi"; 3 | import { useDeployedContractInfo } from "~~/hooks/scaffold-eth"; 4 | import { getTargetNetwork } from "~~/utils/scaffold-eth"; 5 | import { 6 | AbiFunctionReturnType, 7 | ContractAbi, 8 | ContractName, 9 | UseScaffoldReadConfig, 10 | } from "~~/utils/scaffold-eth/contract"; 11 | 12 | /** 13 | * @dev wrapper for wagmi's useContractRead hook which loads in deployed contract contract abi, address automatically 14 | * @param config - The config settings, including extra wagmi configuration 15 | * @param config.contractName - deployed contract name 16 | * @param config.functionName - name of the function to be called 17 | * @param config.args - args to be passed to the function call 18 | */ 19 | export const useScaffoldContractRead = < 20 | TContractName extends ContractName, 21 | TFunctionName extends ExtractAbiFunctionNames, "pure" | "view">, 22 | >({ 23 | contractName, 24 | functionName, 25 | args, 26 | ...readConfig 27 | }: UseScaffoldReadConfig) => { 28 | const { data: deployedContract } = useDeployedContractInfo(contractName); 29 | 30 | return useContractRead({ 31 | chainId: getTargetNetwork().id, 32 | functionName, 33 | address: deployedContract?.address, 34 | abi: deployedContract?.abi, 35 | watch: true, 36 | args, 37 | enabled: !Array.isArray(args) || !args.some(arg => arg === undefined), 38 | ...(readConfig as any), 39 | }) as Omit, "data" | "refetch"> & { 40 | data: AbiFunctionReturnType | undefined; 41 | refetch: (options?: { 42 | throwOnError: boolean; 43 | cancelRefetch: boolean; 44 | }) => Promise>; 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useIsMounted } from "usehooks-ts"; 3 | import { usePublicClient } from "wagmi"; 4 | import scaffoldConfig from "~~/scaffold.config"; 5 | import { Contract, ContractCodeStatus, ContractName, contracts } from "~~/utils/scaffold-eth/contract"; 6 | 7 | /** 8 | * Gets the matching contract info from the contracts file generated by `yarn deploy` 9 | * @param contractName - name of deployed contract 10 | */ 11 | export const useDeployedContractInfo = (contractName: TContractName) => { 12 | const isMounted = useIsMounted(); 13 | const deployedContract = contracts?.[scaffoldConfig.targetNetwork.id]?.[0]?.contracts?.[ 14 | contractName as ContractName 15 | ] as Contract; 16 | const [status, setStatus] = useState(ContractCodeStatus.LOADING); 17 | const publicClient = usePublicClient({ chainId: scaffoldConfig.targetNetwork.id }); 18 | 19 | useEffect(() => { 20 | const checkContractDeployment = async () => { 21 | if (!deployedContract) { 22 | setStatus(ContractCodeStatus.NOT_FOUND); 23 | return; 24 | } 25 | const code = await publicClient.getBytecode({ 26 | address: deployedContract.address, 27 | }); 28 | 29 | if (!isMounted()) { 30 | return; 31 | } 32 | // If contract code is `0x` => no contract deployed on that address 33 | if (code === "0x") { 34 | setStatus(ContractCodeStatus.NOT_FOUND); 35 | return; 36 | } 37 | setStatus(ContractCodeStatus.DEPLOYED); 38 | }; 39 | 40 | checkContractDeployment(); 41 | }, [isMounted, contractName, deployedContract, publicClient]); 42 | 43 | return { 44 | data: status === ContractCodeStatus.DEPLOYED ? deployedContract : undefined, 45 | isLoading: status === ContractCodeStatus.LOADING, 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Input/IntegerInput.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | import { CommonInputProps, InputBase, IntegerVariant, isValidInteger } from "~~/components/scaffold-eth"; 3 | 4 | type IntegerInputProps = CommonInputProps & { 5 | variant?: IntegerVariant; 6 | }; 7 | 8 | export const IntegerInput = ({ 9 | value, 10 | onChange, 11 | name, 12 | placeholder, 13 | disabled, 14 | variant = IntegerVariant.UINT256, 15 | }: IntegerInputProps) => { 16 | const [inputError, setInputError] = useState(false); 17 | const multiplyBy1e18 = useCallback(() => { 18 | if (!value) { 19 | return; 20 | } 21 | if (typeof value === "bigint") { 22 | return onChange(value * 10n ** 18n); 23 | } 24 | return onChange(BigInt(Math.round(Number(value) * 10 ** 18))); 25 | }, [onChange, value]); 26 | 27 | useEffect(() => { 28 | if (isValidInteger(variant, value, false)) { 29 | setInputError(false); 30 | } else { 31 | setInputError(true); 32 | } 33 | }, [value, variant]); 34 | 35 | return ( 36 | 49 | 56 | 57 | ) 58 | } 59 | /> 60 | ); 61 | }; 62 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/utilsDisplay.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from "react"; 2 | import { TransactionBase, TransactionReceipt, formatEther } from "viem"; 3 | import { Address } from "~~/components/scaffold-eth"; 4 | import { replacer } from "~~/utils/scaffold-eth/common"; 5 | 6 | type DisplayContent = 7 | | string 8 | | number 9 | | bigint 10 | | Record 11 | | TransactionBase 12 | | TransactionReceipt 13 | | undefined 14 | | unknown; 15 | 16 | export const displayTxResult = ( 17 | displayContent: DisplayContent | DisplayContent[], 18 | asText = false, 19 | ): string | ReactElement | number => { 20 | if (displayContent == null) { 21 | return ""; 22 | } 23 | 24 | if (typeof displayContent === "bigint") { 25 | try { 26 | const asNumber = Number(displayContent); 27 | if (asNumber <= Number.MAX_SAFE_INTEGER && asNumber >= Number.MIN_SAFE_INTEGER) { 28 | return asNumber; 29 | } else { 30 | return "Ξ" + formatEther(displayContent); 31 | } 32 | } catch (e) { 33 | return "Ξ" + formatEther(displayContent); 34 | } 35 | } 36 | 37 | if (typeof displayContent === "string" && displayContent.indexOf("0x") === 0 && displayContent.length === 42) { 38 | return asText ? displayContent :
; 39 | } 40 | 41 | if (Array.isArray(displayContent)) { 42 | const mostReadable = (v: DisplayContent) => 43 | ["number", "boolean"].includes(typeof v) ? v : displayTxResultAsText(v); 44 | const displayable = JSON.stringify(displayContent.map(mostReadable), replacer); 45 | 46 | return asText ? ( 47 | displayable 48 | ) : ( 49 | {displayable.replaceAll(",", ",\n")} 50 | ); 51 | } 52 | 53 | return JSON.stringify(displayContent, replacer, 2); 54 | }; 55 | 56 | const displayTxResultAsText = (displayContent: DisplayContent) => displayTxResult(displayContent, true); 57 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/ContractEvents.tsx: -------------------------------------------------------------------------------- 1 | import { Address } from "../Address"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | 4 | type ContractEventEntryProps = { 5 | name: string; 6 | args: Record; 7 | }; 8 | 9 | type ContractEventProps = { 10 | logs: any[]; 11 | }; 12 | 13 | const ContractEventEntry = ({ name, args }: ContractEventEntryProps) => { 14 | const keys = Object.keys(args); 15 | 16 | console.log("Event(s) emitted:", args); 17 | 18 | return ( 19 |
20 |
{name}
21 | {keys.map(key => ( 22 |
23 |
36 | {key} 37 |
38 | {typeof args[key] === "string" && args[key].includes("0x") && args[key].length === 42 ? ( 39 |
40 | ) : ( 41 |
51 | {args[key].toString()} 52 |
53 | )} 54 |
55 | ))} 56 |
57 | ); 58 | }; 59 | 60 | export const EventsEmitted = ({ logs = [] }: ContractEventProps) => { 61 | if (logs.length === 0) return <>No events emitted!; 62 | 63 | return ( 64 | <> 65 | {logs.map((log, index) => ( 66 | <> 67 | {" "} 68 | 69 | {index !== logs.length - 1 &&
} 70 | 71 | ))} 72 | 73 | ); 74 | }; 75 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/DisplayVariable.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { Abi, AbiFunction } from "abitype"; 3 | import { Address } from "viem"; 4 | import { useContractRead } from "wagmi"; 5 | import { ArrowPathIcon } from "@heroicons/react/24/outline"; 6 | import { displayTxResult } from "~~/components/scaffold-eth"; 7 | import { useAnimationConfig } from "~~/hooks/scaffold-eth"; 8 | import { notification } from "~~/utils/scaffold-eth"; 9 | 10 | type DisplayVariableProps = { 11 | contractAddress: Address; 12 | abiFunction: AbiFunction; 13 | refreshDisplayVariables: boolean; 14 | }; 15 | 16 | export const DisplayVariable = ({ contractAddress, abiFunction, refreshDisplayVariables }: DisplayVariableProps) => { 17 | const { 18 | data: result, 19 | isFetching, 20 | refetch, 21 | } = useContractRead({ 22 | address: contractAddress, 23 | functionName: abiFunction.name, 24 | abi: [abiFunction] as Abi, 25 | onError: error => { 26 | notification.error(error.message); 27 | }, 28 | }); 29 | 30 | const { showAnimation } = useAnimationConfig(result); 31 | 32 | useEffect(() => { 33 | refetch(); 34 | }, [refetch, refreshDisplayVariables]); 35 | 36 | return ( 37 |
38 |
39 |

{abiFunction.name}

40 | 47 |
48 |
49 |
50 |
55 | {displayTxResult(result)} 56 |
57 |
58 |
59 |
60 | ); 61 | }; 62 | -------------------------------------------------------------------------------- /nextjs/components/swap-ui/assets/HareIcon.tsx: -------------------------------------------------------------------------------- 1 | export const HareIcon = ({ className }: { className: string }) => { 2 | return ( 3 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@se-2/nextjs", 3 | "private": true, 4 | "version": "0.1.0", 5 | "scripts": { 6 | "dev": "next dev", 7 | "start": "next dev", 8 | "build": "next build", 9 | "serve": "next start", 10 | "lint": "next lint", 11 | "format": "prettier --write . '!(node_modules|.next|contracts)/**/*'", 12 | "check-types": "tsc --noEmit --incremental", 13 | "vercel": "vercel", 14 | "vercel:yolo": "vercel --build-env NEXT_PUBLIC_IGNORE_BUILD_ERROR=true", 15 | "wagmi": "cd ../ && forge build --force && cd nextjs/ && wagmi generate" 16 | }, 17 | "dependencies": { 18 | "@ethersproject/providers": "^5.7.2", 19 | "@heroicons/react": "^2.0.11", 20 | "@nextui-org/react": "^2.1.13", 21 | "@rainbow-me/rainbowkit": "1.0.8", 22 | "@uniswap/sdk-core": "^4.0.1", 23 | "@uniswap/v2-sdk": "^3.0.1", 24 | "blo": "^1.0.1", 25 | "daisyui": "^3.5.1", 26 | "framer-motion": "^10.16.4", 27 | "next": "^13.3", 28 | "nextjs-progressbar": "^0.0.16", 29 | "qrcode.react": "^3.1.0", 30 | "react": "^18.2.0", 31 | "react-copy-to-clipboard": "^5.1.0", 32 | "react-dom": "^18.2.0", 33 | "react-fast-marquee": "^1.3.5", 34 | "react-github-btn": "^1.4.0", 35 | "react-hot-toast": "^2.4.0", 36 | "react-icons": "^4.11.0", 37 | "react-syntax-highlighter": "^15.6.1", 38 | "react-tooltip": "^5.21.6", 39 | "react-type-animation": "^3.2.0", 40 | "use-debounce": "^8.0.4", 41 | "usehooks-ts": "^2.7.2", 42 | "viem": "^1.6.7", 43 | "wagmi": "1.3.10", 44 | "zustand": "^4.1.2" 45 | }, 46 | "devDependencies": { 47 | "@trivago/prettier-plugin-sort-imports": "^4.1.1", 48 | "@types/node": "20.8.5", 49 | "@types/react": "18.2.28", 50 | "@types/react-copy-to-clipboard": "^5.0.4", 51 | "@types/react-syntax-highlighter": "^15.5.13", 52 | "@typescript-eslint/eslint-plugin": "^5.39.0", 53 | "@wagmi/cli": "^1.5.2", 54 | "autoprefixer": "^10.4.12", 55 | "esbuild": "^0.19.5", 56 | "eslint": "^8.15.0", 57 | "eslint-config-next": "^13.1.6", 58 | "eslint-config-prettier": "^8.5.0", 59 | "eslint-plugin-prettier": "^4.2.1", 60 | "postcss": "^8.4.16", 61 | "prettier": "^2.8.4", 62 | "tailwindcss": "^3.3.3", 63 | "typescript": "^5.1.6", 64 | "vercel": "^28.15.1" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /nextjs/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import type { AppProps } from "next/app"; 3 | import { NextUIProvider } from "@nextui-org/react"; 4 | import { RainbowKitProvider, darkTheme, lightTheme } from "@rainbow-me/rainbowkit"; 5 | import "@rainbow-me/rainbowkit/styles.css"; 6 | import NextNProgress from "nextjs-progressbar"; 7 | import { Toaster } from "react-hot-toast"; 8 | import { useDarkMode } from "usehooks-ts"; 9 | import { WagmiConfig } from "wagmi"; 10 | import { Footer } from "~~/components/Footer"; 11 | import { Header } from "~~/components/Header"; 12 | import { BlockieAvatar } from "~~/components/scaffold-eth"; 13 | import { useNativeCurrencyPrice } from "~~/hooks/scaffold-eth"; 14 | import { useGlobalState } from "~~/services/store/store"; 15 | import { wagmiConfig } from "~~/services/web3/wagmiConfig"; 16 | import { appChains } from "~~/services/web3/wagmiConnectors"; 17 | import "~~/styles/globals.css"; 18 | 19 | const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => { 20 | const price = useNativeCurrencyPrice(); 21 | const setNativeCurrencyPrice = useGlobalState(state => state.setNativeCurrencyPrice); 22 | // This variable is required for initial client side rendering of correct theme for RainbowKit 23 | const [isDarkTheme, setIsDarkTheme] = useState(true); 24 | const { isDarkMode } = useDarkMode(); 25 | 26 | useEffect(() => { 27 | if (price > 0) { 28 | setNativeCurrencyPrice(price); 29 | } 30 | }, [setNativeCurrencyPrice, price]); 31 | 32 | useEffect(() => { 33 | setIsDarkTheme(isDarkMode); 34 | }, [isDarkMode]); 35 | 36 | return ( 37 | 38 | 39 | 44 |
45 | {/*
*/} 46 | 47 |
48 | 49 |
50 |
51 | 52 | {/*
*/} 53 |
54 | 55 |
56 |
57 | ); 58 | }; 59 | 60 | export default ScaffoldEthApp; 61 | -------------------------------------------------------------------------------- /nextjs/utils/scaffold-eth/decodeTxData.ts: -------------------------------------------------------------------------------- 1 | import { TransactionWithFunction } from "./block"; 2 | import { GenericContractsDeclaration } from "./contract"; 3 | import { Abi, decodeFunctionData, getAbiItem } from "viem"; 4 | import { hardhat } from "wagmi/chains"; 5 | import contractData from "~~/generated/deployedContracts"; 6 | 7 | type ContractsInterfaces = Record; 8 | type TransactionType = TransactionWithFunction | null; 9 | 10 | const deployedContracts = contractData as GenericContractsDeclaration | null; 11 | const chainMetaData = deployedContracts?.[hardhat.id]?.[0]; 12 | const interfaces = chainMetaData 13 | ? Object.entries(chainMetaData.contracts).reduce((finalInterfacesObj, [contractName, contract]) => { 14 | finalInterfacesObj[contractName] = contract.abi; 15 | return finalInterfacesObj; 16 | }, {} as ContractsInterfaces) 17 | : {}; 18 | 19 | export const decodeTransactionData = (tx: TransactionWithFunction) => { 20 | if (tx.input.length >= 10 && !tx.input.startsWith("0x60e06040")) { 21 | for (const [, contractAbi] of Object.entries(interfaces)) { 22 | try { 23 | const { functionName, args } = decodeFunctionData({ 24 | abi: contractAbi, 25 | data: tx.input, 26 | }); 27 | tx.functionName = functionName; 28 | tx.functionArgs = args as any[]; 29 | tx.functionArgNames = getAbiItem({ abi: contractAbi, name: functionName }).inputs.map( 30 | (input: any) => input.name, 31 | ); 32 | tx.functionArgTypes = getAbiItem({ abi: contractAbi, name: functionName }).inputs.map( 33 | (input: any) => input.type, 34 | ); 35 | 36 | break; 37 | } catch (e) { 38 | console.error(`Parsing failed: ${e}`); 39 | } 40 | } 41 | } 42 | return tx; 43 | }; 44 | 45 | export const getFunctionDetails = (transaction: TransactionType) => { 46 | if ( 47 | transaction && 48 | transaction.functionName && 49 | transaction.functionArgNames && 50 | transaction.functionArgTypes && 51 | transaction.functionArgs 52 | ) { 53 | const details = transaction.functionArgNames.map( 54 | (name, i) => `${transaction.functionArgTypes?.[i] || ""} ${name} = ${transaction.functionArgs?.[i] ?? ""}`, 55 | ); 56 | return `${transaction.functionName}(${details.join(", ")})`; 57 | } 58 | return ""; 59 | }; 60 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/NetworkSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Image from "next/image"; 3 | import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger } from "@nextui-org/react"; 4 | import { Abi, createWalletClient, http, parseEther } from "viem"; 5 | import { useNetwork, useSwitchNetwork } from "wagmi"; 6 | import { enabledChains } from "~~/services/web3/wagmiConnectors"; 7 | import { notification } from "~~/utils/scaffold-eth"; 8 | 9 | export const NetworkSwitcher = () => { 10 | const { chain: chainCurrent } = useNetwork(); 11 | console.log("🚀 ~ file: NetworkSwitcher.tsx:10 ~ NetworkSwitcher ~ chain:", chainCurrent); 12 | const { chains, error, isLoading, pendingChainId, switchNetwork } = useSwitchNetwork({ 13 | throwForSwitchChainNotSupported: true, 14 | onError(error) { 15 | console.log("Error", error); 16 | notification.error( 17 |
18 |
Error switching networks
19 |
{error.message}
20 |
, 21 | ); 22 | }, 23 | 24 | onSuccess(data) { 25 | console.log("Success", data); 26 | }, 27 | }); 28 | console.log("🚀 ~ file: NetworkSwitcher.tsx:20 ~ NetworkSwitcher ~ chains:", chains); 29 | 30 | const handleNetworkSwitch = async networkId => { 31 | switchNetwork?.(networkId); 32 | }; 33 | 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | {enabledChains.map((chain: Chain) => { 41 | return ( 42 | 54 | } 55 | onClick={() => handleNetworkSwitch(chain.id)} 56 | > 57 | {chain.name} 58 | 59 | ); 60 | })} 61 | 62 | 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /nextjs/components/ui/timeline.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useEffect, useRef, useState } from "react"; 4 | import { motion, useScroll, useTransform } from "framer-motion"; 5 | 6 | interface TimelineEntry { 7 | title: string; 8 | content: React.ReactNode; 9 | } 10 | 11 | export const Timeline = ({ data }: { data: TimelineEntry[] }) => { 12 | const ref = useRef(null); 13 | const containerRef = useRef(null); 14 | const [height, setHeight] = useState(0); 15 | 16 | useEffect(() => { 17 | if (ref.current) { 18 | const rect = ref.current.getBoundingClientRect(); 19 | setHeight(rect.height); 20 | } 21 | }, [ref]); 22 | 23 | const { scrollYProgress } = useScroll({ 24 | target: containerRef, 25 | offset: ["start 10%", "end 50%"], 26 | }); 27 | 28 | const heightTransform = useTransform(scrollYProgress, [0, 1], [0, height]); 29 | const opacityTransform = useTransform(scrollYProgress, [0, 0.1], [0, 1]); 30 | 31 | return ( 32 |
33 |
34 | {data.map((item, index) => ( 35 |
36 |
37 |
38 |
39 |
40 |

{item.title}

41 |
42 | 43 |
{item.content}
44 |
45 | ))} 46 |
52 | 59 |
60 |
61 |
62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /nextjs/utils/scaffold-eth/fetchPriceFromUniswap.ts: -------------------------------------------------------------------------------- 1 | import { CurrencyAmount, Token } from "@uniswap/sdk-core"; 2 | import { Pair, Route } from "@uniswap/v2-sdk"; 3 | import { createPublicClient, http, parseAbi } from "viem"; 4 | import { mainnet } from "wagmi"; 5 | import scaffoldConfig from "~~/scaffold.config"; 6 | import { getTargetNetwork } from "~~/utils/scaffold-eth"; 7 | 8 | const publicClient = createPublicClient({ 9 | chain: mainnet, 10 | transport: http(`${mainnet.rpcUrls.alchemy.http[0]}/${scaffoldConfig.alchemyApiKey}`), 11 | }); 12 | 13 | const ABI = parseAbi([ 14 | "function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)", 15 | "function token0() external view returns (address)", 16 | "function token1() external view returns (address)", 17 | ]); 18 | 19 | export const fetchPriceFromUniswap = async (): Promise => { 20 | const configuredNetwork = getTargetNetwork(); 21 | if ( 22 | configuredNetwork.nativeCurrency.symbol !== "ETH" && 23 | configuredNetwork.nativeCurrency.symbol !== "SEP" && 24 | !configuredNetwork.nativeCurrencyTokenAddress 25 | ) { 26 | return 0; 27 | } 28 | try { 29 | const DAI = new Token(1, "0x6B175474E89094C44Da98b954EedeAC495271d0F", 18); 30 | const TOKEN = new Token( 31 | 1, 32 | configuredNetwork.nativeCurrencyTokenAddress || "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 33 | 18, 34 | ); 35 | const pairAddress = Pair.getAddress(TOKEN, DAI); 36 | 37 | const wagmiConfig = { 38 | address: pairAddress, 39 | abi: ABI, 40 | }; 41 | 42 | const reserves = await publicClient.readContract({ 43 | ...wagmiConfig, 44 | functionName: "getReserves", 45 | }); 46 | 47 | const token0Address = await publicClient.readContract({ 48 | ...wagmiConfig, 49 | functionName: "token0", 50 | }); 51 | 52 | const token1Address = await publicClient.readContract({ 53 | ...wagmiConfig, 54 | functionName: "token1", 55 | }); 56 | const token0 = [TOKEN, DAI].find(token => token.address === token0Address) as Token; 57 | const token1 = [TOKEN, DAI].find(token => token.address === token1Address) as Token; 58 | const pair = new Pair( 59 | CurrencyAmount.fromRawAmount(token0, reserves[0].toString()), 60 | CurrencyAmount.fromRawAmount(token1, reserves[1].toString()), 61 | ); 62 | const route = new Route([pair], TOKEN, DAI); 63 | const price = parseFloat(route.midPrice.toSignificant(6)); 64 | return price; 65 | } catch (error) { 66 | console.error("useNativeCurrencyPrice - Error fetching ETH price from Uniswap: ", error); 67 | return 0; 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /nextjs/pages/blockexplorer/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import type { NextPage } from "next"; 3 | import { hardhat } from "wagmi/chains"; 4 | import { PaginationButton } from "~~/components/blockexplorer/PaginationButton"; 5 | import { SearchBar } from "~~/components/blockexplorer/SearchBar"; 6 | import { TransactionsTable } from "~~/components/blockexplorer/TransactionsTable"; 7 | import { useFetchBlocks } from "~~/hooks/scaffold-eth"; 8 | import { getTargetNetwork, notification } from "~~/utils/scaffold-eth"; 9 | 10 | const Blockexplorer: NextPage = () => { 11 | const { blocks, transactionReceipts, currentPage, totalBlocks, setCurrentPage, error } = useFetchBlocks(); 12 | 13 | useEffect(() => { 14 | if (getTargetNetwork().id === hardhat.id && error) { 15 | notification.error( 16 | <> 17 |

Cannot connect to local provider

18 |

19 | - Did you forget to run yarn chain ? 20 |

21 |

22 | - Or you can change targetNetwork in{" "} 23 | scaffold.config.ts 24 |

25 | , 26 | ); 27 | } 28 | 29 | if (getTargetNetwork().id !== hardhat.id) { 30 | notification.error( 31 | <> 32 |

33 | targeNetwork is not localhost 34 |

35 |

36 | - You are on {getTargetNetwork().name} .This 37 | block explorer is only for localhost. 38 |

39 |

40 | - You can use{" "} 41 | 42 | {getTargetNetwork().blockExplorers?.default.name} 43 | {" "} 44 | instead 45 |

46 | , 47 | ); 48 | } 49 | }, [error]); 50 | 51 | return ( 52 |
53 | 54 | 55 | 56 |
57 | ); 58 | }; 59 | 60 | export default Blockexplorer; 61 | -------------------------------------------------------------------------------- /nextjs/services/web3/wagmiConnectors.tsx: -------------------------------------------------------------------------------- 1 | import { connectorsForWallets } from "@rainbow-me/rainbowkit"; 2 | import { 3 | braveWallet, 4 | coinbaseWallet, 5 | ledgerWallet, 6 | metaMaskWallet, 7 | rainbowWallet, 8 | safeWallet, 9 | walletConnectWallet, 10 | } from "@rainbow-me/rainbowkit/wallets"; 11 | import { configureChains } from "wagmi"; 12 | import * as chains from "wagmi/chains"; 13 | import { alchemyProvider } from "wagmi/providers/alchemy"; 14 | import { publicProvider } from "wagmi/providers/public"; 15 | import scaffoldConfig from "~~/scaffold.config"; 16 | import { burnerWalletConfig } from "~~/services/web3/wagmi-burner/burnerWalletConfig"; 17 | import { getTargetNetwork } from "~~/utils/scaffold-eth"; 18 | 19 | const configuredNetwork = getTargetNetwork(); 20 | const { onlyLocalBurnerWallet } = scaffoldConfig; 21 | 22 | // We always want to have mainnet enabled (ENS resolution, ETH price, etc). But only once. 23 | export const enabledChains = [ 24 | chains.foundry, 25 | chains.goerli, 26 | chains.sepolia, 27 | chains.polygonMumbai, 28 | chains.polygonZkEvmTestnet, 29 | chains.arbitrumGoerli, 30 | chains.optimismGoerli, 31 | chains.baseGoerli, 32 | chains.scrollSepolia, 33 | ]; 34 | 35 | /** 36 | * Chains for the app 37 | */ 38 | export const appChains = configureChains( 39 | enabledChains, 40 | [ 41 | alchemyProvider({ 42 | apiKey: scaffoldConfig.alchemyApiKey, 43 | }), 44 | publicProvider(), 45 | ], 46 | { 47 | // We might not need this checkout https://github.com/scaffold-eth/scaffold-eth-2/pull/45#discussion_r1024496359, will test and remove this before merging 48 | stallTimeout: 3_000, 49 | // Sets pollingInterval if using chain's other than local hardhat chain 50 | ...(configuredNetwork.id !== chains.hardhat.id 51 | ? { 52 | pollingInterval: scaffoldConfig.pollingInterval, 53 | } 54 | : {}), 55 | }, 56 | ); 57 | 58 | const walletsOptions = { chains: appChains.chains, projectId: scaffoldConfig.walletConnectProjectId }; 59 | const wallets = [ 60 | metaMaskWallet({ ...walletsOptions, shimDisconnect: true }), 61 | walletConnectWallet(walletsOptions), 62 | ledgerWallet(walletsOptions), 63 | braveWallet(walletsOptions), 64 | coinbaseWallet({ ...walletsOptions, appName: "scaffold-eth-2" }), 65 | rainbowWallet(walletsOptions), 66 | ...(configuredNetwork.id === chains.hardhat.id || !onlyLocalBurnerWallet 67 | ? [burnerWalletConfig({ chains: [appChains.chains[0]] })] 68 | : []), 69 | safeWallet({ ...walletsOptions, debug: false, allowedDomains: [/gnosis-safe.io$/, /app.safe.global$/] }), 70 | ]; 71 | 72 | /** 73 | * wagmi connectors for the wagmi context 74 | */ 75 | export const wagmiConnectors = connectorsForWallets([ 76 | { 77 | groupName: "Supported Wallets", 78 | wallets, 79 | }, 80 | ]); 81 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Abi, AbiFunction } from "abitype"; 3 | import { Address } from "viem"; 4 | import { useContractRead } from "wagmi"; 5 | import { 6 | ContractInput, 7 | displayTxResult, 8 | getFunctionInputKey, 9 | getInitialFormState, 10 | getParsedContractFunctionArgs, 11 | } from "~~/components/scaffold-eth"; 12 | import { notification } from "~~/utils/scaffold-eth"; 13 | import { Button } from "@nextui-org/react"; 14 | 15 | type TReadOnlyFunctionFormProps = { 16 | contractAddress: Address; 17 | abiFunction: AbiFunction; 18 | }; 19 | 20 | export const ReadOnlyFunctionForm = ({ contractAddress, abiFunction }: TReadOnlyFunctionFormProps) => { 21 | const [form, setForm] = useState>(() => getInitialFormState(abiFunction)); 22 | const [result, setResult] = useState(); 23 | 24 | const { isFetching, refetch } = useContractRead({ 25 | address: contractAddress, 26 | functionName: abiFunction.name, 27 | abi: [abiFunction] as Abi, 28 | args: getParsedContractFunctionArgs(form), 29 | enabled: false, 30 | onError: (error: any) => { 31 | notification.error(error.message); 32 | }, 33 | }); 34 | 35 | const inputElements = abiFunction.inputs.map((input, inputIndex) => { 36 | const key = getFunctionInputKey(abiFunction.name, input, inputIndex); 37 | return ( 38 | { 41 | setResult(undefined); 42 | setForm(updatedFormValue); 43 | }} 44 | form={form} 45 | stateObjectKey={key} 46 | paramType={input} 47 | /> 48 | ); 49 | }); 50 | 51 | return ( 52 |
53 |

{abiFunction.name}

54 | {inputElements} 55 |
56 |
57 | {result !== null && result !== undefined && ( 58 |
59 |

Result:

60 |
{displayTxResult(result)}
61 |
62 | )} 63 |
64 | 77 |
78 |
79 | ); 80 | }; 81 | -------------------------------------------------------------------------------- /nextjs/components/swap-ui/assets/DiamondIcon.tsx: -------------------------------------------------------------------------------- 1 | export const DiamondIcon = ({ className }: { className: string }) => { 2 | return ( 3 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Input/AddressInput.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | import { blo } from "blo"; 3 | import { isAddress } from "viem"; 4 | import { Address } from "viem"; 5 | import { useEnsAddress, useEnsAvatar, useEnsName } from "wagmi"; 6 | import { CommonInputProps, InputBase } from "~~/components/scaffold-eth"; 7 | 8 | // ToDo: move this function to an utility file 9 | const isENS = (address = "") => address.endsWith(".eth") || address.endsWith(".xyz"); 10 | 11 | /** 12 | * Address input with ENS name resolution 13 | */ 14 | export const AddressInput = ({ value, name, placeholder, onChange, disabled }: CommonInputProps
) => { 15 | const { data: ensAddress, isLoading: isEnsAddressLoading } = useEnsAddress({ 16 | name: value, 17 | enabled: isENS(value), 18 | chainId: 1, 19 | cacheTime: 30_000, 20 | }); 21 | 22 | const [enteredEnsName, setEnteredEnsName] = useState(); 23 | const { data: ensName, isLoading: isEnsNameLoading } = useEnsName({ 24 | address: value, 25 | enabled: isAddress(value), 26 | chainId: 1, 27 | cacheTime: 30_000, 28 | }); 29 | 30 | const { data: ensAvatar } = useEnsAvatar({ 31 | name: ensName, 32 | enabled: Boolean(ensName), 33 | chainId: 1, 34 | cacheTime: 30_000, 35 | }); 36 | 37 | // ens => address 38 | useEffect(() => { 39 | if (!ensAddress) return; 40 | 41 | // ENS resolved successfully 42 | setEnteredEnsName(value); 43 | onChange(ensAddress); 44 | }, [ensAddress, onChange, value]); 45 | 46 | const handleChange = useCallback( 47 | (newValue: Address) => { 48 | setEnteredEnsName(undefined); 49 | onChange(newValue); 50 | }, 51 | [onChange], 52 | ); 53 | 54 | return ( 55 | 56 | name={name} 57 | placeholder={placeholder} 58 | error={ensAddress === null} 59 | value={value} 60 | onChange={handleChange} 61 | disabled={isEnsAddressLoading || isEnsNameLoading || disabled} 62 | suffix={ 63 | ensName && ( 64 |
65 | {ensAvatar ? ( 66 | 67 | { 68 | // eslint-disable-next-line 69 | {`${ensAddress} 70 | } 71 | 72 | ) : null} 73 | {enteredEnsName ?? ensName} 74 |
75 | ) 76 | } 77 | prefix={ 78 | // Don't want to use nextJS Image here (and adding remote patterns for the URL) 79 | // eslint-disable-next-line @next/next/no-img-element 80 | value && 81 | } 82 | /> 83 | ); 84 | }; 85 | -------------------------------------------------------------------------------- /contracts/script/DeployHook.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Script.sol"; 5 | import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; 6 | import {HookMiner} from "../test/utils/HookMiner.sol"; 7 | 8 | contract DeployHookScript is Script { 9 | address constant CREATE2_DEPLOYER = address(0x4e59b44847b379578588920cA78FbF26c0B4956C); 10 | address manager = payable(vm.envAddress("POOL_MANAGER_ADDR")); 11 | 12 | function setUp() public {} 13 | 14 | function run() public { 15 | bytes memory constructorArgs = abi.encode(manager); 16 | 17 | // hook contracts must have specific flags encoded in the address 18 | // ------------------------------ // 19 | // --- Set your flags in .env --- // 20 | // ------------------------------ // 21 | uint160 flags = getFlagsFromEnv(); 22 | console2.logBytes32(bytes32(uint256(flags))); 23 | 24 | // Mine a salt that will produce a hook address with the correct flags 25 | bytes memory creationCode = vm.getCode(vm.envString("HOOK_CONTRACT")); 26 | (address hookAddress, bytes32 salt) = 27 | HookMiner.find(CREATE2_DEPLOYER, flags, creationCode, constructorArgs); 28 | 29 | // Deploy the hook using CREATE2 30 | bytes memory bytecode = abi.encodePacked(creationCode, constructorArgs); 31 | vm.startBroadcast(); 32 | address deployedHook; 33 | assembly { 34 | deployedHook := create2(0, add(bytecode, 0x20), mload(bytecode), salt) 35 | } 36 | vm.stopBroadcast(); 37 | 38 | // verify proper create2 usage 39 | require(deployedHook == hookAddress, "DeployScript: hook address mismatch"); 40 | } 41 | 42 | /// @dev Read booleans flags from the environemnt and encode them into the uint160 bit flags 43 | function getFlagsFromEnv() internal view returns (uint160) { 44 | uint256 flags; 45 | if (vm.envBool("BEFORE_SWAP")) flags |= Hooks.BEFORE_SWAP_FLAG; 46 | if (vm.envBool("AFTER_SWAP")) flags |= Hooks.AFTER_SWAP_FLAG; 47 | 48 | if (vm.envBool("BEFORE_ADD_LIQUIDITY")) flags |= Hooks.BEFORE_ADD_LIQUIDITY_FLAG; 49 | if (vm.envBool("AFTER_ADD_LIQUIDITY")) flags |= Hooks.AFTER_ADD_LIQUIDITY_FLAG; 50 | if (vm.envBool("BEFORE_REMOVE_LIQUIDITY")) flags |= Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG; 51 | if (vm.envBool("AFTER_REMOVE_LIQUIDITY")) flags |= Hooks.AFTER_REMOVE_LIQUIDITY_FLAG; 52 | 53 | if (vm.envBool("BEFORE_INITIALIZE")) flags |= Hooks.BEFORE_INITIALIZE_FLAG; 54 | if (vm.envBool("AFTER_INITIALIZE")) flags |= Hooks.AFTER_INITIALIZE_FLAG; 55 | 56 | if (vm.envBool("BEFORE_DONATE")) flags |= Hooks.BEFORE_DONATE_FLAG; 57 | if (vm.envBool("AFTER_DONATE")) flags |= Hooks.AFTER_DONATE_FLAG; 58 | 59 | if (vm.envBool("NO_OP")) flags |= Hooks.NO_OP_FLAG; 60 | if (vm.envBool("ACCESS_LOCK")) flags |= Hooks.ACCESS_LOCK_FLAG; 61 | 62 | return uint160(flags); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /nextjs/components/base/pool-key.tsx: -------------------------------------------------------------------------------- 1 | import { Address, AddressInput } from "../scaffold-eth"; 2 | import { NumericInput } from "./numeric-input"; 3 | import { Accordion, AccordionItem } from "@nextui-org/react"; 4 | import { getPoolId } from "~~/utils/v4helpers"; 5 | 6 | export function PoolKeyId({ 7 | currency0, 8 | currency1, 9 | swapFee, 10 | setSwapFee, 11 | tickSpacing, 12 | setTickSpacing, 13 | hookAddress, 14 | setHookAddress, 15 | }: { 16 | currency0: string; 17 | currency1: string; 18 | swapFee: bigint; 19 | setSwapFee: React.Dispatch>; 20 | tickSpacing: bigint; 21 | setTickSpacing: React.Dispatch>; 22 | hookAddress: `0x${string}`; 23 | setHookAddress: React.Dispatch<`0x${string}`>; 24 | }) { 25 | const poolId = getPoolId({ 26 | currency0, 27 | currency1, 28 | fee: Number(swapFee), 29 | tickSpacing: Number(tickSpacing), 30 | hooks: hookAddress, 31 | }); 32 | return ( 33 | 34 | 39 | PoolKey ID 40 |
41 | null} /> 42 |
43 |
44 | } 45 | > 46 |
47 |
48 | Currency 0 49 |
50 |
51 |
52 | Currency 1 53 |
54 |
55 |
56 | 57 | setSwapFee(BigInt(Number(e.target.value) * 10_000))} 63 | /> 64 | setTickSpacing(BigInt(e.target.value))} 70 | /> 71 | setHookAddress(e.target.value as `0x${string}`)} 77 | /> 78 | 79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/utilsContract.tsx: -------------------------------------------------------------------------------- 1 | import { AbiFunction, AbiParameter } from "abitype"; 2 | import { BaseError as BaseViemError } from "viem"; 3 | 4 | /** 5 | * @dev utility function to generate key corresponding to function metaData 6 | * @param {AbiFunction} functionName 7 | * @param {utils.ParamType} input - object containing function name and input type corresponding to index 8 | * @param {number} inputIndex 9 | * @returns {string} key 10 | */ 11 | const getFunctionInputKey = (functionName: string, input: AbiParameter, inputIndex: number): string => { 12 | const name = input?.name || `input_${inputIndex}_`; 13 | return functionName + "_" + name + "_" + input.internalType + "_" + input.type; 14 | }; 15 | 16 | /** 17 | * @dev utility function to parse error 18 | * @param e - error object 19 | * @returns {string} parsed error string 20 | */ 21 | const getParsedError = (e: any | BaseViemError): string => { 22 | let message = e.message ?? "An unknown error occurred"; 23 | 24 | if (e instanceof BaseViemError) { 25 | if (e.details) { 26 | message = e.details; 27 | } else if (e.shortMessage) { 28 | message = e.shortMessage; 29 | } else if (e.message) { 30 | message = e.message; 31 | } else if (e.name) { 32 | message = e.name; 33 | } 34 | } 35 | 36 | return message; 37 | }; 38 | 39 | // This regex is used to identify array types in the form of `type[size]` 40 | const ARRAY_TYPE_REGEX = /\[.*\]$/; 41 | /** 42 | * @dev Parse form input with array support 43 | * @param {Record} form - form object containing key value pairs 44 | * @returns parsed error string 45 | */ 46 | const getParsedContractFunctionArgs = (form: Record) => { 47 | const keys = Object.keys(form); 48 | const parsedArguments = keys.map(key => { 49 | try { 50 | const keySplitArray = key.split("_"); 51 | const baseTypeOfArg = keySplitArray[keySplitArray.length - 1]; 52 | let valueOfArg = form[key]; 53 | 54 | if (ARRAY_TYPE_REGEX.test(baseTypeOfArg) || baseTypeOfArg === "tuple") { 55 | valueOfArg = JSON.parse(valueOfArg); 56 | } else if (baseTypeOfArg === "bool") { 57 | if (["true", "1", "0x1", "0x01", "0x0001"].includes(valueOfArg)) { 58 | valueOfArg = 1; 59 | } else { 60 | valueOfArg = 0; 61 | } 62 | } 63 | return valueOfArg; 64 | } catch (error: any) { 65 | // ignore error, it will be handled when sending/reading from a function 66 | } 67 | }); 68 | return parsedArguments; 69 | }; 70 | 71 | const getInitialFormState = (abiFunction: AbiFunction) => { 72 | const initialForm: Record = {}; 73 | if (!abiFunction.inputs) return initialForm; 74 | abiFunction.inputs.forEach((input, inputIndex) => { 75 | const key = getFunctionInputKey(abiFunction.name, input, inputIndex); 76 | initialForm[key] = ""; 77 | }); 78 | return initialForm; 79 | }; 80 | 81 | export { getFunctionInputKey, getInitialFormState, getParsedContractFunctionArgs, getParsedError }; 82 | -------------------------------------------------------------------------------- /nextjs/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import GitHubButton from "react-github-btn"; 3 | import { TimelineDemo } from "~~/components/timeline"; 4 | import { TextHoverEffect } from "~~/components/ui/text-hover-effect"; 5 | 6 | const Home: NextPage = () => { 7 | return ( 8 |
9 |
10 | 17 | Star 18 | 19 | 20 | 21 | 22 |

23 | 24 | Market Maker Hook 25 | {" "} 26 | 旨在在{" "} 27 | 28 | Aggregator Hook 29 | {" "} 30 | 的基础上拓展一个实用案例:使用一个 MatchEngine 维护做市商们提供的 orderbook,当用户发起交易时,使用 Aggregator 31 | Hook 的技术填充流动性,使得用户能直接使用 orderbook 的报价。这种设计也使得做市商无缝接入 Uniswap V4 32 | 生态成为可能。 33 |

34 |

Aggregator // Order Limit // Tick Mapping

35 | 36 |
37 | 38 | 39 |
40 |

San Francisco, CA // ETHGlobal Hackathon 2024/10/19

41 | 75 |
76 |
77 | ); 78 | }; 79 | 80 | export default Home; 81 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Input/utils.ts: -------------------------------------------------------------------------------- 1 | export interface CommonInputProps { 2 | value: T; 3 | onChange: (newValue: T) => void; 4 | name?: string; 5 | placeholder?: string; 6 | disabled?: boolean; 7 | } 8 | 9 | export enum IntegerVariant { 10 | UINT8 = "uint8", 11 | UINT16 = "uint16", 12 | UINT24 = "uint24", 13 | UINT32 = "uint32", 14 | UINT40 = "uint40", 15 | UINT48 = "uint48", 16 | UINT56 = "uint56", 17 | UINT64 = "uint64", 18 | UINT72 = "uint72", 19 | UINT80 = "uint80", 20 | UINT88 = "uint88", 21 | UINT96 = "uint96", 22 | UINT104 = "uint104", 23 | UINT112 = "uint112", 24 | UINT120 = "uint120", 25 | UINT128 = "uint128", 26 | UINT136 = "uint136", 27 | UINT144 = "uint144", 28 | UINT152 = "uint152", 29 | UINT160 = "uint160", 30 | UINT168 = "uint168", 31 | UINT176 = "uint176", 32 | UINT184 = "uint184", 33 | UINT192 = "uint192", 34 | UINT200 = "uint200", 35 | UINT208 = "uint208", 36 | UINT216 = "uint216", 37 | UINT224 = "uint224", 38 | UINT232 = "uint232", 39 | UINT240 = "uint240", 40 | UINT248 = "uint248", 41 | UINT256 = "uint256", 42 | INT8 = "int8", 43 | INT16 = "int16", 44 | INT24 = "int24", 45 | INT32 = "int32", 46 | INT40 = "int40", 47 | INT48 = "int48", 48 | INT56 = "int56", 49 | INT64 = "int64", 50 | INT72 = "int72", 51 | INT80 = "int80", 52 | INT88 = "int88", 53 | INT96 = "int96", 54 | INT104 = "int104", 55 | INT112 = "int112", 56 | INT120 = "int120", 57 | INT128 = "int128", 58 | INT136 = "int136", 59 | INT144 = "int144", 60 | INT152 = "int152", 61 | INT160 = "int160", 62 | INT168 = "int168", 63 | INT176 = "int176", 64 | INT184 = "int184", 65 | INT192 = "int192", 66 | INT200 = "int200", 67 | INT208 = "int208", 68 | INT216 = "int216", 69 | INT224 = "int224", 70 | INT232 = "int232", 71 | INT240 = "int240", 72 | INT248 = "int248", 73 | INT256 = "int256", 74 | } 75 | 76 | export const SIGNED_NUMBER_REGEX = /^-?\d+\.?\d*$/; 77 | export const UNSIGNED_NUMBER_REGEX = /^\.?\d+\.?\d*$/; 78 | 79 | export const isValidInteger = (dataType: IntegerVariant, value: bigint | string, strict = true) => { 80 | const isSigned = dataType.startsWith("i"); 81 | const bitcount = Number(dataType.substring(isSigned ? 3 : 4)); 82 | 83 | let valueAsBigInt; 84 | try { 85 | valueAsBigInt = BigInt(value); 86 | } catch (e) {} 87 | if (typeof valueAsBigInt !== "bigint") { 88 | if (strict) { 89 | return false; 90 | } 91 | if (!value || typeof value !== "string") { 92 | return true; 93 | } 94 | return isSigned ? SIGNED_NUMBER_REGEX.test(value) || value === "-" : UNSIGNED_NUMBER_REGEX.test(value); 95 | } else if (!isSigned && valueAsBigInt < 0) { 96 | return false; 97 | } 98 | const hexString = valueAsBigInt.toString(16); 99 | const significantHexDigits = hexString.match(/.*x0*(.*)$/)?.[1] ?? ""; 100 | if ( 101 | significantHexDigits.length * 4 > bitcount || 102 | (isSigned && significantHexDigits.length * 4 === bitcount && parseInt(significantHexDigits.slice(-1)?.[0], 16) < 8) 103 | ) { 104 | return false; 105 | } 106 | return true; 107 | }; 108 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "anvil": "source .env && anvil --hardfork cancun --fork-url $ANVIL_FORK_URL --chain-id $ANVIL_CHAIN_ID --init genesis.json", 7 | "deploy": "source .env && forge create contracts/src/Counter.sol:Counter --rpc-url $FORGE_RPC_URL --private-key $FORGE_PRIVATE_KEY", 8 | "deploy:anvil": "source .env && POOL_MANAGER_ADDR=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", 9 | "deploy:goerli": "source .env && POOL_MANAGER_ADDR=0x3A9D48AB9751398BbFa63ad67599Bb04e4BdF98b forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url https://rpc.ankr.com/eth_goerli --private-key $FORGE_PRIVATE_KEY", 10 | "deploy:arbitrum-goerli": "source .env && POOL_MANAGER_ADDR=0x60AbEb98b3b95A0c5786261c1Ab830e3D2383F9e forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url https://arbitrum-goerli.publicnode.com --private-key $FORGE_PRIVATE_KEY", 11 | "deploy:arbitrum-sepolia": "source .env && POOL_MANAGER_ADDR=0xb673AE03413860776497B8C9b3E3F8d4D8745cB3 forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url https://sepolia-rollup.arbitrum.io/rpc --private-key $FORGE_PRIVATE_KEY", 12 | "deploy:optimism-goerli": "source .env && POOL_MANAGER_ADDR=0xb673AE03413860776497B8C9b3E3F8d4D8745cB3 forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url https://goerli.optimism.io --private-key $FORGE_PRIVATE_KEY", 13 | "deploy:base-goerli": "source .env && POOL_MANAGER_ADDR=0x0Bf5c45Bc0419229FB512bb00366A612877ffF2D forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url https://base-goerli.publicnode.com --private-key $FORGE_PRIVATE_KEY", 14 | "deploy:scroll-sepolia": "source .env && POOL_MANAGER_ADDR=0xeb4708989b42f0cd327A6Bd8f76a931429137fd7 forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url https://rpc.ankr.com/scroll_sepolia_testnet --private-key $FORGE_PRIVATE_KEY", 15 | "deploy:sepolia": "source .env && POOL_MANAGER_ADDR=0x7B2B5A2c377B34079589DDbCeA20427cdb7C8219 forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url https://ethereum-sepolia.publicnode.com --private-key $FORGE_PRIVATE_KEY", 16 | "deploy:polygon-mumbai": "source .env && POOL_MANAGER_ADDR=0x84642fEf6ef575e3B2f4d7C72022F24AB9C9Ffa6 forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url https://rpc.ankr.com/polygon_mumbai --private-key $FORGE_PRIVATE_KEY", 17 | "deploy:polygon-zkevm-testnet": "source .env && POOL_MANAGER_ADDR=0xb673AE03413860776497B8C9b3E3F8d4D8745cB3 forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url https://rpc.public.zkevm-test.net --private-key $FORGE_PRIVATE_KEY", 18 | "deploy:conduit": "source .env && POOL_MANAGER_ADDR=0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 forge script contracts/script/DeployHook.s.sol --broadcast --rpc-url https://l2-uniswap-v4-hook-sandbox-6tl5qq8i4d.t.conduit.xyz/ --private-key $FORGE_PRIVATE_KEY", 19 | "genesis": "source .env && forge script contracts/script/GenerateAnvilGenesis.s.sol --ffi --broadcast --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useAutoConnect.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useEffectOnce, useLocalStorage, useReadLocalStorage } from "usehooks-ts"; 3 | import { Connector, useAccount, useConnect } from "wagmi"; 4 | import { hardhat } from "wagmi/chains"; 5 | import scaffoldConfig from "~~/scaffold.config"; 6 | import { burnerWalletId, defaultBurnerChainId } from "~~/services/web3/wagmi-burner/BurnerConnector"; 7 | import { getTargetNetwork } from "~~/utils/scaffold-eth"; 8 | 9 | const SCAFFOLD_WALLET_STROAGE_KEY = "scaffoldEth2.wallet"; 10 | const WAGMI_WALLET_STORAGE_KEY = "wagmi.wallet"; 11 | 12 | // ID of the SAFE connector instance 13 | const SAFE_ID = "safe"; 14 | 15 | /** 16 | * This function will get the initial wallet connector (if any), the app will connect to 17 | * @param previousWalletId 18 | * @param connectors 19 | * @returns 20 | */ 21 | const getInitialConnector = ( 22 | previousWalletId: string, 23 | connectors: Connector[], 24 | ): { connector: Connector | undefined; chainId?: number } | undefined => { 25 | // Look for the SAFE connector instance and connect to it instantly if loaded in SAFE frame 26 | const safeConnectorInstance = connectors.find(connector => connector.id === SAFE_ID && connector.ready); 27 | 28 | if (safeConnectorInstance) { 29 | return { connector: safeConnectorInstance }; 30 | } 31 | 32 | const targetNetwork = getTargetNetwork(); 33 | const allowBurner = scaffoldConfig.onlyLocalBurnerWallet ? targetNetwork.id === hardhat.id : true; 34 | 35 | if (!previousWalletId) { 36 | // The user was not connected to a wallet 37 | if (allowBurner && scaffoldConfig.walletAutoConnect) { 38 | const connector = connectors.find(f => f.id === burnerWalletId); 39 | return { connector, chainId: defaultBurnerChainId }; 40 | } 41 | } else { 42 | // the user was connected to wallet 43 | if (scaffoldConfig.walletAutoConnect) { 44 | if (previousWalletId === burnerWalletId && !allowBurner) { 45 | return; 46 | } 47 | 48 | const connector = connectors.find(f => f.id === previousWalletId); 49 | return { connector }; 50 | } 51 | } 52 | 53 | return undefined; 54 | }; 55 | 56 | /** 57 | * Automatically connect to a wallet/connector based on config and prior wallet 58 | */ 59 | export const useAutoConnect = (): void => { 60 | const wagmiWalletValue = useReadLocalStorage(WAGMI_WALLET_STORAGE_KEY); 61 | const [walletId, setWalletId] = useLocalStorage(SCAFFOLD_WALLET_STROAGE_KEY, wagmiWalletValue ?? ""); 62 | const connectState = useConnect(); 63 | const accountState = useAccount(); 64 | 65 | useEffect(() => { 66 | if (accountState.isConnected) { 67 | // user is connected, set walletName 68 | setWalletId(accountState.connector?.id ?? ""); 69 | } else { 70 | // user has disconnected, reset walletName 71 | window.localStorage.setItem(WAGMI_WALLET_STORAGE_KEY, JSON.stringify("")); 72 | setWalletId(""); 73 | } 74 | // eslint-disable-next-line react-hooks/exhaustive-deps 75 | }, [accountState.isConnected, accountState.connector?.name]); 76 | 77 | useEffectOnce(() => { 78 | const initialConnector = getInitialConnector(walletId, connectState.connectors); 79 | 80 | if (initialConnector?.connector) { 81 | connectState.connect({ connector: initialConnector.connector, chainId: initialConnector.chainId }); 82 | } 83 | }); 84 | }; 85 | -------------------------------------------------------------------------------- /nextjs/utils/scaffold-eth/notification.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { toast } from "react-hot-toast"; 3 | import { XMarkIcon } from "@heroicons/react/20/solid"; 4 | import { 5 | CheckCircleIcon, 6 | ExclamationCircleIcon, 7 | ExclamationTriangleIcon, 8 | InformationCircleIcon, 9 | } from "@heroicons/react/24/solid"; 10 | import { Spinner } from "~~/components/assets/Spinner"; 11 | 12 | type TPositions = "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right"; 13 | 14 | type TNotificationProps = { 15 | content: React.ReactNode; 16 | status: "success" | "info" | "loading" | "error" | "warning"; 17 | duration?: number; 18 | icon?: string; 19 | position?: TPositions; 20 | }; 21 | 22 | type NotificationOptions = { 23 | duration?: number; 24 | icon?: string; 25 | position?: TPositions; 26 | }; 27 | 28 | const ENUM_STATUSES = { 29 | success: , 30 | loading: , 31 | error: , 32 | info: , 33 | warning: , 34 | }; 35 | 36 | const DEFAULT_DURATION = 3000; 37 | const DEFAULT_POSITION: TPositions = "top-center"; 38 | 39 | /** 40 | * Custom Notification 41 | */ 42 | const Notification = ({ 43 | content, 44 | status, 45 | duration = DEFAULT_DURATION, 46 | icon, 47 | position = DEFAULT_POSITION, 48 | }: TNotificationProps) => { 49 | return toast.custom( 50 | t => ( 51 |
59 |
{icon ? icon : ENUM_STATUSES[status]}
60 |
{content}
61 | 62 |
toast.dismiss(t.id)}> 63 | toast.remove(t.id)} /> 64 |
65 |
66 | ), 67 | { 68 | duration: status === "loading" ? Infinity : duration, 69 | position, 70 | }, 71 | ); 72 | }; 73 | 74 | export const notification = { 75 | success: (content: React.ReactNode, options?: NotificationOptions) => { 76 | return Notification({ content, status: "success", ...options }); 77 | }, 78 | info: (content: React.ReactNode, options?: NotificationOptions) => { 79 | return Notification({ content, status: "info", ...options }); 80 | }, 81 | warning: (content: React.ReactNode, options?: NotificationOptions) => { 82 | return Notification({ content, status: "warning", ...options }); 83 | }, 84 | error: (content: React.ReactNode, options?: NotificationOptions) => { 85 | return Notification({ content, status: "error", ...options }); 86 | }, 87 | loading: (content: React.ReactNode, options?: NotificationOptions) => { 88 | return Notification({ content, status: "loading", ...options }); 89 | }, 90 | remove: (toastId: string) => { 91 | toast.remove(toastId); 92 | }, 93 | }; 94 | -------------------------------------------------------------------------------- /contracts/test/utils/HookMiner.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.19; 3 | 4 | /// @title HookMiner - a library for mining hook addresses 5 | /// @dev This library is intended for `forge test` environments. There may be gotchas when using salts in `forge script` or `forge create` 6 | library HookMiner { 7 | // mask to slice out the top 10 bits of the address 8 | uint160 constant FLAG_MASK = 0x3FF << 150; 9 | 10 | // Maximum number of iterations to find a salt, avoid infinite loops 11 | uint256 constant MAX_LOOP = 10_000; 12 | 13 | /// @notice Find a salt that produces a hook address with the desired `flags` 14 | /// @param deployer The address that will deploy the hook. 15 | /// In `forge test`, this will be the test contract `address(this)` or the pranking address 16 | /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) 17 | /// @param flags The desired flags for the hook address 18 | /// @param seed Use 0 for as a default. An optional starting salt when linearly searching for a salt 19 | /// Useful for finding salts for multiple hooks with the same flags 20 | /// @param creationCode The creation code of a hook contract. Example: `type(Counter).creationCode` 21 | /// @param constructorArgs The encoded constructor arguments of a hook contract. Example: `abi.encode(address(manager))` 22 | /// @return hookAddress salt and corresponding address that was found 23 | /// The salt can be used in `new Hook{salt: salt}()` 24 | function find( 25 | address deployer, 26 | uint160 flags, 27 | uint256 seed, 28 | bytes memory creationCode, 29 | bytes memory constructorArgs 30 | ) external pure returns (address, bytes32) { 31 | address hookAddress; 32 | bytes memory creationCodeWithArgs = abi.encodePacked( 33 | creationCode, 34 | constructorArgs 35 | ); 36 | 37 | uint256 salt = seed; 38 | for (salt; salt < MAX_LOOP; ) { 39 | hookAddress = computeAddress(deployer, salt, creationCodeWithArgs); 40 | if (uint160(hookAddress) & FLAG_MASK == flags) { 41 | return (hookAddress, bytes32(salt)); 42 | } 43 | 44 | unchecked { 45 | ++salt; 46 | } 47 | } 48 | 49 | revert("HookMiner: could not find salt"); 50 | } 51 | 52 | /// @notice Precompute a contract address deployed via CREATE2 53 | /// @param deployer The address that will deploy the hook 54 | /// In `forge test`, this will be the test contract `address(this)` or the pranking address 55 | /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) 56 | /// @param salt The salt used to deploy the hook 57 | /// @param creationCode The creation code of a hook contract 58 | function computeAddress( 59 | address deployer, 60 | uint256 salt, 61 | bytes memory creationCode 62 | ) public pure returns (address hookAddress) { 63 | return 64 | address( 65 | uint160( 66 | uint256( 67 | keccak256( 68 | abi.encodePacked( 69 | bytes1(0xFF), 70 | deployer, 71 | salt, 72 | keccak256(creationCode) 73 | ) 74 | ) 75 | ) 76 | ) 77 | ); 78 | } 79 | } -------------------------------------------------------------------------------- /nextjs/components/blockexplorer/TransactionsTable.tsx: -------------------------------------------------------------------------------- 1 | import { Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from "@nextui-org/react"; 2 | import { formatEther } from "viem"; 3 | import { TransactionHash } from "~~/components/blockexplorer/TransactionHash"; 4 | import { Address } from "~~/components/scaffold-eth"; 5 | import { TransactionWithFunction, getTargetNetwork } from "~~/utils/scaffold-eth"; 6 | import { TransactionsTableProps } from "~~/utils/scaffold-eth/"; 7 | 8 | export const TransactionsTable = ({ blocks, transactionReceipts }: TransactionsTableProps) => { 9 | const targetNetwork = getTargetNetwork(); 10 | 11 | return ( 12 |
13 |
14 | 15 | 16 | Transaction Hash 17 | Function Called 18 | Block Number 19 | Time Mined 20 | From 21 | To 22 | Value ({targetNetwork.nativeCurrency.symbol}) 23 | 24 | 25 | {blocks.map(block => 26 | (block.transactions as TransactionWithFunction[]).map(tx => { 27 | const receipt = transactionReceipts[tx.hash]; 28 | const timeMined = new Date(Number(block.timestamp) * 1000).toLocaleString(); 29 | const functionCalled = tx.input.substring(0, 10); 30 | 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | {tx.functionName === "0x" ? "" : {tx.functionName}} 38 | {functionCalled !== "0x" && ( 39 | {functionCalled} 40 | )} 41 | 42 | {block.number?.toString()} 43 | {timeMined} 44 | 45 |
46 | 47 | 48 | {!receipt?.contractAddress ? ( 49 | tx.to &&
50 | ) : ( 51 |
52 |
53 | (Contract Creation) 54 |
55 | )} 56 | 57 | 58 | {formatEther(tx.value)} {targetNetwork.nativeCurrency.symbol} 59 | 60 | 61 | ); 62 | }), 63 | )} 64 | 65 |
66 |
67 |
68 | ); 69 | }; 70 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Abi, ExtractAbiFunctionNames } from "abitype"; 3 | import { useContractWrite, useNetwork } from "wagmi"; 4 | import { getParsedError } from "~~/components/scaffold-eth"; 5 | import { useDeployedContractInfo, useTransactor } from "~~/hooks/scaffold-eth"; 6 | import { getTargetNetwork, notification } from "~~/utils/scaffold-eth"; 7 | import { ContractAbi, ContractName, UseScaffoldWriteConfig } from "~~/utils/scaffold-eth/contract"; 8 | 9 | type UpdatedArgs = Parameters>["writeAsync"]>[0]; 10 | 11 | /** 12 | * @dev wrapper for wagmi's useContractWrite hook(with config prepared by usePrepareContractWrite hook) which loads in deployed contract abi and address automatically 13 | * @param config - The config settings, including extra wagmi configuration 14 | * @param config.contractName - deployed contract name 15 | * @param config.functionName - name of the function to be called 16 | * @param config.args - arguments for the function 17 | * @param config.value - value in ETH that will be sent with transaction 18 | */ 19 | export const useScaffoldContractWrite = < 20 | TContractName extends ContractName, 21 | TFunctionName extends ExtractAbiFunctionNames, "nonpayable" | "payable">, 22 | >({ 23 | contractName, 24 | functionName, 25 | args, 26 | value, 27 | onBlockConfirmation, 28 | blockConfirmations, 29 | ...writeConfig 30 | }: UseScaffoldWriteConfig) => { 31 | const { data: deployedContractData } = useDeployedContractInfo(contractName); 32 | const { chain } = useNetwork(); 33 | const writeTx = useTransactor(); 34 | const [isMining, setIsMining] = useState(false); 35 | const configuredNetwork = getTargetNetwork(); 36 | 37 | const wagmiContractWrite = useContractWrite({ 38 | chainId: configuredNetwork.id, 39 | address: deployedContractData?.address, 40 | abi: deployedContractData?.abi as Abi, 41 | functionName: functionName as any, 42 | args: args as unknown[], 43 | value: value, 44 | ...writeConfig, 45 | }); 46 | 47 | const sendContractWriteTx = async ({ 48 | args: newArgs, 49 | value: newValue, 50 | ...otherConfig 51 | }: { 52 | args?: UseScaffoldWriteConfig["args"]; 53 | value?: UseScaffoldWriteConfig["value"]; 54 | } & UpdatedArgs = {}) => { 55 | if (!deployedContractData) { 56 | notification.error("Target Contract is not deployed, did you forget to run `yarn deploy`?"); 57 | return; 58 | } 59 | if (!chain?.id) { 60 | notification.error("Please connect your wallet"); 61 | return; 62 | } 63 | if (chain?.id !== configuredNetwork.id) { 64 | notification.error("You are on the wrong network"); 65 | return; 66 | } 67 | 68 | if (wagmiContractWrite.writeAsync) { 69 | try { 70 | setIsMining(true); 71 | await writeTx( 72 | () => 73 | wagmiContractWrite.writeAsync({ 74 | args: newArgs ?? args, 75 | value: newValue ?? value, 76 | ...otherConfig, 77 | }), 78 | { onBlockConfirmation, blockConfirmations }, 79 | ); 80 | } catch (e: any) { 81 | const message = getParsedError(e); 82 | notification.error(message); 83 | } finally { 84 | setIsMining(false); 85 | } 86 | } else { 87 | notification.error("Contract writer error. Try again."); 88 | return; 89 | } 90 | }; 91 | 92 | return { 93 | ...wagmiContractWrite, 94 | isMining, 95 | // Overwrite wagmi's write async 96 | writeAsync: sendContractWriteTx, 97 | }; 98 | }; 99 | -------------------------------------------------------------------------------- /nextjs/utils/scaffold-eth/networks.ts: -------------------------------------------------------------------------------- 1 | import * as chains from "wagmi/chains"; 2 | import scaffoldConfig from "~~/scaffold.config"; 3 | 4 | export type TChainAttributes = { 5 | // color | [lightThemeColor, darkThemeColor] 6 | color: string | [string, string]; 7 | // Used to fetch price by providing mainnet token address 8 | // for networks having native currency other than ETH 9 | nativeCurrencyTokenAddress?: string; 10 | }; 11 | 12 | export const NETWORKS_EXTRA_DATA: Record = { 13 | [chains.hardhat.id]: { 14 | color: "#b8af0c", 15 | }, 16 | [chains.mainnet.id]: { 17 | color: "#ff8b9e", 18 | }, 19 | [chains.sepolia.id]: { 20 | color: ["#5f4bb6", "#87ff65"], 21 | }, 22 | [chains.goerli.id]: { 23 | color: "#0975F6", 24 | }, 25 | [chains.gnosis.id]: { 26 | color: "#48a9a6", 27 | }, 28 | [chains.polygon.id]: { 29 | color: "#2bbdf7", 30 | nativeCurrencyTokenAddress: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0", 31 | }, 32 | [chains.polygonMumbai.id]: { 33 | color: "#92D9FA", 34 | nativeCurrencyTokenAddress: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0", 35 | }, 36 | [chains.optimismGoerli.id]: { 37 | color: "#f01a37", 38 | }, 39 | [chains.optimism.id]: { 40 | color: "#f01a37", 41 | }, 42 | [chains.arbitrumGoerli.id]: { 43 | color: "#28a0f0", 44 | }, 45 | [chains.arbitrum.id]: { 46 | color: "#28a0f0", 47 | }, 48 | [chains.fantom.id]: { 49 | color: "#1969ff", 50 | }, 51 | [chains.fantomTestnet.id]: { 52 | color: "#1969ff", 53 | }, 54 | [chains.scrollSepolia.id]: { 55 | color: '#fbebd4' 56 | } 57 | }; 58 | 59 | /** 60 | * Gives the block explorer transaction URL. 61 | * @param network 62 | * @param txnHash 63 | * @dev returns empty string if the network is localChain 64 | */ 65 | export function getBlockExplorerTxLink(chainId: number, txnHash: string) { 66 | const chainNames = Object.keys(chains); 67 | 68 | const targetChainArr = chainNames.filter(chainName => { 69 | const wagmiChain = chains[chainName as keyof typeof chains]; 70 | return wagmiChain.id === chainId; 71 | }); 72 | 73 | if (targetChainArr.length === 0) { 74 | return ""; 75 | } 76 | 77 | const targetChain = targetChainArr[0] as keyof typeof chains; 78 | // @ts-expect-error : ignoring error since `blockExplorers` key may or may not be present on some chains 79 | const blockExplorerTxURL = chains[targetChain]?.blockExplorers?.default?.url; 80 | 81 | if (!blockExplorerTxURL) { 82 | return ""; 83 | } 84 | 85 | return `${blockExplorerTxURL}/tx/${txnHash}`; 86 | } 87 | 88 | /** 89 | * Gives the block explorer Address URL. 90 | * @param network - wagmi chain object 91 | * @param address 92 | * @returns block explorer address URL and etherscan URL if block explorer URL is not present for wagmi network 93 | */ 94 | export function getBlockExplorerAddressLink(network: chains.Chain, address: string) { 95 | const blockExplorerBaseURL = network.blockExplorers?.default?.url; 96 | if (network.id === chains.hardhat.id) { 97 | return `/blockexplorer/address/${address}`; 98 | } 99 | 100 | if (!blockExplorerBaseURL) { 101 | return `https://etherscan.io/address/${address}`; 102 | } 103 | 104 | return `${blockExplorerBaseURL}/address/${address}`; 105 | } 106 | 107 | /** 108 | * @returns targetNetwork object consisting targetNetwork from scaffold.config and extra network metadata 109 | */ 110 | 111 | export function getTargetNetwork(): chains.Chain & Partial { 112 | const configuredNetwork = scaffoldConfig.targetNetwork; 113 | 114 | return { 115 | ...configuredNetwork, 116 | ...NETWORKS_EXTRA_DATA[configuredNetwork.id], 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /contracts/src/Counter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {BaseTestHooks} from "@uniswap/v4-core/src/test/BaseTestHooks.sol"; 5 | import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; 6 | import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; 7 | import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; 8 | import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; 9 | import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; 10 | import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; 11 | import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; 12 | 13 | contract Counter is BaseTestHooks { 14 | using PoolIdLibrary for PoolKey; 15 | 16 | // NOTE: --------------------------------------------------------- 17 | // state variables should typically be unique to a pool 18 | // a single hook contract should be able to service multiple pools 19 | // --------------------------------------------------------------- 20 | 21 | mapping(PoolId => uint256 count) public beforeSwapCount; 22 | mapping(PoolId => uint256 count) public afterSwapCount; 23 | 24 | mapping(PoolId => uint256 count) public beforeAddLiquidityCount; 25 | mapping(PoolId => uint256 count) public afterAddLiquidityCount; 26 | 27 | IPoolManager public immutable poolManager; 28 | 29 | constructor(IPoolManager _poolManager) { 30 | poolManager = _poolManager; 31 | Hooks.validateHookPermissions(IHooks(address(this)), getHookPermissions()); 32 | } 33 | 34 | function getHookPermissions() public pure returns (Hooks.Permissions memory) { 35 | return Hooks.Permissions({ 36 | beforeInitialize: false, 37 | afterInitialize: false, 38 | beforeAddLiquidity: true, 39 | afterAddLiquidity: true, 40 | beforeRemoveLiquidity: false, 41 | afterRemoveLiquidity: false, 42 | beforeSwap: true, 43 | afterSwap: true, 44 | beforeDonate: false, 45 | afterDonate: false, 46 | beforeSwapReturnDelta: false, 47 | afterSwapReturnDelta: false, 48 | afterAddLiquidityReturnDelta: false, 49 | afterRemoveLiquidityReturnDelta: false 50 | }); 51 | } 52 | 53 | // ----------------------------------------------- 54 | // NOTE: see IHooks.sol for function documentation 55 | // ----------------------------------------------- 56 | 57 | function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) 58 | external 59 | override 60 | returns (bytes4, BeforeSwapDelta, uint24) 61 | { 62 | beforeSwapCount[key.toId()]++; 63 | return (BaseTestHooks.beforeSwap.selector, BeforeSwapDelta.wrap(0), 0); 64 | } 65 | 66 | function afterSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) 67 | external 68 | override 69 | returns (bytes4, int128) 70 | { 71 | afterSwapCount[key.toId()]++; 72 | return (BaseTestHooks.afterSwap.selector, 0); 73 | } 74 | 75 | function beforeAddLiquidity( 76 | address, 77 | PoolKey calldata key, 78 | IPoolManager.ModifyLiquidityParams calldata, 79 | bytes calldata 80 | ) external override returns (bytes4) { 81 | beforeAddLiquidityCount[key.toId()]++; 82 | return BaseTestHooks.beforeAddLiquidity.selector; 83 | } 84 | 85 | function afterAddLiquidity( 86 | address, 87 | PoolKey calldata key, 88 | IPoolManager.ModifyLiquidityParams calldata, 89 | BalanceDelta, 90 | BalanceDelta, 91 | bytes calldata 92 | ) external override returns (bytes4, BalanceDelta) { 93 | afterAddLiquidityCount[key.toId()]++; 94 | return (BaseTestHooks.afterAddLiquidity.selector, BalanceDelta.wrap(0)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useTransactor.tsx: -------------------------------------------------------------------------------- 1 | import { WriteContractResult, getPublicClient } from "@wagmi/core"; 2 | import { Hash, SendTransactionParameters, TransactionReceipt, WalletClient } from "viem"; 3 | import { useWalletClient } from "wagmi"; 4 | import { getParsedError } from "~~/components/scaffold-eth"; 5 | import { getBlockExplorerTxLink, notification } from "~~/utils/scaffold-eth"; 6 | 7 | type TransactionFunc = ( 8 | tx: (() => Promise) | SendTransactionParameters, 9 | options?: { 10 | onBlockConfirmation?: (txnReceipt: TransactionReceipt) => void; 11 | blockConfirmations?: number; 12 | }, 13 | ) => Promise; 14 | 15 | /** 16 | * Custom notification content for TXs. 17 | */ 18 | const TxnNotification = ({ message, blockExplorerLink }: { message: string; blockExplorerLink?: string }) => { 19 | return ( 20 |
21 |

{message}

22 | {blockExplorerLink && blockExplorerLink.length > 0 ? ( 23 | 24 | check out transaction 25 | 26 | ) : null} 27 |
28 | ); 29 | }; 30 | 31 | /** 32 | * @description Runs Transaction passed in to returned funtion showing UI feedback. 33 | * @param _walletClient 34 | * @returns function that takes a transaction and returns a promise of the transaction hash 35 | */ 36 | export const useTransactor = (_walletClient?: WalletClient): TransactionFunc => { 37 | let walletClient = _walletClient; 38 | const { data } = useWalletClient(); 39 | if (walletClient === undefined && data) { 40 | walletClient = data; 41 | } 42 | 43 | const result: TransactionFunc = async (tx, options) => { 44 | if (!walletClient) { 45 | notification.error("Cannot access account"); 46 | console.error("⚡️ ~ file: useTransactor.tsx ~ error"); 47 | return; 48 | } 49 | 50 | let notificationId = null; 51 | let transactionHash: Awaited["hash"] | undefined = undefined; 52 | try { 53 | const network = await walletClient.getChainId(); 54 | // Get full transaction from public client 55 | const publicClient = getPublicClient(); 56 | 57 | notificationId = notification.loading(); 58 | if (typeof tx === "function") { 59 | // Tx is already prepared by the caller 60 | transactionHash = (await tx()).hash; 61 | } else if (tx != null) { 62 | transactionHash = await walletClient.sendTransaction(tx); 63 | } else { 64 | throw new Error("Incorrect transaction passed to transactor"); 65 | } 66 | notification.remove(notificationId); 67 | 68 | const blockExplorerTxURL = network ? getBlockExplorerTxLink(network, transactionHash) : ""; 69 | 70 | notificationId = notification.loading( 71 | , 72 | ); 73 | 74 | const transactionReceipt = await publicClient.waitForTransactionReceipt({ 75 | hash: transactionHash, 76 | confirmations: options?.blockConfirmations, 77 | }); 78 | notification.remove(notificationId); 79 | 80 | notification.success( 81 | , 82 | { 83 | icon: "🎉", 84 | }, 85 | ); 86 | 87 | if (options?.onBlockConfirmation) options.onBlockConfirmation(transactionReceipt); 88 | } catch (error: any) { 89 | if (notificationId) { 90 | notification.remove(notificationId); 91 | } 92 | console.error("⚡️ ~ file: useTransactor.ts ~ error", error); 93 | const message = getParsedError(error); 94 | notification.error(message); 95 | } 96 | 97 | return transactionHash; 98 | }; 99 | 100 | return result; 101 | }; 102 | -------------------------------------------------------------------------------- /nextjs/components/ui/text-hover-effect.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useEffect, useRef, useState } from "react"; 4 | import { motion } from "framer-motion"; 5 | 6 | export const TextHoverEffect = ({ text, duration }: { text: string; duration?: number; automatic?: boolean }) => { 7 | const svgRef = useRef(null); 8 | const [cursor, setCursor] = useState({ x: 0, y: 0 }); 9 | const [hovered, setHovered] = useState(false); 10 | const [maskPosition, setMaskPosition] = useState({ cx: "50%", cy: "50%" }); 11 | 12 | useEffect(() => { 13 | if (svgRef.current && cursor.x !== null && cursor.y !== null) { 14 | const svgRect = svgRef.current.getBoundingClientRect(); 15 | const cxPercentage = ((cursor.x - svgRect.left) / svgRect.width) * 100; 16 | const cyPercentage = ((cursor.y - svgRect.top) / svgRect.height) * 100; 17 | setMaskPosition({ 18 | cx: `${cxPercentage}%`, 19 | cy: `${cyPercentage}%`, 20 | }); 21 | } 22 | }, [cursor]); 23 | 24 | return ( 25 | setHovered(true)} 31 | onMouseLeave={() => setHovered(false)} 32 | onMouseMove={e => setCursor({ x: e.clientX, y: e.clientY })} 33 | className="select-none" 34 | > 35 | 36 | 37 | {hovered && ( 38 | <> 39 | 40 | 41 | 42 | 43 | 44 | 45 | )} 46 | 47 | 48 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 79 | {text} 80 | 81 | 98 | {text} 99 | 100 | 110 | {text} 111 | 112 | 113 | ); 114 | }; 115 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/FaucetButton.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger } from "@nextui-org/react"; 3 | import { createWalletClient, http, parseEther } from "viem"; 4 | import { useAccount, useChainId, useToken } from "wagmi"; 5 | import { hardhat } from "wagmi/chains"; 6 | import { BanknotesIcon } from "@heroicons/react/24/outline"; 7 | import { useMockErc20Mint } from "~~/generated/generated"; 8 | import { useTransactor } from "~~/hooks/scaffold-eth"; 9 | import { TOKEN_ADDRESSES } from "~~/utils/config"; 10 | 11 | // Number of ETH faucet sends to an address 12 | const NUM_OF_ETH = "1"; 13 | const FAUCET_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; 14 | 15 | const localWalletClient = createWalletClient({ 16 | chain: hardhat, 17 | transport: http(), 18 | }); 19 | 20 | /** 21 | * FaucetButton button which lets you grab eth. 22 | */ 23 | export const FaucetButton = () => { 24 | const chainId = useChainId(); 25 | const { address } = useAccount(); 26 | 27 | const token0 = useToken({ 28 | address: TOKEN_ADDRESSES[0][chainId as keyof (typeof TOKEN_ADDRESSES)[0]], 29 | }); 30 | 31 | const { writeAsync: writeAsyncToken0Mint } = useMockErc20Mint({ 32 | address: TOKEN_ADDRESSES[0][chainId as keyof (typeof TOKEN_ADDRESSES)[0]], 33 | args: [address ?? FAUCET_ADDRESS, parseEther("1000")], 34 | }); 35 | 36 | const token1 = useToken({ 37 | address: TOKEN_ADDRESSES[1][chainId as keyof (typeof TOKEN_ADDRESSES)[1]], 38 | }); 39 | 40 | const { writeAsync: writeAsyncToken1Mint } = useMockErc20Mint({ 41 | address: TOKEN_ADDRESSES[1][chainId as keyof (typeof TOKEN_ADDRESSES)[1]], 42 | args: [address ?? FAUCET_ADDRESS, parseEther("1000")], 43 | }); 44 | 45 | const [loading, setLoading] = useState(false); 46 | 47 | const faucetTxn = useTransactor(localWalletClient); 48 | 49 | const sendETH = async () => { 50 | try { 51 | setLoading(true); 52 | await faucetTxn({ 53 | chain: hardhat, 54 | account: address ?? FAUCET_ADDRESS, 55 | to: address, 56 | value: parseEther(NUM_OF_ETH), 57 | }); 58 | setLoading(false); 59 | } catch (error) { 60 | console.error("⚡️ ~ file: FaucetButton.tsx:sendETH ~ error", error); 61 | setLoading(false); 62 | } 63 | }; 64 | 65 | const sendToken0 = async () => { 66 | try { 67 | setLoading(true); 68 | 69 | writeAsyncToken0Mint().then(() => { 70 | setLoading(false); 71 | }); 72 | } catch (error) { 73 | console.error("⚡️ ~ file: FaucetButton.tsx:send Token0 ~ error", error); 74 | setLoading(false); 75 | } 76 | }; 77 | const sendToken1 = async () => { 78 | try { 79 | setLoading(true); 80 | 81 | writeAsyncToken1Mint().then(() => { 82 | setLoading(false); 83 | }); 84 | } catch (error) { 85 | console.error("⚡️ ~ file: FaucetButton.tsx:send Token1 ~ error", error); 86 | setLoading(false); 87 | } 88 | }; 89 | 90 | return ( 91 | 92 | 93 | 97 | 98 | 99 | {/* really ugly, but you cant conditionally render dropdown items */} 100 | {chainId === hardhat.id ? ( 101 | 102 | ETH 103 | {token0.data?.name ?? "Token0"} 104 | {token1.data?.name ?? "Token1"} 105 | 106 | ) : ( 107 | 108 | {token0.data?.name ?? "Token0"} 109 | {token1.data?.name ?? "Token1"} 110 | 111 | )} 112 | 113 | ); 114 | }; 115 | -------------------------------------------------------------------------------- /nextjs/components/swap-ui/ContractInteraction.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { CopyIcon } from "./assets/CopyIcon"; 3 | import { DiamondIcon } from "./assets/DiamondIcon"; 4 | import { HareIcon } from "./assets/HareIcon"; 5 | import { parseEther } from "viem"; 6 | import { ArrowSmallRightIcon, XMarkIcon } from "@heroicons/react/24/outline"; 7 | import { useScaffoldContractWrite } from "~~/hooks/scaffold-eth"; 8 | 9 | export const ContractInteraction = () => { 10 | const [visible, setVisible] = useState(true); 11 | const [newGreeting, setNewGreeting] = useState(""); 12 | 13 | const { writeAsync, isLoading } = useScaffoldContractWrite({ 14 | contractName: "YourContract", 15 | functionName: "setGreeting", 16 | args: [newGreeting], 17 | value: parseEther("0.01"), 18 | onBlockConfirmation: txnReceipt => { 19 | console.log("📦 Transaction blockHash", txnReceipt.blockHash); 20 | }, 21 | }); 22 | 23 | return ( 24 |
25 | 26 | 27 | 28 |
29 |
30 |
31 | 👋🏻 32 |
33 |
34 | In this page you can see how some of our hooks & components work, and how you can bring 35 | them to life with your own design! Have fun and try it out! 36 |
37 |
38 | Check out{" "} 39 | 40 | packages / nextjs/pages / swap-ui.tsx 41 | {" "} 42 | and its underlying components. 43 |
44 |
45 |
46 | 52 |
53 | 54 |
55 | Set a Greeting_ 56 | 57 |
58 | setNewGreeting(e.target.value)} 63 | /> 64 |
65 |
66 | 79 |
80 |
81 |
82 | 83 |
84 | Price: 85 |
0.01 ETH + Gas
86 |
87 |
88 |
89 |
90 | ); 91 | }; 92 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Address.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import Link from "next/link"; 3 | import { CopyToClipboard } from "react-copy-to-clipboard"; 4 | import { isAddress } from "viem"; 5 | import { useEnsAvatar, useEnsName } from "wagmi"; 6 | import { hardhat } from "wagmi/chains"; 7 | import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline"; 8 | import { BlockieAvatar } from "~~/components/scaffold-eth"; 9 | import { getBlockExplorerAddressLink, getTargetNetwork } from "~~/utils/scaffold-eth"; 10 | 11 | type TAddressProps = { 12 | address?: string; 13 | disableAddressLink?: boolean; 14 | format?: "short" | "long"; 15 | size?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl"; 16 | }; 17 | 18 | const blockieSizeMap = { 19 | xs: 6, 20 | sm: 7, 21 | base: 8, 22 | lg: 9, 23 | xl: 10, 24 | "2xl": 12, 25 | "3xl": 15, 26 | }; 27 | 28 | /** 29 | * Displays an address (or ENS) with a Blockie image and option to copy address. 30 | */ 31 | export const Address = ({ address, disableAddressLink, format, size = "base" }: TAddressProps) => { 32 | const [ens, setEns] = useState(); 33 | const [ensAvatar, setEnsAvatar] = useState(); 34 | const [addressCopied, setAddressCopied] = useState(false); 35 | 36 | const { data: fetchedEns } = useEnsName({ address, enabled: isAddress(address ?? ""), chainId: 1 }); 37 | const { data: fetchedEnsAvatar } = useEnsAvatar({ 38 | name: fetchedEns, 39 | enabled: Boolean(fetchedEns), 40 | chainId: 1, 41 | cacheTime: 30_000, 42 | }); 43 | 44 | // We need to apply this pattern to avoid Hydration errors. 45 | useEffect(() => { 46 | setEns(fetchedEns); 47 | }, [fetchedEns]); 48 | 49 | useEffect(() => { 50 | setEnsAvatar(fetchedEnsAvatar); 51 | }, [fetchedEnsAvatar]); 52 | 53 | // Skeleton UI 54 | if (!address) { 55 | return ( 56 |
57 |
58 |
59 |
60 |
61 |
62 | ); 63 | } 64 | 65 | if (!isAddress(address)) { 66 | return Wrong address; 67 | } 68 | 69 | const blockExplorerAddressLink = getBlockExplorerAddressLink(getTargetNetwork(), address); 70 | let displayAddress = address?.slice(0, 5) + "..." + address?.slice(-4); 71 | 72 | if (ens) { 73 | displayAddress = ens; 74 | } else if (format === "long") { 75 | displayAddress = address; 76 | } 77 | 78 | return ( 79 |
80 |
81 | 86 |
87 | {disableAddressLink ? ( 88 | {displayAddress} 89 | ) : getTargetNetwork().id === hardhat.id ? ( 90 | 91 | {displayAddress} 92 | 93 | ) : ( 94 | 100 | {displayAddress} 101 | 102 | )} 103 | {addressCopied ? ( 104 |
125 | ); 126 | }; 127 | -------------------------------------------------------------------------------- /contracts/test/Counter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; 6 | import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; 7 | import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; 8 | import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; 9 | import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; 10 | import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; 11 | import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; 12 | import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; 13 | import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; 14 | import {Constants} from "@uniswap/v4-core/test/utils/Constants.sol"; 15 | import {Counter} from "../src/Counter.sol"; 16 | import {HookMiner} from "./utils/HookMiner.sol"; 17 | import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; 18 | import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; 19 | 20 | contract CounterTest is Test, Deployers { 21 | using PoolIdLibrary for PoolKey; 22 | using CurrencyLibrary for Currency; 23 | 24 | Counter counter; 25 | PoolKey poolKey; 26 | PoolId poolId; 27 | 28 | function setUp() public { 29 | // creates the pool manager, test tokens, and other utility routers 30 | Deployers.deployFreshManagerAndRouters(); 31 | (currency0, currency1) = Deployers.deployMintAndApprove2Currencies(); 32 | 33 | // Deploy the hook to an address with the correct flags 34 | uint160 flags = uint160( 35 | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG 36 | | Hooks.AFTER_ADD_LIQUIDITY_FLAG 37 | ); 38 | (address hookAddress, bytes32 salt) = 39 | HookMiner.find(address(this), flags, 132, type(Counter).creationCode, abi.encode(address(manager))); 40 | counter = new Counter{salt: salt}(IPoolManager(address(manager))); 41 | require(address(counter) == hookAddress, "CounterTest: hook address mismatch"); 42 | 43 | // Create the pool 44 | poolKey = PoolKey(currency0, currency1, 3000, 60, IHooks(counter)); 45 | poolId = poolKey.toId(); 46 | manager.initialize(poolKey, Constants.SQRT_PRICE_1_1); 47 | 48 | // Provide liquidity to the pool 49 | modifyLiquidityRouter.modifyLiquidity( 50 | poolKey, IPoolManager.ModifyLiquidityParams(-60, 60, 10 ether, bytes32(0)), ZERO_BYTES 51 | ); 52 | modifyLiquidityRouter.modifyLiquidity( 53 | poolKey, IPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, bytes32(0)), ZERO_BYTES 54 | ); 55 | modifyLiquidityRouter.modifyLiquidity( 56 | poolKey, 57 | IPoolManager.ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether, bytes32(0)), 58 | ZERO_BYTES 59 | ); 60 | } 61 | 62 | function testCounterHooks() public { 63 | // positions were created in setup() 64 | assertEq(counter.beforeAddLiquidityCount(poolId), 3); 65 | assertEq(counter.afterAddLiquidityCount(poolId), 3); 66 | 67 | assertEq(counter.beforeSwapCount(poolId), 0); 68 | assertEq(counter.afterSwapCount(poolId), 0); 69 | 70 | // Perform a test swap // 71 | int256 amount = 100; 72 | bool zeroForOne = true; 73 | BalanceDelta swapDelta = swap(poolKey, amount, zeroForOne, ZERO_BYTES); 74 | // ------------------- // 75 | 76 | assertEq(int256(swapDelta.amount0()), amount); 77 | 78 | assertEq(counter.beforeSwapCount(poolId), 1); 79 | assertEq(counter.afterSwapCount(poolId), 1); 80 | } 81 | 82 | // --- Util Helper --- // 83 | function swap(PoolKey memory key, int256 amountSpecified, bool zeroForOne, bytes memory hookData) 84 | internal 85 | returns (BalanceDelta swapDelta) 86 | { 87 | IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ 88 | zeroForOne: zeroForOne, 89 | amountSpecified: amountSpecified, 90 | sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 // unlimited impact 91 | }); 92 | 93 | PoolSwapTest.TestSettings memory testSettings = 94 | PoolSwapTest.TestSettings({ 95 | takeClaims: true, 96 | settleUsingBurn: false}); 97 | 98 | swapDelta = swapRouter.swap(key, params, testSettings, hookData); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Input/EtherInput.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useState } from "react"; 2 | import { ArrowsRightLeftIcon } from "@heroicons/react/24/outline"; 3 | import { CommonInputProps, InputBase, SIGNED_NUMBER_REGEX } from "~~/components/scaffold-eth"; 4 | import { useGlobalState } from "~~/services/store/store"; 5 | 6 | const MAX_DECIMALS_USD = 2; 7 | 8 | function etherValueToDisplayValue(usdMode: boolean, etherValue: string, nativeCurrencyPrice: number) { 9 | if (usdMode && nativeCurrencyPrice) { 10 | const parsedEthValue = parseFloat(etherValue); 11 | if (Number.isNaN(parsedEthValue)) { 12 | return etherValue; 13 | } else { 14 | // We need to round the value rather than use toFixed, 15 | // since otherwise a user would not be able to modify the decimal value 16 | return ( 17 | Math.round(parsedEthValue * nativeCurrencyPrice * 10 ** MAX_DECIMALS_USD) / 18 | 10 ** MAX_DECIMALS_USD 19 | ).toString(); 20 | } 21 | } else { 22 | return etherValue; 23 | } 24 | } 25 | 26 | function displayValueToEtherValue(usdMode: boolean, displayValue: string, nativeCurrencyPrice: number) { 27 | if (usdMode && nativeCurrencyPrice) { 28 | const parsedDisplayValue = parseFloat(displayValue); 29 | if (Number.isNaN(parsedDisplayValue)) { 30 | // Invalid number. 31 | return displayValue; 32 | } else { 33 | // Compute the ETH value if a valid number. 34 | return (parsedDisplayValue / nativeCurrencyPrice).toString(); 35 | } 36 | } else { 37 | return displayValue; 38 | } 39 | } 40 | 41 | /** 42 | * Input for ETH amount with USD conversion. 43 | * 44 | * onChange will always be called with the value in ETH 45 | */ 46 | export const EtherInput = ({ value, name, placeholder, onChange, disabled }: CommonInputProps) => { 47 | const [transitoryDisplayValue, setTransitoryDisplayValue] = useState(); 48 | const nativeCurrencyPrice = useGlobalState(state => state.nativeCurrencyPrice); 49 | const [usdMode, setUSDMode] = useState(false); 50 | 51 | // The displayValue is derived from the ether value that is controlled outside of the component 52 | // In usdMode, it is converted to its usd value, in regular mode it is unaltered 53 | const displayValue = useMemo(() => { 54 | const newDisplayValue = etherValueToDisplayValue(usdMode, value, nativeCurrencyPrice); 55 | if (transitoryDisplayValue && parseFloat(newDisplayValue) === parseFloat(transitoryDisplayValue)) { 56 | return transitoryDisplayValue; 57 | } 58 | // Clear any transitory display values that might be set 59 | setTransitoryDisplayValue(undefined); 60 | return newDisplayValue; 61 | }, [nativeCurrencyPrice, transitoryDisplayValue, usdMode, value]); 62 | 63 | const handleChangeNumber = (newValue: string) => { 64 | if (newValue && !SIGNED_NUMBER_REGEX.test(newValue)) { 65 | return; 66 | } 67 | 68 | // Following condition is a fix to prevent usdMode from experiencing different display values 69 | // than what the user entered. This can happen due to floating point rounding errors that are introduced in the back and forth conversion 70 | if (usdMode) { 71 | const decimals = newValue.split(".")[1]; 72 | if (decimals && decimals.length > MAX_DECIMALS_USD) { 73 | return; 74 | } 75 | } 76 | 77 | // Since the display value is a derived state (calculated from the ether value), usdMode would not allow introducing a decimal point. 78 | // This condition handles a transitory state for a display value with a trailing decimal sign 79 | if (newValue.endsWith(".") || newValue.endsWith(".0")) { 80 | setTransitoryDisplayValue(newValue); 81 | } else { 82 | setTransitoryDisplayValue(undefined); 83 | } 84 | 85 | const newEthValue = displayValueToEtherValue(usdMode, newValue, nativeCurrencyPrice); 86 | onChange(newEthValue); 87 | }; 88 | 89 | const toggleMode = () => { 90 | setUSDMode(!usdMode); 91 | }; 92 | 93 | return ( 94 | {usdMode ? "$" : "Ξ"}} 101 | suffix={ 102 | 109 | } 110 | /> 111 | ); 112 | }; 113 | -------------------------------------------------------------------------------- /nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { Abi, AbiFunction } from "abitype"; 3 | import { Address, TransactionReceipt } from "viem"; 4 | import { useContractWrite, useNetwork, useWaitForTransaction } from "wagmi"; 5 | import { 6 | ContractInput, 7 | IntegerInput, 8 | TxReceipt, 9 | getFunctionInputKey, 10 | getInitialFormState, 11 | getParsedContractFunctionArgs, 12 | getParsedError, 13 | } from "~~/components/scaffold-eth"; 14 | import { useTransactor } from "~~/hooks/scaffold-eth"; 15 | import { getTargetNetwork, notification } from "~~/utils/scaffold-eth"; 16 | import { Button } from "@nextui-org/react"; 17 | 18 | type WriteOnlyFunctionFormProps = { 19 | abiFunction: AbiFunction; 20 | onChange: () => void; 21 | contractAddress: Address; 22 | }; 23 | 24 | export const WriteOnlyFunctionForm = ({ abiFunction, onChange, contractAddress }: WriteOnlyFunctionFormProps) => { 25 | const [form, setForm] = useState>(() => getInitialFormState(abiFunction)); 26 | const [txValue, setTxValue] = useState(""); 27 | const { chain } = useNetwork(); 28 | const writeTxn = useTransactor(); 29 | const writeDisabled = !chain || chain?.id !== getTargetNetwork().id; 30 | 31 | const { 32 | data: result, 33 | isLoading, 34 | writeAsync, 35 | } = useContractWrite({ 36 | chainId: getTargetNetwork().id, 37 | address: contractAddress, 38 | functionName: abiFunction.name, 39 | abi: [abiFunction] as Abi, 40 | args: getParsedContractFunctionArgs(form), 41 | }); 42 | 43 | const handleWrite = async () => { 44 | if (writeAsync) { 45 | try { 46 | const makeWriteWithParams = () => writeAsync({ value: BigInt(txValue) }); 47 | await writeTxn(makeWriteWithParams); 48 | onChange(); 49 | } catch (e: any) { 50 | const message = getParsedError(e); 51 | notification.error(message); 52 | } 53 | } 54 | }; 55 | 56 | const [displayedTxResult, setDisplayedTxResult] = useState(); 57 | const { data: txResult } = useWaitForTransaction({ 58 | hash: result?.hash, 59 | }); 60 | useEffect(() => { 61 | setDisplayedTxResult(txResult); 62 | }, [txResult]); 63 | 64 | // TODO use `useMemo` to optimize also update in ReadOnlyFunctionForm 65 | const inputs = abiFunction.inputs.map((input, inputIndex) => { 66 | const key = getFunctionInputKey(abiFunction.name, input, inputIndex); 67 | return ( 68 | { 71 | setDisplayedTxResult(undefined); 72 | setForm(updatedFormValue); 73 | }} 74 | form={form} 75 | stateObjectKey={key} 76 | paramType={input} 77 | /> 78 | ); 79 | }); 80 | const zeroInputs = inputs.length === 0 && abiFunction.stateMutability !== "payable"; 81 | 82 | return ( 83 |
84 |
85 |

{abiFunction.name}

86 | {inputs} 87 | {abiFunction.stateMutability === "payable" ? ( 88 | { 91 | setDisplayedTxResult(undefined); 92 | setTxValue(updatedTxValue); 93 | }} 94 | placeholder="value (wei)" 95 | /> 96 | ) : null} 97 |
98 | {!zeroInputs && ( 99 |
100 | {displayedTxResult ? : null} 101 |
102 | )} 103 |
110 | 114 |
115 |
116 |
117 | {zeroInputs && txResult ? ( 118 |
119 | 120 |
121 | ) : null} 122 |
123 | ); 124 | }; 125 | -------------------------------------------------------------------------------- /nextjs/pages/debug.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { Button } from "@nextui-org/react"; 3 | import { watchContractEvent } from "@wagmi/core"; 4 | import type { NextPage } from "next"; 5 | import { useLocalStorage } from "usehooks-ts"; 6 | import { useChainId } from "wagmi"; 7 | import { MetaHeader } from "~~/components/MetaHeader"; 8 | import { ContractUI } from "~~/components/scaffold-eth"; 9 | import { poolManagerABI, poolManagerAddress } from "~~/generated/generated"; 10 | import { DEBUGGABLE_ADDRESSES } from "~~/utils/config"; 11 | import { notification } from "~~/utils/scaffold-eth"; 12 | import { ContractName } from "~~/utils/scaffold-eth/contract"; 13 | import { getContractNames } from "~~/utils/scaffold-eth/contractNames"; 14 | 15 | const selectedContractStorageKey = "scaffoldEth2.selectedContract"; 16 | const contractNames = getContractNames(); 17 | 18 | const Debug: NextPage = () => { 19 | const chainId = useChainId(); 20 | const [selectedContract, setSelectedContract] = useLocalStorage( 21 | selectedContractStorageKey, 22 | contractNames[0], 23 | ); 24 | 25 | useEffect(() => { 26 | if (!contractNames.includes(selectedContract)) { 27 | setSelectedContract(contractNames[0]); 28 | } 29 | }, [selectedContract, setSelectedContract]); 30 | 31 | useEffect(() => { 32 | const unwatch = watchContractEvent( 33 | { 34 | address: poolManagerAddress[chainId as keyof typeof poolManagerAddress], 35 | abi: poolManagerABI, 36 | eventName: "Initialize", 37 | }, 38 | log => { 39 | const successMsg = ( 40 |
41 |

New Pool Created!

42 |

43 | - Pool ID: {log[0].args.id} 44 |

45 |

46 | - Hook Address:{" "} 47 | {log[0].args.hooks} 48 |

49 |
50 | ); 51 | console.log(log); 52 | notification.success(successMsg, { duration: 1000000 }); 53 | }, 54 | ); 55 | 56 | return () => unwatch(); 57 | }, []); 58 | 59 | return ( 60 | <> 61 | 65 |
66 | {DEBUGGABLE_ADDRESSES.length === 0 ? ( 67 |

No contracts found!

68 | ) : ( 69 | <> 70 | {DEBUGGABLE_ADDRESSES.length > 1 && ( 71 |
72 |
77 | Inspect Contracts Deployed:{" "} 78 |
79 |
80 | {DEBUGGABLE_ADDRESSES.map(contract => ( 81 | 90 | ))} 91 |
92 | )} 93 | {DEBUGGABLE_ADDRESSES.map(contract => ( 94 | 101 | ))} 102 | 103 | )} 104 |
105 |
106 |

Debug Contracts

107 |

108 | You can debug & interact with your deployed contracts here. 109 |
Check{" "} 110 | 111 | packages / nextjs / pages / debug.tsx 112 | {" "} 113 |

114 |
115 | 116 | ); 117 | }; 118 | 119 | export default Debug; 120 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useScaffoldEventHistory.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { Abi, AbiEvent, ExtractAbiEventNames } from "abitype"; 3 | import { Hash } from "viem"; 4 | import { usePublicClient } from "wagmi"; 5 | import { useDeployedContractInfo } from "~~/hooks/scaffold-eth"; 6 | import { replacer } from "~~/utils/scaffold-eth/common"; 7 | import { 8 | ContractAbi, 9 | ContractName, 10 | UseScaffoldEventHistoryConfig, 11 | UseScaffoldEventHistoryData, 12 | } from "~~/utils/scaffold-eth/contract"; 13 | 14 | /** 15 | * @dev reads events from a deployed contract 16 | * @param config - The config settings 17 | * @param config.contractName - deployed contract name 18 | * @param config.eventName - name of the event to listen for 19 | * @param config.fromBlock - the block number to start reading events from 20 | * @param config.filters - filters to be applied to the event (parameterName: value) 21 | * @param config.blockData - if set to true it will return the block data for each event (default: false) 22 | * @param config.transactionData - if set to true it will return the transaction data for each event (default: false) 23 | * @param config.receiptData - if set to true it will return the receipt data for each event (default: false) 24 | */ 25 | export const useScaffoldEventHistory = < 26 | TContractName extends ContractName, 27 | TEventName extends ExtractAbiEventNames>, 28 | TBlockData extends boolean = false, 29 | TTransactionData extends boolean = false, 30 | TReceiptData extends boolean = false, 31 | >({ 32 | contractName, 33 | eventName, 34 | fromBlock, 35 | filters, 36 | blockData, 37 | transactionData, 38 | receiptData, 39 | }: UseScaffoldEventHistoryConfig) => { 40 | const [events, setEvents] = useState(); 41 | const [isLoading, setIsLoading] = useState(true); 42 | const [error, setError] = useState(); 43 | const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo(contractName); 44 | const publicClient = usePublicClient(); 45 | 46 | useEffect(() => { 47 | async function readEvents() { 48 | try { 49 | if (!deployedContractData) { 50 | throw new Error("Contract not found"); 51 | } 52 | 53 | const event = (deployedContractData.abi as Abi).find( 54 | part => part.type === "event" && part.name === eventName, 55 | ) as AbiEvent; 56 | 57 | const logs = await publicClient.getLogs({ 58 | address: deployedContractData?.address, 59 | event, 60 | args: filters as any, // TODO: check if it works and fix type 61 | fromBlock, 62 | }); 63 | const newEvents = []; 64 | for (let i = logs.length - 1; i >= 0; i--) { 65 | newEvents.push({ 66 | log: logs[i], 67 | args: logs[i].args, 68 | block: 69 | blockData && logs[i].blockHash === null 70 | ? null 71 | : await publicClient.getBlock({ blockHash: logs[i].blockHash as Hash }), 72 | transaction: 73 | transactionData && logs[i].transactionHash !== null 74 | ? await publicClient.getTransaction({ hash: logs[i].transactionHash as Hash }) 75 | : null, 76 | receipt: 77 | receiptData && logs[i].transactionHash !== null 78 | ? await publicClient.getTransactionReceipt({ hash: logs[i].transactionHash as Hash }) 79 | : null, 80 | }); 81 | } 82 | setEvents(newEvents); 83 | setError(undefined); 84 | } catch (e: any) { 85 | console.error(e); 86 | setEvents(undefined); 87 | setError(e); 88 | } finally { 89 | setIsLoading(false); 90 | } 91 | } 92 | if (!deployedContractLoading) { 93 | readEvents(); 94 | } 95 | // eslint-disable-next-line react-hooks/exhaustive-deps 96 | }, [ 97 | publicClient, 98 | fromBlock, 99 | contractName, 100 | eventName, 101 | deployedContractLoading, 102 | deployedContractData?.address, 103 | deployedContractData, 104 | // eslint-disable-next-line react-hooks/exhaustive-deps 105 | JSON.stringify(filters, replacer), 106 | blockData, 107 | transactionData, 108 | receiptData, 109 | ]); 110 | 111 | return { 112 | data: events?.map(addIndexedArgsToEvent) as UseScaffoldEventHistoryData< 113 | TContractName, 114 | TEventName, 115 | TBlockData, 116 | TTransactionData, 117 | TReceiptData 118 | >, 119 | isLoading: isLoading, 120 | error: error, 121 | }; 122 | }; 123 | 124 | export const addIndexedArgsToEvent = (event: any) => { 125 | if (event.args && !Array.isArray(event.args)) { 126 | return { ...event, args: { ...event.args, ...Object.values(event.args) } }; 127 | } 128 | 129 | return event; 130 | }; 131 | -------------------------------------------------------------------------------- /nextjs/hooks/scaffold-eth/useFetchBlocks.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | import { 3 | Block, 4 | Hash, 5 | Transaction, 6 | TransactionReceipt, 7 | createTestClient, 8 | publicActions, 9 | walletActions, 10 | webSocket, 11 | } from "viem"; 12 | import { foundry, hardhat } from "wagmi/chains"; 13 | import { decodeTransactionData } from "~~/utils/scaffold-eth"; 14 | 15 | const BLOCKS_PER_PAGE = 1; 16 | 17 | export const testClient = createTestClient({ 18 | chain: foundry, 19 | mode: "anvil", 20 | transport: webSocket("ws://127.0.0.1:8545"), 21 | }) 22 | .extend(publicActions) 23 | .extend(walletActions); 24 | 25 | export const useFetchBlocks = () => { 26 | const [blocks, setBlocks] = useState([]); 27 | const [transactionReceipts, setTransactionReceipts] = useState<{ 28 | [key: string]: TransactionReceipt; 29 | }>({}); 30 | const [currentPage, setCurrentPage] = useState(0); 31 | const [totalBlocks, setTotalBlocks] = useState(0n); 32 | const [error, setError] = useState(null); 33 | 34 | const fetchBlocks = useCallback(async () => { 35 | setError(null); 36 | 37 | try { 38 | const blockNumber = await testClient.getBlockNumber(); 39 | console.log("blockNumber", blockNumber) 40 | 41 | setTotalBlocks(blockNumber); 42 | 43 | const startingBlock = blockNumber - BigInt(currentPage * BLOCKS_PER_PAGE); 44 | const blockNumbersToFetch = Array.from( 45 | { length: Number(BLOCKS_PER_PAGE < startingBlock + 1n ? BLOCKS_PER_PAGE : startingBlock + 1n) }, 46 | (_, i) => startingBlock - BigInt(i), 47 | ); 48 | 49 | const blocksWithTransactions = blockNumbersToFetch.map(async blockNumber => { 50 | try { 51 | return testClient.getBlock({ blockNumber, includeTransactions: true }); 52 | } catch (err) { 53 | setError(err instanceof Error ? err : new Error("An error occurred.")); 54 | throw err; 55 | } 56 | }); 57 | const fetchedBlocks = await Promise.all(blocksWithTransactions); 58 | 59 | fetchedBlocks.forEach(block => { 60 | block.transactions.forEach(tx => decodeTransactionData(tx as Transaction)); 61 | }); 62 | 63 | const txReceipts = await Promise.all( 64 | fetchedBlocks.flatMap(block => 65 | block.transactions.map(async tx => { 66 | try { 67 | const receipt = await testClient.getTransactionReceipt({ hash: (tx as Transaction).hash }); 68 | return { [(tx as Transaction).hash]: receipt }; 69 | } catch (err) { 70 | setError(err instanceof Error ? err : new Error("An error occurred.")); 71 | throw err; 72 | } 73 | }), 74 | ), 75 | ); 76 | 77 | setBlocks(fetchedBlocks); 78 | setTransactionReceipts(prevReceipts => ({ ...prevReceipts, ...Object.assign({}, ...txReceipts) })); 79 | } catch (err) { 80 | setError(err instanceof Error ? err : new Error("An error occurred.")); 81 | } 82 | }, [currentPage]); 83 | 84 | useEffect(() => { 85 | fetchBlocks(); 86 | }, [fetchBlocks]); 87 | 88 | useEffect(() => { 89 | const handleNewBlock = async (newBlock: any) => { 90 | try { 91 | if (currentPage === 0) { 92 | if (newBlock.transactions.length > 0) { 93 | const transactionsDetails = await Promise.all( 94 | newBlock.transactions.map((txHash: string) => testClient.getTransaction({ hash: txHash as Hash })), 95 | ); 96 | newBlock.transactions = transactionsDetails; 97 | } 98 | 99 | newBlock.transactions.forEach((tx: Transaction) => decodeTransactionData(tx as Transaction)); 100 | 101 | const receipts = await Promise.all( 102 | newBlock.transactions.map(async (tx: Transaction) => { 103 | try { 104 | const receipt = await testClient.getTransactionReceipt({ hash: (tx as Transaction).hash }); 105 | return { [(tx as Transaction).hash]: receipt }; 106 | } catch (err) { 107 | setError(err instanceof Error ? err : new Error("An error occurred fetching receipt.")); 108 | throw err; 109 | } 110 | }), 111 | ); 112 | 113 | setBlocks(prevBlocks => [newBlock, ...prevBlocks.slice(0, BLOCKS_PER_PAGE - 1)]); 114 | setTransactionReceipts(prevReceipts => ({ ...prevReceipts, ...Object.assign({}, ...receipts) })); 115 | } 116 | if (newBlock.number) { 117 | setTotalBlocks(newBlock.number); 118 | } 119 | } catch (err) { 120 | setError(err instanceof Error ? err : new Error("An error occurred.")); 121 | } 122 | }; 123 | 124 | return testClient.watchBlocks({ onBlock: handleNewBlock, includeTransactions: true }); 125 | }, [currentPage]); 126 | 127 | return { 128 | blocks, 129 | transactionReceipts, 130 | currentPage, 131 | totalBlocks, 132 | setCurrentPage, 133 | error, 134 | }; 135 | }; 136 | -------------------------------------------------------------------------------- /nextjs/services/web3/wagmi-burner/burnerWalletConfig.ts: -------------------------------------------------------------------------------- 1 | import { Chain, Wallet } from "@rainbow-me/rainbowkit"; 2 | import { hardhat } from "wagmi/chains"; 3 | import scaffoldConfig from "~~/scaffold.config"; 4 | import { 5 | BurnerConnector, 6 | burnerWalletId, 7 | burnerWalletName, 8 | defaultBurnerChainId, 9 | } from "~~/services/web3/wagmi-burner/BurnerConnector"; 10 | import { getTargetNetwork } from "~~/utils/scaffold-eth"; 11 | 12 | const { onlyLocalBurnerWallet } = scaffoldConfig; 13 | const targetNetwork = getTargetNetwork(); 14 | export interface BurnerWalletOptions { 15 | chains: Chain[]; 16 | } 17 | 18 | const burnerWalletIconBase64 = 19 | "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzUzIiBoZWlnaHQ9IjM1MiIgdmlld0JveD0iMCAwIDM1MyAzNTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjAuNzE2MzA5IiB5PSIwLjMxNzEzOSIgd2lkdGg9IjM1MS4zOTQiIGhlaWdodD0iMzUxLjM5NCIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzNfMTUxKSIvPgo8Y2lyY2xlIGN4PSIzNC40OTUzIiBjeT0iMzQuNDk1MyIgcj0iMzQuNDk1MyIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMjA3LjAxOCAyNTQuMTIpIiBmaWxsPSIjRkY2NjBBIi8+CjxwYXRoIGQ9Ik0xNTQuMzE4IDMxNy45NTVDMTcxLjI3MyAzMTAuODkgMTc2LjU4MiAyOTAuNzE1IDE3Ni4xNTcgMjgzLjQ4N0wyMDcuMDE4IDI4OC44NjRDMjA3LjAxOCAzMDMuMzE0IDIwMC4yMTIgMzA5LjQwMiAxOTcuODI0IDMxMi40MzNDMTkzLjQ3NCAzMTcuOTU1IDE3My4zNTEgMzMwLjAzIDE1NC4zMTggMzE3Ljk1NVoiIGZpbGw9InVybCgjcGFpbnQxX3JhZGlhbF8zXzE1MSkiLz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfZF8zXzE1MSkiPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTIyNy4zNzcgMzAyLjI3NkMyMjYuNDI2IDMwNS44OTcgMjMwLjMxNSAzMDkuNDA1IDIzMy4zOTYgMzA3LjI3OUMyNTQuNTM4IDI5Mi42ODQgMjcwLjQ3OSAyNjkuOTQ1IDI3NC44OSAyNDcuNDg5QzI4Mi4yNCAyMTAuMDcxIDI3Mi4yMzUgMTc1LjcyNyAyMzguMDI4IDE0NS45MjVDMjAwLjg3NCAxMTMuNTU2IDE5MS44NDQgODguNDU2MSAxOTAuMTYyIDUwLjg3MThDMTg5Ljc5NyA0Mi43MjE4IDE4MS42MDQgMzcuMjk0NyAxNzQuODI0IDQxLjgzMTdDMTUyLjY2OCA1Ni42NTc0IDEzMi41MTIgODQuNDk5IDEzOC45MTEgMTIwLjc1OEMxNDEuMDA0IDEzMi42MjEgMTQ2Ljc5NCAxNDEuMDE2IDE1MS45NyAxNDguNTIzQzE1OC40OTEgMTU3Ljk3OCAxNjQuMDM5IDE2Ni4wMjMgMTU5Ljk5NyAxNzcuODFDMTU1LjIwMyAxOTEuNzk0IDEzOS4xMzQgMTk5LjE2MiAxMjguNzQ3IDE5Mi40MjlDMTE0LjE3IDE4Mi45ODEgMTEzLjI1MyAxNjYuNjUxIDExNy45NjkgMTQ5LjQ1NkMxMTguOTAyIDE0Ni4wNTUgMTE1LjQ3MSAxNDMuMjA0IDExMi42OCAxNDUuMzU5QzkxLjM2MDQgMTYxLjgyMSA2OS4xNTMyIDE5OS4yNjcgNzcuNjY0NyAyNDcuNDg5Qzg1Ljk3OTIgMjc2LjIxMiA5Ny45Mjc3IDI5Mi41MzcgMTEwLjk3MSAzMDEuNTQxQzExMy43NjMgMzAzLjQ2OCAxMTcuMTU5IDMwMC42MzEgMTE2LjU5NyAyOTcuMjg2QzExNi4wODEgMjk0LjIxMiAxMTUuODEzIDI5MS4wNTQgMTE1LjgxMyAyODcuODMzQzExNS44MTMgMjU2LjUxMyAxNDEuMjAzIDIzMS4xMjMgMTcyLjUyMyAyMzEuMTIzQzIwMy44NDIgMjMxLjEyMyAyMjkuMjMyIDI1Ni41MTMgMjI5LjIzMiAyODcuODMzQzIyOS4yMzIgMjkyLjgyNCAyMjguNTg3IDI5Ny42NjUgMjI3LjM3NyAzMDIuMjc2WiIgZmlsbD0idXJsKCNwYWludDJfbGluZWFyXzNfMTUxKSIvPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfM18xNTEiIHg9IjcyLjExMTIiIHk9IjM2LjQ5NCIgd2lkdGg9IjIwOC43NDIiIGhlaWdodD0iMjc1LjEyIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjg0NTA2Ii8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAxIDAgMCAwIDAgMC40MiAwIDAgMCAwIDAgMCAwIDAgMC43IDAiLz4KPGZlQmxlbmQgbW9kZT0ibXVsdGlwbHkiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvd18zXzE1MSIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd18zXzE1MSIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzNfMTUxIiB4MT0iMTc2LjQxMyIgeTE9IjAuMzE3MTM5IiB4Mj0iMTc2LjQxMyIgeTI9IjM1MS43MTEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZGRjI3OSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRkQzMzYiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJhZGlhbEdyYWRpZW50IGlkPSJwYWludDFfcmFkaWFsXzNfMTUxIiBjeD0iMCIgY3k9IjAiIHI9IjEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKDIxOC4wNDggMjQ5LjM0Nykgcm90YXRlKDEyNC4wMTgpIHNjYWxlKDg5LjI5NTUgMjY0LjgwOSkiPgo8c3RvcCBvZmZzZXQ9IjAuNjQwODUiIHN0b3AtY29sb3I9IiNGRjY2MEEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkZCRTE1Ii8+CjwvcmFkaWFsR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQyX2xpbmVhcl8zXzE1MSIgeDE9IjE3Ni40ODIiIHkxPSI0MC4xODQxIiB4Mj0iMTc2LjQ4MiIgeTI9IjMxNy4yNzgiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agb2Zmc2V0PSIwLjMzODU0MiIgc3RvcC1jb2xvcj0iI0ZGOEYzRiIvPgo8c3RvcCBvZmZzZXQ9IjAuNjU2MjUiIHN0b3AtY29sb3I9IiNGRjcwMjAiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkYzRDAwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="; 20 | 21 | /** 22 | * Wagmi config for burner wallet 23 | * @param param0 24 | * @returns 25 | */ 26 | export const burnerWalletConfig = ({ chains }: BurnerWalletOptions): Wallet => ({ 27 | id: burnerWalletId, 28 | name: burnerWalletName, 29 | iconUrl: burnerWalletIconBase64, 30 | iconBackground: "#ffffff", 31 | hidden: () => { 32 | if (onlyLocalBurnerWallet) { 33 | return targetNetwork.id !== hardhat.id; 34 | } 35 | 36 | return false; 37 | }, 38 | createConnector: () => { 39 | const connector = new BurnerConnector({ chains, options: { defaultChainId: defaultBurnerChainId } }); 40 | 41 | return { 42 | connector, 43 | }; 44 | }, 45 | }); 46 | --------------------------------------------------------------------------------