Generate type-safe TypeScript wrappers for Sui Move smart contracts.
- Parses Sui Move source files (
.move) using the officialmove-compilerparser crate - Generates TypeScript functions that call Move entry/public methods via the
@mysten/suiSDK - Type-safe: maps Move types to TypeScript types (
u64tobigint,vector<u8>toUint8Array, etc.) - Detects singleton objects (created only in
init()) and backs them with environment variables - Auto-strips
TxContext, auto-injectsClockandRandomparameters - All generated functions return
TransactionResultfor composability in Programmable Transaction Blocks - Uses the SDK's
TransactionObjectInputfor all object parameters - Supports generic type parameters
- Skips abort-only functions (deprecated stubs like
abort E_DEPRECATED) - CLI filtering:
--methods,--skip-methods - Portable generated code: works with Node.js, Deno, Bun
brew install avbel/tap/move2ts
npm install -g @move2ts/move2ts
# or
pnpm add -g @move2ts/move2ts
# or
npx @move2ts/move2ts <input>
Download the binary for your platform from the Releases page.
Available platforms: Linux x64/arm64, macOS arm64, Windows x64.
git clone https://github.com/avbel/move2ts.git
cd move2ts
cargo build --release
# Binary at target/release/move2ts
# Single file
move2ts path/to/module.move
# Package directory (with Move.toml)
move2ts path/to/package/
# Custom output directory
move2ts path/to/package/ -o ./src/generatedmove2ts <input> [options]
Arguments:
<input> .move file or package directory
Options:
-o, --output <dir> Output directory (default: ./generated)
--methods <method1,method2> Generate only these methods
--skip-methods <m1,m2> Skip these methods
--singletons <Struct1,Struct2> Manual singleton overrides (struct names)
--package-id-name <ENV_VAR> Override package ID env var name
--events Include event type definitions in output
Generated code reads these environment variables at runtime:
{PROJECT}_PACKAGE_ID-- The on-chain package address (required). Derived from the Move.toml project name in package mode, or from the module name in single-file mode. Override with--package-id-name.{PROJECT}_{STRUCT}_ID-- Singleton object IDs (optional). When a singleton parameter is not explicitly passed, the generated code falls back to the corresponding env var.
| Move Type | TypeScript Type | SDK Encoding |
|---|---|---|
u8 |
number |
tx.pure.u8(v) |
u16 |
number |
tx.pure.u16(v) |
u32 |
number |
tx.pure.u32(v) |
u64 |
bigint |
tx.pure.u64(v) |
u128 |
bigint |
tx.pure.u128(v) |
u256 |
bigint |
tx.pure.u256(v) |
bool |
boolean |
tx.pure.bool(v) |
address |
string |
tx.pure.address(v) |
0x1::string::String |
string |
tx.pure.string(v) |
0x2::object::ID |
string |
tx.pure.id(v) |
vector<u8> |
Uint8Array |
tx.pure('vector<u8>', v) |
vector<T> |
MappedT[] |
tx.pure.vector('innerType', v) |
Option<T> |
MappedT | null |
tx.pure.option('innerType', v) |
VecMap<K, V> |
Map<MappedK, MappedV> |
tx.pure(bcs.map(K, V).serialize(v)) |
struct (copy+drop) |
{ field1: type1, ... } |
tx.pure(bcs.struct(...).serialize(v)) |
Coin<T> / Balance<T> (by ref) |
TransactionObjectInput |
tx.object(v) |
&T / &mut T (object) |
TransactionObjectInput |
tx.object(v) |
Option<T> maps to T | null (not T | undefined) because the SDK's tx.pure.option() uses null for absent values.
Given a Move module with a marketplace, move2ts produces TypeScript like this:
import process from 'node:process';
import type { TransactionObjectInput, TransactionResult } from '@mysten/sui/transactions';
import { Transaction } from '@mysten/sui/transactions';
import { isValidSuiAddress } from '@mysten/sui/utils';
import { InvalidConfigError } from './move2ts-errors';
// Entry function -- singleton resolved lazily
export function listItem(
tx: Transaction,
args: {
price: bigint;
marketplaceId?: TransactionObjectInput;
},
): TransactionResult {
return tx.moveCall({
target: `${getPackageId()}::marketplace::list_item`,
arguments: [
tx.object(args.marketplaceId ?? getMarketplaceId()),
tx.pure.u64(args.price),
],
});
}
// Public function with generics
export function withdraw(
tx: Transaction,
args: {
typeT: string;
poolId: TransactionObjectInput;
amount: bigint;
},
): TransactionResult {
return tx.moveCall({
target: `${getPackageId()}::marketplace::withdraw`,
typeArguments: [args.typeT],
arguments: [
tx.object(args.poolId),
tx.pure.u64(args.amount),
],
});
}
// Function with Clock -- auto-injected
export function getTimedPrice(
tx: Transaction,
args: {
marketplaceId?: TransactionObjectInput;
},
): TransactionResult {
return tx.moveCall({
target: `${getPackageId()}::marketplace::get_timed_price`,
arguments: [
tx.object(args.marketplaceId ?? getMarketplaceId()),
tx.object.clock(),
],
});
}
// --- Internal helpers (not exported) ---
function getPackageId(): string {
const id = process.env.MY_PROJECT_PACKAGE_ID;
if (!id) {
throw new InvalidConfigError('MY_PROJECT_PACKAGE_ID environment variable is not set');
}
if (!isValidSuiAddress(id)) {
throw new InvalidConfigError(`MY_PROJECT_PACKAGE_ID is not a valid Sui address: ${id}`);
}
return id;
}
function getMarketplaceId(): string {
const id = process.env.MY_PROJECT_MARKETPLACE_ID;
if (!id) {
throw new InvalidConfigError('MY_PROJECT_MARKETPLACE_ID environment variable is not set');
}
if (!isValidSuiAddress(id)) {
throw new InvalidConfigError(`MY_PROJECT_MARKETPLACE_ID is not a valid Sui address: ${id}`);
}
return id;
}Key points in the generated code:
- Lazy validation. Package ID and singleton object IDs are read from env vars at call time, not at import time. This prevents import-time crashes in tests and preserves tree-shaking.
- Singleton parameters are optional. If a struct is only ever constructed inside
init(), the corresponding parameter becomes optional with a?suffix. When omitted, the generated code falls back to the env var. - Clock and Random are auto-injected. Move functions that accept
&Clockor&Randomhave those parameters stripped from the TypeScript signature; the generated code passestx.object.clock()ortx.object.random()automatically. - All functions return
TransactionResult. This enables composability -- callers can destructure results and pass them to subsequent transaction commands.
When a function parameter is a pure value struct (has copy and drop abilities but no key), the generated code imports bcs from @mysten/bcs and serializes the struct using BCS encoding instead of tx.object().
A shared move2ts-errors.ts file is also generated with the InvalidConfigError class. Address validation uses isValidSuiAddress from @mysten/sui/utils.
When --events is passed, the tool detects structs emitted via event::emit() and generates export type declarations with all fields as readonly string:
// --- Event Types ---
export type ItemPurchased = {
readonly buyer: string;
readonly seller: string;
readonly price: string;
readonly item_id: string;
};Event detection works by scanning function bodies for event::emit() calls. Only actually emitted structs are included — copy+drop structs that are never emitted are excluded.
If a struct is both emitted AND used as a function parameter, two types are generated: a BCS interface (for the param) and a type with an Event suffix (for event consumption).
# Generate wrappers for your entire DeFi package
move2ts ./contracts/my-dex/ -o ./sdk/src/generated
# Set env vars and use in your app
export MY_DEX_PACKAGE_ID=0xabc123...
export MY_DEX_POOL_ID=0xdef456...import { Transaction } from '@mysten/sui/transactions';
import { swap } from './generated/pool';
const tx = new Transaction();
swap(tx, {
typeX: '0x2::sui::SUI',
typeY: '0xdead::usdc::USDC',
poolId: '0xdef456...',
amountIn: 1000000n,
minOut: 990000n,
});# Generate wrappers for listing operations only
move2ts ./marketplace.move --methods list_item,cancel_listing -o ./sdk
# Skip admin-only functions
move2ts ./marketplace.move --skip-methods admin_withdraw,set_fee -o ./sdkimport { Transaction } from '@mysten/sui/transactions';
import { listItem } from './generated/marketplace';
import { mintNft } from './generated/nft';
const tx = new Transaction();
// Mint an NFT, then list it — results are composable
const [nft] = mintNft(tx, { name: 'Cool NFT' });
listItem(tx, { item: nft, price: 1000000n });
// Sign and execute
await client.signAndExecuteTransaction({ transaction: tx, signer: keypair });# When the automatic singleton detection doesn't work (e.g., helper function creates the object)
move2ts ./contracts/ --singletons Registry,Configmove2ts uses the move-compiler parser crate from the MystenLabs/sui repository (via git dependency) to parse Move source files into an AST. It then:
- Parses
.movefiles usingparse_file_stringfrom the Move compiler, reusing a singleCompilationEnvacross all files for efficiency. - Analyzes the AST to extract function signatures (
entryandpublic), struct definitions, and type information. Singleton detection identifies structs that are only constructed insideinit(). - Generates TypeScript using the
@mysten/suiSDK types. Each Move module becomes a.tsfile with typed wrapper functions that constructmoveCallinvocations with correctly encoded arguments.
The tool operates at the parser level only -- it does not compile or verify Move bytecode.
