import React, { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useSnackbar } from 'notistack'; import BN from 'bn.js'; import { Account, PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, SYSVAR_CLOCK_PUBKEY, } from '@solana/web3.js'; import { TokenInstructions } from '@project-serum/serum'; import { createTokenAccountInstrs } from '@project-serum/common'; import TextField from '@material-ui/core/TextField'; import Button from '@material-ui/core/Button'; import FormControl from '@material-ui/core/FormControl'; import InputLabel from '@material-ui/core/InputLabel'; import Select from '@material-ui/core/Select'; import FormHelperText from '@material-ui/core/FormHelperText'; import MenuItem from '@material-ui/core/MenuItem'; import Typography from '@material-ui/core/Typography'; import CircularProgress from '@material-ui/core/CircularProgress'; import DialogContent from '@material-ui/core/DialogContent'; import Dialog from '@material-ui/core/Dialog'; import DialogTitle from '@material-ui/core/DialogTitle'; import DialogActions from '@material-ui/core/DialogActions'; import { State as StoreState } from '../../store/reducer'; import { ActionType } from '../../store/actions'; import { useWallet } from '../../components/common/WalletProvider'; import OwnedTokenAccountsSelect from '../../components/common/OwnedTokenAccountsSelect'; import { fromDisplay } from '../../utils/tokens'; import { vestingSigner } from '../../utils/lockup'; import { ViewTransactionOnExplorerButton } from '../common/Notification'; export default function NewVestingButton() { const [open, setOpen] = useState(false); return ( <>
setOpen(true)}>
setOpen(false)} /> ); } type NewVestingDialogProps = { open: boolean; onClose: () => void; }; function NewVestingDialog(props: NewVestingDialogProps) { const { open, onClose } = props; const { network, accounts } = useSelector((state: StoreState) => { return { network: state.common.network, accounts: state.accounts, }; }); const defaultStartDate = new Date().toString(); const defaultStartTs = new Date(defaultStartDate).getTime() / 1000; const defaultEndDate = '2027-01-01T12:00'; const defaultEndTs = new Date(defaultEndDate).getTime() / 1000; const [beneficiary, setBeneficiary] = useState(''); const isValidBeneficiary = (() => { try { new PublicKey(beneficiary); return true; } catch (_) { return false; } })(); const displayBeneficiaryError = !isValidBeneficiary && beneficiary !== ''; const [fromAccount, setFromAccount] = useState(null); const [startTimestamp, setStartTimestamp] = useState(defaultStartTs); const [timestamp, setTimestamp] = useState(defaultEndTs); const [periodCount, setPeriodCount] = useState(7); const [displayAmount, setDisplayAmount] = useState(null); const { lockupClient } = useWallet(); const [isLoading, setIsLoading] = useState(false); const [mint, setMint] = useState(null); const { enqueueSnackbar } = useSnackbar(); const dispatch = useDispatch(); const submitBtnEnabled = mint !== null && fromAccount !== null && isValidBeneficiary && displayAmount !== null; const createVestingClickHandler = async () => { setIsLoading(true); try { const beneficiaryPublicKey = new PublicKey(beneficiary); const beneficiaryAccount = await lockupClient.provider.connection.getAccountInfo( beneficiaryPublicKey, ); if (beneficiaryAccount === null) { enqueueSnackbar('Unable to validate given beneficiary.', { variant: 'error', }); setIsLoading(false); return; } if (!beneficiaryAccount.owner.equals(SystemProgram.programId)) { enqueueSnackbar( 'The beneficiary must be owned by the System Program.', { variant: 'error', }, ); setIsLoading(false); return; } enqueueSnackbar('Creating vesting acount...', { variant: 'info', }); const mintAccount = accounts[mint!.toString()]; let amount = mintAccount ? fromDisplay(displayAmount!, mintAccount.decimals) : new BN(displayAmount!); const vesting = new Account(); const vestingVault = new Account(); const _vestingSigner = await vestingSigner( lockupClient.programId, vesting.publicKey, ); let tx = await lockupClient.rpc.createVesting( beneficiaryPublicKey, amount, _vestingSigner.nonce, new BN(startTimestamp), new BN(timestamp), new BN(periodCount), null, { accounts: { vesting: vesting.publicKey, vault: vestingVault.publicKey, depositor: fromAccount, depositorAuthority: lockupClient.provider.wallet.publicKey, tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID, rent: SYSVAR_RENT_PUBKEY, clock: SYSVAR_CLOCK_PUBKEY, }, signers: [vesting, vestingVault], instructions: [ await lockupClient.account.vesting.createInstruction(vesting), ...(await createTokenAccountInstrs( lockupClient.provider, vestingVault.publicKey, mint!, _vestingSigner.publicKey, )), ], }, ); // Only add to the local store if the lockup belongs to the current user. if (beneficiaryPublicKey.equals(lockupClient.provider.wallet.publicKey)) { const vestingAccount = await lockupClient.account.vesting( vesting.publicKey, ); dispatch({ type: ActionType.LockupCreateVesting, item: { vesting: { publicKey: vesting.publicKey, account: vestingAccount, }, }, }); } enqueueSnackbar(`Vesting account created`, { variant: 'success', action: , }); onClose(); } catch (err) { enqueueSnackbar(`Error creating vesting account: ${err.toString()}`, { variant: 'error', }); } setIsLoading(false); }; return ( New Vesting Account
{isLoading && (
)}
Mint
{false && (
setMint(new PublicKey(e.target.value))} /> Mint of the token to lockup
)}
From setFromAccount(f)} /> Token account to send from
setBeneficiary(e.target.value)} /> Owner of the new vesting account
{false && ( Note: Amounts for custom mints (i.e., not SRM/MSRM) are in their raw, non-decimal form. Make sure to convert before entering into the fields here. For example, if a token has 6 decimals, then multiply your desired amount by 10^6. )} setDisplayAmount(parseFloat(e.target.value))} /> Amount to deposit into the vesting account
{ const d = new Date(e.target.value); setStartTimestamp(d.getTime() / 1000); }} /> Date when vesting begins
{ const d = new Date(e.target.value); setTimestamp(d.getTime() / 1000); }} /> Date when all tokens are vested
setPeriodCount(parseInt(e.target.value) as number) } InputProps={{ inputProps: { min: 1 } }} /> Number of vesting periods
); }