Aptos Move Integration Guide
This guide covers integrating Aptos Move smart contracts with TypeScript frontends, including contract interaction patterns, resource management, event handling, and testing strategies for 2025.
Prerequisites
- Aptos CLI installed for Move development
- Understanding of Move language basics
- TypeScript/React knowledge for frontend integration
- @aptos-labs/ts-sdk for blockchain interactions
Move Contract Structure
Basic Move Module Example
// sources/counter.move
module my_address::counter {
use std::signer;
use aptos_framework::event;
struct Counter has key {
value: u64,
}
#[event]
struct CounterCreated has drop, store {
account: address,
initial_value: u64,
}
#[event]
struct CounterIncremented has drop, store {
account: address,
old_value: u64,
new_value: u64,
}
public entry fun initialize_counter(account: &signer) {
let account_addr = signer::address_of(account);
assert!(!exists<Counter>(account_addr), 1);
let counter = Counter { value: 0 };
move_to(account, counter);
event::emit(CounterCreated {
account: account_addr,
initial_value: 0,
});
}
public entry fun increment(account: &signer) acquires Counter {
let account_addr = signer::address_of(account);
let counter = borrow_global_mut<Counter>(account_addr);
let old_value = counter.value;
counter.value = counter.value + 1;
event::emit(CounterIncremented {
account: account_addr,
old_value,
new_value: counter.value,
});
}
#[view]
public fun get_value(account_addr: address): u64 acquires Counter {
borrow_global<Counter>(account_addr).value
}
public entry fun set_value(account: &signer, new_value: u64) acquires Counter {
let account_addr = signer::address_of(account);
let counter = borrow_global_mut<Counter>(account_addr);
counter.value = new_value;
}
}
Move.toml Configuration
[package]
name = "counter"
version = "1.0.0"
authors = ["your-email@example.com"]
[dependencies.AptosFramework]
git = "https://github.com/aptos-labs/aptos-core.git"
rev = "mainnet"
subdir = "aptos-move/framework/aptos-framework"
[addresses]
my_address = "_"
TypeScript Integration Patterns
Contract Interaction Utilities
// utils/counterContract.ts
import {
Aptos,
AptosConfig,
Network,
InputViewFunctionData,
Account,
AccountAddress,
} from '@aptos-labs/ts-sdk';
export class CounterContract {
private moduleAddress: string;
private moduleName: string = 'counter';
constructor(
private aptos: Aptos,
moduleAddress: string
) {
this.moduleAddress = moduleAddress;
}
// Initialize counter for an account
async initializeCounter(account: Account) {
const transaction = await this.aptos.transaction.build.simple({
sender: account.accountAddress,
data: {
function: `${this.moduleAddress}::${this.moduleName}::initialize_counter`,
functionArguments: [],
},
});
const response = await this.aptos.signAndSubmitTransaction({
signer: account,
transaction,
});
return this.aptos.waitForTransaction({
transactionHash: response.hash,
});
}
// Increment counter
async incrementCounter(account: Account) {
const transaction = await this.aptos.transaction.build.simple({
sender: account.accountAddress,
data: {
function: `${this.moduleAddress}::${this.moduleName}::increment`,
functionArguments: [],
},
});
const response = await this.aptos.signAndSubmitTransaction({
signer: account,
transaction,
});
return this.aptos.waitForTransaction({
transactionHash: response.hash,
});
}
// Set counter value
async setCounterValue(account: Account, value: number) {
const transaction = await this.aptos.transaction.build.simple({
sender: account.accountAddress,
data: {
function: `${this.moduleAddress}::${this.moduleName}::set_value`,
functionArguments: [value],
},
});
const response = await this.aptos.signAndSubmitTransaction({
signer: account,
transaction,
});
return this.aptos.waitForTransaction({
transactionHash: response.hash,
});
}
// Read counter value using view function
async getCounterValue(accountAddress: string): Promise<number> {
try {
const result = await this.aptos.view({
payload: {
function: `${this.moduleAddress}::${this.moduleName}::get_value`,
functionArguments: [accountAddress],
},
});
return Number(result[0]);
} catch (error) {
console.error('Error reading counter value:', error);
throw error;
}
}
// Check if counter exists for account
async hasCounter(accountAddress: string): Promise<boolean> {
try {
const resource = await this.aptos.getAccountResource({
accountAddress,
resourceType: `${this.moduleAddress}::${this.moduleName}::Counter`,
});
return !!resource;
} catch (error) {
return false;
}
}
// Get counter resource directly
async getCounterResource(accountAddress: string) {
return this.aptos.getAccountResource({
accountAddress,
resourceType: `${this.moduleAddress}::${this.moduleName}::Counter`,
});
}
}
React Hook for Contract Interaction
// hooks/useCounter.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useWallet } from '@aptos-labs/wallet-adapter-react';
import { Aptos, AptosConfig, Network } from '@aptos-labs/ts-sdk';
import { CounterContract } from '../utils/counterContract';
const MODULE_ADDRESS = '0x...'; // Your deployed module address
export function useCounter(accountAddress?: string) {
const { account, signAndSubmitTransaction } = useWallet();
const queryClient = useQueryClient();
const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));
const contract = new CounterContract(aptos, MODULE_ADDRESS);
// Query if counter exists
const {
data: hasCounter,
isLoading: checkingCounter
} = useQuery({
queryKey: ['hasCounter', accountAddress],
queryFn: () => contract.hasCounter(accountAddress!),
enabled: !!accountAddress,
});
// Query counter value
const {
data: counterValue,
isLoading: loadingValue,
error
} = useQuery({
queryKey: ['counterValue', accountAddress],
queryFn: () => contract.getCounterValue(accountAddress!),
enabled: !!accountAddress && hasCounter,
refetchInterval: 5000, // Refresh every 5 seconds
});
// Initialize counter mutation
const initializeCounter = useMutation({
mutationFn: async () => {
if (!account) throw new Error('Wallet not connected');
const transaction = await aptos.transaction.build.simple({
sender: account.address,
data: {
function: `${MODULE_ADDRESS}::counter::initialize_counter`,
functionArguments: [],
},
});
const response = await signAndSubmitTransaction({ transaction });
return aptos.waitForTransaction({ transactionHash: response.hash });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['hasCounter', account?.address] });
queryClient.invalidateQueries({ queryKey: ['counterValue', account?.address] });
},
});
// Increment mutation
const incrementCounter = useMutation({
mutationFn: async () => {
if (!account) throw new Error('Wallet not connected');
const transaction = await aptos.transaction.build.simple({
sender: account.address,
data: {
function: `${MODULE_ADDRESS}::counter::increment`,
functionArguments: [],
},
});
const response = await signAndSubmitTransaction({ transaction });
return aptos.waitForTransaction({ transactionHash: response.hash });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['counterValue', account?.address] });
},
});
// Set value mutation
const setCounterValue = useMutation({
mutationFn: async (value: number) => {
if (!account) throw new Error('Wallet not connected');
const transaction = await aptos.transaction.build.simple({
sender: account.address,
data: {
function: `${MODULE_ADDRESS}::counter::set_value`,
functionArguments: [value],
},
});
const response = await signAndSubmitTransaction({ transaction });
return aptos.waitForTransaction({ transactionHash: response.hash });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['counterValue', account?.address] });
},
});
return {
hasCounter,
checkingCounter,
counterValue,
loadingValue,
error,
initializeCounter,
incrementCounter,
setCounterValue,
};
}