Home

Awesome

Telescope πŸ”­

<p align="center"> <img width="280" src="https://user-images.githubusercontent.com/545047/175660665-5cbde84b-0928-4e59-ab56-be6adb2f3a7e.png"/> </p> <p align="center" width="100%"> <a href="https://github.com/cosmology-tech/telescope/actions/workflows/run-tests.yaml"> <img height="20" src="https://github.com/cosmology-tech/telescope/actions/workflows/run-tests.yaml/badge.svg" /> </a> <a href="https://github.com/cosmology-tech/lib-count"> <img height="20" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fcosmology-tech%2Flib-count%2Fmain%2Foutput%2Fbadges%2Fproducts%2Ftelescope%2Ftotal.json"/> </a> <a href="https://github.com/cosmology-tech/lib-count"> <img height="20" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fcosmology-tech%2Flib-count%2Fmain%2Foutput%2Fbadges%2Fproducts%2Ftelescope%2Fmonthly.json"/> </a> <br /> <a href="https://github.com/cosmology-tech/telescope/blob/main/LICENSE-MIT"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a> <a href="https://github.com/cosmology-tech/telescope/blob/main/LICENSE-Apache"><img height="20" src="https://img.shields.io/badge/license-Apache-blue.svg"/></a> <a href="https://www.npmjs.com/package/@cosmology/telescope"><img height="20" src="https://img.shields.io/github/package-json/v/cosmology-tech/telescope?filename=packages%2Ftelescope%2Fpackage.json"/></a> </p> <p align="center"> <img src="https://user-images.githubusercontent.com/545047/178129981-5a7d18ed-d2b5-4d85-b828-ca084d38501e.png"/> </p>

Telescope serves as the TypeScript Companion for CosmJS, used to generate developer-friendly TypeScript libraries for Cosmos blockchains. As a TypeScript transpiler for Cosmos Protobufs, akin to a "babel for the Cosmos", simply point to your protobuffer files and create full-typed libraries for teams to build dApps on your blockchain.

The following blockchain libraries (generated by Telescope) are available via npm

πŸŽ₯ Checkout our video course to learn how to use telescope!

Table of contents

Quickstart

Follow the instructions below to generate a new Typescript package that you can publish to npm.

First, install telescope and create-cosmos-app

npm install -g @cosmology/telescope create-cosmos-app

Generate

Use the create-cosmos-app command to create a new package from the telescope boilerplate.

cca --boilerplate telescope

Then, you'll navigate into ./your-project/packages/telescope package for the next steps.

You can also use telescope generate command to generate package according to the prompt or terminal command params such as: telescope generate --access public --userfullname testname --useremail test@gmail.com --module-desc test --username salkfl --license MIT --module-name test --chain-name cosmos --use-npm-scoped

The available options are: --userfullname --useremail --module-desc --username --module-name --chain-name --access --use-npm-scoped --license

If some required options are missing, it will prompt to ask for the info.

To be noted, --use-npm-scoped only works when --access is public

Download protos with CLI

The old telescope install command has been deprecated

You can use our CLI to download protos by using download command.

telescope download

You should now see some repos cloned in ./git-modules and proto files generated in ./protos. These are the proto files downloaded according to your config.

Examples:

# Telescope will do the download according to .json file of --config
# Telescope will put proto into location specified by --out 
telescope download --config ./protod.config.json --out ./git-modules
# Telescope download from target repo according to --git-repo
# in format of (i.e. <owner>/<repository> or <owner>/<repository>/<branch>)
# <branch> can be empty, it will use main as default
# Also --targets is required to specify the targets to download 
# in format like cosmos/auth/v1beta1/auth.proto
telescope download --git-repo target-repo --targets target-proto
# ssh arg is optional, default is false
telescope download --config ./protod.config.json --out ./git-modules --ssh true
// .protod.config.json example
//
// `repos` are the repository it's going to clone
// in format of (i.e. { "owner": <owner>, "repo": <repo> } or { "owner": <owner>, "repo": <repo>, "branch": <branch> })
// <branch> can be empty, it will use main as default
//
// `protoDirMapping` is the directory of repo specified if the proto is not under repo's root directory ./protos
// in format of (i.e. <owner>/<repository> or <owner>/<repository>/<branch>)
// <branch> can be empty, it will use main as default
//
// `outDir` is where the output proto will be put
//
// `targets` are the target proto to download 
// `targets` can be patterns like:
// "cosmos/bank/v1beta1/tx.proto",
// "cosmos/gov/**/*.proto",
// "cosmos/authz/**/*.proto",
{
  "repos": [
    { "owner": "cosmos", "repo": "cosmos-sdk" },
    ...
  ],
  "protoDirMapping": {
    "gogo/protobuf/master": ".",
    ...
  },
  "outDir": "protos",
  "ssh": true,
  "tempRepoDir": "git-modules",
  "targets": [
    "cosmos/auth/v1beta1/auth.proto",
    ...
  ]
}

Transpile

To create the Typescript files for your chain, run the yarn codegen command inside of the package.

yarn codegen

Transpile with CLI

Less recommended, but you can also use our CLI for transpilation. To create the Typescript files with the cli, run the transpile command.

telescope transpile

You should now see some .ts files generated in ./src. These are the real source files used in your application.

Examples:

