UNPKG

@solana/transactions

Version:

Helpers for creating and serializing transactions

200 lines (196 loc) • 7.84 kB
'use strict'; 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