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
- Telescope π
- Usage
- Programatic Usage
- Options
- Types
- Composing Messages
- Calculating Fees
- Stargate Clients
- Creating Signers
- Broadcasting messages
- LCD Clients
- LCD Clients Classes
- RPC Clients
- RPC Client Classes
- Instant RPC Methods
- Manually registering types
- JSON Patch Protos
- CosmWasm
- Helper Functions Configuration
- Dependencies
- Troubleshooting
- Developing
- Sponsors
- Related
- Credits
- Disclaimer
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
option | description | defaults |
---|---|---|
aminoEncoding.enabled | generate amino types and amino converters | true |
aminoEncoding.omitEmptyTags | An 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.disableMsgTypes | disable generating AminoMsg types | false |
aminoEncoding.casingFn | set the amino-casing function for a project | snake() |
aminoEncoding.exceptions | set specific aminoType name exceptions | see code |
aminoEncoding.typeUrlToAmino | create functions for aminoType name exceptions | undefined |
aminoEncoding.useLegacyInlineEncoding | @deprecated. To use legacy inline encoding instead of using v2 recursive encoding | false |
aminoEncoding.useRecursiveV2encoding | this's been removed. See useLegacyInlineEncoding instead. | |
aminoEncoding.legacy.useNullHandling | handle null case when generating legacy amino converters(those in tx.amino.ts) | |
aminoEncoding.legacy.useOmitEmpty | handle omit empty or not when generating legacy amino converters(those in tx.amino.ts) |
Implemented Interface Options
option | description | defaults |
---|---|---|
interfaces.enabled | enables converters convert between Any type and specific implemented interfaces. | true |
interfaces.useGlobalDecoderRegistry | enables 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.useUseInterfacesParams | decides if add useInterfaces argument to decode and toAmino functions. | false |
interfaces.useByDefault | decides if interface decoders are used by default (default for useInterfaces argument to decode and toAmino functions). | true |
interfaces.useByDefaultRpc | decides if interface decoders are used by default by the RPC clients. | true |
interfaces.useUnionTypes | Generate Any type as union types(TextProposal | RegisterIncentiveProposal) instead of intersection types(TextProposal & RegisterIncentiveProposal). | false |
Prototypes Options
option | description | defaults |
---|---|---|
prototypes.enabled | enables the generation of proto encoding methods | true |
prototypes.includePackageVar | export a protoPackage variable to indicate package name | false |
prototypes.includes.packages | include a set of packages when transpilation. (if a package both meet include and exclude, it'll be excluded) | undefined |
prototypes.includes.protos | include a set of proto files when transpilation. (if a proto both meet include and exclude, it'll be excluded) | undefined |
prototypes.excluded.packages | exclude a set of packages from transpilation | undefined |
prototypes.excluded.protos | try 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.hardProtos | exclude 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.fieldDefaultIsOptional | boolean value representing default optionality of field | false |
prototypes.useOptionalNullable | use (gogoproto.nullable) values in determining optionality | true |
prototypes.allowUndefinedTypes | boolean value allowing Type s to be undefined | false |
prototypes.allowEncodeDefaultScalars | boolean value allowing encoders encoding default values of scalar types. e.g. empty string, 0 or false | false |
prototypes.isScalarDefaultToNullable | Determines 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 required | false |
prototypes.enforceNullCheck | Determines whether to enforce checking required scalar fields not null or undefined during encoding | false |
prototypes.optionalQueryParams | boolean value setting queryParams to be optional | false |
prototypes.optionalPageRequests | boolean value setting PageRequest fields to optional | false |
prototypes.addTypeUrlToDecoders | add $typeUrl field to generated interfaces | true |
prototypes.addAminoTypeToObjects | add aminoType field to generated Decoders | false |
prototypes.addTypeUrlToObjects | add typeUrl field to generated Decoders | true |
prototypes.enableRegistryLoader | generate Registry loader to *.registry.ts files | true |
prototypes.enableMessageComposer | generate MessageComposer to *.registry.ts files | true |
prototypes.patch | An object mapping filenames to an array of Operation to be applied as patches to proto files during generation. See JSON Patch Protos | undefined |
Prototypes Methods
option | description | defaults |
---|---|---|
prototypes.methods.encode | boolean to enable encode method on proto objects | true |
prototypes.methods.decode | boolean to enable decode method on proto objects | true |
prototypes.methods.fromJSON | boolean to enable fromJSON method on proto objects | true |
prototypes.methods.toJSON | boolean to enable toJSON method on proto objects | true |
prototypes.methods.fromPartial | boolean to enable fromPartial method on proto objects | true |
prototypes.methods.fromSDK | boolean to enable fromSDK method on proto objects | false |
prototypes.methods.toSDK | boolean to enable toSDK method on proto objects | false |
Enums Options
option | description | defaults |
---|---|---|
enums.useCustomNames | Enables 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
option | description | defaults |
---|---|---|
lcdClients.enabled | generate LCD clients that can query proto Query messages | true |
lcdClients.bundle | will generate factory bundle aggregate of all LCD Clients | true |
lcdClients.scoped | will generate factory of scoped LCD Clients | undefined |
lcdClients.scopedIsExclusive | will allow both scoped bundles and all RPC Clients | true |
See LCD Clients for more info.
RPC Client Options
option | description | defaults |
---|---|---|
rpcClients.type | will generate this type of RPC client (tendermint , gRPC-web , gRPC ) | tendermint |
rpcClients.enabled | generate RPC clients that can interact with proto messages | true |
rpcClients.bundle | will generate factory bundle aggregate of all RPC Clients | true |
rpcClients.camelCase | use camel-case for RPC methods when generating RPC clients | true |
rpcClients.scoped | will generate factory of scoped RPC Clients | undefined |
rpcClients.scopedIsExclusive | will allow both scoped bundles and all RPC Clients | true |
rpcClients.enabledServices | which services to enable | [Msg ,Query ,Service ] |
rpcClients.instantOps | will generate instant rpc operations in the file service-ops.ts under root folder, which contains customized classes having selected rpc methods | undefined |
rpcClients.serviceImplement | assign implement type of rpc methods, Query or Tx , by setting patterns under service types. | undefined |
rpcClients.clientStyle.useUpdatedClientStyle | The default value is false , which sets the generated client to use the legacy style. Setting it to true applies the updated style and activates the remaining options in clientStyle. | false |
rpcClients.clientStyle.type | A string array containing possible values: all-client , sdk-module-client , and custom-client . The value all-client generates an all-module-client file. The value sdk-module-client generates a client for the module specified by the sdkModuleClientOption . The value custom-client generates a customized client as specified by customClientOption | undefined |
rpcClients.clientStyle.customClientOption.name | assign the client name like {name}AminoConverters , get{name}SigningClient etc | undefined |
rpcClients.clientStyle.customClientOption.fileName | assign the file name of generated client in root directory | undefined |
rpcClients.clientStyle.customClientOption.include.patterns | determine which proto files will be imported for the current client such as cosmos.gov.v1beta1.** | undefined |
See RPC Clients for more info.
Helper Functions
Option | Description | Defaults |
---|---|---|
helperFuncCreators.enabled | Enable the generation of helper function files .func.ts | false |
helperFuncCreators.genCustomHooks | Generates React hooks alongside helper functions | false |
helperFuncCreators.include.serviceTypes | Specifies which types of services to include (Query , Msg ). undefined includes all types. | undefined |
helperFuncCreators.include.patterns | Array of glob patterns patterns (e.g., "**" , "cosmos.bank.v1beta1.bala*" , etc.) to match specific proto services. | undefined |
helperFuncCreators.nameMappers | Configuration object for customizing function names and prefixes | {} |
helperFuncCreators.nameMappers.All.funcBody | Maps method names to a new name for all services. | "unchanged" |
helperFuncCreators.nameMappers.All.creatorPrefix | Prefix for the function creator. | "create" |
helperFuncCreators.nameMappers.All.hookPrefix | Prefix for the hooks. | "use" |
helperFuncCreators.nameMappers.Query.funcBody | Maps method names to a new name for Query services. | "get" |
helperFuncCreators.nameMappers.Query.creatorPrefix | Prefix for the function creator for Query services. | "create" |
helperFuncCreators.nameMappers.Query.hookPrefix | Prefix for the hooks for Query services. | "use" |
helperFuncCreators.nameMappers.Msg.funcBody | Maps method names to a new name for Msg services. | "unchanged" |
helperFuncCreators.nameMappers.Msg.creatorPrefix | Prefix for the function creator for Msg services. | "create" |
helperFuncCreators.nameMappers.Msg.hookPrefix | Prefix for the hooks for Msg services. | "use" |
See Helper Functions Configuration for more info.
Stargate Client Options
option | description | defaults |
---|---|---|
stargateClients.includeCosmosDefaultTypes | if true, will include the cosmjs defaults with stargate clients | true (except cosmos package) |
stargateClients.addGetTxRpc | if true, will add getSigningTxRpc to clients in namespaces | false |
State Management
React Query
option | description | defaults |
---|---|---|
reactQuery.enabled | if true, will create react hooks that use @tanstack/react-query hooks | false |
reactQuery.needExtraQueryKey | if true, users can input extra react query key to some customized hooks. e.g.['rpcEndpoint', 'yourExtraKey'] | false |
reactQuery.include.protos | if set, will create the hooks on matched proto filenames or patterns using minimatch | [] |
reactQuery.include.packages | if set, will create the hooks on matched packages files using minimatch | [] |
reactQuery.include.patterns | if 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.patterns | if 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.nameMapping | map 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
option | description | defaults |
---|---|---|
mobx.enabled | if true, will create mobx stores that use mobx | false |
mobx.include.protos | if set, will create the mobx stores on matched proto filenames or patterns using minimatch | [] |
mobx.include.packages | if set, will create the mobx stores on matched packages files using minimatch | [] |
mobx.include.patterns | if 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
option | description | defaults |
---|---|---|
pinia.enabled | if true, will create pinia stores that use pinia | false |
mobx.include.protos | if set, will create the pinia stores on matched proto filenames or patterns using minimatch | [] |
mobx.include.packages | if set, will create the pinia stores on matched packages files using minimatch | [] |
mobx.include.patterns | if 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) | [] |
Vue Query
option | description | defaults |
---|---|---|
vueQuery.enabled | if true, will create vue composables that use @tanstack/vue-query composables | false |
vueQuery.include.protos | if set, will create the composables on matched proto filenames or patterns using minimatch | [] |
vueQuery.include.packages | if set, will create the composables on matched packages files using minimatch | [] |
Typings and Formating
option | description | defaults |
---|---|---|
prototypes.typingsFormat.customTypes.useCosmosSDKDec | enable 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.useEnhancedDecimal | To use patched decimal other then decimal from @cosmjs/math | false |
prototypes.typingsFormat.customTypes.base64Lib | To use endo/base64 methods | undefined |
prototypes.typingsFormat.num64 | 'long' or 'bigint', the way of generating int64 proto types, set to 'bigint' to enable using more stable built-in type | bigint |
prototypes.typingsFormat.useTelescopeGeneratedType | Discard GeneratedType from cosmjs, use TelescopeGeneratedType instead inside *.registry.ts files | false |
prototypes.typingsFormat.useDeepPartial | defaults to true, but if disabled uses the Partial TS type | false |
prototypes.typingsFormat.useExact | defaults to false, but if enabled uses the Exact TS type | false |
prototypes.typingsFormat.toJsonUnknown | defaults to true, but if disabled uses the JsonSafe for toJSON methods | true |
prototypes.typingsFormat.timestamp | use either date or timestamp for Timestamp proto type | "date" |
prototypes.typingsFormat.duration | use either duration or string for Duration proto type | "duration" |
prototypes.typingsFormat.setDefaultEnumToUnrecognized | false: enum empty value would be 0, true: -1(value for enum unrecognized) | true |
prototypes.typingsFormat.setDefaultCustomTypesToUndefined | true: Timestamp,Duration,Any,Coin empty value would be undefined., false: using fromPartial to get an empty obj | false |
prototypes.typingsFormat.autoFixUndefinedEnumDefault | the 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
option | description | defaults |
---|---|---|
prototypes.parser.keepCase | passes keepCase to protobuf parse() to keep original casing | true |
prototypes.parser.alternateCommentMode | passes alternateCommentMode to protobuf parse() method | true |
prototypes.parser.preferTrailingComment | passes preferTrailingComment to protobuf parse() method | false |
Typescript Disabling
option | description | defaults |
---|---|---|
tsDisable.disableAll | if true, will include //@ts-nocheck on every output file | false |
tsDisable.patterns | if set, will include //@ts-nocheck on matched patterns | [] |
tsDisable.files | if set, will include //@ts-nocheck on matched files | [] |
ESLint Disabling
option | description | defaults |
---|---|---|
eslintDisable.disableAll | if true, will include /* eslint-disable */ on every output file | false |
eslintDisable.patterns | if set, will include /* eslint-disable */ on matched patterns | [] |
eslintDisable.files | if set, will include /* eslint-disable */ on matched files | [] |
Bundle
option | description | defaults |
---|---|---|
bundle.enabled | bundle all files into a scoped index file | true |
Output
option | description | defaults |
---|---|---|
env | 'default' or 'v-next', set to 'v-next' to enable yet to release features | default |
removeUnusedImports | removes unused imports | true |
classesUseArrowFunctions | classes use arrow functions instead of bind() ing in constructors | false |
includeExternalHelpers | exports a few helpers functions in extern.ts | false |
restoreImportExtension | restore extensions of imported paths. e.g: '.js'. null means no ext | null |
Types
Timestamp
The representation of google.protobuf.Timestamp
is configurable by the prototypes.typingsFormat.timestamp
option.
Protobuf type | Default/date='date' | date='timestamp' |
---|---|---|
google.protobuf.Timestamp | Date | { seconds: Long, nanos: number } |
TODO
- add
date='string'
option
Duration
The representation of google.protobuf.Duration
is configurable by the prototypes.typingsFormat.duration
option.
Protobuf type | Default/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;
}
}
Client Style Methods
Use client style to define the client file generated according to the config
For example, for this config:
clientStyle: {
useUpdatedClientStyle: true,
type: ['all-client', 'sdk-module-client', 'custom-client'],
customClientOption: [
{
name: "custom",
fileName: "custom-client.ts",
include: {
patterns: [
"cosmos.gov.v1beta1*",
"cosmos.gov.v1*",
"ibc.core.channel.*",
],
},
},
],
sdkModuleClientOption: [
'akash',
'osmosis',
'cosmos',
],
},
There'll be client files (all-module-client.ts
, akash-sdk-module-client.ts
, osmosis-sdk-module-client.ts
, cosmos-sdk-module-client.ts
, custom-client.ts
) generated in the root directory according to the setting.<br>
The all-module-client.ts
file consolidates all proto imports into one file and exports them as a single client.<br>
All sdk module client files will be identical to the legacy client.ts
files generated in each module directory, except they will be located in the root directory. <br>
The custom client imports proto files based on include.patterns
, allowing protos to originate from different modules.<br>
For example the custom-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 Operation
s, where each operation is defined by:
op
: The operation type (add
orreplace
).path
: The JSON path to the target field, optionally prefixed with@
to denote paths derived automatically from the package name, simplifying navigation within the proto file's structure.value
: The new value to be set at the target location specified by the path.
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:
- Service-specific patterns (
Query
,Msg
) take precedence overAll
patterns - More specific patterns take precedence over general patterns
- Patterns are case-sensitive
Generated Output Examples:
- For a method named
VoteWeighted
: - Default:
createVoteWeighted
anduseVoteWeighted
- With custom mapping:
constructLetsVoteWeighted
anduseTxLetsVoteWeighted
Notes:
- Patterns support glob-style matching (e.g.,
**
,*
) - Each service type can have its own naming conventions
- Custom prefixes are optional; defaults will be used if not specified
- Function body transformations can be customized using the funcBody property
Dependencies
If you don't use the boilerplate, you will need to manually install
@cosmjs/amino
@cosmjs/proto-signing
@cosmjs/stargate
@cosmjs/tendermint-rpc
yarn add @cosmjs/amino @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc
If you use the LCD Client generation, you'll need to add
@cosmology/lcd
yarn add @cosmology/lcd
Troubleshooting
Create React App
CRA requires that you update Webpack configurations:
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:
- babel-plugin-proposal-numeric-separator
- babel-plugin-proposal-optional-chaining
- babel-plugin-proposal-nullish-coalescing-operator
Developing
See our documentation for how to contribute and develop Telescope.
Sponsors
Kudos to our sponsors:
- Osmosis funded the creation of Telescope.
Related
Checkout these related projects:
- @cosmology/telescope Your Frontend Companion for Building with TypeScript with Cosmos SDK Modules.
- @cosmwasm/ts-codegen Convert your CosmWasm smart contracts into dev-friendly TypeScript classes.
- chain-registry Everything from token symbols, logos, and IBC denominations for all assets you want to support in your application.
- cosmos-kit Experience the convenience of connecting with a variety of web3 wallets through a single, streamlined interface.
- create-cosmos-app Set up a modern Cosmos app by running one command.
- interchain-ui The Interchain Design System, empowering developers with a flexible, easy-to-use UI kit.
- starship Unified Testing and Development for the Interchain.
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.