@solana/transactions
Version:
Helpers for creating and serializing transactions
200 lines (196 loc) • 7.84 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 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)
};
}
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 {
lifetimeConstraint = {
nonce: transactionMessage.lifetimeConstraint.nonce,
nonceAccountAddress: transactionMessage.instructions[0].accounts[0].address
};
}
const transaction = {
lifetimeConstraint,
messageBytes,
signatures: Object.freeze(signatures)
};
return Object.freeze(transaction);
}
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;
}
function uint8ArraysEqual(arr1, arr2) {
return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
}
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 && uint8ArraysEqual(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);
assertTransactionIsFullySigned(out);
Object.freeze(out);
return out;
}
function assertTransactionIsFullySigned(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);
}
exports.assertTransactionIsFullySigned = assertTransactionIsFullySigned;
exports.compileTransaction = compileTransaction;
exports.getBase64EncodedWireTransaction = getBase64EncodedWireTransaction;
exports.getSignatureFromTransaction = getSignatureFromTransaction;
exports.getTransactionCodec = getTransactionCodec;
exports.getTransactionDecoder = getTransactionDecoder;
exports.getTransactionEncoder = getTransactionEncoder;
exports.partiallySignTransaction = partiallySignTransaction;
exports.signTransaction = signTransaction;
//# sourceMappingURL=index.browser.cjs.map
//# sourceMappingURL=index.browser.cjs.map
;