# Telescope takes chain1 folder as input,
# and generate files in 'gen/src' folder.
telescope transpile --protoDirs ../../__fixtures__/chain1 --outPath gen/src
# Telescope takes chain1 folder as input,
# and generate files in 'gen/src' folder using default telescope options.
telescope transpile --protoDirs ../../__fixtures__/chain1 --outPath gen/src --useDefaults
# Telescope takes chain1 folder(from args) and chain2 folder(from config) as input,
# and generate files in 'gen/src'(defined in the config file, will override outPath in args) folder using a config file.
# Note: --config will override --useDefaults.
telescope transpile --protoDirs ../../__fixtures__/chain1 --config .telescope.json
# Telescope takes more than one config. The config afterward will override those in front. In this case values in .telescope-ext.json will override those in .telescope.json.
telescope transpile --config .telescope.json --config .telescope-ext.json
//.telescope.json
{
  "protoDirs": [
    "../../fixtures/chain2"
  ],
  "outPath": "gen/src",
  "options": {
    // telescope options
    ...
  }
}

Build

Finally, run install and build to generate the JS and types for publishing your module to npm.

yarn build

Publishing

Now you should have code inside of your ./src folder, ready for publshing. If you used the create-cosmos-app boilerplate, use lerna to publish (and/or read the README in the boilerplate for instructions), or run npm publish from your repository.

Usage

Programatic Usage

First add telescope to your devDependencies:

yarn add --dev @cosmology/telescope

Install helpers and cosmjs dependencies listed here

import { join } from 'path';
import telescope from '@cosmology/telescope';
import { sync as rimraf } from 'rimraf';

const protoDirs = [join(__dirname, '/../proto')];
const outPath = join(__dirname, '../src');
rimraf(outPath);

telescope({
  protoDirs,
  outPath,

  // all options are totally optional ;)
  options: {
    aminoEncoding: {
        enabled: true
    },
    lcdClients: {
        enabled: false
    },
    rpcClients: {
        enabled: false,
        camelCase: true
    },

    // you can scope options to certain packages:
    packages: {
      nebula: {
        prototypes: {
          typingsFormat: {
            useExact: false
          }
        }
      },
      akash: {
        stargateClients: {
            enabled: true,
            includeCosmosDefaultTypes: false
        },
        prototypes: {
          typingsFormat: {
              useExact: false
          }
        }
      }
    }
  }
}).then(() => {
  console.log('✨ all done!');
}).catch(e => {
  console.error(e);
  process.exit(1);
})

Options

Amino Encoding

optiondescriptiondefaults
aminoEncoding.enabledgenerate amino types and amino converterstrue
aminoEncoding.omitEmptyTagsAn array of strings that determines whether a field should be omitted when serialized to JSON. If the array includes "omitempty", any field with the "omitempty" option in either gogoproto.jsontag or cosmos_proto.json_tag will be omitted. If the array includes "dont_omitempty", the field will be omitted or not based on the value of "(amino.dont_omitempty)": if it's null or false, the field will be omitted; if it's true, the field will not be omitted.["omitempty", "dont_omitempty"]
aminoEncoding.disableMsgTypesdisable generating AminoMsg typesfalse
aminoEncoding.casingFnset the amino-casing function for a projectsnake()
aminoEncoding.exceptionsset specific aminoType name exceptionssee code
aminoEncoding.typeUrlToAminocreate functions for aminoType name exceptionsundefined
aminoEncoding.useLegacyInlineEncoding@deprecated. To use legacy inline encoding instead of using v2 recursive encodingfalse
aminoEncoding.useRecursiveV2encodingthis's been removed. See useLegacyInlineEncoding instead.
aminoEncoding.legacy.useNullHandlinghandle null case when generating legacy amino converters(those in tx.amino.ts)
aminoEncoding.legacy.useOmitEmptyhandle omit empty or not when generating legacy amino converters(those in tx.amino.ts)

Implemented Interface Options

optiondescriptiondefaults
interfaces.enabledenables converters convert between Any type and specific implemented interfaces.true
interfaces.useGlobalDecoderRegistryenables GlobalDecoderRegistry and related functions. Highly recommended to enable when dealing with fields with 'accepted_interface' option. Please see 'packages/telescope/tests/impl-interfaces.test.ts' for usage.false
interfaces.useUseInterfacesParamsdecides if add useInterfaces argument to decode and toAmino functions.false
interfaces.useByDefaultdecides if interface decoders are used by default (default for useInterfaces argument to decode and toAmino functions).true
interfaces.useByDefaultRpcdecides if interface decoders are used by default by the RPC clients.true
interfaces.useUnionTypesGenerate Any type as union types(TextProposal | RegisterIncentiveProposal) instead of intersection types(TextProposal & RegisterIncentiveProposal).false

Prototypes Options

