@aptos-labs/ts-sdk
Version:
Aptos TypeScript SDK
377 lines (349 loc) • 12.9 kB
text/typescript
/**
* 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.
*/
import { AptosConfig } from "../api/aptosConfig";
import { MoveVector, U8 } from "../bcs";
import { postAptosFullNode } from "../client";
import { Account } from "../account";
import { AccountAddress, AccountAddressInput } from "../core/accountAddress";
import { PrivateKey } from "../core/crypto";
import { AccountAuthenticator } from "../transactions/authenticator/account";
import { RotationProofChallenge } from "../transactions/instances/rotationProofChallenge";
import {
buildTransaction,
generateTransactionPayload,
generateSignedTransactionForSimulation,
generateSignedTransaction,
} from "../transactions/transactionBuilder/transactionBuilder";
import {
InputGenerateTransactionData,
AnyRawTransaction,
InputSimulateTransactionData,
InputGenerateTransactionOptions,
InputGenerateTransactionPayloadDataWithRemoteABI,
InputSubmitTransactionData,
InputGenerateMultiAgentRawTransactionData,
InputGenerateSingleSignerRawTransactionData,
AnyTransactionPayloadInstance,
EntryFunctionABI,
} from "../transactions/types";
import { getInfo } from "./account";
import { UserTransactionResponse, PendingTransactionResponse, MimeType, HexInput, TransactionResponse } from "../types";
import { TypeTagU8, TypeTagVector, generateSigningMessageForTransaction } from "../transactions";
import { SimpleTransaction } from "../transactions/instances/simpleTransaction";
import { MultiAgentTransaction } from "../transactions/instances/multiAgentTransaction";
/**
* We are defining function signatures, each with its specific input and output.
* These are the possible function signature for `generateTransaction` function.
* When we call `generateTransaction` function with the relevant type properties,
* Typescript can infer the return type based on the appropriate function overload.
*/
export async function generateTransaction(
args: { aptosConfig: AptosConfig } & InputGenerateSingleSignerRawTransactionData,
): Promise<SimpleTransaction>;
export async function generateTransaction(
args: { aptosConfig: AptosConfig } & InputGenerateMultiAgentRawTransactionData,
): Promise<MultiAgentTransaction>;
/**
* 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
* }
* ```
*/
export async function generateTransaction(
args: { aptosConfig: AptosConfig } & InputGenerateTransactionData,
): Promise<AnyRawTransaction> {
const payload = await buildTransactionPayload(args);
return buildRawTransaction(args, payload);
}
export async function buildTransactionPayload(
args: { aptosConfig: AptosConfig } & InputGenerateTransactionData,
): Promise<AnyTransactionPayloadInstance> {
const { aptosConfig, data } = args;
// Merge in aptosConfig for remote ABI on non-script payloads
let generateTransactionPayloadData: InputGenerateTransactionPayloadDataWithRemoteABI;
let payload: AnyTransactionPayloadInstance;
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;
}
export async function buildRawTransaction(
args: { aptosConfig: AptosConfig } & InputGenerateTransactionData,
payload: AnyTransactionPayloadInstance,
): Promise<AnyRawTransaction> {
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,
});
}
function isFeePayerTransactionInput(data: InputGenerateTransactionData): boolean {
return data.withFeePayer === true;
}
function isMultiAgentTransactionInput(
data: InputGenerateTransactionData,
): data is InputGenerateMultiAgentRawTransactionData {
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.transaction AnyRawTransaction, as generated by `generateTransaction()`
*
* @return The message to be signed
*/
export function getSigningMessage(args: { transaction: AnyRawTransaction }): Uint8Array {
const { transaction } = args;
return generateSigningMessageForTransaction(transaction);
}
/**
* Sign a transaction that can later be submitted to chain
*
* @param args.signer The signer account to sign the transaction
* @param args.transaction An instance of a RawTransaction, plus optional secondary/fee payer addresses
* ```
* {
* rawTransaction: RawTransaction,
* secondarySignerAddresses? : Array<AccountAddress>,
* feePayerAddress?: AccountAddress
* }
* ```
*
* @return The signer AccountAuthenticator
*/
export function signTransaction(args: { signer: Account; transaction: AnyRawTransaction }): AccountAuthenticator {
const { signer, transaction } = args;
return signer.signTransactionWithAuthenticator(transaction);
}
/**
* Simulates a transaction before singing it.
*
* @param args.signerPublicKey The signer public key
* @param args.transaction The raw transaction to simulate
* @param args.secondarySignersPublicKeys optional. For when the transaction is a multi signers transaction
* @param args.feePayerPublicKey optional. For when the transaction is a fee payer (aka sponsored) transaction
* @param args.options optional. A config to simulate the transaction with
*/
export async function simulateTransaction(
args: { aptosConfig: AptosConfig } & InputSimulateTransactionData,
): Promise<Array<UserTransactionResponse>> {
const { aptosConfig, transaction, signerPublicKey, secondarySignersPublicKeys, feePayerPublicKey, options } = args;
const signedTransaction = generateSignedTransactionForSimulation({
transaction,
signerPublicKey,
secondarySignersPublicKeys,
feePayerPublicKey,
options,
});
const { data } = await postAptosFullNode<Uint8Array, Array<UserTransactionResponse>>({
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,
});
return data;
}
/**
* Submit transaction to chain
*
* @param args.transaction A aptos transaction type
* @param args.senderAuthenticator The account authenticator of the transaction sender
* @param args.secondarySignerAuthenticators optional. For when the transaction is a multi signers transaction
*
* @return PendingTransactionResponse
*/
export async function submitTransaction(
args: {
aptosConfig: AptosConfig;
} & InputSubmitTransactionData,
): Promise<PendingTransactionResponse> {
const { aptosConfig } = args;
const signedTransaction = generateSignedTransaction({ ...args });
const { data } = await postAptosFullNode<Uint8Array, PendingTransactionResponse>({
aptosConfig,
body: signedTransaction,
path: "transactions",
originMethod: "submitTransaction",
contentType: MimeType.BCS_SIGNED_TRANSACTION,
});
return data;
}
export async function signAndSubmitTransaction(args: {
aptosConfig: AptosConfig;
signer: Account;
transaction: AnyRawTransaction;
}): Promise<PendingTransactionResponse> {
const { aptosConfig, signer, transaction } = args;
const authenticator = signTransaction({ signer, transaction });
return submitTransaction({
aptosConfig,
transaction,
senderAuthenticator: authenticator,
});
}
const packagePublishAbi: EntryFunctionABI = {
typeParameters: [],
parameters: [TypeTagVector.u8(), new TypeTagVector(TypeTagVector.u8())],
};
export async function publicPackageTransaction(args: {
aptosConfig: AptosConfig;
account: AccountAddressInput;
metadataBytes: HexInput;
moduleBytecode: Array<HexInput>;
options?: InputGenerateTransactionOptions;
}): Promise<SimpleTransaction> {
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: packagePublishAbi,
},
options,
});
}
const rotateAuthKeyAbi: EntryFunctionABI = {
typeParameters: [],
parameters: [
new TypeTagU8(),
TypeTagVector.u8(),
new TypeTagU8(),
TypeTagVector.u8(),
TypeTagVector.u8(),
TypeTagVector.u8(),
],
};
/**
* TODO: Need to refactor and move this function out of transactionSubmission
*/
export async function rotateAuthKey(args: {
aptosConfig: AptosConfig;
fromAccount: Account;
toNewPrivateKey: PrivateKey;
}): Promise<TransactionResponse> {
const { aptosConfig, fromAccount, toNewPrivateKey } = args;
const accountInfo = await getInfo({
aptosConfig,
accountAddress: fromAccount.accountAddress,
});
const newAccount = Account.fromPrivateKey({ privateKey: toNewPrivateKey, legacy: true });
const challenge = new RotationProofChallenge({
sequenceNumber: BigInt(accountInfo.sequence_number),
originator: fromAccount.accountAddress,
currentAuthKey: AccountAddress.from(accountInfo.authentication_key),
newPublicKey: newAccount.publicKey,
});
// Sign the challenge
const challengeHex = challenge.bcsToBytes();
const proofSignedByCurrentPrivateKey = fromAccount.sign(challengeHex);
const proofSignedByNewPrivateKey = newAccount.sign(challengeHex);
// Generate transaction
const rawTxn = await generateTransaction({
aptosConfig,
sender: fromAccount.accountAddress,
data: {
function: "0x1::account::rotate_authentication_key",
functionArguments: [
new U8(fromAccount.signingScheme), // from scheme
MoveVector.U8(fromAccount.publicKey.toUint8Array()),
new U8(newAccount.signingScheme), // to scheme
MoveVector.U8(newAccount.publicKey.toUint8Array()),
MoveVector.U8(proofSignedByCurrentPrivateKey.toUint8Array()),
MoveVector.U8(proofSignedByNewPrivateKey.toUint8Array()),
],
abi: rotateAuthKeyAbi,
},
});
return signAndSubmitTransaction({
aptosConfig,
signer: fromAccount,
transaction: rawTxn,
});
}