UNPKG

@aptos-labs/ts-sdk

Version:
411 lines 18.1 kB
/** * This file contains the underlying implementations for exposed submission API surface in * the {@link api/transaction}. By moving the methods out into a separate file, * other namespaces and processes can access these methods without depending on the entire * transaction namespace and without having a dependency cycle error. * @group Implementation */ import { Deserializer, MoveVector } from "../bcs/index.js"; import { postAptosFullNode } from "../client/index.js"; import { isKeylessSigner } from "../account/keylessSigner.js"; import { AccountAddress } from "../core/accountAddress.js"; import { buildTransaction, generateTransactionPayload, generateSignedTransactionForSimulation, generateSignedTransaction, } from "../transactions/transactionBuilder/transactionBuilder.js"; import { MimeType, AnyPublicKeyVariant, } from "../types/index.js"; import { SignedTransaction, TypeTagVector, generateSigningMessageForTransaction } from "../transactions/index.js"; import { TransactionPayloadEncryptedPayload } from "../transactions/instances/transactionPayload.js"; /** * Generates any transaction by passing in the required arguments * * @param args.sender The transaction sender's account address as a AccountAddressInput * @param args.data EntryFunctionData | ScriptData | MultiSigData * @param args.feePayerAddress optional. For a fee payer (aka sponsored) transaction * @param args.secondarySignerAddresses optional. For a multi-agent or fee payer (aka sponsored) transactions * @param args.options optional. GenerateTransactionOptions type * * @example * For a single signer entry function * move function name, move function type arguments, move function arguments * ` * data: { * function:"0x1::aptos_account::transfer", * typeArguments:[] * functionArguments :[receiverAddress,10] * } * ` * * @example * For a single signer script function * module bytecode, move function type arguments, move function arguments * ``` * data: { * bytecode:"0x001234567", * typeArguments:[], * functionArguments :[receiverAddress,10] * } * ``` * * @return An instance of a RawTransaction, plus optional secondary/fee payer addresses * ``` * { * rawTransaction: RawTransaction, * secondarySignerAddresses?: Array<AccountAddress>, * feePayerAddress?: AccountAddress * } * ``` * @group Implementation */ export async function generateTransaction(args) { const payload = await buildTransactionPayload(args); return buildRawTransaction(args, payload); } /** * Builds a transaction payload based on the provided configuration and input data. * This function is essential for preparing transaction data for execution on the Aptos blockchain. * * @param args - The arguments for building the transaction payload. * @param args.aptosConfig - Configuration settings for the Aptos network. * @param args.data - Input data required to generate the transaction payload, which may include bytecode, multisig address, * function name, function arguments, type arguments, and ABI. * @returns A promise that resolves to the generated transaction payload instance. * @group Implementation */ export async function buildTransactionPayload(args) { const { aptosConfig, data } = args; // Merge in aptosConfig for remote ABI on non-script payloads let generateTransactionPayloadData; let payload; if ("bytecode" in data) { // TODO: Add ABI checking later payload = await generateTransactionPayload(data); } else if ("multisigAddress" in data) { generateTransactionPayloadData = { aptosConfig, multisigAddress: data.multisigAddress, function: data.function, functionArguments: data.functionArguments, typeArguments: data.typeArguments, abi: data.abi, }; payload = await generateTransactionPayload(generateTransactionPayloadData); } else { generateTransactionPayloadData = { aptosConfig, function: data.function, functionArguments: data.functionArguments, typeArguments: data.typeArguments, abi: data.abi, }; payload = await generateTransactionPayload(generateTransactionPayloadData); } return payload; } /** * Builds a raw transaction based on the provided configuration and payload. * This function helps in creating a transaction that can be sent to the Aptos blockchain. * * @param args - The arguments for generating the transaction. * @param args.aptosConfig - The configuration settings for Aptos. * @param args.sender - The address of the sender of the transaction. * @param args.options - Additional options for the transaction. * @param payload - The payload of the transaction, which defines the action to be performed. * @group Implementation */ export async function buildRawTransaction(args, payload) { const { aptosConfig, sender, options } = args; let feePayerAddress; if (isFeePayerTransactionInput(args)) { feePayerAddress = AccountAddress.ZERO.toString(); } if (isMultiAgentTransactionInput(args)) { const { secondarySignerAddresses } = args; return buildTransaction({ aptosConfig, sender, payload, options, secondarySignerAddresses, feePayerAddress, }); } return buildTransaction({ aptosConfig, sender, payload, options, feePayerAddress, }); } /** * Determine if the transaction input includes a fee payer. * * @param data - The input data for generating a transaction. * @param data.withFeePayer - Indicates whether a fee payer is included in the transaction input. * @returns A boolean value indicating if the transaction input has a fee payer. * @group Implementation */ function isFeePayerTransactionInput(data) { return data.withFeePayer === true; } /** * Determines whether the provided transaction input data includes multiple agent signatures. * * @param data - The transaction input data to evaluate. * @param data.secondarySignerAddresses - An array of secondary signer addresses, indicating multiple agents. * @group Implementation */ function isMultiAgentTransactionInput(data) { return "secondarySignerAddresses" in data; } /** * Builds a signing message that can be signed by external signers. * * Note: Please prefer using `signTransaction` unless signing outside the SDK. * * @param args - The arguments for generating the signing message. * @param args.transaction - AnyRawTransaction, as generated by `generateTransaction()`. * * @returns The message to be signed. * @group Implementation */ export function getSigningMessage(args) { const { transaction } = args; return generateSigningMessageForTransaction(transaction); } /** * Sign a transaction that can later be submitted to the chain. * * @param args The arguments for signing the transaction. * @param args.signer The signer account to sign the transaction. * @param args.transaction An instance of a RawTransaction, plus optional secondary/fee payer addresses. * * @return The signer AccountAuthenticator. * @group Implementation */ export function signTransaction(args) { const { signer, transaction } = args; return signer.signTransactionWithAuthenticator(transaction); } export function signAsFeePayer(args) { const { signer, transaction } = args; // if transaction doesn't hold a "feePayerAddress" prop it means // this is not a fee payer transaction if (!transaction.feePayerAddress) { throw new Error(`Transaction ${transaction} is not a Fee Payer transaction`); } // Set the feePayerAddress to the signer account address transaction.feePayerAddress = signer.accountAddress; return signTransaction({ signer, transaction, }); } /** * Error thrown when {@link simulateTransaction} is called with an encrypted payload. * Simulation is not supported for encrypted transactions; build the same entry function without * `encrypted: true`, simulate that transaction, then build with encryption for submit. * @group Implementation */ export const ENCRYPTED_TRANSACTION_SIMULATION_NOT_SUPPORTED_MESSAGE = "Transaction simulation is not supported for encrypted payloads. Build a plaintext transaction with the same entry function and simulate it, then build with options.encrypted true for submission."; /** * Throws if the transaction cannot be simulated because its payload is encrypted. * @group Implementation */ export function assertSimulatableTransaction(transaction) { if (transaction.rawTransaction.payload instanceof TransactionPayloadEncryptedPayload) { throw new Error(ENCRYPTED_TRANSACTION_SIMULATION_NOT_SUPPORTED_MESSAGE); } } /** * Simulates a transaction before signing it to evaluate its potential outcome. * * @param args The arguments for simulating the transaction. * @param args.aptosConfig The configuration for the Aptos network. * @param args.transaction The raw transaction to simulate. Must not use an encrypted payload * (`options.encrypted` when building); otherwise this function throws before contacting the node. * @param args.signerPublicKey Optional. The signer public key. * @param args.secondarySignersPublicKeys Optional. For when the transaction involves multiple signers. * @param args.feePayerPublicKey Optional. For when the transaction is sponsored by a fee payer. * @param args.options Optional. A configuration object to customize the simulation process. * @param args.options.estimateGasUnitPrice Optional. Indicates whether to estimate the gas unit price. * @param args.options.estimateMaxGasAmount Optional. Indicates whether to estimate the maximum gas amount. * @param args.options.estimatePrioritizedGasUnitPrice Optional. Indicates whether to estimate the prioritized gas unit price. * * @throws Error if `transaction` has an encrypted payload (see {@link assertSimulatableTransaction}). * @group Implementation */ export async function simulateTransaction(args) { const { aptosConfig, transaction, signerPublicKey, secondarySignersPublicKeys, feePayerPublicKey, options } = args; assertSimulatableTransaction(transaction); const signedTransaction = await generateSignedTransactionForSimulation({ transaction, signerPublicKey, secondarySignersPublicKeys, feePayerPublicKey, options, }); const { data } = await postAptosFullNode({ aptosConfig, body: signedTransaction, path: "transactions/simulate", params: { estimate_gas_unit_price: args.options?.estimateGasUnitPrice ?? false, estimate_max_gas_amount: args.options?.estimateMaxGasAmount ?? false, estimate_prioritized_gas_unit_price: args.options?.estimatePrioritizedGasUnitPrice ?? false, }, originMethod: "simulateTransaction", contentType: MimeType.BCS_SIGNED_TRANSACTION, }); // If the server's gas estimation produced a value below the network minimum, // retry without estimation so the transaction's original max_gas_amount is used. if (args.options?.estimateMaxGasAmount && data.length > 0 && data[0].vm_status === "MAX_GAS_UNITS_BELOW_MIN_TRANSACTION_GAS_UNITS") { const { data: retryData } = await postAptosFullNode({ aptosConfig, body: signedTransaction, path: "transactions/simulate", params: { estimate_gas_unit_price: args.options?.estimateGasUnitPrice ?? false, estimate_max_gas_amount: false, estimate_prioritized_gas_unit_price: args.options?.estimatePrioritizedGasUnitPrice ?? false, }, originMethod: "simulateTransaction", contentType: MimeType.BCS_SIGNED_TRANSACTION, }); return retryData; } return data; } /** * Submit a transaction to the Aptos blockchain. * * @param args - The arguments for submitting the transaction. * @param args.aptosConfig - The configuration for connecting to the Aptos network. * @param args.transaction - The Aptos transaction data to be submitted. * @param args.senderAuthenticator - The account authenticator of the transaction sender. * @param args.secondarySignerAuthenticators - Optional. Authenticators for additional signers in a multi-signer transaction. * * @returns PendingTransactionResponse - The response containing the status of the submitted transaction. * @group Implementation */ export async function submitTransaction(args) { const { aptosConfig, transactionSubmitter } = args; const maybeTransactionSubmitter = transactionSubmitter === undefined ? aptosConfig.getTransactionSubmitter() : transactionSubmitter; if (maybeTransactionSubmitter) { return maybeTransactionSubmitter.submitTransaction(args); } const signedTransaction = generateSignedTransaction({ ...args }); try { const { data } = await postAptosFullNode({ aptosConfig, body: signedTransaction, path: "transactions", originMethod: "submitTransaction", contentType: MimeType.BCS_SIGNED_TRANSACTION, }); return data; } catch (e) { // Best-effort diagnostic: if this was a keyless submission, refresh the // JWK so the next attempt can succeed. Any failure here (deserialization, // unregistered variant, network error while fetching JWKs, etc.) must // NOT mask the original submission error `e`. try { const signedTxn = SignedTransaction.deserialize(new Deserializer(signedTransaction)); if (signedTxn.authenticator.isSingleSender() && signedTxn.authenticator.sender.isSingleKey()) { const { variant } = signedTxn.authenticator.sender.public_key; if (variant === AnyPublicKeyVariant.Keyless || variant === AnyPublicKeyVariant.FederatedKeyless) { // Dynamic import to avoid pulling poseidon-lite into the main bundle const { AbstractKeylessAccount } = await import("../account/AbstractKeylessAccount.js"); await AbstractKeylessAccount.fetchJWK({ aptosConfig, publicKey: signedTxn.authenticator.sender.public_key.publicKey, kid: signedTxn.authenticator.sender.signature.signature.getJwkKid(), }); } } } catch { // Swallow diagnostic errors so we always rethrow the original submission error. } throw e; } } export async function signAndSubmitTransaction(args) { const { aptosConfig, signer, feePayer, transaction, ...rest } = args; // If the signer contains a KeylessAccount, await proof fetching in case the proof // was fetched asynchronously. if (isKeylessSigner(signer)) { await signer.checkKeylessAccountValidity(aptosConfig); } if (isKeylessSigner(feePayer)) { await feePayer.checkKeylessAccountValidity(aptosConfig); } if (transaction.rawTransaction.payload instanceof TransactionPayloadEncryptedPayload && (isKeylessSigner(signer) || isKeylessSigner(feePayer))) { throw new Error("Encrypted transactions are not supported with keyless or federated keyless signers (signature mutability breaks payload binding)."); } const feePayerAuthenticator = args.feePayerAuthenticator || (feePayer && signAsFeePayer({ signer: feePayer, transaction })); const senderAuthenticator = signTransaction({ signer, transaction }); return submitTransaction({ aptosConfig, transaction, senderAuthenticator, feePayerAuthenticator, ...rest, }); } export async function signAndSubmitAsFeePayer(args) { const { aptosConfig, senderAuthenticator, feePayer, transaction, ...rest } = args; if (isKeylessSigner(feePayer)) { await feePayer.checkKeylessAccountValidity(aptosConfig); } const feePayerAuthenticator = signAsFeePayer({ signer: feePayer, transaction }); return submitTransaction({ aptosConfig, transaction, senderAuthenticator, feePayerAuthenticator, ...rest, }); } // Lazy-initialized to avoid circular dependency issues at module evaluation time. let _packagePublishAbi; function getPackagePublishAbi() { if (!_packagePublishAbi) { _packagePublishAbi = { typeParameters: [], parameters: [TypeTagVector.u8(), new TypeTagVector(TypeTagVector.u8())], }; } return _packagePublishAbi; } /** * Publishes a package transaction to the Aptos blockchain. * This function allows you to create and send a transaction that publishes a package with the specified metadata and bytecode. * * @param args - The arguments for the package transaction. * @param args.aptosConfig - The configuration settings for the Aptos client. * @param args.account - The address of the account sending the transaction. * @param args.metadataBytes - The metadata associated with the package, represented as hexadecimal input. * @param args.moduleBytecode - An array of module bytecode, each represented as hexadecimal input. * @param args.options - Optional parameters for generating the transaction. * @group Implementation */ export async function publicPackageTransaction(args) { const { aptosConfig, account, metadataBytes, moduleBytecode, options } = args; const totalByteCode = moduleBytecode.map((bytecode) => MoveVector.U8(bytecode)); return generateTransaction({ aptosConfig, sender: AccountAddress.from(account), data: { function: "0x1::code::publish_package_txn", functionArguments: [MoveVector.U8(metadataBytes), new MoveVector(totalByteCode)], abi: getPackagePublishAbi(), }, options, }); } //# sourceMappingURL=transactionSubmission.js.map