optiondescriptiondefaults
prototypes.enabledenables the generation of proto encoding methodstrue
prototypes.includePackageVarexport a protoPackage variable to indicate package namefalse
prototypes.includes.packagesinclude a set of packages when transpilation. (if a package both meet include and exclude, it'll be excluded)undefined
prototypes.includes.protosinclude a set of proto files when transpilation. (if a proto both meet include and exclude, it'll be excluded)undefined
prototypes.excluded.packagesexclude a set of packages from transpilationundefined
prototypes.excluded.protostry to exclude a set of proto files from transpilation. if files inside the list are dependencies to other files, they'll be still transpiled.undefined
prototypes.excluded.hardProtosexclude a set of proto files from transpilation. Files in this list will be excluded no matter it is a dependency to other files or not.undefined
prototypes.fieldDefaultIsOptionalboolean value representing default optionality of fieldfalse
prototypes.useOptionalNullableuse (gogoproto.nullable) values in determining optionalitytrue
prototypes.allowUndefinedTypesboolean value allowing Types to be undefinedfalse
prototypes.allowEncodeDefaultScalarsboolean value allowing encoders encoding default values of scalar types. e.g. empty string, 0 or falsefalse
prototypes.isScalarDefaultToNullableDetermines whether scalar types are nullable by default when gogoproto.nullable is not specified. If set to true, scalar types will be nullable; if false, they will be requiredfalse
prototypes.enforceNullCheckDetermines whether to enforce checking required scalar fields not null or undefined during encodingfalse
prototypes.optionalQueryParamsboolean value setting queryParams to be optionalfalse
prototypes.optionalPageRequestsboolean value setting PageRequest fields to optionalfalse
prototypes.addTypeUrlToDecodersadd $typeUrl field to generated interfacestrue
prototypes.addAminoTypeToObjectsadd aminoType field to generated Decodersfalse
prototypes.addTypeUrlToObjectsadd typeUrl field to generated Decoderstrue
prototypes.enableRegistryLoadergenerate Registry loader to *.registry.ts filestrue
prototypes.enableMessageComposergenerate MessageComposer to *.registry.ts filestrue
prototypes.patchAn object mapping filenames to an array of Operation to be applied as patches to proto files during generation. See JSON Patch Protosundefined

Prototypes Methods

optiondescriptiondefaults
prototypes.methods.encodeboolean to enable encode method on proto objectstrue
prototypes.methods.decodeboolean to enable decode method on proto objectstrue
prototypes.methods.fromJSONboolean to enable fromJSON method on proto objectstrue
prototypes.methods.toJSONboolean to enable toJSON method on proto objectstrue
prototypes.methods.fromPartialboolean to enable fromPartial method on proto objectstrue
prototypes.methods.fromSDKboolean to enable fromSDK method on proto objectsfalse
prototypes.methods.toSDKboolean to enable toSDK method on proto objectsfalse

Enums Options

optiondescriptiondefaults
enums.useCustomNamesEnables the usage of custom names for enums if specified through proto options or annotations, allowing for more descriptive or project-specific naming conventions.false

LCD Client Options

optiondescriptiondefaults
lcdClients.enabledgenerate LCD clients that can query proto Query messagestrue
lcdClients.bundlewill generate factory bundle aggregate of all LCD Clientstrue
lcdClients.scopedwill generate factory of scoped LCD Clientsundefined
lcdClients.scopedIsExclusivewill allow both scoped bundles and all RPC Clientstrue

See LCD Clients for more info.

RPC Client Options

optiondescriptiondefaults
rpcClients.typewill generate this type of RPC client (tendermint, gRPC-web, gRPC)tendermint
rpcClients.enabledgenerate RPC clients that can interact with proto messagestrue
rpcClients.bundlewill generate factory bundle aggregate of all RPC Clientstrue
rpcClients.camelCaseuse camel-case for RPC methods when generating RPC clientstrue
rpcClients.scopedwill generate factory of scoped RPC Clientsundefined
rpcClients.scopedIsExclusivewill allow both scoped bundles and all RPC Clientstrue
rpcClients.enabledServiceswhich services to enable[Msg,Query,Service]
rpcClients.instantOpswill generate instant rpc operations in the file service-ops.ts under root folder, which contains customized classes having selected rpc methodsundefined
rpcClients.serviceImplementassign implement type of rpc methods, Query or Tx, by setting patterns under service types.undefined
rpcClients.combinedClient.nameassign the client name like {name}AminoConverters, get{name}SigningClient etcundefined
rpcClients.combinedClient.fileNameassign the file name of generated client in root directoryundefined
rpcClients.combinedClient.include.patternsdetermine which proto files will be imported for the current client such as cosmos.gov.v1beta1.**undefined

See RPC Clients for more info.

Helper Functions

OptionDescriptionDefaults
helperFuncCreators.enabledEnable the generation of helper function files .func.tsfalse
helperFuncCreators.genCustomHooksGenerates React hooks alongside helper functionsfalse
helperFuncCreators.include.serviceTypesSpecifies which types of services to include (Query, Msg). undefined includes all types.undefined
helperFuncCreators.include.patternsArray of glob patterns patterns (e.g., "**", "cosmos.bank.v1beta1.bala*", etc.) to match specific proto services.undefined
helperFuncCreators.nameMappersConfiguration object for customizing function names and prefixes{}
helperFuncCreators.nameMappers.All.funcBodyMaps method names to a new name for all services."unchanged"
helperFuncCreators.nameMappers.All.creatorPrefixPrefix for the function creator."create"
helperFuncCreators.nameMappers.All.hookPrefixPrefix for the hooks."use"
helperFuncCreators.nameMappers.Query.funcBodyMaps method names to a new name for Query services."get"
helperFuncCreators.nameMappers.Query.creatorPrefixPrefix for the function creator for Query services."create"
helperFuncCreators.nameMappers.Query.hookPrefixPrefix for the hooks for Query services."use"
helperFuncCreators.nameMappers.Msg.funcBodyMaps method names to a new name for Msg services."unchanged"
helperFuncCreators.nameMappers.Msg.creatorPrefixPrefix for the function creator for Msg services."create"
helperFuncCreators.nameMappers.Msg.hookPrefixPrefix for the hooks for Msg services."use"

See Helper Functions Configuration for more info.

Stargate Client Options

optiondescriptiondefaults
stargateClients.includeCosmosDefaultTypesif true, will include the cosmjs defaults with stargate clientstrue (except cosmos package)
stargateClients.addGetTxRpcif true, will add getSigningTxRpc to clients in namespacesfalse

State Management

React Query

optiondescriptiondefaults
reactQuery.enabledif true, will create react hooks that use @tanstack/react-query hooksfalse
reactQuery.needExtraQueryKeyif true, users can input extra react query key to some customized hooks. e.g.['rpcEndpoint', 'yourExtraKey']false
reactQuery.include.protosif set, will create the hooks on matched proto filenames or patterns using minimatch[]
reactQuery.include.packagesif set, will create the hooks on matched packages files using minimatch[]
reactQuery.include.patternsif set, will create the hooks on matched patterns of files using minimatch(deprecated in favor of packages and protos have been supported minimatch)[]
reactQuery.instantExport.include.patternsif set, will expose instant hooks on matched patterns of packages + method(e.g. cosmos.bank.v1beta1.useBalance) using minimatch. If there're duplicated method names in multiple packages without setting reactQuery.instantExport.nameMapping, one duplicated name will created like: useCosmosBankV1beta1Balance[]
reactQuery.instantExport.nameMappingmap an alias to a package + method in case of better naming of duplicated method names. (e.g. useBankBalance: cosmos.bank.v1beta1.useBalance) Customized hook name is set in front of pkg+method, by doing this we can prevent duplicate alias.{}

Mobx

optiondescriptiondefaults
mobx.enabledif true, will create mobx stores that use mobxfalse
mobx.include.protosif set, will create the mobx stores on matched proto filenames or patterns using minimatch[]
mobx.include.packagesif set, will create the mobx stores on matched packages files using minimatch[]
mobx.include.patternsif set, will create the mobx stores on matched patterns of proto files using minimatch(deprecated in favor of packages and protos have been supported minimatch)[]

Pinia

optiondescriptiondefaults
pinia.enabledif true, will create pinia stores that use piniafalse
mobx.include.protosif set, will create the pinia stores on matched proto filenames or patterns using minimatch[]
mobx.include.packagesif set, will create the pinia stores on matched packages files using minimatch[]
mobx.include.patternsif set, will create the pinia stores on matched patterns of proto files using minimatch(deprecated in favor of packages and protos have been supported minimatch)[]

Typings and Formating

optiondescriptiondefaults
prototypes.typingsFormat.customTypes.useCosmosSDKDecenable handling "prototypes.typingsFormat.customTypes.useCosmosSDKDec" proto custom type. Used to show decimal fields with the custom type correctly. Highly recommend set to true.true
prototypes.typingsFormat.customTypes.useEnhancedDecimalTo use patched decimal other then decimal from @cosmjs/mathfalse
prototypes.typingsFormat.customTypes.base64LibTo use endo/base64 methodsundefined
prototypes.typingsFormat.num64'long' or 'bigint', the way of generating int64 proto types, set to 'bigint' to enable using more stable built-in typebigint
prototypes.typingsFormat.useTelescopeGeneratedTypeDiscard GeneratedType from cosmjs, use TelescopeGeneratedType instead inside *.registry.ts filesfalse
prototypes.typingsFormat.useDeepPartialdefaults to true, but if disabled uses the Partial TS typefalse
prototypes.typingsFormat.useExactdefaults to false, but if enabled uses the Exact TS typefalse
prototypes.typingsFormat.toJsonUnknowndefaults to true, but if disabled uses the JsonSafe for toJSON methodstrue
prototypes.typingsFormat.timestampuse either date or timestamp for Timestamp proto type"date"
prototypes.typingsFormat.durationuse either duration or string for Duration proto type"duration"
prototypes.typingsFormat.setDefaultEnumToUnrecognizedfalse: enum empty value would be 0, true: -1(value for enum unrecognized)true
prototypes.typingsFormat.setDefaultCustomTypesToUndefinedtrue: Timestamp,Duration,Any,Coin empty value would be undefined., false: using fromPartial to get an empty objfalse
prototypes.typingsFormat.autoFixUndefinedEnumDefaultthe default value of an enum field would be: 1(proto2); 0(proto3); But in some rare cases, those default values are not existing. By enabling this, the default value will be automatically fixed with the smallest value inside the enum.false

Protobuf parser

optiondescriptiondefaults
prototypes.parser.keepCasepasses keepCase to protobuf parse() to keep original casingtrue
prototypes.parser.alternateCommentModepasses alternateCommentMode to protobuf parse() methodtrue
prototypes.parser.preferTrailingCommentpasses preferTrailingComment to protobuf parse() methodfalse

Typescript Disabling

optiondescriptiondefaults
tsDisable.disableAllif true, will include //@ts-nocheck on every output filefalse
tsDisable.patternsif set, will include //@ts-nocheck on matched patterns[]
tsDisable.filesif set, will include //@ts-nocheck on matched files[]

ESLint Disabling

optiondescriptiondefaults
eslintDisable.disableAllif true, will include /* eslint-disable */ on every output filefalse
eslintDisable.patternsif set, will include /* eslint-disable */ on matched patterns[]
eslintDisable.filesif set, will include /* eslint-disable */ on matched files[]

Bundle

optiondescriptiondefaults
bundle.enabledbundle all files into a scoped index filetrue

Output

optiondescriptiondefaults
env'default' or 'v-next', set to 'v-next' to enable yet to release featuresdefault
removeUnusedImportsremoves unused importstrue
classesUseArrowFunctionsclasses use arrow functions instead of bind()ing in constructorsfalse
includeExternalHelpersexports a few helpers functions in extern.tsfalse
restoreImportExtensionrestore extensions of imported paths. e.g: '.js'. null means no extnull

Types

Timestamp

The representation of google.protobuf.Timestamp is configurable by the prototypes.typingsFormat.timestamp option.

Protobuf typeDefault/date='date'date='timestamp'
google.protobuf.TimestampDate{ seconds: Long, nanos: number }

TODO

Duration

The representation of google.protobuf.Duration is configurable by the prototypes.typingsFormat.duration option.

Protobuf typeDefault/duration='duration'duration='string'
google.protobuf.Duration{ seconds: Long, nanos: number }string

Composing Messages

This example shows messages from the osmojs, which was built with Telescope.

Import the osmosis object from osmojs. In this case, we're show the messages available from the osmosis.gamm.v1beta1 module:

import { osmosis } from 'osmojs';

const {
    joinPool,
    exitPool,
    exitSwapExternAmountOut,
    exitSwapShareAmountIn,
    joinSwapExternAmountIn,
    joinSwapShareAmountOut,
    swapExactAmountIn,
    swapExactAmountOut
} = osmosis.gamm.v1beta1.MessageComposer.withTypeUrl;

Now you can construct messages. If you use vscode or another typescript-enabled IDE, you should also be able to use ctrl+space to see auto-completion of the fields required for the message.

import { coin } from '@cosmjs/amino';

const msg = swapExactAmountIn({
  sender,
  routes,
  tokenIn: coin(amount, denom),
  tokenOutMinAmount
});

Calculating Fees

Make sure to create a fee object in addition to your message.

import { coins } from '@cosmjs/amino';

const fee = {
    amount: coins(0, 'uosmo'),
    gas: '250000'
}

if you are broadcasting multiple messages in a batch, you should simulate your tx and estimate the fee

import { Dec, IntPretty } from '@keplr-wallet/unit';

const gasEstimated = await stargateClient.simulate(address, msgs, memo);
const fee = {
  amount: coins(0, 'uosmo'),
  gas: new IntPretty(new Dec(gasEstimated).mul(new Dec(1.3)))
    .maxDecimals(0)
    .locale(false)
    .toString()
};

Stargate Clients

Every module gets their own signing client. This example demonstrates for the osmosis module.

Use getSigningOsmosisClient to get your SigningStargateClient, with the Osmosis proto/amino messages full-loaded. No need to manually add amino types, just require and initialize the client:

import { getSigningOsmosisClient } from 'osmojs';

const client = await getSigningOsmosisClient({
  rpcEndpoint,
  signer // OfflineSigner
});

Creating Signers

To broadcast messages, you'll want to use either keplr or an OfflineSigner from cosmjs using mnemonics.

Amino Signer

Likely you'll want to use the Amino, so unless you need proto, you should use this one:

import { getOfflineSigner as getOfflineSignerAmino } from 'cosmjs-utils';

Proto Signer

import { getOfflineSigner as getOfflineSignerProto } from 'cosmjs-utils';

WARNING: NOT RECOMMENDED TO USE PLAIN-TEXT MNEMONICS. Please take care of your security and use best practices such as AES encryption and/or methods from 12factor applications.

import { chains } from 'chain-registry';

const mnemonic =
    'unfold client turtle either pilot stock floor glow toward bullet car science';
const chain = chains.find(({ chain_name }) => chain_name === 'osmosis');
const signer = await getOfflineSigner({
    mnemonic,
    chain
});

Broadcasting messages

Now that you have your client, you can broadcast messages:

import { signAndBroadcast } from '@osmosnauts/helpers';

const res = await signAndBroadcast({
  client, // SigningStargateClient
  chainId: 'osmosis-1', // use 'osmo-test-4' for testnet
  address,
  msgs: [msg],
  fee,
  memo: ''
});

LCD Clients

For querying data via REST endpoints, you can use LCD Clients. For a better developer experience, you can generate a factory of scoped bundles of all LCD Clients with the lcdClients option.

const options: TelescopeOptions = {
    lcdClients: {
        enabled: true,
    },
};

If you use the lcdClients.scoped array, you can scope to only the modules of your interest.

const options: TelescopeOptions = {
  lcdClients: {
    enabled: true,
    scoped: [
      {
        dir: 'osmosis',
        filename: 'custom-lcd-client.ts',
        packages: [
          'cosmos.bank.v1beta1',
          'cosmos.gov.v1beta1',
          'osmosis.gamm.v1beta1'
        ],
        addToBundle: true,
        methodName: 'createCustomLCDClient'
      },
      {
        dir: 'evmos',
        filename: 'custom-lcd-client.ts',
        packages: [
          'cosmos.bank.v1beta1',
          'cosmos.gov.v1beta1',
          'evmos.erc20.v1'
        ],
        addToBundle: true,
        methodName: 'createEvmosLCDClient'
      }
    ]
  }
};

This will generate a nice helper in the ClientFactory, which you can then use to query multiple modules from a single object:

import { osmosis } from './codegen';

const main = async () => {
   const client = await osmosis.ClientFactory.createLCDClient({ restEndpoint: REST_ENDPOINT });

   // now you can query the modules
   const pool = await client.osmosis.gamm.v1beta1.pool({ poolId: "1" });
   const balance = await client.cosmos.bank.v1beta1.allBalances({ address: 'osmo1addresshere' });
};

LCD Clients Classes

If you want to instantiate a single client, for any module that has a Query type, there will be a LCDQueryClient object:

import { osmosis } from "osmojs";

export const main = async () => {
    const requestClient = new LCDClient({ restEndpoint: REST_ENDPOINT });
    const client = new osmosis.gamm.v1beta1.LCDQueryClient({ requestClient });
    const pools = await client.pools();
    console.log(pools);
};

main().then(() => {
    console.log('all done')
})

RPC Clients

Tendermint Client

For querying data via RPC endpoints, you can use RPC Clients. For a better developer experience, you can generate a factory of scoped bundles of all RPC Clients with the rpcClients option.

const options: TelescopeOptions = {
  rpcClients: {
    type: 'tendermint',
    enabled: true,
    camelCase: true
  }
};

If you use the rpcClients.scoped array, you can scope to only the modules of your interest. gRPC-web and gRPC-gateway work the same way with this option.

const options: TelescopeOptions = {
  rpcClients: {
    enabled: true,
    camelCase: true,
    scoped: [
      {
        dir: 'osmosis',
        filename: 'osmosis-rpc-client.ts',
        packages: [
          'cosmos.bank.v1beta1',
          'cosmos.gov.v1beta1',
          'osmosis.gamm.v1beta1'
        ],
        addToBundle: true,
        methodNameQuery: 'createRPCQueryClient',
        methodNameTx: 'createRPCTxClient'
      }
    ]
  }
};

This will generate helpers createRPCQueryClient and createRPCTxClient in the ClientFactory, which you can then use to query multiple modules from a single object:

import { osmosis } from './codegen';

const main = async () => {
  const client = await osmosis.ClientFactory.createRPCQueryClient({ rpcEndpoint });

  // now you can query the modules
  const pool = await client.osmosis.gamm.v1beta1.pool({ poolId: "1" });
  const balance = await client.cosmos.bank.v1beta1.allBalances({ address: 'osmo1addresshere' });
};

gRPC-web Client

For querying data via gRPC-web endpoints, you can use gRPC-web Clients. For a better developer experience, you can generate a factory of scoped bundles of all gRPC-web Clients with the rpcClients option.

const options: TelescopeOptions = {
  rpcClients: {
    type: 'grpc-web',
    enabled: true,
    camelCase: true
  }
};

This will generate helpers createGrpcWebClient and createGrpcMsgClient in the ClientFactory, which you can then use to query multiple modules from a single object, if you need an example with scaffold and broadcast msg you can refer to the example below in grpc-gateway:

import { osmosis } from './codegen';

const main = async () => {
  const client = await osmosis.ClientFactory.createGrpcWebClient({ endpoint });

  // now you can query the modules
  const pool = await client.osmosis.gamm.v1beta1.pool({ poolId: "1" });
  const balance = await client.cosmos.bank.v1beta1.allBalances({ address: 'osmo1addresshere' });
};

gRPC-gateway Client

For querying data via gRPC-web endpoints, you can use gRPC-web Clients. For a better developer experience, you can generate a factory of scoped bundles of all gRPC-web Clients with the rpcClients option.

const options: TelescopeOptions = {
  rpcClients: {
    type: 'grpc-gateway',
    enabled: true,
    camelCase: true
  }
};

This will generate helpers createGrpcGateWayClient in the ClientFactory, which you can then use to query multiple modules from a single object:

import { osmosis } from './codegen';

const main = async () => {
  // endpoint here is lcd endpoint
  const client = await osmosis.ClientFactory.createGrpcGateWayClient({ endpoint });

  // now you can query the modules
  const pool = await client.osmosis.gamm.v1beta1.pool({ poolId: "1" });
  const balance = await client.cosmos.bank.v1beta1.allBalances({ address: 'osmo1addresshere' });
};

Below will be an example of scaffold a grant Proto Msg for grpc-web and grpc-gateway and then broadcast it.

const { grant } = cosmos.authz.v1beta1.MessageComposer.withTypeUrl;
const msg = grant({
    granter: 'granter_address',
    grantee: 'grantee_address',
    grant: {
        authorization: StakeAuthorization.toProtoMsg({
        maxTokens: {
            denom: 'uosmo',
            amount: '100000000'
        },
        authorizationType: AuthorizationType.AUTHORIZATION_TYPE_DELEGATE
    }),
    expiration: new Date(Date.now() + 60 * 60 * 24 * 7)
}})

const signed_tx = await signClient.sign('granter_address', [msg], fee, 'telescope: grant', signerData);
const txRawBytes = Uint8Array.from(TxRaw.encode(signed_tx).finish());

const res = await client.cosmos.tx.v1beta1.broadcastTx({
    txBytes: txRawBytes,
    mode: BroadcastMode.BROADCAST_MODE_BLOCK
})

console.log(res);

RPC Client Classes

If you want to instantiate a single client, you can generate RPC classes with the rpcClients option;

For any module that has a Msg, Query or Service type, a

import { osmosis, cosmos } from 'osmojs';

const MsgClient = osmosis.gamm.v1beta1.MsgClientImpl;
const QueryClient = osmosis.gamm.v1beta1.QueryClientImpl;
const ServiceClient = cosmos.base.tendermint.v1beta1.ServiceClientImpl;

Here is an example of making a query if you want to use the RPC client classes manually:

import { osmosis } from "osmojs";
import { createProtobufRpcClient, QueryClient } from "@cosmjs/stargate";
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";

export const main = async () => {
    const tmClient = await Tendermint34Client.connect(RPC_ENDPOINT);
    const QueryClientImpl = osmosis.gamm.v1beta1.QueryClientImpl;
    const client = new QueryClient(tmClient);
    const rpc = createProtobufRpcClient(client);
    const queryService = new QueryClientImpl(rpc);
    const pools = await queryService.pools({})
    console.log(pools);
};

main().then(() => {
    console.log('all done')
})

Instant RPC Methods

Using instantOps option to expose instant RPC methods.

For example, for this config:

{
    instantOps: [
      {
        className: "OsmosisClaim",
        include: {
          patterns: ["osmosis.**.*claim*"],
        },
      },
      {
        className: "CosmosAuthAccount",
        include: {
          patterns: [
            "cosmos.auth.**.*account*",
            "cosmos.auth.**.*Account*",
            "cosmos.gov.v1beta1.**",
          ],
        },
        nameMapping: {
          // name mapping rule for both Msg and Query methods.
          // moduleAccounts will be renamed to authModuleAccounts in generated class.
          All: {
            authModuleAccounts: "cosmos.auth.v1beta1.moduleAccounts",
          },
          // name mapping rule for Msg methods.
          Msg: {
            // deposit method under Msg will be renamed to txDeposit in generated class. While deposit method under Query will remain the same.
            txDeposit: "cosmos.gov.v1beta1.deposit",
            // Same for vote method.
            txVote: "cosmos.gov.v1beta1.vote",
          },
        },
      },
    ],
}

There'll be an extra file generated in the root folder called service-ops.ts:

export interface OsmosisClaim extends _OsmosisClaimV1beta1Queryrpc.OsmosisClaim {}
export class OsmosisClaim {
  rpc: TxRpc;
  init(rpc: TxRpc) {
    this.rpc = rpc;
    this.claimRecord = _OsmosisClaimV1beta1Queryrpc.createClientImpl(rpc).claimRecord;
    this.claimableForAction = _OsmosisClaimV1beta1Queryrpc.createClientImpl(rpc).claimableForAction;
  }
}
export interface CosmosAuthAccount extends _CosmosAuthV1beta1Queryrpc.CosmosAuthAccount, _CosmosGovV1beta1Queryrpc.CosmosAuthAccount, _CosmosGovV1beta1Txrpc.CosmosAuthAccount {}
export class CosmosAuthAccount {
  rpc: TxRpc;
  init(rpc: TxRpc) {
    this.rpc = rpc;
    this.accounts = _CosmosAuthV1beta1Queryrpc.createClientImpl(rpc).accounts;
    this.account = _CosmosAuthV1beta1Queryrpc.createClientImpl(rpc).account;

    // moduleAccounts has been renamed to authModuleAccounts as the nameMapping in settings.
    this.authModuleAccounts = _CosmosAuthV1beta1Queryrpc.createClientImpl(rpc).moduleAccounts;

    this.proposal = _CosmosGovV1beta1Queryrpc.createClientImpl(rpc).proposal;
    this.proposals = _CosmosGovV1beta1Queryrpc.createClientImpl(rpc).proposals;

    // vote under Query remains the same.
    this.vote = _CosmosGovV1beta1Queryrpc.createClientImpl(rpc).vote;

    this.votes = _CosmosGovV1beta1Queryrpc.createClientImpl(rpc).votes;
    this.params = _CosmosGovV1beta1Queryrpc.createClientImpl(rpc).params;

    // deposit under Query remains the same.
    this.deposit = _CosmosGovV1beta1Queryrpc.createClientImpl(rpc).deposit;

    this.deposits = _CosmosGovV1beta1Queryrpc.createClientImpl(rpc).deposits;
    this.tallyResult = _CosmosGovV1beta1Queryrpc.createClientImpl(rpc).tallyResult;
    this.submitProposal = _CosmosGovV1beta1Txrpc.createClientImpl(rpc).submitProposal;

    //same as txDeposite for vote here.
    this.txVote = _CosmosGovV1beta1Txrpc.createClientImpl(rpc).vote;

    this.voteWeighted = _CosmosGovV1beta1Txrpc.createClientImpl(rpc).voteWeighted;

    // deposit method under Msg will be renamed to txDeposit in generated class. While deposit method under Query will remain the same.
    this.txDeposit = _CosmosGovV1beta1Txrpc.createClientImpl(rpc).deposit;
  }
}

Combined Clients Methods

Using combined option to generate multiple module clients

For example, for this config:

{
    combinedClient: [
      {
        name: "CosmosIbc",
        fileName: "cosmos-ibc-client.ts",
        include: {
          patterns: [
            "cosmos.gov.v1beta1*",
            "cosmos.gov.v1*",
            "ibc.core.channel.*",
          ],
        },
      },
      {
        name: "AkashCosmos",
        fileName: "akash-cosmos-client.ts",
        include: {
          patterns: [
            "cosmos.group.v1*",
            "cosmos.nft.v1beta1*",
            "akash.**.v1beta2",
            "akash.audit.v1beta1*",
          ],
        },
      },
    ],
}

There'll be client files (cosmos-ibc-client.ts, akash-cosmos-client.ts) generated in the root folder according to fileName in the setting. The combined client will import proto files accroding to include.patterns. (The protos can come from different modules) For example the cosmos-ibc-client.ts will be like:

export const cosmosIbcAminoConverters = {
  ...cosmosGovV1TxAmino.AminoConverter,
  ...cosmosGovV1beta1TxAmino.AminoConverter,
  ...ibcCoreChannelV1TxAmino.AminoConverter
};
export const cosmosIbcProtoRegistry: ReadonlyArray<[string, GeneratedType]> = [...cosmosGovV1TxRegistry.registry, ...cosmosGovV1beta1TxRegistry.registry, ...ibcCoreChannelV1TxRegistry.registry];
export const getCosmosIbcSigningClientOptions = ({
  defaultTypes = defaultRegistryTypes
}: {
  ...
};
export const getCosmosIbcSigningClient = async ({
  rpcEndpoint,
  signer,
  defaultTypes = defaultRegistryTypes
}: {
  rpcEndpoint: string | HttpEndpoint;
  signer: OfflineSigner;
  defaultTypes?: ReadonlyArray<[string, GeneratedType]>;
}) => {
  ...
};

Manually registering types

This example is with osmosis module in osmojs, but it is the same pattern for any module.

NOTE: this is using @cosmjs/stargate@0.28.4

import {
    AminoTypes,
    SigningStargateClient
} from '@cosmjs/stargate';
import { Registry } from '@cosmjs/proto-signing';
import { defaultRegistryTypes } from '@cosmjs/stargate';
import { OfflineSigner } from '@cosmjs/proto-signing'
import { osmosis } from 'osmojs';

export const getCustomSigningClient = async ({ rpcEndpoint, signer }: { rpcEndpoint: string, signer: OfflineSigner }) => {
  // registry
  const registry = new Registry(defaultRegistryTypes);

  // aminotypes
  const aminoTypes = new AminoTypes({
    ...osmosis.gamm.v1beta1.AminoConverter,
    ...osmosis.lockup.AminoConverter,
    ...osmosis.superfluid.AminoConverter
  });

  // load the
  osmosis.gamm.v1beta1.load(registry);
  osmosis.lockup.load(registry);
  osmosis.superfluid.load(registry);

  const client = await SigningStargateClient.connectWithSigner(
    rpcEndpoint,
    signer,
    { registry, aminoTypes }
  );

  return client;
};

JSON Patch Protos

The prototypes.patch configuration within the options object allows for dynamic modifications to protobuf definitions during code generation. This feature is designed to apply specific changes to proto files without altering the original source. By using JSON Patch operations such as replace and add, developers can customize the generated output to better fit project requirements when upstream SDK PRs are lagging or not in production.

Patches are specified as arrays of Operations, where each operation is defined by:

Here is how these patches can be defined within the prototypes configuration:

{
    "prototypes": {
        "patch": {
            "cosmwasm/wasm/v1/types.proto": [
                {
                    "op": "replace",
                    "path": "@/AccessType/valuesOptions/ACCESS_TYPE_UNSPECIFIED/(gogoproto.enumvalue_customname)",
                    "value": "UnspecifiedAccess"
                },
                {
                    "op": "replace",
                    "path": "@/AccessType/valuesOptions/ACCESS_TYPE_NOBODY/(gogoproto.enumvalue_customname)",
                    "value": "NobodyAccess"
                },
                {
                    "op": "add",
                    "path": "@/AccessType/values/ACCESS_TYPE_SUPER_FUN",
                    "value": 4
                },
                {
                    "op": "add",
                    "path": "@/AccessType/valuesOptions/ACCESS_TYPE_SUPER_FUN",
                    "value": {
                        "(gogoproto.enumvalue_customname)": "SuperFunAccessType"
                    }
                }
            ]
        }
    }
}

CosmWasm

Generate TypeScript SDKs for your CosmWasm smart contracts by using the cosmwasm option on TelescopeOptions. The cosmwasm option is actually a direct reference to the TSBuilderInput object, for the most up-to-date documentation, visit @cosmwasm/ts-codegen.

import { TSBuilderInput } from '@cosmwasm/ts-codegen';
const options: TelescopeOptions = {
  cosmwasm: {
    contracts: [
      {
        name: 'SG721',
        dir: './path/to/sg721/schema'
      },
      {
        name: 'Minter',
        dir: './path/to/Minter/schema'
      }
    ],
    outPath: './path/to/code/src/'
  }
};

Helper Functions Configuration

The nameMappers object supports three service types: All, Query, and Msg. Each pattern within these categories can specify:

{
  "pattern": {
    funcBody: (name: string) => string,    // Function to transform the method name
    creatorPrefix?: string,                // Prefix for the creator function (default: "create")
    hookPrefix?: string                    // Prefix for the hook function (default: "use")
  }
}
const options: TelescopeOptions = {
  helperFuncCreators: {
    enabled: true,
    genCustomHooks: true,
    include: {
      patterns: ["cosmos.gov.v1beta1.**", "cosmos.bank.v1beta1.*Send*"],
    },
    nameMappers: {
      All: {
        "cosmos.gov.v1beta1.*Vote*": {
          funcBody: (name) => `helper${name}`,
          creatorPrefix: "build",
          hookPrefix: "use",
        },
      },
      Query: {
        "cosmos.gov.v1beta1.*Deposits*": {
          funcBody: (name) => `goOver${name}`,
        },
      },
      Msg: {
        "cosmos.gov.v1beta1.*VoteWeighted*": {
          funcBody: (name) => `lets${name}`,
          creatorPrefix: "construct",
          hookPrefix: "useTx",
        },
      },
    },
  },
};

Pattern Matching Priority:

  1. Service-specific patterns (Query, Msg) take precedence over All patterns
  2. More specific patterns take precedence over general patterns
  3. Patterns are case-sensitive

Generated Output Examples:

Notes:

Dependencies

If you don't use the boilerplate, you will need to manually install

yarn add @cosmjs/amino @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc

If you use the LCD Client generation, you'll need to add

yarn add @cosmology/lcd

Troubleshooting

Create React App

CRA requires that you update Webpack configurations:

https://github.com/cosmos/cosmjs/blob/656e02374898afe755e980e93390591b4b65fd86/README.md#webpack-configs

Here is an example of a config-overrides.js:

https://github.com/pyramation/osmosis-ui/blob/main/config-overrides.js

Babel

This should not be an issue, but if you experience problems with syntax or are not using preset-env, you may need these babel plugins:

Developing

See our documentation for how to contribute and develop Telescope.

Sponsors

Kudos to our sponsors:

Related

Checkout these related projects:

Credits

πŸ›  Built by Cosmology β€” if you like our tools, please consider delegating to our validator βš›οΈ

Thanks to these engineers, teams and projects for inspiring Telescope:

Disclaimer

AS DESCRIBED IN THE TELESCOPE LICENSES, THE SOFTWARE IS PROVIDED β€œAS IS”, AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.

No developer or entity involved in creating Telescope will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the Telescope code or Telescope CLI, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.