UNPKG

@solana/transactions

Version:

Helpers for creating and serializing transactions

190 lines (187 loc) • 7.69 kB
import { getAddressDecoder, getAddressFromPublicKey } from '@solana/addresses'; import { transformDecoder, fixDecoderSize, combineCodec, padRightDecoder, transformEncoder, fixEncoderSize } from '@solana/codecs-core'; import { getStructEncoder, getBytesEncoder, getStructDecoder, getArrayDecoder, getBytesDecoder, getTupleDecoder, getArrayEncoder } from '@solana/codecs-data-structures'; import { getShortU16Decoder, getU8Decoder, getShortU16Encoder } from '@solana/codecs-numbers'; import { SolanaError, SOLANA_ERROR__TRANSACTION__MESSAGE_SIGNATURES_MISMATCH, SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING, SOLANA_ERROR__TRANSACTION__ADDRESSES_CANNOT_SIGN_TRANSACTION, SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, SOLANA_ERROR__TRANSACTION__CANNOT_ENCODE_WITH_EMPTY_SIGNATURES } from '@solana/errors'; import { getTransactionVersionDecoder, compileTransactionMessage, getCompiledTransactionMessageEncoder, isTransactionMessageWithBlockhashLifetime } from '@solana/transaction-messages'; import { getBase58Decoder, getBase64Decoder } from '@solana/codecs-strings'; import { signBytes } from '@solana/keys'; // src/codecs/transaction-codec.ts function getSignaturesToEncode(signaturesMap) { const signatures = Object.values(signaturesMap); if (signatures.length === 0) { throw new SolanaError(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 transformEncoder( getArrayEncoder(fixEncoderSize(getBytesEncoder(), 64), { size: getShortU16Encoder() }), getSignaturesToEncode ); } // src/codecs/transaction-codec.ts function getTransactionEncoder() { return getStructEncoder([ ["signatures", getSignaturesEncoder()], ["messageBytes", getBytesEncoder()] ]); } function getTransactionDecoder() { return transformDecoder( getStructDecoder([ ["signatures", getArrayDecoder(fixDecoderSize(getBytesDecoder(), 64), { size: getShortU16Decoder() })], ["messageBytes", getBytesDecoder()] ]), decodePartiallyDecodedTransaction ); } function getTransactionCodec() { return combineCodec(getTransactionEncoder(), getTransactionDecoder()); } function decodePartiallyDecodedTransaction(transaction) { const { messageBytes, signatures } = transaction; const signerAddressesDecoder = getTupleDecoder([ // read transaction version getTransactionVersionDecoder(), // read first byte of header, `numSignerAccounts` // padRight to skip the next 2 bytes, `numReadOnlySignedAccounts` and `numReadOnlyUnsignedAccounts` which we don't need padRightDecoder(getU8Decoder(), 2), // read static addresses getArrayDecoder(getAddressDecoder(), { size: getShortU16Decoder() }) ]); const [_txVersion, numRequiredSignatures, staticAddresses] = signerAddressesDecoder.decode(messageBytes); const signerAddresses = staticAddresses.slice(0, numRequiredSignatures); if (signerAddresses.length !== signatures.length) { throw new SolanaError(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 = compileTransactionMessage(transactionMessage); const messageBytes = 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 (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 = getBase58Decoder(); const signatureBytes = Object.values(transaction.signatures)[0]; if (!signatureBytes) { throw new SolanaError(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 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 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 SolanaError(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 SolanaError(SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, { addresses: missingSigs }); } } function getBase64EncodedWireTransaction(transaction) { const wireTransactionBytes = getTransactionEncoder().encode(transaction); return getBase64Decoder().decode(wireTransactionBytes); } export { assertTransactionIsFullySigned, compileTransaction, getBase64EncodedWireTransaction, getSignatureFromTransaction, getTransactionCodec, getTransactionDecoder, getTransactionEncoder, partiallySignTransaction, signTransaction }; //# sourceMappingURL=index.native.mjs.map //# sourceMappingURL=index.native.mjs.map