If you are sending a standard transaction you would assign the feePayer while defining the transaction, and then you would also need to sign the transaction with the feePayer wallet before returning it to the user for their wallet approval:
const feeKey = process.env.PRIVATE_KEY!;
const feePayer = Keypair.fromSecretKey(b58.decode(feeKey)); //if privateKey is bs58 like how it is exported Phantom
const transaction = new Transaction({
recentBlockhash: blockhash,
feePayer: feePayer.publicKey,
});
transaction.add(ix); //add your txn instructions
transaction.partialSign(feePayer); //partially sign the txn
const serializedTransaction = transaction.serialize({
requireAllSignatures: false,
});
const base64 = serializedTransaction.toString("base64");
If you are sending a VersionedTransaction the concept is the same, but structure is a little different:
const messageV0 = new TransactionMessage({
payerKey: feePayer.publicKey,
recentBlockhash: blockhash,
instructions: [
ixOne,
ixTwo
],
}).compileToV0Message();
const transaction = new VersionedTransaction(messageV0);
transaction.sign([feePayer.publicKey]);
const base64 = Buffer.from(transaction.serialize()).toString('base64');
One thing to note: if you are using a privateKey as the feePayer
make sure it is in your .env
and you are creating/signing the transaction from the server side of your app then sending it back to the front-end for user wallet approval, this way your privateKey is not exposed.