# Telescope Documentation Developing # Developing Telescope We're excited you made it here! Please read about how to build and contribute to Telescope! - [Setup](/telescope/developing/setup) - [Tests](/telescope/developing/tests) - [Starship Tests](/telescope/developing/starship-tests) - [Packages](/telescope/developing/packages) - [Working with ASTs](/telescope/developing/working-with-asts) - [Common helpers or utils](/telescope/developing/helpers) - [Creating generators](/telescope/developing/creating-new-generators) - [Migration to v1.0](/telescope/developing/migration) - [Plugin generators](https://github.com/hyperweb-io/telescope/blob/main/packages/telescope/src/generators) ---------------------------------- Creating New Generators # creating new generators ## 0 find a home for the code Find a folder that makes sense for the new generator. Only if you really need to, create a new one inside of `packages/ast/src`: ``` ├── bundle ├── clients ├── docs ├── encoding ├── plugins ├── registry ├── state ├── utils ``` | folder | purpose | | --------- | ----------------------------------------------------------------------------- | | bundle | for creating bundled object that map to go exports, e.g. `cosmos.bank.v1beta1` | | clients | for building LCD, RPC, gRPC or Stargate clients | | docs | for generating markdown and automated documentation | | encoding | for generating code that does Proto or Amino encoding | | plugins | for looking up options when building plugins | | registry | for generating the CosmJS message registry | | state | for generating state management plugins, such as react-query or mobx | | utils | other AST utilities, usually babel helpers | ## 1 write the AST function for example, in `packages/ast/src/clients/rpc/class/some-new-rpc-client.ts`. Make sure to include `context: GenericParseContext` as the first property of all AST functions. The `ProtoService` is only here in this example because we intend to use it as we create the AST generator. If you are not sure how to write ASTs with `@babel/types`, please see our docs on [working with ASTs](https://github.com/hyperweb-io/telescope/blob/main/docs/working-with-asts.md). You can also checkout [astexplorer.net](https://astexplorer.net) to visually browse and learn ASTs, but they are quite verbose compared to our script when you run `yarn test:ast`. ```js import { ProtoService } from "@cosmology/types"; import { GenericParseContext } from "../../../encoding"; import * as t from '@babel/types'; export const createSomeNewRpcClient = ( context: GenericParseContext, service: ProtoService ) => { context.addUtil("SuperInterface"); return t.exportNamedDeclaration( t.tsInterfaceDeclaration( t.identifier('MyAwesomeInterface'), null, [t.tsExpressionWithTypeArguments(t.identifier('SuperInterface'))], t.tsInterfaceBody([ // ... more code ... ]) ) ); }; ``` If we need to import from other packages, context.addUtil is recommended. For detail about this, please see: [Common helpers or utils](https://github.com/hyperweb-io/telescope/blob/main/docs/helpers.md) ## 2 add it to the index in this example, we're in `packages/ast/src/clients/rpc/class/index.ts` ```js export * from './rpc'; export * from './some-new-rpc-client'; ``` ## 3 add a test in this example, `packages/ast/src/clients/rpc/class/some-new-rpc-client.test.ts` ```js import { ProtoStore, traverse, getNestedProto } from '@cosmology/proto-parser' import { defaultTelescopeOptions, ProtoService } from '@cosmology/types'; import { expectCode, getTestProtoStore, printCode } from '../../../../test-utils'; import { GenericParseContext } from '../../../encoding'; import { createSomeNewRpcClient } from './some-new-rpc-client'; const store = getTestProtoStore(); // set any options that you need for this feature store.options.rpcClients.type = 'some-new-rpc-client' store.traverseAll(); it('GRPC web Msg Client', () => { const ref = store.findProto('cosmos/bank/v1beta1/tx.proto'); const res = traverse(store, ref); const service: ProtoService = getNestedProto(res).Msg; const context = new GenericParseContext(ref, store, store.options); // while you are prototyping, you can use `printCode` printCode(createSomeNewRpcClient(context, service)) // once you finish, use `expectCode` to generate a snapshot expectCode(createSomeNewRpcClient(context, service)) }); ``` Note: Run "yarn buidl" in ast package folder to keep it updated to other packages, by doing this, code in telescope package can invoke newly built ast functions. More detail on this, please see our docs on [Packages and workspace](https://github.com/hyperweb-io/telescope/blob/main/docs/packages.md). ``` cd packages/ast yarn buidl ``` ## 4 options add option for the new plugin in packages/types/src/telescope.ts. In this example, we add an option for the new rpc client plugin. ```js interface TelescopeOpts { // ... more options ... newRpcClient?: { enabled: boolean; // the scope of packages to control the logic of the plugin. include?: { // patterns including wildcards. eg: 'osmosis/**/gamm/**/query.proto' patterns?: string[]; // certian package paths. eg: 'cosmos.bank.v1beta1' packages?: string[]; // certain proto file. eg: 'akash/cert/v1beta2/query.proto' protos?: string[]; } }; ``` Note: After editing the option in types package, don't forget to run "yarn buidl" inside the types package keeping other packages up-to-date with the newest changes of the option. More detail on this, please see our docs on [Packages and workspace](https://github.com/hyperweb-io/telescope/blob/main/docs/packages.md). ``` cd packages/types yarn buidl ``` ## 5 generators create a generator for the new plugin in packages/telescope/src/generators ``` ├── create-aggregated-lcd-client.ts ├── ...other generators ├── create-helpers.ts ├── create-index.ts ``` | file | purpose | | --------- | ----------------------------------------------------------------------------- | | create-helpers.ts | Create common helper files as needed. Edit this if a new common helper's added. | | create-index.ts | automatically include all generated files into index.ts. | A generator is a function with at least one parameter called builder, which carries the context and files to generate. And bundler as the second parameter if needed, which carries the context to generate bundle files. In this example, we create a simple generator ```js export const plugin = ( builder: TelescopeBuilder, bundler: Bundler ) => { // see if the plugin's enabled. if (!builder.options.reactQuery.enabled) { return; } // define a name of the file. const localname = 'hooks.ts'; ...code for creating proto ref. // create context or get context from bundler. const pCtx = new TelescopeParseContext( ref, builder.store, builder.options ); // generate code using ast functions. const ast = createScopedRpcHookFactory( pCtx.proto, obj, 'createRpcQueryHooks' ) // generate imports added by context.addUtil const imports = fixlocalpaths(aggregateImports(pCtx, {}, localname)); const importStmts = getImportStatements( localname, imports ); // construct the AST const prog = [] .concat(importStmts) .concat(ast); // write the file. const filename = join(builder.outPath, localname); builder.files.push(localname); writeAstToFile(builder.outPath, builder.options, prog, filename); }; ``` ## 6 add generator to builder add newly created generator to packages/telescope/src/builder.ts ```js export class TelescopeBuilder { async build() { ... invoking generators // find a proper place for new generators. // create all common helper files. createHelpers(this); // finally, write one index file with all files, exported createIndex(this); } } ``` ## 7 add a test and generate fixtures in this example, we generate fixtures to verify the code has been generated as expected. Please make sure don't output to __fixtures__/output1 or output2, those're production code that shouldn't be broken. Any test code should be generated under folders under v-next folder. ```js const outPath = __dirname + '/../../../__fixtures__/v-next/outputv3'; // setup options with your new plugin enabled const options: TelescopeOptions = { ...options }; const input: TelescopeInput = { outPath, // input path with proto files protoDirs: [__dirname + '/../../../__fixtures__/chain1'], options }; const telescope = new TelescopeBuilder(input); describe('bundle package registries and root file names', () => { it('bundleRegistries', async () => { await telescope.build(); const registries = bundleRegistries(telescope); const result = registries.map(reg => ({ ['package']: reg.package, contexts: parseContextsForRegistry(reg.contexts as TelescopeParseContext[]) })) }); }) ``` ---------------------------------- Helpers # Common helpers or utils when generating code, sometimes we need to import helpers, utils or packages. In that case, using addUtil method in context when building ast is recommend, since we only add utils as needed without worrying repetitive import: ``` context.addUtil('useQuery'); ``` We need to edit several files to make this work under packages/telescope: ``` ├── telescope ├── src ├── generators ├── create-helpers.ts ├── helpers ├── ...all helpers.ts ├── index.ts ├── utils ├── index.ts ``` | folder | purpose | | --------- | ----------------------------------------------------------------------------- | | all helpers.ts | files that contain common code, will be used in create-helper.ts | | helpers/index.ts | export files defined in this folder. | | create-helpers.ts | create helper files as needed using contents defined in helper folder. | | utils/index.ts | this defines mappings of common functions and packages. And defines alias for common helper files.| ## 0 mapping functions with packages add mapping between function and package in packages/telescope/src/utils/index.ts. In this example, two ways of mappings are shown: ```js export const UTILS = { // this will create import * as _m0 from "protobufjs/minimal"; _m0: { type: 'namespace', path: 'protobufjs/minimal', name: '_m0' }, ... other mappings // this will create import useQuery from '@tanstack/react-query' useQuery: '@tanstack/react-query', }; ``` If we have to import from the helper files created by ourselves, we can continue reading sections below. ## 1 create helper contents In this example, content of a common helper will be created and exported under folder packages/telescope/src/helpers ```js export const useEndpoint = defineStore('endpoint', { }) `; ``` And export this file in packages/telescope/src/helpers/index.ts. ```js export * from './endpoint'; ``` ## 2 generate helper file In this example, the endpoint helper will be generated by packages/telescope/src/generators/create-helpers.ts ```js import { endpoints } from '../helpers'; export const plugin = ( builder: TelescopeBuilder ) => { write(builder, 'helpers.ts', internal); // generate based on option if (builder.options.isEndpointsIncluded) { builder.files.push('endpoints.ts'); write(builder, 'endpoints.ts', endpoints); } }; ``` ## 3 set alias for the helper file and mapping we have to set an alias for generated helper file in packages/telescope/src/utils/index.ts. The rule is taking the filename part without extension and then surrounding with "__". For example: for the file "endpoints.ts", the alias would be "\_\_endpoints\_\_". In this example, an alias is created and a mapping is built. ``` export const UTILS = { ... other mappings useEndpoint: "__endpoints__" }; export const UTIL_HELPERS = [ ... other aliases // this will be automatically resolved to correct path in the final result. '__endpoints__' ]; ``` ---------------------------------- Migration ## Migration to v1.0 ### deprecated aminoEncoding.useRecursiveV2encoding If `aminoEncoding.useRecursiveV2encoding: false` is used in your config, and want to keep it(not recommended). It's changed into `aminoEncoding.useLegacyInlineEncoding: true`. We have removed the `useRecursiveV2encoding` option, then Telescope will use `useLegacyInlineEncoding: false` as default value. And the new version of the logic of this part will be applied. And we'll deprecate `useLegacyInlineEncoding` option eventually to make sure the new logic will be applied. ### interfaces.enabled the default value of `interfaces.enabled` handling decoding of Any type has been changed into true. If it's wanted to be kept off, please set explicitly: ``` interfaces: { enabled: false, }, ``` ### prototypes.typingsFormat.customTypes.useCosmosSDKDec the default value of `prototypes.typingsFormat.customTypes.useCosmosSDKDec` has been changed into true for decimal fields to be properly decoded using Decimal.fromAtomics. Even though it's not recommended, if it's wanted to be kept off, please set explicitly: ``` prototypes: { typingsFormat: { customTypes: { useCosmosSDKDec: false } }, } ``` ### BigInt The default value of `prototypes.typingsFormat.num64` has been changed into 'bigint' for better handling int64 fields. If it's wanted to be kept to be 'long', please set explicitly: ``` prototypes: { typingsFormat: { num64: 'long', }, } ``` ### useDeepPartial The default value of `prototypes.typingsFormat.useDeepPartial` has been changed into 'false'. If it's wanted to be kept on, please set explicitly: ``` prototypes: { typingsFormat: { useDeepPartial: true }, } ``` ### typeUrls The default values of `prototypes.addTypeUrlToDecoders` and `prototypes.addTypeUrlToObjects` have been changed into 'true'. If they're wanted to be kept off, please set explicitly: ``` prototypes: { addTypeUrlToDecoders: false, addTypeUrlToObjects: false, } ``` ### prototypes.methods The default values of listed methods have been changed to 'true'. ``` prototypes: { methods: { toAmino: true, fromAmino: true, toProto: true, fromProto: true }, } ``` If they're wanted to be kept off, please set explicitly: ``` prototypes: { methods: { toAmino: false, fromAmino: false, toProto: false, fromProto: false }, } ``` ---------------------------------- Packages # Packages and workspace We use lerna/yarn workspaces. You'll see we have in our `./packages` folder numerous packages: ``` ├── ast ├── babel ├── lcd ├── parser ├── telescope ├── test ├── types └── utils ``` | folder | purpose | | --------- | ----------------------------------------------------------------------------- | | ast | for creating new AST functions | | lcd | for giving LCD clients to generated code that uses the LCD client | | parser | protobuf parser, and protobuf traversal | | telescope | the CLI, as well as the main `telescope` builder | | starship | using starship for testing things locally or in CI workflow | | types | types and `TelescopeOptions` objects | | utils | other utilities | ### Editing interdependent packages Because we are using lerna/yarn workspaces, packages in the `./packages` folder act as local `npm` modules. If you edit a package, make sure to cd into it, and run `yarn buidl`, or separately, `yarn build` and `yarn build:ts`. For example, if you edit a file in `./packages/types` and you want to make sure it's up-to-date in another package, such as `./packages/ast`, the changes propagate after you run these commands: ``` yarn build yarn build:ts # now the types will also show up ``` ---------------------------------- Setup ## Setup ### Initial setup In the root directory: ``` yarn bootstrap ## this installs dependencies and links the local packages instead of the versions that live on npm yarn build ## this recursively builds all packages (this command also exists inside each package) ``` ---------------------------------- Starship Tests # Starship Tests Starship's setup for testing generated features on a virtual environment. Starship tests can be found in: 'telescope/packages/starship'. ## Dependencies ### Dockers Install and start docker, then check in the panel: "Settings -> Kubernetes" to make sure k8s’s enabled and running. ### Cluster tools Install related cluster tools following the instruction [here](https://docs.cosmology.zone/starship/get-started/step-1): - kind - kubectl - helm - yq ## Start Starship Go to the folder 'telescope/packages/starship'. - Double check you do clean up before start, skip this if you're sure previous pods have been cleaned up: ```bash make stop ``` ```bash # using kubectl get pods to know the status of running pods kubectl get pods #or keep watching by: kubectl get pods -w # wait for "No resources found in default namespace." ``` - With these two steps you’re ready to go: 1. To start pods ```bash make install ``` ```bash # using kubectl get pods to know the status of running pods kubectl get pods #or keep watching by: kubectl get pods -w # wait for all listed pods running like: # cosmos-2-genesis-0 3/3 # explorer-5575dc8774-kxs8v 1/1 # etc... ``` 2. To open ports for access ```bash make port-forward ``` ## Generate Code Setup scripts for generating code in the folder 'telescope/packages/starship/scripts/codegen.js' Generate code using: ```bash npm run codegen ``` Code'll be generated in the folder 'telescope/packages/starship/src/codegen' ## Test ```bash # do tests, don't run all tests at the same time on your local machine: # 1 setup: npx jest __tests__/v1/setup.test.ts # 2 authz: npx jest __tests__/v1/authz.test.ts ``` ## Stop the infra When tests have finished, better stop the infra for starting successfully next time. ```bash npm run e2e:stop ``` Which will * Stop port-forwarding the traffic to your local * Delete all the helm charts deployed ## Cleanup kind (optional) If you are using kind for your kubernetes cluster, you can delete it with ```bash npm run e2e:clean ``` ---------------------------------- Tests # Tests `cd` into a package and run the tests, for example: ``` cd ./packages/ast yarn test:watch ``` If you are doing major development, you'll probably want to open up a few windows, and run tests in at least these three packages: the `ast` package: ``` cd ./packages/ast yarn test:watch ``` the `parser` package: ``` cd ./packages/parser yarn test:watch ``` the `telescope` package will also generate more fixtures, so you can see the end result. ``` cd ./packages/telescope yarn test:watch ``` ---------------------------------- Working With Asts # working with ASTs ### 0 navigate to the `ast` package ```sh cd ./packages/ast ``` ### 1 edit the fixture edit `./scripts/fixture.ts`, for example: ```js // ./scripts/fixture.ts export interface InstantiateMsg { admin?: string | null; members: Member[]; } ``` ### 2 run AST generator ``` yarn test:ast ``` ### 3 look at the JSON produced ``` code ./scripts/test-output.json ``` We use the npm module `ast-stringify` to strip out unnecessary props, and generate a JSON for reference. You will see a `File` and `Program`... only concern yourself with the `body[]`: ```json { "type": "File", "errors": [], "program": { "type": "Program", "sourceType": "module", "interpreter": null, "body": [ { "type": "ExportNamedDeclaration", "exportKind": "type", "specifiers": [], "source": null, "declaration": { "type": "TSInterfaceDeclaration", "id": { "type": "Identifier", "name": "InstantiateMsg" }, "body": { "type": "TSInterfaceBody", "body": [ { "type": "TSPropertySignature", "key": { "type": "Identifier", "name": "admin" }, "computed": false, "optional": true, "typeAnnotation": { "type": "TSTypeAnnotation", "typeAnnotation": { "type": "TSUnionType", "types": [ { "type": "TSStringKeyword" }, { "type": "TSNullKeyword" } ] } } }, { "type": "TSPropertySignature", "key": { "type": "Identifier", "name": "members" }, "computed": false, "typeAnnotation": { "type": "TSTypeAnnotation", "typeAnnotation": { "type": "TSArrayType", "elementType": { "type": "TSTypeReference", "typeName": { "type": "Identifier", "name": "Member" } } } } } ] } } } ], "directives": [] }, "comments": [] } ``` ### 4 code with `@babel/types` using the JSON as a reference NOTE: ideally you should be writing a test with your generator! ```js import * as t from '@babel/types'; export const createNewGenerator = () => { return t.exportNamedDeclaration( t.tsInterfaceDeclaration( t.identifier('InstantiateMsg'), null, [], t.tsInterfaceBody([ // ... more code ... ]) ) ); }; ``` NOTE, see our docs on [creating new generators](https://github.com/hyperweb-io/telescope/blob/main/docs/creating-new-generators.md) which shows how to create and run a test. ---------------------------------- Advanced Install And Use # Advanced Installation and Usage This document outlines all available methods to install and use Telescope. ## Telescope CLI Install Telescope globally: ```sh npm install -g @cosmology/telescope ``` ### Generate a Package Use the interactive prompt: ```sh telescope generate ``` Or specify options directly: ```sh telescope generate --access public --userfullname "Your Name" --useremail "your@email.com" --module-desc "Your module description" --username "your-username" --license MIT --module-name "your-module" --chain-name cosmos --use-npm-scoped ``` #### Available Options: - `--userfullname`: Your full name - `--useremail`: Your email - `--module-desc`: Module description - `--username`: GitHub username - `--module-name`: Module name - `--chain-name`: Chain name - `--access`: Package access (`public` or `private`) - `--use-npm-scoped`: Use npm scoped package (only works with `--access public`) - `--license`: License type ### Download Protocol Buffers Basic usage: ```sh telescope download ``` With a config file: ```sh telescope download --config ./protod.config.json --out ./git-modules ``` protod.config.json example: ```json { "repos": [ { "owner": "cosmos", "repo": "cosmos-sdk", "branch": "release/v0.50.x" }, { "owner": "cosmos", "repo": "ibc-go" }, ], "protoDirMapping": { "gogo/protobuf/master": ".", "googleapis/googleapis/master": ".", "protocolbuffers/protobuf/main": "src" }, "outDir": "protos", "ssh": true, "tempRepoDir": "git-modules", "targets": [ "cosmos/**/*.proto", "cosmwasm/**/*.proto", "ibc/**/*.proto", ] } ``` From a specific repository: ```sh telescope download --git-repo owner/repository --targets cosmos/auth/v1beta1/auth.proto ``` ### Transpile Proto Files With default options: ```sh telescope transpile ``` With custom configuration: ```sh telescope transpile --config telescope-config.json ``` telescope-config.json exmaple: ```json { "protoDirs": [ "./protos/" ], "outPath": "./codegen/", "options": { "classesUseArrowFunctions": true, "env": "v-next", "useInterchainJs": true, "useSDKTypes": false, "prototypes": { "enableRegistryLoader": false, "enableMessageComposer": false, "enabled": true, "parser": { "keepCase": false }, "methods": { "fromJSON": false, "toJSON": false, "encode": true, "decode": true, "fromPartial": true, "toAmino": true, "fromAmino": true, "fromProto": false, "toProto": false, "fromProtoMsg": false, "toProtoMsg": false, "toAminoMsg": true, "fromAminoMsg": true }, "addTypeUrlToDecoders": false, "addTypeUrlToObjects": true, "addAminoTypeToObjects": true, "typingsFormat": { "duration": "duration", "timestamp": "date", "useExact": false, "useDeepPartial": true, "num64": "bigint", "customTypes": { "useCosmosSDKDec": true, "useEnhancedDecimal": false }, "useTelescopeGeneratedType": true, "autoFixUndefinedEnumDefault": true } }, "bundle": { "enabled": false }, "stargateClients": { "enabled": false }, "lcdClients": { "enabled": false }, "rpcClients": { "enabled": false }, "helperFunctions": { "enabled": true, "useGlobalDecoderRegistry": true, "hooks": { "react": true, "vue": false } }, "interfaces": { "enabled": true, "useGlobalDecoderRegistry": true, "registerAllDecodersToGlobal": false, "useUnionTypes": true }, "aminoEncoding": { "enabled": true, "useLegacyInlineEncoding": false, "disableMsgTypes": false, "useProtoOptionality": true, "customTypes": { "useCosmosSDKDec": true } } } } ``` ## Create Interchain App (CIA) Install CIA globally: ```sh npm install -g create-interchain-app ``` Create a new project with the Telescope boilerplate: ```sh cia --boilerplate telescope ``` Navigate to your project directory: ```sh cd ./your-project/packages/your-module yarn install ``` Download protos and generate code: ```sh yarn download-protos yarn codegen ``` ## Create Cosmos App (CCA) Install CCA globally: ```sh npm install -g create-cosmos-app ``` Create a new package with the Telescope boilerplate: ```sh cca --boilerplate telescope ``` Navigate to your package directory: ```sh cd ./your-project/packages/telescope yarn install ``` Download protos and generate code: ```sh telescope download --config ./your.config.json yarn codegen ``` ## Manual Installation Add Telescope to your project: ```sh yarn add --dev @cosmology/telescope ``` Install required dependencies: ```sh yarn add @cosmjs/amino @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc ``` Use either the programmatic API or the CLI with npm/yarn prefixes: ```sh yarn telescope generate npx telescope download ``` ## Programmatic Usage First, add Telescope and dependencies: ```sh yarn add --dev @cosmology/telescope yarn add @cosmjs/amino @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc ``` ### Downloading Protos Programmatically ```js import downloadProtos from '@cosmology/telescope/main/commands/download' const config = { repos: [ { owner: "cosmos", repo: "cosmos-sdk", branch: "release/v0.50.x" }, { owner: "cosmos", repo: "ibc-go" }, ], protoDirMapping: { "gogo/protobuf/master": ".", "googleapis/googleapis/master": ".", "protocolbuffers/protobuf/main": "src" }, outDir: "protos", ssh: false, tempRepoDir: "git-modules", targets: [ "cosmos/**/*.proto", "ibc/**/*.proto", ] }; downloadProtos(config) .then(() => console.log('✅ Proto download completed')) // @ts-ignore .catch((error) => { console.error('❌ Proto download failed:', error); process.exit(1); }); ``` ### Generating Code Programmatically ```js import { join } from 'path'; import telescope from '@cosmology/telescope'; import { sync as rimraf } from 'rimraf'; // Define input and output paths const protoDirs = [join(__dirname, '/../proto')]; const outPath = join(__dirname, '../src'); rimraf(outPath); // Run Telescope with options telescope({ protoDirs, outPath, // all options are totally optional options: { aminoEncoding: { enabled: true }, lcdClients: { enabled: false }, rpcClients: { enabled: false, camelCase: true }, // Package-specific options 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); }); ``` ### Example: Build Script Integration ```js // scripts/codegen.js 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/generated'); rimraf(outPath); telescope({ protoDirs, outPath, options: { tsDisable: { disableAll: false, patterns: ['**/amino/**'] }, eslintDisable: { patterns: ['**/tx.amino.ts'] }, prototypes: { includePackageVar: true, typingsFormat: { useDeepPartial: true, timestamp: 'date', duration: 'duration' } } } }).then(() => { console.log('✨ Code generation complete'); }); ---------------------------------- Broadcasting Messages # Broadcasting Messages This document provides a reference for broadcasting transactions with messages in Telescope-generated clients. ## Transaction Broadcasting Methods | Method | Description | Best For | | ------ | ----------- | -------- | | `signAndBroadcast` | Signs and broadcasts transactions in one step | Most use cases | | `sign` + `broadcastTx` | Separates signing and broadcasting steps | Multi-signature or offline signing | | `signAndBroadcastWithoutBalanceCheck` | Skips balance check for speedier execution | When balance is known to be sufficient | ## Using SigningStargateClient The standard way to broadcast transactions: ```typescript import { SigningStargateClient } from "@cosmjs/stargate"; // Create a SigningStargateClient (see Creating Signers documentation) const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, signer, options); // Broadcast a transaction const result = await client.signAndBroadcast( signerAddress, messages, fee, memo ); ``` ## Telescope Convenience Methods ```typescript import { getSigningClient } from "./my-chain-client"; // Create a chain-specific client const client = await getSigningClient({ rpcEndpoint, signer }); // Broadcast a transaction using convenience method const result = await client.sendMessages( signerAddress, messages, fee, memo ); ``` ## Broadcasting Options | Option | Description | Default | | ------ | ----------- | ------- | | `broadcastTimeoutMs` | Maximum time to wait for confirmation | 60000 | | `broadcastPollIntervalMs` | Time between broadcast status checks | 3000 | | `gasAdjustment` | Multiplier for simulated gas | 1.5 | | `gasPrice` | Price per unit of gas | Chain-specific | ## Transaction Result Structure ```typescript interface BroadcastTxResult { readonly height: number; // Block height at which tx was committed readonly transactionHash: string; // The hash of the transaction readonly rawLog: string; // Raw log information readonly code?: number; // 0 for success, error code otherwise readonly data?: Uint8Array; // Return value from transaction readonly gasUsed: number; // Amount of gas used readonly gasWanted: number; // Amount of gas requested readonly events: readonly Event[]; // Events emitted by the transaction } ``` ## Broadcasting Workflow 1. **Preparation**: Create messages and fee 2. **Simulation** (optional): Estimate gas 3. **Signing**: Create signature using signer 4. **Broadcasting**: Send to network 5. **Confirmation**: Wait for inclusion in a block 6. **Result Processing**: Handle success/failure ## Transaction Simulation ```typescript // Simulate to estimate gas requirements const gasEstimated = await client.simulate( signerAddress, messages, memo ); // Apply safety factor const gasLimit = Math.round(gasEstimated * 1.3); // Create fee with estimated gas const fee = { amount: [{ denom: "uatom", amount: "5000" }], gas: gasLimit.toString() }; ``` ## Broadcast Modes | Mode | Description | Use Case | | ---- | ----------- | -------- | | `block` | Wait for block inclusion | Standard usage, ensures transaction success | | `sync` | Return after mempool validation | Faster response, when block confirmation not needed | | `async` | Return immediately | Maximum speed, no validation | ```typescript // Set broadcast mode (applicable for direct ABCI clients) const result = await client.broadcastTx( signedTx, timeoutMs, broadcastMode ); ``` ## Error Handling ```typescript try { const result = await client.signAndBroadcast( signerAddress, messages, fee, memo ); if (result.code === 0) { console.log("Transaction successful"); } else { console.error(`Transaction failed with code ${result.code}: ${result.rawLog}`); } } catch (error) { if (error.message.includes("insufficient funds")) { console.error("Account has insufficient funds to pay for fees"); } else if (error.message.includes("out of gas")) { console.error("Transaction ran out of gas"); } else if (error.message.includes("account sequence mismatch")) { console.error("Account sequence mismatch, retry with updated sequence"); } else { console.error("Broadcasting error:", error); } } ``` ## Monitoring Transaction Status ```typescript // Get transaction status after broadcasting const txStatus = await client.getTx(txHash); // Check if transaction was included in a block if (txStatus) { console.log("Transaction included at height:", txStatus.height); console.log("Transaction success:", txStatus.code === 0); } else { console.log("Transaction not yet included in a block"); } ``` ## Sequence Number Management ```typescript // Get account data to check sequence const account = await client.getAccount(signerAddress); if (!account) { throw new Error("Account not found"); } console.log("Current sequence:", account.sequence); // For manual sequence management in advanced cases const signDoc = { chainId, accountNumber: account.accountNumber, sequence: account.sequence, fee, msgs: messages, memo }; ``` ## Multi-Signature Transactions ```typescript // Collect signatures offline const signatures = await Promise.all( signers.map(signer => signer.sign(signerAddress, signDoc)) ); // Combine signatures const combinedSignature = combineSignatures(signatures); // Broadcast with combined signature const signedTx = createSignedTx(signDoc, combinedSignature); const result = await client.broadcastTx(signedTx); ``` ## Best Practices 1. Always simulate transactions first to estimate gas 2. Apply a safety margin to estimated gas (1.3-1.5x) 3. Handle account sequence mismatches with retries 4. Verify transaction success by checking result code 5. Store transaction hashes for later queries 6. Use appropriate broadcast modes for your use case 7. Implement retry logic for transient failures 8. Monitor network congestion and adjust gas prices ---------------------------------- Calculating Fees # Calculating Fees This document provides a reference for calculating transaction fees for Cosmos SDK blockchains using Telescope-generated types. ## Fee Structure | Component | Description | | --------- | ----------- | | Gas | Computational resources required to process a transaction | | Gas Price | Price per unit of gas in a specific denomination | | Fee | Total amount paid for transaction processing (Gas × Gas Price) | ## Fee Object Structure ```typescript export interface StdFee { readonly amount: readonly Coin[]; readonly gas: string; } export interface Coin { readonly denom: string; readonly amount: string; } ``` ## Gas Estimation Methods | Method | Description | Usage | | ------ | ----------- | ----- | | Manual Specification | Explicitly set gas limit | Simple transactions with known gas requirements | | Simulation | Estimate gas by simulating transaction | Complex transactions with variable gas needs | | Auto | Automatically estimate and adjust gas | General-purpose usage | ## Manual Fee Calculation ```typescript // Define a standard fee with fixed gas const fee = { amount: [{ denom: "uatom", amount: "5000" }], gas: "200000" // 200,000 gas units }; ``` ## Using Gas Prices ```typescript import { calculateFee, GasPrice } from "@cosmjs/stargate"; // Define gas price (e.g., 0.025 uatom per gas unit) const gasPrice = GasPrice.fromString("0.025uatom"); // Calculate fee for a given gas limit const fee = calculateFee(200000, gasPrice); // Result: { amount: [{ denom: "uatom", amount: "5000" }], gas: "200000" } ``` ## Automatic Gas Estimation ```typescript import { SigningStargateClient } from "@cosmjs/stargate"; // Auto gas estimation when sending transactions const result = await client.signAndBroadcast( sender, messages, "auto" // Use auto gas estimation ); ``` ## Gas Estimation via Simulation ```typescript import { SigningStargateClient } from "@cosmjs/stargate"; // First simulate the transaction const gasEstimate = await client.simulate( sender, messages, memo ); // Apply a safety margin (e.g., 1.3x the estimated gas) const gasLimit = Math.round(gasEstimate * 1.3); // Calculate fee with the estimated gas const fee = calculateFee(gasLimit, gasPrice); ``` ## Fee Adjustment Strategies | Strategy | Description | Use Case | | -------- | ----------- | -------- | | Fixed Multiplier | Multiply estimated gas by a factor | General usage (1.3-1.5x typical) | | Minimum Gas | Set a floor for gas estimation | Ensure transaction validity | | Dynamic Pricing | Adjust gas price based on network congestion | Prioritize during high traffic | ## Fee Estimation with Telescope-generated Clients ```typescript import { createTxClient } from "./tx"; const txClient = await createTxClient({ signer: wallet, rpcEndpoint: "https://rpc.cosmos.network" }); // Simulate to estimate gas const gasEstimate = await txClient.simulate({ messages, signer: sender }); // Apply safety margin and calculate fee const gasLimit = Math.round(gasEstimate * 1.3); const fee = { amount: [{ denom: "uatom", amount: (gasLimit * 0.025).toString() }], gas: gasLimit.toString() }; ``` ## Fee Denom Selection ```typescript // Get available account balances const { balances } = await queryClient.cosmos.bank.v1beta1.allBalances({ address: sender }); // Select appropriate denom for fees function selectFeeDenom(balances, preferredDenom = "uatom") { // First try preferred denom const preferred = balances.find(coin => coin.denom === preferredDenom); if (preferred && parseInt(preferred.amount) > 5000) { return preferredDenom; } // Fallback to first denom with sufficient balance const fallback = balances.find(coin => parseInt(coin.amount) > 5000); return fallback ? fallback.denom : preferredDenom; } const feeDenom = selectFeeDenom(balances); ``` ## Fee Grants Some chains support fee grants, allowing one account to pay fees for another: ```typescript import { MsgGrantAllowance } from "./cosmos/feegrant/v1beta1/tx"; import { BasicAllowance } from "./cosmos/feegrant/v1beta1/feegrant"; // Create a basic allowance const expirationDate = new Date(); expirationDate.setMonth(expirationDate.getMonth() + 1); // 1 month expiry const basicAllowance = BasicAllowance.fromPartial({ spendLimit: [{ denom: "uatom", amount: "1000000" }], expiration: expirationDate }); // Grant fee allowance const grantMsg = MsgGrantAllowance.fromPartial({ granter: "cosmos1granter...", grantee: "cosmos1grantee...", allowance: Any.pack(basicAllowance, "/cosmos.feegrant.v1beta1.BasicAllowance") }); ``` ## Chain-Specific Fee Parameters | Parameter | Value Source | Example | | --------- | ----------- | ------- | | Minimum Gas Prices | Chain configuration | "0.0025uatom" | | Fee Denominations | Chain-accepted tokens | ["uatom", "uosmo", ...] | | Gas Adjustment | Client configuration | 1.3 | ## Gas Weights for Common Operations | Operation | Approximate Gas Cost | Notes | | --------- | -------------------- | ----- | | Token Transfer | 80,000 - 100,000 | Single MsgSend | | Delegate | 140,000 - 180,000 | MsgDelegate | | Undelegate | 150,000 - 190,000 | MsgUndelegate | | Claim Rewards | 150,000 - 250,000 | MsgWithdrawDelegatorReward | | Submit Proposal | 200,000+ | Depends on proposal content size | | Vote on Proposal | 100,000 - 140,000 | MsgVote | ## Handling Out of Gas Errors ```typescript try { const result = await client.signAndBroadcast(sender, messages, fee); // Process result } catch (error) { if (error.message.includes("out of gas")) { // Increase gas and retry const newGasLimit = Math.round(parseInt(fee.gas) * 1.5); const newFee = { amount: fee.amount, gas: newGasLimit.toString() }; // Retry with new fee const retryResult = await client.signAndBroadcast(sender, messages, newFee); // Process retry result } else { // Handle other errors console.error("Transaction failed:", error); } } ``` ## Best Practices 1. Always use gas estimation for complex transactions 2. Apply a safety margin of 1.3x to 1.5x to estimated gas 3. Set a reasonable maximum gas limit to avoid excessive fees 4. Check account balances before sending to ensure sufficient funds for fees 5. Consider network congestion and adjust gas prices accordingly 6. Use appropriate denominations accepted by validators 7. Test fee calculations on testnets before mainnet deployment ---------------------------------- Clients ## 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: ```js import { getSigningOsmosisClient } from 'osmojs'; const client = await getSigningOsmosisClient({ rpcEndpoint, signer // OfflineSigner }); ``` ## Creating Signers To broadcast messages, you'll want to use either [keplr](https://docs.keplr.app/api/cosmjs.html) 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: ```js import { getOfflineSigner as getOfflineSignerAmino } from 'cosmjs-utils'; ``` ### Proto Signer ```js 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. ```js 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: ```js 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. ```ts const options: TelescopeOptions = { lcdClients: { enabled: true; } }; ``` If you use the `lcdClients.scoped` array, you can scope to only the modules of your interest. ```ts 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: ```js 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: ```js 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. ```ts 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. ```ts 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: ```js 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. ```ts 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`: ```js 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. ```ts 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: ```js 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. ```js 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 ```js 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: ```js 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: ```json 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: ```js 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; } } ``` ---------------------------------- Composing Messages # Composing Messages This document provides a reference for composing messages using Telescope-generated types to interact with Cosmos SDK blockchains. ## Message Structure | Component | Description | | --------- | ----------- | | Message Type | Protocol Buffer message type with corresponding TypeScript interface | | Message Content | Data conforming to the message structure | | Encoding | Binary or JSON encoding of the message | | Signing | Cryptographic signature of the message for on-chain verification | ## Available Message Types Message types are typically organized by module. Common modules include: | Module | Description | Examples | | ------ | ----------- | -------- | | `bank` | Token transfers | `MsgSend`, `MsgMultiSend` | | `staking` | Staking and delegation | `MsgDelegate`, `MsgUndelegate` | | `gov` | Governance | `MsgSubmitProposal`, `MsgVote` | | `distribution` | Fee distribution | `MsgWithdrawDelegatorReward` | | `authz` | Authorization | `MsgExec`, `MsgGrant` | ## Creating Messages Messages can be created using the `.fromPartial()` method: ```typescript import { MsgSend } from "./cosmos/bank/v1beta1/tx"; // Create a bank send message const sendMsg = MsgSend.fromPartial({ fromAddress: "cosmos1...", toAddress: "cosmos1...", amount: [ { denom: "uatom", amount: "1000000" } ] }); ``` ### Message Validation Telescope-generated types perform runtime validation when using helper methods: ```typescript // Will throw an error if invalid const validatedMsg = MsgSend.fromJSON({ from_address: "cosmos1...", to_address: "cosmos1...", amount: [ { denom: "uatom", amount: "1000000" } ] }); ``` ## Message Composition Methods | Method | Description | Example | | ------ | ----------- | ------- | | `fromPartial` | Creates a message with default values for missing fields | `MsgSend.fromPartial({...})` | | `fromJSON` | Creates a message from a JSON object | `MsgSend.fromJSON({...})` | | `encode` | Encodes a message to binary | `MsgSend.encode(msg, writer)` | | `decode` | Decodes a binary message | `MsgSend.decode(bytes)` | ## MessageComposer For convenience, Telescope generates a `MessageComposer` class for each module: ```typescript import { bankComposer } from "./cosmos/bank/v1beta1/tx.composer"; const composedSendMsg = bankComposer.send({ fromAddress: "cosmos1...", toAddress: "cosmos1...", amount: [{ denom: "uatom", amount: "1000000" }] }); ``` ### MessageComposer Methods Each MessageComposer provides: | Property | Description | | -------- | ----------- | | `typeUrl` | The type URL for the message | | `value` | The message content | ## Transaction Composition Multiple messages can be combined in a single transaction: ```typescript // Compose multiple messages const messages = [ bankComposer.send({ fromAddress: "cosmos1...", toAddress: "cosmos1...", amount: [{ denom: "uatom", amount: "1000000" }] }), stakingComposer.delegate({ delegatorAddress: "cosmos1...", validatorAddress: "cosmosvaloper1...", amount: { denom: "uatom", amount: "5000000" } }) ]; ``` ## Protobuf vs. Amino Encoding Telescope supports both Protobuf and Amino encoding formats: | Format | Usage | Type Field | | ------ | ----- | ---------- | | Protobuf | Modern Cosmos SDKs | `typeUrl` (e.g., "/cosmos.bank.v1beta1.MsgSend") | | Amino | Legacy systems, some wallets | `type` (e.g., "cosmos-sdk/MsgSend") | ### Amino Conversion Example ```typescript import { AminoTypes } from "@cosmjs/stargate"; import { aminoConverters } from "./amino/converters"; const aminoTypes = new AminoTypes(aminoConverters); // Convert Protobuf to Amino format const aminoMsg = aminoTypes.toAmino({ typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: sendMsg }); // Convert back to Protobuf format const protoMsg = aminoTypes.fromAmino(aminoMsg); ``` ## Message Registry The registry maps message type URLs to their corresponding Protobuf types: ```typescript import { registry } from "./registry"; import { Registry } from "@cosmjs/proto-signing"; const protoRegistry = new Registry(); registry.forEach(([typeUrl, type]) => { protoRegistry.register(typeUrl, type); }); ``` ## Using Messages with Clients ### With Stargate Client ```typescript import { SigningStargateClient } from "@cosmjs/stargate"; import { MsgSend } from "./cosmos/bank/v1beta1/tx"; // Create a message const sendMsg = MsgSend.fromPartial({ fromAddress: "cosmos1...", toAddress: "cosmos1...", amount: [{ denom: "uatom", amount: "1000000" }] }); // Send transaction with message const result = await signingClient.signAndBroadcast( "cosmos1...", [ { typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: sendMsg } ], fee ); ``` ### With RPC Client ```typescript import { createTxClient } from "./tx"; const txClient = await createTxClient({ signer: wallet, rpcEndpoint: "https://rpc.cosmos.network" }); const result = await txClient.signAndBroadcast( "cosmos1...", [sendMsg], fee ); ``` ## Common Message Types ### Bank Messages ```typescript // MsgSend const msgSend = MsgSend.fromPartial({ fromAddress: "cosmos1...", toAddress: "cosmos1...", amount: [{ denom: "uatom", amount: "1000000" }] }); // MsgMultiSend const msgMultiSend = MsgMultiSend.fromPartial({ inputs: [ { address: "cosmos1...", coins: [{ denom: "uatom", amount: "1000000" }] } ], outputs: [ { address: "cosmos1a...", coins: [{ denom: "uatom", amount: "500000" }] }, { address: "cosmos1b...", coins: [{ denom: "uatom", amount: "500000" }] } ] }); ``` ### Staking Messages ```typescript // MsgDelegate const msgDelegate = MsgDelegate.fromPartial({ delegatorAddress: "cosmos1...", validatorAddress: "cosmosvaloper1...", amount: { denom: "uatom", amount: "1000000" } }); // MsgUndelegate const msgUndelegate = MsgUndelegate.fromPartial({ delegatorAddress: "cosmos1...", validatorAddress: "cosmosvaloper1...", amount: { denom: "uatom", amount: "1000000" } }); ``` ### Governance Messages ```typescript // MsgSubmitProposal const msgSubmitProposal = MsgSubmitProposal.fromPartial({ content: Any.pack(textProposal, "/cosmos.gov.v1beta1.TextProposal"), initialDeposit: [{ denom: "uatom", amount: "10000000" }], proposer: "cosmos1..." }); // MsgVote const msgVote = MsgVote.fromPartial({ proposalId: 1, voter: "cosmos1...", option: VoteOption.VOTE_OPTION_YES }); ``` ## Best Practices 1. Always use the `.fromPartial()` method to ensure default values are properly set 2. Keep type URLs consistent with the SDK version you're targeting 3. Register all message types you plan to use with the registry 4. Use MessageComposer for convenience when composing multiple messages 5. Verify message structure before sending to avoid on-chain errors ---------------------------------- Cosmwasm # CosmWasm Integration This document provides a reference for generating TypeScript SDKs for CosmWasm smart contracts using Telescope. ## Overview Telescope integrates with [@cosmwasm/ts-codegen](https://github.com/CosmWasm/ts-codegen) to generate TypeScript client libraries for CosmWasm smart contracts. This enables you to work with your smart contracts using strongly-typed interfaces in your frontend applications. ## Configuration To generate TypeScript SDKs for your CosmWasm contracts, add the `cosmwasm` option to your Telescope configuration: ```typescript import { TelescopeOptions } from "@cosmology/types"; const options: TelescopeOptions = { cosmwasm: { contracts: [ { name: "MyContract", dir: "./path/to/schema" } ], outPath: "./src/contracts" } }; ``` The `cosmwasm` option is a direct reference to the `TSBuilderInput` object from the `@cosmwasm/ts-codegen` package. ## Contract Configuration ### Basic Contract Setup At minimum, each contract configuration requires: | Property | Type | Description | | -------- | ---- | ----------- | | `name` | string | Name of the contract, used in generated class names | | `dir` | string | Path to the contract schema directory | Example: ```typescript { name: "SG721", dir: "./schema/sg721" } ``` ### Advanced Contract Configuration For more control, you can use additional configuration options: ```typescript { name: "Marketplace", dir: "./schema/marketplace", // Additional options camelCase: true, customTypes: { "cosmos.base.v1beta1.Coin": { module: "@cosmjs/stargate", type: "Coin" } }, messageComposer: { enabled: true } } ``` ## Output Configuration The `outPath` property specifies where the generated files should be placed: ```typescript { outPath: "./src/generated/contracts" } ``` ## Full Configuration Options | Property | Type | Description | Default | | -------- | ---- | ----------- | ------- | | `contracts` | array | List of contract configurations | Required | | `contracts[].name` | string | Contract name | Required | | `contracts[].dir` | string | Path to contract schema | Required | | `contracts[].camelCase` | boolean | Convert snake_case to camelCase | `true` | | `contracts[].customTypes` | object | Custom type mappings | `{}` | | `contracts[].messageComposer` | object | Message composer options | `{ enabled: true }` | | `contracts[].types` | object | Custom types generation options | `{ enabled: true }` | | `contracts[].client` | object | Client generation options | `{ enabled: true }` | | `contracts[].reactQuery` | object | React Query hooks options | `{ enabled: false, version: 'v4' }` | | `contracts[].recoil` | object | Recoil state management options | `{ enabled: false }` | | `contracts[].bundle` | object | Bundle generation options | `{ enabled: false }` | | `outPath` | string | Output directory for generated files | Current directory | ## Generated Files For each contract, the following files are generated: | File | Description | | ---- | ----------- | | `.types.ts` | TypeScript interfaces for contract interactions | | `.client.ts` | Client classes for interacting with the contract | | `.message-composer.ts` | Helper for composing contract messages | | `.react-query.ts` | React Query hooks (if enabled) | | `.recoil.ts` | Recoil state atoms and selectors (if enabled) | ## Using Generated Clients The generated clients provide a type-safe way to interact with your CosmWasm contracts: ```typescript import { MyContractClient } from "./generated/contracts/MyContract.client"; import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; // Create a client instance const client = new MyContractClient( signingCosmWasmClient, "cosmos1...", // Contract address {} // Default options ); // Query contract state const { balance } = await client.getBalance({ address: "cosmos1..." }); // Execute contract functions const result = await client.mint({ tokenId: "123", owner: "cosmos1..." }); ``` ## Message Composer The generated message composer helps create contract messages in the correct format: ```typescript import { MyContractMessageComposer } from "./generated/contracts/MyContract.message-composer"; const msgComposer = new MyContractMessageComposer("cosmos1..."); // Contract address // Create a message for broadcasting const mintMsg = msgComposer.mint({ tokenId: "123", owner: "cosmos1..." }); // Use with a SigningCosmWasmClient const result = await signingClient.signAndBroadcast( senderAddress, [mintMsg], fee ); ``` ## React Query Integration If you enable React Query integration, you'll get custom hooks for each query and mutation: ```typescript import { useMyContractGetBalance } from "./generated/contracts/MyContract.react-query"; function BalanceDisplay({ address }) { const { data, isLoading, error } = useMyContractGetBalance({ address }); if (isLoading) return
Loading...
; if (error) return
Error: {error.message}
; return
Balance: {data.balance}
; } ``` ## Customizing Type Imports You can customize how external types are imported using the `customTypes` configuration: ```typescript customTypes: { "cosmos.base.v1beta1.Coin": { module: "@cosmjs/stargate", type: "Coin" }, "SomeCustomType": { module: "../types", type: "MyType" } } ``` ## Dependencies To use the generated code, you'll need to install the following dependencies: ```bash npm install @cosmjs/cosmwasm-stargate @cosmjs/stargate ``` For React Query integration: ```bash npm install @tanstack/react-query ``` For Recoil integration: ```bash npm install recoil ``` ## Limitations - Schema files must follow the CosmWasm JSON Schema format - Custom integration might be needed for very complex contract architectures - Generated code relies on the runtime libraries specified in dependencies ``` ---------------------------------- Creating Signers # Creating Signers This document provides a reference for creating and using signers in Telescope generated clients. ## Overview To broadcast transactions to Cosmos-based blockchains, you need to sign them with a valid account. Telescope supports multiple signer implementations that work with its generated clients. ## Signer Types ### OfflineSigner The `OfflineSigner` interface from `@cosmjs/proto-signing` is the base interface for all signers in Telescope. It provides the following methods: - `getAccounts()`: Returns the accounts controlled by this signer - `signDirect(signerAddress, signDoc)`: Signs a transaction using the direct signing method ### DirectSecp256k1Wallet A wallet implementation that uses the Secp256k1 elliptic curve for signing. ```ts import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; // Create a wallet from a private key const wallet = await DirectSecp256k1Wallet.fromKey(privateKey, prefix); // Create a random wallet const wallet = await DirectSecp256k1Wallet.generate(prefix); ``` ### Amino Signer vs Proto Signer Telescope supports both Amino and Protobuf transaction signing: #### Amino Signer Most applications should use the Amino signer for compatibility with wallets like Keplr: ```ts import { getOfflineSigner as getOfflineSignerAmino } from 'cosmjs-utils'; const signer = await getOfflineSignerAmino({ mnemonic, chain }); ``` #### Proto Signer For direct Protobuf signing: ```ts import { getOfflineSigner as getOfflineSignerProto } from 'cosmjs-utils'; const signer = await getOfflineSignerProto({ mnemonic, chain }); ``` ## Wallet Integration ### Keplr Wallet Telescope clients integrate well with the Keplr browser extension: ```ts // Request connection to Keplr await window.keplr.enable(chainId); // Get the offline signer from Keplr const signer = window.keplr.getOfflineSigner(chainId); // Use with your Telescope client const client = await getSigningClient({ rpcEndpoint, signer }); ``` ### Using Mnemonics (Development Only) You can create signers from mnemonics for development purposes: ```ts 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 getOfflineSignerAmino({ mnemonic, chain }); ``` > **WARNING**: Never use plain-text mnemonics in production. Always use secure key management practices. ## Custom Registry Configuration When creating signers for custom chains, you may need to configure the registry and amino types: ```ts import { Registry } from '@cosmjs/proto-signing'; import { AminoTypes } from '@cosmjs/stargate'; import { cosmosAminoConverters, cosmosProtoRegistry, cosmwasmAminoConverters, cosmwasmProtoRegistry, ibcAminoConverters, ibcProtoRegistry, myChainAminoConverters, myChainProtoRegistry } from 'my-chain-js'; const protoRegistry = [ ...cosmosProtoRegistry, ...cosmwasmProtoRegistry, ...ibcProtoRegistry, ...myChainProtoRegistry ]; const aminoConverters = { ...cosmosAminoConverters, ...cosmwasmAminoConverters, ...ibcAminoConverters, ...myChainAminoConverters }; const registry = new Registry(protoRegistry); const aminoTypes = new AminoTypes(aminoConverters); const stargateClient = await SigningStargateClient.connectWithSigner( rpcEndpoint, signer, { registry, aminoTypes } ); ``` ## Key Management Best Practices When working with signers in production applications: 1. **Never store private keys or mnemonics in client-side code** 2. **Use hardware wallets or browser extensions when possible** 3. **Consider using secure enclave or TPM storage for server-side applications** 4. **Implement proper key rotation and revocation procedures** 5. **Use AES encryption for any stored key material** ## Common Issues and Solutions ### Chain Prefix Mismatch If you encounter address verification errors, ensure you're using the correct chain prefix: ```ts // For Cosmos Hub const wallet = await DirectSecp256k1Wallet.generate('cosmos'); // For Osmosis const wallet = await DirectSecp256k1Wallet.generate('osmo'); ``` ### Transaction Simulation Before broadcasting transactions, it's recommended to simulate them first: ```ts // Simulate the transaction to estimate gas const gasEstimated = await stargateClient.simulate(address, msgs, memo); // Add buffer for safety const gasWithBuffer = Math.round(gasEstimated * 1.3); // Create fee with estimated gas const fee = { amount: coins(0, 'uatom'), gas: gasWithBuffer.toString() }; ``` ## Related Documentation - [Stargate Clients](./stargate-clients.md) - [Composing Messages](./composing-messages.md) - [Calculating Fees](./calculating-fees.md) ---------------------------------- Dependencies # Dependencies This document provides a reference for the required dependencies when using Telescope-generated code. ## Required Dependencies When using code generated by Telescope, you need to install several CosmJS packages as dependencies: ```sh yarn add @cosmjs/amino @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc ``` Or using npm: ```sh npm install @cosmjs/amino @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc ``` ## Core Dependencies | Package | Description | Required For | | ------- | ----------- | ------------ | | `@cosmjs/amino` | Implements the Amino encoding format | Amino encoding support, transaction signing | | `@cosmjs/proto-signing` | Provides Protobuf-based transaction signing | Direct signing, registry management | | `@cosmjs/stargate` | High-level client for interacting with Cosmos SDK chains | Stargate client functionality | | `@cosmjs/tendermint-rpc` | Tendermint RPC client implementation | Lower-level blockchain interaction | ## Optional Dependencies Depending on which Telescope features you're using, you may need additional dependencies: ### LCD Clients If you're using the LCD Client functionality: ```sh yarn add @cosmology/lcd ``` ### React Query Integration If you're using the React Query integration: ```sh yarn add @tanstack/react-query ``` ### Vue Query Integration If you're using the Vue Query integration: ```sh yarn add @tanstack/vue-query ``` ### Recoil Integration If you're using the Recoil state management: ```sh yarn add recoil ``` ### Pinia Integration If you're using the Pinia state management: ```sh yarn add pinia ``` ### Mobx Integration If you're using the Mobx state management: ```sh yarn add mobx mobx-react ``` ### CosmWasm Integration If you're working with CosmWasm contracts: ```sh yarn add @cosmjs/cosmwasm-stargate ``` ## Peer Dependencies The following packages are often used alongside Telescope-generated code but are not strictly required: | Package | Description | | ------- | ----------- | | `@cosmjs/crypto` | Cryptographic primitives | | `@cosmjs/encoding` | Encoding utilities | | `@cosmjs/math` | Mathematical utilities, including Decimal type | | `@cosmjs/utils` | General utilities | ## Development Dependencies When setting up a new project with Telescope, you'll need: ```sh yarn add --dev @cosmology/telescope ``` ## Version Compatibility Ensure that your CosmJS packages have compatible versions. Typically, you should install the same version for all CosmJS packages. Current recommended CosmJS version: ```sh yarn add @cosmjs/amino@0.31.1 @cosmjs/proto-signing@0.31.1 @cosmjs/stargate@0.31.1 @cosmjs/tendermint-rpc@0.31.1 ``` ## Example package.json Here's an example `package.json` dependencies section for a project using Telescope-generated code with React: ```json { "dependencies": { "@cosmjs/amino": "^0.31.1", "@cosmjs/proto-signing": "^0.31.1", "@cosmjs/stargate": "^0.31.1", "@cosmjs/tendermint-rpc": "^0.31.1", "@cosmology/lcd": "^0.12.0", "@tanstack/react-query": "^4.29.5", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@cosmology/telescope": "^1.0.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "typescript": "^5.0.4" } } ``` ---------------------------------- Developing # Developing This document provides guidance for contributing to the Telescope project and developing with Telescope-generated code. ## Setting Up the Development Environment ### Prerequisites Before you begin, ensure you have the following installed: - Node.js (v14 or later) - Yarn (v1.x) - Git ### Clone the Repository ```sh git clone https://github.com/hyperweb-io/telescope.git cd telescope ``` ### Install Dependencies ```sh yarn install ``` ### Build Packages Build all packages in the monorepo: ```sh yarn build ``` ### Run Tests ```sh yarn test ``` ## Project Structure Telescope is organized as a monorepo with several packages: | Package | Description | | ------- | ----------- | | `packages/telescope` | Main Telescope code generator package | | `packages/types` | TypeScript type definitions | | `packages/ast` | Abstract syntax tree utilities | | `packages/parser` | Protobuf parsing utilities | | `packages/utils` | Shared utility functions | | `packages/lcd` | LCD client utilities | | `packages/starship` | Testing and development utilities | ## Making Changes ### Development Workflow 1. Create a new branch for your changes: ```sh git checkout -b feature/your-feature-name ``` 2. Make your changes to the codebase 3. Add tests for your changes 4. Run the tests to ensure they pass: ```sh yarn test ``` 5. Build the packages to ensure they compile correctly: ```sh yarn build ``` 6. Commit your changes with a descriptive commit message: ```sh git commit -m "feat: add new feature" ``` 7. Push your branch to GitHub: ```sh git push origin feature/your-feature-name ``` 8. Create a pull request on GitHub ### Commit Message Convention Telescope follows the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages: - `feat`: A new feature - `fix`: A bug fix - `docs`: Documentation changes - `style`: Code style changes (formatting, etc.) - `refactor`: Code changes that neither fix a bug nor add a feature - `perf`: Performance improvements - `test`: Adding or fixing tests - `chore`: Changes to the build process or auxiliary tools Example: ``` feat(parser): add support for custom annotations ``` ## Testing ### Running Tests Run all tests: ```sh yarn test ``` Run tests for a specific package: ```sh yarn workspace @cosmology/telescope test ``` Run a specific test: ```sh yarn test -t "test name" ``` ### Adding Tests When adding new features or fixing bugs, you should also add tests to ensure the functionality works as expected. Tests are located in the `__tests__` directory of each package. Example test: ```typescript import { parseProto } from '../src/parser'; describe('Parser', () => { it('should parse a simple proto file', () => { const proto = ` syntax = "proto3"; package example; message Test { string name = 1; } `; const result = parseProto(proto); expect(result.package).toBe('example'); expect(result.messages.length).toBe(1); expect(result.messages[0].name).toBe('Test'); }); }); ``` ## Debugging ### Debugging Telescope If you're developing Telescope itself, you can add debug logging to help understand what's happening: ```typescript import { logger } from '@cosmology/utils'; logger.debug('Some debug information', { data }); ``` Set the log level to debug: ```typescript import { setLogLevel } from '@cosmology/utils'; setLogLevel('debug'); ``` ### Debugging Generated Code If you're developing with Telescope-generated code, you can use the standard TypeScript debugging tools, such as: - VS Code debugging - Chrome DevTools with source maps - `console.log` statements (temporarily) ## Creating a New Release ### Versioning Telescope follows [Semantic Versioning](https://semver.org/): - MAJOR version for incompatible API changes - MINOR version for adding functionality in a backward-compatible manner - PATCH version for backward-compatible bug fixes ### Release Process 1. Update the version numbers in package.json files 2. Update the CHANGELOG.md files 3. Create a new release with a tag matching the version number 4. Publish to npm ```sh yarn publish ``` ## Best Practices ### Code Style - Use TypeScript for type safety - Follow the existing code style in the codebase - Use prettier for code formatting - Add JSDoc comments for public APIs ### Pull Requests - Keep PRs focused on a single topic - Add sufficient description of the changes - Reference related issues - Ensure all tests pass - Update documentation as needed ### Documentation When making changes, update the relevant documentation: - Update READMEs if necessary - Add JSDoc comments to new functions - Update examples if API changes are made ## Additional Resources - [Telescope GitHub Repository](https://github.com/hyperweb-io/telescope) - [Cosmos SDK Documentation](https://docs.cosmos.network/) - [CosmJS Documentation](https://cosmos.github.io/cosmjs/) - [Protobuf Documentation](https://developers.google.com/protocol-buffers/docs/overview) ---------------------------------- Helper Functions Configuration # Helper Functions Configuration This document provides a reference for configuring helper function generation in Telescope. ## Overview Helper functions provide a simplified interface to interact with your blockchain's services. Telescope can automatically generate these functions based on your proto definitions, allowing for cleaner and more intuitive code. ## Configuration Helper function generation is configured in the `helperFunctions` section of your Telescope options: ```typescript import { TelescopeOptions } from "@cosmology/types"; const options: TelescopeOptions = { helperFunctions: { enabled: true, hooks: { react: true, vue: false, }, include: { serviceTypes: ["Query", "Msg"], patterns: ["cosmos.gov.v1beta1.**", "cosmos.bank.v1beta1.*Send*"], }, nameMappers: { All: { "cosmos.gov.v1beta1.*Vote*": { funcBody: (ctx: AliasNameMappersContext) => { return `helper${ctx.name}`; }, hookPrefix: "use", }, }, Query: { funcBody: (ctx: AliasNameMappersContext) => { return `get${ctx.name}`; }, hookPrefix: "use", }, Msg: { funcBody: "unchanged", hookPrefix: "useTx", }, }, }, }; ``` ## Configuration Properties ### Basic Options | Property | Type | Description | Default | | ------------------------- | ------- | ------------------------------------------------------------- | ------------------------------ | | `helperFunctions.enabled` | boolean | Enable or disable helper function generation | `false` | | `helperFunctions.hooks` | object | Configuration for generating hook functions for React and Vue | `{ react: false, vue: false }` | ### Include Options | Property | Type | Description | Default | | -------------------------------------- | -------- | ----------------------------------------------- | -------------------------- | | `helperFunctions.include.serviceTypes` | string[] | Which service types to include (`Query`, `Msg`) | `undefined` (all types) | | `helperFunctions.include.patterns` | string[] | Glob patterns to match specific services | `undefined` (all services) | ### Name Mapping Options The `nameMappers` object allows you to customize how function names are generated. It has three categories: 1. `All` - Applied to all service types 2. `Query` - Applied only to Query services 3. `Msg` - Applied only to Msg services For each category, you can specify: | Property | Type | Description | Default | | ------------ | ------------------ | ----------------------------- | ------------------------------ | | `funcBody` | string or function | How to transform method names | Query: "get", Msg: "unchanged" | | `hookPrefix` | string | Prefix for hook functions | "use" | ## Pattern Matching Priority When multiple patterns match a service, the following priority rules apply: 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 ## Name Transformation The `funcBody` property can be either: 1. A string value: - `"unchanged"` - Keep the original method name - Any other string - Use as a prefix for the method name 2. A function: `(name: string) => string` - Receives the original method name - Returns the transformed name Example function transformation: ```typescript funcBody: (ctx: AliasNameMappersContext) => { return `execute${ctx.name.charAt(0).toUpperCase()}${ctx.name.slice(1)}`; }; ``` ## Generated Output Examples ### Basic Helper Functions For a service `cosmos.gov.v1beta1.Query.proposals`, with default configuration: ```typescript // Generated function export function getProposals( rpcEndpoint: string, params: QueryProposalsRequest ): Promise { // Implementation } ``` ### React Hooks If React hooks are enabled: ```typescript // Generated React hook export function useProposals( rpcEndpoint: string, params: QueryProposalsRequest, options?: UseQueryOptions ): UseQueryResult { // Implementation } ``` ### Custom Naming With custom naming rules: ```typescript nameMappers: { Query: { "cosmos.gov.v1beta1.*Proposals*": { funcBody: (ctx: AliasNameMappersContext) => { return `fetch${ctx.name}`; }, hookPrefix: "useGov" } } } ``` Would generate: ```typescript // Generated function export function fetchProposals(/* ... */) { /* ... */ } // Generated React hook export function useGovFetchProposals(/* ... */) { /* ... */ } ``` ## Best Practices 1. **Limit the scope**: Use `include.patterns` to generate helpers only for the services you need 2. **Use consistent naming**: Maintain a consistent naming convention across your codebase 3. **Prefer descriptive names**: Use prefixes that describe the action (e.g., `get` for queries, `send` for transactions) 4. **Document custom mappers**: If using complex name transformations, document the logic for your team ---------------------------------- Instant Rpc Methods # Instant RPC Methods This document provides a reference for using Instant RPC Methods in Telescope-generated code to simplify access to common blockchain operations. ## Overview Instant RPC Methods allow you to create customized client classes that expose only specific RPC methods you need, making your code more concise and focused. Telescope generates these specialized clients in a `service-ops.ts` file in your project's root directory. ## Configuration To enable Instant RPC Methods generation in Telescope: ```typescript import { TelescopeOptions } from "@cosmology/types"; const options: TelescopeOptions = { rpcClients: { enabled: true, instantOps: [ { className: "CosmosClient", nameMapping: { // Optional name mapping configuration All: { // Methods to apply to both Query and Msg types "cosmos.bank.v1beta1.balance": "getBalance", "cosmos.bank.v1beta1.allBalances": "getAllBalances" }, Query: { // Methods specific to Query types "cosmos.staking.v1beta1.validators": "getValidators" }, Msg: { // Methods specific to Msg types "cosmos.bank.v1beta1.send": "sendTokens" } } } ] } }; ``` ## Configuration Parameters | Parameter | Type | Description | | --------- | ---- | ----------- | | `rpcClients.instantOps` | array | Array of instant RPC operations configurations | | `rpcClients.instantOps[].className` | string | Name of the generated client class | | `rpcClients.instantOps[].nameMapping` | object | Optional object to map method names | | `rpcClients.instantOps[].nameMapping.All` | object | Method mapping for both Query and Msg types | | `rpcClients.instantOps[].nameMapping.Query` | object | Method mapping specific to Query types | | `rpcClients.instantOps[].nameMapping.Msg` | object | Method mapping specific to Msg types | ## Basic Usage Once generated, the Instant RPC client can be used as follows: ```typescript import { CosmosClient } from "./service-ops"; import { createRPCQueryClient } from "./codegen/rpc"; async function queryWithInstantClient() { // First create a regular RPC client const rpcClient = await createRPCQueryClient({ rpcEndpoint: "https://rpc.cosmos.network" }); // Initialize the instant client with the RPC client const cosmosClient = new CosmosClient(); cosmosClient.init(rpcClient); // Use the simplified methods const { balance } = await cosmosClient.getBalance({ address: "cosmos1...", denom: "uatom" }); console.log(`Balance: ${balance.amount} ${balance.denom}`); } ``` ## Method Mapping The nameMapping configuration allows you to rename methods to make them more intuitive: ```typescript nameMapping: { All: { // Original method name -> New method name "cosmos.bank.v1beta1.balance": "getBalance", "cosmos.bank.v1beta1.allBalances": "getAllBalances" } } ``` With this mapping, `client.cosmos.bank.v1beta1.balance()` becomes `cosmosClient.getBalance()`. ## Creating Multiple Client Classes You can define multiple instant client classes, each with their own focused set of methods: ```typescript instantOps: [ { className: "BankClient", nameMapping: { All: { "cosmos.bank.v1beta1.balance": "getBalance", "cosmos.bank.v1beta1.allBalances": "getAllBalances" } } }, { className: "StakingClient", nameMapping: { All: { "cosmos.staking.v1beta1.validators": "getValidators", "cosmos.staking.v1beta1.delegations": "getDelegations" } } } ] ``` ## Combining Query and Msg Operations Instant RPC clients can combine both query (read) and msg (write) operations in a single client: ```typescript // Configuration nameMapping: { Query: { "cosmos.bank.v1beta1.balance": "getBalance" }, Msg: { "cosmos.bank.v1beta1.send": "sendTokens" } } // Usage const { balance } = await cosmosClient.getBalance({ address: "cosmos1...", denom: "uatom" }); const sendResult = await cosmosClient.sendTokens({ fromAddress: "cosmos1sender...", toAddress: "cosmos1recipient...", amount: [{ denom: "uatom", amount: "1000000" }] }); ``` ## Benefits of Instant RPC Methods 1. **Simplified Interface**: Access methods directly without navigating through module hierarchies 2. **Intuitive Naming**: Use custom method names that better describe their purpose 3. **Focused API**: Include only the methods your application needs 4. **Improved Maintainability**: Cleaner code with less nesting and shorter method calls 5. **Better Developer Experience**: More intuitive API for team members ## Generated Code Structure The generated `service-ops.ts` file contains: 1. An interface with all included operations 2. A class that implements the interface 3. An initialization method that links to the underlying RPC client ```typescript // Generated example (simplified) export interface CosmosClient extends _CosmosAuthV1beta1Queryrpc.CosmosAuthAccountQuery, _CosmosBankV1beta1Queryrpc.CosmosBankBalanceQuery { } export class CosmosClient { rpc: Rpc; init(rpc: Rpc): void { this.rpc = rpc; this.getBalance = _CosmosBankV1beta1Queryrpc.createClientImpl(rpc).balance; this.getAllBalances = _CosmosBankV1beta1Queryrpc.createClientImpl(rpc).allBalances; // Additional method assignments... } } ``` ## Best Practices 1. **Use Meaningful Names**: Choose method names that clearly describe what the operation does 2. **Group Related Operations**: Create separate client classes for different functional areas 3. **Maintain Consistency**: Use consistent naming patterns across your custom methods 4. **Document Custom Methods**: Add comments to explain your custom methods if their purpose isn't obvious 5. **Use with TypeScript**: Leverage type safety to catch errors at compile time ---------------------------------- Interfaces ## Interfaces To utilize polymorphic type fields(with '(cosmos_proto.accepts_interface)') generation, automatic wrapping/unwrapping, and type checking with the `is` function, configure as follows: ```json "interfaces": { "enabled": true, "useGlobalDecoderRegistry": true, "useUnionTypes": true } ``` This configuration enables the generation of more expressive and type-safe TypeScript code, particularly for handling polymorphic types and improving runtime type checking. ### Samples #### 1. Polymorphic Type Field Generation When the tool encounters a field with the annotation `(cosmos_proto.accepts_interface) = "Authorization"`, it generates a polymorphic type field. This means the field will be represented as a union type in the generated TypeScript code, allowing for more specific type handling. For example, a field annotated in the `.proto` file will result in a TypeScript field like: ```typescript authorization: GenericAuthorization | DepositDeploymentAuthorization | Any; ``` #### 2. Automatic Wrapping or Unwrapping The tool automatically wraps or unwraps the polymorphic field when encoding or decoding. This process translates the specific TypeScript object into a generic `Any` type format for Protobuf and vice versa, without requiring manual conversion. ##### Encoding Sample: ```typescript const msg = MsgGrant.fromPartial({ granter: address1, grantee: address2, grant: Grant.fromPartial({ authorization: SendAuthorization.fromPartial({ spendLimit: [{ denom: denom, amount: "1000000", }], }), }), }); // SendAuthorization will be wrapped into Any type when broadcasting // client.signAndBroadcast(..., [ msg ], ...) ``` ##### Decoding Sample: ```typescript const authsResults = await queryClient.cosmos.authz.v1beta1.granteeGrants({ grantee: address2, }); // auth's been unwrapped into SendAuthorization type from Any. const auth = authsResults.grants[0].authorization; ``` #### 3. The `is` Function The `is` function is automatically generated for polymorphic types, allowing developers to check at runtime if a decoded object matches a specific type. This function enhances type safety by providing a straightforward way to assert the type of polymorphic fields. ##### Using the `is` Function: ```typescript if (SendAuthorization.is(auth)) { expect(auth.spendLimit[0].amount).toBe("1000000"); } ``` This example demonstrates how to use the `is` function to check if the `authorization` field is of the type `SendAuthorization` and then perform operations based on that check. ---------------------------------- Json Patch Protos # JSON Patch Protos This document provides a reference for using JSON Patch Protos feature to modify protocol buffer definitions without changing the original files. ## Overview JSON Patch Protos allows you to apply modifications to protobuf definitions during code generation. This feature is particularly useful when you need to customize the generated code without altering the original proto files, such as when upstream SDK PRs are delayed or not in production. ## Configuration JSON Patch Protos is configured in the `prototypes.patch` section of your Telescope options: ```typescript import { TelescopeOptions } from "@cosmology/types"; const options: TelescopeOptions = { prototypes: { patch: { "cosmwasm/wasm/v1/types.proto": [ { op: "replace", path: "@/AccessType/valuesOptions/ACCESS_TYPE_UNSPECIFIED/(gogoproto.enumvalue_customname)", value: "UnspecifiedAccess" }, // more operations... ], // more files... } } }; ``` ## Operation Structure Each patch operation consists of: | Property | Type | Description | | -------- | ---- | ----------- | | `op` | string | The operation type (`add` or `replace`) | | `path` | string | The JSON path to the target field (may use `@` prefix) | | `value` | any | The new value to set at the target location | ## Path Specification Paths can be specified in two formats: 1. **Absolute paths**: Direct JSON paths to the field you want to modify. 2. **Package-derived paths**: Paths prefixed with `@` will be automatically derived from the package name, making navigation within the proto file's structure simpler. ## Operation Types ### Replace Operation The `replace` operation updates an existing value: ```json { "op": "replace", "path": "@/AccessType/valuesOptions/ACCESS_TYPE_UNSPECIFIED/(gogoproto.enumvalue_customname)", "value": "UnspecifiedAccess" } ``` ### Add Operation The `add` operation creates a new value or adds to an existing one: ```json { "op": "add", "path": "@/AccessType/values/ACCESS_TYPE_SUPER_FUN", "value": 4 } ``` ## Common Use Cases ### Modifying Enum Values Customize enum values and their properties: ```json [ { "op": "replace", "path": "@/AccessType/valuesOptions/ACCESS_TYPE_UNSPECIFIED/(gogoproto.enumvalue_customname)", "value": "UnspecifiedAccess" }, { "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" } } ] ``` ### Modifying Message Fields Add or replace fields in a message: ```json [ { "op": "add", "path": "@/Contract/fields/admin", "value": { "rule": "optional", "type": "string", "name": "admin", "id": 3, "options": { "(gogoproto.moretags)": "yaml:\"admin\"" } } } ] ``` ## Best Practices 1. **Use sparingly**: Only patch what's necessary while waiting for upstream changes 2. **Document patches**: Add comments explaining why each patch exists 3. **Review regularly**: Check if patches are still needed when upgrading dependencies 4. **Test thoroughly**: Ensure patched proto definitions work correctly 5. **Consider alternatives**: For extensive changes, consider maintaining a fork of the proto files ## Limitations 1. Not all proto structures can be modified (complex nested structures may be difficult) 2. Changes are applied during code generation and do not affect the original files 3. Patches may need to be updated if proto file structure changes significantly ---------------------------------- Lcd Clients Classes # LCD Client Classes This document provides a reference for using and extending LCD Client Classes in Telescope-generated code. ## Overview LCD Client Classes provide an object-oriented approach to interact with blockchain REST endpoints. Telescope generates these classes to enable modularity and extensibility while maintaining type safety. ## Configuration To enable LCD Client Classes generation in Telescope: ```typescript import { TelescopeOptions } from "@cosmology/types"; const options: TelescopeOptions = { lcdClientClasses: { enabled: true } }; ``` ## Configuration Parameters | Parameter | Type | Default | Description | | --------- | ---- | ------- | ----------- | | `lcdClientClasses.enabled` | boolean | `false` | Enables LCD Client Classes generation | | `lcdClientClasses.bundle` | boolean | `true` | Whether to include classes in the bundle | | `lcdClientClasses.methodName` | string | `"createLCDClientClasses"` | Factory method name | ## Basic Usage ### Creating LCD Client Classes ```typescript import { createLCDClientClasses } from "./codegen/client-classes"; // Create client classes const clientClasses = createLCDClientClasses({ restEndpoint: "https://rest.cosmos.network" }); // Access modules using class instances const bankModule = clientClasses.cosmos.bank.v1beta1; const stakingModule = clientClasses.cosmos.staking.v1beta1; // Make queries const balances = await bankModule.allBalances({ address: "cosmos1..." }); const validators = await stakingModule.validators({}); ``` ## Class Structure Each module is represented by a class with methods corresponding to the available queries: ```typescript class BankQueryClient { constructor(protected readonly axios: AxiosInstance, protected readonly baseUrl: string) {} // Get all balances for an address async allBalances(params: QueryAllBalancesRequest): Promise { // Implementation details... } // Get a specific token balance async balance(params: QueryBalanceRequest): Promise { // Implementation details... } // Get total supply of all tokens async totalSupply(params: QueryTotalSupplyRequest = {}): Promise { // Implementation details... } // ...other methods } ``` ## Extending Client Classes You can extend the generated classes to add custom functionality: ```typescript import { BankQueryClient } from "./codegen/cosmos/bank/v1beta1/query.lcd"; class ExtendedBankClient extends BankQueryClient { // Add a convenience method for common operations async getTokenBalance(address: string, denom: string): Promise { const response = await this.balance({ address, denom }); return response.balance?.amount || "0"; } // Add methods for formatted output async getFormattedBalance(address: string, denom: string): Promise { const amount = await this.getTokenBalance(address, denom); // Convert microunits to display units const displayAmount = Number(amount) / 1_000_000; // Format the display amount and append symbol const symbol = denom.startsWith('u') ? denom.substring(1).toUpperCase() : denom; return `${displayAmount.toLocaleString()} ${symbol}`; } } ``` ## Module Composition Combine multiple client classes to create a composite client: ```typescript import { BankQueryClient } from "./codegen/cosmos/bank/v1beta1/query.lcd"; import { StakingQueryClient } from "./codegen/cosmos/staking/v1beta1/query.lcd"; import { GovQueryClient } from "./codegen/cosmos/gov/v1beta1/query.lcd"; import axios from "axios"; // Create a class that composes multiple module clients class CompositeClient { public readonly bank: BankQueryClient; public readonly staking: StakingQueryClient; public readonly gov: GovQueryClient; constructor(restEndpoint: string) { const axiosInstance = axios.create({ baseURL: restEndpoint }); this.bank = new BankQueryClient(axiosInstance, restEndpoint); this.staking = new StakingQueryClient(axiosInstance, restEndpoint); this.gov = new GovQueryClient(axiosInstance, restEndpoint); } // Add composite methods that use multiple modules async getAccountOverview(address: string) { const [balances, delegations, unbonding, rewards] = await Promise.all([ this.bank.allBalances({ address }), this.staking.delegatorDelegations({ delegatorAddr: address }), this.staking.delegatorUnbondingDelegations({ delegatorAddr: address }), this.gov.proposals({}) ]); return { availableBalances: balances.balances, stakedBalance: delegations.delegationResponses, unbondingBalance: unbonding.unbondingResponses, activeProposals: rewards.proposals }; } } ``` ## Axios Configuration Customize the underlying Axios instance for better control: ```typescript import axios, { AxiosRequestConfig } from "axios"; import { createLCDClientClasses } from "./codegen/client-classes"; // Custom Axios configuration const axiosConfig: AxiosRequestConfig = { timeout: 15000, headers: { "Accept": "application/json", "Cache-Control": "no-cache" }, validateStatus: (status) => status < 500, // Only throw for server errors }; // Create a custom Axios instance const axiosInstance = axios.create(axiosConfig); // Add request interceptor for logging axiosInstance.interceptors.request.use(config => { console.log(`Making request to: ${config.url}`); return config; }); // Add response interceptor for error handling axiosInstance.interceptors.response.use( response => response, error => { console.error("API Error:", error.message); return Promise.reject(error); } ); // Create client classes with custom Axios instance const clientClasses = createLCDClientClasses({ restEndpoint: "https://rest.cosmos.network", axios: axiosInstance }); ``` ## Implementing Caching Add caching to improve performance: ```typescript import { BankQueryClient } from "./codegen/cosmos/bank/v1beta1/query.lcd"; // Simple in-memory cache implementation class CachingBankClient extends BankQueryClient { private cache = new Map(); private readonly TTL = 30000; // 30 seconds cache TTL private getCacheKey(method: string, params: any): string { return `${method}:${JSON.stringify(params)}`; } async allBalances(params: any): Promise { const cacheKey = this.getCacheKey('allBalances', params); // Check cache const cached = this.cache.get(cacheKey); if (cached && (Date.now() - cached.timestamp) < this.TTL) { return cached.data; } // Not in cache, call parent method const result = await super.allBalances(params); // Store in cache this.cache.set(cacheKey, { data: result, timestamp: Date.now() }); return result; } // Implement similar caching for other methods } ``` ## Retry Logic Implement retry logic for resilient applications: ```typescript import { BankQueryClient } from "./codegen/cosmos/bank/v1beta1/query.lcd"; class RetryingBankClient extends BankQueryClient { private maxRetries = 3; private retryDelay = 1000; // 1 second private async withRetry(method: () => Promise): Promise { let lastError: Error | null = null; for (let attempt = 1; attempt <= this.maxRetries; attempt++) { try { return await method(); } catch (error) { lastError = error; console.warn(`Request failed (attempt ${attempt}/${this.maxRetries}): ${error.message}`); if (attempt < this.maxRetries) { // Wait before retrying (with exponential backoff) const delay = this.retryDelay * Math.pow(2, attempt - 1); await new Promise(resolve => setTimeout(resolve, delay)); } } } throw lastError || new Error("Max retries exceeded"); } async allBalances(params: any): Promise { return this.withRetry(() => super.allBalances(params)); } // Apply retry logic to other methods as needed } ``` ## Pagination Helpers Add pagination helpers to simplify working with paginated APIs: ```typescript import { StakingQueryClient } from "./codegen/cosmos/staking/v1beta1/query.lcd"; class PaginatedStakingClient extends StakingQueryClient { // Get all validators with automatic pagination handling async getAllValidators(params: any = {}): Promise { let validators: any[] = []; let nextKey: string | null = null; do { // Prepare pagination parameters const paginationParams = nextKey ? { ...params, pagination: { ...params.pagination, key: nextKey } } : params; // Call parent method const response = await super.validators(paginationParams); // Add results to our collection validators = [...validators, ...response.validators]; // Get next key for pagination nextKey = response.pagination?.nextKey || null; } while (nextKey); return validators; } } ``` ## Chain-specific Client Classes Different chains may have unique modules and classes: ```typescript import { GammQueryClient } from "./codegen/osmosis/gamm/v1beta1/query.lcd"; // Osmosis-specific client for liquidity pools class OsmosisDexClient extends GammQueryClient { // Get all pools with price information async getPoolsWithPrices(): Promise { const { pools } = await this.pools({}); return pools.map(pool => { // Extract tokens from pool const assets = pool.poolAssets || []; // Calculate prices based on pool ratios const prices = this.calculatePrices(assets); return { id: pool.id, type: pool["@type"], assets, prices }; }); } private calculatePrices(assets: any[]): Record { // Price calculation logic // This would calculate relative prices between assets in a pool // ... return { /* calculated prices */ }; } } ``` ## TypeScript Type Helpers Add TypeScript type helpers for better developer experience: ```typescript // Define helper types for better IntelliSense export type CosmosAddress = string; export type TokenAmount = string; export type Denom = string; export interface Token { denom: Denom; amount: TokenAmount; } // Extend the client with strongly typed methods class TypedBankClient extends BankQueryClient { async getBalance(address: CosmosAddress, denom: Denom): Promise { const response = await this.balance({ address, denom }); // Ensure we always return a valid token object return response.balance || { denom, amount: "0" }; } } ``` ## Best Practices 1. **Extend don't modify**: Extend the generated classes rather than modifying them 2. **Share Axios instances**: Reuse the same Axios instance across client classes 3. **Add helper methods**: Implement domain-specific helper methods 4. **Implement caching**: Add caching for frequently accessed data 5. **Use type safety**: Leverage TypeScript's type system 6. **Handle failures gracefully**: Add retry logic and error handling 7. **Maintain modularity**: Create classes with a single responsibility 8. **Document extensions**: Add clear documentation for custom methods ---------------------------------- Lcd Clients # LCD Clients This document provides a reference for using LCD (Light Client Daemon) clients in Telescope-generated code to query blockchain data via REST endpoints. ## Overview LCD clients provide a way to query blockchain state through REST API endpoints exposed by Cosmos SDK chains. Telescope can generate TypeScript clients that interact with these endpoints with full type safety. ## Configuration Options To enable LCD client generation in Telescope, use the following options: ```typescript import { TelescopeOptions } from "@cosmology/types"; const options: TelescopeOptions = { lcdClients: { enabled: true, // Additional optional configurations scoped: [ // Optional array of scoped client configurations ] } }; ``` ## Configuration Parameters | Parameter | Type | Default | Description | | --------- | ---- | ------- | ----------- | | `lcdClients.enabled` | boolean | `false` | Enables LCD client generation | | `lcdClients.scoped` | array | `[]` | Configuration for scoped LCD clients | | `lcdClients.scoped[].dir` | string | - | Directory to generate scoped client | | `lcdClients.scoped[].filename` | string | - | Filename for the generated client | | `lcdClients.scoped[].packages` | string[] | - | Array of packages to include | | `lcdClients.scoped[].addToBundle` | boolean | `false` | Add to bundle export | | `lcdClients.scoped[].methodName` | string | - | Method name for the client factory | ## Basic Usage ### Creating an LCD Client ```typescript import { createLCDClient } from "./codegen/client"; // Create the client const client = await createLCDClient({ restEndpoint: "https://rest.cosmos.network" }); // Use the client to query blockchain data const balanceResponse = await client.cosmos.bank.v1beta1.allBalances({ address: "cosmos1..." }); console.log("Balances:", balanceResponse.balances); ``` ### Query Parameters Queries accept parameters based on the endpoint definition: ```typescript // Query with parameters const validatorsResponse = await client.cosmos.staking.v1beta1.validators({ status: "BOND_STATUS_BONDED", pagination: { key: "", offset: "0", limit: "100", countTotal: true } }); console.log(`Found ${validatorsResponse.validators.length} active validators`); ``` ## Module Organization LCD clients are organized by modules, versions, and query methods: ```typescript // Bank module const bankModule = client.cosmos.bank.v1beta1; const balances = await bankModule.allBalances({ address: "cosmos1..." }); // Staking module const stakingModule = client.cosmos.staking.v1beta1; const validators = await stakingModule.validators({}); // Governance module const govModule = client.cosmos.gov.v1beta1; const proposals = await govModule.proposals({}); ``` ## Scoped LCD Clients For more focused applications, create scoped clients that include only specific modules: ```typescript // Configuration for generating scoped clients const options: TelescopeOptions = { lcdClients: { enabled: true, scoped: [ { dir: "osmosis", filename: "osmosis-lcd-client.ts", packages: [ "cosmos.bank.v1beta1", "osmosis.gamm.v1beta1", "osmosis.lockup.v1beta1" ], addToBundle: true, methodName: "createOsmosisLCDClient" } ] } }; // Using the generated scoped client import { createOsmosisLCDClient } from "./codegen/osmosis/osmosis-lcd-client"; const osmosisClient = await createOsmosisLCDClient({ restEndpoint: "https://rest.osmosis.zone" }); // Now you can only access the modules specified in the configuration const pools = await osmosisClient.osmosis.gamm.v1beta1.pools({}); ``` ## Pagination Many queries support pagination for handling large datasets: ```typescript // First page const firstPage = await client.cosmos.staking.v1beta1.validators({ pagination: { limit: "10" } }); // Next page, if there's more data if (firstPage.pagination && firstPage.pagination.nextKey) { const nextPage = await client.cosmos.staking.v1beta1.validators({ pagination: { key: firstPage.pagination.nextKey, limit: "10" } }); } ``` ## Error Handling ```typescript try { const response = await client.cosmos.bank.v1beta1.balance({ address: "cosmos1...", denom: "uatom" }); console.log("Balance:", response.balance); } catch (error) { if (error.response) { // The request was made and the server responded with an error console.error("Error status:", error.response.status); console.error("Error data:", error.response.data); } else if (error.request) { // The request was made but no response was received console.error("No response received:", error.request); } else { // Something happened in setting up the request console.error("Request error:", error.message); } } ``` ## Advanced: Creating Custom LCD Clients For custom chains or specific needs, you can create custom LCD clients: ```typescript import { LCDClient } from "@cosmology/lcd"; // Create a LCDClient with custom axios config const client = new LCDClient({ restEndpoint: "https://rest.cosmos.network", axiosConfig: { timeout: 15000, headers: { "Custom-Header": "Value" } } }); // Register a custom endpoint client.registerEndpoint({ path: "/custom/endpoint", method: "GET", namespace: "custom", operationId: "customQuery" }); // Use the custom endpoint const response = await client.custom.customQuery({ param: "value" }); ``` ## Chain-specific Endpoints Different Cosmos chains may have unique modules and endpoints: ### Osmosis DEX Queries ```typescript // Query liquidity pools on Osmosis const poolsResponse = await osmosisClient.osmosis.gamm.v1beta1.pools({}); console.log(`Found ${poolsResponse.pools.length} liquidity pools`); // Query specific pool const poolResponse = await osmosisClient.osmosis.gamm.v1beta1.pool({ poolId: "1" }); console.log("Pool details:", poolResponse.pool); ``` ### CosmWasm Smart Contract Queries ```typescript // Query a CosmWasm smart contract const contractResponse = await client.cosmwasm.wasm.v1.smartContractState({ address: "juno1contract...", queryData: btoa(JSON.stringify({ get_config: {} })) }); // Parse the query result const contractResult = JSON.parse( new TextDecoder().decode(contractResponse.data) ); console.log("Contract state:", contractResult); ``` ## Response Types All LCD client responses are fully typed based on the Protobuf definitions: ```typescript // Response type for bank balance query interface QueryAllBalancesResponse { balances: Coin[]; pagination?: PageResponse; } // Response type for validator query interface QueryValidatorsResponse { validators: Validator[]; pagination?: PageResponse; } ``` ## Best Practices 1. **Use Typings**: Take advantage of TypeScript typings for autocomplete and type checking 2. **Handle Pagination**: For large datasets, implement proper pagination handling 3. **Cache Responses**: Consider caching responses for frequently queried data 4. **Error Handling**: Implement proper error handling for all queries 5. **Rate Limiting**: Be mindful of rate limits imposed by public REST endpoints 6. **Connection Management**: Close connections when they're no longer needed 7. **Timeout Configuration**: Set appropriate timeouts for network requests 8. **Prefer Specific Queries**: Use specific query methods when available instead of generic ones ---------------------------------- Manually Registering Types # Manually Registering Types This document provides a reference for manually registering types in Telescope-generated code. ## Overview When working with Cosmos SDK applications, sometimes you need to extend Telescope's generated types with custom or third-party types. Manual type registration allows you to integrate these external types with Telescope's registry system. ## Why Register Types Manually? Manual type registration is necessary when: 1. Using types from third-party libraries not generated by Telescope 2. Working with custom types defined outside of Protocol Buffers 3. Integrating with chains or modules that have specialized type requirements 4. Creating hybrid applications with some manually defined types ## Type Registry Basics Telescope generates registries for each message type: ```typescript // Example of a generated registry export const registry = [ ["/cosmos.bank.v1beta1.MsgSend", MsgSend], ["/cosmos.staking.v1beta1.MsgDelegate", MsgDelegate], // other message types... ]; ``` Each entry pairs a type URL with its corresponding TypeScript implementation. ## Manual Registration Methods ### Method 1: Adding to Default Registry ```typescript import { registry } from "./codegen/cosmos/bank/v1beta1/tx"; import { GeneratedType } from "@cosmjs/proto-signing"; import { MyCustomType } from "./my-custom-types"; // Add your custom type to the registry const customRegistry: Array<[string, GeneratedType]> = [ ...registry, ["/my.custom.v1beta1.MyCustomType", MyCustomType] ]; // Use the extended registry when creating a client const client = await SigningStargateClient.connectWithSigner( rpcEndpoint, signer, { registry: customRegistry } ); ``` ### Method 2: Using Registry Class ```typescript import { Registry } from "@cosmjs/proto-signing"; import { defaultRegistryTypes } from "@cosmjs/stargate"; import { MyCustomType } from "./my-custom-types"; // Create a new registry with default types const registry = new Registry(defaultRegistryTypes); // Register your custom types registry.register("/my.custom.v1beta1.MyCustomType", MyCustomType); // Use the registry const client = await SigningStargateClient.connectWithSigner( rpcEndpoint, signer, { registry } ); ``` ### Method 3: Creating Registration Functions ```typescript import { Registry } from "@cosmjs/proto-signing"; import { MsgCustom } from "./my-module/types"; // Create a registration function export function registerMyModuleTypes(registry: Registry): void { registry.register("/my.module.v1beta1.MsgCustom", MsgCustom); // Register additional types here } // Usage const registry = new Registry(defaultRegistryTypes); registerMyModuleTypes(registry); ``` ## Implementation Requirements For a type to be registrable, it must implement the `GeneratedType` interface: ```typescript interface GeneratedType { readonly typeUrl: string; readonly encode: (message: any) => Uint8Array; readonly decode: (binary: Uint8Array) => any; } ``` When creating custom types, ensure they have appropriate encoding and decoding functions. ## Custom Type Example ```typescript import { Writer, Reader } from "protobufjs/minimal"; // Define your custom type interface export interface CustomMessage { field1: string; field2: number; } // Implement the required methods export const CustomMessage = { typeUrl: "/my.custom.v1beta1.CustomMessage", encode(message: CustomMessage, writer: Writer = Writer.create()): Writer { if (message.field1 !== "") { writer.uint32(10).string(message.field1); } if (message.field2 !== 0) { writer.uint32(16).int32(message.field2); } return writer; }, decode(input: Reader | Uint8Array, length?: number): CustomMessage { const reader = input instanceof Reader ? input : new Reader(input); let end = length === undefined ? reader.len : reader.pos + length; const message = { field1: "", field2: 0 } as CustomMessage; while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { case 1: message.field1 = reader.string(); break; case 2: message.field2 = reader.int32(); break; default: reader.skipType(tag & 7); break; } } return message; }, fromPartial(object: any): CustomMessage { const message = { field1: "", field2: 0 } as CustomMessage; if (object.field1 !== undefined && object.field1 !== null) { message.field1 = object.field1; } if (object.field2 !== undefined && object.field2 !== null) { message.field2 = object.field2; } return message; } }; ``` ## Amino Type Registration For chains that use Amino (legacy encoding), you'll also need to register Amino converters: ```typescript import { AminoTypes } from "@cosmjs/stargate"; import { AminoMsg } from "@cosmjs/amino"; // Define the Amino interface interface AminoCustomMessage extends AminoMsg { type: "my-module/CustomMessage"; value: { field1: string; field2: string; // Note: numbers are strings in Amino }; } // Create Amino converters const aminoConverters = { "/my.custom.v1beta1.CustomMessage": { aminoType: "my-module/CustomMessage", toAmino: ({ field1, field2 }: CustomMessage): AminoCustomMessage["value"] => ({ field1, field2: field2.toString(), }), fromAmino: ({ field1, field2 }: AminoCustomMessage["value"]): CustomMessage => ({ field1, field2: parseInt(field2, 10), }), }, }; // Register with AminoTypes const aminoTypes = new AminoTypes(aminoConverters); ``` ## Global Decoder Registry Telescope v1.0+ includes a global decoder registry system: ```typescript import { GlobalDecoderRegistry } from "./codegen/registry"; import { CustomMessage } from "./my-custom-types"; // Register a custom type GlobalDecoderRegistry.register(CustomMessage.typeUrl, CustomMessage); // Later, decode a message of any registered type const decodedMessage = GlobalDecoderRegistry.decode(binaryData); ``` ## Using Custom Types with Telescope Clients ```typescript import { getSigningCosmosClient } from "./codegen/client"; import { CustomMessage } from "./my-custom-types"; import { Registry } from "@cosmjs/proto-signing"; // Create a registry with custom types const registry = new Registry(); registry.register(CustomMessage.typeUrl, CustomMessage); // Create the signing client const client = await getSigningCosmosClient({ rpcEndpoint, signer, registry }); // Now you can use custom message types const customMsg = { typeUrl: CustomMessage.typeUrl, value: CustomMessage.fromPartial({ field1: "value", field2: 42 }) }; // Use in transactions const result = await client.signAndBroadcast( signerAddress, [customMsg], fee ); ``` ## Best Practices 1. **Organize custom types** in dedicated modules for better maintainability 2. **Follow the same patterns** as Telescope-generated types for consistency 3. **Include proper TypeScript types** for better IDE support 4. **Implement all required methods** (encode, decode, fromPartial) 5. **Register both Proto and Amino types** if working with wallets that use Amino 6. **Use meaningful type URLs** that follow the pattern `/namespace.module.version.Type` 7. **Cache registries** where possible to avoid repeated registrations ---------------------------------- Options ��# Options Telescope provides various configuration options that allow developers to customize the generated TypeScript code according to project requirements. This document provides a reference guide for all available options. ## Amino Encoding Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `aminoEncoding.enabled` | Generate amino types and amino converters | `true` | | `aminoEncoding.omitEmptyTags` | An array of strings that determines when to omit fields during JSON serialization | `["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` | Use legacy inline encoding instead of V2 recursive encoding (deprecated) | `false` | | `aminoEncoding.legacy.useNullHandling` | Handle null case when generating legacy amino converters | - | | `aminoEncoding.legacy.useOmitEmpty` | Handle omit empty option when generating legacy amino converters | - | ## Implemented Interface Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `interfaces.enabled` | Enables converters between Any type and specific implemented interfaces | `true` | | `interfaces.useGlobalDecoderRegistry` | Enables GlobalDecoderRegistry and related functions | `false` | | `interfaces.useUseInterfacesParams` | Decides if `useInterfaces` argument is added to `decode` and `toAmino` functions | `false` | | `interfaces.useByDefault` | Decides if interface decoders are used by default | `true` | | `interfaces.useByDefaultRpc` | Decides if interface decoders are used by default by RPC clients | `true` | | `interfaces.useUnionTypes` | Generate Any type as union types instead of intersection types | `false` | ## Prototypes Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `prototypes.enabled` | Enables 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 during transpilation | `undefined` | | `prototypes.includes.protos` | Include a set of proto files during transpilation | `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 | `undefined` | | `prototypes.excluded.hardProtos` | Forcibly exclude a set of proto files regardless of dependencies | `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 to encode default values of scalar types | `false` | | `prototypes.isScalarDefaultToNullable` | Whether scalar types are nullable by default when gogoproto.nullable is not specified | `false` | | `prototypes.enforceNullCheck` | 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 | `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 custom names for enums specified through proto options or annotations | `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` | ## RPC Client Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `rpcClients.type` | Type of RPC client to generate (`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 root folder | `undefined` | | `rpcClients.useConnectComet` | Will use connectComet function to get a tendermint client | `undefined` | | `rpcClients.useMakeClient` | Allow user to pass a query client resolver to create query client | `undefined` | | `rpcClients.serviceImplement` | Assign implement type of rpc methods by setting patterns under service types | `undefined` | | `rpcClients.clientStyle.useUpdatedClientStyle` | Whether to use updated client style | `false` | | `rpcClients.clientStyle.type` | Client type array with possible values: `all-client`, `sdk-module-client`, and `custom-client` | `undefined` | | `rpcClients.clientStyle.customClientOption.name` | Specify client name | `undefined` | | `rpcClients.clientStyle.customClientOption.fileName` | Specify generated client file name in root directory | `undefined` | | `rpcClients.clientStyle.customClientOption.include.patterns` | Determine which proto files will be imported for the current client | `undefined` | ## Helper Functions Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `helperFunctions.enabled` | Enable generation of helper function files `.func.ts` | `false` | | `helperFunctions.hooks` | Generate hooks alongside helper functions | `{ react: false, vue: false }` | | `helperFunctions.include.serviceTypes` | Specify which types of services to include (`Query`, `Msg`) | `undefined` | | `helperFunctions.include.patterns` | Array of glob patterns to match specific proto services | `undefined` | | `helperFunctions.nameMappers` | Configuration object for customizing function names and prefixes | `{}` | | `helperFunctions.nameMappers.All.funcBody` | Maps method names to new names for all services | `"unchanged"` | | `helperFunctions.nameMappers.All.hookPrefix` | Prefix for hooks | `"use"` | | `helperFunctions.nameMappers.Query.funcBody` | Maps method names to new names for `Query` services | `"get"` | | `helperFunctions.nameMappers.Query.hookPrefix` | Prefix for hooks for `Query` services | `"use"` | | `helperFunctions.nameMappers.Msg.funcBody` | Maps method names to new names for `Msg` services | `"unchanged"` | | `helperFunctions.nameMappers.Msg.hookPrefix` | Prefix for hooks for `Msg` services | `"use"` | ## Stargate Client Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `stargateClients.includeCosmosDefaultTypes` | Include cosmjs defaults with stargate clients | `true` (except cosmos package) | | `stargateClients.addGetTxRpc` | Add getSigningTxRpc to clients in namespaces | `false` | ## State Management Options ### React Query Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `reactQuery.enabled` | Create react hooks using `@tanstack/react-query` hooks | `false` | | `reactQuery.needExtraQueryKey` | Allow users to input extra react query key to customized hooks | `false` | | `reactQuery.include.protos` | Create hooks on matched proto filenames or patterns | `[]` | | `reactQuery.include.packages` | Create hooks on matched packages | `[]` | | `reactQuery.include.patterns` | Create hooks on matched patterns (deprecated) | `[]` | | `reactQuery.instantExport.include.patterns` | Expose instant hooks on matched patterns of packages + method | `[]` | | `reactQuery.instantExport.nameMapping` | Map aliases to package + method for better naming of duplicate methods | `{}` | ### Mobx Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `mobx.enabled` | Create mobx stores using `mobx` | `false` | | `mobx.include.protos` | Create mobx stores on matched proto filenames or patterns | `[]` | | `mobx.include.packages` | Create mobx stores on matched packages | `[]` | | `mobx.include.patterns` | Create mobx stores on matched patterns (deprecated) | `[]` | ### Pinia Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `pinia.enabled` | Create pinia stores using `pinia` | `false` | | `pinia.include.protos` | Create pinia stores on matched proto filenames or patterns | `[]` | | `pinia.include.packages` | Create pinia stores on matched packages | `[]` | | `pinia.include.patterns` | Create pinia stores on matched patterns (deprecated) | `[]` | ### Vue Query Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `vueQuery.enabled` | Create vue composables using `@tanstack/vue-query` | `false` | | `vueQuery.include.protos` | Create composables on matched proto filenames or patterns | `[]` | | `vueQuery.include.packages` | Create composables on matched packages | `[]` | ## Typings and Formatting Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `prototypes.typingsFormat.customTypes.useCosmosSDKDec` | Enable handling of the "useCosmosSDKDec" custom type | `true` | | `prototypes.typingsFormat.customTypes.useEnhancedDecimal` | Use patched decimal instead of decimal from @cosmjs/math | `false` | | `prototypes.typingsFormat.customTypes.base64Lib` | Use endo/base64 methods | `undefined` | | `prototypes.typingsFormat.num64` | Method for generating int64 proto types: 'long' or 'bigint' | `bigint` | | `prototypes.typingsFormat.useTelescopeGeneratedType` | Use TelescopeGeneratedType instead of GeneratedType from cosmjs | `false` | | `prototypes.typingsFormat.useDeepPartial` | Use the `DeepPartial` TS type if true, otherwise `Partial` | `false` | | `prototypes.typingsFormat.useExact` | Use the `Exact` TS type if true | `false` | | `prototypes.typingsFormat.toJsonUnknown` | Use `JsonSafe` for `toJSON` methods if false | `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` | If false, enum empty value is 0; if true, -1 (unrecognized) | `true` | | `prototypes.typingsFormat.setDefaultCustomTypesToUndefined` | If true, Timestamp, Duration, Any, Coin empty values are undefined | `false` | | `prototypes.typingsFormat.autoFixUndefinedEnumDefault` | Automatically fix enum default values | `false` | ## Protobuf Parser Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `prototypes.parser.keepCase` | Pass `keepCase` to protobuf `parse()` to keep original casing | `true` | | `prototypes.parser.alternateCommentMode` | Pass `alternateCommentMode` to protobuf `parse()` method | `true` | | `prototypes.parser.preferTrailingComment` | Pass `preferTrailingComment` to protobuf `parse()` method | `false` | ## TypeScript Disabling Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `tsDisable.disableAll` | Include `//@ts-nocheck` on every output file if true | `false` | | `tsDisable.patterns` | Include `//@ts-nocheck` on matched patterns | `[]` | | `tsDisable.files` | Include `//@ts-nocheck` on matched files | `[]` | ## ESLint Disabling Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `eslintDisable.disableAll` | Include `/* eslint-disable */` on every output file if true | `false` | | `eslintDisable.patterns` | Include `/* eslint-disable */` on matched patterns | `[]` | | `eslintDisable.files` | Include `/* eslint-disable */` on matched files | `[]` | ## Bundle Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `bundle.enabled` | Bundle all files into a scoped index file | `true` | > **Warning:** This option is not recommended. It will generate a bundle file that exports all types and functions under one namespace, making the bundle file very large and hard to maintain. ## Output Options | Option | Description | Defaults | | ------ | ----------- | -------- | | `env` | Set to 'v-next' to enable unreleased features; otherwise 'default' | `default` | | `removeUnusedImports` | Remove unused imports | `true` | | `classesUseArrowFunctions` | Use arrow functions in classes instead of binding in constructors | `false` | | `includeExternalHelpers` | Export helper functions in `extern.ts` | `false` | | `restoreImportExtension` | Restore extensions of imported paths (e.g., '.js'); null means no extension | `null` | ---------------------------------- Quickstart # Quickstart This guide provides a quick reference for setting up and using Telescope to generate TypeScript packages for Cosmos SDK modules. Here is the video for using create-interchain-app boilerplate: https://youtu.be/iQf6p65fbdY ## Installation Install create-interchain-app: ```sh npm install -g create-interchain-app ``` ## Package Generation ### Using create-interchain-app boilerplate Create a new package using the telescope boilerplate: ```sh cia --boilerplate telescope ``` Navigate to the generated project: ```sh cd ./your-project/packages/your-module yarn install ``` ## Working with Protos Download protocol buffer files: ```sh yarn download-protos ``` This will clone repositories into `./git-modules` and generate proto files in `./protos`. You can modify the configuration in ./scripts/download-protos.ts ## Code Generation Generate TypeScript code from proto files: ```sh yarn codegen ``` You can modify the configuration in ./scripts/codegen.ts ## Building Build the package for distribution: ```sh yarn build ``` ## Publishing Use lerna for package management: ```sh lerna publish ``` ---------------------------------- Rpc Client Classes # RPC Client Classes This document provides a reference for using and extending RPC Client Classes in Telescope-generated code. ## Overview RPC Client Classes provide an object-oriented approach to interact with Cosmos SDK blockchains through RPC endpoints. Telescope generates these classes to enable extensibility and modularity while maintaining type safety. ## Configuration To enable RPC Client Classes generation in Telescope: ```typescript import { TelescopeOptions } from "@cosmology/types"; const options: TelescopeOptions = { rpcClientClasses: { enabled: true } }; ``` ## Configuration Parameters | Parameter | Type | Default | Description | | --------- | ---- | ------- | ----------- | | `rpcClientClasses.enabled` | boolean | `false` | Enables RPC Client Classes generation | | `rpcClientClasses.camelCase` | boolean | `true` | Converts method names to camelCase | | `rpcClientClasses.methodName` | string | `"createRPCQueryClientClasses"` | Factory method name | ## Basic Usage ### Creating RPC Client Classes ```typescript import { createRPCQueryClientClasses } from "./codegen/client-classes"; // Create client classes const clientClasses = createRPCQueryClientClasses({ rpcEndpoint: "https://rpc.cosmos.network" }); // Access modules using class instances const bankModule = clientClasses.cosmos.bank.v1beta1; const stakingModule = clientClasses.cosmos.staking.v1beta1; // Make queries const balances = await bankModule.allBalances({ address: "cosmos1..." }); const validators = await stakingModule.validators({}); ``` ## Class Structure Each module is represented by a class with query methods: ```typescript class BankQueryClient { constructor( protected readonly rpc: TendermintClient, protected readonly queryClient: QueryClient ) {} // Get all balances for an address async allBalances(request: QueryAllBalancesRequest): Promise { // Implementation details... } // Get a specific token balance async balance(request: QueryBalanceRequest): Promise { // Implementation details... } // Get total supply of all tokens async totalSupply(request: QueryTotalSupplyRequest = {}): Promise { // Implementation details... } } ``` ## Client Types RPC Client Classes support different underlying implementations: ```typescript // Create client classes with specific client type const tendermintClasses = createRPCQueryClientClasses({ rpcEndpoint: "https://rpc.cosmos.network", clientType: "tendermint" }); const grpcWebClasses = createRPCQueryClientClasses({ rpcEndpoint: "https://grpc-web.cosmos.network", clientType: "grpc-web" }); const grpcGatewayClasses = createRPCQueryClientClasses({ rpcEndpoint: "https://rest.cosmos.network", clientType: "grpc-gateway" }); ``` ## Extending Client Classes You can extend the generated classes to add custom functionality: ```typescript import { BankQueryClient } from "./codegen/cosmos/bank/v1beta1/query.rpc.Query"; class ExtendedBankClient extends BankQueryClient { // Add a convenience method for common operations async getTokenBalance(address: string, denom: string): Promise { const response = await this.balance({ address, denom }); return response.balance?.amount || "0"; } // Add methods for formatted output async getFormattedBalance(address: string, denom: string): Promise { const amount = await this.getTokenBalance(address, denom); // Convert microunits to display units const displayAmount = Number(amount) / 1_000_000; // Format the display amount and append symbol const symbol = denom.startsWith('u') ? denom.substring(1).toUpperCase() : denom; return `${displayAmount.toLocaleString()} ${symbol}`; } } ``` ## Module Composition Combine multiple client classes to create composite clients: ```typescript import { BankQueryClient } from "./codegen/cosmos/bank/v1beta1/query.rpc.Query"; import { StakingQueryClient } from "./codegen/cosmos/staking/v1beta1/query.rpc.Query"; import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { QueryClient } from "@cosmjs/stargate"; // Create a class that composes multiple module clients class CompositeClient { public readonly bank: BankQueryClient; public readonly staking: StakingQueryClient; constructor(rpcEndpoint: string) { // Create base Tendermint client const tendermintClient = await Tendermint34Client.connect(rpcEndpoint); const queryClient = new QueryClient(tendermintClient); // Create module clients this.bank = new BankQueryClient(tendermintClient, queryClient); this.staking = new StakingQueryClient(tendermintClient, queryClient); } // Add composite methods that use multiple modules async getAccountOverview(address: string) { const [balances, delegations] = await Promise.all([ this.bank.allBalances({ address }), this.staking.delegatorDelegations({ delegatorAddr: address }) ]); return { availableBalances: balances.balances, stakedBalance: delegations.delegationResponses }; } // Clean up resources disconnect() { this.bank.rpc.disconnect(); } } ``` ## Working with Tendermint Subscriptions RPC Client Classes can be used with Tendermint WebSocket subscriptions: ```typescript import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { createRPCQueryClientClasses } from "./codegen/client-classes"; // Create raw Tendermint client for subscriptions const tendermintClient = await Tendermint34Client.connect("wss://rpc.cosmos.network/websocket"); // Create RPC Client Classes using existing Tendermint client const clientClasses = createRPCQueryClientClasses({ rpcEndpoint: tendermintClient }); // Set up a block subscription const blockSubscription = tendermintClient.subscribeNewBlock().subscribe({ next: async (block) => { console.log(`New block: ${block.header.height}`); // Use clientClasses to query data related to this block const validators = await clientClasses.cosmos.staking.v1beta1.validators({}); console.log(`Current validators: ${validators.validators.length}`); }, error: (err) => { console.error("Subscription error:", err); } }); // Later, clean up resources blockSubscription.unsubscribe(); tendermintClient.disconnect(); ``` ## Implementing Retry Logic Add retry logic for resilient applications: ```typescript import { StakingQueryClient } from "./codegen/cosmos/staking/v1beta1/query.rpc.Query"; class RetryingStakingClient extends StakingQueryClient { private maxRetries = 3; private retryDelay = 1000; // 1 second private async withRetry(method: () => Promise): Promise { let lastError: Error | null = null; for (let attempt = 1; attempt <= this.maxRetries; attempt++) { try { return await method(); } catch (error) { lastError = error; console.warn(`Request failed (attempt ${attempt}/${this.maxRetries}): ${error.message}`); if (attempt < this.maxRetries) { // Wait before retrying (with exponential backoff) const delay = this.retryDelay * Math.pow(2, attempt - 1); await new Promise(resolve => setTimeout(resolve, delay)); } } } throw lastError || new Error("Max retries exceeded"); } async validators(request: any): Promise { return this.withRetry(() => super.validators(request)); } // Apply retry logic to other methods as needed } ``` ## Pagination Helpers Add pagination helpers to simplify working with paginated APIs: ```typescript import { StakingQueryClient } from "./codegen/cosmos/staking/v1beta1/query.rpc.Query"; class PaginatedStakingClient extends StakingQueryClient { // Get all validators with automatic pagination handling async getAllValidators(request: any = {}): Promise { let validators: any[] = []; let nextKey: string | null = null; do { // Prepare pagination parameters const paginationParams = nextKey ? { ...request, pagination: { ...request.pagination, key: nextKey } } : request; // Call parent method const response = await super.validators(paginationParams); // Add results to our collection validators = [...validators, ...response.validators]; // Get next key for pagination nextKey = response.pagination?.nextKey || null; } while (nextKey); return validators; } } ``` ## Chain-specific Client Classes Different chains may have unique modules: ```typescript import { GammQueryClient } from "./codegen/osmosis/gamm/v1beta1/query.rpc.Query"; // Osmosis-specific client for liquidity pools class OsmosisDexClient extends GammQueryClient { // Get pool with token prices async getPoolWithPrices(poolId: string): Promise { const { pool } = await this.pool({ poolId }); // Extract assets from pool const assets = pool.poolAssets || []; // Calculate prices based on pool ratios const prices = this.calculatePrices(assets); return { id: pool.id, type: pool["@type"], assets, prices }; } private calculatePrices(assets: any[]): Record { // Price calculation logic // ...implementation details... return { /* calculated prices */ }; } } ``` ## TypeScript Type Helpers Add TypeScript type helpers for better developer experience: ```typescript // Define helper types for better IntelliSense export type CosmosAddress = string; export type TokenAmount = string; export type Denom = string; export interface Token { denom: Denom; amount: TokenAmount; } // Extend the client with strongly typed methods class TypedBankClient extends BankQueryClient { async getBalance(address: CosmosAddress, denom: Denom): Promise { const response = await this.balance({ address, denom }); // Ensure we always return a valid token object return response.balance || { denom, amount: "0" }; } } ``` ## Connection Management Proper connection management is important: ```typescript import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { QueryClient } from "@cosmjs/stargate"; import { BankQueryClient } from "./codegen/cosmos/bank/v1beta1/query.rpc.Query"; class ManagedBankClient { private tendermintClient: Tendermint34Client; private queryClient: QueryClient; private bankClient: BankQueryClient; static async connect(endpoint: string): Promise { const instance = new ManagedBankClient(); await instance.initialize(endpoint); return instance; } private async initialize(endpoint: string): Promise { this.tendermintClient = await Tendermint34Client.connect(endpoint); this.queryClient = new QueryClient(this.tendermintClient); this.bankClient = new BankQueryClient(this.tendermintClient, this.queryClient); } async getBalance(address: string, denom: string): Promise { return this.bankClient.balance({ address, denom }); } // Always call this when done with the client disconnect(): void { this.tendermintClient.disconnect(); } } // Usage async function main() { const client = await ManagedBankClient.connect("https://rpc.cosmos.network"); try { const balance = await client.getBalance("cosmos1...", "uatom"); console.log(balance); } finally { client.disconnect(); // Always disconnect when done } } ``` ## Request Batching For efficient network usage, batch related requests: ```typescript class BatchingClient { private stakingClient: StakingQueryClient; constructor(stakingClient: StakingQueryClient) { this.stakingClient = stakingClient; } // Get validator details in batch async getValidatorDetails(validatorAddresses: string[]): Promise { // Create an array of promise-returning functions const queries = validatorAddresses.map(address => () => this.stakingClient.validator({ validatorAddr: address }) ); // Execute with concurrency control return this.batchExecute(queries, 5); } // Generic batching helper with concurrency limit private async batchExecute( tasks: Array<() => Promise>, concurrencyLimit: number ): Promise { const results: T[] = []; const executing: Promise[] = []; for (const task of tasks) { const p = task().then(result => { results.push(result); executing.splice(executing.indexOf(p), 1); }); executing.push(p); if (executing.length >= concurrencyLimit) { // Wait for one task to complete before adding more await Promise.race(executing); } } // Wait for all tasks to complete await Promise.all(executing); return results; } } ``` ## Performance Considerations 1. **Connection Reuse**: Create a single client and reuse it 2. **Pagination**: Use appropriate page sizes 3. **Batching**: Control concurrency for multiple requests 4. **Caching**: Consider caching for frequently accessed data 5. **Connection Pooling**: Use a connection pool for high-traffic applications ## Best Practices 1. **Extend don't modify**: Extend the generated classes rather than modifying them 2. **Resource Management**: Always disconnect clients when done 3. **Error Handling**: Implement proper error handling with retries 4. **Pagination**: Use key-based pagination for large datasets 5. **Type Safety**: Leverage TypeScript's type system 6. **Composition**: Compose multiple clients for complex functionality 7. **Client Lifecycle**: Manage connection lifecycle properly ---------------------------------- Rpc Clients # RPC Clients This document provides a reference for using RPC (Remote Procedure Call) clients in Telescope-generated code to interact with Cosmos SDK blockchains. ## Overview Telescope supports generating TypeScript clients that can interact with Cosmos SDK chains through different RPC interfaces: 1. **Tendermint RPC** - Direct access to Tendermint endpoints 2. **gRPC-web Client** - Browser-compatible gRPC client 3. **gRPC-gateway Client** - REST-based access to gRPC services ## Configuration To enable RPC client generation in Telescope, use the following configuration: ```typescript import { TelescopeOptions } from "@cosmology/types"; const options: TelescopeOptions = { rpcClients: { enabled: true, camelCase: true // Optional: convert method names to camelCase } }; ``` ## Configuration Parameters | Parameter | Type | Default | Description | | --------- | ---- | ------- | ----------- | | `rpcClients.enabled` | boolean | `false` | Enables RPC client generation | | `rpcClients.camelCase` | boolean | `true` | Converts RPC method names to camelCase | | `rpcClients.bundle` | boolean | `true` | Include RPC clients in bundle | | `rpcClients.methodName` | string | `"createRPCQueryClient"` | Factory method name | ## Tendermint Client ### Basic Usage ```typescript import { createRPCQueryClient } from "./codegen/rpc"; // Create a client const client = await createRPCQueryClient({ rpcEndpoint: "https://rpc.cosmos.network" }); // Query blockchain data const { validators } = await client.cosmos.staking.v1beta1.validators({}); console.log(`Found ${validators.length} validators`); ``` ### Tendermint-specific Endpoints ```typescript // Get node information const nodeInfo = await client.cosmos.base.tendermint.v1beta1.getNodeInfo({}); console.log("Chain ID:", nodeInfo.defaultNodeInfo.network); // Get latest block const latestBlock = await client.cosmos.base.tendermint.v1beta1.getLatestBlock({}); console.log("Latest block height:", latestBlock.block.header.height); // Get syncing status const syncingStatus = await client.cosmos.base.tendermint.v1beta1.getSyncing({}); console.log("Node is syncing:", syncingStatus.syncing); ``` ## gRPC-web Client For browser applications, use the gRPC-web client: ```typescript import { createRPCQueryClient } from "./codegen/rpc"; // Create a gRPC-web client const client = await createRPCQueryClient({ rpcEndpoint: "https://grpc-web.cosmos.network" }); // Use the same interface as Tendermint client const { balance } = await client.cosmos.bank.v1beta1.balance({ address: "cosmos1...", denom: "uatom" }); ``` ## gRPC-gateway Client For REST-based access to gRPC services: ```typescript import { createRPCQueryClient } from "./codegen/rpc"; // Create a client using gRPC-gateway endpoint const client = await createRPCQueryClient({ rpcEndpoint: "https://rest.cosmos.network" }); // Query as normal const { supply } = await client.cosmos.bank.v1beta1.totalSupply({}); ``` ## Client Types Specify the client type explicitly: ```typescript import { createRPCQueryClient } from "./codegen/rpc"; // Choose the client implementation const client = await createRPCQueryClient({ rpcEndpoint: "https://rpc.cosmos.network", clientType: "tendermint" // or "grpc-web" or "grpc-gateway" }); ``` ## Module Organization RPC clients are organized by modules, versions, and methods: ```typescript // Bank module const bank = client.cosmos.bank.v1beta1; const balances = await bank.allBalances({ address: "cosmos1..." }); // Staking module const staking = client.cosmos.staking.v1beta1; const validators = await staking.validators({}); // Governance module const gov = client.cosmos.gov.v1beta1; const proposals = await gov.proposals({}); ``` ## Pagination Many RPC methods support pagination for handling large result sets: ```typescript // First page of validators const { validators, pagination } = await client.cosmos.staking.v1beta1.validators({ pagination: { limit: "10", offset: "0", countTotal: true } }); console.log(`Page 1: ${validators.length} validators`); console.log(`Total count: ${pagination.total}`); // Next page const nextPage = await client.cosmos.staking.v1beta1.validators({ pagination: { limit: "10", offset: "10", countTotal: false } }); console.log(`Page 2: ${nextPage.validators.length} validators`); ``` ## Key-based Pagination For larger datasets, use key-based pagination: ```typescript let nextKey = null; let allValidators = []; do { // Query with nextKey if available const paginationParams = nextKey ? { key: nextKey, limit: "100" } : { limit: "100" }; const response = await client.cosmos.staking.v1beta1.validators({ pagination: paginationParams }); // Add results to collection allValidators = [...allValidators, ...response.validators]; // Get the next pagination key nextKey = response.pagination?.nextKey; } while (nextKey && nextKey.length > 0); console.log(`Retrieved ${allValidators.length} validators in total`); ``` ## Error Handling ```typescript try { const { balance } = await client.cosmos.bank.v1beta1.balance({ address: "cosmos1...", denom: "uatom" }); console.log("Balance:", balance); } catch (error) { if (error.code === 3) { // NOT_FOUND in gRPC console.error("Address not found"); } else if (error.code === 4) { // DEADLINE_EXCEEDED console.error("Request timed out"); } else if (error.code === 13) { // INTERNAL console.error("Internal server error"); } else { console.error("Error:", error.message); } } ``` ## Custom Endpoints For custom or extended RPC endpoints: ```typescript import { QueryClient } from "@cosmjs/stargate"; import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { setupCustomExtension } from "./my-extension"; // Create a Tendermint client const tendermintClient = await Tendermint34Client.connect("https://rpc.cosmos.network"); // Create a QueryClient with custom extension const queryClient = QueryClient.withExtensions( tendermintClient, setupCustomExtension ); // Use the custom extension const customResult = await queryClient.custom.myCustomQuery(params); ``` ## RPC Client Comparison | Feature | Tendermint RPC | gRPC-web | gRPC-gateway | | ------- | ------------- | -------- | ------------ | | Browser Support | Limited | Yes | Yes | | Efficiency | High | Medium | Low | | Streaming | Yes | Limited | No | | Compatibility | Node.js | All | All | | Communication | Direct | HTTP/1.1 + Protobuf | HTTP/REST | ## Tendermint RPC Specific Methods ```typescript // Create a raw Tendermint client import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; const tmClient = await Tendermint34Client.connect("https://rpc.cosmos.network"); // Get blockchain information const abciInfo = await tmClient.abciInfo(); console.log("ABCI application version:", abciInfo.applicationVersion); // Get a block by height const block = await tmClient.block(123456); console.log("Block time:", block.block.header.time); // Get validator set at a height const validators = await tmClient.validatorsAll(123456); console.log("Validator count:", validators.length); // Subscribe to new blocks const subscription = tmClient.subscribeNewBlock().subscribe({ next: (event) => { console.log("New block:", event.header.height); }, error: (error) => { console.error("Subscription error:", error); }, complete: () => { console.log("Subscription completed"); } }); // Later, unsubscribe subscription.unsubscribe(); ``` ## WebSocket Subscriptions ```typescript // Subscribe to transaction events const subscription = tmClient.subscribeTx().subscribe({ next: (event) => { console.log("Transaction hash:", event.hash); console.log("Transaction result:", event.result); }, error: (error) => { console.error("Subscription error:", error); } }); // Subscribe to specific events const subscription = tmClient.subscribeNewBlockHeader().subscribe({ next: (header) => { console.log("New block height:", header.height); console.log("Block time:", header.time); console.log("Proposer:", header.proposerAddress); } }); ``` ## Request Batching For efficient network usage, batch related requests: ```typescript import { createRPCQueryClient } from "./codegen/rpc"; async function getBatchedData(addresses) { const client = await createRPCQueryClient({ rpcEndpoint: "https://rpc.cosmos.network" }); // Execute multiple queries in parallel const balancePromises = addresses.map(address => client.cosmos.bank.v1beta1.allBalances({ address }) ); // Wait for all queries to complete const balanceResults = await Promise.all(balancePromises); // Process results return addresses.map((address, index) => ({ address, balances: balanceResults[index].balances })); } // Example usage const addresses = [ "cosmos1address1...", "cosmos1address2...", "cosmos1address3..." ]; const results = await getBatchedData(addresses); console.log(results); ``` ## Connection Management ```typescript import { createRPCQueryClient } from "./codegen/rpc"; import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; // Create a Tendermint client directly const tmClient = await Tendermint34Client.connect("https://rpc.cosmos.network"); // Create RPC client using existing Tendermint client const client = await createRPCQueryClient({ rpcEndpoint: tmClient }); // Use the client const chainId = await client.cosmos.base.tendermint.v1beta1.getNodeInfo({}); // When done, disconnect tmClient.disconnect(); ``` ## Performance Considerations 1. **Connection Reuse**: Create a single client and reuse it 2. **Pagination**: Use appropriate page sizes 3. **Batching**: Batch related requests with Promise.all 4. **Disconnection**: Always disconnect when done 5. **Error Handling**: Implement proper error handling and retries 6. **Timeouts**: Set appropriate timeouts for RPC calls ## Best Practices 1. **Client Lifecycle**: Manage client connections properly 2. **Error Handling**: Implement proper error handling with retries 3. **Batching**: Batch related requests for efficiency 4. **Pagination**: Use pagination for large datasets 5. **Disconnection**: Always disconnect clients when done 6. **Type Safety**: Leverage TypeScript typing for better developer experience 7. **Authentication**: Include authentication if required by the endpoint ---------------------------------- Sponsors # Sponsors This document provides information about the organizations and individuals who sponsor and support the Telescope project. ## Current Sponsors ### Hyperweb Hyperweb Logo [Hyperweb](https://hyperweb.ai) is the primary sponsor and maintainer of the Telescope project. Hyperweb is dedicated to building developer tools and infrastructure for web3 and the Cosmos ecosystem. ### Cosmology Cosmology Logo [Cosmology](https://cosmology.tech) provides substantial support for Telescope development and maintenance. Cosmology specializes in developer tooling and infrastructure for the Cosmos ecosystem. ### Interchain Foundation Interchain Foundation Logo The [Interchain Foundation](https://interchain.io) has provided grants to support Telescope development, particularly features that benefit the broader Cosmos ecosystem. ## Corporate Sponsors The following organizations provide financial or resource support for Telescope: | Sponsor | Contribution | | ------- | ------------ | | Osmosis Labs | Financial support, testing, integration | | Akash Network | Infrastructure resources | | Juno Network | Financial support, feedback | | Keplr Wallet | Integration testing, feedback | | Cosmos Hub | Financial support through grants | ## Individual Sponsors Many individual developers and community members contribute to Telescope through GitHub Sponsors, grants, or direct contributions. ## Open Collective Telescope is supported through [Open Collective](https://opencollective.com/telescope), which provides a transparent way to support the project financially. ## Becoming a Sponsor If you or your organization uses Telescope and would like to support its continued development, there are several ways to become a sponsor: ### GitHub Sponsors You can sponsor the project through GitHub Sponsors, which allows for recurring monthly donations: [Sponsor Telescope on GitHub](https://github.com/sponsors/hyperweb-io) ### Open Collective For organizations that prefer invoicing or require more formal financial agreements: [Sponsor Telescope on Open Collective](https://opencollective.com/telescope) ### Custom Sponsorship Agreements For larger sponsorships or custom arrangements, please contact the maintainers directly at [sponsors@hyperweb.ai](mailto:sponsors@hyperweb.ai). ## Sponsorship Tiers | Tier | Monthly Contribution | Benefits | | ---- | -------------------- | -------- | | Community Supporter | $5-$49 | Name in sponsors list, Discord role | | Bronze Sponsor | $50-$199 | Logo in README, priority issue responses | | Silver Sponsor | $200-$499 | Medium logo in README, consulting hours | | Gold Sponsor | $500-$999 | Large logo in README and docs, integration support | | Platinum Sponsor | $1000+ | Premium placement, dedicated support channel, prioritized feature development | ## Sponsor Recognition ### README All sponsors at Bronze tier and above are recognized in the main README.md file of the project. ### Documentation Gold and Platinum sponsors are recognized throughout the documentation. ### Website All sponsors are listed on the Telescope website with logo placement according to sponsorship tier. ## How Sponsorship Funds Are Used Sponsorship funds are used to: 1. Maintain and improve the core Telescope library 2. Develop new features 3. Fix bugs and address security issues 4. Improve documentation 5. Provide support to users 6. Cover infrastructure costs 7. Compensate maintainers and contributors ## Transparency Telescope maintains transparency in how sponsorship funds are used: - Financial reports are published quarterly on Open Collective - Major expenditures are announced to sponsors - Development priorities influenced by sponsors are clearly labeled ## Sponsor-Driven Development While sponsorship does influence development priorities, Telescope maintains a commitment to open-source principles and the broader community's needs. Features requested by sponsors are prioritized based on: 1. Alignment with project roadmap 2. Benefit to the broader community 3. Technical feasibility 4. Sponsorship level ## Current Funding Goals | Goal | Target | Progress | Description | | ---- | ------ | -------- | ----------- | | Full-time Maintainer | $8,000/mo | 65% | Fund a full-time maintainer position | | Documentation Overhaul | $5,000 | 90% | Complete documentation rewrite and examples | | React Integration | $3,000 | 100% | React hooks and components (Completed) | | Vue Integration | $3,000 | 40% | Vue composables and components | ## Contact For questions about sponsorship or to discuss custom sponsorship arrangements, please contact: - Email: [sponsors@hyperweb.ai](mailto:sponsors@hyperweb.ai) - Discord: Join the [Telescope Discord](https://discord.gg/telescope) and message the maintainers Thank you to all our sponsors for making Telescope possible! ---------------------------------- Stack # Technology Stack This document provides a reference for the technology stack used by Telescope and Telescope-generated code. ## Core Technologies ### TypeScript Telescope is built with TypeScript and generates TypeScript code. TypeScript provides static typing that helps catch errors early in the development process and improves developer productivity through better tooling. **Version**: 4.7+ **Resources**: - [TypeScript Documentation](https://www.typescriptlang.org/docs/) - [TypeScript GitHub Repository](https://github.com/microsoft/TypeScript) ### Protocol Buffers Telescope uses Protocol Buffers (Protobuf), a language-neutral, platform-neutral, extensible mechanism for serializing structured data. Cosmos SDK chains use Protobuf for defining messages, services, and types. **Version**: proto3 **Resources**: - [Protocol Buffers Documentation](https://developers.google.com/protocol-buffers) - [proto3 Language Guide](https://developers.google.com/protocol-buffers/docs/proto3) ### CosmJS Telescope-generated code relies on CosmJS, a library for building JavaScript applications that interact with Cosmos SDK blockchains. **Version**: 0.29+ (recommended 0.31+) **Key Packages**: - `@cosmjs/stargate` - High-level client for Cosmos SDK chains - `@cosmjs/proto-signing` - Protobuf-based transaction signing - `@cosmjs/tendermint-rpc` - Interface to Tendermint RPC - `@cosmjs/amino` - Legacy Amino encoding support **Resources**: - [CosmJS Documentation](https://cosmos.github.io/cosmjs/) - [CosmJS GitHub Repository](https://github.com/cosmos/cosmjs) ## Build System ### Node.js Telescope runs on Node.js, a JavaScript runtime built on Chrome's V8 JavaScript engine. **Version**: 14+ (recommended 16+) **Resources**: - [Node.js Documentation](https://nodejs.org/en/docs/) ### Yarn Telescope uses Yarn for package management. **Version**: 1.x **Resources**: - [Yarn Documentation](https://classic.yarnpkg.com/en/docs) ### Lerna Telescope is organized as a monorepo using Lerna for managing multiple packages. **Version**: 4.x **Resources**: - [Lerna Documentation](https://lerna.js.org/) ## Code Generation ### AST Manipulation Telescope uses Abstract Syntax Tree (AST) manipulation to generate TypeScript code from Protobuf definitions. **Key Technologies**: - TypeScript Compiler API - Custom AST processing ### Templates Telescope uses code templates for generating consistent patterns across different services and message types. ## Optional Integrations ### React Query Telescope can generate React Query hooks for interacting with Cosmos SDK chains. **Version**: 4.x **Resources**: - [TanStack Query Documentation](https://tanstack.com/query/v4/docs/overview) ### Vue Query Telescope can generate Vue Query composables for Vue.js applications. **Version**: 4.x **Resources**: - [Vue Query Documentation](https://tanstack.com/query/v4/docs/adapters/vue-query) ### Recoil Telescope supports integration with Recoil for React state management. **Version**: 0.7+ **Resources**: - [Recoil Documentation](https://recoiljs.org/docs/introduction/installation) ### CosmWasm Telescope integrates with CosmWasm for generating TypeScript clients for CosmWasm smart contracts. **Technology**: - [@cosmwasm/ts-codegen](https://github.com/CosmWasm/ts-codegen) **Resources**: - [CosmWasm Documentation](https://docs.cosmwasm.com/) ## Development Tools ### Jest Telescope uses Jest for testing. **Version**: 27+ **Resources**: - [Jest Documentation](https://jestjs.io/docs/getting-started) ### ESLint Telescope uses ESLint for code linting. **Version**: 8+ **Resources**: - [ESLint Documentation](https://eslint.org/docs/user-guide/getting-started) ### Prettier Telescope uses Prettier for code formatting. **Version**: 2+ **Resources**: - [Prettier Documentation](https://prettier.io/docs/en/index.html) ## Runtime Dependencies for Generated Code ### Long.js Used for handling 64-bit integers in JavaScript. **Resources**: - [Long.js GitHub Repository](https://github.com/dcodeIO/long.js) ### buffer Provides Buffer implementation for browser environments. **Resources**: - [buffer npm package](https://www.npmjs.com/package/buffer) ## Browser Compatibility Telescope-generated code is compatible with modern browsers. For older browsers, polyfills may be required: **Required Polyfills**: - `Promise` - `fetch` - `TextEncoder`/`TextDecoder` - `Buffer` **Recommended Polyfill Solution**: - `core-js` for standard JavaScript features - Custom polyfills for Node.js built-ins in browser environments ## Recommended Development Environment - **IDE**: Visual Studio Code with TypeScript support - **Node Version Manager**: nvm or volta - **Package Manager**: Yarn 1.x - **Git Hooks**: husky with lint-staged ## Version Compatibility Matrix | Telescope Version | TypeScript | Node.js | CosmJS | Protobuf | | ----------------- | ---------- | ------- | ------ | -------- | | 1.0.x | 4.7+ | 14+ | 0.29+ | proto3 | | 0.12.x | 4.5+ | 14+ | 0.28+ | proto3 | | 0.11.x | 4.4+ | 12+ | 0.27+ | proto3 | ## Deployment Environments Telescope-generated code can be deployed in various environments: ### Browser - Webpack, Rollup, or Vite for bundling - Polyfills for Node.js built-ins - CORS considerations for RPC endpoints ### Node.js - Direct usage without special configuration - Better performance for cryptographic operations ### React Native - Requires appropriate polyfills - May need native modules for cryptography ## Resource Requirements Typical resource requirements for running Telescope: | Resource | Minimum | Recommended | | ----------------- | ------- | ----------- | | CPU | 2 cores | 4+ cores | | Memory | 2 GB | 4+ GB | | Disk Space | 1 GB | 5+ GB | | Node.js Version | 14.x | 16.x+ | ---------------------------------- Stargate Clients # Stargate Clients This document provides a reference for using Stargate clients with Telescope-generated types to interact with Cosmos SDK blockchains. ## Overview Stargate clients facilitate communication between your application and Cosmos SDK blockchains. Telescope generates the necessary type definitions and registries to use these clients with type safety. ## Client Types | Client Type | Purpose | Package | | ----------- | ------- | ------- | | `QueryClient` | Read-only queries of blockchain state | `@cosmjs/stargate` | | `SigningStargateClient` | Send signed transactions | `@cosmjs/stargate` | | `StargateClient` | Basic read-only operations | `@cosmjs/stargate` | ## Registry Configuration The registry maps Protobuf type URLs to their corresponding TypeScript types: ```typescript import { Registry } from "@cosmjs/proto-signing"; import { defaultRegistryTypes } from "@cosmjs/stargate"; import { registry as telescopeRegistry } from "./registry"; // Create a registry with default types plus Telescope-generated types function createRegistry(): Registry { const registry = new Registry(defaultRegistryTypes); // Register Telescope-generated types telescopeRegistry.forEach(([typeUrl, type]) => { registry.register(typeUrl, type); }); return registry; } ``` ## Creating a StargateClient ```typescript import { StargateClient } from "@cosmjs/stargate"; // Connect to a Cosmos SDK chain const client = await StargateClient.connect("https://rpc.cosmos.network"); // Basic queries const height = await client.getHeight(); const balance = await client.getAllBalances("cosmos1..."); const account = await client.getAccount("cosmos1..."); ``` ## Creating a QueryClient ```typescript import { QueryClient, setupStargateClient } from "@cosmjs/stargate"; import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; // Create a Tendermint client const tmClient = await Tendermint34Client.connect("https://rpc.cosmos.network"); // Create a query client const queryClient = QueryClient.withExtensions( tmClient, setupStargateClient ); // Query chain ID and height const chainId = await queryClient.stargateClient.getChainId(); const height = await queryClient.stargateClient.getHeight(); ``` ## Querying with Module Extensions ```typescript import { setupBankExtension, BankExtension } from "@cosmjs/stargate"; // Query client with bank module extension const queryClient = QueryClient.withExtensions( tmClient, setupBankExtension ); // Query balances using the extension const balance = await queryClient.bank.allBalances("cosmos1..."); ``` ## Creating a SigningStargateClient ```typescript import { SigningStargateClient } from "@cosmjs/stargate"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { registry } from "./registry"; import { GasPrice } from "@cosmjs/stargate"; // Create a wallet const wallet = await DirectSecp256k1HdWallet.fromMnemonic( "your mnemonic here", { prefix: "cosmos" } ); // Create a signing client const client = await SigningStargateClient.connectWithSigner( "https://rpc.cosmos.network", wallet, { registry, gasPrice: GasPrice.fromString("0.025uatom") } ); // Get the first account from the wallet const [account] = await wallet.getAccounts(); // Send a transaction const result = await client.sendTokens( account.address, "cosmos1recipient...", [{ denom: "uatom", amount: "1000000" }], { amount: [{ denom: "uatom", amount: "5000" }], gas: "200000" } ); ``` ## Using Telescope-generated Types with SigningStargateClient ```typescript import { SigningStargateClient } from "@cosmjs/stargate"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { registry } from "./registry"; import { MsgSend } from "./cosmos/bank/v1beta1/tx"; // Create client and wallet const wallet = await DirectSecp256k1HdWallet.fromMnemonic("..."); const client = await SigningStargateClient.connectWithSigner( "https://rpc.cosmos.network", wallet, { registry } ); const [account] = await wallet.getAccounts(); // Create a MsgSend using Telescope-generated types const sendMsg = { typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: MsgSend.fromPartial({ fromAddress: account.address, toAddress: "cosmos1recipient...", amount: [{ denom: "uatom", amount: "1000000" }] }) }; // Send the transaction const result = await client.signAndBroadcast( account.address, [sendMsg], { amount: [{ denom: "uatom", amount: "5000" }], gas: "200000" } ); ``` ## Amino Converters Configuration ```typescript import { SigningStargateClient, AminoTypes } from "@cosmjs/stargate"; import { aminoConverters } from "./amino/converters"; // Create a custom AminoTypes instance with Telescope-generated converters const customAminoTypes = new AminoTypes(aminoConverters); // Create a signing client with custom Amino types const client = await SigningStargateClient.connectWithSigner( "https://rpc.cosmos.network", wallet, { registry, aminoTypes: customAminoTypes } ); ``` ## Telescope-specific Stargate Client ```typescript import { createStargateClient } from "./stargate"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; // Create Telescope's enhanced Stargate client const client = await createStargateClient({ rpcEndpoint: "https://rpc.cosmos.network", signer: wallet }); // Use convenience methods for sending messages const result = await client.sendMsgs( "cosmos1sender...", [sendMsg, delegateMsg], fee ); ``` ## Client Configuration Options | Option | Description | Default | | ------ | ----------- | ------- | | `registry` | Maps type URLs to their TypeScript types | Default registry | | `aminoTypes` | Custom Amino converters for legacy compatibility | Default converters | | `gasPrice` | Price per unit of gas | Chain-dependent | | `broadcastTimeoutMs` | Max wait time for transaction broadcasting | 60000 | | `broadcastPollIntervalMs` | Polling interval for transaction confirmation | 3000 | | `prefix` | Bech32 address prefix | "cosmos" | ## Error Handling ```typescript try { const result = await client.signAndBroadcast( account.address, messages, fee ); if (result.code === 0) { console.log("Transaction successful:", result.transactionHash); } else { console.error("Transaction failed with code:", result.code); console.error("Error message:", result.rawLog); } } catch (error) { if (error.message.includes("Account does not exist")) { console.error("Account not found on chain. Make sure it's funded."); } else if (error.message.includes("insufficient fees")) { console.error("Insufficient fees provided."); } else if (error.message.includes("out of gas")) { console.error("Transaction ran out of gas."); } else { console.error("Transaction error:", error); } } ``` ## Reading Transaction Results ```typescript // Send transaction const result = await client.signAndBroadcast( account.address, messages, fee ); // Access transaction data console.log("Transaction hash:", result.transactionHash); console.log("Block height:", result.height); console.log("Gas used:", result.gasUsed); console.log("Gas wanted:", result.gasWanted); // Access events and attributes result.events.forEach(event => { console.log("Event type:", event.type); event.attributes.forEach(attr => { console.log(` ${attr.key}: ${attr.value}`); }); }); ``` ## Custom Query Extensions ```typescript import { QueryClient } from "@cosmjs/stargate"; import { createProtobufRpcClient, QueryClient as TMClient } from "@cosmjs/stargate"; import { QueryClientImpl } from "./cosmos/bank/v1beta1/query"; // Create a query extension for a specific module function setupCustomBankExtension(base: TMClient) { const rpc = createProtobufRpcClient(base); const queryService = new QueryClientImpl(rpc); return { bank: { balance: async (address: string, denom: string) => { const { balance } = await queryService.Balance({ address, denom }); return balance; }, allBalances: async (address: string) => { const { balances } = await queryService.AllBalances({ address }); return balances; } } }; } // Use the custom extension const queryClient = QueryClient.withExtensions( tmClient, setupCustomBankExtension ); const balance = await queryClient.bank.balance("cosmos1...", "uatom"); ``` ## Chain-specific Configurations ```typescript import { GasPrice } from "@cosmjs/stargate"; // Chain-specific configurations const chainConfigs = { cosmos: { rpcEndpoint: "https://rpc.cosmos.network", prefix: "cosmos", gasPrice: GasPrice.fromString("0.025uatom"), feeDenom: "uatom" }, osmosis: { rpcEndpoint: "https://rpc.osmosis.zone", prefix: "osmo", gasPrice: GasPrice.fromString("0.025uosmo"), feeDenom: "uosmo" } }; // Create a client for a specific chain const chainId = "osmosis-1"; const config = chainConfigs.osmosis; const client = await SigningStargateClient.connectWithSigner( config.rpcEndpoint, wallet, { registry, prefix: config.prefix, gasPrice: config.gasPrice } ); ``` ## Best Practices 1. Always use a specific registry with Telescope-generated types 2. Handle network connection errors gracefully 3. Implement proper transaction result parsing 4. Set reasonable timeout values for network operations 5. Use appropriate gas prices for each chain 6. Close Tendermint clients when done to free resources 7. Implement proper error handling for chain-specific errors ---------------------------------- Tree Shakable Hooks # Guide to Generating Tree-Shakable Hooks We are planning to **deprecate the `reactQuery` option** in the Telescope options. Instead, we are introducing **tree-shakable hooks** to replace the functionality. This guide explains how to enable and use the new hooks. ## Overview Tree-shakable hooks are available for both **React** and **Vue**. These hooks are designed to improve modularity and optimize performance by allowing you to import only the functionality you need. Here's the video tutorial link: https://youtu.be/3dRm9HEklMo ## Enabling Tree-Shakable Hooks To enable the generation of tree-shakable hooks, configure the `TelescopeOptions` as follows: ```typescript TelescopeOptions: { ..., helperFunctions?: { ..., enabled: true, hooks?: { react: boolean; vue?: boolean; }; }; } ``` ### **React** Set `TelescopeOptions.helperFunctions.hooks.react` to `true`. ### **Vue** Set `TelescopeOptions.helperFunctions.hooks.vue` to `true`. --- ## Generated Files When enabled, the following files will be generated in the respective directories of each package: - **React Query File**: `v-next/outputhelperfunc/cosmos/react-query` - **Vue Query File**: `v-next/outputhelperfunc/cosmos/vue-query` Additionally, subdirectory files will be generated for specific modules, such as: - `v-next/outputhelperfunc/cosmos/bank/v1beta1/query.rpc.func.ts` - `v-next/outputhelperfunc/cosmos/bank/v1beta1/query.rpc.react.ts` - `v-next/outputhelperfunc/cosmos/bank/v1beta1/query.rpc.vue.ts` --- ## File Details ### **1. `query.rpc.func.ts`** This file contains shared functions that can be used by both React and Vue hooks. For example: ```typescript export const createGetBalance = (clientResolver?: RpcResolver) => buildQuery({ encode: QueryBalanceRequest.encode, decode: QueryBalanceResponse.decode, service: "cosmos.bank.v1beta1.Query", method: "Balance", clientResolver, deps: [QueryBalanceRequest, QueryBalanceResponse], }); ``` --- ### **2. `query.rpc.react.ts`** This file contains React-specific hooks. Example: ```typescript export const useGetBalance = buildUseQuery({ builderQueryFn: createGetBalance, queryKeyPrefix: "BalanceQuery", }); ``` --- ### **3. `query.rpc.vue.ts`** This file contains Vue-specific hooks. Example: ```typescript export const useGetBalance = buildUseVueQuery({ builderQueryFn: createGetBalance, queryKeyPrefix: "BalanceQuery", }); ``` --- ## Advantages of Tree-Shakable Hooks 1. **Modularity**: Single functionality can be invoked independently. 2. **Improved Performance**: Reduces the need for large, bloated objects with unwanted properties. 3. **Framework-Specific**: Provides optimized hooks tailored for React and Vue. By adopting this approach, you can ensure cleaner, more maintainable, and efficient code. ## Use Example of Tree-Shakable Hooks Tree-shakable hooks provide a framework-specific API interaction method. Here's an example of how to use the tree-shakable hook `useGetBalance` in a React component: ```typescript import BigNumber from "bignumber.js"; import { useGetBalance } from '@interchainjs/vue/cosmos/bank/v1beta1/query.rpc.vue'; import { Ref, computed } from "vue"; import { assetLists } from "@chain-registry/v2"; const defaultChainName = 'osmosistestnet' // 'cosmoshub'\ const defaultAssetList = assetLists.find((assetList) => assetList.chainName === defaultChainName) const defaultRpcEndpoint = 'https://rpc.testnet.osmosis.zone' // 'https://cosmos-rpc.publicnode.com' export const useBalanceVue = (address: Ref) => { const coin = defaultAssetList?.assets[0]; const denom = coin!.base! const COIN_DISPLAY_EXPONENT = coin!.denomUnits.find( (unit) => unit.denom === coin!.display )?.exponent as number; const request = computed(() => ({ address: address.value, denom, })); const { data: balance, isSuccess: isBalanceLoaded, isLoading: isFetchingBalance, refetch: refetchBalance } = useGetBalance({ request, options: { enabled: !!address, //@ts-ignore select: ({ balance }) => new BigNumber(balance?.amount ?? 0).multipliedBy( 10 ** -COIN_DISPLAY_EXPONENT ), }, clientResolver: defaultRpcEndpoint, }) return { balance, isBalanceLoaded, isFetchingBalance, refetchBalance, }; }; export default useBalanceVue; ``` In this example, the `defaultChainName`, `defaultAssetList`, and `defaultRpcEndpoint` are hardcoded for demonstration purposes. Please modify them according to your specific requirements. ---------------------------------- Troubleshooting # Troubleshooting This document provides solutions for common issues encountered when using Telescope and Telescope-generated code. ## Common Issues ### Webpack Configuration in Create React App When using Telescope-generated code with Create React App (CRA), you might encounter issues with polyfills and certain modules not being properly resolved due to CRA's webpack configuration restrictions. #### Solution You need to customize the webpack configuration. Since CRA doesn't allow direct webpack configuration modifications, you can use `react-app-rewired` to override the default configuration: 1. Install required packages: ```sh npm install --save-dev react-app-rewired customize-cra ``` 2. Create a `config-overrides.js` file in the root of your project: ```js const { override, addWebpackAlias, addWebpackPlugin } = require('customize-cra'); const webpack = require('webpack'); module.exports = override( // Add polyfills and resolve issues addWebpackPlugin( new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'], process: 'process/browser', }) ), addWebpackAlias({ 'stream': 'stream-browserify', 'path': 'path-browserify', 'crypto': 'crypto-browserify', 'http': 'stream-http', 'https': 'https-browserify', 'os': 'os-browserify/browser', }) ); ``` 3. Modify your `package.json` scripts to use `react-app-rewired`: ```json "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject" } ``` 4. Install the necessary polyfill packages: ```sh npm install --save-dev buffer process stream-browserify path-browserify crypto-browserify stream-http https-browserify os-browserify ``` ### Babel Configuration If you're using a custom Babel setup, you might encounter syntax errors with features like numeric separators or optional chaining that are used in Telescope-generated code. #### Solution Make sure your Babel configuration includes the necessary plugins: ```sh npm install --save-dev @babel/plugin-proposal-numeric-separator @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-nullish-coalescing-operator ``` Add these plugins to your `.babelrc` or `babel.config.js`: ```json { "plugins": [ "@babel/plugin-proposal-numeric-separator", "@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-nullish-coalescing-operator" ] } ``` Or if you're using a preset: ```json { "presets": [ ["@babel/preset-env", { "targets": { "browsers": ["last 2 versions", "not dead"] } }] ] } ``` ### Long Type Errors When using Telescope-generated code, you might see extremely long and complex TypeScript errors that are difficult to understand. #### Solution 1. Use a specific TypeScript version that works well with CosmJS (4.7.x or later recommended) 2. Simplify your types with type assertions in places where TypeScript is having trouble 3. Add `//@ts-ignore` comments for problematic lines as a last resort 4. Enable the `useDeepPartial` option in your Telescope configuration to simplify partial types ### Module Resolution Issues You might encounter "Cannot find module" errors when importing from Telescope-generated code. #### Solution Check your `tsconfig.json` to ensure it has the correct module resolution settings: ```json { "compilerOptions": { "target": "es2020", "module": "esnext", "moduleResolution": "node", "esModuleInterop": true, "resolveJsonModule": true, "baseUrl": "." } } ``` ### Browser Compatibility Issues Telescope-generated code uses modern JavaScript features that might not be compatible with older browsers. #### Solution 1. Ensure you have proper polyfills added to your project 2. Configure Babel to target the browsers you need to support 3. Consider using a tool like `core-js` for comprehensive polyfills: ```sh npm install core-js ``` Then import it at the top of your entry file: ```js import 'core-js/stable'; ``` ### WebAssembly Support Some CosmJS dependencies use WebAssembly, which might cause issues in certain environments. #### Solution Make sure your bundler is configured to handle WebAssembly files (.wasm). For webpack: ```js module.exports = { // ... experiments: { asyncWebAssembly: true, }, }; ``` ### Specific Blockchain Issues #### Transaction Broadcasting Fails If transactions are being rejected by the network: 1. Ensure account sequence is correct 2. Verify gas settings are appropriate 3. Check that the chain ID matches the network you're connecting to 4. Verify that the RPC endpoint is functioning correctly #### Query Errors If queries are failing: 1. Check the RPC or API endpoint URL 2. Ensure the blockchain node is synchronized 3. Verify query parameters are correctly formatted 4. Check for rate limiting issues ## Debugging Tips ### Enable Console Logging Add more verbose logging to help identify issues: ```typescript const client = await SigningStargateClient.connectWithSigner( rpcEndpoint, signer, { logger: new ConsoleLogger("debug") } ); ``` ### Capture Error Details Ensure you're capturing and logging full error details: ```typescript try { // Your code } catch (error) { console.error("Detailed error:", { message: error.message, name: error.name, stack: error.stack, details: error.details || "no details", data: error.data || "no data", code: error.code || "no code" }); } ``` ### Network Monitoring Use browser developer tools to monitor network requests: 1. Open your browser's developer tools (F12) 2. Go to the Network tab 3. Filter for XHR/Fetch requests 4. Look for requests to RPC endpoints 5. Examine request payloads and response data ## Getting Help If you encounter issues not covered in this document: 1. Check the [GitHub Issues](https://github.com/hyperweb-io/telescope/issues) for similar problems 2. Ask on the [Cosmos Developer Discord](https://discord.gg/cosmosnetwork) in the #developers channel 3. For Telescope-specific help, reach out in the #telescope channel on Discord 4. [Submit an issue](https://github.com/hyperweb-io/telescope/issues/new) with a complete description, including: - Error messages - Telescope version - Node.js version - Operating system - Steps to reproduce the issue ---------------------------------- Types # Types Telescope generates TypeScript types from Protocol Buffer definitions for easy integration with TypeScript projects. This page documents the key types generated by Telescope and how to use them. ## Basic Types | Type Category | Description | Example | | ------------- | ----------- | ------- | | Message Types | TypeScript interfaces that match the structure of Protobuf messages | `MsgSend`, `QueryBalanceRequest` | | Enum Types | TypeScript enums that represent Protobuf enumerations | `BondStatus`, `ResponseCheckTxType` | | Service Types | Types for RPC service interfaces | `MsgClientImpl`, `QueryClientImpl` | | Registry Types | Types for registering and managing message types | `GeneratedType`, `Registry` | ## Message Types For each Protobuf message, Telescope generates several types and helper functions: | Generated Item | Description | Example | | -------------- | ----------- | ------- | | Interface | TypeScript interface matching the message structure | `export interface MsgSend { ... }` | | Constructor Functions | Functions to create message instances | `export const MsgSend = { encode, decode, ... }` | | Encoding Functions | Functions to serialize/deserialize messages | `encode`, `decode`, `fromPartial`, `fromJSON` | ### Example Message Type ```typescript // Generated interface export interface MsgSend { fromAddress: string; toAddress: string; amount: Coin[]; } // Generated static methods for the message export const MsgSend = { encode(message: MsgSend, writer: Writer = Writer.create()): Writer { ... }, decode(input: Reader | Uint8Array, length?: number): MsgSend { ... }, fromJSON(object: any): MsgSend { ... }, toJSON(message: MsgSend): unknown { ... }, fromPartial(object: DeepPartial): MsgSend { ... } } ``` ## Any Types Telescope handles Protobuf's `Any` type, which allows for dynamic typing: ```typescript export interface Any { typeUrl: string; value: Uint8Array; } ``` ### Working with Any Types ```typescript import { Any } from "./google/protobuf/any"; import { MsgSend } from "./cosmos/bank/v1beta1/tx"; // Packing a message into Any const msgSend: MsgSend = { ... }; const packedMsg = Any.pack(msgSend, "/cosmos.bank.v1beta1.MsgSend"); // Unpacking a message from Any (using interfaces) const unpackedMsg = packedMsg.unpack(MsgSend); ``` ## Amino Types (for Legacy Compatibility) For compatibility with legacy systems (like Keplr), Telescope generates Amino types: | Type | Description | Example | | ---- | ----------- | ------- | | AminoMsg | Amino-compatible message format for signing | `export interface AminoMsgSend { ... }` | | AminoConverter | Converts between Amino and native formats | `export const aminoConverter = { ... }` | ### Example Amino Types ```typescript export interface AminoMsgSend { type: "cosmos-sdk/MsgSend"; value: { from_address: string; to_address: string; amount: { denom: string; amount: string; }[]; }; } export const aminoConverters = { "/cosmos.bank.v1beta1.MsgSend": { aminoType: "cosmos-sdk/MsgSend", toAmino: (message: MsgSend): AminoMsgSend["value"] => { ... }, fromAmino: (object: AminoMsgSend["value"]): MsgSend => { ... } } } ``` ## Registry Types Telescope generates registry types for registering message types with various client libraries: ```typescript export const registry = [ ["/cosmos.bank.v1beta1.MsgSend", MsgSend], ["/cosmos.staking.v1beta1.MsgDelegate", MsgDelegate], // other message types... ]; export const load = (protoRegistry: Registry) => { registry.forEach(([typeUrl, type]) => { protoRegistry.register(typeUrl, type as GeneratedType); }); }; ``` ## Custom Type Handling Telescope handles special types with custom encoding/decoding: | Type | TypeScript Representation | Notes | | ---- | ------------------------- | ----- | | `google.protobuf.Timestamp` | `Date` or `{ seconds: Long; nanos: number }` | Configurable via options | | `google.protobuf.Duration` | `{ seconds: Long; nanos: number }` or `string` | Configurable via options | | `cosmos.base.v1beta1.Coin` | `{ denom: string; amount: string }` | Represents blockchain tokens | | `Int64`/`Uint64` | `bigint` or `Long` | Configurable via options | ## Client Types Telescope generates several client types for interacting with blockchain nodes: | Client Type | Description | Example Usage | | ----------- | ----------- | ------------ | | Query Client | For querying blockchain state | `queryClient.getBalance(...)` | | Tx Client | For sending transactions | `txClient.send(...)` | | RPC Client | For low-level RPC calls | `rpcClient.abciQuery(...)` | | LCD Client | For REST API calls | `lcdClient.cosmos.bank.v1beta1.allBalances(...)` | ## Type Helper Functions | Function | Description | Example | | -------- | ----------- | ------- | | `DeepPartial` | Creates a type with all properties of T set to optional | `DeepPartial` | | `fromPartial` | Creates an object from a partial input | `MsgSend.fromPartial({...})` | | `toJSON` | Converts a message to a JSON-compatible object | `MsgSend.toJSON(msg)` | | `fromJSON` | Creates a message from a JSON-compatible object | `MsgSend.fromJSON({...})` | ## Using Generated Types ```typescript import { MsgSend } from "./cosmos/bank/v1beta1/tx"; import { queryClient, txClient } from "./client"; // Create a message const sendMsg = MsgSend.fromPartial({ fromAddress: "cosmos1...", toAddress: "cosmos1...", amount: [{ denom: "uatom", amount: "1000000" }] }); // Query the blockchain const balance = await queryClient.cosmos.bank.v1beta1.balance({ address: "cosmos1...", denom: "uatom" }); // Send a transaction const result = await txClient.signAndBroadcast( [sendMsg], { gas: "200000", amount: [{ denom: "uatom", amount: "5000" }] } ); ``` ## Advanced Type Features | Feature | Description | | ------- | ----------- | | Union Types | Generated for oneOf fields in Protobuf | | Nested Types | Generated for nested message definitions | | Global Interface Registry | Manages type information for runtime reflection | | Type Information | Metadata attached to types for runtime operations | ## Type Safety Considerations - All generated types are strongly typed for TypeScript safety - Optional fields are properly marked with `?` - Arrays and repeated fields are correctly typed - Custom scalar types have proper handling and validation - Functions include proper type checking