diff --git a/frontend/pages/approve.tsx b/frontend/pages/approve.tsx new file mode 100644 index 00000000..0c274a17 --- /dev/null +++ b/frontend/pages/approve.tsx @@ -0,0 +1,103 @@ +import type { NextPage } from 'next' +import Layout from '../components/Layout' +import SEO from '../components/SEO' +import { + useAnchorWallet, + useConnection, + useWallet, +} from '@solana/wallet-adapter-react' +import { + PythBalance, + StakeAccount, + StakeConnection, + STAKING_ADDRESS, +} from '@pythnetwork/staking' +import { useEffect, useState } from 'react' +import { utils, Wallet } from '@project-serum/anchor' +import toast from 'react-hot-toast' +import { capitalizeFirstLetter } from '../utils/capitalizeFirstLetter' +import { PublicKey } from '@solana/web3.js' +import { useRouter } from 'next/router' + +const ApproveSplit: NextPage = () => { + const { connection } = useConnection() + const anchorWallet = useAnchorWallet() + const { publicKey, connected } = useWallet() + + const [stakeConnection, setStakeConnection] = useState() + + const [stakeAccount, setStakeAccount] = useState() + const [amount, setAmount] = useState() + const [recipient, setRecipient] = useState() + + useEffect(() => { + const initialize = async () => { + try { + const stakeConnection = await StakeConnection.createStakeConnection( + connection, + anchorWallet as Wallet, + STAKING_ADDRESS + ) + setStakeConnection(stakeConnection) + } catch (e) { + toast.error(capitalizeFirstLetter(e.message)) + } + } + if (!connected) { + setStakeConnection(undefined) + } else { + initialize() + } + }, [connected]) + + const router = useRouter() + const { key } = router.query + + useEffect(() => { + const helper = async () => { + if (stakeConnection !== undefined) { + const splitAccountOwner: PublicKey = new PublicKey(key!) + const stakeAccount = (await stakeConnection!.getMainAccount( + splitAccountOwner + ))! + + const { balance, recipient } = (await stakeConnection.getSplitRequest( + stakeAccount + ))! + + setStakeAccount(stakeAccount) + setAmount(balance) + setRecipient(recipient) + } + } + helper() + }, [stakeConnection]) + + const approveSplit = async () => { + await stakeConnection!.acceptSplit(stakeAccount!, amount!, recipient!) + } + + return ( + + +

Approve a split request from {key}

+

+ {stakeAccount != undefined + ? `stake account address: ${stakeAccount.address}` + : 'no owner'} +

+

{amount != undefined ? `amount: ${amount}` : 'no amount'}

+

+ {recipient != undefined ? `recipient: ${recipient}` : 'no recipient'} +

+ +
+ ) +} + +export default ApproveSplit diff --git a/staking/app/StakeConnection.ts b/staking/app/StakeConnection.ts index 4e4e4b18..c378ed21 100644 --- a/staking/app/StakeConnection.ts +++ b/staking/app/StakeConnection.ts @@ -873,6 +873,26 @@ export class StakeConnection { .rpc(); } + public async getSplitRequest( + stakeAccount: StakeAccount + ): Promise<{ balance: PythBalance; recipient: PublicKey } | undefined> { + const splitRequestAccount = PublicKey.findProgramAddressSync( + [ + utils.bytes.utf8.encode(wasm.Constants.SPLIT_REQUEST()), + stakeAccount.address.toBuffer(), + ], + this.program.programId + )[0]; + const splitRequest = await this.program.account.splitRequest.fetch( + splitRequestAccount + ); + + return { + balance: new PythBalance(splitRequest.amount), + recipient: splitRequest.recipient, + }; + } + public async acceptSplit( stakeAccount: StakeAccount, amount: PythBalance, diff --git a/staking/programs/staking/src/context.rs b/staking/programs/staking/src/context.rs index 7d734080..9793f1e5 100644 --- a/staking/programs/staking/src/context.rs +++ b/staking/programs/staking/src/context.rs @@ -320,7 +320,7 @@ pub struct AcceptSplit<'info> { pub source_stake_account_positions: AccountLoader<'info, positions::PositionData>, #[account(mut, seeds = [STAKE_ACCOUNT_METADATA_SEED.as_bytes(), source_stake_account_positions.key().as_ref()], bump = source_stake_account_metadata.metadata_bump)] pub source_stake_account_metadata: Box>, - #[account(seeds = [SPLIT_REQUEST.as_bytes(), source_stake_account_positions.key().as_ref()], bump)] + #[account(mut, seeds = [SPLIT_REQUEST.as_bytes(), source_stake_account_positions.key().as_ref()], bump)] pub source_stake_account_split_request: Box>, #[account( mut, diff --git a/staking/programs/staking/src/wasm.rs b/staking/programs/staking/src/wasm.rs index 8bf4a5ce..98711e39 100644 --- a/staking/programs/staking/src/wasm.rs +++ b/staking/programs/staking/src/wasm.rs @@ -297,6 +297,7 @@ reexport_seed_const!(TARGET_SEED); reexport_seed_const!(MAX_VOTER_RECORD_SEED); reexport_seed_const!(VOTING_TARGET_SEED); reexport_seed_const!(DATA_TARGET_SEED); +reexport_seed_const!(SPLIT_REQUEST); #[wasm_bindgen] impl Constants { diff --git a/staking/target/idl/staking.json b/staking/target/idl/staking.json index bc74f6c2..c6066a57 100644 --- a/staking/target/idl/staking.json +++ b/staking/target/idl/staking.json @@ -949,7 +949,7 @@ }, { "name": "sourceStakeAccountSplitRequest", - "isMut": false, + "isMut": true, "isSigner": false, "pda": { "seeds": [ diff --git a/staking/target/types/staking.ts b/staking/target/types/staking.ts index 42fe3a48..6549bddd 100644 --- a/staking/target/types/staking.ts +++ b/staking/target/types/staking.ts @@ -949,7 +949,7 @@ export type Staking = { }, { "name": "sourceStakeAccountSplitRequest", - "isMut": false, + "isMut": true, "isSigner": false, "pda": { "seeds": [ @@ -2920,7 +2920,7 @@ export const IDL: Staking = { }, { "name": "sourceStakeAccountSplitRequest", - "isMut": false, + "isMut": true, "isSigner": false, "pda": { "seeds": [ diff --git a/staking/tests/split_vesting_account.ts b/staking/tests/split_vesting_account.ts index b34a886f..0898ce70 100644 --- a/staking/tests/split_vesting_account.ts +++ b/staking/tests/split_vesting_account.ts @@ -179,12 +179,30 @@ describe("split vesting account", async () => { aliceConnection.userPublicKey() ); + let request = await samConnection.getSplitRequest(stakeAccount); + assert.equal( + JSON.stringify(request), + JSON.stringify({ + balance: PythBalance.fromString("33"), + recipient: aliceConnection.userPublicKey(), + }) + ); + await pdaConnection.acceptSplit( stakeAccount, PythBalance.fromString("33"), aliceConnection.userPublicKey() ); + request = await samConnection.getSplitRequest(stakeAccount); + assert.equal( + JSON.stringify(request), + JSON.stringify({ + balance: PythBalance.fromString("0"), + recipient: aliceConnection.userPublicKey(), + }) + ); + await assertMainAccountBalance( samConnection, VestingAccountState.UnvestedTokensFullyUnlocked,