Created
December 14, 2025 09:18
-
-
Save qapquiz/97d135b983d25de80dcc744b77542f0a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { DasApiAsset } from "@metaplex-foundation/digital-asset-standard-api"; | |
| import { useMutation } from "@tanstack/react-query"; | |
| import { | |
| type UiWalletAccount, | |
| useWalletAccountTransactionSendingSigner, | |
| useWalletAccountTransactionSigner, | |
| useWalletUi, | |
| useWalletUiCluster | |
| } from "@wallet-ui/react"; | |
| import { | |
| type Address, | |
| address, | |
| appendTransactionMessageInstructions, | |
| createTransactionMessage, | |
| getAddressEncoder, | |
| getProgramDerivedAddress, | |
| pipe, | |
| setTransactionMessageFeePayerSigner, | |
| setTransactionMessageLifetimeUsingBlockhash, | |
| signAndSendTransactionMessageWithSigners, | |
| type TransactionSigner, | |
| type TransactionSendingSigner, | |
| ProgramDerivedAddress, | |
| getBase58Decoder, | |
| createTransaction, | |
| signTransactionMessageWithSigners, | |
| } from "gill"; | |
| import { toast } from "sonner"; | |
| import { useTransactionToast } from "../use-transaction-toast"; | |
| import { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction } from '@solana-program/compute-budget'; | |
| import { getWithdrawNftsInstruction } from "@/clients/js/generated"; | |
| import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, getAssociatedTokenAccountAddress, getCreateAssociatedTokenIdempotentInstruction, getCreateAssociatedTokenInstruction, TOKEN_PROGRAM_ADDRESS } from "gill/programs/token"; | |
| const addresses = { | |
| kidsTokenMint: address("4peG5vF6VXbUt8PPA5LDbtdeRAPBGGrspDMW3ot6TdeX"), | |
| ghostKidCollection: address("FSw4cZhK5pMmhEDenDpa3CauJ9kLt5agr2U1oQxaH2cv"), | |
| ghostKidPool: address("JCSbaLqdn6nKtTVTUjAaxsv28TBhmpypcY3VAqdGKWLA"), | |
| ghostKidProgram: address("4BTy6FpUakBpNNTJFF6V7BK4fKR2bds6Sh523Z3gxy4k"), | |
| ghostKidVault: address("JCSbaLqdn6nKtTVTUjAaxsv28TBhmpypcY3VAqdGKWLA"), | |
| ghostKidVaultTokenAccount: address("6koxtKZV3LxSrS8dMpkMj1xLmSzMSTrRY1KCTsXTPvCC"), | |
| ghostKidAuthority: address("qgDDcomgjASwB27LaxMFXyzhpuzvRpkCSzbdDJcoEks"), | |
| ghostKidMetaplexRuleset: address("eBJLFYPxJmMGKuFwpDWkzxZeUrad92kZRC5BJLpzyT9"), | |
| } | |
| const programs = { | |
| metaplexTokenMetadataProgram: address("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), | |
| } | |
| export function useGhostkidProgram({ account }: { account: UiWalletAccount }) { | |
| const { client } = useWalletUi(); | |
| const { cluster } = useWalletUiCluster(); | |
| const transactionSigner = useWalletAccountTransactionSigner(account, cluster.id); | |
| const transactionSendingSigner = useWalletAccountTransactionSendingSigner(account, cluster.id); | |
| const toastTransaction = useTransactionToast(); | |
| const addressEncoder = getAddressEncoder(); | |
| const withdrawNfts = useMutation({ | |
| mutationKey: ["ghostkid-program", "withdraw-nfts"], | |
| mutationFn: async (selectedNft: DasApiAsset) => { | |
| const nftMint = address(selectedNft.id.toString()); | |
| // const nftMint = address("Cv7M4bizntRBUASUd2egm21wA4kxCa4sax2T78GrPmPj") | |
| const [nftReceipt, _bump] = await getProgramDerivedAddress({ | |
| programAddress: addresses.ghostKidProgram, | |
| seeds: ["nft_receipt", addressEncoder.encode(nftMint)] | |
| }); | |
| // ATA | |
| const withdrawerKidsTokenAccount = await getAssociatedTokenAccountAddress(addresses.kidsTokenMint, address(account.address)); | |
| const withdrawerNftAta = await getAssociatedTokenAccountAddress(nftMint, address(account.address)); | |
| // Magic TokenAccount | |
| const lastSignature = await client.rpc.getSignaturesForAddress(nftMint, { limit: 2 }).send(); | |
| const depositTx = await client.rpc.getTransaction(lastSignature[1].signature).send(); | |
| if (!depositTx) { | |
| throw new Error("Cannot get deposit transaction"); | |
| } | |
| const magicAccountIndex = depositTx.transaction.message.instructions[2].accounts[5]; | |
| const magicTokenAccount = address(depositTx.transaction.message.accountKeys[magicAccountIndex]); | |
| const editionIndex = depositTx.transaction.message.instructions[2].accounts[11]; | |
| const editionAccount = address(depositTx.transaction.message.accountKeys[editionIndex]); | |
| const metadataIndex = depositTx.transaction.message.instructions[2].accounts[12]; | |
| const metadataAccount = address(depositTx.transaction.message.accountKeys[metadataIndex]); | |
| // TokenRecord (pNFT) | |
| const [record, _recordBump] = await findTokenRecordPDA({ nftMint, nftAta: magicTokenAccount }); | |
| const [destinationRecord, _destinationRecordBump] = await findTokenRecordPDA({ nftMint, nftAta: withdrawerNftAta }) | |
| const instructions = [ | |
| // getSetComputeUnitPriceInstruction({ microLamports: 400_000 }), | |
| // getSetComputeUnitLimitInstruction({ units: 600_000 }), | |
| getCreateAssociatedTokenInstruction({ | |
| payer: transactionSendingSigner as unknown as TransactionSendingSigner, | |
| ata: withdrawerNftAta, | |
| owner: address(account.address), | |
| mint: nftMint, | |
| tokenProgram: TOKEN_PROGRAM_ADDRESS, | |
| }), | |
| getWithdrawNftsInstruction({ | |
| nftReceipt, | |
| vault: addresses.ghostKidVault, | |
| vaultTokenAccount: addresses.ghostKidVaultTokenAccount, | |
| authority: addresses.ghostKidAuthority, | |
| withdrawer: transactionSendingSigner as unknown as TransactionSendingSigner, | |
| withdrawerTokenAccount: withdrawerKidsTokenAccount, // kids token account | |
| nftTokenAccount: magicTokenAccount, // current token account | |
| mint: nftMint, | |
| token: withdrawerNftAta, // withdrawer nft token account | |
| record, | |
| destinationRecord, | |
| edition: editionAccount, | |
| metadata: metadataAccount, | |
| tokenMint: addresses.kidsTokenMint, | |
| metaplexRuleset: addresses.ghostKidMetaplexRuleset, | |
| metadataProgram: programs.metaplexTokenMetadataProgram, | |
| associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, | |
| }), | |
| ]; | |
| const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send(); | |
| const transactionMessage = pipe( | |
| createTransactionMessage({ version: 0 }), | |
| m => setTransactionMessageFeePayerSigner(transactionSendingSigner as unknown as TransactionSendingSigner, m), | |
| m => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), | |
| m => appendTransactionMessageInstructions(instructions, m), | |
| ); | |
| const tx = createTransaction({ | |
| feePayer: transactionSendingSigner as unknown as TransactionSendingSigner, | |
| version: 0, | |
| instructions, | |
| latestBlockhash, | |
| computeUnitLimit: 600_000, | |
| }) | |
| const signature = await signAndSendTransactionMessageWithSigners(tx); | |
| return getBase58Decoder().decode(signature); | |
| }, | |
| onSuccess: (signature: string) => { | |
| toastTransaction(signature); | |
| }, | |
| onError: (error, _variables, _context) => { | |
| toast.error(`Transaction failed! ${error}`); | |
| }, | |
| }); | |
| return { | |
| withdrawNfts, | |
| }; | |
| } | |
| async function findTokenRecordPDA({ nftMint, nftAta }: { nftMint: Address<string>, nftAta: Address<string> }): Promise<ProgramDerivedAddress> { | |
| const addressEncoder = getAddressEncoder(); | |
| return await getProgramDerivedAddress({ | |
| programAddress: programs.metaplexTokenMetadataProgram, | |
| seeds: [ | |
| "metadata", | |
| addressEncoder.encode(programs.metaplexTokenMetadataProgram), | |
| addressEncoder.encode(nftMint), | |
| "token_record", | |
| addressEncoder.encode(nftAta), | |
| ] | |
| }); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment