forked from Scaffold-Stark/scaffold-stark-2
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into challenge-view
- Loading branch information
Showing
10 changed files
with
540 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
"use client"; | ||
|
||
import { useState } from "react"; | ||
import { Address as AddressType, devnet } from "@starknet-react/chains"; | ||
import { BanknotesIcon } from "@heroicons/react/24/outline"; | ||
import { | ||
Address, | ||
AddressInput, | ||
Balance, | ||
EtherInput, | ||
} from "~~/components/scaffold-stark"; | ||
import { useNetwork } from "@starknet-react/core"; | ||
import { mintEth } from "~~/services/web3/faucet"; | ||
|
||
/** | ||
* Faucet modal which lets you send ETH to any address. | ||
*/ | ||
export const Faucet = () => { | ||
const [loading, setLoading] = useState(false); | ||
const [inputAddress, setInputAddress] = useState<AddressType>(); | ||
const [faucetAddress] = useState<AddressType>( | ||
"0x78662e7352d062084b0010068b99288486c2d8b914f6e2a55ce945f8792c8b1", | ||
); | ||
const [sendValue, setSendValue] = useState(""); | ||
|
||
const { chain: ConnectedChain } = useNetwork(); | ||
|
||
const sendETH = async () => { | ||
if (!faucetAddress || !inputAddress) { | ||
return; | ||
} | ||
|
||
try { | ||
setLoading(true); | ||
await mintEth(inputAddress, sendValue); | ||
setLoading(false); | ||
setInputAddress(undefined); | ||
setSendValue(""); | ||
} catch (error) { | ||
console.error("⚡️ ~ file: Faucet.tsx:sendETH ~ error", error); | ||
setLoading(false); | ||
} | ||
}; | ||
|
||
// Render only on local chain | ||
if (ConnectedChain?.id !== devnet.id) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div> | ||
<label | ||
htmlFor="faucet-modal" | ||
className="btn btn-primary btn-sm font-normal gap-1" | ||
> | ||
<BanknotesIcon className="h-4 w-4" /> | ||
<span>Faucet</span> | ||
</label> | ||
<input type="checkbox" id="faucet-modal" className="modal-toggle" /> | ||
<label htmlFor="faucet-modal" className="modal cursor-pointer"> | ||
<label className="modal-box relative"> | ||
{/* dummy input to capture event onclick on modal box */} | ||
<input className="h-0 w-0 absolute top-0 left-0" /> | ||
<h3 className="text-xl font-bold mb-3">Local Faucet</h3> | ||
<label | ||
htmlFor="faucet-modal" | ||
className="btn btn-ghost btn-sm btn-circle absolute right-3 top-3" | ||
> | ||
✕ | ||
</label> | ||
<div className="space-y-3"> | ||
<div className="flex space-x-4"> | ||
<div> | ||
<span className="text-sm font-bold">From:</span> | ||
<Address address={faucetAddress} /> | ||
</div> | ||
<div> | ||
<span className="text-sm font-bold pl-3">Available:</span> | ||
<Balance address={faucetAddress} /> | ||
</div> | ||
</div> | ||
<div className="flex flex-col space-y-3"> | ||
<AddressInput | ||
placeholder="Destination Address" | ||
value={inputAddress ?? ""} | ||
onChange={(value) => setInputAddress(value as AddressType)} | ||
/> | ||
<EtherInput | ||
placeholder="Amount to send" | ||
value={sendValue} | ||
onChange={(value) => setSendValue(value)} | ||
/> | ||
<button | ||
className="h-10 btn btn-primary btn-sm px-2 rounded-full" | ||
onClick={sendETH} | ||
disabled={loading} | ||
> | ||
{!loading ? ( | ||
<BanknotesIcon className="h-6 w-6" /> | ||
) : ( | ||
<span className="loading loading-spinner loading-sm"></span> | ||
)} | ||
<span>Send</span> | ||
</button> | ||
</div> | ||
</div> | ||
</label> | ||
</label> | ||
</div> | ||
); | ||
}; |
73 changes: 73 additions & 0 deletions
73
packages/nextjs/components/scaffold-stark/FaucetButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
"use client"; | ||
|
||
import { useState } from "react"; | ||
import { BanknotesIcon } from "@heroicons/react/24/outline"; | ||
import { mintEth } from "~~/services/web3/faucet"; | ||
import { Address, devnet } from "@starknet-react/chains"; | ||
import { useAccount, useBalance, useNetwork } from "@starknet-react/core"; | ||
import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork"; | ||
|
||
// Number of ETH faucet sends to an address | ||
const NUM_OF_ETH = "1"; | ||
|
||
/** | ||
* FaucetButton button which lets you grab eth. | ||
*/ | ||
export const FaucetButton = () => { | ||
const { address } = useAccount(); | ||
const { data: balance, refetch } = useBalance({ | ||
address, | ||
watch: true, | ||
refetchInterval: 4500, | ||
}); | ||
|
||
const { targetNetwork } = useTargetNetwork(); | ||
|
||
const [loading, setLoading] = useState(false); | ||
|
||
const sendETH = async () => { | ||
if (!address) { | ||
return; | ||
} | ||
|
||
try { | ||
setLoading(true); | ||
await mintEth(address as Address, NUM_OF_ETH); | ||
await refetch(); | ||
setLoading(false); | ||
} catch (error) { | ||
console.error("⚡️ ~ file: FaucetButton.tsx:sendETH ~ error", error); | ||
setLoading(false); | ||
} | ||
}; | ||
|
||
// Render only on local chain | ||
if (targetNetwork.id !== devnet.id) { | ||
return null; | ||
} | ||
|
||
const isBalanceZero = balance && balance.value === 0n; | ||
|
||
return ( | ||
<div | ||
className={ | ||
!isBalanceZero | ||
? "ml-1" | ||
: "ml-1 tooltip tooltip-bottom tooltip-secondary tooltip-open font-bold before:left-auto before:transform-none before:content-[attr(data-tip)] before:right-0" | ||
} | ||
data-tip="Grab funds from faucet" | ||
> | ||
<button | ||
className="btn btn-secondary btn-sm px-2 rounded-full" | ||
onClick={sendETH} | ||
disabled={loading} | ||
> | ||
{!loading ? ( | ||
<BanknotesIcon className="h-4 w-4" /> | ||
) : ( | ||
<span className="loading loading-spinner loading-xs"></span> | ||
)} | ||
</button> | ||
</div> | ||
); | ||
}; |
134 changes: 134 additions & 0 deletions
134
packages/nextjs/components/scaffold-stark/Input/AddressInput.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { useCallback } from "react"; | ||
import { blo } from "blo"; | ||
import { useDebounceValue } from "usehooks-ts"; | ||
import { CommonInputProps, InputBase } from "~~/components/scaffold-stark"; | ||
import { Address } from "@starknet-react/chains"; | ||
import { isAddress } from "~~/utils/scaffold-stark/common"; | ||
import Image from "next/image"; | ||
|
||
/** | ||
* Address input with ENS name resolution | ||
*/ | ||
export const AddressInput = ({ | ||
value, | ||
name, | ||
placeholder, | ||
onChange, | ||
disabled, | ||
}: CommonInputProps<Address | string>) => { | ||
// Debounce the input to keep clean RPC calls when resolving ENS names | ||
// If the input is an address, we don't need to debounce it | ||
const [_debouncedValue] = useDebounceValue(value, 500); | ||
//const debouncedValue = isAddress(value) ? value : _debouncedValue; | ||
//const isDebouncedValueLive = debouncedValue === value; | ||
|
||
// If the user changes the input after an ENS name is already resolved, we want to remove the stale result | ||
//const settledValue = isDebouncedValueLive ? debouncedValue : undefined; | ||
|
||
// const { | ||
// data: ensAddress, | ||
// isLoading: isEnsAddressLoading, | ||
// isError: isEnsAddressError, | ||
// isSuccess: isEnsAddressSuccess, | ||
// } = useEnsAddress({ | ||
// name: settledValue, | ||
// enabled: isDebouncedValueLive && isENS(debouncedValue), | ||
// chainId: 1, | ||
// cacheTime: 30_000, | ||
// }); | ||
// | ||
//const [enteredEnsName, setEnteredEnsName] = useState<string>(); | ||
// const { | ||
// data: ensName, | ||
// isLoading: isEnsNameLoading, | ||
// isError: isEnsNameError, | ||
// isSuccess: isEnsNameSuccess, | ||
// } = useEnsName({ | ||
// address: settledValue as Address, | ||
// enabled: isAddress(debouncedValue), | ||
// chainId: 1, | ||
// cacheTime: 30_000, | ||
// }); | ||
// | ||
// const { data: ensAvatar, isLoading: isEnsAvtarLoading } = useEnsAvatar({ | ||
// name: ensName, | ||
// enabled: Boolean(ensName), | ||
// chainId: 1, | ||
// cacheTime: 30_000, | ||
// }); | ||
|
||
// ens => address | ||
// useEffect(() => { | ||
// if (!ensAddress) return; | ||
// | ||
// // ENS resolved successfully | ||
// setEnteredEnsName(debouncedValue); | ||
// onChange(ensAddress); | ||
// }, [ensAddress, onChange, debouncedValue]); | ||
|
||
const handleChange = useCallback( | ||
(newValue: Address) => { | ||
//setEnteredEnsName(undefined); | ||
onChange(newValue); | ||
}, | ||
[onChange], | ||
); | ||
|
||
// const reFocus = | ||
// isEnsAddressError || | ||
// isEnsNameError || | ||
// isEnsNameSuccess || | ||
// isEnsAddressSuccess || | ||
// ensName === null || | ||
// ensAddress === null; | ||
|
||
return ( | ||
<InputBase<Address> | ||
name={name} | ||
placeholder={placeholder} | ||
// error={ensAddress === null} | ||
value={value as Address} | ||
onChange={handleChange} | ||
// disabled={isEnsAddressLoading || isEnsNameLoading || disabled} | ||
disabled={disabled} | ||
// reFocus={reFocus} | ||
prefix={ | ||
null | ||
// ensName ? ( | ||
// <div className="flex bg-base-300 rounded-l-full items-center"> | ||
// {isEnsAvtarLoading && <div className="skeleton bg-base-200 w-[35px] h-[35px] rounded-full shrink-0"></div>} | ||
// {ensAvatar ? ( | ||
// <span className="w-[35px]"> | ||
// { | ||
// // eslint-disable-next-line | ||
// <img className="w-full rounded-full" src={ensAvatar} alt={`${ensAddress} avatar`} /> | ||
// } | ||
// </span> | ||
// ) : null} | ||
// <span className="text-accent px-2">{enteredEnsName ?? ensName}</span> | ||
// </div> | ||
// ) : ( | ||
// (isEnsNameLoading || isEnsAddressLoading) && ( | ||
// <div className="flex bg-base-300 rounded-l-full items-center gap-2 pr-2"> | ||
// <div className="skeleton bg-base-200 w-[35px] h-[35px] rounded-full shrink-0"></div> | ||
// <div className="skeleton bg-base-200 h-3 w-20"></div> | ||
// </div> | ||
// ) | ||
// ) | ||
} | ||
suffix={ | ||
// Don't want to use nextJS Image here (and adding remote patterns for the URL) | ||
// eslint-disable-next-line @next/next/no-img-element | ||
value && ( | ||
<Image | ||
alt="" | ||
className="!rounded-full" | ||
src={blo(value as `0x${string}`)} | ||
width="35" | ||
height="35" | ||
/> | ||
) | ||
} | ||
/> | ||
); | ||
}; |
Oops, something went wrong.