Skip to content

Commit

Permalink
[view] Add BCS view functions
Browse files Browse the repository at this point in the history
  • Loading branch information
gregnazario committed Mar 21, 2024
1 parent f16f279 commit 672618b
Show file tree
Hide file tree
Showing 15 changed files with 236 additions and 112 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T
# Unreleased
- Use indexer API via API Gateway
- Add signers to entry function ABI for future signature count checking
- [`Breaking`] Add type-safe view functions with ABI support
- Turn off code splitting on CJS

# 1.10.0 (2024-03-11)

Expand Down
4 changes: 2 additions & 2 deletions examples/typescript-esm/multisig_v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
Account,
Aptos,
AptosConfig,
InputViewRequestData,
Network,
NetworkToNetworkName,
MoveString,
Expand All @@ -30,6 +29,7 @@ import {
TransactionPayloadMultiSig,
MultiSig,
AccountAddress,
InputViewFunctionData,
} from "@aptos-labs/ts-sdk";

// Default to devnet, but allow for overriding
Expand Down Expand Up @@ -90,7 +90,7 @@ const settingUpMultiSigAccount = async () => {
// ===========================================================================================
// Get the next multisig account address. This will be the same as the account address of the multisig account we'll
// be creating.
const payload: InputViewRequestData = {
const payload: InputViewFunctionData = {
function: "0x1::multisig_account::get_next_multisig_account_address",
functionArguments: [owner1.accountAddress.toString()],
};
Expand Down
4 changes: 2 additions & 2 deletions examples/typescript/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import {
Aptos,
AptosConfig,
Ed25519PrivateKey,
InputViewFunctionData,
Network,
NetworkToNetworkName,
InputViewRequestData,
} from "@aptos-labs/ts-sdk";
import { createInterface } from "readline";
// Default to devnet, but allow for overriding
Expand All @@ -38,7 +38,7 @@ const getOptimalLpAmount = async (
token1Addr: AccountAddress,
token2Addr: AccountAddress,
): Promise<void> => {
const payload: InputViewRequestData = {
const payload: InputViewFunctionData = {
function: `${swap.toString()}::router::optimal_liquidity_amounts`,
functionArguments: [token1Addr, token2Addr, false, "200000", "300000", "200", "300"],
};
Expand Down
4 changes: 2 additions & 2 deletions examples/typescript/your_fungible_asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
AnyNumber,
Aptos,
AptosConfig,
InputViewRequestData,
InputViewFunctionData,
Network,
NetworkToNetworkName,
} from "@aptos-labs/ts-sdk";
Expand Down Expand Up @@ -127,7 +127,7 @@ const getFaBalance = async (owner: Account, assetType: string): Promise<number>

/** Return the address of the managed fungible asset that's created when this module is deployed */
async function getMetadata(admin: Account): Promise<string> {
const payload: InputViewRequestData = {
const payload: InputViewFunctionData = {
function: `${admin.accountAddress}::fa_coin::get_metadata`,
functionArguments: [],
};
Expand Down
6 changes: 3 additions & 3 deletions src/api/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
getProcessorStatus,
getTableItem,
queryIndexer,
view,
} from "../internal/general";
import { view } from "../internal/view";
import {
AnyNumber,
Block,
Expand All @@ -23,9 +23,9 @@ import {
LedgerVersionArg,
MoveValue,
TableItemRequest,
InputViewRequestData,
} from "../types";
import { ProcessorType } from "../utils/const";
import { InputViewFunctionData } from "../transactions";

/**
* A class to query all `General` Aptos related queries
Expand Down Expand Up @@ -137,7 +137,7 @@ export class General {
* @returns an array of Move values
*/
async view<T extends Array<MoveValue>>(args: {
payload: InputViewRequestData;
payload: InputViewFunctionData;
options?: LedgerVersionArg;
}): Promise<T> {
return view<T>({ aptosConfig: this.config, ...args });
Expand Down
17 changes: 6 additions & 11 deletions src/internal/ans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
import { AptosConfig } from "../api/aptosConfig";
import { Account, AccountAddress, AccountAddressInput } from "../core";
import { InputGenerateTransactionOptions, SimpleTransaction } from "../transactions/types";
import { GetANSNameResponse, MoveAddressType, MoveValue, OrderByArg, PaginationArgs, WhereArg } from "../types";
import { GetANSNameResponse, MoveAddressType, OrderByArg, PaginationArgs, WhereArg } from "../types";
import { GetNamesQuery } from "../types/generated/operations";
import { GetNames } from "../types/generated/queries";
import { CurrentAptosNamesBoolExp } from "../types/generated/types";
import { Network } from "../utils/apiEndpoints";
import { queryIndexer, view } from "./general";
import { queryIndexer } from "./general";
import { view } from "./view";
import { generateTransaction } from "./transactionSubmission";

export const VALIDATION_RULES_DESCRIPTION = [
Expand Down Expand Up @@ -85,12 +86,6 @@ function getRouterAddress(aptosConfig: AptosConfig): string {
return address;
}

const Some = <T>(value: T): MoveValue => ({ vec: [value] });
const None = (): MoveValue => ({ vec: [] });
// != here is intentional, we want to check for null and undefined
// eslint-disable-next-line eqeqeq
const Option = <T>(value: T | undefined | null): MoveValue => (value != undefined ? Some(value) : None());

const unwrapOption = <T>(option: any): T | undefined => {
if (!!option && typeof option === "object" && "vec" in option && Array.isArray(option.vec)) {
return option.vec[0];
Expand All @@ -108,7 +103,7 @@ export async function getOwnerAddress(args: { aptosConfig: AptosConfig; name: st
aptosConfig,
payload: {
function: `${routerAddress}::router::get_owner_addr`,
functionArguments: [domainName, Option(subdomainName)],
functionArguments: [domainName, subdomainName],
},
});

Expand Down Expand Up @@ -219,7 +214,7 @@ export async function getExpiration(args: { aptosConfig: AptosConfig; name: stri
aptosConfig,
payload: {
function: `${routerAddress}::router::get_expiration`,
functionArguments: [domainName, Option(subdomainName)],
functionArguments: [domainName, subdomainName],
},
});

Expand Down Expand Up @@ -303,7 +298,7 @@ export async function getTargetAddress(args: {
aptosConfig,
payload: {
function: `${routerAddress}::router::get_target_addr`,
functionArguments: [domainName, Option(subdomainName)],
functionArguments: [domainName, subdomainName],
},
});

Expand Down
24 changes: 0 additions & 24 deletions src/internal/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ import {
GraphqlQuery,
LedgerInfo,
LedgerVersionArg,
MoveValue,
TableItemRequest,
ViewRequest,
InputViewRequestData,
} from "../types";
import { GetChainTopUserTransactionsQuery, GetProcessorStatusQuery } from "../types/generated/operations";
import { GetChainTopUserTransactions, GetProcessorStatus } from "../types/generated/queries";
Expand Down Expand Up @@ -84,27 +81,6 @@ export async function getTableItem<T>(args: {
return response.data as T;
}

export async function view<T extends Array<MoveValue> = Array<MoveValue>>(args: {
aptosConfig: AptosConfig;
payload: InputViewRequestData;
options?: LedgerVersionArg;
}): Promise<T> {
const { aptosConfig, payload, options } = args;
const { data } = await postAptosFullNode<ViewRequest, MoveValue[]>({
aptosConfig,
originMethod: "view",
path: "view",
params: { ledger_version: options?.ledgerVersion },
body: {
function: payload.function,
type_arguments: payload.typeArguments ?? [],
arguments: payload.functionArguments ?? [],
},
});

return data as T;
}

export async function getChainTopUserTransactions(args: {
aptosConfig: AptosConfig;
limit: number;
Expand Down
23 changes: 8 additions & 15 deletions src/internal/transactionSubmission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
generateSignedTransaction,
sign,
generateSigningMessage,
generateTransactionPayloadWithABI,
} from "../transactions/transactionBuilder/transactionBuilder";
import {
InputGenerateTransactionData,
Expand Down Expand Up @@ -111,20 +110,14 @@ export async function buildTransactionPayload(
// TODO: Add ABI checking later
payload = await generateTransactionPayload(data);
} else if ("multisigAddress" in data) {
if (data.abi) {
payload = generateTransactionPayloadWithABI({ abi: data.abi, ...data });
} else {
generateTransactionPayloadData = {
aptosConfig,
multisigAddress: data.multisigAddress,
function: data.function,
functionArguments: data.functionArguments,
typeArguments: data.typeArguments,
};
payload = await generateTransactionPayload(generateTransactionPayloadData);
}
} else if (data.abi) {
payload = generateTransactionPayloadWithABI({ abi: data.abi, ...data });
generateTransactionPayloadData = {
aptosConfig,
multisigAddress: data.multisigAddress,
function: data.function,
functionArguments: data.functionArguments,
typeArguments: data.typeArguments,
};
payload = await generateTransactionPayload(generateTransactionPayloadData);
} else {
generateTransactionPayloadData = {
aptosConfig,
Expand Down
35 changes: 35 additions & 0 deletions src/internal/view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

import { LedgerVersionArg, MimeType, MoveValue } from "../types";
import { AptosConfig } from "../api/aptosConfig";
import { generateViewFunctionPayload, InputViewFunctionData } from "../transactions";
import { Serializer } from "../bcs";
import { postAptosFullNode } from "../client";

export async function view<T extends Array<MoveValue> = Array<MoveValue>>(args: {
aptosConfig: AptosConfig;
payload: InputViewFunctionData;
options?: LedgerVersionArg;
}): Promise<T> {
const { aptosConfig, payload, options } = args;
const viewFunctionPayload = await generateViewFunctionPayload({
...payload,
aptosConfig,
});

const serializer = new Serializer();
viewFunctionPayload.serialize(serializer);
const bytes = serializer.toUint8Array();

const { data } = await postAptosFullNode<Uint8Array, MoveValue[]>({
aptosConfig,
path: "view",
originMethod: "view",
contentType: MimeType.BCS_VIEW_FUNCTION,
params: { ledger_version: options?.ledgerVersion },
body: bytes,
});

return data as T;
}
70 changes: 64 additions & 6 deletions src/transactions/transactionBuilder/remoteAbi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
import { parseTypeTag } from "../typeTag/parser";
import { TypeTag, TypeTagStruct } from "../typeTag";
import { AptosConfig } from "../../api/aptosConfig";
import { EntryFunctionArgumentTypes, SimpleEntryFunctionArgumentTypes, EntryFunctionABI } from "../types";
import {
EntryFunctionArgumentTypes,
SimpleEntryFunctionArgumentTypes,
EntryFunctionABI,
ViewFunctionABI,
FunctionABI,
} from "../types";
import { Bool, MoveOption, MoveString, MoveVector, U128, U16, U256, U32, U64, U8 } from "../../bcs";
import { AccountAddress } from "../../core";
import { getModule } from "../../internal/account";
Expand Down Expand Up @@ -45,6 +51,18 @@ export function standardizeTypeTags(typeArguments?: Array<TypeTag | string>): Ar
);
}

export async function fetchFunctionAbi(
moduleAddress: string,
moduleName: string,
functionName: string,
aptosConfig: AptosConfig,
) {
// This fetch from the API is currently cached
const module = await getModule({ aptosConfig, accountAddress: moduleAddress, moduleName });

return module.abi?.exposed_functions.find((func) => func.name === functionName);
}

/**
* Fetches the ABI for an entry function from the module
*
Expand All @@ -59,10 +77,7 @@ export async function fetchEntryFunctionAbi(
functionName: string,
aptosConfig: AptosConfig,
): Promise<EntryFunctionABI> {
// This fetch from the API is currently cached
const module = await getModule({ aptosConfig, accountAddress: moduleAddress, moduleName });

const functionAbi = module.abi?.exposed_functions.find((func) => func.name === functionName);
const functionAbi = await fetchFunctionAbi(moduleAddress, moduleName, functionName, aptosConfig);

// If there's no ABI, then the function is invalid
if (!functionAbi) {
Expand All @@ -88,6 +103,49 @@ export async function fetchEntryFunctionAbi(
};
}

/**
* Fetches the ABI for an entry function from the module
*
* @param moduleAddress
* @param moduleName
* @param functionName
* @param aptosConfig
*/
export async function fetchViewFunctionAbi(
moduleAddress: string,
moduleName: string,
functionName: string,
aptosConfig: AptosConfig,
): Promise<ViewFunctionABI> {
const functionAbi = await fetchFunctionAbi(moduleAddress, moduleName, functionName, aptosConfig);

// If there's no ABI, then the function is invalid
if (!functionAbi) {
throw new Error(`Could not find view function ABI for '${moduleAddress}::${moduleName}::${functionName}'`);
}

// Non-view functions also can't be used
if (!functionAbi.is_view) {
throw new Error(`'${moduleAddress}::${moduleName}::${functionName}' is not an view function`);
}

const params: TypeTag[] = [];
for (let i = 0; i < functionAbi.params.length; i += 1) {
params.push(parseTypeTag(functionAbi.params[i], { allowGenerics: true }));
}

const returnTypes: TypeTag[] = [];
for (let i = 0; i < functionAbi.return.length; i += 1) {
returnTypes.push(parseTypeTag(functionAbi.return[i], { allowGenerics: true }));
}

return {
typeParameters: functionAbi.generic_type_params,
parameters: params,
returnTypes,
};
}

/**
* Converts a non-BCS encoded argument into BCS encoded, if necessary
* @param functionName
Expand All @@ -97,7 +155,7 @@ export async function fetchEntryFunctionAbi(
*/
export function convertArgument(
functionName: string,
functionAbi: EntryFunctionABI,
functionAbi: FunctionABI,
arg: EntryFunctionArgumentTypes | SimpleEntryFunctionArgumentTypes,
position: number,
genericTypeParams: Array<TypeTag>,
Expand Down
Loading

0 comments on commit 672618b

Please sign in to comment.