Message.compile constructor
Message.compile({
- required int? version,
- required Pubkey payer,
- required List<
TransactionInstruction> instructions, - required Blockhash recentBlockhash,
- 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,
);
}