0x0x230x

Untitled

Feb 24th, 2026
39
0
Never
5
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.13 KB | None | 0 0
  1. 'use client';
  2.  
  3. import { Program } from '@coral-xyz/anchor';
  4. import { useConnectorClient, useTransactionSigner } from '@solana/connector';
  5. import {
  6. ASSOCIATED_TOKEN_PROGRAM_ID,
  7. createAssociatedTokenAccountInstruction,
  8. createCloseAccountInstruction,
  9. getAssociatedTokenAddressSync,
  10. NATIVE_MINT,
  11. TOKEN_PROGRAM_ID,
  12. } from '@solana/spl-token';
  13. import { Connection, PublicKey, Transaction, type TransactionInstruction } from '@solana/web3.js';
  14. import { useMutation } from '@tanstack/react-query';
  15. import BN from 'bn.js';
  16. import { useCallback } from 'react';
  17.  
  18. import {
  19. ORACLE_VAULT_DATA_SEED,
  20. SOLANA_PRICE_DENOMINATOR,
  21. VAULT_AUTHORITY_SEED,
  22. VAULT_DATA_SEED,
  23. YO_ORACLE_PROGRAM_ID,
  24. YOSOL_FEE_RECIPIENT,
  25. } from '@/lib/config/vaults/solana/config';
  26. import type { YoVault } from '@/lib/config/vaults/solana/yo-vault-sol';
  27. import yoVaultSolIdl from '@/lib/config/vaults/solana/yo-vault-sol.json';
  28.  
  29. type SolanaVaultAddresses = {
  30. tokenMint: string; // vault.asset.address
  31. shareMint: string; // vault.shareAsset.address
  32. };
  33.  
  34. type RedeemResult = {
  35. signature: string;
  36. confirmed: boolean;
  37. };
  38.  
  39. const DENOMINATOR_BN = new BN(SOLANA_PRICE_DENOMINATOR.toString());
  40.  
  41. /**
  42. * Preview redeem to determine if the redemption can be fulfilled immediately.
  43. * Matches solana-dapp's previewRedeem logic.
  44. */
  45. async function previewRedeem(
  46. program: Program<YoVault>,
  47. connection: Connection,
  48. shares: BN,
  49. tokenMint: PublicKey
  50. ): Promise<{ immediate: boolean; assets: BN; fee: BN }> {
  51. const vaultProgramId = program.programId;
  52.  
  53. const [oraclePda] = PublicKey.findProgramAddressSync(
  54. [Buffer.from(ORACLE_VAULT_DATA_SEED), vaultProgramId.toBuffer()],
  55. YO_ORACLE_PROGRAM_ID
  56. );
  57. const [vaultDataPda] = PublicKey.findProgramAddressSync(
  58. [Buffer.from(VAULT_DATA_SEED)],
  59. vaultProgramId
  60. );
  61. const [vaultAuthorityPda] = PublicKey.findProgramAddressSync(
  62. [Buffer.from(VAULT_AUTHORITY_SEED)],
  63. vaultProgramId
  64. );
  65.  
  66. const vaultTokenAccount = getAssociatedTokenAddressSync(tokenMint, vaultAuthorityPda, true);
  67.  
  68. const [oracleAccount, vaultData, vaultBalanceInfo] = await Promise.all([
  69. connection.getAccountInfo(oraclePda),
  70. program.account.vaultData.fetch(vaultDataPda),
  71. connection.getTokenAccountBalance(vaultTokenAccount),
  72. ]);
  73.  
  74. if (!oracleAccount?.data) {
  75. throw new Error('Could not fetch oracle vault data');
  76. }
  77.  
  78. const oracleData = program.coder.accounts.decode('oracleVaultData', oracleAccount.data);
  79. const lastPrice: BN = oracleData.lastPrice;
  80. const pctRedemptionFee: number = vaultData.pctRedemptionFee;
  81. const totalPendingRedeems: BN = vaultData.totalPendingRedeems;
  82. const vaultBalance = new BN(vaultBalanceInfo.value.amount);
  83.  
  84. // Compute assets and fee
  85. const assets = shares.mul(lastPrice).div(DENOMINATOR_BN);
  86. const fee = assets.mul(new BN(pctRedemptionFee)).div(DENOMINATOR_BN);
  87. const assetsAfterFee = assets.sub(fee);
  88.  
  89. const available = vaultBalance.sub(totalPendingRedeems);
  90.  
  91. return {
  92. immediate: available.gte(assetsAfterFee),
  93. assets: assetsAfterFee,
  94. fee,
  95. };
  96. }
  97.  
  98. /**
  99. * Hook for redeeming shares from a Solana vault.
  100. *
  101. * Flow (matches solana-dapp):
  102. * 1. Check if tokenMint is NATIVE_MINT - if so, create WSOL ATA if needed
  103. * 2. Call vault requestRedeem instruction via Anchor
  104. * 3. Only unwrap WSOL if redemption is immediately fulfilled AND tokenMint is NATIVE_MINT
  105. *
  106. * @param addresses - Vault addresses from backend vault data
  107. * @param enabled - Whether the hook should be active (use false for EVM vaults)
  108. */
  109. export function useSolanaRedeem(addresses: SolanaVaultAddresses | null, enabled = true) {
  110. const { signer, ready, address: walletAddress } = useTransactionSigner();
  111. const client = useConnectorClient();
  112.  
  113. const mutation = useMutation({
  114. mutationFn: async (sharesLamports: bigint): Promise<RedeemResult> => {
  115. if (!signer || !walletAddress) {
  116. throw new Error('Wallet not connected');
  117. }
  118. if (!addresses) {
  119. throw new Error('Vault addresses not available');
  120. }
  121.  
  122. // Get RPC URL from connector client
  123. const rpcUrl = client?.getRpcUrl();
  124. if (!rpcUrl) {
  125. throw new Error('No RPC endpoint configured');
  126. }
  127.  
  128. const connection = new Connection(rpcUrl, 'confirmed');
  129. const walletPubkey = new PublicKey(walletAddress);
  130. const sharesBN = new BN(sharesLamports.toString());
  131.  
  132. const tokenMint = new PublicKey(addresses.tokenMint);
  133. const shareMint = new PublicKey(addresses.shareMint);
  134. const isNativeMint = tokenMint.equals(NATIVE_MINT);
  135.  
  136. const program: Program<YoVault> = new Program(yoVaultSolIdl as YoVault, { connection });
  137.  
  138. // Preview redeem to determine if immediate
  139. const { immediate } = await previewRedeem(program, connection, sharesBN, tokenMint);
  140.  
  141. const ixs: TransactionInstruction[] = [];
  142.  
  143. // Only handle WSOL ATA for native SOL vault
  144. if (isNativeMint) {
  145. const userWsolAccount = getAssociatedTokenAddressSync(NATIVE_MINT, walletPubkey);
  146. const ataInfo = await connection.getAccountInfo(userWsolAccount);
  147. if (!ataInfo) {
  148. ixs.push(
  149. createAssociatedTokenAccountInstruction(
  150. walletPubkey,
  151. userWsolAccount,
  152. walletPubkey,
  153. NATIVE_MINT,
  154. TOKEN_PROGRAM_ID,
  155. ASSOCIATED_TOKEN_PROGRAM_ID
  156. )
  157. );
  158. }
  159. }
  160.  
  161. // Build redeem instruction via Anchor
  162. const redeemIx = await program.methods
  163. .requestRedeem(sharesBN)
  164. .accounts({
  165. user: walletPubkey,
  166. tokenMint,
  167. shareMint,
  168. feeRecipient: YOSOL_FEE_RECIPIENT,
  169. program: program.programId,
  170. })
  171. .instruction();
  172. ixs.push(redeemIx);
  173.  
  174. // Only unwrap WSOL if immediate fulfillment AND native SOL vault
  175. if (isNativeMint && immediate) {
  176. const userWsolAccount = getAssociatedTokenAddressSync(NATIVE_MINT, walletPubkey);
  177. ixs.push(
  178. createCloseAccountInstruction(
  179. userWsolAccount,
  180. walletPubkey,
  181. walletPubkey,
  182. [],
  183. TOKEN_PROGRAM_ID
  184. )
  185. );
  186. }
  187.  
  188. // Build and send transaction
  189. const tx = new Transaction().add(...ixs);
  190. const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
  191. tx.recentBlockhash = blockhash;
  192. tx.feePayer = walletPubkey;
  193. tx.lastValidBlockHeight = lastValidBlockHeight;
  194.  
  195. // Sign and send via ConnectorKit
  196. const signature = await signer.signAndSendTransaction(tx);
  197.  
  198. return { signature, confirmed: true };
  199. },
  200. });
  201.  
  202. const reset = useCallback(() => {
  203. mutation.reset();
  204. }, [mutation]);
  205.  
  206. return {
  207. executeRedeem: mutation.mutate,
  208. executeRedeemAsync: mutation.mutateAsync,
  209. isPending: mutation.isPending,
  210. isConfirming: false,
  211. isConfirmed: !!mutation.data?.signature,
  212. error: mutation.error,
  213. txSignature: mutation.data?.signature,
  214. isReady: enabled && ready && !!signer,
  215. reset,
  216. };
  217. }
  218.  
  219. export default useSolanaRedeem;
  220.  
Advertisement
Comments
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
Add Comment
Please, Sign In to add comment