Add modules#

When a new module is added to Terra core, you must add it to several places in Terra.js too. To add it, complete the following steps:

Create a new folder#

In the src/core folder, create a new folder and name it after the new module. For example,src/core/greeting.

Add messages#

  1. Register new messages in a subdirectory in the folder that you created for your new module, as shown in the following example:

src/core/greeting/msgs

For this example, let’s assume that you are creating a two new messages, MsgHello and MsgGoodbye. The following example shows the code for MsgHello, and you can extrapolate from it the way to implement MsgGoodbye.

src/core/greeting/msgs/MsgHello.ts

import { JSONSerializable } from '../../../util/json';
import { AccAddress } from '../../strings'

/**
 * Just a simple greeting on the blockchain.
 */
export class MsgHello extends JSONSerializable<MsgHello.Data> {
  constructor(
    public recipient: AccAddress
  ) {
    super();
  }

  public static fromData(data: MsgHello.Data): MsgHello {
    const {
      value: { recipient }
    } = data;
    return new MsgHello(recipient);
  }

  public toData(): MsgHello.Data {
    const { recipient } = this;
    return {
      type: 'greeting/MsgHello',
      value: {
        recipient
      },
    };
  }
}

export namespace MsgHello {
  export interface Data {
    type: 'greeting/MsgHello';
    value: {
      recipient: AccAddress
    };
  }
}
  1. Create the following file, which will index your new messages.

src/core/greeting/msgs/index.ts

import { MsgHello } from './MsgHello';
import { MsgGoodbye } from './MsgGoodbye';

export * from './MsgHello';
export * from './MsgGoodbye';

export type GreetingMsg = MsgHello | MsgGoodbye;

export namespace GreetingMsg {
  export type Data = MsgHello.Data | MsgGoodbye.Data;
}
  1. Register the messages in src/core/Msg.ts so that they can be parsed correctly.

src/core/Msg.ts

// import greeting module messages
...
import {
  MsgHello,
  MsgGoodbye,
  GreetingMsg
} from './greeting/msgs';
...

// register GreetingMsg
export type Msg =
  | BankMsg
  | DistributionMsg
  | GovMsg
  | GreetingMsg // ADD HERE
  | MarketMsg
  | MsgAuthMsg
  | OracleMsg
  | SlashingMsg
  | StakingMsg
  | WasmMsg;

...

// register GreetingMsg.Data
export namespace Msg {
  export type Data =
    | BankMsg.Data
    | DistributionMsg.Data
    | GovMsg.Data
    | Greeting.Data // ADD HERE
    | MarketMsg.Data
    | MsgAuthMsg.Data
    | OracleMsg.Data
    | SlashingMsg.Data
    | StakingMsg.Data
    | WasmMsg.Data;
...

  // register deserializer in Msg.fromData(...)
  export function fromData(data: Msg.Data): Msg {
  ...
      // greeting
      case 'greeting/MsgHello':
        return MsgHello.fromData(data);
      case 'greeting/MsgGoodbye':
        return MsgGoodbye.fromData(data);
  ...
  }
}
  1. Register the messages to be exported in src/core/index.ts:

...
// greeting
export 'greeting/msgs';
  1. Add parameter changes.

Terra.js provides an easy way to generate ParameterChangeProposals, which is a proposal for changing the blockchain parameters associated with a module. If your module has parameters that can be changed via proposal, you should create the following files:

src/core/greeting/params.ts

import { ParamChange } from '..';
import { Convert } from '../../util/convert';

type MaxHellos = ParamChange.Type<
  'greeting',
  'maxhellos',
  number
>;

type MaxGoodbyes = ParamChange.Type<
  'greeting',
  'maxgoodbyes',
  number
>;

export type GreetingParamChange =
  | MaxHellos
  | MaxGoodbyes;

export namespace GreetingParamChange {
  export type Data =
    | ParamChange.Data.Type<MaxHellos>
    | ParamChange.Data.Type<MaxGoodbyes>
}

export interface GreetingParamChanges {
  greeting?: {
    maxhellos?: number;
    maxgoodbyes?: number;
  };
}

export namespace GreetingParamChanges {
  export const ConversionTable = {
    greeting: {
      maxhellos: [Convert.toNumber, Convert.toFixed],
      maxgoodbyes: [Convert.toNumber, Convert.toFixed],
    },
  };
}
  1. Register parameter change types

src/core/params/ParamChange.ts

...
import { GreetingParamChange, GreetingParamChanges } from '../greeting/params';
...

export type ParamChanges = DistributionParamChanges &
  GovParamChanges &
  GreetingParamChanges & // ADD HERE
  MarketParamChanges &
  OracleParamChanges &
  SlashingParamChanges &
  StakingParamChanges &
  TreasuryParamChanges &
  WasmParamChanges;

