Skip to content

Instantly share code, notes, and snippets.

@qapquiz
Created December 14, 2025 09:18
Show Gist options
  • Select an option

  • Save qapquiz/97d135b983d25de80dcc744b77542f0a to your computer and use it in GitHub Desktop.

Select an option

Save qapquiz/97d135b983d25de80dcc744b77542f0a to your computer and use it in GitHub Desktop.
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