Skip to content

Commit 29df91b

Browse files
committed
[abi] Allow passing module ABI, which is the same as from surf
1 parent db88e98 commit 29df91b

File tree

6 files changed

+147
-24
lines changed

6 files changed

+147
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T
1212
- Add support for skipping struct type tag validation.
1313
- Add support for known enum structs: DelegationKey and RateLimiter.
1414
- Deprecated `fetchMoveFunctionAbi` and `convertCallArgument`
15+
- Allow passing in `MoveModule` as ABI for a function, rather than the parsed ABI
1516

1617
# 1.35.0 (2025-02-11)
1718

src/transactions/transactionBuilder/remoteAbi.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,24 @@ export async function fetchEntryFunctionAbi(
152152
functionName: string,
153153
aptosConfig: AptosConfig,
154154
): Promise<EntryFunctionABI> {
155-
const functionAbi = await fetchFunctionAbi(moduleAddress, moduleName, functionName, aptosConfig);
155+
const moduleAbi = await fetchModuleAbi(moduleAddress, moduleName, aptosConfig);
156+
if (!moduleAbi) throw new Error(`Could not find module ABI for '${moduleAddress}::${moduleName}'`);
157+
return parseEntryFunctionAbi({
158+
moduleAbi,
159+
moduleAddress,
160+
moduleName,
161+
functionName,
162+
});
163+
}
164+
165+
export function parseEntryFunctionAbi(args: {
166+
moduleAbi: MoveModule;
167+
moduleAddress: string;
168+
moduleName: string;
169+
functionName: string;
170+
}): EntryFunctionABI {
171+
const { moduleAbi, moduleAddress, moduleName, functionName } = args;
172+
const functionAbi = moduleAbi.exposed_functions.find((func) => func.name === functionName);
156173

157174
// If there's no ABI, then the function is invalid
158175
if (!functionAbi) {
@@ -197,7 +214,24 @@ export async function fetchViewFunctionAbi(
197214
functionName: string,
198215
aptosConfig: AptosConfig,
199216
): Promise<ViewFunctionABI> {
200-
const functionAbi = await fetchFunctionAbi(moduleAddress, moduleName, functionName, aptosConfig);
217+
const moduleAbi = await fetchModuleAbi(moduleAddress, moduleName, aptosConfig);
218+
if (!moduleAbi) throw new Error(`Could not find module ABI for '${moduleAddress}::${moduleName}'`);
219+
return parseViewFunctionAbi({
220+
moduleAbi,
221+
moduleAddress,
222+
moduleName,
223+
functionName,
224+
});
225+
}
226+
227+
export function parseViewFunctionAbi(args: {
228+
moduleAbi: MoveModule;
229+
moduleAddress: string;
230+
moduleName: string;
231+
functionName: string;
232+
}): ViewFunctionABI {
233+
const { moduleAbi, moduleAddress, moduleName, functionName } = args;
234+
const functionAbi = moduleAbi.exposed_functions.find((func) => func.name === functionName);
201235

202236
// If there's no ABI, then the function is invalid
203237
if (!functionAbi) {

src/transactions/transactionBuilder/transactionBuilder.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,23 @@ import {
7676
InputViewFunctionDataWithRemoteABI,
7777
InputViewFunctionDataWithABI,
7878
FunctionABI,
79+
ViewFunctionABI,
80+
EntryFunctionABI,
7981
} from "../types";
80-
import { convertArgument, fetchEntryFunctionAbi, fetchViewFunctionAbi, standardizeTypeTags } from "./remoteAbi";
82+
import {
83+
convertArgument,
84+
fetchEntryFunctionAbi,
85+
fetchViewFunctionAbi,
86+
parseEntryFunctionAbi,
87+
parseViewFunctionAbi,
88+
standardizeTypeTags,
89+
} from "./remoteAbi";
8190
import { memoizeAsync } from "../../utils/memoize";
8291
import { isScriptDataInput } from "./helpers";
8392
import { SimpleTransaction } from "../instances/simpleTransaction";
8493
import { MultiAgentTransaction } from "../instances/multiAgentTransaction";
8594
import { getFunctionParts } from "../../utils/helpers";
95+
import { MoveModule } from "../../types";
8696

8797
/**
8898
* Builds a transaction payload based on the provided arguments and returns a transaction payload.
@@ -145,6 +155,7 @@ export async function generateTransactionPayload(
145155
aptosConfig: args.aptosConfig,
146156
abi: args.abi,
147157
fetch: fetchEntryFunctionAbi,
158+
parse: parseEntryFunctionAbi,
148159
});
149160

150161
// Fill in the ABI
@@ -179,9 +190,16 @@ export function generateTransactionPayloadWithABI(args: InputMultiSigDataWithABI
179190
export function generateTransactionPayloadWithABI(
180191
args: InputGenerateTransactionPayloadDataWithABI,
181192
): AnyTransactionPayloadInstance {
182-
const functionAbi = args.abi;
183193
const { moduleAddress, moduleName, functionName } = getFunctionParts(args.function);
184194

195+
// Parse the ABI if it's the whole module (aka surf)
196+
let functionAbi: EntryFunctionABI | undefined;
197+
if ("exposed_functions" in args.abi) {
198+
functionAbi = parseEntryFunctionAbi({ moduleAbi: args.abi, moduleAddress, moduleName, functionName });
199+
} else {
200+
functionAbi = args.abi;
201+
}
202+
185203
// Ensure that all type arguments are typed properly
186204
const typeArguments = standardizeTypeTags(args.typeArguments);
187205

@@ -263,6 +281,7 @@ export async function generateViewFunctionPayload(args: InputViewFunctionDataWit
263281
aptosConfig: args.aptosConfig,
264282
abi: args.abi,
265283
fetch: fetchViewFunctionAbi,
284+
parse: parseViewFunctionAbi,
266285
});
267286

268287
// Fill in the ABI
@@ -286,9 +305,16 @@ export async function generateViewFunctionPayload(args: InputViewFunctionDataWit
286305
* @category Transactions
287306
*/
288307
export function generateViewFunctionPayloadWithABI(args: InputViewFunctionDataWithABI): EntryFunction {
289-
const functionAbi = args.abi;
290308
const { moduleAddress, moduleName, functionName } = getFunctionParts(args.function);
291309

310+
// Parse the ABI if it's the whole module (aka surf)
311+
let functionAbi: ViewFunctionABI | undefined;
312+
if ("exposed_functions" in args.abi) {
313+
functionAbi = parseViewFunctionAbi({ moduleAbi: args.abi, moduleAddress, moduleName, functionName });
314+
} else {
315+
functionAbi = args.abi;
316+
}
317+
292318
// Ensure that all type arguments are typed properly
293319
const typeArguments = standardizeTypeTags(args.typeArguments);
294320

@@ -771,6 +797,7 @@ export function generateUserTransactionHash(args: InputSubmitTransactionData): s
771797
* @param aptosConfig - Configuration settings for Aptos.
772798
* @param abi - An optional ABI to use if already available.
773799
* @param fetch - A function to fetch the ABI if it is not provided.
800+
* @param parse - A function to parse the ABI if as MoveModule ABI is provided.
774801
* @group Implementation
775802
* @category Transactions
776803
*/
@@ -782,17 +809,23 @@ async function fetchAbi<T extends FunctionABI>({
782809
aptosConfig,
783810
abi,
784811
fetch,
812+
parse,
785813
}: {
786814
key: string;
787815
moduleAddress: string;
788816
moduleName: string;
789817
functionName: string;
790818
aptosConfig: AptosConfig;
791-
abi?: T;
819+
abi?: T | MoveModule;
792820
fetch: (moduleAddress: string, moduleName: string, functionName: string, aptosConfig: AptosConfig) => Promise<T>;
821+
parse: (args: { moduleAddress: string; moduleName: string; functionName: string; moduleAbi: MoveModule }) => T;
793822
}): Promise<T> {
794823
if (abi !== undefined) {
795-
return abi;
824+
if ("exposed_functions" in abi) {
825+
return parse({ moduleAddress, moduleName, functionName, moduleAbi: abi });
826+
} else {
827+
return abi;
828+
}
796829
}
797830

798831
// We fetch the entry function ABI, and then pretend that we already had the ABI

src/transactions/types.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,15 @@ import {
1616
TransactionPayloadMultiSig,
1717
TransactionPayloadScript,
1818
} from "./instances";
19-
import { AnyNumber, HexInput, MoveFunctionGenericTypeParam, MoveFunctionId, MoveStructId, MoveValue } from "../types";
19+
import {
20+
AnyNumber,
21+
HexInput,
22+
MoveFunctionGenericTypeParam,
23+
MoveFunctionId,
24+
MoveModule,
25+
MoveStructId,
26+
MoveValue,
27+
} from "../types";
2028
import { TypeTag } from "./typeTag";
2129
import { AccountAuthenticator } from "./authenticator/account";
2230
import { SimpleTransaction } from "./instances/simpleTransaction";
@@ -159,7 +167,7 @@ export type InputEntryFunctionData = {
159167
function: MoveFunctionId;
160168
typeArguments?: Array<TypeArgument>;
161169
functionArguments: Array<EntryFunctionArgumentTypes | SimpleEntryFunctionArgumentTypes>;
162-
abi?: EntryFunctionABI;
170+
abi?: EntryFunctionABI | MoveModule;
163171
};
164172

165173
/**
@@ -175,7 +183,7 @@ export type InputGenerateTransactionPayloadDataWithABI = InputEntryFunctionDataW
175183
* @category Transactions
176184
*/
177185
export type InputEntryFunctionDataWithABI = Omit<InputEntryFunctionData, "abi"> & {
178-
abi: EntryFunctionABI;
186+
abi: EntryFunctionABI | MoveModule;
179187
};
180188

181189
/**
@@ -241,7 +249,7 @@ export type InputViewFunctionData = {
241249
function: MoveFunctionId;
242250
typeArguments?: Array<TypeArgument>;
243251
functionArguments?: Array<EntryFunctionArgumentTypes | SimpleEntryFunctionArgumentTypes>;
244-
abi?: ViewFunctionABI;
252+
abi?: ViewFunctionABI | MoveModule;
245253
};
246254

247255
/**
@@ -278,7 +286,7 @@ export type InputViewFunctionDataWithRemoteABI = InputViewFunctionData & { aptos
278286
* @group Implementation
279287
* @category Transactions
280288
*/
281-
export type InputViewFunctionDataWithABI = InputViewFunctionData & { abi: ViewFunctionABI };
289+
export type InputViewFunctionDataWithABI = InputViewFunctionData & { abi: ViewFunctionABI | MoveModule };
282290

283291
/**
284292
* Data needed for a generic function ABI, applicable to both view and entry functions.

tests/e2e/transaction/transactionBuilder.test.ts

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,32 @@
33

44
import {
55
Account,
6-
Deserializer,
7-
U64,
86
AccountAddress,
9-
EntryFunctionABI,
10-
parseTypeTag,
117
AccountAuthenticator,
128
AccountAuthenticatorEd25519,
13-
FeePayerRawTransaction,
14-
MultiAgentRawTransaction,
15-
RawTransaction,
16-
TransactionPayloadEntryFunction,
17-
TransactionPayloadMultiSig,
18-
TransactionPayloadScript,
199
buildTransaction,
2010
deriveTransactionType,
11+
Deserializer,
12+
EntryFunctionABI,
13+
FeePayerRawTransaction,
2114
generateRawTransaction,
2215
generateSignedTransaction,
2316
generateSignedTransactionForSimulation,
2417
generateTransactionPayload,
2518
generateTransactionPayloadWithABI,
26-
SignedTransaction,
2719
generateUserTransactionHash,
2820
KeylessPublicKey,
21+
MoveAbility,
22+
MoveFunctionVisibility,
23+
MoveModule,
24+
MultiAgentRawTransaction,
25+
parseTypeTag,
26+
RawTransaction,
27+
SignedTransaction,
28+
TransactionPayloadEntryFunction,
29+
TransactionPayloadMultiSig,
30+
TransactionPayloadScript,
31+
U64,
2932
} from "../../../src";
3033
import { FUND_AMOUNT, longTestTimeout } from "../../unit/helper";
3134
import { getAptosClient } from "../helper";
@@ -104,7 +107,24 @@ describe("transaction builder", () => {
104107
describe("generate transaction payload with preset ABI", () => {
105108
const functionAbi: EntryFunctionABI = {
106109
typeParameters: [],
107-
parameters: [parseTypeTag("address"), parseTypeTag("0x1::object::Object<T0>", { allowGenerics: true })],
110+
parameters: [parseTypeTag("address"), parseTypeTag("0x1::object::ObjectCore")],
111+
};
112+
const moduleAbi: MoveModule = {
113+
address: contractPublisherAccount.accountAddress.toString(),
114+
name: "transfer",
115+
friends: [],
116+
structs: [],
117+
exposed_functions: [
118+
{
119+
name: "transfer",
120+
visibility: MoveFunctionVisibility.PUBLIC,
121+
is_entry: true,
122+
is_view: false,
123+
generic_type_params: [],
124+
params: ["&signer", "address", "0x1::object::Object<0x1::object::ObjectCore>"],
125+
return: [],
126+
},
127+
],
108128
};
109129

110130
test("it generates a multi sig transaction payload", async () => {
@@ -138,6 +158,21 @@ describe("transaction builder", () => {
138158
});
139159
expect(payload2 instanceof TransactionPayloadEntryFunction).toBeTruthy();
140160
});
161+
162+
test("it generates an entry function transaction payload with mixed arguments and a module / surf ABI", async () => {
163+
const payload = generateTransactionPayloadWithABI({
164+
function: `${contractPublisherAccount.accountAddress}::transfer::transfer`,
165+
functionArguments: [AccountAddress.ONE, "0x1"],
166+
abi: moduleAbi,
167+
});
168+
expect(payload instanceof TransactionPayloadEntryFunction).toBeTruthy();
169+
const payload2 = generateTransactionPayloadWithABI({
170+
function: `${contractPublisherAccount.accountAddress}::transfer::transfer`,
171+
functionArguments: ["0x1", AccountAddress.ONE],
172+
abi: moduleAbi,
173+
});
174+
expect(payload2 instanceof TransactionPayloadEntryFunction).toBeTruthy();
175+
});
141176
});
142177
describe("generate raw transaction", () => {
143178
test("it generates a raw transaction with script payload", async () => {

tests/unit/remoteAbi.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,18 @@ describe("Remote ABI", () => {
126126
expect(checkOrConvertArgument(null, parseTypeTag("0x1::option::Option<u8>"), 0, [])).toEqual(
127127
new MoveOption<U8>(),
128128
);
129+
expect(checkOrConvertArgument(undefined, parseTypeTag("0x1::option::Option<signer>"), 0, [])).toEqual(
130+
new MoveOption<U8>(),
131+
);
132+
expect(checkOrConvertArgument(null, parseTypeTag("0x1::option::Option<signer>"), 0, [])).toEqual(
133+
new MoveOption<U8>(),
134+
);
135+
expect(checkOrConvertArgument(undefined, parseTypeTag("0x1::option::Option<&signer>"), 0, [])).toEqual(
136+
new MoveOption<U8>(),
137+
);
138+
expect(checkOrConvertArgument(null, parseTypeTag("0x1::option::Option<&signer>"), 0, [])).toEqual(
139+
new MoveOption<U8>(),
140+
);
129141

130142
// Objects are account addresses, and it doesn't matter about the type used
131143
expect(checkOrConvertArgument("0x1", parseTypeTag("0x1::object::Object<0x1::string::String>"), 0, [])).toEqual(

0 commit comments

Comments
 (0)