Skip to content

Instantly share code, notes, and snippets.

@shramee
Created October 14, 2025 20:44
Show Gist options
  • Save shramee/c298419dc2f0ece0f56d5cfda4c6b768 to your computer and use it in GitHub Desktop.
Save shramee/c298419dc2f0ece0f56d5cfda4c6b768 to your computer and use it in GitHub Desktop.
Reusable UI components
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