create-stylus 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/issue_template.md +7 -0
- package/.github/pull_request_template.md +11 -0
- package/.github/workflows/release-alpha.yml +32 -0
- package/.github/workflows/release-manual.yml +26 -0
- package/.yarnrc.yml +1 -0
- package/CONTRIBUTING.md +42 -0
- package/README.md +66 -0
- package/bin/create-dapp-ss.js +4 -0
- package/package.json +46 -0
- package/rollup.config.js +22 -0
- package/src/cli.ts +14 -0
- package/src/extensions.json +14 -0
- package/src/main.ts +72 -0
- package/src/tasks/copy-extension-file.ts +227 -0
- package/src/tasks/copy-template-files.ts +252 -0
- package/src/tasks/create-first-git-commit.ts +35 -0
- package/src/tasks/create-project-directory.ts +34 -0
- package/src/tasks/index.ts +5 -0
- package/src/tasks/install-packages.ts +15 -0
- package/src/tasks/prettier-format.ts +17 -0
- package/src/types.ts +31 -0
- package/src/utils/consts.ts +1 -0
- package/src/utils/find-files-recursively.ts +19 -0
- package/src/utils/link.ts +44 -0
- package/src/utils/load-extensions.ts +10 -0
- package/src/utils/merge-package-json.ts +33 -0
- package/src/utils/parse-arguments-into-options.ts +38 -0
- package/src/utils/prompt-for-missing-options.ts +53 -0
- package/src/utils/render-intro-message.ts +11 -0
- package/src/utils/render-outro-message.ts +34 -0
- package/templates/base/.github/ISSUE_TEMPLATE/bug_report.yml +58 -0
- package/templates/base/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/templates/base/.github/pull_request_template.md +16 -0
- package/templates/base/.github/workflows/lint.yaml +300 -0
- package/templates/base/.gitignore.template.mjs +19 -0
- package/templates/base/.gitmodules +0 -0
- package/templates/base/.husky/pre-commit +4 -0
- package/templates/base/.lintstagedrc.js +21 -0
- package/templates/base/.vscode/settings.json +7 -0
- package/templates/base/.yarn/plugins/@yarnpkg/plugin-typescript.cjs +9 -0
- package/templates/base/.yarn/releases/yarn-3.2.3.cjs +783 -0
- package/templates/base/.yarnrc.yml +11 -0
- package/templates/base/CONTRIBUTING.md +86 -0
- package/templates/base/LICENCE +21 -0
- package/templates/base/dist/cli.js +683 -0
- package/templates/base/dist/cli.js.map +1 -0
- package/templates/base/nitro-devnode/LICENSE +201 -0
- package/templates/base/nitro-devnode/README.md +70 -0
- package/templates/base/nitro-devnode/run-dev-node.sh +132 -0
- package/templates/base/nitro-devnode/start-chain-with-cors.sh +150 -0
- package/templates/base/nitro-devnode/stylus-deployer-bytecode.txt +1 -0
- package/templates/base/nitro-devnode/stylus-dev/Dockerfile +10 -0
- package/templates/base/package.json +44 -0
- package/templates/base/packages/nextjs/.env.example +13 -0
- package/templates/base/packages/nextjs/.eslintignore +11 -0
- package/templates/base/packages/nextjs/.eslintrc.json +15 -0
- package/templates/base/packages/nextjs/.gitignore.template.mjs +42 -0
- package/templates/base/packages/nextjs/.prettierrc.js +9 -0
- package/templates/base/packages/nextjs/.prettierrc.json +8 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/AddressCodeTab.tsx +27 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx +36 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/AddressLogsTab.tsx +21 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/AddressStorageTab.tsx +61 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/BackButton.tsx +12 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx +102 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/PaginationButton.tsx +39 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/SearchBar.tsx +49 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/TransactionHash.tsx +28 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx +71 -0
- package/templates/base/packages/nextjs/app/blockexplorer/_components/index.tsx +7 -0
- package/templates/base/packages/nextjs/app/blockexplorer/address/[address]/page.tsx +101 -0
- package/templates/base/packages/nextjs/app/blockexplorer/layout.tsx +12 -0
- package/templates/base/packages/nextjs/app/blockexplorer/page.tsx +83 -0
- package/templates/base/packages/nextjs/app/blockexplorer/transaction/[txHash]/page.tsx +23 -0
- package/templates/base/packages/nextjs/app/blockexplorer/transaction/_components/TransactionComp.tsx +152 -0
- package/templates/base/packages/nextjs/app/debug/_components/DebugContracts.tsx +73 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/ContractInput.tsx +84 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/ContractReadMethods.tsx +43 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/ContractUI.tsx +164 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/ContractVariables.tsx +50 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/ContractWriteMethods.tsx +49 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx +85 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/InheritanceTooltip.tsx +14 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx +102 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/Tuple.tsx +44 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/TupleArray.tsx +142 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/TxReceipt.tsx +42 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx +144 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/index.tsx +8 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/utilsContract.tsx +166 -0
- package/templates/base/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx +114 -0
- package/templates/base/packages/nextjs/app/debug/page.tsx +14 -0
- package/templates/base/packages/nextjs/app/layout.tsx +67 -0
- package/templates/base/packages/nextjs/app/not-found.tsx +16 -0
- package/templates/base/packages/nextjs/app/page.tsx +94 -0
- package/templates/base/packages/nextjs/components/Background.tsx +37 -0
- package/templates/base/packages/nextjs/components/Card.tsx +40 -0
- package/templates/base/packages/nextjs/components/Footer.tsx +93 -0
- package/templates/base/packages/nextjs/components/Header.tsx +114 -0
- package/templates/base/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx +77 -0
- package/templates/base/packages/nextjs/components/SwitchTheme.tsx +41 -0
- package/templates/base/packages/nextjs/components/ThemeProvider.tsx +13 -0
- package/templates/base/packages/nextjs/components/assets/BuidlGuidlLogo.tsx +18 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Address/Address.tsx +187 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Address/AddressCopyIcon.tsx +23 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Address/AddressLinkWrapper.tsx +29 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Balance.tsx +75 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/BlockieAvatar.tsx +17 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Faucet.tsx +131 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx +75 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx +120 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Input/Bytes32Input.tsx +31 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Input/BytesInput.tsx +28 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Input/EtherInput.tsx +128 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Input/InputBase.tsx +66 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Input/IntegerInput.tsx +63 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Input/index.ts +9 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/Input/utils.ts +109 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx +121 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressQRCodeModal.tsx +33 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/BurnerWalletModal.tsx +63 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx +48 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/WrongNetworkDropdown.tsx +32 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx +89 -0
- package/templates/base/packages/nextjs/components/scaffold-eth/index.tsx +7 -0
- package/templates/base/packages/nextjs/contracts/deployedContracts.ts +9 -0
- package/templates/base/packages/nextjs/contracts/externalContracts.ts +16 -0
- package/templates/base/packages/nextjs/eslint.config.mjs +32 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/index.ts +17 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useAnimationConfig.ts +20 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useContractLogs.ts +40 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useCopyToClipboard.ts +19 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts +86 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useDisplayUsdMode.ts +21 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useFetchBlocks.ts +133 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useInitializeNativeCurrencyPrice.ts +32 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useNativeCurrencyPrice.ts +34 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts +22 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useOutsideClick.ts +23 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContract.ts +65 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldEventHistory.ts +213 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldReadContract.ts +80 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldWatchContractEvent.ts +40 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldWriteContract.ts +191 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useSelectedNetwork.ts +18 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useTargetNetwork.ts +23 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx +114 -0
- package/templates/base/packages/nextjs/hooks/scaffold-eth/useWatchBalance.ts +21 -0
- package/templates/base/packages/nextjs/icons/CompassIcon.tsx +39 -0
- package/templates/base/packages/nextjs/icons/DarkBugAntIcon.tsx +30 -0
- package/templates/base/packages/nextjs/icons/LightBugAntIcon.tsx +52 -0
- package/templates/base/packages/nextjs/next-env.d.ts +5 -0
- package/templates/base/packages/nextjs/next.config.js +19 -0
- package/templates/base/packages/nextjs/package.json +58 -0
- package/templates/base/packages/nextjs/postcss.config.js +6 -0
- package/templates/base/packages/nextjs/public/debug-image.png +0 -0
- package/templates/base/packages/nextjs/public/favicon.png +0 -0
- package/templates/base/packages/nextjs/public/logo.svg +8 -0
- package/templates/base/packages/nextjs/public/manifest.json +5 -0
- package/templates/base/packages/nextjs/public/thumbnail.jpg +0 -0
- package/templates/base/packages/nextjs/react-copy-to-clipboard.d.ts +44 -0
- package/templates/base/packages/nextjs/scaffold.config.ts +56 -0
- package/templates/base/packages/nextjs/services/store/store.ts +39 -0
- package/templates/base/packages/nextjs/services/web3/wagmiConfig.tsx +44 -0
- package/templates/base/packages/nextjs/services/web3/wagmiConnectors.tsx +51 -0
- package/templates/base/packages/nextjs/styles/globals.css +80 -0
- package/templates/base/packages/nextjs/tailwind.config.js +97 -0
- package/templates/base/packages/nextjs/tsconfig.json +28 -0
- package/templates/base/packages/nextjs/types/abitype/abi.d.ts +16 -0
- package/templates/base/packages/nextjs/types/utils.ts +3 -0
- package/templates/base/packages/nextjs/utils/scaffold-eth/block.ts +17 -0
- package/templates/base/packages/nextjs/utils/scaffold-eth/common.ts +8 -0
- package/templates/base/packages/nextjs/utils/scaffold-eth/contract.ts +352 -0
- package/templates/base/packages/nextjs/utils/scaffold-eth/contractsData.ts +11 -0
- package/templates/base/packages/nextjs/utils/scaffold-eth/decodeTxData.ts +65 -0
- package/templates/base/packages/nextjs/utils/scaffold-eth/fetchPriceFromUniswap.ts +72 -0
- package/templates/base/packages/nextjs/utils/scaffold-eth/getMetadata.ts +50 -0
- package/templates/base/packages/nextjs/utils/scaffold-eth/getParsedError.ts +35 -0
- package/templates/base/packages/nextjs/utils/scaffold-eth/index.ts +6 -0
- package/templates/base/packages/nextjs/utils/scaffold-eth/notification.tsx +90 -0
- package/templates/base/packages/nextjs/utils/scaffold-stylus/burner.ts +59 -0
- package/templates/base/packages/nextjs/utils/scaffold-stylus/chain.ts +42 -0
- package/templates/base/packages/nextjs/utils/scaffold-stylus/index.ts +3 -0
- package/templates/base/packages/nextjs/utils/scaffold-stylus/networks.ts +94 -0
- package/templates/base/packages/nextjs/vercel.json +3 -0
- package/templates/base/packages/stylus/.env.example +16 -0
- package/templates/base/packages/stylus/.eslintrc.js +23 -0
- package/templates/base/packages/stylus/.gitignore.template.mjs +7 -0
- package/templates/base/packages/stylus/jest.config.js +15 -0
- package/templates/base/packages/stylus/package.json +48 -0
- package/templates/base/packages/stylus/scripts/deploy.ts +46 -0
- package/templates/base/packages/stylus/scripts/deploy_contract.ts +84 -0
- package/templates/base/packages/stylus/scripts/deploy_wrapper.ts +39 -0
- package/templates/base/packages/stylus/scripts/export_abi.ts +87 -0
- package/templates/base/packages/stylus/scripts/index.ts +0 -0
- package/templates/base/packages/stylus/scripts/new_module.sh +35 -0
- package/templates/base/packages/stylus/scripts/test_network.ts +31 -0
- package/templates/base/packages/stylus/scripts/utils/command.ts +152 -0
- package/templates/base/packages/stylus/scripts/utils/contract.ts +228 -0
- package/templates/base/packages/stylus/scripts/utils/deployment.ts +260 -0
- package/templates/base/packages/stylus/scripts/utils/index.ts +6 -0
- package/templates/base/packages/stylus/scripts/utils/network.ts +132 -0
- package/templates/base/packages/stylus/scripts/utils/type.ts +51 -0
- package/templates/base/packages/stylus/scripts/utils.ts +3 -0
- package/templates/base/packages/stylus/tsconfig.json +41 -0
- package/templates/base/packages/stylus/your-contract/.cargo/config.toml +18 -0
- package/templates/base/packages/stylus/your-contract/Cargo.lock +5761 -0
- package/templates/base/packages/stylus/your-contract/Cargo.toml +48 -0
- package/templates/base/packages/stylus/your-contract/examples/counter.rs +78 -0
- package/templates/base/packages/stylus/your-contract/header.png +0 -0
- package/templates/base/packages/stylus/your-contract/licenses/Apache-2.0 +201 -0
- package/templates/base/packages/stylus/your-contract/licenses/COPYRIGHT.md +5 -0
- package/templates/base/packages/stylus/your-contract/licenses/DCO.txt +34 -0
- package/templates/base/packages/stylus/your-contract/licenses/MIT +21 -0
- package/templates/base/packages/stylus/your-contract/rust-toolchain.toml +2 -0
- package/templates/base/packages/stylus/your-contract/src/lib.rs +241 -0
- package/templates/base/packages/stylus/your-contract/src/main.rs +10 -0
- package/templates/base/readme.md +352 -0
- package/templates/base/yarn.lock +17859 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Address, formatEther } from "viem";
|
|
4
|
+
import { useDisplayUsdMode } from "~~/hooks/scaffold-eth/useDisplayUsdMode";
|
|
5
|
+
import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
|
|
6
|
+
import { useWatchBalance } from "~~/hooks/scaffold-eth/useWatchBalance";
|
|
7
|
+
import { useGlobalState } from "~~/services/store/store";
|
|
8
|
+
|
|
9
|
+
type BalanceProps = {
|
|
10
|
+
address?: Address;
|
|
11
|
+
className?: string;
|
|
12
|
+
usdMode?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Display (ETH & USD) balance of an ETH address.
|
|
17
|
+
*/
|
|
18
|
+
export const Balance = ({ address, className = "", usdMode }: BalanceProps) => {
|
|
19
|
+
const { targetNetwork } = useTargetNetwork();
|
|
20
|
+
const nativeCurrencyPrice = useGlobalState(state => state.nativeCurrency.price);
|
|
21
|
+
const isNativeCurrencyPriceFetching = useGlobalState(state => state.nativeCurrency.isFetching);
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
data: balance,
|
|
25
|
+
isError,
|
|
26
|
+
isLoading,
|
|
27
|
+
} = useWatchBalance({
|
|
28
|
+
address,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const { displayUsdMode, toggleDisplayUsdMode } = useDisplayUsdMode({ defaultUsdMode: usdMode });
|
|
32
|
+
|
|
33
|
+
if (!address || isLoading || balance === null || (isNativeCurrencyPriceFetching && nativeCurrencyPrice === 0)) {
|
|
34
|
+
return (
|
|
35
|
+
<div className="animate-pulse flex space-x-4">
|
|
36
|
+
<div className="rounded-md bg-slate-300 h-6 w-6"></div>
|
|
37
|
+
<div className="flex items-center space-y-6">
|
|
38
|
+
<div className="h-2 w-28 bg-slate-300 rounded-sm"></div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isError) {
|
|
45
|
+
return (
|
|
46
|
+
<div className="border-2 border-base-content/30 rounded-md px-2 flex flex-col items-center max-w-fit cursor-pointer">
|
|
47
|
+
<div className="text-warning">Error</div>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const formattedBalance = balance ? Number(formatEther(balance.value)) : 0;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<button
|
|
56
|
+
className={`btn btn-sm btn-ghost flex flex-col font-normal items-center hover:bg-transparent ${className}`}
|
|
57
|
+
onClick={toggleDisplayUsdMode}
|
|
58
|
+
type="button"
|
|
59
|
+
>
|
|
60
|
+
<div className="w-full flex items-center justify-center">
|
|
61
|
+
{displayUsdMode ? (
|
|
62
|
+
<>
|
|
63
|
+
<span className="text-[0.8em] font-bold mr-1">$</span>
|
|
64
|
+
<span>{(formattedBalance * nativeCurrencyPrice).toFixed(2)}</span>
|
|
65
|
+
</>
|
|
66
|
+
) : (
|
|
67
|
+
<>
|
|
68
|
+
<span>{formattedBalance.toFixed(4)}</span>
|
|
69
|
+
<span className="text-[0.8em] font-bold ml-1">{targetNetwork.nativeCurrency.symbol}</span>
|
|
70
|
+
</>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
</button>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { AvatarComponent } from "@rainbow-me/rainbowkit";
|
|
4
|
+
import { blo } from "blo";
|
|
5
|
+
|
|
6
|
+
// Custom Avatar for RainbowKit
|
|
7
|
+
export const BlockieAvatar: AvatarComponent = ({ address, ensImage, size }) => (
|
|
8
|
+
// Don't want to use nextJS Image here (and adding remote patterns for the URL)
|
|
9
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
10
|
+
<img
|
|
11
|
+
className="rounded-full"
|
|
12
|
+
src={ensImage || blo(address as `0x${string}`)}
|
|
13
|
+
width={size}
|
|
14
|
+
height={size}
|
|
15
|
+
alt={`${address} avatar`}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Address as AddressType, createWalletClient, http, parseEther } from "viem";
|
|
5
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
6
|
+
import { useAccount } from "wagmi";
|
|
7
|
+
import { BanknotesIcon } from "@heroicons/react/24/outline";
|
|
8
|
+
import { Address, AddressInput, Balance, EtherInput } from "~~/components/scaffold-eth";
|
|
9
|
+
import { useTransactor } from "~~/hooks/scaffold-eth";
|
|
10
|
+
import { notification } from "~~/utils/scaffold-eth";
|
|
11
|
+
import { arbitrumNitro } from "~~/utils/scaffold-stylus/chain";
|
|
12
|
+
|
|
13
|
+
// Account index to use from generated arbitrum accounts.
|
|
14
|
+
const FAUCET_ACCOUNT_INDEX = 0;
|
|
15
|
+
|
|
16
|
+
const localWalletClient = createWalletClient({
|
|
17
|
+
account: privateKeyToAccount(arbitrumNitro.accounts[0].privateKey),
|
|
18
|
+
chain: arbitrumNitro,
|
|
19
|
+
transport: http(arbitrumNitro.rpcUrls.default.http[0]),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Faucet modal which lets you send ETH to any address.
|
|
24
|
+
*/
|
|
25
|
+
export const Faucet = () => {
|
|
26
|
+
const [loading, setLoading] = useState(false);
|
|
27
|
+
const [inputAddress, setInputAddress] = useState<AddressType>();
|
|
28
|
+
const [faucetAddress, setFaucetAddress] = useState<AddressType>(arbitrumNitro.accounts[0].address);
|
|
29
|
+
const [sendValue, setSendValue] = useState("");
|
|
30
|
+
|
|
31
|
+
const { chain: ConnectedChain } = useAccount();
|
|
32
|
+
|
|
33
|
+
const faucetTxn = useTransactor(localWalletClient);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const getFaucetAddress = async () => {
|
|
37
|
+
try {
|
|
38
|
+
const accounts = await localWalletClient.getAddresses();
|
|
39
|
+
setFaucetAddress(accounts[FAUCET_ACCOUNT_INDEX]);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
notification.error(
|
|
42
|
+
<>
|
|
43
|
+
<p className="font-bold mt-0 mb-1">Cannot connect to local provider</p>
|
|
44
|
+
<p className="m-0">
|
|
45
|
+
- Did you forget to run <code className="italic bg-base-300 text-base font-bold">yarn chain</code> ?
|
|
46
|
+
</p>
|
|
47
|
+
<p className="mt-1 break-normal">
|
|
48
|
+
- Or you can change <code className="italic bg-base-300 text-base font-bold">targetNetwork</code> in{" "}
|
|
49
|
+
<code className="italic bg-base-300 text-base font-bold">scaffold.config.ts</code>
|
|
50
|
+
</p>
|
|
51
|
+
</>,
|
|
52
|
+
);
|
|
53
|
+
console.error("⚡️ ~ file: Faucet.tsx:getFaucetAddress ~ error", error);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
getFaucetAddress();
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
const sendETH = async () => {
|
|
60
|
+
if (!faucetAddress || !inputAddress) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
setLoading(true);
|
|
65
|
+
await faucetTxn({
|
|
66
|
+
to: inputAddress,
|
|
67
|
+
value: parseEther(sendValue as `${number}`),
|
|
68
|
+
account: faucetAddress,
|
|
69
|
+
});
|
|
70
|
+
setLoading(false);
|
|
71
|
+
setInputAddress(undefined);
|
|
72
|
+
setSendValue("");
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("⚡️ ~ file: Faucet.tsx:sendETH ~ error", error);
|
|
75
|
+
setLoading(false);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Render only on local chain
|
|
80
|
+
if (ConnectedChain?.id !== arbitrumNitro.id) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div>
|
|
86
|
+
<label htmlFor="faucet-modal" className="btn btn-primary btn-sm font-normal gap-1">
|
|
87
|
+
<BanknotesIcon className="h-4 w-4" />
|
|
88
|
+
<span>Faucet</span>
|
|
89
|
+
</label>
|
|
90
|
+
<input type="checkbox" id="faucet-modal" className="modal-toggle" />
|
|
91
|
+
<label htmlFor="faucet-modal" className="modal cursor-pointer">
|
|
92
|
+
<label className="modal-box relative">
|
|
93
|
+
{/* dummy input to capture event onclick on modal box */}
|
|
94
|
+
<input className="h-0 w-0 absolute top-0 left-0" />
|
|
95
|
+
<h3 className="text-xl font-bold mb-3">Local Faucet</h3>
|
|
96
|
+
<label htmlFor="faucet-modal" className="btn btn-ghost btn-sm btn-circle absolute right-3 top-3">
|
|
97
|
+
✕
|
|
98
|
+
</label>
|
|
99
|
+
<div className="space-y-3">
|
|
100
|
+
<div className="flex space-x-4">
|
|
101
|
+
<div>
|
|
102
|
+
<span className="text-sm font-bold">From:</span>
|
|
103
|
+
<Address address={faucetAddress} onlyEnsOrAddress />
|
|
104
|
+
</div>
|
|
105
|
+
<div>
|
|
106
|
+
<span className="text-sm font-bold pl-3">Available:</span>
|
|
107
|
+
<Balance address={faucetAddress} />
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<div className="flex flex-col space-y-3">
|
|
111
|
+
<AddressInput
|
|
112
|
+
placeholder="Destination Address"
|
|
113
|
+
value={inputAddress ?? ""}
|
|
114
|
+
onChange={value => setInputAddress(value as AddressType)}
|
|
115
|
+
/>
|
|
116
|
+
<EtherInput placeholder="Amount to send" value={sendValue} onChange={value => setSendValue(value)} />
|
|
117
|
+
<button className="h-10 btn btn-primary btn-sm px-2 rounded-full" onClick={sendETH} disabled={loading}>
|
|
118
|
+
{!loading ? (
|
|
119
|
+
<BanknotesIcon className="h-6 w-6" />
|
|
120
|
+
) : (
|
|
121
|
+
<span className="loading loading-spinner loading-sm"></span>
|
|
122
|
+
)}
|
|
123
|
+
<span>Send</span>
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</label>
|
|
128
|
+
</label>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { createWalletClient, http, parseEther } from "viem";
|
|
5
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
6
|
+
import { useAccount } from "wagmi";
|
|
7
|
+
import { useWatchBalance } from "~~/hooks/scaffold-eth/useWatchBalance";
|
|
8
|
+
import { BanknotesIcon } from "@heroicons/react/24/outline";
|
|
9
|
+
import { useTransactor } from "~~/hooks/scaffold-eth";
|
|
10
|
+
import { arbitrumNitro } from "~~/utils/scaffold-stylus/chain";
|
|
11
|
+
|
|
12
|
+
// Number of ETH faucet sends to an address
|
|
13
|
+
const NUM_OF_ETH = "1";
|
|
14
|
+
const FAUCET_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
|
|
15
|
+
|
|
16
|
+
const localWalletClient = createWalletClient({
|
|
17
|
+
account: privateKeyToAccount(arbitrumNitro.accounts[0].privateKey),
|
|
18
|
+
chain: arbitrumNitro,
|
|
19
|
+
transport: http(arbitrumNitro.rpcUrls.default.http[0]),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* FaucetButton button which lets you grab eth.
|
|
24
|
+
*/
|
|
25
|
+
export const FaucetButton = () => {
|
|
26
|
+
const { address, chain: ConnectedChain } = useAccount();
|
|
27
|
+
|
|
28
|
+
const { data: balance } = useWatchBalance({ address });
|
|
29
|
+
|
|
30
|
+
const [loading, setLoading] = useState(false);
|
|
31
|
+
|
|
32
|
+
const faucetTxn = useTransactor(localWalletClient);
|
|
33
|
+
|
|
34
|
+
const sendETH = async () => {
|
|
35
|
+
if (!address) return;
|
|
36
|
+
try {
|
|
37
|
+
setLoading(true);
|
|
38
|
+
await faucetTxn({
|
|
39
|
+
account: FAUCET_ADDRESS,
|
|
40
|
+
to: address,
|
|
41
|
+
value: parseEther(NUM_OF_ETH),
|
|
42
|
+
});
|
|
43
|
+
setLoading(false);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("⚡️ ~ file: FaucetButton.tsx:sendETH ~ error", error);
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Render only on local chain
|
|
51
|
+
if (ConnectedChain?.id !== arbitrumNitro.id) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const isBalanceZero = balance && balance.value === 0n;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
className={
|
|
60
|
+
!isBalanceZero
|
|
61
|
+
? "ml-1"
|
|
62
|
+
: "ml-1 tooltip tooltip-bottom tooltip-primary tooltip-open font-bold before:left-auto before:transform-none before:content-[attr(data-tip)] before:-translate-x-2/5"
|
|
63
|
+
}
|
|
64
|
+
data-tip="Grab funds from faucet"
|
|
65
|
+
>
|
|
66
|
+
<button className="btn btn-secondary btn-sm px-2 rounded-full" onClick={sendETH} disabled={loading}>
|
|
67
|
+
{!loading ? (
|
|
68
|
+
<BanknotesIcon className="h-4 w-4" />
|
|
69
|
+
) : (
|
|
70
|
+
<span className="loading loading-spinner loading-xs"></span>
|
|
71
|
+
)}
|
|
72
|
+
</button>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { blo } from "blo";
|
|
3
|
+
import { useDebounceValue } from "usehooks-ts";
|
|
4
|
+
import { Address, isAddress } from "viem";
|
|
5
|
+
import { normalize } from "viem/ens";
|
|
6
|
+
import { useEnsAddress, useEnsAvatar, useEnsName } from "wagmi";
|
|
7
|
+
import { CommonInputProps, InputBase, isENS } from "~~/components/scaffold-eth";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Address input with ENS name resolution
|
|
11
|
+
*/
|
|
12
|
+
export const AddressInput = ({ value, name, placeholder, onChange, disabled }: CommonInputProps<Address | string>) => {
|
|
13
|
+
// Debounce the input to keep clean RPC calls when resolving ENS names
|
|
14
|
+
// If the input is an address, we don't need to debounce it
|
|
15
|
+
const [_debouncedValue] = useDebounceValue(value, 500);
|
|
16
|
+
const debouncedValue = isAddress(value) ? value : _debouncedValue;
|
|
17
|
+
const isDebouncedValueLive = debouncedValue === value;
|
|
18
|
+
|
|
19
|
+
// If the user changes the input after an ENS name is already resolved, we want to remove the stale result
|
|
20
|
+
const settledValue = isDebouncedValueLive ? debouncedValue : undefined;
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
data: ensAddress,
|
|
24
|
+
isLoading: isEnsAddressLoading,
|
|
25
|
+
isError: isEnsAddressError,
|
|
26
|
+
isSuccess: isEnsAddressSuccess,
|
|
27
|
+
} = useEnsAddress({
|
|
28
|
+
name: settledValue,
|
|
29
|
+
chainId: 1,
|
|
30
|
+
query: {
|
|
31
|
+
gcTime: 30_000,
|
|
32
|
+
enabled: isDebouncedValueLive && isENS(debouncedValue),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const [enteredEnsName, setEnteredEnsName] = useState<string>();
|
|
37
|
+
const {
|
|
38
|
+
data: ensName,
|
|
39
|
+
isLoading: isEnsNameLoading,
|
|
40
|
+
isError: isEnsNameError,
|
|
41
|
+
isSuccess: isEnsNameSuccess,
|
|
42
|
+
} = useEnsName({
|
|
43
|
+
address: settledValue as Address,
|
|
44
|
+
chainId: 1,
|
|
45
|
+
query: {
|
|
46
|
+
enabled: isAddress(debouncedValue),
|
|
47
|
+
gcTime: 30_000,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const { data: ensAvatar, isLoading: isEnsAvatarLoading } = useEnsAvatar({
|
|
52
|
+
name: ensName ? normalize(ensName) : undefined,
|
|
53
|
+
chainId: 1,
|
|
54
|
+
query: {
|
|
55
|
+
enabled: Boolean(ensName),
|
|
56
|
+
gcTime: 30_000,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ens => address
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!ensAddress) return;
|
|
63
|
+
|
|
64
|
+
// ENS resolved successfully
|
|
65
|
+
setEnteredEnsName(debouncedValue);
|
|
66
|
+
onChange(ensAddress);
|
|
67
|
+
}, [ensAddress, onChange, debouncedValue]);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
setEnteredEnsName(undefined);
|
|
71
|
+
}, [value]);
|
|
72
|
+
|
|
73
|
+
const reFocus =
|
|
74
|
+
isEnsAddressError ||
|
|
75
|
+
isEnsNameError ||
|
|
76
|
+
isEnsNameSuccess ||
|
|
77
|
+
isEnsAddressSuccess ||
|
|
78
|
+
ensName === null ||
|
|
79
|
+
ensAddress === null;
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<InputBase<Address>
|
|
83
|
+
name={name}
|
|
84
|
+
placeholder={placeholder}
|
|
85
|
+
error={ensAddress === null}
|
|
86
|
+
value={value as Address}
|
|
87
|
+
onChange={onChange}
|
|
88
|
+
disabled={isEnsAddressLoading || isEnsNameLoading || disabled}
|
|
89
|
+
reFocus={reFocus}
|
|
90
|
+
prefix={
|
|
91
|
+
ensName ? (
|
|
92
|
+
<div className="flex bg-base-300 rounded-l-full items-center">
|
|
93
|
+
{isEnsAvatarLoading && <div className="skeleton bg-base-200 w-[35px] h-[35px] rounded-full shrink-0"></div>}
|
|
94
|
+
{ensAvatar ? (
|
|
95
|
+
<span className="w-[35px]">
|
|
96
|
+
{
|
|
97
|
+
// eslint-disable-next-line
|
|
98
|
+
<img className="w-full rounded-full" src={ensAvatar} alt={`${ensAddress} avatar`} />
|
|
99
|
+
}
|
|
100
|
+
</span>
|
|
101
|
+
) : null}
|
|
102
|
+
<span className="text-accent px-2">{enteredEnsName ?? ensName}</span>
|
|
103
|
+
</div>
|
|
104
|
+
) : (
|
|
105
|
+
(isEnsNameLoading || isEnsAddressLoading) && (
|
|
106
|
+
<div className="flex bg-base-300 rounded-l-full items-center gap-2 pr-2">
|
|
107
|
+
<div className="skeleton bg-base-200 w-[35px] h-[35px] rounded-full shrink-0"></div>
|
|
108
|
+
<div className="skeleton bg-base-200 h-3 w-20"></div>
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
suffix={
|
|
114
|
+
// Don't want to use nextJS Image here (and adding remote patterns for the URL)
|
|
115
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
116
|
+
value && <img alt="" className="rounded-full!" src={blo(value as `0x${string}`)} width="35" height="35" />
|
|
117
|
+
}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
<InputBase
|
|
15
|
+
name={name}
|
|
16
|
+
value={value}
|
|
17
|
+
placeholder={placeholder}
|
|
18
|
+
onChange={onChange}
|
|
19
|
+
disabled={disabled}
|
|
20
|
+
suffix={
|
|
21
|
+
<button
|
|
22
|
+
className="self-center cursor-pointer text-xl font-semibold px-4 text-accent"
|
|
23
|
+
onClick={convertStringToBytes32}
|
|
24
|
+
type="button"
|
|
25
|
+
>
|
|
26
|
+
#
|
|
27
|
+
</button>
|
|
28
|
+
}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
<InputBase
|
|
12
|
+
name={name}
|
|
13
|
+
value={value}
|
|
14
|
+
placeholder={placeholder}
|
|
15
|
+
onChange={onChange}
|
|
16
|
+
disabled={disabled}
|
|
17
|
+
suffix={
|
|
18
|
+
<button
|
|
19
|
+
className="self-center cursor-pointer text-xl font-semibold px-4 text-accent"
|
|
20
|
+
onClick={convertStringToBytes}
|
|
21
|
+
type="button"
|
|
22
|
+
>
|
|
23
|
+
#
|
|
24
|
+
</button>
|
|
25
|
+
}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
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 { useDisplayUsdMode } from "~~/hooks/scaffold-eth/useDisplayUsdMode";
|
|
5
|
+
import { useGlobalState } from "~~/services/store/store";
|
|
6
|
+
|
|
7
|
+
const MAX_DECIMALS_USD = 2;
|
|
8
|
+
|
|
9
|
+
function etherValueToDisplayValue(usdMode: boolean, etherValue: string, nativeCurrencyPrice: number) {
|
|
10
|
+
if (usdMode && nativeCurrencyPrice) {
|
|
11
|
+
const parsedEthValue = parseFloat(etherValue);
|
|
12
|
+
if (Number.isNaN(parsedEthValue)) {
|
|
13
|
+
return etherValue;
|
|
14
|
+
} else {
|
|
15
|
+
// We need to round the value rather than use toFixed,
|
|
16
|
+
// since otherwise a user would not be able to modify the decimal value
|
|
17
|
+
return (
|
|
18
|
+
Math.round(parsedEthValue * nativeCurrencyPrice * 10 ** MAX_DECIMALS_USD) /
|
|
19
|
+
10 ** MAX_DECIMALS_USD
|
|
20
|
+
).toString();
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
return etherValue;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function displayValueToEtherValue(usdMode: boolean, displayValue: string, nativeCurrencyPrice: number) {
|
|
28
|
+
if (usdMode && nativeCurrencyPrice) {
|
|
29
|
+
const parsedDisplayValue = parseFloat(displayValue);
|
|
30
|
+
if (Number.isNaN(parsedDisplayValue)) {
|
|
31
|
+
// Invalid number.
|
|
32
|
+
return displayValue;
|
|
33
|
+
} else {
|
|
34
|
+
// Compute the ETH value if a valid number.
|
|
35
|
+
return (parsedDisplayValue / nativeCurrencyPrice).toString();
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
return displayValue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Input for ETH amount with USD conversion.
|
|
44
|
+
*
|
|
45
|
+
* onChange will always be called with the value in ETH
|
|
46
|
+
*/
|
|
47
|
+
export const EtherInput = ({
|
|
48
|
+
value,
|
|
49
|
+
name,
|
|
50
|
+
placeholder,
|
|
51
|
+
onChange,
|
|
52
|
+
disabled,
|
|
53
|
+
usdMode,
|
|
54
|
+
}: CommonInputProps & { usdMode?: boolean }) => {
|
|
55
|
+
const [transitoryDisplayValue, setTransitoryDisplayValue] = useState<string>();
|
|
56
|
+
const nativeCurrencyPrice = useGlobalState(state => state.nativeCurrency.price);
|
|
57
|
+
const isNativeCurrencyPriceFetching = useGlobalState(state => state.nativeCurrency.isFetching);
|
|
58
|
+
|
|
59
|
+
const { displayUsdMode, toggleDisplayUsdMode } = useDisplayUsdMode({ defaultUsdMode: usdMode });
|
|
60
|
+
|
|
61
|
+
// The displayValue is derived from the ether value that is controlled outside of the component
|
|
62
|
+
// In usdMode, it is converted to its usd value, in regular mode it is unaltered
|
|
63
|
+
const displayValue = useMemo(() => {
|
|
64
|
+
const newDisplayValue = etherValueToDisplayValue(displayUsdMode, value, nativeCurrencyPrice || 0);
|
|
65
|
+
if (transitoryDisplayValue && parseFloat(newDisplayValue) === parseFloat(transitoryDisplayValue)) {
|
|
66
|
+
return transitoryDisplayValue;
|
|
67
|
+
}
|
|
68
|
+
// Clear any transitory display values that might be set
|
|
69
|
+
setTransitoryDisplayValue(undefined);
|
|
70
|
+
return newDisplayValue;
|
|
71
|
+
}, [nativeCurrencyPrice, transitoryDisplayValue, displayUsdMode, value]);
|
|
72
|
+
|
|
73
|
+
const handleChangeNumber = (newValue: string) => {
|
|
74
|
+
if (newValue && !SIGNED_NUMBER_REGEX.test(newValue)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Following condition is a fix to prevent usdMode from experiencing different display values
|
|
79
|
+
// than what the user entered. This can happen due to floating point rounding errors that are introduced in the back and forth conversion
|
|
80
|
+
if (displayUsdMode) {
|
|
81
|
+
const decimals = newValue.split(".")[1];
|
|
82
|
+
if (decimals && decimals.length > MAX_DECIMALS_USD) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Since the display value is a derived state (calculated from the ether value), usdMode would not allow introducing a decimal point.
|
|
88
|
+
// This condition handles a transitory state for a display value with a trailing decimal sign
|
|
89
|
+
if (newValue.endsWith(".") || newValue.endsWith(".0")) {
|
|
90
|
+
setTransitoryDisplayValue(newValue);
|
|
91
|
+
} else {
|
|
92
|
+
setTransitoryDisplayValue(undefined);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const newEthValue = displayValueToEtherValue(displayUsdMode, newValue, nativeCurrencyPrice || 0);
|
|
96
|
+
onChange(newEthValue);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<InputBase
|
|
101
|
+
name={name}
|
|
102
|
+
value={displayValue}
|
|
103
|
+
placeholder={placeholder}
|
|
104
|
+
onChange={handleChangeNumber}
|
|
105
|
+
disabled={disabled}
|
|
106
|
+
prefix={<span className="pl-4 -mr-2 text-accent self-center">{displayUsdMode ? "$" : "Ξ"}</span>}
|
|
107
|
+
suffix={
|
|
108
|
+
<div
|
|
109
|
+
className={`${
|
|
110
|
+
nativeCurrencyPrice > 0
|
|
111
|
+
? ""
|
|
112
|
+
: "tooltip tooltip-secondary before:content-[attr(data-tip)] before:right-[-10px] before:left-auto before:transform-none"
|
|
113
|
+
}`}
|
|
114
|
+
data-tip={isNativeCurrencyPriceFetching ? "Fetching price" : "Unable to fetch price"}
|
|
115
|
+
>
|
|
116
|
+
<button
|
|
117
|
+
className="btn btn-primary h-[2.2rem] min-h-[2.2rem]"
|
|
118
|
+
onClick={toggleDisplayUsdMode}
|
|
119
|
+
disabled={!displayUsdMode && !nativeCurrencyPrice}
|
|
120
|
+
type="button"
|
|
121
|
+
>
|
|
122
|
+
<ArrowsRightLeftIcon className="h-3 w-3 cursor-pointer" aria-hidden="true" />
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ChangeEvent, FocusEvent, ReactNode, useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { CommonInputProps } from "~~/components/scaffold-eth";
|
|
3
|
+
|
|
4
|
+
type InputBaseProps<T> = CommonInputProps<T> & {
|
|
5
|
+
error?: boolean;
|
|
6
|
+
prefix?: ReactNode;
|
|
7
|
+
suffix?: ReactNode;
|
|
8
|
+
reFocus?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const InputBase = <T extends { toString: () => string } | undefined = string>({
|
|
12
|
+
name,
|
|
13
|
+
value,
|
|
14
|
+
onChange,
|
|
15
|
+
placeholder,
|
|
16
|
+
error,
|
|
17
|
+
disabled,
|
|
18
|
+
prefix,
|
|
19
|
+
suffix,
|
|
20
|
+
reFocus,
|
|
21
|
+
}: InputBaseProps<T>) => {
|
|
22
|
+
const inputReft = useRef<HTMLInputElement>(null);
|
|
23
|
+
|
|
24
|
+
let modifier = "";
|
|
25
|
+
if (error) {
|
|
26
|
+
modifier = "border-error";
|
|
27
|
+
} else if (disabled) {
|
|
28
|
+
modifier = "border-disabled bg-base-300";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const handleChange = useCallback(
|
|
32
|
+
(e: ChangeEvent<HTMLInputElement>) => {
|
|
33
|
+
onChange(e.target.value as unknown as T);
|
|
34
|
+
},
|
|
35
|
+
[onChange],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Runs only when reFocus prop is passed, useful for setting the cursor
|
|
39
|
+
// at the end of the input. Example AddressInput
|
|
40
|
+
const onFocus = (e: FocusEvent<HTMLInputElement, Element>) => {
|
|
41
|
+
if (reFocus !== undefined) {
|
|
42
|
+
e.currentTarget.setSelectionRange(e.currentTarget.value.length, e.currentTarget.value.length);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (reFocus !== undefined && reFocus === true) inputReft.current?.focus();
|
|
47
|
+
}, [reFocus]);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className={`flex border-2 border-base-300 bg-base-200 rounded-full text-accent ${modifier}`}>
|
|
51
|
+
{prefix}
|
|
52
|
+
<input
|
|
53
|
+
className="input input-ghost focus-within:border-transparent focus:outline-hidden focus:bg-transparent h-[2.2rem] min-h-[2.2rem] px-4 border w-full font-medium placeholder:text-accent/70 text-base-content/70 focus:text-base-content/70"
|
|
54
|
+
placeholder={placeholder}
|
|
55
|
+
name={name}
|
|
56
|
+
value={value?.toString()}
|
|
57
|
+
onChange={handleChange}
|
|
58
|
+
disabled={disabled}
|
|
59
|
+
autoComplete="off"
|
|
60
|
+
ref={inputReft}
|
|
61
|
+
onFocus={onFocus}
|
|
62
|
+
/>
|
|
63
|
+
{suffix}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
};
|