import { Address } from '@solana/addresses'; import { SOLANA_ERROR__TRANSACTION__TOO_MANY_ACCOUNT_ADDRESSES, SOLANA_ERROR__TRANSACTION__TOO_MANY_ACCOUNTS_IN_INSTRUCTION, SOLANA_ERROR__TRANSACTION__TOO_MANY_INSTRUCTIONS, SOLANA_ERROR__TRANSACTION__TOO_MANY_SIGNER_ADDRESSES, SolanaError, } from '@solana/errors'; import { isSignerRole } from '@solana/instructions'; import { TransactionMessageWithFeePayer, TransactionMessageWithLifetime } from '../..'; import { TransactionMessage } from '../../transaction-message'; import { ForwardTransactionMessageLifetime } from '../message-types'; import { getAddressMapFromInstructions, getOrderedAccountsFromAddressMap } from './accounts'; import { getCompiledMessageHeader } from './header'; import { getCompiledInstructions } from './instructions'; import { getCompiledLifetimeToken } from './lifetime-token'; export type LegacyCompiledTransactionMessage = Readonly<{ /** Information about the role of the accounts loaded. */ header: ReturnType; /** A list of instructions that this transaction will execute */ instructions: ReturnType; /** A list of addresses indicating which accounts to load */ staticAccounts: Address[]; version: 'legacy'; }>; /** * Converts the type of transaction message data structure that you create in your application to * the type of transaction message data structure that can be encoded for execution on the network. * * This is a lossy process; you can not fully reconstruct a source message from a compiled message * without extra information. In particular, supporting details about the lifetime constraint will * be lost to compilation. * * @see {@link decompileTransactionMessage} */ export function compileTransactionMessage< TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer & { version: 'legacy' }, >( transactionMessage: TTransactionMessage, ): ForwardTransactionMessageLifetime { type ReturnType = ForwardTransactionMessageLifetime; const addressMap = getAddressMapFromInstructions( transactionMessage.feePayer.address, transactionMessage.instructions, ); const orderedAccounts = getOrderedAccountsFromAddressMap(addressMap); const numAccounts = orderedAccounts.length; if (numAccounts > 64) { throw new SolanaError(SOLANA_ERROR__TRANSACTION__TOO_MANY_ACCOUNT_ADDRESSES, { actualCount: numAccounts, maxAllowed: 64, }); } const numSigners = orderedAccounts.filter(account => isSignerRole(account.role)).length; if (numSigners > 12) { throw new SolanaError(SOLANA_ERROR__TRANSACTION__TOO_MANY_SIGNER_ADDRESSES, { actualCount: numSigners, maxAllowed: 12, }); } const numInstructions = transactionMessage.instructions.length; if (numInstructions > 64) { throw new SolanaError(SOLANA_ERROR__TRANSACTION__TOO_MANY_INSTRUCTIONS, { actualCount: numInstructions, maxAllowed: 64, }); } for (let i = 0; i < transactionMessage.instructions.length; i++) { const numAccountsInInstruction = transactionMessage.instructions[i].accounts?.length ?? 0; if (numAccountsInInstruction > 255) { throw new SolanaError(SOLANA_ERROR__TRANSACTION__TOO_MANY_ACCOUNTS_IN_INSTRUCTION, { actualCount: numAccountsInInstruction, instructionIndex: i, maxAllowed: 255, }); } } const lifetimeConstraint = (transactionMessage as Partial).lifetimeConstraint; return { ...(lifetimeConstraint ? { lifetimeToken: getCompiledLifetimeToken(lifetimeConstraint) } : null), header: getCompiledMessageHeader(orderedAccounts), instructions: getCompiledInstructions(transactionMessage.instructions, orderedAccounts), staticAccounts: orderedAccounts.map(account => account.address), version: transactionMessage.version, } as ReturnType; }