import {
Keypair,
PublicKey,
SystemProgram,
Transaction,
TransactionInstruction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import { createRpc } from "@lightprotocol/stateless.js";
import { LightTokenProgram } from "@lightprotocol/compressed-token";
import {
ExtensionType,
TOKEN_2022_PROGRAM_ID,
createInitializeMint2Instruction,
getMintLen,
} from "@solana/spl-token";
const rpc = createRpc(RPC_ENDPOINT);
/**
* Build the InitializeMint instruction for ConfidentialTransferMint.
*
* The @solana/spl-token SDK defines ExtensionType.ConfidentialTransferMint
* but does not yet export a helper for this instruction, so we construct
* it manually using the Token-2022 instruction layout.
*/
function createInitializeConfidentialTransferMintIx(
mint: PublicKey,
authority: PublicKey | null,
autoApproveNewAccounts: boolean,
auditorElGamalPubkey: Uint8Array | null,
): TransactionInstruction {
// TokenInstruction::ConfidentialTransferExtension = 27
// ConfidentialTransferInstruction::InitializeMint = 0
const data = Buffer.alloc(2 + 1 + 32 + 1 + 1 + 32);
let offset = 0;
data.writeUInt8(27, offset); offset += 1; // TokenInstruction
data.writeUInt8(0, offset); offset += 1; // InitializeMint sub-instruction
// authority (COption<Pubkey>): 1 byte tag + 32 bytes
if (authority) {
data.writeUInt8(1, offset); offset += 1;
authority.toBuffer().copy(data, offset); offset += 32;
} else {
data.writeUInt8(0, offset); offset += 1;
offset += 32;
}
// auto_approve_new_accounts: bool (1 byte)
data.writeUInt8(autoApproveNewAccounts ? 1 : 0, offset); offset += 1;
// auditor_elgamal_pubkey (COption<ElGamalPubkey>): 1 byte tag + 32 bytes
if (auditorElGamalPubkey) {
data.writeUInt8(1, offset); offset += 1;
Buffer.from(auditorElGamalPubkey).copy(data, offset);
} else {
data.writeUInt8(0, offset); offset += 1;
}
return new TransactionInstruction({
keys: [{ pubkey: mint, isSigner: false, isWritable: true }],
programId: TOKEN_2022_PROGRAM_ID,
data,
});
}
const mintKeypair = Keypair.generate();
const decimals = 9;
// Calculate space including ConfidentialTransferMint extension
const mintLen = getMintLen([ExtensionType.ConfidentialTransferMint]);
const rentExemptBalance =
await rpc.getMinimumBalanceForRentExemption(mintLen);
// Create account
const createAccountIx = SystemProgram.createAccount({
fromPubkey: payer.publicKey,
lamports: rentExemptBalance,
newAccountPubkey: mintKeypair.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
space: mintLen,
});
// Initialize ConfidentialTransferMint extension (must come before mint init)
// auto_approve_new_accounts: false — extension is initialized but not enabled
// auditor_elgamal_pubkey: null — no auditor configured
const initConfidentialTransferIx = createInitializeConfidentialTransferMintIx(
mintKeypair.publicKey,
payer.publicKey, // authority
false, // auto_approve_new_accounts (not enabled)
null, // auditor_elgamal_pubkey
);
// Initialize mint
const initMintIx = createInitializeMint2Instruction(
mintKeypair.publicKey,
decimals,
payer.publicKey, // mint authority
null, // freeze authority
TOKEN_2022_PROGRAM_ID,
);
// Register interface PDA with Light Token
const createSplInterfaceIx = await LightTokenProgram.createSplInterface({
feePayer: payer.publicKey,
mint: mintKeypair.publicKey,
tokenProgramId: TOKEN_2022_PROGRAM_ID,
});
const tx = new Transaction().add(
createAccountIx,
initConfidentialTransferIx,
initMintIx,
createSplInterfaceIx,
);
const signature = await sendAndConfirmTransaction(rpc, tx, [
payer,
mintKeypair,
]);