@solana/transaction-messages
Version:
Helpers for creating transaction messages
948 lines (936 loc) • 38.8 kB
JavaScript
var errors = require('@solana/errors');
var rpcTypes = require('@solana/rpc-types');
var addresses = require('@solana/addresses');
var codecsCore = require('@solana/codecs-core');
var codecsDataStructures = require('@solana/codecs-data-structures');
var codecsNumbers = require('@solana/codecs-numbers');
var instructions = require('@solana/instructions');
var functional = require('@solana/functional');
// src/blockhash.ts
function isTransactionMessageWithBlockhashLifetime(transactionMessage) {
const lifetimeConstraintShapeMatches = "lifetimeConstraint" in transactionMessage && typeof transactionMessage.lifetimeConstraint.blockhash === "string" && typeof transactionMessage.lifetimeConstraint.lastValidBlockHeight === "bigint";
if (!lifetimeConstraintShapeMatches) return false;
try {
rpcTypes.assertIsBlockhash(transactionMessage.lifetimeConstraint.blockhash);
return true;
} catch {
return false;
}
}
function assertIsTransactionMessageWithBlockhashLifetime(transactionMessage) {
if (!isTransactionMessageWithBlockhashLifetime(transactionMessage)) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__EXPECTED_BLOCKHASH_LIFETIME);
}
}
function setTransactionMessageLifetimeUsingBlockhash(blockhashLifetimeConstraint, transactionMessage) {
if ("lifetimeConstraint" in transactionMessage && transactionMessage.lifetimeConstraint.blockhash === blockhashLifetimeConstraint.blockhash && transactionMessage.lifetimeConstraint.lastValidBlockHeight === blockhashLifetimeConstraint.lastValidBlockHeight) {
return transactionMessage;
}
const out = {
...transactionMessage,
lifetimeConstraint: Object.freeze(blockhashLifetimeConstraint)
};
Object.freeze(out);
return out;
}
function assertValidBaseString(alphabet4, testValue, givenValue = testValue) {
if (!testValue.match(new RegExp(`^[${alphabet4}]*$`))) {
throw new errors.SolanaError(errors.SOLANA_ERROR__CODECS__INVALID_STRING_FOR_BASE, {
alphabet: alphabet4,
base: alphabet4.length,
value: givenValue
});
}
}
var getBaseXEncoder = (alphabet4) => {
return codecsCore.createEncoder({
getSizeFromValue: (value) => {
const [leadingZeroes, tailChars] = partitionLeadingZeroes(value, alphabet4[0]);
if (!tailChars) return value.length;
const base10Number = getBigIntFromBaseX(tailChars, alphabet4);
return leadingZeroes.length + Math.ceil(base10Number.toString(16).length / 2);
},
write(value, bytes, offset) {
assertValidBaseString(alphabet4, value);
if (value === "") return offset;
const [leadingZeroes, tailChars] = partitionLeadingZeroes(value, alphabet4[0]);
if (!tailChars) {
bytes.set(new Uint8Array(leadingZeroes.length).fill(0), offset);
return offset + leadingZeroes.length;
}
let base10Number = getBigIntFromBaseX(tailChars, alphabet4);
const tailBytes = [];
while (base10Number > 0n) {
tailBytes.unshift(Number(base10Number % 256n));
base10Number /= 256n;
}
const bytesToAdd = [...Array(leadingZeroes.length).fill(0), ...tailBytes];
bytes.set(bytesToAdd, offset);
return offset + bytesToAdd.length;
}
});
};
var getBaseXDecoder = (alphabet4) => {
return codecsCore.createDecoder({
read(rawBytes, offset) {
const bytes = offset === 0 ? rawBytes : rawBytes.slice(offset);
if (bytes.length === 0) return ["", 0];
let trailIndex = bytes.findIndex((n) => n !== 0);
trailIndex = trailIndex === -1 ? bytes.length : trailIndex;
const leadingZeroes = alphabet4[0].repeat(trailIndex);
if (trailIndex === bytes.length) return [leadingZeroes, rawBytes.length];
const base10Number = bytes.slice(trailIndex).reduce((sum, byte) => sum * 256n + BigInt(byte), 0n);
const tailChars = getBaseXFromBigInt(base10Number, alphabet4);
return [leadingZeroes + tailChars, rawBytes.length];
}
});
};
function partitionLeadingZeroes(value, zeroCharacter) {
const [leadingZeros, tailChars] = value.split(new RegExp(`((?!${zeroCharacter}).*)`));
return [leadingZeros, tailChars];
}
function getBigIntFromBaseX(value, alphabet4) {
const base = BigInt(alphabet4.length);
let sum = 0n;
for (const char of value) {
sum *= base;
sum += BigInt(alphabet4.indexOf(char));
}
return sum;
}
function getBaseXFromBigInt(value, alphabet4) {
const base = BigInt(alphabet4.length);
const tailChars = [];
while (value > 0n) {
tailChars.unshift(alphabet4[Number(value % base)]);
value /= base;
}
return tailChars.join("");
}
var alphabet2 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
var getBase58Encoder = () => getBaseXEncoder(alphabet2);
var getBase58Decoder = () => getBaseXDecoder(alphabet2);
var memoizedAddressTableLookupEncoder;
function getAddressTableLookupEncoder() {
if (!memoizedAddressTableLookupEncoder) {
const indexEncoder = codecsDataStructures.getArrayEncoder(codecsNumbers.getU8Encoder(), { size: codecsNumbers.getShortU16Encoder() });
memoizedAddressTableLookupEncoder = codecsDataStructures.getStructEncoder([
["lookupTableAddress", addresses.getAddressEncoder()],
["writableIndexes", indexEncoder],
["readonlyIndexes", indexEncoder]
]);
}
return memoizedAddressTableLookupEncoder;
}
var memoizedAddressTableLookupDecoder;
function getAddressTableLookupDecoder() {
if (!memoizedAddressTableLookupDecoder) {
const indexEncoder = codecsDataStructures.getArrayDecoder(codecsNumbers.getU8Decoder(), { size: codecsNumbers.getShortU16Decoder() });
memoizedAddressTableLookupDecoder = codecsCore.transformDecoder(
codecsDataStructures.getStructDecoder([
["lookupTableAddress", addresses.getAddressDecoder()],
["writableIndexes", indexEncoder],
["readonlyIndexes", indexEncoder]
]),
(lookupTable) => "readableIndices" in lookupTable ? {
...lookupTable,
readonlyIndexes: lookupTable.readableIndices,
// @ts-expect-error Remove when `readableIndices` and `writableIndices` are removed.
writableIndexes: lookupTable.writableIndices
} : lookupTable
);
}
return memoizedAddressTableLookupDecoder;
}
var memoizedU8Encoder;
function getMemoizedU8Encoder() {
if (!memoizedU8Encoder) memoizedU8Encoder = codecsNumbers.getU8Encoder();
return memoizedU8Encoder;
}
var memoizedU8Decoder;
function getMemoizedU8Decoder() {
if (!memoizedU8Decoder) memoizedU8Decoder = codecsNumbers.getU8Decoder();
return memoizedU8Decoder;
}
function getMessageHeaderEncoder() {
return codecsDataStructures.getStructEncoder([
["numSignerAccounts", getMemoizedU8Encoder()],
["numReadonlySignerAccounts", getMemoizedU8Encoder()],
["numReadonlyNonSignerAccounts", getMemoizedU8Encoder()]
]);
}
function getMessageHeaderDecoder() {
return codecsDataStructures.getStructDecoder([
["numSignerAccounts", getMemoizedU8Decoder()],
["numReadonlySignerAccounts", getMemoizedU8Decoder()],
["numReadonlyNonSignerAccounts", getMemoizedU8Decoder()]
]);
}
var memoizedGetInstructionEncoder;
function getInstructionEncoder() {
if (!memoizedGetInstructionEncoder) {
memoizedGetInstructionEncoder = codecsCore.transformEncoder(
codecsDataStructures.getStructEncoder([
["programAddressIndex", codecsNumbers.getU8Encoder()],
["accountIndices", codecsDataStructures.getArrayEncoder(codecsNumbers.getU8Encoder(), { size: codecsNumbers.getShortU16Encoder() })],
["data", codecsCore.addEncoderSizePrefix(codecsDataStructures.getBytesEncoder(), codecsNumbers.getShortU16Encoder())]
]),
// Convert an instruction to have all fields defined
(instruction) => {
if (instruction.accountIndices !== void 0 && instruction.data !== void 0) {
return instruction;
}
return {
...instruction,
accountIndices: instruction.accountIndices ?? [],
data: instruction.data ?? new Uint8Array(0)
};
}
);
}
return memoizedGetInstructionEncoder;
}
var memoizedGetInstructionDecoder;
function getInstructionDecoder() {
if (!memoizedGetInstructionDecoder) {
memoizedGetInstructionDecoder = codecsCore.transformDecoder(
codecsDataStructures.getStructDecoder([
["programAddressIndex", codecsNumbers.getU8Decoder()],
["accountIndices", codecsDataStructures.getArrayDecoder(codecsNumbers.getU8Decoder(), { size: codecsNumbers.getShortU16Decoder() })],
[
"data",
codecsCore.addDecoderSizePrefix(codecsDataStructures.getBytesDecoder(), codecsNumbers.getShortU16Decoder())
]
]),
// Convert an instruction to exclude optional fields if they are empty
(instruction) => {
if (instruction.accountIndices.length && instruction.data.byteLength) {
return instruction;
}
const { accountIndices, data, ...rest } = instruction;
return {
...rest,
...accountIndices.length ? { accountIndices } : null,
...data.byteLength ? { data } : null
};
}
);
}
return memoizedGetInstructionDecoder;
}
var VERSION_FLAG_MASK = 128;
function getTransactionVersionEncoder() {
return codecsCore.createEncoder({
getSizeFromValue: (value) => value === "legacy" ? 0 : 1,
maxSize: 1,
write: (value, bytes, offset) => {
if (value === "legacy") {
return offset;
}
if (value < 0 || value > 127) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__VERSION_NUMBER_OUT_OF_RANGE, {
actualVersion: value
});
}
bytes.set([value | VERSION_FLAG_MASK], offset);
return offset + 1;
}
});
}
function getTransactionVersionDecoder() {
return codecsCore.createDecoder({
maxSize: 1,
read: (bytes, offset) => {
const firstByte = bytes[offset];
if ((firstByte & VERSION_FLAG_MASK) === 0) {
return ["legacy", offset];
} else {
const version = firstByte ^ VERSION_FLAG_MASK;
return [version, offset + 1];
}
}
});
}
function getTransactionVersionCodec() {
return codecsCore.combineCodec(getTransactionVersionEncoder(), getTransactionVersionDecoder());
}
// src/codecs/message.ts
function getCompiledMessageLegacyEncoder() {
return codecsDataStructures.getStructEncoder(getPreludeStructEncoderTuple());
}
function getCompiledMessageVersionedEncoder() {
return codecsCore.transformEncoder(
codecsDataStructures.getStructEncoder([
...getPreludeStructEncoderTuple(),
["addressTableLookups", getAddressTableLookupArrayEncoder()]
]),
(value) => {
if (value.version === "legacy") {
return value;
}
return {
...value,
addressTableLookups: value.addressTableLookups ?? []
};
}
);
}
function getPreludeStructEncoderTuple() {
return [
["version", getTransactionVersionEncoder()],
["header", getMessageHeaderEncoder()],
["staticAccounts", codecsDataStructures.getArrayEncoder(addresses.getAddressEncoder(), { size: codecsNumbers.getShortU16Encoder() })],
["lifetimeToken", codecsCore.fixEncoderSize(getBase58Encoder(), 32)],
["instructions", codecsDataStructures.getArrayEncoder(getInstructionEncoder(), { size: codecsNumbers.getShortU16Encoder() })]
];
}
function getPreludeStructDecoderTuple() {
return [
["version", getTransactionVersionDecoder()],
["header", getMessageHeaderDecoder()],
["staticAccounts", codecsDataStructures.getArrayDecoder(addresses.getAddressDecoder(), { size: codecsNumbers.getShortU16Decoder() })],
["lifetimeToken", codecsCore.fixDecoderSize(getBase58Decoder(), 32)],
["instructions", codecsDataStructures.getArrayDecoder(getInstructionDecoder(), { size: codecsNumbers.getShortU16Decoder() })],
["addressTableLookups", getAddressTableLookupArrayDecoder()]
];
}
function getAddressTableLookupArrayEncoder() {
return codecsDataStructures.getArrayEncoder(getAddressTableLookupEncoder(), { size: codecsNumbers.getShortU16Encoder() });
}
function getAddressTableLookupArrayDecoder() {
return codecsDataStructures.getArrayDecoder(getAddressTableLookupDecoder(), { size: codecsNumbers.getShortU16Decoder() });
}
function getCompiledTransactionMessageEncoder() {
return codecsCore.createEncoder({
getSizeFromValue: (compiledMessage) => {
if (compiledMessage.version === "legacy") {
return getCompiledMessageLegacyEncoder().getSizeFromValue(compiledMessage);
} else {
return getCompiledMessageVersionedEncoder().getSizeFromValue(compiledMessage);
}
},
write: (compiledMessage, bytes, offset) => {
if (compiledMessage.version === "legacy") {
return getCompiledMessageLegacyEncoder().write(compiledMessage, bytes, offset);
} else {
return getCompiledMessageVersionedEncoder().write(compiledMessage, bytes, offset);
}
}
});
}
function getCompiledTransactionMessageDecoder() {
return codecsCore.transformDecoder(
codecsDataStructures.getStructDecoder(getPreludeStructDecoderTuple()),
({ addressTableLookups, ...restOfMessage }) => {
if (restOfMessage.version === "legacy" || !addressTableLookups?.length) {
return restOfMessage;
}
return { ...restOfMessage, addressTableLookups };
}
);
}
function getCompiledTransactionMessageCodec() {
return codecsCore.combineCodec(getCompiledTransactionMessageEncoder(), getCompiledTransactionMessageDecoder());
}
function upsert(addressMap, address, update) {
addressMap[address] = update(addressMap[address] ?? { role: instructions.AccountRole.READONLY });
}
var TYPE = Symbol("AddressMapTypeProperty");
function getAddressMapFromInstructions(feePayer, instructions$1) {
const addressMap = {
[feePayer]: { [TYPE]: 0 /* FEE_PAYER */, role: instructions.AccountRole.WRITABLE_SIGNER }
};
const addressesOfInvokedPrograms = /* @__PURE__ */ new Set();
for (const instruction of instructions$1) {
upsert(addressMap, instruction.programAddress, (entry) => {
addressesOfInvokedPrograms.add(instruction.programAddress);
if (TYPE in entry) {
if (instructions.isWritableRole(entry.role)) {
switch (entry[TYPE]) {
case 0 /* FEE_PAYER */:
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__INVOKED_PROGRAMS_CANNOT_PAY_FEES, {
programAddress: instruction.programAddress
});
default:
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE, {
programAddress: instruction.programAddress
});
}
}
if (entry[TYPE] === 2 /* STATIC */) {
return entry;
}
}
return { [TYPE]: 2 /* STATIC */, role: instructions.AccountRole.READONLY };
});
let addressComparator;
if (!instruction.accounts) {
continue;
}
for (const account of instruction.accounts) {
upsert(addressMap, account.address, (entry) => {
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
address: _,
...accountMeta
} = account;
if (TYPE in entry) {
switch (entry[TYPE]) {
case 0 /* FEE_PAYER */:
return entry;
case 1 /* LOOKUP_TABLE */: {
const nextRole = instructions.mergeRoles(entry.role, accountMeta.role);
if ("lookupTableAddress" in accountMeta) {
const shouldReplaceEntry = (
// Consider using the new LOOKUP_TABLE if its address is different...
entry.lookupTableAddress !== accountMeta.lookupTableAddress && // ...and sorts before the existing one.
(addressComparator ||= addresses.getAddressComparator())(
accountMeta.lookupTableAddress,
entry.lookupTableAddress
) < 0
);
if (shouldReplaceEntry) {
return {
[TYPE]: 1 /* LOOKUP_TABLE */,
...accountMeta,
role: nextRole
};
}
} else if (instructions.isSignerRole(accountMeta.role)) {
return {
[TYPE]: 2 /* STATIC */,
role: nextRole
};
}
if (entry.role !== nextRole) {
return {
...entry,
role: nextRole
};
} else {
return entry;
}
}
case 2 /* STATIC */: {
const nextRole = instructions.mergeRoles(entry.role, accountMeta.role);
if (
// Check to see if this address represents a program that is invoked
// in this transaction.
addressesOfInvokedPrograms.has(account.address)
) {
if (instructions.isWritableRole(accountMeta.role)) {
throw new errors.SolanaError(
errors.SOLANA_ERROR__TRANSACTION__INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE,
{
programAddress: account.address
}
);
}
if (entry.role !== nextRole) {
return {
...entry,
role: nextRole
};
} else {
return entry;
}
} else if ("lookupTableAddress" in accountMeta && // Static accounts can be 'upgraded' to lookup table accounts as
// long as they are not require to sign the transaction.
!instructions.isSignerRole(entry.role)) {
return {
...accountMeta,
[TYPE]: 1 /* LOOKUP_TABLE */,
role: nextRole
};
} else {
if (entry.role !== nextRole) {
return {
...entry,
role: nextRole
};
} else {
return entry;
}
}
}
}
}
if ("lookupTableAddress" in accountMeta) {
return {
...accountMeta,
[TYPE]: 1 /* LOOKUP_TABLE */
};
} else {
return {
...accountMeta,
[TYPE]: 2 /* STATIC */
};
}
});
}
}
return addressMap;
}
function getOrderedAccountsFromAddressMap(addressMap) {
let addressComparator;
const orderedAccounts = Object.entries(addressMap).sort(([leftAddress, leftEntry], [rightAddress, rightEntry]) => {
if (leftEntry[TYPE] !== rightEntry[TYPE]) {
if (leftEntry[TYPE] === 0 /* FEE_PAYER */) {
return -1;
} else if (rightEntry[TYPE] === 0 /* FEE_PAYER */) {
return 1;
} else if (leftEntry[TYPE] === 2 /* STATIC */) {
return -1;
} else if (rightEntry[TYPE] === 2 /* STATIC */) {
return 1;
}
}
const leftIsSigner = instructions.isSignerRole(leftEntry.role);
if (leftIsSigner !== instructions.isSignerRole(rightEntry.role)) {
return leftIsSigner ? -1 : 1;
}
const leftIsWritable = instructions.isWritableRole(leftEntry.role);
if (leftIsWritable !== instructions.isWritableRole(rightEntry.role)) {
return leftIsWritable ? -1 : 1;
}
addressComparator ||= addresses.getAddressComparator();
if (leftEntry[TYPE] === 1 /* LOOKUP_TABLE */ && rightEntry[TYPE] === 1 /* LOOKUP_TABLE */ && leftEntry.lookupTableAddress !== rightEntry.lookupTableAddress) {
return addressComparator(leftEntry.lookupTableAddress, rightEntry.lookupTableAddress);
} else {
return addressComparator(leftAddress, rightAddress);
}
}).map(([address, addressMeta]) => ({
address,
...addressMeta
}));
return orderedAccounts;
}
function getCompiledAddressTableLookups(orderedAccounts) {
const index = {};
for (const account of orderedAccounts) {
if (!("lookupTableAddress" in account)) {
continue;
}
const entry = index[account.lookupTableAddress] ||= {
/** @deprecated Remove in a future major version */
readableIndices: [],
readonlyIndexes: [],
writableIndexes: [],
/** @deprecated Remove in a future major version */
writableIndices: []
};
if (account.role === instructions.AccountRole.WRITABLE) {
entry.writableIndexes.push(account.addressIndex);
entry.writableIndices.push(account.addressIndex);
} else {
entry.readableIndices.push(account.addressIndex);
entry.readonlyIndexes.push(account.addressIndex);
}
}
return Object.keys(index).sort(addresses.getAddressComparator()).map((lookupTableAddress) => ({
lookupTableAddress,
...index[lookupTableAddress]
}));
}
function getCompiledMessageHeader(orderedAccounts) {
let numReadonlyNonSignerAccounts = 0;
let numReadonlySignerAccounts = 0;
let numSignerAccounts = 0;
for (const account of orderedAccounts) {
if ("lookupTableAddress" in account) {
break;
}
const accountIsWritable = instructions.isWritableRole(account.role);
if (instructions.isSignerRole(account.role)) {
numSignerAccounts++;
if (!accountIsWritable) {
numReadonlySignerAccounts++;
}
} else if (!accountIsWritable) {
numReadonlyNonSignerAccounts++;
}
}
return {
numReadonlyNonSignerAccounts,
numReadonlySignerAccounts,
numSignerAccounts
};
}
// src/compile/instructions.ts
function getAccountIndex(orderedAccounts) {
const out = {};
for (const [index, account] of orderedAccounts.entries()) {
out[account.address] = index;
}
return out;
}
function getCompiledInstructions(instructions, orderedAccounts) {
const accountIndex = getAccountIndex(orderedAccounts);
return instructions.map(({ accounts, data, programAddress }) => {
return {
programAddressIndex: accountIndex[programAddress],
...accounts ? { accountIndices: accounts.map(({ address }) => accountIndex[address]) } : null,
...data ? { data } : null
};
});
}
// src/compile/lifetime-token.ts
function getCompiledLifetimeToken(lifetimeConstraint) {
if ("nonce" in lifetimeConstraint) {
return lifetimeConstraint.nonce;
}
return lifetimeConstraint.blockhash;
}
// src/compile/static-accounts.ts
function getCompiledStaticAccounts(orderedAccounts) {
const firstLookupTableAccountIndex = orderedAccounts.findIndex((account) => "lookupTableAddress" in account);
const orderedStaticAccounts = firstLookupTableAccountIndex === -1 ? orderedAccounts : orderedAccounts.slice(0, firstLookupTableAccountIndex);
return orderedStaticAccounts.map(({ address }) => address);
}
// src/compile/message.ts
function compileTransactionMessage(transactionMessage) {
const addressMap = getAddressMapFromInstructions(
transactionMessage.feePayer.address,
transactionMessage.instructions
);
const orderedAccounts = getOrderedAccountsFromAddressMap(addressMap);
return {
...transactionMessage.version !== "legacy" ? { addressTableLookups: getCompiledAddressTableLookups(orderedAccounts) } : null,
header: getCompiledMessageHeader(orderedAccounts),
instructions: getCompiledInstructions(transactionMessage.instructions, orderedAccounts),
lifetimeToken: getCompiledLifetimeToken(transactionMessage.lifetimeConstraint),
staticAccounts: getCompiledStaticAccounts(orderedAccounts),
version: transactionMessage.version
};
}
function findAddressInLookupTables(address, role, addressesByLookupTableAddress) {
for (const [lookupTableAddress, addresses] of Object.entries(addressesByLookupTableAddress)) {
for (let i = 0; i < addresses.length; i++) {
if (address === addresses[i]) {
return {
address,
addressIndex: i,
lookupTableAddress,
role
};
}
}
}
}
function compressTransactionMessageUsingAddressLookupTables(transactionMessage, addressesByLookupTableAddress) {
const lookupTableAddresses = new Set(Object.values(addressesByLookupTableAddress).flatMap((a) => a));
const newInstructions = [];
let updatedAnyInstructions = false;
for (const instruction of transactionMessage.instructions) {
if (!instruction.accounts) {
newInstructions.push(instruction);
continue;
}
const newAccounts = [];
let updatedAnyAccounts = false;
for (const account of instruction.accounts) {
if ("lookupTableAddress" in account || !lookupTableAddresses.has(account.address) || instructions.isSignerRole(account.role)) {
newAccounts.push(account);
continue;
}
const lookupMetaAccount = findAddressInLookupTables(
account.address,
account.role,
addressesByLookupTableAddress
);
newAccounts.push(Object.freeze(lookupMetaAccount));
updatedAnyAccounts = true;
updatedAnyInstructions = true;
}
newInstructions.push(
Object.freeze(updatedAnyAccounts ? { ...instruction, accounts: newAccounts } : instruction)
);
}
return Object.freeze(
updatedAnyInstructions ? { ...transactionMessage, instructions: newInstructions } : transactionMessage
);
}
// src/create-transaction-message.ts
function createTransactionMessage({
version
}) {
return Object.freeze({
instructions: Object.freeze([]),
version
});
}
var RECENT_BLOCKHASHES_SYSVAR_ADDRESS = "SysvarRecentB1ockHashes11111111111111111111";
var SYSTEM_PROGRAM_ADDRESS = "11111111111111111111111111111111";
function assertIsDurableNonceTransactionMessage(transactionMessage) {
if (!isDurableNonceTransaction(transactionMessage)) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__EXPECTED_NONCE_LIFETIME);
}
}
function createAdvanceNonceAccountInstruction(nonceAccountAddress, nonceAuthorityAddress) {
return {
accounts: [
{ address: nonceAccountAddress, role: instructions.AccountRole.WRITABLE },
{
address: RECENT_BLOCKHASHES_SYSVAR_ADDRESS,
role: instructions.AccountRole.READONLY
},
{ address: nonceAuthorityAddress, role: instructions.AccountRole.READONLY_SIGNER }
],
data: new Uint8Array([4, 0, 0, 0]),
programAddress: SYSTEM_PROGRAM_ADDRESS
};
}
function isAdvanceNonceAccountInstruction(instruction) {
return instruction.programAddress === SYSTEM_PROGRAM_ADDRESS && // Test for `AdvanceNonceAccount` instruction data
instruction.data != null && isAdvanceNonceAccountInstructionData(instruction.data) && // Test for exactly 3 accounts
instruction.accounts?.length === 3 && // First account is nonce account address
instruction.accounts[0].address != null && instruction.accounts[0].role === instructions.AccountRole.WRITABLE && // Second account is recent blockhashes sysvar
instruction.accounts[1].address === RECENT_BLOCKHASHES_SYSVAR_ADDRESS && instruction.accounts[1].role === instructions.AccountRole.READONLY && // Third account is nonce authority account
instruction.accounts[2].address != null && instructions.isSignerRole(instruction.accounts[2].role);
}
function isAdvanceNonceAccountInstructionData(data) {
return data.byteLength === 4 && data[0] === 4 && data[1] === 0 && data[2] === 0 && data[3] === 0;
}
function isDurableNonceTransaction(transactionMessage) {
return "lifetimeConstraint" in transactionMessage && typeof transactionMessage.lifetimeConstraint.nonce === "string" && transactionMessage.instructions[0] != null && isAdvanceNonceAccountInstruction(transactionMessage.instructions[0]);
}
function isAdvanceNonceAccountInstructionForNonce(instruction, nonceAccountAddress, nonceAuthorityAddress) {
return instruction.accounts[0].address === nonceAccountAddress && instruction.accounts[2].address === nonceAuthorityAddress;
}
function setTransactionMessageLifetimeUsingDurableNonce({
nonce,
nonceAccountAddress,
nonceAuthorityAddress
}, transactionMessage) {
let newInstructions;
const firstInstruction = transactionMessage.instructions[0];
if (firstInstruction && isAdvanceNonceAccountInstruction(firstInstruction)) {
if (isAdvanceNonceAccountInstructionForNonce(firstInstruction, nonceAccountAddress, nonceAuthorityAddress)) {
if (isDurableNonceTransaction(transactionMessage) && transactionMessage.lifetimeConstraint.nonce === nonce) {
return transactionMessage;
} else {
newInstructions = [firstInstruction, ...transactionMessage.instructions.slice(1)];
}
} else {
newInstructions = [
Object.freeze(createAdvanceNonceAccountInstruction(nonceAccountAddress, nonceAuthorityAddress)),
...transactionMessage.instructions.slice(1)
];
}
} else {
newInstructions = [
Object.freeze(createAdvanceNonceAccountInstruction(nonceAccountAddress, nonceAuthorityAddress)),
...transactionMessage.instructions
];
}
return Object.freeze({
...transactionMessage,
instructions: Object.freeze(newInstructions),
lifetimeConstraint: Object.freeze({
nonce
})
});
}
// src/fee-payer.ts
function setTransactionMessageFeePayer(feePayer, transactionMessage) {
if ("feePayer" in transactionMessage && feePayer === transactionMessage.feePayer?.address && isAddressOnlyFeePayer(transactionMessage.feePayer)) {
return transactionMessage;
}
const out = {
...transactionMessage,
feePayer: Object.freeze({ address: feePayer })
};
Object.freeze(out);
return out;
}
function isAddressOnlyFeePayer(feePayer) {
return !!feePayer && "address" in feePayer && typeof feePayer.address === "string" && Object.keys(feePayer).length === 1;
}
// src/instructions.ts
function appendTransactionMessageInstruction(instruction, transactionMessage) {
return appendTransactionMessageInstructions([instruction], transactionMessage);
}
function appendTransactionMessageInstructions(instructions, transactionMessage) {
return Object.freeze({
...transactionMessage,
instructions: Object.freeze([...transactionMessage.instructions, ...instructions])
});
}
function prependTransactionMessageInstruction(instruction, transactionMessage) {
return prependTransactionMessageInstructions([instruction], transactionMessage);
}
function prependTransactionMessageInstructions(instructions, transactionMessage) {
return Object.freeze({
...transactionMessage,
instructions: Object.freeze([...instructions, ...transactionMessage.instructions])
});
}
// src/decompile-message.ts
function getAccountMetas(message) {
const { header } = message;
const numWritableSignerAccounts = header.numSignerAccounts - header.numReadonlySignerAccounts;
const numWritableNonSignerAccounts = message.staticAccounts.length - header.numSignerAccounts - header.numReadonlyNonSignerAccounts;
const accountMetas = [];
let accountIndex = 0;
for (let i = 0; i < numWritableSignerAccounts; i++) {
accountMetas.push({
address: message.staticAccounts[accountIndex],
role: instructions.AccountRole.WRITABLE_SIGNER
});
accountIndex++;
}
for (let i = 0; i < header.numReadonlySignerAccounts; i++) {
accountMetas.push({
address: message.staticAccounts[accountIndex],
role: instructions.AccountRole.READONLY_SIGNER
});
accountIndex++;
}
for (let i = 0; i < numWritableNonSignerAccounts; i++) {
accountMetas.push({
address: message.staticAccounts[accountIndex],
role: instructions.AccountRole.WRITABLE
});
accountIndex++;
}
for (let i = 0; i < header.numReadonlyNonSignerAccounts; i++) {
accountMetas.push({
address: message.staticAccounts[accountIndex],
role: instructions.AccountRole.READONLY
});
accountIndex++;
}
return accountMetas;
}
function getAddressLookupMetas(compiledAddressTableLookups, addressesByLookupTableAddress) {
const compiledAddressTableLookupAddresses = compiledAddressTableLookups.map((l) => l.lookupTableAddress);
const missing = compiledAddressTableLookupAddresses.filter((a) => addressesByLookupTableAddress[a] === void 0);
if (missing.length > 0) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_CONTENTS_MISSING, {
lookupTableAddresses: missing
});
}
const readOnlyMetas = [];
const writableMetas = [];
for (const lookup of compiledAddressTableLookups) {
const addresses = addressesByLookupTableAddress[lookup.lookupTableAddress];
const readonlyIndexes = lookup.readonlyIndexes ?? /** @deprecated Remove in a future major version */
lookup.readableIndices;
const writableIndexes = lookup.writableIndexes ?? /** @deprecated Remove in a future major version */
lookup.writableIndices;
const highestIndex = Math.max(...readonlyIndexes, ...writableIndexes);
if (highestIndex >= addresses.length) {
throw new errors.SolanaError(
errors.SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_INDEX_OUT_OF_RANGE,
{
highestKnownIndex: addresses.length - 1,
highestRequestedIndex: highestIndex,
lookupTableAddress: lookup.lookupTableAddress
}
);
}
const readOnlyForLookup = readonlyIndexes.map((r) => ({
address: addresses[r],
addressIndex: r,
lookupTableAddress: lookup.lookupTableAddress,
role: instructions.AccountRole.READONLY
}));
readOnlyMetas.push(...readOnlyForLookup);
const writableForLookup = writableIndexes.map((w) => ({
address: addresses[w],
addressIndex: w,
lookupTableAddress: lookup.lookupTableAddress,
role: instructions.AccountRole.WRITABLE
}));
writableMetas.push(...writableForLookup);
}
return [...writableMetas, ...readOnlyMetas];
}
function convertInstruction(instruction, accountMetas) {
const programAddress = accountMetas[instruction.programAddressIndex]?.address;
if (!programAddress) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND, {
index: instruction.programAddressIndex
});
}
const accounts = instruction.accountIndices?.map((accountIndex) => accountMetas[accountIndex]);
const { data } = instruction;
return Object.freeze({
programAddress,
...accounts && accounts.length ? { accounts: Object.freeze(accounts) } : {},
...data && data.length ? { data } : {}
});
}
function getLifetimeConstraint(messageLifetimeToken, firstInstruction, lastValidBlockHeight) {
if (!firstInstruction || !isAdvanceNonceAccountInstruction(firstInstruction)) {
return {
blockhash: messageLifetimeToken,
lastValidBlockHeight: lastValidBlockHeight ?? 2n ** 64n - 1n
// U64 MAX
};
} else {
const nonceAccountAddress = firstInstruction.accounts[0].address;
addresses.assertIsAddress(nonceAccountAddress);
const nonceAuthorityAddress = firstInstruction.accounts[2].address;
addresses.assertIsAddress(nonceAuthorityAddress);
return {
nonce: messageLifetimeToken,
nonceAccountAddress,
nonceAuthorityAddress
};
}
}
function decompileTransactionMessage(compiledTransactionMessage, config) {
const feePayer = compiledTransactionMessage.staticAccounts[0];
if (!feePayer) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_FEE_PAYER_MISSING);
}
const accountMetas = getAccountMetas(compiledTransactionMessage);
const accountLookupMetas = "addressTableLookups" in compiledTransactionMessage && compiledTransactionMessage.addressTableLookups !== void 0 && compiledTransactionMessage.addressTableLookups.length > 0 ? getAddressLookupMetas(
compiledTransactionMessage.addressTableLookups,
config?.addressesByLookupTableAddress ?? {}
) : [];
const transactionMetas = [...accountMetas, ...accountLookupMetas];
const instructions = compiledTransactionMessage.instructions.map(
(compiledInstruction) => convertInstruction(compiledInstruction, transactionMetas)
);
const firstInstruction = instructions[0];
const lifetimeConstraint = getLifetimeConstraint(
compiledTransactionMessage.lifetimeToken,
firstInstruction,
config?.lastValidBlockHeight
);
return functional.pipe(
createTransactionMessage({ version: compiledTransactionMessage.version }),
(m) => setTransactionMessageFeePayer(feePayer, m),
(m) => instructions.reduce((acc, instruction) => {
return appendTransactionMessageInstruction(instruction, acc);
}, m),
(m) => "blockhash" in lifetimeConstraint ? setTransactionMessageLifetimeUsingBlockhash(lifetimeConstraint, m) : setTransactionMessageLifetimeUsingDurableNonce(lifetimeConstraint, m)
);
}
exports.appendTransactionMessageInstruction = appendTransactionMessageInstruction;
exports.appendTransactionMessageInstructions = appendTransactionMessageInstructions;
exports.assertIsDurableNonceTransactionMessage = assertIsDurableNonceTransactionMessage;
exports.assertIsTransactionMessageWithBlockhashLifetime = assertIsTransactionMessageWithBlockhashLifetime;
exports.compileTransactionMessage = compileTransactionMessage;
exports.compressTransactionMessageUsingAddressLookupTables = compressTransactionMessageUsingAddressLookupTables;
exports.createTransactionMessage = createTransactionMessage;
exports.decompileTransactionMessage = decompileTransactionMessage;
exports.getCompiledTransactionMessageCodec = getCompiledTransactionMessageCodec;
exports.getCompiledTransactionMessageDecoder = getCompiledTransactionMessageDecoder;
exports.getCompiledTransactionMessageEncoder = getCompiledTransactionMessageEncoder;
exports.getTransactionVersionCodec = getTransactionVersionCodec;
exports.getTransactionVersionDecoder = getTransactionVersionDecoder;
exports.getTransactionVersionEncoder = getTransactionVersionEncoder;
exports.isAdvanceNonceAccountInstruction = isAdvanceNonceAccountInstruction;
exports.isDurableNonceTransaction = isDurableNonceTransaction;
exports.isTransactionMessageWithBlockhashLifetime = isTransactionMessageWithBlockhashLifetime;
exports.prependTransactionMessageInstruction = prependTransactionMessageInstruction;
exports.prependTransactionMessageInstructions = prependTransactionMessageInstructions;
exports.setTransactionMessageFeePayer = setTransactionMessageFeePayer;
exports.setTransactionMessageLifetimeUsingBlockhash = setTransactionMessageLifetimeUsingBlockhash;
exports.setTransactionMessageLifetimeUsingDurableNonce = setTransactionMessageLifetimeUsingDurableNonce;
//# sourceMappingURL=index.node.cjs.map
//# sourceMappingURL=index.node.cjs.map
;