Skip to content

Commit

Permalink
Add the NoAccountAuthenticator variant
Browse files Browse the repository at this point in the history
Simulation API Update: Allows users to simulate transactions without providing public keys by updating simulateTransaction to accept PublicKey | null instead of PublicKey. If null is provided, NoAccountAuthenticator is used as an authenticator.

Multisig V2 Example Update: The multisig v2 example (multisig_v2.ts) is updated to reflect the change in the multisig transaction simulation behavior. To pre-check the multisig payload before creation, an entry function payload simulation with the multisig account as the sender and 0x0 as the fee payer is used.
  • Loading branch information
junkil-park committed Nov 12, 2024
1 parent 7c01dd3 commit 07ca3c5
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 58 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ examples/typescript/facoin/facoin.json

# Vim swap files
*.swp
/.fleet
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
All notable changes to the Aptos TypeScript SDK will be captured in this file. This changelog is written by hand for now. It adheres to the format set out by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

# Unreleased
- Allow optional provision of public keys in transaction simulation
- Update the multisig v2 example to demonstrate a new way to pre-check a multisig payload before it is created on-chain

# 1.32.1 (2024-11-11)
- Add support for Firebase issuers in the `updateFederatedKeylessJwkSetTransaction` function
Expand Down
24 changes: 15 additions & 9 deletions examples/typescript-esm/multisig_v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,19 +141,25 @@ const createMultiSigTransferTransaction = async () => {
aptosConfig: config,
});

