Created
October 14, 2025 20:44
-
-
Save shramee/c298419dc2f0ece0f56d5cfda4c6b768 to your computer and use it in GitHub Desktop.
Reusable UI components
This file contains hidden or 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
| import React, { useEffect, useState } from 'react'; | |
| import { Field, Dropdown, InputField, Button, DropdownOption, baseUIBoxClasses } from './UI'; | |
| import { RotateCcw, Send, DollarSign, User, Link, WalletMinimal } from 'lucide-react'; | |
| import StarknetWalletGate from './StarknetWalletGate'; | |
| import { useAccount, useContract, useProvider, useSendTransaction } from '@starknet-react/core'; | |
| import { init as garagaInit } from 'garaga'; | |
| import { uint256 } from "starknet" | |
| import { useMist } from '@mistcash/react'; | |
| import { generateClaimingKey, txSecret } from '@mistcash/crypto'; | |
| import { ERC20_ABI, tokensData, tokensMap } from '@mistcash/config'; | |
| import { fmtAmount, fmtAmtToBigInt } from '@mistcash/sdk'; | |
| function icon(bg: string, Icon: React.ElementType): { bg: string; content: React.ReactElement } { | |
| return { | |
| bg, content: <Icon className="w-4 h-4 text-white" /> | |
| }; | |
| } | |
| const CryptoTransferUI: React.FC = () => { | |
| useEffect(() => { | |
| (async () => { | |
| garagaInit(); | |
| })() | |
| }, []); | |
| // State for dropdown selections | |
| const { | |
| // input fields | |
| valTo, setTo, valKey, setKey, | |
| // asset selection | |
| asset, | |
| // contract | |
| contract, send, isPending, txError: error, | |
| chamberAddress, | |
| } = useMist(useProvider(), useSendTransaction({})); | |
| const [selectedToken, setSelectedToken] = useState<string>(asset?.addr || (tokensData[0] || { id: '' }).id); | |
| const selectedTokenObj = tokensMap[selectedToken]; | |
| const [valAmount, setAmount] = useState<string>(asset?.amount ? fmtAmount(asset.amount, selectedTokenObj?.decimals || 18) : ''); // default to 6 decimals for input | |
| const { address } = useAccount(); | |
| const { contract: erc20 } = useContract({ abi: ERC20_ABI, address: selectedToken as `0x${string}` }); | |
| // Find selected token object | |
| useEffect(() => { | |
| (async () => { | |
| if (erc20 && address) { | |
| erc20.address = selectedToken; | |
| erc20.balanceOf(address as string) | |
| .then(balance => { | |
| console.log("Token balance:", balance); | |
| }) | |
| .catch(console.error); | |
| } | |
| })() | |
| }, [address, erc20, selectedToken]); | |
| // Prepare deposit call | |
| const handleDeposit = async (e: React.FormEvent<HTMLFormElement>) => { | |
| e.preventDefault(); | |
| if (!valKey || !valTo || !valAmount || !selectedTokenObj || !contract || !erc20) return; | |
| // Allow 6 digits of precision | |
| const amount_bi = fmtAmtToBigInt(valAmount, selectedTokenObj.decimals || 18); | |
| const amount = uint256.bnToUint256(amount_bi); | |
| erc20.address = selectedToken | |
| const asset = { | |
| amount, | |
| addr: selectedToken | |
| }; | |
| try { | |
| send([ | |
| erc20.populate('approve', [chamberAddress, amount]), | |
| contract.populate('deposit', [uint256.bnToUint256(await txSecret(valKey, valTo)), asset]) | |
| ]); | |
| } catch (error) { | |
| console.error("Failed to send transaction:", error); | |
| } | |
| }; | |
| const waiting = isPending; | |
| function handleTokenSelect(token: DropdownOption) { | |
| setSelectedToken(token.id) | |
| if (erc20) { | |
| erc20.address = token.id; | |
| } | |
| } | |
| return <form onSubmit={handleDeposit} className="w-full"> | |
| <h2 className="text-3xl md:text-4xl font-bold text-white text-center mb-8"> | |
| Send any token privately to a wallet address | |
| </h2> | |
| <p className="text-lg text-red-300 text-center mb-12 max-w-2xl mx-auto"> | |
| This is an alpha preview, expect bugs, including complete loss of funds.<br /> | |
| Please use at your own risk. | |
| </p> | |
| {/* Token Section */} | |
| <Field label="Token"> | |
| <Dropdown | |
| options={tokensData as DropdownOption[]} | |
| selectedId={selectedToken} | |
| onSelect={handleTokenSelect} | |
| placeholder="Select Token" | |
| /> | |
| </Field> | |
| {/* Recipient Section */} | |
| <Field label="To"> | |
| <InputField | |
| required={true} | |
| icon={icon('#FF6B35', User)} | |
| placeholder='Account address (0x...)' | |
| value={valTo} | |
| onChange={e => setTo(e.target.value)} | |
| /> | |
| </Field> | |
| {/* Claiming Key Section */} | |
| <Field | |
| label="Claiming Key" | |
| subtitle="Share this code with the recipient to claim the funds." | |
| > | |
| <InputField | |
| required={true} | |
| icon={icon('#10B981', Link)} | |
| placeholder='Generate Claiming Key' | |
| value={valKey} | |
| onChange={e => setKey(e.target.value)} | |
| action={<RotateCcw className="w-4 h-4" onClick={() => setKey(generateClaimingKey())} />} | |
| /> | |
| </Field> | |
| {/* Amount Section */} | |
| <Field label="Amount"> | |
| <InputField | |
| required={true} | |
| icon={icon('#10B981', DollarSign)} | |
| placeholder='Amount' | |
| value={valAmount} | |
| type="number" | |
| min="0" | |
| step='0.000001' | |
| onChange={e => setAmount(e.target.value)} | |
| /> | |
| </Field> | |
| {/* Enter Details Button */} | |
| <div> | |
| <StarknetWalletGate label={<><WalletMinimal className="w-5 h-5" />Connect Wallet</>}> | |
| <Button disabled={waiting}> | |
| <Send className="w-5 h-5" /> | |
| {isPending ? 'Sending...' : 'Send Deposit'} | |
| </Button> | |
| </StarknetWalletGate> | |
| {error && <div className={baseUIBoxClasses + " bg-red-800 px-3 py-2 mt-2"}>{error.message}</div>} | |
| </div> | |
| </form>; | |
| }; | |
| export default CryptoTransferUI; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment