Message.compile constructor

Message.compile({
  1. required int? version,
  2. required Pubkey payer,
  3. required List<TransactionInstruction> instructions,
  4. required Blockhash recentBlockhash,
  5. List<AddressLookupTableAccount>? addressLookupTableAccounts,
})

Creates a transaction message.

Implementation

factory Message.compile({
  required final int? version,
  required final Pubkey payer,
  required final List<TransactionInstruction> instructions,
  required final Blockhash recentBlockhash,
  final List<AddressLookupTableAccount>? addressLookupTableAccounts,
}) {
  /// Program account keys.
  final HashSet<Pubkey> programMetasMap = HashSet();

  /// Static account keys (i.e. all non address table lookup keys).
  final Map<Pubkey, AccountMeta> staticAccountMetasMap = {};

  /// Add the fee payer account.
  staticAccountMetasMap[payer] = AccountMeta.signerAndWritable(payer);

  /// Add the program and instruction accounts.
  for (final TransactionInstruction instruction in instructions) {
    final AccountMeta programMeta = AccountMeta(instruction.programId);
    programMetasMap.add(instruction.programId);
    staticAccountMetasMap[instruction.programId] ??= programMeta;
    for (final AccountMeta key in instruction.keys) {
      final AccountMeta? meta = staticAccountMetasMap[key.pubkey];
      staticAccountMetasMap[key.pubkey] = meta?.copyWith(
            isSigner: meta.isSigner || key.isSigner,
            isWritable: meta.isWritable || key.isWritable,
          ) ??
          key;
    }
  }

  /// Address table lookups.
  final List<MessageAddressTableLookup> addressTableLookups = [];
  final List<Pubkey> writableLookups = [];
  final List<Pubkey> readonlyLookups = [];

  /// Extract address table lookup accounts from [staticAccountMetasMap].
  for (final AddressLookupTableAccount lookupAccount
      in addressLookupTableAccounts ?? const []) {
    final List<Pubkey> lookupAddresses = lookupAccount.state.addresses;
    final List<int> writableIndexes = [];
    final List<int> readonlyIndexes = [];
    for (int i = 0; i < lookupAddresses.length; ++i) {
      final Pubkey lookupAddress = lookupAddresses[i];
      final AccountMeta? meta = staticAccountMetasMap[lookupAddress];
      if (meta != null &&
          !meta.isSigner &&
          !programMetasMap.contains(meta.pubkey)) {
        check(i < 256, 'Address lookup table index $i exceeds the max 255.');
        staticAccountMetasMap.remove(lookupAddress);
        if (meta.isWritable) {
          writableIndexes.add(i);
          writableLookups.add(lookupAddress);
        } else {
          readonlyIndexes.add(i);
          readonlyLookups.add(lookupAddress);
        }
      }
    }
    if (writableIndexes.isNotEmpty || readonlyIndexes.isNotEmpty) {
      addressTableLookups.add(
        MessageAddressTableLookup(
          accountKey: lookupAccount.key,
          writableIndexes: writableIndexes,
          readonlyIndexes: readonlyIndexes,
        ),
      );
    }
  }

  // Sort by signer accounts, followed by writable accounts.
  final List<AccountMeta> staticAccountMetas =
      staticAccountMetasMap.values.toList(growable: false);
  staticAccountMetas.sort((final AccountMeta x, final AccountMeta y) {
    // Signers must be placed before non-signers.
    if (x.isSigner != y.isSigner) {
      return x.isSigner ? -1 : 1;
    }
    // Writable accounts must be placed before read-only accounts.
    if (x.isWritable != y.isWritable) {
      return x.isWritable ? -1 : 1;
    }
    return 0;
  });

  // Group the accounts.
  final List<Pubkey> writableSigners = [];
  final List<Pubkey> readonlySigners = [];
  final List<Pubkey> writableNonSigners = [];
  final List<Pubkey> readonlyNonSigners = [];
  for (final AccountMeta meta in staticAccountMetas) {
    if (meta.isSigner) {
      meta.isWritable
          ? writableSigners.add(meta.pubkey)
          : readonlySigners.add(meta.pubkey);
    } else {
      meta.isWritable
          ? writableNonSigners.add(meta.pubkey)
          : readonlyNonSigners.add(meta.pubkey);
    }
  }

  // Create the header.
  final MessageHeader header = MessageHeader(
    numRequiredSignatures: writableSigners.length + readonlySigners.length,
    numReadonlySignedAccounts: readonlySigners.length,
    numReadonlyUnsignedAccounts: readonlyNonSigners.length,
  );

  // Collect static account keys.
  final List<Pubkey> staticAccountKeys = writableSigners +
      readonlySigners +
      writableNonSigners +
      readonlyNonSigners;

  // Collect all account keys.
  final List<Pubkey> accountKeys =
      staticAccountKeys + writableLookups + readonlyLookups;

  // DEBUG: Check that the first account is the payer.
  assert(accountKeys.first == payer, 'Missing payer.');

  // DEBUG: Check for duplicate accounts.
  assert(accountKeys.length == Set.of(accountKeys).length, 'Duplicate keys.');

  // Create map from account key to index.
  final Map<Pubkey, int> accountKeysMap = {};
  for (int i = 0; i < accountKeys.length; ++i) {
    accountKeysMap[accountKeys[i]] = i;
  }

  // Create message instructions.
  final List<MessageInstruction> compiledInstructions = [];
  for (final TransactionInstruction instruction in instructions) {
    compiledInstructions.add(
      MessageInstruction(
        programIdIndex: accountKeysMap[instruction.programId]!,
        accounts: instruction.keys.map((e) => accountKeysMap[e.pubkey]!),
        data: base58.encode(instruction.data),
      ),
    );
  }

  return Message(
    version: version,
    header: header,
    accountKeys: staticAccountKeys,
    recentBlockhash: recentBlockhash,
    instructions: compiledInstructions,
    addressTableLookups: addressTableLookups,
  );
}