GetDatatype - Query Move Struct Definitions
Retrieve Move struct and datatype definitions via gRPC including fields and type parameters. Essential for understanding smart contract data structures with Dwellir.
Query Move Struct and Type Definitions
The GetDatatype method retrieves detailed information about Move structs and datatypes, including their fields, type parameters, and abilities. This is essential for understanding smart contract data structures, building transaction inputs, and parsing event data.
Overview
In Sui's Move programming model, every on-chain object is an instance of a Move struct. The GetDatatype method lets you inspect these struct definitions programmatically, revealing their fields, type parameters, and Move abilities (copy, drop, store, key). This is critical for developers who need to understand unfamiliar smart contracts, build generic transaction constructors, or parse event and object data without hardcoded type knowledge.
Move's ability system is unique to the language: it controls how values can be copied, dropped, stored in global storage, or used as object identifiers. Understanding a struct's abilities tells you exactly what operations are valid for that type. For example, a struct with the key ability can exist as a standalone object on Sui, while store allows it to be nested inside other objects.
Key Capabilities
- Struct Inspection: Retrieve field names, types, and positions for any deployed struct
- Ability Analysis: Check whether a type can be copied, dropped, stored, or used as an object
- Generic Type Support: Inspect type parameters and their constraints (phantom types, ability bounds)
- Enum Support: Query enum variants and their associated data fields
- Event Parsing: Use struct definitions to deserialize event data at runtime
- Schema Generation: Automatically generate TypeScript interfaces or Python dataclasses from Move types
Method Signature
Service: sui.rpc.v2.MovePackageService
Method: GetDatatype
Type: Unary RPC
Use Cases
Generate TypeScript Interface
async function generateTypeScriptInterface(
packageId: string,
moduleName: string,
datatypeName: string
): Promise<string> {
const struct = await getDatatype(packageId, moduleName, datatypeName);
const typeParams = struct.type_parameters?.length > 0
? `<${struct.type_parameters.map((t: any) => t.name).join(', ')}>`
: '';
const fields = struct.fields
.map((field: any) => ` ${field.name}: ${mapMoveTypeToTS(field.type)};`)
.join('\n');
return `
interface ${struct.name}${typeParams} {
${fields}
}
`;
}
function mapMoveTypeToTS(moveType: string): string {
// Simplified mapping
if (moveType.includes('u64') || moveType.includes('u128')) return 'bigint';
if (moveType.includes('u8') || moveType.includes('u16') || moveType.includes('u32')) return 'number';
if (moveType.includes('bool')) return 'boolean';
if (moveType.includes('address')) return 'string';
if (moveType.includes('vector')) return 'any[]';
return 'any';
}
// Usage
const tsInterface = await generateTypeScriptInterface('0x2', 'coin', 'Coin');
console.log(tsInterface);Validate Object Structure
interface ValidationResult {
isValid: boolean;
missingFields: string[];
extraFields: string[];
}
async function validateObjectStructure(
packageId: string,
moduleName: string,
datatypeName: string,
objectData: any
): Promise<ValidationResult> {
const struct = await getDatatype(packageId, moduleName, datatypeName);
const expectedFields = new Set(struct.fields.map((f: any) => f.name));
const actualFields = new Set(Object.keys(objectData));
const missingFields = Array.from(expectedFields).filter(f => !actualFields.has(f));
const extraFields = Array.from(actualFields).filter(f => !expectedFields.has(f));
return {
isValid: missingFields.length === 0 && extraFields.length === 0,
missingFields,
extraFields
};
}
// Usage
const validation = await validateObjectStructure(
'0x2',
'coin',
'Coin',
{ id: '0x...', balance: '1000', value: '1000' }
);
if (!validation.isValid) {
console.error('Invalid structure:');
if (validation.missingFields.length > 0) {
console.error(' Missing:', validation.missingFields);
}
if (validation.extraFields.length > 0) {
console.error(' Extra:', validation.extraFields);
}
}Build Struct Documentation
interface StructDocs {
name: string;
fullName: string;
abilities: string[];
typeParameters: string[];
fields: Array<{
name: string;
type: string;
}>;
description: string;
}
async function generateStructDocs(
packageId: string,
moduleName: string,
datatypeName: string
): Promise<StructDocs> {
const struct = await getDatatype(packageId, moduleName, datatypeName);
return {
name: struct.name,
fullName: `${packageId}::${moduleName}::${struct.name}`,
abilities: struct.abilities || [],
typeParameters: struct.type_parameters?.map((t: any) => t.name) || [],
fields: struct.fields.map((f: any) => ({
name: f.name,
type: f.type.toString()
})),
description: `Struct defined in ${moduleName} module`
};
}
// Usage
const docs = await generateStructDocs('0x2', 'coin', 'Coin');
console.log(`# ${docs.name}\n`);
console.log(`**Full Type:** \`${docs.fullName}\`\n`);
console.log(`**Abilities:** ${docs.abilities.join(', ')}\n`);
if (docs.typeParameters.length > 0) {
console.log(`**Type Parameters:** ${docs.typeParameters.join(', ')}\n`);
}
console.log(`## Fields\n`);
docs.fields.forEach(field => {
console.log(`- \`${field.name}\`: ${field.type}`);
});Parse Event Data
async function parseEventData(
eventType: string,
eventData: any
): Promise<any> {
// Parse type string: package::module::Type
const [packageId, moduleName, datatypeName] = eventType.split('::');
const struct = await getDatatype(packageId, moduleName, datatypeName);
const parsed: any = {};
struct.fields.forEach((field: any, index: number) => {
const value = eventData[field.name] || eventData[index];
parsed[field.name] = value;
});
return parsed;
}
// Usage
const event = {
type: '0x2::coin::DepositEvent',
data: { amount: '1000000', sender: '0x...' }
};
const parsedEvent = await parseEventData(event.type, event.data);
console.log('Parsed event:', parsedEvent);Check Struct Abilities
async function checkAbilities(
packageId: string,
moduleName: string,
datatypeName: string
): Promise<{
canCopy: boolean;
canDrop: boolean;
canStore: boolean;
hasKey: boolean;
}> {
const struct = await getDatatype(packageId, moduleName, datatypeName);
const abilities = struct.abilities || [];
return {
canCopy: abilities.includes('COPY'),
canDrop: abilities.includes('DROP'),
canStore: abilities.includes('STORE'),
hasKey: abilities.includes('KEY')
};
}
// Usage
const abilities = await checkAbilities('0x2', 'coin', 'Coin');
console.log('Coin abilities:');
console.log(' Can copy:', abilities.canCopy);
console.log(' Can drop:', abilities.canDrop);
console.log(' Can store:', abilities.canStore);
console.log(' Has key:', abilities.hasKey);Build Object Schema
interface ObjectSchema {
type: string;
fields: Map<string, {
type: string;
required: boolean;
}>;
}
async function buildObjectSchema(
packageId: string,
moduleName: string,
datatypeName: string
): Promise<ObjectSchema> {
const struct = await getDatatype(packageId, moduleName, datatypeName);
const fields = new Map();
struct.fields.forEach((field: any) => {
fields.set(field.name, {
type: field.type.toString(),
required: true // All fields are required in Move structs
});
});
return {
type: `${packageId}::${moduleName}::${struct.name}`,
fields
};
}
// Usage
const schema = await buildObjectSchema('0x2', 'coin', 'Coin');
console.log('Schema for', schema.type);
for (const [fieldName, fieldInfo] of schema.fields) {
console.log(` ${fieldName}: ${fieldInfo.type} (required: ${fieldInfo.required})`);
}Best Practices
Cache Struct Definitions
class DatatypeCache {
private cache = new Map<string, { data: any; timestamp: number }>();
private ttl = 3600000; // 1 hour
async getDatatype(
packageId: string,
moduleName: string,
datatypeName: string
): Promise<any> {
const key = `${packageId}::${moduleName}::${datatypeName}`;
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
const datatype = await getDatatype(packageId, moduleName, datatypeName);
this.cache.set(key, {
data: datatype,
timestamp: Date.now()
});
return datatype;
}
}Handle Generic Types
async function getGenericStructInfo(
packageId: string,
moduleName: string,
datatypeName: string
): Promise<{
name: string;
isGeneric: boolean;
typeParameters: string[];
}> {
const struct = await getDatatype(packageId, moduleName, datatypeName);
return {
name: struct.name,
isGeneric: struct.type_parameters && struct.type_parameters.length > 0,
typeParameters: struct.type_parameters?.map((t: any) => t.name) || []
};
}Performance Characteristics
| Metric | Value |
|---|---|
| Typical Latency | 15-40ms |
| Response Size | 500-5000 bytes |
| Cache Recommended | Yes (long TTL) |
| Rate Limit Impact | Low |
Common Sui Framework Types
| Type | Package::Module | Description |
|---|---|---|
| Coin | 0x2::coin::Coin | Generic coin type |
| Object | 0x2::object::UID | Object identifier |
| Balance | 0x2::balance::Balance | Generic balance type |
| TxContext | 0x2::tx_context::TxContext | Transaction context |
Common Errors
| Error Code | Scenario | Solution |
|---|---|---|
NOT_FOUND | Struct doesn't exist | Verify package, module, and struct names |
INVALID_ARGUMENT | Invalid names | Check naming format |
UNAUTHENTICATED | Missing/invalid token | Verify x-api-key header |
Related Methods
- GetPackage - Query entire package
- GetFunction - Query function signatures
Need help? Contact support@dwellir.com or check the gRPC overview.
GetTransaction
Retrieve comprehensive Sui transaction details via gRPC including effects, events, and state changes. Perfect for transaction verification and blockchain analytics with Dwellir.
GetFunction
Retrieve detailed Move function information via gRPC including parameters, return types, and visibility. Essential for smart contract integration with Dwellir.