# schemos > Type-safe CosmWasm contract interactions, zero codegen ## Overview ### How It Works ``` cargo schema → JSON Schema (as const) → schemos ├─ compile time: json-schema-to-ts infers TS types └─ runtime: Ajv validates before tx broadcast ``` Single source of truth. The same JSON Schema drives both type inference and runtime validation. [json-schema-to-ts](https://github.com/ThomasAriwortsell/json-schema-to-ts) (used by Fastify, 2M+ weekly downloads) handles compile-time inference. [Ajv](https://ajv.js.org/) (100M+ weekly downloads) handles runtime validation. schemos connects these to any CosmWasm client through a minimal interface. ### Architecture schemos is organized into four layers: | Layer | Module | Role | Output | | ----- | ------------------- | ------------------------------------------------------------ | ---------------------------------------------------------- | | **1** | `schemos/msg` | Type inference (zero runtime) | `InferMsg`, `MessageNames`, `MessageArgs`, `InferResponse` | | **2** | `schemos/msg` | Msg build + Ajv validation (with CosmWasm format validators) | `{ transfer: { amount, recipient } }` | | — | `schemos/encoding` | JSON encoding utilities | `Uint8Array`, `base64 string` | | **3** | `schemos/telescope` | Callback-based client adapter | `CosmWasmQueryClient`, `CosmWasmExecuteClient` | | **4** | `schemos` | Bind msg builder to client | `TypedContract`, `TypedQueryContract` | * **Layers 1–2** are schemos's core: type inference + validation from JSON Schema * **Layers 3–4** bridge to chain SDK message layers * Telescope/interchainjs users can use Layer 2 alone for typed msgs, then handle protobuf encoding with their own chain SDK ### Why Factory Pattern schemos uses a factory pattern (`createTypedContract`, `createMsgBuilder`, `createMsgValidator`) instead of inline function calls: ```ts twoslash // @moduleResolution: bundler // @module: esnext import { createMsgBuilder, createMsgValidator } from 'schemos' import { cw20 } from 'schemos/schemas' // ---cut--- // ✅ Factory — FromSchema evaluated once at creation, reused across calls const cw20Msg = createMsgBuilder(cw20.execute) cw20Msg('transfer', { amount: '1000', recipient: 'osmo1...' }) const validateInit = createMsgValidator(cw20.instantiate) validateInit({ name: 'Token', symbol: 'TKN', decimals: 6, initial_balances: [] }) ``` EVM ABIs are flat arrays of function signatures. CosmWasm JSON Schemas use recursive `oneOf` with `$ref`/`definitions` — `FromSchema` must resolve all branches recursively, which is far more expensive for the type checker. Without the factory pattern, each call site re-evaluates `FromSchema` on the full schema, hitting TypeScript's instantiation depth limit (TS2589). The factory resolves it once at creation time and reuses the result across calls. :::note schemos patches `json-schema-to-ts` to raise its internal depth limit, but this is only available via pnpm's `patchedDependencies` until the [upstream PR](https://github.com/ThomasAribart/json-schema-to-ts/pull/230) is merged. The factory pattern is the actual solution — it works regardless of package manager. ::: ### Client Interface schemos does not depend on cosmjs, interchainjs, or any specific client. It defines a minimal interface that any CosmWasm client already satisfies: ```ts interface CosmWasmQueryClient { queryContractSmart( address: string, query: Record, ): Promise } interface CosmWasmExecuteClient extends CosmWasmQueryClient { execute( sender: string, address: string, msg: Record, fee: StdFee | 'auto', memo?: string, funds?: readonly Coin[], ): Promise } ``` * cosmjs's `SigningCosmWasmClient` conforms as-is — no adapter needed * Telescope SDKs use protobuf RPCs with a different signature — schemos provides a [callback-based adapter](/api/telescope) * `TExecuteResult` captures the client's broadcast result type (e.g., cosmjs's `ExecuteResult`) * Query return types come from the schema, not the client ### Why Telescope Adapter Uses Callbacks CosmWasm is an opt-in Cosmos SDK module. `MsgExecuteContract` lives in each chain's telescope package, not in a shared package: ``` @interchainjs/cosmos-types → Cosmos SDK core only (no cosmwasm) Per-chain telescope packages: @xpla/xplajs → cosmwasm/wasm/v1/tx osmojs → cosmwasm/wasm/v1/tx neutronjs → cosmwasm/wasm/v1/tx ``` Since each chain imports `MsgExecuteContract` from a different package, schemos cannot depend on any specific one. The adapter accepts callbacks so schemos stays chain-agnostic. ### Why No cosmjs Adapter Layer 2 output (plain JS object envelope) is directly usable by cosmjs APIs: ```ts // cosmjs — execute() accepts JsonObject (= any) await client.execute(sender, contract, envelope, fee) // graz — Record executeContract({ msg: envelope, ... }) ``` cosmjs's `SigningCosmWasmClient.execute()` handles `MsgExecuteContract` construction and protobuf encoding internally. schemos only needs to provide the typed `msg` field. ### Comparison | | @cosmwasm/ts-codegen | Manual typing | **schemos** | | ------------------ | ---------------------- | ---------------- | ----------------------------- | | Approach | Full codegen | Hand-written | JSON Schema → inference | | Generated code | Hundreds of lines | 0 | 0 | | Schema change | Re-run + commit | Manual update | Replace JSON | | Compile-time types | Yes | If maintained | Yes | | Runtime validation | No | No | Yes (Ajv) | | Execute result | cosmjs `ExecuteResult` | Any | Inferred from client generic | | Query result | Generated per query | Manual per query | Inferred from response schema | | Client dependency | cosmjs only | Any | Any (minimal interface) | ### EVM Analogy | Layer | EVM | CosmWasm (with schemos) | | ------------------ | --------------------- | ----------------------- | | Contract language | Solidity | Rust (CosmWasm) | | Schema output | ABI JSON | JSON Schema | | Type inference | abitype | json-schema-to-ts | | Runtime validation | viem (ABI validation) | Ajv | | Typed client | viem | schemos | ## cosmjs `SigningCosmWasmClient` works directly with schemos — no adapter needed. ### Execute + Query ```ts twoslash // @moduleResolution: bundler // @module: esnext import type { OfflineSigner } from '@cosmjs/proto-signing' declare const rpcEndpoint: string declare const signer: OfflineSigner declare const contractAddress: string declare const senderAddress: string // ---cut--- import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { createTypedContract } from 'schemos' import { cw20 } from 'schemos/schemas' const client = await SigningCosmWasmClient.connectWithSigner(rpcEndpoint, signer) const token = createTypedContract(client, contractAddress, cw20) // Message names autocomplete, fields are type-checked await token.execute( senderAddress, 'transfer', { recipient: 'osmo1...', amount: '1000' }, 'auto', ) // Return type inferred from response schema const { balance } = await token.query('balance', { address: 'osmo1...' }) // ^? ``` ### Query-only Use `CosmWasmClient` (no signing) for read-only access: ```ts twoslash // @moduleResolution: bundler // @module: esnext declare const rpcEndpoint: string // ---cut--- import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { createTypedContract } from 'schemos' import { cw20 } from 'schemos/schemas' const client = await CosmWasmClient.connect(rpcEndpoint) const token = createTypedContract(client, 'osmo1...', { query: cw20.query, responses: cw20.responses, }) // query() available with typed responses const { balance } = await token.query('balance', { address: 'osmo1...' }) // ^? // execute() does not exist on query-only contracts ``` ### Full Example ```ts twoslash // @moduleResolution: bundler // @module: esnext declare const mnemonic: string declare const rpcEndpoint: string declare const contractAddress: string // ---cut--- import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' import { GasPrice } from '@cosmjs/stargate' import { createTypedContract } from 'schemos' import { cw20 } from 'schemos/schemas' // 1. Connect with cosmjs const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic) const client = await SigningCosmWasmClient.connectWithSigner( rpcEndpoint, wallet, { gasPrice: GasPrice.fromString('0.025uosmo') }, ) // 2. Create typed contract — no adapter needed const token = createTypedContract(client, contractAddress, cw20) // 3. Type-safe interactions const [account] = await wallet.getAccounts() await token.execute( account.address, 'transfer', { recipient: 'osmo1...', amount: '1000' }, 'auto', ) const { balance } = await token.query('balance', { address: account.address }) // ^? ``` ## Telescope SDKs Telescope-generated SDKs (xplajs, osmojs, neutronjs) use protobuf RPCs instead of cosmjs's JSON API. schemos provides a generic adapter via `schemos/telescope`. See [Telescope Adapters API](/api/telescope) for full type reference. ### Why an Adapter? CosmWasm is an opt-in Cosmos SDK module. `MsgExecuteContract` and query RPCs live in each chain's telescope package — not in a shared package: ``` @interchainjs/cosmos-types → Cosmos SDK core only (no cosmwasm) Per-chain telescope packages (include cosmwasm if chain supports it): @xpla/xplajs → cosmwasm/wasm/v1/tx osmojs → cosmwasm/wasm/v1/tx neutronjs → cosmwasm/wasm/v1/tx ``` The adapter accepts callbacks so schemos doesn't depend on any specific chain SDK. ### Query Adapter ```ts twoslash // @moduleResolution: bundler // @module: esnext declare const rpcEndpoint: string // ---cut--- import { createQueryAdapter } from 'schemos/telescope' import { createTypedContract } from 'schemos' import { cw20 } from 'schemos/schemas' import { createGetSmartContractState } from '@xpla/xplajs/cosmwasm/wasm/v1/query.rpc.func' const smartContractState = createGetSmartContractState(rpcEndpoint) const adapter = createQueryAdapter(smartContractState) const token = createTypedContract(adapter, 'xpla1...', { query: cw20.query, responses: cw20.responses, }) const { balance } = await token.query('balance', { address: 'xpla1...' }) // ^? ``` ### Execute Adapter ```ts twoslash // @moduleResolution: bundler // @module: esnext import type { DirectSigner, AminoSigner } from '@interchainjs/cosmos' declare const signingClient: DirectSigner | AminoSigner declare const rpcEndpoint: string declare const sender: string // ---cut--- import { createExecuteAdapter } from 'schemos/telescope' import { createTypedContract } from 'schemos' import { cw20 } from 'schemos/schemas' import { MsgExecuteContract } from '@xpla/xplajs/cosmwasm/wasm/v1/tx' import { createGetSmartContractState } from '@xpla/xplajs/cosmwasm/wasm/v1/query.rpc.func' const smartContractState = createGetSmartContractState(rpcEndpoint) const { signAndBroadcast } = signingClient const adapter = createExecuteAdapter( smartContractState, signAndBroadcast, (p) => MsgExecuteContract.encode( MsgExecuteContract.fromPartial({ ...p, funds: [...p.funds] }), ).finish(), ) const token = createTypedContract(adapter, 'xpla1...', cw20) await token.execute( sender, 'transfer', { recipient: 'xpla1...', amount: '1000' }, 'auto', ) ``` :::info The `MsgExecuteContract.encode()` pattern is identical across all telescope packages — only the import path changes. ::: ### Chain-specific imports #### xplajs ```ts import { MsgExecuteContract } from '@xpla/xplajs/cosmwasm/wasm/v1/tx' import { createGetSmartContractState } from '@xpla/xplajs/cosmwasm/wasm/v1/query.rpc.func' ``` #### osmojs ```ts import { MsgExecuteContract } from 'osmojs/cosmwasm/wasm/v1/tx' ``` :::note osmojs uses an older telescope codegen that exports a class-based `QueryClientImpl` instead of `createGetSmartContractState`. The `smartContractState` method signature is compatible — bind it from the query client instance. ::: #### neutronjs ```ts import { MsgExecuteContract } from 'neutronjs/cosmwasm/wasm/v1/tx' import { createGetSmartContractState } from 'neutronjs/cosmwasm/wasm/v1/query.rpc.func' ``` ## Wallet Kits (React) schemos works with any React wallet library. Here are patterns for the most popular ones. ### interchain-kit [interchain-kit](https://github.com/cosmology-tech/interchain-kit) provides `useChain` with an interchainjs signing client — requires the `schemos/telescope` adapter. ```ts twoslash // @moduleResolution: bundler // @module: esnext import { useMemo } from 'react' import { useChain } from '@interchain-kit/react' import { MsgExecuteContract } from '@xpla/xplajs/cosmwasm/wasm/v1/tx' import { createGetSmartContractState } from '@xpla/xplajs/cosmwasm/wasm/v1/query.rpc.func' import { createExecuteAdapter } from 'schemos/telescope' import { createTypedContract } from 'schemos' import { cw20 } from 'schemos/schemas' function useCw20(contractAddress: string, rpcEndpoint: string) { const { signingClient } = useChain('xpla') return useMemo(() => { if (!signingClient) return null const smartContractState = createGetSmartContractState(rpcEndpoint) const adapter = createExecuteAdapter( smartContractState, (sender, msgs, fee, memo) => signingClient.signAndBroadcast(sender, msgs, fee, memo), (p) => MsgExecuteContract.encode( MsgExecuteContract.fromPartial({ ...p, funds: [...p.funds] }), ).finish(), ) return createTypedContract(adapter, contractAddress, cw20) }, [signingClient, contractAddress, rpcEndpoint]) } ``` ### cosmos-kit [cosmos-kit](https://github.com/cosmology-tech/cosmos-kit) returns cosmjs clients — **no adapter needed**. ```ts twoslash // @moduleResolution: bundler // @module: esnext import { useMemo } from 'react' import { useChain } from '@cosmos-kit/react' import { createTypedContract } from 'schemos' import { cw20 } from 'schemos/schemas' function useCw20(contractAddress: string) { const { getSigningCosmWasmClient } = useChain('osmosis') return useMemo(async () => { const client = await getSigningCosmWasmClient() return createTypedContract(client, contractAddress, cw20) }, [getSigningCosmWasmClient, contractAddress]) } ``` ### graz [graz](https://github.com/graz-sh/graz) provides lightweight cosmjs hooks — **no adapter needed**. ```ts twoslash // @moduleResolution: bundler // @module: esnext import { useMemo } from 'react' import { useCosmWasmSigningClient } from 'graz' import { createTypedContract } from 'schemos' import { cw20 } from 'schemos/schemas' const CHAIN_ID = 'osmosis-1' function useCw20(contractAddress: string) { const { data: clients } = useCosmWasmSigningClient({ chainId: [CHAIN_ID] }) return useMemo(() => { const client = clients?.[CHAIN_ID] if (!client) return undefined return createTypedContract(client, contractAddress, cw20) }, [clients, contractAddress]) } ``` ### Comparison | | interchain-kit | cosmos-kit | graz | | -------------- | ---------------------------------- | ------------------------------ | -------------------------------- | | Signing client | interchainjs | cosmjs | cosmjs | | Adapter needed | `schemos/telescope` | None | None | | Wallet support | Keplr, Cosmostation, WalletConnect | Keplr, Leap, Cosmostation, +20 | Keplr, Leap, Cosmostation, +more | | React hooks | `useChain()` | `useChain()` | `useCosmWasmSigningClient()` | | Bundle size | Larger | Medium | Lightweight | All approaches give you the same schemos DX — type-safe execute/query with autocomplete and runtime validation. The difference is only in how the signing client is obtained and whether an adapter is needed. ## Custom Contracts schemos works with any CosmWasm contract that has JSON Schema output from `cargo schema`. ### Generate Schemas In your contract's Rust project: ```bash cargo schema # → schema/raw/ # execute.json # query.json # response_to_balance.json # response_to_get_config.json # ... ``` ### Import as `as const` Copy the JSON files into your TypeScript project and paste them as `as const` objects: ```ts const executeSchema = { /* paste cargo schema JSON here */ } as const const querySchema = { /* paste cargo schema JSON here */ } as const ``` :::info `as const` is required. Without it, TypeScript widens the JSON to generic types and `json-schema-to-ts` cannot infer specific message types. This is a TypeScript limitation — JSON imports (`import ... from '*.json'`) lose literal types during module resolution. The same constraint applies to EVM tooling like abitype/viem with ABI JSON. Paste the JSON directly and mark it `as const`. See. [https://github.com/microsoft/TypeScript/issues/32063](https://github.com/microsoft/TypeScript/issues/32063) ::: ### Basic Usage ```ts import { createTypedContract } from 'schemos' const contract = createTypedContract(client, 'osmo1...', { execute: executeSchema, query: querySchema, }) // Full autocomplete + type checking for your contract messages await contract.execute(sender, 'your_msg', { /* typed fields */ }, 'auto') const result = await contract.query('get_state', {}) // result: unknown (no response schemas provided) ``` ### With Response Typing Paste response schemas and map them by query message name: ```ts const responseSchemas = { get_state: { /* paste response_to_get_state.json */ } as const, get_config: { /* paste response_to_get_config.json */ } as const, } const contract = createTypedContract(client, 'osmo1...', { execute: executeSchema, query: querySchema, responses: responseSchemas, }) // Return type inferred from response schema const state = await contract.query('get_state', {}) // state: { owner: string; count: number; ... } — inferred from response JSON Schema ``` ### Standalone Message Building Use [`createMsgBuilder`](/api/msg#createmsgbuilder) when you need typed messages without a client: ```ts import { createMsgBuilder } from 'schemos' const myMsg = createMsgBuilder(executeSchema) const envelope = myMsg('your_msg', { /* typed fields */ }) // Use envelope with any client or SDK await client.execute(sender, contract, envelope, fee) ``` ### Type Extraction Extract TypeScript types from your schemas for use elsewhere: ```ts import type { InferMsg, MessageNames, MessageArgs, InferResponse } from 'schemos' // Full message union type type MyExecuteMsg = InferMsg // Just the message names type MyMsgNames = MessageNames // Args for a specific message type YourMsgArgs = MessageArgs // Response type for a specific query type StateResponse = InferResponse ``` ### Limitations * **Source required**: JSON Schema is not stored on-chain. You need the contract source (or its published schema) to use schemos. This is the same limitation as EVM contracts without verified source/ABI. * **`as const` required**: JSON imports lose literal types during module resolution ([TypeScript#32063](https://github.com/microsoft/TypeScript/issues/32063)). Paste the JSON directly and mark it `as const`. ## createTypedContract Creates a typed contract instance that binds JSON Schema validation and type inference to a CosmWasm client. ```ts import { createTypedContract } from 'schemos' ``` ### Signatures #### Execute + Query ```ts function createTypedContract( client: CosmWasmExecuteClient, contractAddress: string, schemas: { execute: TExecuteSchema query: TQuerySchema responses?: TResponses validateResponses?: boolean }, ): TypedContract, FromSchema, TExecuteResult, TResponses> ``` #### Query-only ```ts function createTypedContract( client: CosmWasmQueryClient, contractAddress: string, schemas: { query: TQuerySchema responses?: TResponses validateResponses?: boolean }, ): TypedQueryContract, TResponses> ``` When only `query` (and optionally `responses`) is provided, the returned contract has no `execute` method. ### Parameters | Parameter | Type | Description | | ----------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `client` | `CosmWasmExecuteClient` \| `CosmWasmQueryClient` | Any object matching the [client interface](/overview#client-interface). cosmjs clients work directly; telescope SDKs need an [adapter](/api/telescope). | | `contractAddress` | `string` | Contract bech32 address | | `schemas` | `{ execute?, query, responses?, validateResponses? }` | Raw `cargo schema` JSON imported as `as const` | ### Return Type #### TypedContract Returned when `execute` schema is provided. ```ts interface TypedContract { execute>( sender: string, msg: K, args: MessageArgs, fee: StdFee | 'auto', memo?: string, funds?: readonly Coin[], ): Promise query>( msg: K, args: MessageArgs, ): Promise } ``` #### TypedQueryContract Returned when only `query` is provided (no `execute`). ```ts interface TypedQueryContract { query>( msg: K, args: MessageArgs, ): Promise } ``` ### Response Typing Query return types are inferred from the `responses` schema map. Each key maps a query message name to its response JSON Schema: ```ts const token = createTypedContract(client, addr, { query: cw20.query, responses: cw20.responses, }) const { balance } = await token.query('balance', { address: 'osmo1...' }) // balance: string — inferred from response schema ``` Without `responses`, query returns `unknown`. Execute return types come from the client's `TExecuteResult` generic — not from the contract schema. #### Response Validation Set `validateResponses: true` to also validate query responses at runtime against the response schemas: ```ts const token = createTypedContract(client, addr, { query: cw20.query, responses: cw20.responses, validateResponses: true, }) // Response is validated against the response schema after fetching const { balance } = await token.query('balance', { address: 'osmo1...' }) ``` This is useful for catching unexpected contract responses early. Disabled by default since responses come from the chain and are typically well-formed. ### Runtime Validation Every `execute` and `query` call validates the message envelope against the JSON Schema using Ajv before sending. If validation fails, an error is thrown immediately — no gas is spent. ```ts await token.execute(sender, 'transfer', { recipient: 123 }, 'auto') // throws: Execute validation failed for "transfer": data/transfer/recipient must be string ``` Validators are compiled once per schema and cached via `WeakMap`. Custom CosmWasm format validators (uint8, uint32, uint64) are registered automatically. ### Examples See integration guides for full usage examples: * [cosmjs](/integrations/cosmjs) — direct usage, no adapter needed * [Telescope SDKs](/integrations/telescope) — xplajs, osmojs, neutronjs with adapter * [Wallet Kits](/integrations/wallet-kits) — React hooks (interchain-kit, cosmos-kit, graz) * [Custom Contracts](/guides/custom-contracts) — your own contract schemas ## Encoding Platform-neutral JSON encoding utilities for CosmWasm message fields. ```ts import { Json } from 'schemos' ``` ### Json.toBytes Encode a JSON-serializable object to UTF-8 bytes. ```ts Json.toBytes(value: Record): Uint8Array ``` Use for `MsgExecuteContract.msg` and `queryData` fields in telescope SDKs: ```ts import { Json } from 'schemos' import { MsgExecuteContract } from 'osmojs/cosmwasm/wasm/v1/tx' // Json is re-exported from the main schemos entrypoint const envelope = cw20Msg('transfer', { amount: '1000', recipient: 'osmo1...' }) MsgExecuteContract.fromPartial({ sender, contract, msg: Json.toBytes(envelope), funds: [], }) ``` ### Json.toBase64 Encode a JSON-serializable object to a base64 string. ```ts Json.toBase64(value: Record): string ``` Use for CosmWasm `Binary` fields — e.g., the `msg` field in cw20 `send` (hook message to receiving contract): ```ts import { Json } from 'schemos' cw20Msg('send', { amount: '1000', contract: 'osmo1hook...', msg: Json.toBase64({ execute_swap: { offer_asset: 'uosmo' } }), }) ``` ### Json.fromBytes Decode UTF-8 bytes to a JSON object. ```ts Json.fromBytes(bytes: Uint8Array): unknown ``` Use for decoding query responses from telescope RPC: ```ts import { Json } from 'schemos' const res = await rpc.cosmwasm.wasm.v1.smartContractState({ address, queryData }) const result = Json.fromBytes(res.data) ``` ### Implementation Notes * Uses `TextEncoder`/`TextDecoder` for UTF-8 (platform-neutral — works in Node.js and browsers) * Base64 encoding uses a lookup table approach adapted from [ox](https://github.com/wevm/ox) (MIT, wevm LLC) * No dependency on Node.js `Buffer` ## Msg The msg module provides type inference and validated message building from JSON Schema — the core of schemos. ```ts import { createMsgBuilder, createMsgValidator } from 'schemos' import type { InferMsg, MessageNames, MessageArgs, InferResponse, MsgBuilder, MsgValidator, } from 'schemos' ``` ### Why Msg? [`createTypedContract`](/api/create-typed-contract) binds msg building to a specific client and contract address. The msg module gives you just the typed message envelope or validated data — useful when you want to: * Build messages without a client (e.g., for batch transactions) * Use protobuf encoding from your own chain SDK * Validate instantiate messages before deploying * Compose messages for `executeMultiple` or multi-message transactions ### createMsgBuilder Creates a reusable typed message builder from a JSON Schema. The schema type is resolved once at factory level — subsequent calls have zero type overhead. ```ts function createMsgBuilder( schema: TSchema, ): MsgBuilder> ``` #### Parameters | Parameter | Type | Description | | --------- | ------------ | --------------------------------------------------- | | `schema` | `JSONSchema` | A `cargo schema` JSON object imported as `as const` | #### Returns A callable `MsgBuilder` that builds validated `{ [msgName]: args }` envelopes. #### Example ```ts twoslash // @moduleResolution: bundler // @module: esnext import { createMsgBuilder } from 'schemos' import { cw20 } from 'schemos/schemas' const cw20Msg = createMsgBuilder(cw20.execute) // Typed envelope — autocomplete on msg names and args const envelope = cw20Msg('transfer', { amount: '1000', recipient: 'osmo1...' }) // envelope: { transfer: { amount: string; recipient: string } } ``` #### Batch usage The schema is compiled once at factory creation. Safe for repeated calls: ```ts const cw20Msg = createMsgBuilder(cw20.execute) const msgs = [ cw20Msg('transfer', { amount: '100', recipient: addr1 }), cw20Msg('transfer', { amount: '200', recipient: addr2 }), ] ``` #### With telescope protobuf encoding ```ts twoslash declare const sender: string declare const contract: string // ---cut--- // @moduleResolution: bundler // @module: esnext import { createMsgBuilder, Json } from 'schemos' import { cw20 } from 'schemos/schemas' import { MsgExecuteContract } from 'osmojs/cosmwasm/wasm/v1/tx' const cw20Msg = createMsgBuilder(cw20.execute) const envelope = cw20Msg('transfer', { amount: '1000', recipient: 'osmo1...' }) // Use with telescope's protobuf encoding MsgExecuteContract.fromPartial({ sender, contract, msg: Json.toBytes(envelope), funds: [], }) ``` ### createMsgValidator Creates a typed validator from a JSON Schema. Useful for validating instantiate messages and other flat struct schemas where the envelope pattern (`{ [msgName]: args }`) doesn't apply. ```ts function createMsgValidator( schema: TSchema, ): MsgValidator> ``` #### Parameters | Parameter | Type | Description | | --------- | ------------ | --------------------------------------------------- | | `schema` | `JSONSchema` | A `cargo schema` JSON object imported as `as const` | #### Returns A callable `MsgValidator` that type-checks the input at compile time and validates it at runtime. #### Example ```ts twoslash // @moduleResolution: bundler // @module: esnext import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' declare const client: SigningCosmWasmClient declare const sender: string declare const codeId: number // ---cut--- import { createMsgValidator } from 'schemos' import { cw20 } from 'schemos/schemas' const validateInit = createMsgValidator(cw20.instantiate) // initMsg type is inferred from cw20.instantiate schema const initMsg = validateInit({ name: 'Token', symbol: 'TKN', decimals: 6, initial_balances: [{ address: 'osmo1...', amount: '1000000' }], }) await client.instantiate(sender, codeId, initMsg, 'label', 'auto') ``` #### Error handling ```ts twoslash // @moduleResolution: bundler // @module: esnext // @errors: 2322 2561 2345 import { createMsgValidator } from 'schemos' import { cw20 } from 'schemos/schemas' // ---cut--- // Compile error — missing required fields (symbol, decimals, initial_balances) const validateInit = createMsgValidator(cw20.instantiate) validateInit({ name: 'Token', symbol: 'TKN', decimal: 6, initial_balances: [{ address: 'osmo1...', amount: '1000000' }], }) validateInit({ name: 'Token', symbol: 'TKN', decimals: 6, initial_balances: [{ address: 'osmo1...', amount: 1000000 }], }) // Runtime error — if invalid data bypasses type checking (e.g., from external source) // throws: Validation failed: data must have required property 'symbol', ... ``` #### Validation `createMsgBuilder` and `createMsgValidator` both validate data against the schema at runtime. Validation errors are thrown immediately — before gas is spent. Validators are cached in a module-level `WeakMap` keyed by schema reference — out-of-scope schemas are garbage collected. ### CosmWasm Format Validators Ajv is configured with custom format validators for CosmWasm-specific integer formats emitted by `cosmwasm-schema`: | Format | Range | Description | | -------- | ----------------------------- | -------------------------------------------------------- | | `uint8` | 0 - 255 | 8-bit unsigned integer | | `uint32` | 0 - 4,294,967,295 | 32-bit unsigned integer | | `uint64` | 0 - `Number.MAX_SAFE_INTEGER` | 64-bit unsigned integer (limited by JS number precision) | These are registered automatically when any schema is compiled. Values above `Number.MAX_SAFE_INTEGER` for uint64 must be handled via `BigInt` at the application layer. ### Type Utilities #### InferMsg Infer the full TypeScript union type from a JSON Schema: ```ts type InferMsg = FromSchema ``` ```ts import type { InferMsg } from 'schemos' import { cw20 } from 'schemos/schemas' type Cw20ExecuteMsg = InferMsg // { transfer: { recipient: string; amount: string } } // | { burn: { amount: string } } // | { send: { contract: string; amount: string; msg: string } } // | ... ``` #### MessageNames Extract all top-level message names from a message union: ```ts type MessageNames = T extends Record ? keyof T & string : never ``` ```ts import type { InferMsg, MessageNames } from 'schemos' import { cw20 } from 'schemos/schemas' type Names = MessageNames> // 'transfer' | 'burn' | 'send' | 'mint' | 'increase_allowance' | ... ``` #### MessageArgs Extract the args type for a specific message name: ```ts type MessageArgs = T extends Record ? V : never ``` ```ts import type { InferMsg, MessageArgs } from 'schemos' import { cw20 } from 'schemos/schemas' type TransferArgs = MessageArgs, 'transfer'> // { recipient: string; amount: string } ``` #### InferResponse Infer a specific response type from a responses schema map: ```ts type InferResponse< TResponses extends Record, K extends keyof TResponses & string, > = FromSchema ``` ```ts import type { InferResponse } from 'schemos' import { cw20 } from 'schemos/schemas' type BalanceResponse = InferResponse // { balance: string } ``` #### MsgBuilder The callable type returned by `createMsgBuilder`: ```ts type MsgBuilder = >( msg: K, args: MessageArgs, options?: { context?: string }, ) => { [P in K]: MessageArgs } ``` #### MsgValidator The callable type returned by `createMsgValidator`: ```ts type MsgValidator = ( data: TMsg, options?: { context?: string }, ) => TMsg ``` ## Schemas Bundled JSON Schemas for standard CosmWasm contracts. ```ts import { cw20, cw721 } from 'schemos/schemas' ``` ### cw20 Fungible token standard (CW20). Includes execute, query, and response schemas. ```ts import { cw20 } from 'schemos/schemas' const token = createTypedContract(client, addr, cw20) ``` #### Execute messages `transfer`, `burn`, `send`, `mint`, `increase_allowance`, `decrease_allowance`, `transfer_from`, `send_from`, `burn_from`, `update_marketing`, `upload_logo`, `update_minter` #### Query messages `balance`, `token_info`, `minter`, `allowance`, `all_allowances`, `all_spender_allowances`, `all_accounts`, `marketing_info`, `download_logo` #### Individual imports ```ts import { cw20ExecuteSchema, cw20QuerySchema, cw20ResponseSchemas, } from 'schemos/schemas' ``` ### cw721 Non-fungible token standard (CW721). Includes execute, query, and response schemas. ```ts import { cw721 } from 'schemos/schemas' const nft = createTypedContract(client, addr, cw721) ``` #### Execute messages `transfer_nft`, `send_nft`, `approve`, `revoke`, `approve_all`, `revoke_all`, `mint`, `burn`, `extension` #### Query messages `owner_of`, `approval`, `approvals`, `all_operators`, `num_tokens`, `contract_info`, `nft_info`, `all_nft_info`, `tokens`, `all_tokens`, `minter`, `extension`, `ownership` #### Individual imports ```ts import { cw721ExecuteSchema, cw721QuerySchema, cw721ResponseSchemas, } from 'schemos/schemas' ``` ### Schema Structure Each bundled schema is a `{ execute, query, responses }` object: ```ts const cw20: { execute: /* execute msg JSON Schema */ query: /* query msg JSON Schema */ responses: { balance: /* balance response JSON Schema */ token_info: /* token_info response JSON Schema */ // ... one per query message } } ``` The `responses` map keys correspond to query message names. Each value is the JSON Schema for that query's response type. ### Using Your Own Schemas For custom contracts, see [Custom Contracts](/guides/custom-contracts). ## Telescope Adapters Adapters for telescope-generated SDKs (xplajs, osmojs, neutronjs, ...) that use protobuf RPCs instead of cosmjs's JSON API. ```ts import { createQueryAdapter, createExecuteAdapter } from 'schemos/telescope' ``` ### Why Adapters? cosmjs's `SigningCosmWasmClient` conforms to schemos's [client interface](/overview#client-interface) directly. Telescope SDKs don't — they use protobuf-encoded `Uint8Array` messages and chain-specific RPC functions. The adapter bridges this gap with three user-provided callbacks, keeping schemos chain-agnostic. See [Why Callbacks](/overview#why-telescope-adapter-uses-callbacks) for the full rationale. ### createQueryAdapter Wraps a telescope RPC query function into a `CosmWasmQueryClient`. ```ts function createQueryAdapter( smartContractState: SmartContractStateFn, ): CosmWasmQueryClient ``` #### Parameters | Parameter | Type | Description | | -------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------ | | `smartContractState` | `SmartContractStateFn` | Telescope RPC query function. All telescope SDKs export this at `cosmwasm.wasm.v1.smartContractState`. | See [Telescope SDKs integration](/integrations/telescope#query-adapter) for usage examples. ### createExecuteAdapter Creates a full execute+query adapter from three callbacks. ```ts function createExecuteAdapter( smartContractState: SmartContractStateFn, signAndBroadcast: SignAndBroadcastFn, encodeMsgExecuteContract: EncodeMsgExecuteContractFn, ): CosmWasmExecuteClient ``` #### Parameters | Parameter | Type | Description | | -------------------------- | ---------------------------- | --------------------------------------------------------------------------------------- | | `smartContractState` | `SmartContractStateFn` | Telescope RPC query function | | `signAndBroadcast` | `SignAndBroadcastFn` | Signing client's broadcast method | | `encodeMsgExecuteContract` | `EncodeMsgExecuteContractFn` | Protobuf encoder for `MsgExecuteContract` — provided from the chain's telescope package | See [Telescope SDKs integration](/integrations/telescope#execute-adapter) for usage examples. ### Types #### SmartContractStateFn ```ts type SmartContractStateFn = (params: { address: string queryData: Uint8Array }) => Promise<{ data: Uint8Array }> ``` All telescope SDKs export a function matching this signature at `cosmwasm.wasm.v1.smartContractState`. #### SignAndBroadcastFn ```ts type SignAndBroadcastFn = ( sender: string, messages: readonly { typeUrl: string; value: Uint8Array }[], fee: InterchainStdFee | 'auto', memo?: string, ) => Promise ``` Accepts already-encoded protobuf messages. Each telescope SDK provides this via its signing client. #### EncodeMsgExecuteContractFn ```ts type EncodeMsgExecuteContractFn = (params: { sender: string contract: string msg: Uint8Array funds: readonly Coin[] }) => Uint8Array ``` Encodes contract execution params to `MsgExecuteContract` protobuf bytes. The pattern is identical across telescope packages — only the import path changes: ```ts // xplajs import { MsgExecuteContract } from '@xpla/xplajs/cosmwasm/wasm/v1/tx' // osmojs import { MsgExecuteContract } from 'osmojs/cosmwasm/wasm/v1/tx' // neutronjs import { MsgExecuteContract } from 'neutronjs/cosmwasm/wasm/v1/tx' // All use the same encode pattern: const encode = (p) => MsgExecuteContract.encode( MsgExecuteContract.fromPartial({ ...p, funds: [...p.funds] }), ).finish() ``` ### Internal Behavior * `createQueryAdapter` uses [`Json.toBytes`](/api/encoding#jsontoBytes) and [`Json.fromBytes`](/api/encoding#jsonfromBytes) for query encoding/decoding * `createExecuteAdapter` composes `createQueryAdapter` internally, then adds `execute` using `Json.toBytes` for the msg field * The adapter constructs `MsgExecuteContract` with typeUrl `/cosmwasm.wasm.v1.MsgExecuteContract`