Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 'use client';
- import { Program } from '@coral-xyz/anchor';
- import { useConnectorClient, useTransactionSigner } from '@solana/connector';
- import {
- ASSOCIATED_TOKEN_PROGRAM_ID,
- createAssociatedTokenAccountInstruction,
- createCloseAccountInstruction,
- getAssociatedTokenAddressSync,
- NATIVE_MINT,
- TOKEN_PROGRAM_ID,
- } from '@solana/spl-token';
- import { Connection, PublicKey, Transaction, type TransactionInstruction } from '@solana/web3.js';
- import { useMutation } from '@tanstack/react-query';
- import BN from 'bn.js';
- import { useCallback } from 'react';
- import { YOSOL_FEE_RECIPIENT } from '@/lib/config/vaults/solana/config';
- import type { YoVault } from '@/lib/config/vaults/solana/yo-vault-sol';
- import yoVaultSolIdl from '@/lib/config/vaults/solana/yo-vault-sol.json';
- type SolanaVaultAddresses = {
- tokenMint: string; // vault.asset.address
- shareMint: string; // vault.shareAsset.address
- };
- type RedeemResult = {
- signature: string;
- confirmed: boolean;
- };
- /**
- * Hook for redeeming shares from a Solana vault.
- *
- * Flow:
- * 1. Create WSOL ATA if needed (to receive redeemed WSOL)
- * 2. Call vault requestRedeem instruction via Anchor
- * 3. Close WSOL account to unwrap WSOL back to SOL
- *
- * @param addresses - Vault addresses from backend vault data
- * @param enabled - Whether the hook should be active (use false for EVM vaults)
- */
- export function useSolanaRedeem(addresses: SolanaVaultAddresses | null, enabled = true) {
- const { signer, ready, address: walletAddress } = useTransactionSigner();
- const client = useConnectorClient();
- const mutation = useMutation({
- mutationFn: async (sharesLamports: bigint): Promise<RedeemResult> => {
- if (!signer || !walletAddress) {
- throw new Error('Wallet not connected');
- }
- if (!addresses) {
- throw new Error('Vault addresses not available');
- }
- // Get RPC URL from connector client
- const rpcUrl = client?.getRpcUrl();
- if (!rpcUrl) {
- throw new Error('No RPC endpoint configured');
- }
- const connection = new Connection(rpcUrl, 'confirmed');
- const walletPubkey = new PublicKey(walletAddress);
- const sharesBN = new BN(sharesLamports.toString());
- const tokenMint = new PublicKey(addresses.tokenMint);
- const shareMint = new PublicKey(addresses.shareMint);
- // Build pre-redeem instructions (create WSOL ATA if needed)
- const preRedeemIxs = await buildPreRedeemIxs(walletPubkey, connection);
- // Build redeem instruction via Anchor
- const program = new Program(yoVaultSolIdl as YoVault, { connection });
- const redeemIx = await program.methods
- .requestRedeem(sharesBN)
- .accounts({
- user: walletPubkey,
- tokenMint,
- shareMint,
- feeRecipient: YOSOL_FEE_RECIPIENT,
- program: program.programId,
- })
- .instruction();
- // Build post-redeem instructions (close WSOL account to unwrap to SOL)
- const postRedeemIxs = buildUnwrapSolIxs(walletPubkey);
- // Combine instructions into transaction
- const tx = new Transaction().add(...preRedeemIxs, redeemIx, ...postRedeemIxs);
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
- tx.recentBlockhash = blockhash;
- tx.feePayer = walletPubkey;
- tx.lastValidBlockHeight = lastValidBlockHeight;
- // Sign and send via ConnectorKit
- const signature = await signer.signAndSendTransaction(tx);
- return { signature, confirmed: true };
- },
- });
- const reset = useCallback(() => {
- mutation.reset();
- }, [mutation]);
- return {
- executeRedeem: mutation.mutate,
- executeRedeemAsync: mutation.mutateAsync,
- isPending: mutation.isPending,
- isConfirming: false,
- isConfirmed: !!mutation.data?.signature,
- error: mutation.error,
- txSignature: mutation.data?.signature,
- isReady: enabled && ready && !!signer,
- reset,
- };
- }
- /**
- * Build pre-redeem instructions.
- *
- * 1. Create WSOL ATA if it doesn't exist (to receive redeemed tokens)
- */
- async function buildPreRedeemIxs(
- user: PublicKey,
- connection: Connection
- ): Promise<TransactionInstruction[]> {
- const instructions: TransactionInstruction[] = [];
- // Get user's WSOL ATA address
- const userWsolAccount = getAssociatedTokenAddressSync(NATIVE_MINT, user);
- // Check if WSOL ATA exists
- const ataInfo = await connection.getAccountInfo(userWsolAccount);
- if (!ataInfo) {
- instructions.push(
- createAssociatedTokenAccountInstruction(
- user,
- userWsolAccount,
- user,
- NATIVE_MINT,
- TOKEN_PROGRAM_ID,
- ASSOCIATED_TOKEN_PROGRAM_ID
- )
- );
- }
- return instructions;
- }
- /**
- * Build instructions to unwrap WSOL back to SOL.
- *
- * Closes the WSOL token account which returns the SOL to the owner.
- */
- function buildUnwrapSolIxs(user: PublicKey): TransactionInstruction[] {
- const userWsolAccount = getAssociatedTokenAddressSync(NATIVE_MINT, user);
- // Close WSOL account - this transfers the lamports (SOL) back to the owner
- return [
- createCloseAccountInstruction(
- userWsolAccount, // account to close
- user, // destination for lamports
- user, // owner
- [], // multisig signers (none)
- TOKEN_PROGRAM_ID
- ),
- ];
- }
- export default useSolanaRedeem;
Advertisement