@solana/transactions
Version:
Helpers for creating and serializing transactions
306 lines (301 loc) • 13.8 kB
JavaScript
;
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 errors = require('@solana/errors');
var transactionMessages = require('@solana/transaction-messages');
var rpcTypes = require('@solana/rpc-types');
var codecsStrings = require('@solana/codecs-strings');
var keys = require('@solana/keys');
// src/codecs/transaction-codec.ts
function getSignaturesToEncode(signaturesMap) {
const signatures = Object.values(signaturesMap);
if (signatures.length === 0) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__CANNOT_ENCODE_WITH_EMPTY_SIGNATURES);
}
return signatures.map((signature) => {
if (!signature) {
return new Uint8Array(64).fill(0);
}
return signature;
});
}
function getSignaturesEncoder() {
return codecsCore.transformEncoder(
codecsDataStructures.getArrayEncoder(codecsCore.fixEncoderSize(codecsDataStructures.getBytesEncoder(), 64), { size: codecsNumbers.getShortU16Encoder() }),
getSignaturesToEncode
);
}
// src/codecs/transaction-codec.ts
function getTransactionEncoder() {
return codecsDataStructures.getStructEncoder([
["signatures", getSignaturesEncoder()],
["messageBytes", codecsDataStructures.getBytesEncoder()]
]);
}
function getTransactionDecoder() {
return codecsCore.transformDecoder(
codecsDataStructures.getStructDecoder([
["signatures", codecsDataStructures.getArrayDecoder(codecsCore.fixDecoderSize(codecsDataStructures.getBytesDecoder(), 64), { size: codecsNumbers.getShortU16Decoder() })],
["messageBytes", codecsDataStructures.getBytesDecoder()]
]),
decodePartiallyDecodedTransaction
);
}
function getTransactionCodec() {
return codecsCore.combineCodec(getTransactionEncoder(), getTransactionDecoder());
}
function decodePartiallyDecodedTransaction(transaction) {
const { messageBytes, signatures } = transaction;
const signerAddressesDecoder = codecsDataStructures.getTupleDecoder([
// read transaction version
transactionMessages.getTransactionVersionDecoder(),
// read first byte of header, `numSignerAccounts`
// padRight to skip the next 2 bytes, `numReadOnlySignedAccounts` and `numReadOnlyUnsignedAccounts` which we don't need
codecsCore.padRightDecoder(codecsNumbers.getU8Decoder(), 2),
// read static addresses
codecsDataStructures.getArrayDecoder(addresses.getAddressDecoder(), { size: codecsNumbers.getShortU16Decoder() })
]);
const [_txVersion, numRequiredSignatures, staticAddresses] = signerAddressesDecoder.decode(messageBytes);
const signerAddresses = staticAddresses.slice(0, numRequiredSignatures);
if (signerAddresses.length !== signatures.length) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__MESSAGE_SIGNATURES_MISMATCH, {
numRequiredSignatures,
signaturesLength: signatures.length,
signerAddresses
});
}
const signaturesMap = {};
signerAddresses.forEach((address, index) => {
const signatureForAddress = signatures[index];
if (signatureForAddress.every((b) => b === 0)) {
signaturesMap[address] = null;
} else {
signaturesMap[address] = signatureForAddress;
}
});
return {
messageBytes,
signatures: Object.freeze(signaturesMap)
};
}
var SYSTEM_PROGRAM_ADDRESS = "11111111111111111111111111111111";
function compiledInstructionIsAdvanceNonceInstruction(instruction, staticAddresses) {
return staticAddresses[instruction.programAddressIndex] === SYSTEM_PROGRAM_ADDRESS && // Test for `AdvanceNonceAccount` instruction data
instruction.data != null && isAdvanceNonceAccountInstructionData(instruction.data) && // Test for exactly 3 accounts
instruction.accountIndices?.length === 3;
}
function isAdvanceNonceAccountInstructionData(data) {
return data.byteLength === 4 && data[0] === 4 && data[1] === 0 && data[2] === 0 && data[3] === 0;
}
async function getTransactionLifetimeConstraintFromCompiledTransactionMessage(compiledTransactionMessage) {
const firstInstruction = compiledTransactionMessage.instructions[0];
const { staticAccounts } = compiledTransactionMessage;
if (firstInstruction && compiledInstructionIsAdvanceNonceInstruction(firstInstruction, staticAccounts)) {
const nonceAccountAddress = staticAccounts[firstInstruction.accountIndices[0]];
if (!nonceAccountAddress) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__NONCE_ACCOUNT_CANNOT_BE_IN_LOOKUP_TABLE, {
nonce: compiledTransactionMessage.lifetimeToken
});
}
return {
nonce: compiledTransactionMessage.lifetimeToken,
nonceAccountAddress
};
} else {
return {
blockhash: compiledTransactionMessage.lifetimeToken,
// This is not known from the compiled message, so we set it to the maximum possible value
lastValidBlockHeight: 0xffffffffffffffffn
};
}
}
function isTransactionWithBlockhashLifetime(transaction) {
return "lifetimeConstraint" in transaction && "blockhash" in transaction.lifetimeConstraint && typeof transaction.lifetimeConstraint.blockhash === "string" && typeof transaction.lifetimeConstraint.lastValidBlockHeight === "bigint" && rpcTypes.isBlockhash(transaction.lifetimeConstraint.blockhash);
}
function assertIsTransactionWithBlockhashLifetime(transaction) {
if (!isTransactionWithBlockhashLifetime(transaction)) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__EXPECTED_BLOCKHASH_LIFETIME);
}
}
function isTransactionWithDurableNonceLifetime(transaction) {
return "lifetimeConstraint" in transaction && "nonce" in transaction.lifetimeConstraint && typeof transaction.lifetimeConstraint.nonce === "string" && typeof transaction.lifetimeConstraint.nonceAccountAddress === "string" && addresses.isAddress(transaction.lifetimeConstraint.nonceAccountAddress);
}
function assertIsTransactionWithDurableNonceLifetime(transaction) {
if (!isTransactionWithDurableNonceLifetime(transaction)) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__EXPECTED_NONCE_LIFETIME);
}
}
function compileTransaction(transactionMessage) {
const compiledMessage = transactionMessages.compileTransactionMessage(transactionMessage);
const messageBytes = transactionMessages.getCompiledTransactionMessageEncoder().encode(compiledMessage);
const transactionSigners = compiledMessage.staticAccounts.slice(0, compiledMessage.header.numSignerAccounts);
const signatures = {};
for (const signerAddress of transactionSigners) {
signatures[signerAddress] = null;
}
let lifetimeConstraint;
if (transactionMessages.isTransactionMessageWithBlockhashLifetime(transactionMessage)) {
lifetimeConstraint = {
blockhash: transactionMessage.lifetimeConstraint.blockhash,
lastValidBlockHeight: transactionMessage.lifetimeConstraint.lastValidBlockHeight
};
} else if (transactionMessages.isTransactionMessageWithDurableNonceLifetime(transactionMessage)) {
lifetimeConstraint = {
nonce: transactionMessage.lifetimeConstraint.nonce,
nonceAccountAddress: transactionMessage.instructions[0].accounts[0].address
};
}
return Object.freeze({
...lifetimeConstraint ? { lifetimeConstraint } : void 0,
messageBytes,
signatures: Object.freeze(signatures)
});
}
var base58Decoder;
function getSignatureFromTransaction(transaction) {
if (!base58Decoder) base58Decoder = codecsStrings.getBase58Decoder();
const signatureBytes = Object.values(transaction.signatures)[0];
if (!signatureBytes) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING);
}
const transactionSignature = base58Decoder.decode(signatureBytes);
return transactionSignature;
}
async function partiallySignTransaction(keyPairs, transaction) {
let newSignatures;
let unexpectedSigners;
await Promise.all(
keyPairs.map(async (keyPair) => {
const address = await addresses.getAddressFromPublicKey(keyPair.publicKey);
const existingSignature = transaction.signatures[address];
if (existingSignature === void 0) {
unexpectedSigners ||= /* @__PURE__ */ new Set();
unexpectedSigners.add(address);
return;
}
if (unexpectedSigners) {
return;
}
const newSignature = await keys.signBytes(keyPair.privateKey, transaction.messageBytes);
if (existingSignature !== null && codecsCore.bytesEqual(newSignature, existingSignature)) {
return;
}
newSignatures ||= {};
newSignatures[address] = newSignature;
})
);
if (unexpectedSigners && unexpectedSigners.size > 0) {
const expectedSigners = Object.keys(transaction.signatures);
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__ADDRESSES_CANNOT_SIGN_TRANSACTION, {
expectedAddresses: expectedSigners,
unexpectedAddresses: [...unexpectedSigners]
});
}
if (!newSignatures) {
return transaction;
}
return Object.freeze({
...transaction,
signatures: Object.freeze({
...transaction.signatures,
...newSignatures
})
});
}
async function signTransaction(keyPairs, transaction) {
const out = await partiallySignTransaction(keyPairs, transaction);
assertIsFullySignedTransaction(out);
Object.freeze(out);
return out;
}
function isFullySignedTransaction(transaction) {
return Object.entries(transaction.signatures).every(([_, signatureBytes]) => !!signatureBytes);
}
function assertIsFullySignedTransaction(transaction) {
const missingSigs = [];
Object.entries(transaction.signatures).forEach(([address, signatureBytes]) => {
if (!signatureBytes) {
missingSigs.push(address);
}
});
if (missingSigs.length > 0) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, {
addresses: missingSigs
});
}
}
function getBase64EncodedWireTransaction(transaction) {
const wireTransactionBytes = getTransactionEncoder().encode(transaction);
return codecsStrings.getBase64Decoder().decode(wireTransactionBytes);
}
var TRANSACTION_PACKET_SIZE = 1280;
var TRANSACTION_PACKET_HEADER = 40 + 8;
var TRANSACTION_SIZE_LIMIT = TRANSACTION_PACKET_SIZE - TRANSACTION_PACKET_HEADER;
function getTransactionSize(transaction) {
return getTransactionEncoder().getSizeFromValue(transaction);
}
function isTransactionWithinSizeLimit(transaction) {
return getTransactionSize(transaction) <= TRANSACTION_SIZE_LIMIT;
}
function assertIsTransactionWithinSizeLimit(transaction) {
const transactionSize = getTransactionSize(transaction);
if (transactionSize > TRANSACTION_SIZE_LIMIT) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__EXCEEDS_SIZE_LIMIT, {
transactionSize,
transactionSizeLimit: TRANSACTION_SIZE_LIMIT
});
}
}
// src/sendable-transaction.ts
function isSendableTransaction(transaction) {
return isFullySignedTransaction(transaction) && isTransactionWithinSizeLimit(transaction);
}
function assertIsSendableTransaction(transaction) {
assertIsFullySignedTransaction(transaction);
assertIsTransactionWithinSizeLimit(transaction);
}
function getTransactionMessageSize(transactionMessage) {
return getTransactionSize(compileTransaction(transactionMessage));
}
function isTransactionMessageWithinSizeLimit(transactionMessage) {
return getTransactionMessageSize(transactionMessage) <= TRANSACTION_SIZE_LIMIT;
}
function assertIsTransactionMessageWithinSizeLimit(transactionMessage) {
const transactionSize = getTransactionMessageSize(transactionMessage);
if (transactionSize > TRANSACTION_SIZE_LIMIT) {
throw new errors.SolanaError(errors.SOLANA_ERROR__TRANSACTION__EXCEEDS_SIZE_LIMIT, {
transactionSize,
transactionSizeLimit: TRANSACTION_SIZE_LIMIT
});
}
}
exports.TRANSACTION_PACKET_HEADER = TRANSACTION_PACKET_HEADER;
exports.TRANSACTION_PACKET_SIZE = TRANSACTION_PACKET_SIZE;
exports.TRANSACTION_SIZE_LIMIT = TRANSACTION_SIZE_LIMIT;
exports.assertIsFullySignedTransaction = assertIsFullySignedTransaction;
exports.assertIsSendableTransaction = assertIsSendableTransaction;
exports.assertIsTransactionMessageWithinSizeLimit = assertIsTransactionMessageWithinSizeLimit;
exports.assertIsTransactionWithBlockhashLifetime = assertIsTransactionWithBlockhashLifetime;
exports.assertIsTransactionWithDurableNonceLifetime = assertIsTransactionWithDurableNonceLifetime;
exports.assertIsTransactionWithinSizeLimit = assertIsTransactionWithinSizeLimit;
exports.compileTransaction = compileTransaction;
exports.getBase64EncodedWireTransaction = getBase64EncodedWireTransaction;
exports.getSignatureFromTransaction = getSignatureFromTransaction;
exports.getTransactionCodec = getTransactionCodec;
exports.getTransactionDecoder = getTransactionDecoder;
exports.getTransactionEncoder = getTransactionEncoder;
exports.getTransactionLifetimeConstraintFromCompiledTransactionMessage = getTransactionLifetimeConstraintFromCompiledTransactionMessage;
exports.getTransactionMessageSize = getTransactionMessageSize;
exports.getTransactionSize = getTransactionSize;
exports.isFullySignedTransaction = isFullySignedTransaction;
exports.isSendableTransaction = isSendableTransaction;
exports.isTransactionMessageWithinSizeLimit = isTransactionMessageWithinSizeLimit;
exports.isTransactionWithBlockhashLifetime = isTransactionWithBlockhashLifetime;
exports.isTransactionWithDurableNonceLifetime = isTransactionWithDurableNonceLifetime;
exports.isTransactionWithinSizeLimit = isTransactionWithinSizeLimit;
exports.partiallySignTransaction = partiallySignTransaction;
exports.signTransaction = signTransaction;
//# sourceMappingURL=index.node.cjs.map
//# sourceMappingURL=index.node.cjs.map