stake-ui/src/components/rewards/ClaimRewardButton.tsx

201 lines
6.8 KiB
TypeScript

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSnackbar } from 'notistack';
import Button from '@material-ui/core/Button';
import {
Account,
PublicKey,
SYSVAR_RENT_PUBKEY,
SYSVAR_CLOCK_PUBKEY,
} from '@solana/web3.js';
import { TokenInstructions } from '@project-serum/serum';
import { createTokenAccountInstrs } from '@project-serum/common';
import { useWallet } from '../../components/common/WalletProvider';
import * as notification from '../common/Notification';
import OwnedTokenAccountsSelect from '../common/OwnedTokenAccountsSelect';
import { RewardListItemViewModel } from './RewardsList';
import { ActionType } from '../../store/actions';
import { State as StoreState } from '../../store/reducer';
import { vendorSigner } from '../../utils/registry';
import { vestingSigner } from '../../utils/lockup';
type ClaimRewardButtonProps = {
rli: RewardListItemViewModel;
};
export default function ClaimRewardButton(props: ClaimRewardButtonProps) {
const { registryClient, lockupClient } = useWallet();
const { member, registrar } = useSelector((state: StoreState) => {
const registrar = {
publicKey: state.registry.registrar,
account: state.accounts[state.registry.registrar.toString()],
};
const member = state.registry.member
? {
publicKey: state.registry.member,
account: state.accounts[state.registry.member.toString()],
}
: undefined;
return {
registrar,
member,
};
});
const { rli } = props;
const dispatch = useDispatch();
const snack = useSnackbar();
const [token, setToken] = useState<null | PublicKey>(null);
// On click.
const clickHandler = async (): Promise<void> => {
notification.withTx(
snack,
`Processing vendor reward ${rli!.vendor!.publicKey.toString()}`,
'Reward processed',
async () => {
const vendor = await registryClient.account.rewardVendor(
rli.vendor!.publicKey,
);
const _vendorSigner = await vendorSigner(
registryClient.programId,
registrar.publicKey,
rli.vendor!.publicKey,
);
if (rli!.reward.locked) {
const vendoredVesting = new Account();
const vendoredVestingVault = new Account();
const vendoredVestingSigner = await vestingSigner(
lockupClient.programId,
vendoredVesting.publicKey,
);
const remainingAccounts = lockupClient.instruction.createVesting
.accounts({
vesting: vendoredVesting.publicKey,
vault: vendoredVestingVault.publicKey,
depositor: vendor.vault,
depositorAuthority: _vendorSigner.publicKey,
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
rent: SYSVAR_RENT_PUBKEY,
clock: SYSVAR_CLOCK_PUBKEY,
})
// Change the signer status on the vendor signer since it's signed by the program, not the
// client.
.map((meta: any) =>
meta.pubkey.equals(_vendorSigner.publicKey)
? { ...meta, isSigner: false }
: meta,
);
const tx = await registryClient.rpc.claimRewardLocked(
vendoredVestingSigner.nonce,
{
accounts: {
// @ts-ignore
registry: await registryClient.state.address(),
lockupProgram: lockupClient.programId,
cmn: {
registrar: registrar.publicKey,
member: member!.publicKey,
beneficiary: registryClient.provider.wallet.publicKey,
balances: member!.account.balances,
balancesLocked: member!.account.balancesLocked,
vendor: rli.vendor!.publicKey,
vault: rli.vendor!.account.vault,
vendorSigner: _vendorSigner.publicKey,
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
clock: SYSVAR_CLOCK_PUBKEY,
},
},
remainingAccounts,
signers: [vendoredVesting, vendoredVestingVault],
instructions: [
await lockupClient.account.vesting.createInstruction(
vendoredVesting,
),
...(await createTokenAccountInstrs(
registryClient.provider,
vendoredVestingVault.publicKey,
rli.vendor!.account.mint,
vendoredVestingSigner.publicKey,
)),
],
},
);
// Refetch the vesting accounts to update the UI with the new reward.
const vestingAccounts = await lockupClient.account.vesting.all(
registryClient.provider.wallet.publicKey.toBuffer(),
);
vestingAccounts.forEach(account => {
dispatch({
type: ActionType.AccountAdd,
item: {
account,
},
});
});
dispatch({
type: ActionType.LockupSetVestings,
item: {
vestingAccounts: vestingAccounts.map(v => v.publicKey),
},
});
return tx;
} else {
return await registryClient.rpc.claimReward({
accounts: {
to: token,
cmn: {
registrar: registrar.publicKey,
member: member!.publicKey,
beneficiary: registryClient.provider.wallet.publicKey,
balances: member!.account.balances,
balancesLocked: member!.account.balancesLocked,
vendor: rli.vendor!.publicKey,
vault: vendor.vault,
vendorSigner: _vendorSigner.publicKey,
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
clock: SYSVAR_CLOCK_PUBKEY,
},
},
});
}
},
);
};
return (
<>
{!rli.reward.locked && (
<div>
<OwnedTokenAccountsSelect
style={{ width: '400px', height: '100%' }}
mint={rli.vendor.account.mint}
onChange={(f: PublicKey) => setToken(f)}
/>
</div>
)}
<div style={{ marginLeft: '10px', marginRight: '10px' }}>
<Button
disabled={rli.reward.unlockedAlloc && token === null}
variant="contained"
color="primary"
onClick={() =>
clickHandler().catch(err => {
console.error(err);
snack.enqueueSnackbar(
`Error ending pending redemption: ${err.toString()}`,
{
variant: 'error',
},
);
})
}
>
Process Reward
</Button>
</div>
</>
);
}