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 authored and gregnazario committed Oct 9, 2024
1 parent bb9f826 commit 23a1a79
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 61 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T

# 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.29.1 (2024-10-09)
- Fix the `FederatedKeylessAccount` constructor to derive the correct address.

Expand Down
47 changes: 34 additions & 13 deletions examples/typescript-esm/multisig_v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
InputViewFunctionData,
SimpleTransaction,
generateTransactionPayload,
TransactionPayloadEntryFunction,
} from "@aptos-labs/ts-sdk";

// Default to devnet, but allow for overriding
Expand Down Expand Up @@ -141,19 +142,39 @@ 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,
});

const simulateMultisigTx = await aptos.transaction.simulate.simple({
signerPublicKey: owner2.publicKey,
transaction: new SimpleTransaction(transactionToSimulate),
});

console.log("simulateMultisigTx", simulateMultisigTx);
// The simulation enhancement feature is not enabled on the devnet yet.
const isSimulationEnhancementFeatureEnabled = false;
if (!isSimulationEnhancementFeatureEnabled) {
// Simulate the transfer transaction to make sure it passes
const transactionToSimulate = await generateRawTransaction({
aptosConfig: config,
sender: owner2.accountAddress,
payload: transactionPayload,
});

const simulateMultisigTx = await aptos.transaction.simulate.simple({
signerPublicKey: owner2.publicKey,
transaction: new SimpleTransaction(transactionToSimulate),
});

console.log("simulateMultisigTx", simulateMultisigTx);
} else {
// 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 generateRawTransaction({
aptosConfig: config,
sender: transactionPayload.multiSig.multisig_address,
payload: new TransactionPayloadEntryFunction(transactionPayload.multiSig.transaction_payload.transaction_payload),
feePayerAddress: AccountAddress.ZERO,
});

// 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: new SimpleTransaction(transactionToSimulate),
});

console.log("simulateMultisigTx", simulateMultisigTx);
}

// Build create_transaction transaction
const createMultisigTx = await aptos.transaction.build.simple({
Expand Down
1 change: 1 addition & 0 deletions examples/typescript/local_node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ async function stopLocalNode() {
// Query localnet endpoint
await fetch("http://localhost:8080");
} catch (err: any) {
// eslint-disable-next-line no-console
console.log("localnet stopped");
}
}
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 @@ -18,12 +18,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
13 changes: 7 additions & 6 deletions src/api/transactionSubmission/simulate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class Simulate {
/**
* Simulate a simple transaction
*
* @param args.signerPublicKey The signer public key
* @param args.signerPublicKey optional. The signer public key
* @param args.transaction An instance of a raw transaction
* @param args.options optional. Optional transaction configurations
* @param args.feePayerPublicKey optional. The fee payer public key if it is a fee payer transaction
Expand All @@ -30,7 +30,7 @@ export class Simulate {
*/
@ValidateFeePayerDataOnSimulation
async simple(args: {
signerPublicKey: PublicKey;
signerPublicKey?: PublicKey;
transaction: AnyRawTransaction;
feePayerPublicKey?: PublicKey;
options?: InputSimulateTransactionOptions;
Expand All @@ -41,19 +41,20 @@ export class Simulate {
/**
* Simulate a multi agent transaction
*
* @param args.signerPublicKey The signer public key
* @param args.signerPublicKey optional. The signer public key
* @param args.transaction An instance of a raw transaction
* @param args.secondarySignersPublicKeys An array of the secondary signers public keys
* @param args.secondarySignersPublicKeys optional. An array of the secondary signers' public keys.
* Each element of the array can be optional, allowing the corresponding key check to be skipped.
* @param args.options optional. Optional transaction configurations
* @param args.feePayerPublicKey optional. The fee payer public key if it is a fee payer transaction
*
* @returns Array<UserTransactionResponse>
*/
@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 @@ -227,7 +227,7 @@ export function signAsFeePayer(args: { signer: Account; transaction: AnyRawTrans
/**
* Simulates a transaction before singing it.
*
* @param args.signerPublicKey The signer public key
* @param args.signerPublicKey optional. 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
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 @@ -24,6 +24,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 @@ -169,3 +171,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 @@ -27,6 +27,7 @@ import { normalizeBundle } from "../../utils/normalizeBundle";
import {
AccountAuthenticator,
AccountAuthenticatorEd25519,
AccountAuthenticatorNoAccountAuthenticator,
AccountAuthenticatorSingleKey,
} from "../authenticator/account";
import {
Expand Down Expand Up @@ -395,16 +396,18 @@ 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);

const transactionAuthenticator = new TransactionAuthenticatorFeePayer(
Expand All @@ -428,16 +431,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 @@ -456,13 +459,18 @@ export function generateSignedTransactionForSimulation(args: InputSimulateTransa
);
} else if (accountAuthenticator instanceof AccountAuthenticatorSingleKey) {
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 @@ -279,12 +279,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/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export enum AccountAuthenticatorVariant {
MultiEd25519 = 1,
SingleKey = 2,
MultiKey = 3,
NoAccountAuthenticator = 4,
}

export enum AnyPublicKeyVariant {
Expand Down
Loading

0 comments on commit 23a1a79

Please sign in to comment.