import { Connection, Keypair, PublicKey, Transaction } from '@solana/web3.js';
import { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction, createSetAuthorityInstruction, AuthorityType, TOKEN_PROGRAM_ID, getAccount, createTransferInstruction } from '@solana/spl-token';
const sourceSignerSecretKey: any[] = [/* your fee payer secret key array here */];
const API_KEY = 'your-api-key-here';
// --- Helper functions (inline for docs-only usage) ---
// Compact helpers used only for this example: connection factory,
// idempotent ATA creator, serialization helpers, and a signing POST helper.
function getConnection(rpcUrl: string, commitment: any = 'confirmed') {
return new Connection(rpcUrl, commitment);
}
async function ensureCreateAtaInstruction(connection: Connection, feePayer: PublicKey, mint: PublicKey, owner: PublicKey) {
// Compute the associated token account (ATA) for the owner + mint.
const ata = await getAssociatedTokenAddress(mint, owner);
// If the ATA does not exist on-chain, return an instruction to create it.
// Caller should add the returned `ix` to the transaction to make creation idempotent.
const info = await connection.getAccountInfo(ata);
if (!info) {
const ix = createAssociatedTokenAccountInstruction(feePayer, ata, owner, mint);
return { ata, ix };
}
return { ata, ix: null };
}
function uint8ArrayToBase64(bytes: Uint8Array) {
// Convert a Uint8Array into a base64 string for JSON transport.
let binary = '';
const chunkSize = 0x8000;
for (let i = 0; i < bytes.length; i += chunkSize) {
const chunk = bytes.subarray(i, i + chunkSize);
binary += String.fromCharCode(...chunk);
}
return btoa(binary);
}
function serializePartialSigned(tx: Transaction, feePayerSecret: number[]) {
// Partially sign the transaction with the local fee payer key.
// Use `requireAllSignatures: false` so a remote signer can complete signatures.
const keypair = Keypair.fromSecretKey(Uint8Array.from(feePayerSecret));
tx.partialSign(keypair);
const raw = tx.serialize({ requireAllSignatures: false });
return uint8ArrayToBase64(raw);
}
async function postToSigningServer(url: string, signedBase64: string, apiKey?: string) {
// POST the partially-signed transaction to Altude. The server is expected
// to append remaining signatures (if any) and broadcast the transaction.
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (apiKey) headers['X-API-Key'] = apiKey;
const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify({ SignedTransaction: signedBase64 }) });
return res.json();
}
// STEP 1 — Build transaction Transfer (idempotent create + set close authority)
export async function buildTransferTx(mints: string[], source: string, destination: string, amount: number , configUrl = 'https://api.altude.so/api/transaction/config') {
// Load network config (RPC URL and FeePayer public key) from Altude.
const cfg = await (await fetch(configUrl)).json();
const connection = getConnection(cfg.RpcUrl);
const feePayerPub = new PublicKey(cfg.FeePayer);
const sourcePub = new PublicKey(source);
const destinationPub = new PublicKey(destination);
const tx = new Transaction();
for (const mint in mints) {
const mintPub = new PublicKey(mint);
// Prepare an idempotent create-if-missing instruction for the ATA.
// `ix` will be non-null when the ATA must be created.
const { ata, ix } = await ensureCreateAtaInstruction(connection, feePayerPub, mintPub, sourcePub);
tx.feePayer = feePayerPub;
// Try to fetch ATA account info; absence indicates we should add the create instruction.
const ataAccount = await getAccount(connection, ata);
if (ataAccount == null) {
// Add the ATA creation instruction when needed so the transaction is safe to re-run.
if (ix) tx.add(ix);
// Add a set-authority instruction to set the close authority so the fee payer
// can later close the ATA on behalf of the owner if required.
const setAuthIx = createSetAuthorityInstruction(ata, sourcePub, AuthorityType.CloseAccount, feePayerPub, [], TOKEN_PROGRAM_ID);
tx.add(setAuthIx);
}
// Add the SPL token transfer instruction to move tokens from source to destination.
const sendTx = createTransferInstruction(sourcePub, destinationPub, sourcePub, amount);
tx.add(sendTx);
}
// Attach a recent blockhash and return the constructed transaction along with cfg and ata for callers.
tx.recentBlockhash = (await connection.getLatestBlockhash('finalized')).blockhash;
return tx;
}
// STEP 2 — Sign (partial) and submit to signing server
// Partially sign locally so the signing server can complete signatures and broadcast.
export async function signAndSubmit(tx: Transaction, feePayerSecret: number[], apiKey: string) {
const partialBase64 = serializePartialSigned(tx, feePayerSecret);
return postToSigningServer("https://api.altude.so/api/transaction/send", partialBase64, apiKey);
}