diff --git a/package.json b/package.json index e3090ebb5..46d852261 100644 --- a/package.json +++ b/package.json @@ -48,12 +48,14 @@ "update-version": "scripts/updateVersion.sh && pnpm doc" }, "dependencies": { - "@aptos-labs/aptos-client": "^0.1.0", "@aptos-labs/aptos-cli": "^0.1.9", + "@aptos-labs/aptos-client": "^0.1.0", + "@aptos-labs/ts-sdk": "^1.27.1", "@noble/curves": "^1.4.0", "@noble/hashes": "^1.4.0", "@scure/bip32": "^1.4.0", "@scure/bip39": "^1.3.0", + "@wgb5445/aptos-intent-npm": "^0.0.12", "eventemitter3": "^5.0.1", "form-data": "^4.0.0", "js-base64": "^3.7.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f9e0513f..1a7bb839b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@aptos-labs/aptos-client': specifier: ^0.1.0 version: 0.1.0 + '@aptos-labs/ts-sdk': + specifier: ^1.27.1 + version: 1.27.1 '@noble/curves': specifier: ^1.4.0 version: 1.4.0 @@ -23,6 +26,9 @@ dependencies: '@scure/bip39': specifier: ^1.3.0 version: 1.3.0 + '@wgb5445/aptos-intent-npm': + specifier: ^0.0.12 + version: 0.0.12 eventemitter3: specifier: ^5.0.1 version: 5.0.1 @@ -146,6 +152,11 @@ packages: hasBin: true dev: false + /@aptos-labs/aptos-cli@0.2.0: + resolution: {integrity: sha512-6kljJFRsTLXCvgkNhBoOLhVyo7rmih+8+XAtdeciIXkZYwzwVS3TFPLMqBUO2HcY6gYtQQRmTG52R5ihyi/bXA==} + hasBin: true + dev: false + /@aptos-labs/aptos-client@0.1.0: resolution: {integrity: sha512-q3s6pPq8H2buGp+tPuIRInWsYOuhSEwuNJPwd2YnsiID3YSLihn2ug39ktDJAcSOprUcp7Nid8WK7hKqnUmSdA==} engines: {node: '>=15.10.0'} @@ -156,6 +167,35 @@ packages: - debug dev: false + /@aptos-labs/aptos-client@0.1.1: + resolution: {integrity: sha512-kJsoy4fAPTOhzVr7Vwq8s/AUg6BQiJDa7WOqRzev4zsuIS3+JCuIZ6vUd7UBsjnxtmguJJulMRs9qWCzVBt2XA==} + engines: {node: '>=15.10.0'} + dependencies: + axios: 1.7.4 + got: 11.8.6 + transitivePeerDependencies: + - debug + dev: false + + /@aptos-labs/ts-sdk@1.27.1: + resolution: {integrity: sha512-QS4BlivXQy/uJgXcNOfXNjv8l+MSd+qQ256mY/Jc6iaWbfn69nRYh6chjSyLot4fHA49QxlZlWh1mJLlfNdtow==} + engines: {node: '>=11.0.0'} + dependencies: + '@aptos-labs/aptos-cli': 0.2.0 + '@aptos-labs/aptos-client': 0.1.1 + '@noble/curves': 1.4.0 + '@noble/hashes': 1.4.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + eventemitter3: 5.0.1 + form-data: 4.0.0 + js-base64: 3.7.7 + jwt-decode: 4.0.0 + poseidon-lite: 0.2.0 + transitivePeerDependencies: + - debug + dev: false + /@ardatan/relay-compiler@12.0.0(graphql@16.8.1): resolution: {integrity: sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==} hasBin: true @@ -2804,6 +2844,10 @@ packages: '@xtuc/long': 4.2.2 dev: true + /@wgb5445/aptos-intent-npm@0.0.12: + resolution: {integrity: sha512-JG0MpqqzJK8RVAKCkeyg9NuYwHCc4oztZyTEITuOuM6TAgsU9jMrTbwXstj9fQOv7dh3qigx/do1bAAN8C7zpA==} + dev: false + /@whatwg-node/events@0.0.3: resolution: {integrity: sha512-IqnKIDWfXBJkvy/k6tzskWTc2NK3LcqHlb+KHGCrjOCH4jfQckRX0NAiIcC/vIqQkzLYw2r2CTSwAxcrtcD6lA==} dev: true @@ -3107,6 +3151,16 @@ packages: - debug dev: false + /axios@1.7.4: + resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /babel-jest@29.7.0(@babel/core@7.24.6): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} diff --git a/src/account/AbstractedAccount.ts b/src/account/AbstractedAccount.ts new file mode 100644 index 000000000..194e362d6 --- /dev/null +++ b/src/account/AbstractedAccount.ts @@ -0,0 +1,127 @@ +import { AccountAuthenticatorEd25519, AccountAuthenticatorAbstraction } from "../transactions/authenticator/account"; +import { HexInput, SigningScheme } from "../types"; +import { AccountAddress, AccountAddressInput } from "../core/accountAddress"; +import { Ed25519PrivateKey, Ed25519PublicKey, Ed25519Signature } from "../core/crypto"; +import type { Account } from "./Account"; +import { AnyRawTransaction } from "../transactions/types"; +import { generateSigningMessageForTransaction } from "../transactions/transactionBuilder/signingMessage"; +import { FunctionInfo } from "../internal/function_info"; + +export interface Ed25519SignerConstructorArgs { + privateKey: Ed25519PrivateKey; + address?: AccountAddressInput; +} + +export interface Ed25519SignerFromDerivationPathArgs { + path: string; + mnemonic: string; +} + +export interface VerifyEd25519SignatureArgs { + message: HexInput; + signature: Ed25519Signature; +} + +/** + * Signer implementation for the Ed25519 authentication scheme. + * This extends an {@link Ed25519Account} by adding signing capabilities through an {@link Ed25519PrivateKey}. + * + * Note: Generating a signer instance does not create the account on-chain. + */ +export class AbstractedEd25519Account implements Account { + /** + * Private key associated with the account + */ + readonly privateKey: Ed25519PrivateKey; + + readonly publicKey: Ed25519PublicKey; + + readonly accountAddress: AccountAddress; + + readonly signingScheme = SigningScheme.Abstraction; + + // region Constructors + + constructor(args: Ed25519SignerConstructorArgs) { + const { privateKey, address } = args; + this.privateKey = privateKey; + this.publicKey = privateKey.publicKey(); + this.accountAddress = address ? AccountAddress.from(address) : this.publicKey.authKey().derivedAddress(); + } + + /** + * Derives a signer from a randomly generated private key + */ + static generate() { + const privateKey = Ed25519PrivateKey.generate(); + return new AbstractedEd25519Account({ privateKey }); + } + + /** + * Derives an account with bip44 path and mnemonics + * + * @param args.path the BIP44 derive hardened path e.g. m/44'/637'/0'/0'/0' + * Detailed description: {@link https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki} + * @param args.mnemonic the mnemonic seed phrase of the account + */ + static fromDerivationPath(args: Ed25519SignerFromDerivationPathArgs) { + const { path, mnemonic } = args; + const privateKey = Ed25519PrivateKey.fromDerivationPath(path, mnemonic); + return new AbstractedEd25519Account({ privateKey }); + } + + // endregion + + // region Account + + /** + * Verify the given message and signature with the public key. + * + * @param args.message raw message data in HexInput format + * @param args.signature signed message Signature + * @returns + */ + verifySignature(args: VerifyEd25519SignatureArgs): boolean { + return this.publicKey.verifySignature(args); + } + + /** + * Sign a message using the account's Ed25519 private key. + * @param message the signing message, as binary input + * @return the AccountAuthenticator containing the signature, together with the account's public key + */ + signWithAuthenticator(message: HexInput): AccountAuthenticatorAbstraction { + return new AccountAuthenticatorAbstraction(this.publicKey, new FunctionInfo(AccountAddress.ONE, "permissioned_delegation", "authenticate"), this.privateKey.sign(message)); + } + + /** + * Sign a transaction using the account's Ed25519 private key. + * @param transaction the raw transaction + * @return the AccountAuthenticator containing the signature of the transaction, together with the account's public key + */ + signTransactionWithAuthenticator(transaction: AnyRawTransaction): AccountAuthenticatorAbstraction { + return new AccountAuthenticatorAbstraction(this.publicKey, new FunctionInfo(AccountAddress.ONE, "permissioned_delegation", "authenticate"), this.signTransaction(transaction)); + } + + /** + * Sign the given message using the account's Ed25519 private key. + * @param message in HexInput format + * @returns Signature + */ + sign(message: HexInput): Ed25519Signature { + return this.privateKey.sign(message); + } + + /** + * Sign the given transaction using the available signing capabilities. + * @param transaction the transaction to be signed + * @returns Signature + */ + signTransaction(transaction: AnyRawTransaction): Ed25519Signature { + const r = this.sign(new Uint8Array([1, 2, 3])); + console.log(r); + return r; + } + + // endregion +} diff --git a/src/api/transactionSubmission/build.ts b/src/api/transactionSubmission/build.ts index 99341fe9b..d89d690d1 100644 --- a/src/api/transactionSubmission/build.ts +++ b/src/api/transactionSubmission/build.ts @@ -1,12 +1,21 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 +import { get_wasm, initSync } from "@wgb5445/aptos-intent-npm"; import { AccountAddressInput } from "../../core"; import { generateTransaction } from "../../internal/transactionSubmission"; -import { InputGenerateTransactionPayloadData, InputGenerateTransactionOptions } from "../../transactions"; +import { + InputGenerateTransactionPayloadData, + InputGenerateTransactionOptions, + AptosIntentBuilder, + TransactionPayloadScript, + generateRawTransaction, +} from "../../transactions"; import { MultiAgentTransaction } from "../../transactions/instances/multiAgentTransaction"; import { SimpleTransaction } from "../../transactions/instances/simpleTransaction"; import { AptosConfig } from "../aptosConfig"; +import { singleSignerED25519 } from "../../../tests/unit/helper"; +import { Deserializer } from "../../bcs"; /** * A class to handle all `Build` transaction operations @@ -37,6 +46,24 @@ export class Build { return generateTransaction({ aptosConfig: this.config, ...args }); } + async batched_intents(args: { + sender: AccountAddressInput; + builder: (builder: AptosIntentBuilder) => Promise; + options?: InputGenerateTransactionOptions; + withFeePayer?: boolean; + }): Promise { + initSync(await get_wasm()); + let builder = new AptosIntentBuilder(this.config); + builder = await args.builder(builder); + const bytes = builder.build(); + let raw_txn = await generateRawTransaction({ + aptosConfig: this.config, + payload: TransactionPayloadScript.load(new Deserializer(bytes)), + ...args, + }); + return new SimpleTransaction(raw_txn); + } + /** * Build a multi agent transaction * diff --git a/src/bcs/serializable/movePrimitives.ts b/src/bcs/serializable/movePrimitives.ts index b553be310..216a75a4c 100644 --- a/src/bcs/serializable/movePrimitives.ts +++ b/src/bcs/serializable/movePrimitives.ts @@ -12,7 +12,8 @@ import { import { Deserializer } from "../deserializer"; import { Serializable, Serializer, ensureBoolean, validateNumberInRange } from "../serializer"; import { TransactionArgument } from "../../transactions/instances/transactionArgument"; -import { AnyNumber, Uint16, Uint32, Uint8, ScriptTransactionArgumentVariants } from "../../types"; +import { AnyNumber, Uint16, Uint32, Uint8, ScriptTransactionArgumentVariants ,HexInput } from "../../types"; +import { Hex } from "../../core"; export class Bool extends Serializable implements TransactionArgument { public readonly value: boolean; @@ -209,3 +210,30 @@ export class U256 extends Serializable implements TransactionArgument { return new U256(deserializer.deserializeU256()); } } + +export class Raw extends Serializable implements TransactionArgument { + public readonly value: Uint8Array; + + constructor(value: HexInput) { + super(); + this.value = Hex.fromHexInput(value).toUint8Array(); + } + + serialize(serializer: Serializer): void { + serializer.serializeBytes(this.value); + } + + serializeForEntryFunction(serializer: Serializer): void { + const bcsBytes = this.bcsToBytes(); + serializer.serializeBytes(bcsBytes); + } + + serializeForScriptFunction(serializer: Serializer): void { + serializer.serializeU32AsUleb128(ScriptTransactionArgumentVariants.Raw); + serializer.serialize(this); + } + + static deserialize(deserializer: Deserializer): Raw { + return new Raw(deserializer.deserializeBytes()); + } +} \ No newline at end of file diff --git a/src/cli/localNode.ts b/src/cli/localNode.ts index 45bd5710d..566d893e9 100644 --- a/src/cli/localNode.ts +++ b/src/cli/localNode.ts @@ -39,8 +39,8 @@ export class LocalNode { * Starts the local testnet by running the aptos node run-local-testnet command */ start() { - const cliCommand = "npx"; - const cliArgs = ["aptos", "node", "run-local-testnet", "--force-restart", "--assume-yes", "--with-indexer-api"]; + const cliCommand = "aptos"; + const cliArgs = ["node", "run-local-testnet", "--force-restart", "--assume-yes", "--with-indexer-api"]; const currentPlatform = platform(); let childProcess; diff --git a/src/internal/function_info.ts b/src/internal/function_info.ts new file mode 100644 index 000000000..7b8b4d33c --- /dev/null +++ b/src/internal/function_info.ts @@ -0,0 +1,26 @@ +import { Deserializer, Serializable, Serializer } from "../bcs"; +import { AccountAddress, Hex } from "../core"; + +export class FunctionInfo extends Serializable { + readonly module_address: AccountAddress; + readonly module_name: string; + readonly function_name: string; + constructor(module_address: AccountAddress, module_name: string, function_name: string) { + super(); + this.module_address = module_address; + this.module_name = module_name; + this.function_name = function_name; + } + + serialize(serializer: Serializer): void { + this.module_address.serialize(serializer); + serializer.serializeStr(this.module_name); + serializer.serializeStr(this.function_name); + } + static deserialize(deserializer: Deserializer): FunctionInfo { + const module_address = AccountAddress.deserialize(deserializer); + const module_name = deserializer.deserializeStr(); + const function_name = deserializer.deserializeStr(); + return new FunctionInfo(module_address, module_name, function_name); + } +} \ No newline at end of file diff --git a/src/transactions/authenticator/account.ts b/src/transactions/authenticator/account.ts index fa9e5d5b9..40fe7ed7a 100644 --- a/src/transactions/authenticator/account.ts +++ b/src/transactions/authenticator/account.ts @@ -8,6 +8,7 @@ import { AnyPublicKey, AnySignature } from "../../core/crypto"; import { Ed25519PublicKey, Ed25519Signature } from "../../core/crypto/ed25519"; import { MultiEd25519PublicKey, MultiEd25519Signature } from "../../core/crypto/multiEd25519"; import { MultiKey, MultiKeySignature } from "../../core/crypto/multiKey"; +import { FunctionInfo } from "../../internal/function_info"; import { AccountAuthenticatorVariant } from "../../types"; export abstract class AccountAuthenticator extends Serializable { @@ -24,6 +25,8 @@ export abstract class AccountAuthenticator extends Serializable { return AccountAuthenticatorSingleKey.load(deserializer); case AccountAuthenticatorVariant.MultiKey: return AccountAuthenticatorMultiKey.load(deserializer); + case AccountAuthenticatorVariant.Abstraction: + return AccountAuthenticatorAbstraction.load(deserializer); default: throw new Error(`Unknown variant index for AccountAuthenticator: ${index}`); } @@ -44,6 +47,10 @@ export abstract class AccountAuthenticator extends Serializable { isMultiKey(): this is AccountAuthenticatorMultiKey { return this instanceof AccountAuthenticatorMultiKey; } + + isAbstraction(): this is AccountAuthenticatorAbstraction { + return this instanceof AccountAuthenticatorAbstraction; + } } /** @@ -55,7 +62,6 @@ export abstract class AccountAuthenticator extends Serializable { */ export class AccountAuthenticatorEd25519 extends AccountAuthenticator { public readonly public_key: Ed25519PublicKey; - public readonly signature: Ed25519Signature; constructor(public_key: Ed25519PublicKey, signature: Ed25519Signature) { @@ -169,3 +175,42 @@ export class AccountAuthenticatorMultiKey extends AccountAuthenticator { return new AccountAuthenticatorMultiKey(public_keys, signatures); } } + +/** + * AccountAuthenticatorAbstraction + * + * @param function_info FunctionInfo of the aa auth function + * @param signature Signature passed to auth function + * + */ +export class AccountAuthenticatorAbstraction extends AccountAuthenticator { + public readonly public_key: Ed25519PublicKey; + public readonly function_info: FunctionInfo; + public readonly signature: Ed25519Signature; + + constructor(public_key: Ed25519PublicKey, function_info: FunctionInfo, signature: Ed25519Signature) { + super(); + this.public_key = public_key; + this.function_info = function_info; + this.signature = signature; + } + + serialize(serializer: Serializer): void { + serializer.serializeU32AsUleb128(AccountAuthenticatorVariant.Abstraction); + this.function_info.serialize(serializer); + var ser = new Serializer; + this.public_key.serialize(ser); + this.signature.serialize(ser); + const aa_signature = ser.toUint8Array(); + serializer.serializeBytes(aa_signature) + } + + static load(deserializer: Deserializer): AccountAuthenticatorAbstraction { + const function_info = FunctionInfo.deserialize(deserializer); + const aa_signature = deserializer.deserializeBytes() + var der = new Deserializer(aa_signature); + const public_key = Ed25519PublicKey.deserialize(der); + const signature = Ed25519Signature.deserialize(der); + return new AccountAuthenticatorAbstraction(public_key, function_info, signature); + } +} diff --git a/src/transactions/index.ts b/src/transactions/index.ts index b8da34334..dbacd2314 100644 --- a/src/transactions/index.ts +++ b/src/transactions/index.ts @@ -7,3 +7,4 @@ export * from "./transactionBuilder"; export * from "./typeTag"; export * from "./typeTag/parser"; export * from "./types"; +export * from "./intent"; diff --git a/src/transactions/instances/transactionPayload.ts b/src/transactions/instances/transactionPayload.ts index 1d100169f..d9164942c 100644 --- a/src/transactions/instances/transactionPayload.ts +++ b/src/transactions/instances/transactionPayload.ts @@ -6,7 +6,7 @@ import { Deserializer } from "../../bcs/deserializer"; import { Serializable, Serializer } from "../../bcs/serializer"; import { EntryFunctionBytes } from "../../bcs/serializable/entryFunctionBytes"; -import { Bool, U128, U16, U256, U32, U64, U8 } from "../../bcs/serializable/movePrimitives"; +import { Bool, Raw, U128, U16, U256, U32, U64, U8 } from "../../bcs/serializable/movePrimitives"; import { MoveVector } from "../../bcs/serializable/moveStructs"; import { AccountAddress } from "../../core"; import { Identifier } from "./identifier"; @@ -40,6 +40,8 @@ export function deserializeFromScriptArgument(deserializer: Deserializer): Trans return U32.deserialize(deserializer); case ScriptTransactionArgumentVariants.U256: return U256.deserialize(deserializer); + case ScriptTransactionArgumentVariants.Raw: + return Raw.deserialize(deserializer); default: throw new Error(`Unknown variant index for ScriptTransactionArgument: ${index}`); } diff --git a/src/transactions/intent/index.ts b/src/transactions/intent/index.ts new file mode 100644 index 000000000..15c8ed143 --- /dev/null +++ b/src/transactions/intent/index.ts @@ -0,0 +1,69 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import { BatchArgument, BatchedFunctionCallBuilder, get_wasm, initSync } from "@wgb5445/aptos-intent-npm"; +import { Network } from "../../utils"; +import { AptosConfig } from "../../api"; +import { + EntryFunctionArgumentTypes, + FunctionABI, + InputBatchedFunctionData, + SimpleEntryFunctionArgumentTypes, +} from "../types"; +import { convertArgument, fetchMoveFunctionAbi, getFunctionParts, standardizeTypeTags } from "../transactionBuilder"; +import { TypeTag } from "../typeTag"; + +function convert_batch_argument( + argument: BatchArgument | EntryFunctionArgumentTypes | SimpleEntryFunctionArgumentTypes, + functionName: string, + functionAbi: FunctionABI, + position: number, + genericTypeParams: Array, +): BatchArgument { + if (argument instanceof BatchArgument) { + return argument; + } else { + return BatchArgument.new_bytes( + convertArgument(functionName, functionAbi, argument, position, genericTypeParams).bcsToBytes(), + ); + } +} + +export class AptosIntentBuilder { + private builder: BatchedFunctionCallBuilder; + private config: AptosConfig; + + constructor(aptos_config: AptosConfig) { + this.builder = BatchedFunctionCallBuilder.single_signer(); + this.config = aptos_config; + } + + async add_batched_calls(input: InputBatchedFunctionData): Promise { + const { moduleAddress, moduleName, functionName } = getFunctionParts(input.function); + console.log(this.config); + await this.builder.load_module(this.config.fullnode!, moduleAddress + " " + moduleName); + const typeArguments = standardizeTypeTags(input.typeArguments); + const functionAbi = await fetchMoveFunctionAbi(moduleAddress, moduleName, functionName, this.config); + // Check the type argument count against the ABI + if (typeArguments.length !== functionAbi.typeParameters.length) { + throw new Error( + `Type argument count mismatch, expected ${functionAbi.typeParameters.length}, received ${typeArguments.length}`, + ); + } + + const functionArguments: BatchArgument[] = input.functionArguments.map((arg, i) => + convert_batch_argument(arg, functionName, functionAbi, i, typeArguments), + ); + + return this.builder.add_batched_call( + `${moduleAddress}::${moduleName}`, + functionName, + typeArguments.map((arg) => arg.toString()), + functionArguments, + ); + } + + build(): Uint8Array { + return this.builder.generate_batched_calls(); + } +} diff --git a/src/transactions/transactionBuilder/remoteAbi.ts b/src/transactions/transactionBuilder/remoteAbi.ts index d0334992b..d90680a8e 100644 --- a/src/transactions/transactionBuilder/remoteAbi.ts +++ b/src/transactions/transactionBuilder/remoteAbi.ts @@ -87,6 +87,34 @@ export async function fetchFunctionAbi( return undefined; } +/** + * Fetches a function ABI from the on-chain module ABI. It doesn't validate whether it's a view or entry function. + * @param moduleAddress + * @param moduleName + * @param functionName + * @param aptosConfig + */ +export async function fetchMoveFunctionAbi( + moduleAddress: string, + moduleName: string, + functionName: string, + aptosConfig: AptosConfig, +): Promise { + const functionAbi = await fetchFunctionAbi(moduleAddress, moduleName, functionName, aptosConfig); + if (!functionAbi) { + throw new Error(`Could not find entry function ABI for '${moduleAddress}::${moduleName}::${functionName}'`); + } + const params: TypeTag[] = []; + for (let i = 0; i < functionAbi.params.length; i += 1) { + params.push(parseTypeTag(functionAbi.params[i], { allowGenerics: true })); + } + + return { + typeParameters: functionAbi.generic_type_params, + parameters: params, + }; +} + /** * Fetches the ABI for an entry function from the module * diff --git a/src/transactions/types.ts b/src/transactions/types.ts index d107ab9c9..3885783d7 100644 --- a/src/transactions/types.ts +++ b/src/transactions/types.ts @@ -20,6 +20,7 @@ import { TypeTag } from "./typeTag"; import { AccountAuthenticator } from "./authenticator/account"; import { SimpleTransaction } from "./instances/simpleTransaction"; import { MultiAgentTransaction } from "./instances/multiAgentTransaction"; +import { BatchArgument } from "@wgb5445/aptos-intent-npm"; /** * Entry function arguments to be used when building a raw transaction using remote ABI @@ -144,6 +145,16 @@ export type InputMultiSigDataWithABI = { } & InputEntryFunctionDataWithABI; export type InputEntryFunctionDataWithRemoteABI = InputEntryFunctionData & { aptosConfig: AptosConfig }; + +/** + * The data needed to generate a batched function payload + */ +export type InputBatchedFunctionData = { + function: MoveFunctionId; + typeArguments?: Array; + functionArguments: Array; +}; + /** * The data needed to generate a Multi Sig payload */ diff --git a/src/types/index.ts b/src/types/index.ts index e5953e1e8..9a7b4a1e6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -61,6 +61,7 @@ export enum ScriptTransactionArgumentVariants { U16 = 6, U32 = 7, U256 = 8, + Raw = 9, } /** @@ -103,6 +104,8 @@ export enum AccountAuthenticatorVariant { MultiEd25519 = 1, SingleKey = 2, MultiKey = 3, + NoAccountAuthenticator = 4, + Abstraction = 5, } export enum AnyPublicKeyVariant { @@ -750,7 +753,7 @@ export type TransactionSignature = | TransactionSecp256k1Signature | TransactionMultiEd25519Signature | TransactionMultiAgentSignature - | TransactionFeePayerSignature; + | TransactionFeePayerSignature export function isEd25519Signature(signature: TransactionSignature): signature is TransactionFeePayerSignature { return "signature" in signature && signature.signature === "ed25519_signature"; @@ -1129,6 +1132,8 @@ export enum SigningScheme { SingleKey = 2, MultiKey = 3, + + Abstraction = 4, } export enum SigningSchemeInput { diff --git a/tests/e2e/transaction/transactionBuilder.test.ts b/tests/e2e/transaction/transactionBuilder.test.ts index 7060d58b3..81ed0eff4 100644 --- a/tests/e2e/transaction/transactionBuilder.test.ts +++ b/tests/e2e/transaction/transactionBuilder.test.ts @@ -10,6 +10,7 @@ import { parseTypeTag, AccountAuthenticator, AccountAuthenticatorEd25519, + AptosIntentBuilder, FeePayerRawTransaction, MultiAgentRawTransaction, RawTransaction, diff --git a/tests/e2e/transaction/transactionSubmission.test.ts b/tests/e2e/transaction/transactionSubmission.test.ts index 8d5473117..e8752f62e 100644 --- a/tests/e2e/transaction/transactionSubmission.test.ts +++ b/tests/e2e/transaction/transactionSubmission.test.ts @@ -1,6 +1,7 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 +import { BatchArgument, get_wasm, initSync } from "@wgb5445/aptos-intent-npm"; import { Account, U64, @@ -12,17 +13,24 @@ import { TransactionPayloadEntryFunction, Bool, MoveString, + AptosIntentBuilder, + TransactionPayloadScript, + generateRawTransaction, + SimpleTransaction, + Network, AccountAddress, } from "../../../src"; import { MAX_U64_BIG_INT } from "../../../src/bcs/consts"; import { longTestTimeout } from "../../unit/helper"; import { getAptosClient } from "../helper"; import { fundAccounts, multiSignerScriptBytecode, publishTransferPackage, singleSignerScriptBytecode } from "./helper"; +import { AbstractedEd25519Account } from "../../../src/account/AbstractedAccount"; const { aptos } = getAptosClient(); describe("transaction submission", () => { const contractPublisherAccount = Account.generate(); const singleSignerED25519SenderAccount = Account.generate({ scheme: SigningSchemeInput.Ed25519, legacy: false }); + const singleSignerAbstractionSenderAccount = AbstractedEd25519Account.generate(); const legacyED25519SenderAccount = Account.generate(); const receiverAccounts = [Account.generate(), Account.generate()]; const singleSignerSecp256k1Account = Account.generate({ scheme: SigningSchemeInput.Secp256k1Ecdsa }); @@ -61,6 +69,126 @@ describe("transaction submission", () => { expect(response.signature?.type).toBe("single_sender"); }); + test("with batch payload", async () => { + initSync(await get_wasm()) + let _aptos = getAptosClient({ network: Network.CUSTOM, fullnode: "http://127.0.0.1:8080/v1", faucet: "http://127.0.0.1:8081", indexer: "http://127.0.0.1:8090" }) + const builder = new AptosIntentBuilder(_aptos.config); + await builder.add_batched_calls({ + function: `${contractPublisherAccount.accountAddress}::transfer::transfer`, + functionArguments: [BatchArgument.new_signer(0), 1, receiverAccounts[0].accountAddress], + }); + const bytes = builder.build(); + const transaction = await generateRawTransaction({ + aptosConfig: aptos.config, + sender: singleSignerED25519SenderAccount.accountAddress, + payload: TransactionPayloadScript.load(new Deserializer(bytes)), + }); + const response = await aptos.signAndSubmitTransaction({ + signer: singleSignerED25519SenderAccount, + transaction: new SimpleTransaction(transaction), + }); + + await aptos.waitForTransaction({ + transactionHash: response.hash, + }); + + expect(response.signature?.type).toBe("single_sender"); + }); + test.only("with batch setup aa payload", async () => { + let _aptos = getAptosClient({ network: Network.CUSTOM, fullnode: "http://127.0.0.1:8080/v1", faucet: "http://127.0.0.1:8081", indexer: "http://127.0.0.1:8090" }) + + // step 1: setup. + let transaction = await _aptos.aptos.transaction.build.batched_intents({ + sender: singleSignerED25519SenderAccount.accountAddress, + builder: async (builder) => { + // convert apt to fa + await builder.add_batched_calls({ + function: `0x1::coin::migrate_to_fungible_store`, + functionArguments: [BatchArgument.new_signer(0)], + typeArguments: ["0x1::aptos_coin::AptosCoin"] + }); + // set up aa with permissioned signer + let permissioned_signer_handle = await builder.add_batched_calls({ + function: `0x1::permissioned_signer::create_permissioned_handle`, + functionArguments: [BatchArgument.new_signer(0)], + typeArguments: [] + }); + let permissioned_signer = await builder.add_batched_calls({ + function: `0x1::permissioned_signer::signer_from_permissioned`, + functionArguments: [permissioned_signer_handle[0].borrow()], + typeArguments: [] + }); + await builder.add_batched_calls({ + function: `0x1::fungible_asset::grant_permission`, + functionArguments: [BatchArgument.new_signer(0), permissioned_signer[0].borrow(), AccountAddress.A, 10 /* limit */], + typeArguments: [] + }); + await builder.add_batched_calls({ + function: `0x1::permissioned_delegation::add_permissioned_handle`, + functionArguments: [BatchArgument.new_signer(0), singleSignerAbstractionSenderAccount.publicKey.toUint8Array(), permissioned_signer_handle[0]], + typeArguments: [] + }); + await builder.add_batched_calls({ + function: `0x1::lite_account::add_dispatchable_authentication_function`, + functionArguments: [BatchArgument.new_signer(0), AccountAddress.ONE, new MoveString("permissioned_delegation"), new MoveString("authenticate")], + typeArguments: [] + }); + return builder; + } + }); + let response = await _aptos.aptos.signAndSubmitTransaction({ + signer: singleSignerED25519SenderAccount, + transaction: transaction, + }); + + await _aptos.aptos.waitForTransaction({ + transactionHash: response.hash, + }); + expect(response.signature?.type).toBe("single_sender"); + + // step 2: use AA to send APT FA. + transaction = await _aptos.aptos.transaction.build.simple({ + sender: singleSignerED25519SenderAccount.accountAddress, + data: { + function: `0x1::primary_fungible_store::transfer`, + functionArguments: [AccountAddress.A, receiverAccounts[0].accountAddress, 10], + typeArguments: [`0x1::fungible_asset::Metadata`] + }, + }); + response = await _aptos.aptos.signAndSubmitTransaction({ + signer: singleSignerAbstractionSenderAccount, + transaction, + }); + console.log(response); + expect(response.signature?.type).toBe("single_sender"); + + var submittedTransaction = await aptos.waitForTransaction({ + transactionHash: response.hash, + }); + expect(submittedTransaction.success).toBe(true); + + //step 3: use AA to send APT FA again. should fail. + transaction = await _aptos.aptos.transaction.build.simple({ + sender: singleSignerED25519SenderAccount.accountAddress, + data: { + function: `0x1::primary_fungible_store::transfer`, + functionArguments: [AccountAddress.A, receiverAccounts[0].accountAddress, 1], + typeArguments: [`0x1::fungible_asset::Metadata`] + }, + }); + response = await _aptos.aptos.signAndSubmitTransaction({ + signer: singleSignerAbstractionSenderAccount, + transaction, + }); + expect(response.signature?.type).toBe("single_sender"); + submittedTransaction = await aptos.waitForTransaction({ + transactionHash: response.hash, + options: { + checkSuccess: false + } + }); + expect(submittedTransaction.success).toBe(false); + }); test("with entry function payload", async () => { const transaction = await aptos.transaction.build.simple({ sender: singleSignerED25519SenderAccount.accountAddress, diff --git a/tests/preTest.js b/tests/preTest.js index e3553caca..f2fdbb7a2 100644 --- a/tests/preTest.js +++ b/tests/preTest.js @@ -3,5 +3,5 @@ const { LocalNode } = require("../src/cli"); module.exports = async function setup() { const localNode = new LocalNode(); globalThis.__LOCAL_NODE__ = localNode; - await localNode.run(); + // await localNode.run(); };