// Simulate the transfer transaction to make sure it passes
const transactionToSimulate = await generateRawTransaction({
aptosConfig: config,
sender: owner2.accountAddress,
payload: transactionPayload,
// Generate a raw transaction with the multisig address as the sender,
// the provided entry function payload, and 0x0 as the fee payer address.
const transactionToSimulate = await aptos.transaction.build.simple({
sender: transactionPayload.multiSig.multisig_address,
data: {
function: "0x1::aptos_account::transfer",
functionArguments: [recipient.accountAddress, 1_000_000],
},
withFeePayer: true,
});

const simulateMultisigTx = await aptos.transaction.simulate.simple({
signerPublicKey: owner2.publicKey,
transaction: new SimpleTransaction(transactionToSimulate),
// Simulate the transaction, skipping the public/auth key check for both the sender and the fee payer.
const [simulateMultisigTx] = await aptos.transaction.simulate.simple({
transaction: transactionToSimulate,
});

console.log("simulateMultisigTx", simulateMultisigTx);
if (!simulateMultisigTx.success) {
throw new Error("Multisig payload simulation failed.");
}

// Build create_transaction transaction
const createMultisigTx = await aptos.transaction.build.simple({
Expand Down
6 changes: 0 additions & 6 deletions src/api/transactionSubmission/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,6 @@ export function ValidateFeePayerDataOnSimulation(target: unknown, propertyKey: s
const originalMethod = descriptor.value;
/* eslint-disable-next-line func-names, no-param-reassign */
descriptor.value = async function (...args: any[]) {
const [methodArgs] = args;

if (methodArgs.transaction.feePayerAddress && !methodArgs.feePayerPublicKey) {
throw new Error("You are simulating a Fee Payer transaction but missing the feePayerPublicKey");
}

return originalMethod.apply(this, args);
};

Expand Down
17 changes: 9 additions & 8 deletions src/api/transactionSubmission/simulate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class Simulate {
* This function helps you understand the outcome of a transaction before executing it on the blockchain.
*
* @param args - The parameters for simulating the transaction.
* @param args.signerPublicKey - The public key of the signer for the transaction.
* @param args.signerPublicKey - The public key of the signer for the transaction (optional).
* @param args.transaction - The raw transaction data to simulate.
* @param args.feePayerPublicKey - The public key of the fee payer (optional).
* @param args.options - Additional options for simulating the transaction (optional).
Expand Down Expand Up @@ -100,7 +100,7 @@ export class Simulate {
*/
@ValidateFeePayerDataOnSimulation
async simple(args: {
signerPublicKey: PublicKey;
signerPublicKey?: PublicKey;
transaction: AnyRawTransaction;
feePayerPublicKey?: PublicKey;
options?: InputSimulateTransactionOptions;
Expand All @@ -113,11 +113,12 @@ export class Simulate {
* This function helps in understanding the outcome of a transaction involving multiple signers before it is executed.
*
* @param args - The parameters for simulating the transaction.
* @param args.signerPublicKey - The public key of the primary signer.
* @param args.signerPublicKey - The public key of the primary signer (optional).
* @param args.transaction - The raw transaction to be simulated.
* @param args.secondarySignersPublicKeys - An array of public keys for secondary signers.
* @param args.feePayerPublicKey - (Optional) The public key of the fee payer.
* @param args.options - (Optional) Options for simulating the transaction.
* @param args.secondarySignersPublicKeys - An array of public keys for secondary signers (optional).
* Each element of the array can be optional, allowing the corresponding key check to be skipped.
* @param args.feePayerPublicKey - The public key of the fee payer (optional).
* @param args.options - Options for simulating the transaction (optional).
*
* @example
* ```typescript
Expand Down Expand Up @@ -176,9 +177,9 @@ export class Simulate {
*/
@ValidateFeePayerDataOnSimulation
async multiAgent(args: {
signerPublicKey: PublicKey;
signerPublicKey?: PublicKey;
transaction: AnyRawTransaction;
secondarySignersPublicKeys: Array<PublicKey>;
secondarySignersPublicKeys?: Array<PublicKey | undefined>;
feePayerPublicKey?: PublicKey;
options?: InputSimulateTransactionOptions;
}): Promise<Array<UserTransactionResponse>> {
Expand Down
2 changes: 1 addition & 1 deletion src/internal/transactionSubmission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export function signAsFeePayer(args: { signer: Account; transaction: AnyRawTrans
* @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.
* @param args.signerPublicKey The signer public key.
* @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.
Expand Down
19 changes: 19 additions & 0 deletions src/transactions/authenticator/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export abstract class AccountAuthenticator extends Serializable {
return AccountAuthenticatorSingleKey.load(deserializer);
case AccountAuthenticatorVariant.MultiKey:
return AccountAuthenticatorMultiKey.load(deserializer);
case AccountAuthenticatorVariant.NoAccountAuthenticator:
return AccountAuthenticatorNoAccountAuthenticator.load(deserializer);
default:
throw new Error(`Unknown variant index for AccountAuthenticator: ${index}`);
}
Expand Down Expand Up @@ -218,3 +220,20 @@ export class AccountAuthenticatorMultiKey extends AccountAuthenticator {
return new AccountAuthenticatorMultiKey(public_keys, signatures);
}
}

/**
* AccountAuthenticatorNoAccountAuthenticator for no account authenticator
* It represents the absence of a public key for transaction simulation.
* It allows skipping the public/auth key check during the simulation.
*/
export class AccountAuthenticatorNoAccountAuthenticator extends AccountAuthenticator {
// eslint-disable-next-line class-methods-use-this
serialize(serializer: Serializer): void {
serializer.serializeU32AsUleb128(AccountAuthenticatorVariant.NoAccountAuthenticator);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
static load(deserializer: Deserializer): AccountAuthenticatorNoAccountAuthenticator {
return new AccountAuthenticatorNoAccountAuthenticator();
}
}
42 changes: 25 additions & 17 deletions src/transactions/transactionBuilder/transactionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
AccountAuthenticator,
AccountAuthenticatorEd25519,
AccountAuthenticatorMultiKey,
AccountAuthenticatorNoAccountAuthenticator,
AccountAuthenticatorSingleKey,
} from "../authenticator/account";
import {
Expand Down Expand Up @@ -479,15 +480,16 @@ export function generateSignedTransactionForSimulation(args: InputSimulateTransa
transaction.feePayerAddress,
);
let secondaryAccountAuthenticators: Array<AccountAuthenticator> = [];
if (secondarySignersPublicKeys) {
secondaryAccountAuthenticators = secondarySignersPublicKeys.map((publicKey) =>
getAuthenticatorForSimulation(publicKey),
);
}
if (!feePayerPublicKey) {
throw new Error(
"Must provide a feePayerPublicKey argument to generate a signed fee payer transaction for simulation",
);
if (transaction.secondarySignerAddresses) {
if (secondarySignersPublicKeys) {
secondaryAccountAuthenticators = secondarySignersPublicKeys.map((publicKey) =>
getAuthenticatorForSimulation(publicKey),
);
} else {
secondaryAccountAuthenticators = new Array(transaction.secondarySignerAddresses.length)
.fill(undefined)
.map((publicKey) => getAuthenticatorForSimulation(publicKey));
}
}
const feePayerAuthenticator = getAuthenticatorForSimulation(feePayerPublicKey);

Expand All @@ -512,16 +514,16 @@ export function generateSignedTransactionForSimulation(args: InputSimulateTransa

let secondaryAccountAuthenticators: Array<AccountAuthenticator> = [];

if (!secondarySignersPublicKeys) {
throw new Error(
"Must provide a secondarySignersPublicKeys argument to generate a signed multi agent transaction for simulation",
if (secondarySignersPublicKeys) {
secondaryAccountAuthenticators = secondarySignersPublicKeys!.map((publicKey) =>
getAuthenticatorForSimulation(publicKey),
);
} else {
secondaryAccountAuthenticators = new Array(transaction.secondarySignerAddresses.length)
.fill(undefined)
.map((publicKey) => getAuthenticatorForSimulation(publicKey));
}

secondaryAccountAuthenticators = secondarySignersPublicKeys.map((publicKey) =>
getAuthenticatorForSimulation(publicKey),
);

const transactionAuthenticator = new TransactionAuthenticatorMultiAgent(
accountAuthenticator,
transaction.secondarySignerAddresses,
Expand All @@ -543,13 +545,19 @@ export function generateSignedTransactionForSimulation(args: InputSimulateTransa
accountAuthenticator instanceof AccountAuthenticatorMultiKey
) {
transactionAuthenticator = new TransactionAuthenticatorSingleSender(accountAuthenticator);
} else if (accountAuthenticator instanceof AccountAuthenticatorNoAccountAuthenticator) {
transactionAuthenticator = new TransactionAuthenticatorSingleSender(accountAuthenticator);
} else {
throw new Error("Invalid public key");
}
return new SignedTransaction(transaction.rawTransaction, transactionAuthenticator).bcsToBytes();
}

export function getAuthenticatorForSimulation(publicKey: PublicKey) {
export function getAuthenticatorForSimulation(publicKey?: PublicKey) {
if (!publicKey) {
return new AccountAuthenticatorNoAccountAuthenticator();
}

// Wrap the public key types below with AnyPublicKey as they are only support through single sender.
// Learn more about AnyPublicKey here - https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-55.md
const convertToAnyPublicKey =
Expand Down
5 changes: 3 additions & 2 deletions src/transactions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,12 +307,13 @@ export type InputSimulateTransactionData = {
transaction: AnyRawTransaction;
/**
* For a single signer transaction
* This is optional and can be undefined to skip the public/auth key check during the transaction simulation.
*/
signerPublicKey: PublicKey;
signerPublicKey?: PublicKey;
/**
* For a fee payer or multi-agent transaction that requires additional signers in
*/
secondarySignersPublicKeys?: Array<PublicKey>;
secondarySignersPublicKeys?: Array<PublicKey | undefined>;
/**
* For a fee payer transaction (aka Sponsored Transaction)
*/
Expand Down
1 change: 1 addition & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export enum AccountAuthenticatorVariant {
MultiEd25519 = 1,
SingleKey = 2,
MultiKey = 3,
NoAccountAuthenticator = 4,
}

/**
Expand Down
Loading

0 comments on commit 07ca3c5

Please sign in to comment.