UNPKG

@solana/transaction-messages

Version:

Helpers for creating transaction messages

948 lines (936 loc) • 38.8 kB
'use strict'; 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