Telescope
Manually Registering Types

Manually Registering Types

This document provides a reference for manually registering types in Telescope-generated code.

Overview

When working with Cosmos SDK applications, sometimes you need to extend Telescope's generated types with custom or third-party types. Manual type registration allows you to integrate these external types with Telescope's registry system.

Why Register Types Manually?

Manual type registration is necessary when:

  1. Using types from third-party libraries not generated by Telescope
  2. Working with custom types defined outside of Protocol Buffers
  3. Integrating with chains or modules that have specialized type requirements
  4. Creating hybrid applications with some manually defined types

Type Registry Basics

Telescope generates registries for each message type:

// Example of a generated registry
export const registry = [
  ["/cosmos.bank.v1beta1.MsgSend", MsgSend],
  ["/cosmos.staking.v1beta1.MsgDelegate", MsgDelegate],
  // other message types...
];

Each entry pairs a type URL with its corresponding TypeScript implementation.

Manual Registration Methods

Method 1: Adding to Default Registry

import { registry } from "./codegen/cosmos/bank/v1beta1/tx";
import { GeneratedType } from "@cosmjs/proto-signing";
import { MyCustomType } from "./my-custom-types";
 
// Add your custom type to the registry
const customRegistry: Array<[string, GeneratedType]> = [
  ...registry,
  ["/my.custom.v1beta1.MyCustomType", MyCustomType]
];
 
// Use the extended registry when creating a client
const client = await SigningStargateClient.connectWithSigner(
  rpcEndpoint,
  signer,
  { registry: customRegistry }
);

Method 2: Using Registry Class

import { Registry } from "@cosmjs/proto-signing";
import { defaultRegistryTypes } from "@cosmjs/stargate";
import { MyCustomType } from "./my-custom-types";
 
// Create a new registry with default types
const registry = new Registry(defaultRegistryTypes);
 
// Register your custom types
registry.register("/my.custom.v1beta1.MyCustomType", MyCustomType);
 
// Use the registry
const client = await SigningStargateClient.connectWithSigner(
  rpcEndpoint,
  signer,
  { registry }
);

Method 3: Creating Registration Functions

import { Registry } from "@cosmjs/proto-signing";
import { MsgCustom } from "./my-module/types";
 
// Create a registration function
export function registerMyModuleTypes(registry: Registry): void {
  registry.register("/my.module.v1beta1.MsgCustom", MsgCustom);
  // Register additional types here
}
 
// Usage
const registry = new Registry(defaultRegistryTypes);
registerMyModuleTypes(registry);

Implementation Requirements

For a type to be registrable, it must implement the GeneratedType interface:

interface GeneratedType {
  readonly typeUrl: string;
  readonly encode: (message: any) => Uint8Array;
  readonly decode: (binary: Uint8Array) => any;
}

When creating custom types, ensure they have appropriate encoding and decoding functions.

Custom Type Example

import { Writer, Reader } from "protobufjs/minimal";
 
// Define your custom type interface
export interface CustomMessage {
  field1: string;
  field2: number;
}
 
// Implement the required methods
export const CustomMessage = {
  typeUrl: "/my.custom.v1beta1.CustomMessage",
  
  encode(message: CustomMessage, writer: Writer = Writer.create()): Writer {
    if (message.field1 !== "") {
      writer.uint32(10).string(message.field1);
    }
    if (message.field2 !== 0) {
      writer.uint32(16).int32(message.field2);
    }
    return writer;
  },
  
  decode(input: Reader | Uint8Array, length?: number): CustomMessage {
    const reader = input instanceof Reader ? input : new Reader(input);
    let end = length === undefined ? reader.len : reader.pos + length;
    const message = { field1: "", field2: 0 } as CustomMessage;
    
    while (reader.pos < end) {
      const tag = reader.uint32();
      switch (tag >>> 3) {
        case 1:
          message.field1 = reader.string();
          break;
        case 2:
          message.field2 = reader.int32();
          break;
        default:
          reader.skipType(tag & 7);
          break;
      }
    }
    
    return message;
  },
  
  fromPartial(object: any): CustomMessage {
    const message = { field1: "", field2: 0 } as CustomMessage;
    if (object.field1 !== undefined && object.field1 !== null) {
      message.field1 = object.field1;
    }
    if (object.field2 !== undefined && object.field2 !== null) {
      message.field2 = object.field2;
    }
    return message;
  }
};

Amino Type Registration

For chains that use Amino (legacy encoding), you'll also need to register Amino converters:

import { AminoTypes } from "@cosmjs/stargate";
import { AminoMsg } from "@cosmjs/amino";
 
// Define the Amino interface
interface AminoCustomMessage extends AminoMsg {
  type: "my-module/CustomMessage";
  value: {
    field1: string;
    field2: string; // Note: numbers are strings in Amino
  };
}
 
// Create Amino converters
const aminoConverters = {
  "/my.custom.v1beta1.CustomMessage": {
    aminoType: "my-module/CustomMessage",
    toAmino: ({ field1, field2 }: CustomMessage): AminoCustomMessage["value"] => ({
      field1,
      field2: field2.toString(),
    }),
    fromAmino: ({ field1, field2 }: AminoCustomMessage["value"]): CustomMessage => ({
      field1,
      field2: parseInt(field2, 10),
    }),
  },
};
 
// Register with AminoTypes
const aminoTypes = new AminoTypes(aminoConverters);

Global Decoder Registry

Telescope v1.0+ includes a global decoder registry system:

import { GlobalDecoderRegistry } from "./codegen/registry";
import { CustomMessage } from "./my-custom-types";
 
// Register a custom type
GlobalDecoderRegistry.register(CustomMessage.typeUrl, CustomMessage);
 
// Later, decode a message of any registered type
const decodedMessage = GlobalDecoderRegistry.decode(binaryData);

Using Custom Types with Telescope Clients

import { getSigningCosmosClient } from "./codegen/client";
import { CustomMessage } from "./my-custom-types";
import { Registry } from "@cosmjs/proto-signing";
 
// Create a registry with custom types
const registry = new Registry();
registry.register(CustomMessage.typeUrl, CustomMessage);
 
// Create the signing client
const client = await getSigningCosmosClient({
  rpcEndpoint,
  signer, 
  registry
});
 
// Now you can use custom message types
const customMsg = {
  typeUrl: CustomMessage.typeUrl,
  value: CustomMessage.fromPartial({
    field1: "value",
    field2: 42
  })
};
 
// Use in transactions
const result = await client.signAndBroadcast(
  signerAddress,
  [customMsg],
  fee
);

Best Practices

  1. Organize custom types in dedicated modules for better maintainability
  2. Follow the same patterns as Telescope-generated types for consistency
  3. Include proper TypeScript types for better IDE support
  4. Implement all required methods (encode, decode, fromPartial)
  5. Register both Proto and Amino types if working with wallets that use Amino
  6. Use meaningful type URLs that follow the pattern /namespace.module.version.Type
  7. Cache registries where possible to avoid repeated registrations