@aptos-labs/ts-sdk
Version:
Aptos TypeScript SDK
411 lines • 18.1 kB
JavaScript
/**
* 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