import {
AccountRole,
Address,
address,
AddressesByLookupTableAddress,
appendTransactionMessageInstructions,
Base64EncodedBytes,
Blockhash,
compileTransaction,
compressTransactionMessageUsingAddressLookupTables,
createKeyPairSignerFromBytes,
createSolanaRpc,
createTransactionMessage,
getBase58Decoder,
getBase58Encoder,
getBase64Codec,
getBase64EncodedWireTransaction,
AccountMeta,
Instruction,
pipe,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
signTransaction,
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system"; // For adding custom instructions
// ── Config ──────────────────────────────────────────────────────────────────
const BASE_URL = "https://api.jup.ag/swap/v2";
const COMPUTE_BUDGET_PROGRAM: Address = address(
"ComputeBudget111111111111111111111111111111",
);
const COMPUTE_UNIT_LIMIT_MAX = 1_400_000;
const CU_BUFFER_NUMERATOR = 120n;
const CU_BUFFER_DENOMINATOR = 100n; // 120% of simulated CUs
// ── Instruction types ───────────────────────────────────────────────────────
type Account = {
pubkey: Address;
isSigner: boolean;
isWritable: boolean;
};
type ApiInstruction = {
programId: Address;
accounts: Account[];
data: Base64EncodedBytes;
};
function toAccountMeta(acc: Account): AccountMeta {
const role =
acc.isSigner && acc.isWritable
? AccountRole.WRITABLE_SIGNER
: acc.isSigner
? AccountRole.READONLY_SIGNER
: acc.isWritable
? AccountRole.WRITABLE
: AccountRole.READONLY;
return { address: acc.pubkey, role };
}
function createInstruction(
ix: ApiInstruction,
): Instruction<string, readonly AccountMeta[]> {
return {
programAddress: ix.programId,
accounts: ix.accounts.map(toAccountMeta),
data: Uint8Array.from(getBase64Codec().encode(ix.data)),
};
}
// ── Build response type ─────────────────────────────────────────────────────
type BuildResponse = {
inputMint: string;
outputMint: string;
inAmount: string;
outAmount: string;
otherAmountThreshold: string;
swapMode: string;
slippageBps: number;
routePlan: {
percent: number;
bps: number;
swapInfo: {
ammKey: string;
label: string;
inputMint: string;
outputMint: string;
inAmount: string;
outAmount: string;
};
}[];
computeBudgetInstructions: ApiInstruction[];
setupInstructions: ApiInstruction[];
swapInstruction: ApiInstruction;
cleanupInstruction: ApiInstruction | null;
otherInstructions: ApiInstruction[];
addressesByLookupTableAddress: Record<string, string[]> | null;
blockhashWithMetadata: {
blockhash: number[];
lastValidBlockHeight: number;
};
};
// ── Helpers ──────────────────────────────────────────────────────────────────
function makeSetComputeUnitLimitIx(units: number): ApiInstruction {
const data = Buffer.alloc(5);
data.writeUInt8(0x02, 0);
data.writeUInt32LE(units, 1);
return {
programId: COMPUTE_BUDGET_PROGRAM,
accounts: [],
data: data.toString("base64") as ApiInstruction["data"],
};
}
function transformBlockhash(meta: BuildResponse["blockhashWithMetadata"]): {
blockhash: Blockhash;
lastValidBlockHeight: bigint;
} {
return {
blockhash: getBase58Decoder().decode(
Uint8Array.from(meta.blockhash),
) as Blockhash,
lastValidBlockHeight: BigInt(meta.lastValidBlockHeight),
};
}
function transformALTs(
raw: Record<string, string[]> | null,
): AddressesByLookupTableAddress {
if (!raw) return {};
return Object.fromEntries(
Object.entries(raw).map(([key, addrs]) => [
address(key),
addrs.map((a) => address(a)),
]),
);
}
function buildTransaction(
ixs: Instruction<string, readonly AccountMeta[]>[],
blockhash: { blockhash: Blockhash; lastValidBlockHeight: bigint },
alts: AddressesByLookupTableAddress,
feePayer: Address,
) {
return pipe(
createTransactionMessage({ version: 0 }),
(msg) => appendTransactionMessageInstructions(ixs, msg),
(msg) => compressTransactionMessageUsingAddressLookupTables(msg, alts),
(msg) => setTransactionMessageFeePayer(feePayer, msg),
(msg) => setTransactionMessageLifetimeUsingBlockhash(blockhash, msg),
(msg) => compileTransaction(msg),
);
}