Docs

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

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
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

TypeScript
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

MetricValue
Typical Latency15-40ms
Response Size500-5000 bytes
Cache RecommendedYes (long TTL)
Rate Limit ImpactLow

Common Sui Framework Types

TypePackage::ModuleDescription
Coin0x2::coin::CoinGeneric coin type
Object0x2::object::UIDObject identifier
Balance0x2::balance::BalanceGeneric balance type
TxContext0x2::tx_context::TxContextTransaction context

Common Errors

Error CodeScenarioSolution
NOT_FOUNDStruct doesn't existVerify package, module, and struct names
INVALID_ARGUMENTInvalid namesCheck naming format
UNAUTHENTICATEDMissing/invalid tokenVerify x-api-key header

Need help? Contact support@dwellir.com or check the gRPC overview.