export namespace ParamChanges {
  export const ConversionTable = {
    ...DistributionParamChanges.ConversionTable,
    ...GovParamChanges.ConversionTable,
    ...GreetingParamChanges.ConverstionTable, // ADD HERE
    ...MarketParamChanges.ConversionTable,
    ...OracleParamChanges.ConversionTable,
    ...SlashingParamChanges.ConversionTable,
    ...StakingParamChanges.ConversionTable,
    ...TreasuryParamChanges.ConversionTable,
    ...WasmParamChanges.ConversionTable,
  };

...

export type ParamChange =
  | DistributionParamChange
  | GovParamChange
  | GreetingParamChange // ADD HERE
  | MarketParamChange
  | OracleParamChange
  | SlashingParamChange
  | StakingParamChange
  | TreasuryParamChange
  | WasmParamChange;

...

Add API functionality to the LCDClient#

If there are API endpoints that exist for the new module, you will need to add this functionality to LCDClient so that they are accessible.

Assume that the greeting module has the following endpoints:

  • GET /greeting/hello/{accAddress}

  • GET /greeting/parameters

  1. Create src/client/lcd/api/GreetingAPI.ts with the following:

import { BaseAPI } from "./BaseAPI";
import { AccAddress } from "../../../core/strings";

export interface GreetingParams {
  max_hellos: number;
  max_goodbyes: number;
}

export namespace GreetingParams {
  export interface Data {
    max_hellos: string;
    max_goodbyes: string;
  }
}

export class GreetingAPI extends BaseAPI {
  public async hello(accAddress: AccAddress): Promise<AccAddress[]> {
    return this.c
      .get<AccAddress[]>(`/greeting/hello/${accAddress}`)
      .then(d => d.result);
  }

  public async parameters(): Promise<GreetingParams> {
    return this.c
      .get<GreetingParams.Data>(`/greeting/parameters`)
      .then(d => d.result)
      .then(d => ({
        max_hellos: Number.parseInt(d.max_hellos),
        max_goodbyes: Number.parseInt(d.max_goodbyes)
      }));
  }
}
  1. Register the API functionality inside src/client/lcd/api/index.ts:

export * from './AuthAPI';
export * from './BankAPI';
export * from './DistributionAPI';
export * from './GovAPI';
export * from './GreetingAPI'; // ADD HERE
export * from './MarketAPI';
export * from './MsgAuthAPI';
export * from './OracleAPI';
export * from './SlashingAPI';
export * from './StakingAPI';
export * from './SupplyAPI';
export * from './TendermintAPI';
export * from './TreasuryAPI';
export * from './TxAPI';
export * from './WasmAPI';
  1. Add the functionality to src/client/lcd/LCDClient.ts:

...
import {
  AuthAPI,
  BankAPI,
  DistributionAPI,
  GovAPI,
  GreetingAPI, // ADD HERE
  MarketAPI,
  MsgAuthAPI,
  OracleAPI,
  SlashingAPI,
  StakingAPI,
  SupplyAPI,
  TendermintAPI,
  TreasuryAPI,
  TxAPI,
  WasmAPI,
} from './api';
...


export class LCDClient {
  public config: LCDClientConfig;
  public apiRequester: APIRequester;

  // API access
  public auth: AuthAPI;
  public bank: BankAPI;
  public distribution: DistributionAPI;
  public gov: GovAPI;
  public greeting: GreetingAPI; // ADD HERE
  public market: MarketAPI;
  public msgauth: MsgAuthAPI;
  public oracle: OracleAPI;
  public slashing: SlashingAPI;
  public staking: StakingAPI;
  public supply: SupplyAPI;
  public tendermint: TendermintAPI;
  public treasury: TreasuryAPI;
  public wasm: WasmAPI;
  public tx: TxAPI;

  /**
   * Creates a new LCD client with the specified configuration.
   *
   * @param config LCD configuration
   */
  constructor(config: LCDClientConfig) {
    this.config = {
      ...DEFAULT_LCD_OPTIONS,
      ...config,
    };

    this.apiRequester = new APIRequester(this.config.URL);

    // instantiate APIs
    this.auth = new AuthAPI(this.apiRequester);
    this.bank = new BankAPI(this.apiRequester);
    this.distribution = new DistributionAPI(this.apiRequester);
    this.gov = new GovAPI(this.apiRequester);
    this.greeting = new GreetingAPI(this.apiRequester); // ADD HERE
    this.market = new MarketAPI(this.apiRequester);
    this.msgauth = new MsgAuthAPI(this.apiRequester);
    this.oracle = new OracleAPI(this.apiRequester);
    this.slashing = new SlashingAPI(this.apiRequester);
    this.staking = new StakingAPI(this.apiRequester);
    this.supply = new SupplyAPI(this.apiRequester);
    this.tendermint = new TendermintAPI(this.apiRequester);
    this.treasury = new TreasuryAPI(this.apiRequester);
    this.wasm = new WasmAPI(this.apiRequester);
    this.tx = new TxAPI(this);
  }