From 8e0004cf79eeafbf8702ab71b9382bceffcb79b6 Mon Sep 17 00:00:00 2001 From: Gefei Hou Date: Sun, 14 Sep 2025 21:01:20 -0700 Subject: [PATCH] Added cosmwasm example --- .gitignore | 1 + examples/cosmwasm/.eslintrc.json | 3 + examples/cosmwasm/.gitignore | 38 ++ examples/cosmwasm/README.md | 97 +++ examples/cosmwasm/codegen/Minter.client.ts | 174 ++++++ .../codegen/Minter.message-composer.ts | 172 ++++++ examples/cosmwasm/codegen/Minter.types.ts | 143 +++++ examples/cosmwasm/codegen/Sg721.client.ts | 440 ++++++++++++++ .../codegen/Sg721.message-composer.ts | 273 +++++++++ examples/cosmwasm/codegen/Sg721.types.ts | 232 +++++++ examples/cosmwasm/codegen/baseClient.ts | 206 +++++++ examples/cosmwasm/codegen/index.ts | 28 + examples/cosmwasm/components/ContractTest.tsx | 571 ++++++++++++++++++ .../cosmwasm/components/WalletSection.tsx | 205 +++++++ examples/cosmwasm/config/chains.ts | 16 + examples/cosmwasm/config/index.ts | 2 + examples/cosmwasm/config/wallets.ts | 5 + .../cosmwasm/hooks/useCustomSigningClient.ts | 44 ++ examples/cosmwasm/next.config.js | 19 + examples/cosmwasm/package.json | 56 ++ examples/cosmwasm/pages/_app.tsx | 36 ++ examples/cosmwasm/pages/index.tsx | 111 ++++ examples/cosmwasm/styles/globals.css | 102 ++++ examples/cosmwasm/tsconfig.json | 30 + 24 files changed, 3004 insertions(+) create mode 100644 examples/cosmwasm/.eslintrc.json create mode 100644 examples/cosmwasm/.gitignore create mode 100644 examples/cosmwasm/README.md create mode 100644 examples/cosmwasm/codegen/Minter.client.ts create mode 100644 examples/cosmwasm/codegen/Minter.message-composer.ts create mode 100644 examples/cosmwasm/codegen/Minter.types.ts create mode 100644 examples/cosmwasm/codegen/Sg721.client.ts create mode 100644 examples/cosmwasm/codegen/Sg721.message-composer.ts create mode 100644 examples/cosmwasm/codegen/Sg721.types.ts create mode 100644 examples/cosmwasm/codegen/baseClient.ts create mode 100644 examples/cosmwasm/codegen/index.ts create mode 100644 examples/cosmwasm/components/ContractTest.tsx create mode 100644 examples/cosmwasm/components/WalletSection.tsx create mode 100644 examples/cosmwasm/config/chains.ts create mode 100644 examples/cosmwasm/config/index.ts create mode 100644 examples/cosmwasm/config/wallets.ts create mode 100644 examples/cosmwasm/hooks/useCustomSigningClient.ts create mode 100644 examples/cosmwasm/next.config.js create mode 100644 examples/cosmwasm/package.json create mode 100644 examples/cosmwasm/pages/_app.tsx create mode 100644 examples/cosmwasm/pages/index.tsx create mode 100644 examples/cosmwasm/styles/globals.css create mode 100644 examples/cosmwasm/tsconfig.json diff --git a/.gitignore b/.gitignore index c266abb9..0341c763 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ coverage packages/**/build packages/**/main packages/**/module +components/codegen diff --git a/examples/cosmwasm/.eslintrc.json b/examples/cosmwasm/.eslintrc.json new file mode 100644 index 00000000..0e81f9b9 --- /dev/null +++ b/examples/cosmwasm/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} \ No newline at end of file diff --git a/examples/cosmwasm/.gitignore b/examples/cosmwasm/.gitignore new file mode 100644 index 00000000..7770d727 --- /dev/null +++ b/examples/cosmwasm/.gitignore @@ -0,0 +1,38 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# yarn +yarn.lock \ No newline at end of file diff --git a/examples/cosmwasm/README.md b/examples/cosmwasm/README.md new file mode 100644 index 00000000..bc7a218d --- /dev/null +++ b/examples/cosmwasm/README.md @@ -0,0 +1,97 @@ +# CosmWasm TypeScript Codegen Example + +This example demonstrates how to use CosmWasm Ts-Codegen TypeScript code generation with create-interchain-app. It showcases type-safe interactions with CosmWasm smart contracts using automatically generated TypeScript clients. The demo is mainly for baseCient functionality including query and execute, but you can add or modify to do the test for other clients. + +## Features + +- 🔗 **Wallet Integration**: Connect to Cosmos wallets using @interchain-kit +- 📝 **Type Safety**: Fully typed contract interactions using generated TypeScript clients +- ⚡ **Real-time Updates**: Live contract state queries and transaction execution + +## Getting Started + +### Prerequisites + +- Node.js 18+ and npm/yarn +- A Cosmos wallet (Keplr recommended) +- Access to a Cosmos testnet (Stargaze testnet configured by default) + +### Installation + +1. Navigate to the example: + ```bash + cd examples/cosmwasm + ``` + +2. Install dependencies: + ```bash + npm install + # or + yarn install + ``` + +3. Start the development server: + ```bash + npm run dev + # or + yarn dev + ``` + +4. Open [http://localhost:3000](http://localhost:3000) in your browser + +## Code Generation + +The TypeScript clients in the `/codegen` directory are automatically generated from CosmWasm contract schemas using `@cosmwasm/ts-codegen`. + +## Configuration + +### Supported Chains + +The example is configured to work with: +- **Osmosis Testnet** (default) +- **Stargaze** +- **Juno** + +You can modify the chain configuration in `/config/chains.ts` to add support for additional networks. + +### Wallet Support + +Supported wallets include: +- **Keplr** (recommended) +- **Leap** +- **MetaMask** (for Ethereum-compatible chains) + +Wallet configuration is managed in `/config/wallets.ts`. + +## Development + +### Adding New Contract Types + +1. Add your contract schema to the project +2. Generate TypeScript clients using `@cosmwasm/ts-codegen` +3. Create React components for contract interactions +4. Add the components to the main page + +## Troubleshooting + +### Common Issues + +1. **Wallet Connection Fails** + - Ensure Keplr or another supported wallet is installed + - Check that the wallet supports the selected chain + - Verify network configuration in wallet settings + +2. **Contract Queries Fail** + - Verify the contract address is correct + - Ensure the contract exists on the selected network + - Check RPC endpoint connectivity + +3. **Transaction Execution Fails** + - Verify sufficient balance for gas fees + - Check message format matches contract expectations + - Ensure proper permissions for contract execution + +### Getting Help + +- Check the [CosmWasm Ts-Codegen documentation](https://github.com/hyperweb-io/ts-codegen) +- View [Interchainjs documentation](https://github.com/hyperweb-io/interchainjs) diff --git a/examples/cosmwasm/codegen/Minter.client.ts b/examples/cosmwasm/codegen/Minter.client.ts new file mode 100644 index 00000000..cac3c2cd --- /dev/null +++ b/examples/cosmwasm/codegen/Minter.client.ts @@ -0,0 +1,174 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@1.13.1. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +import { ICosmWasmClient, ISigningCosmWasmClient } from "./baseClient"; +import { StdFee } from "@interchainjs/types"; +import { Addr, Timestamp, Uint64, Uint128, Config, Coin, ConfigResponse, ExecuteMsg, Decimal, InstantiateMsg, InstantiateMsg1, CollectionInfoForRoyaltyInfoResponse, RoyaltyInfoResponse, MintCountResponse, MintPriceResponse, MintableNumTokensResponse, QueryMsg, StartTimeResponse } from "./Minter.types"; +export interface MinterReadOnlyInterface { + contractAddress: string; + config: () => Promise; + mintableNumTokens: () => Promise; + startTime: () => Promise; + mintPrice: () => Promise; + mintCount: ({ + address + }: { + address: string; + }) => Promise; +} +export class MinterQueryClient implements MinterReadOnlyInterface { + client: ICosmWasmClient; + contractAddress: string; + constructor(client: ICosmWasmClient, contractAddress: string) { + this.client = client; + this.contractAddress = contractAddress; + this.config = this.config.bind(this); + this.mintableNumTokens = this.mintableNumTokens.bind(this); + this.startTime = this.startTime.bind(this); + this.mintPrice = this.mintPrice.bind(this); + this.mintCount = this.mintCount.bind(this); + } + config = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + config: {} + }); + }; + mintableNumTokens = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + mintable_num_tokens: {} + }); + }; + startTime = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + start_time: {} + }); + }; + mintPrice = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + mint_price: {} + }); + }; + mintCount = async ({ + address + }: { + address: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + mint_count: { + address + } + }); + }; +} +export interface MinterInterface extends MinterReadOnlyInterface { + contractAddress: string; + sender: string; + mint: (fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + setWhitelist: ({ + whitelist + }: { + whitelist: string; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + updateStartTime: (fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + updatePerAddressLimit: ({ + perAddressLimit + }: { + perAddressLimit: number; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + mintTo: ({ + recipient + }: { + recipient: string; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + mintFor: ({ + recipient, + tokenId + }: { + recipient: string; + tokenId: number; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + withdraw: (fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; +} +export class MinterClient extends MinterQueryClient implements MinterInterface { + client: ISigningCosmWasmClient; + sender: string; + contractAddress: string; + constructor(client: ISigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress); + this.client = client; + this.sender = sender; + this.contractAddress = contractAddress; + this.mint = this.mint.bind(this); + this.setWhitelist = this.setWhitelist.bind(this); + this.updateStartTime = this.updateStartTime.bind(this); + this.updatePerAddressLimit = this.updatePerAddressLimit.bind(this); + this.mintTo = this.mintTo.bind(this); + this.mintFor = this.mintFor.bind(this); + this.withdraw = this.withdraw.bind(this); + } + mint = async (fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + mint: {} + }, fee_, memo_, funds_); + }; + setWhitelist = async ({ + whitelist + }: { + whitelist: string; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + set_whitelist: { + whitelist + } + }, fee_, memo_, funds_); + }; + updateStartTime = async (fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + update_start_time: {} + }, fee_, memo_, funds_); + }; + updatePerAddressLimit = async ({ + perAddressLimit + }: { + perAddressLimit: number; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + update_per_address_limit: { + per_address_limit: perAddressLimit + } + }, fee_, memo_, funds_); + }; + mintTo = async ({ + recipient + }: { + recipient: string; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + mint_to: { + recipient + } + }, fee_, memo_, funds_); + }; + mintFor = async ({ + recipient, + tokenId + }: { + recipient: string; + tokenId: number; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + mint_for: { + recipient, + token_id: tokenId + } + }, fee_, memo_, funds_); + }; + withdraw = async (fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + withdraw: {} + }, fee_, memo_, funds_); + }; +} \ No newline at end of file diff --git a/examples/cosmwasm/codegen/Minter.message-composer.ts b/examples/cosmwasm/codegen/Minter.message-composer.ts new file mode 100644 index 00000000..6fb6d979 --- /dev/null +++ b/examples/cosmwasm/codegen/Minter.message-composer.ts @@ -0,0 +1,172 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@1.13.1. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +import { EncodeObject } from "@interchainjs/cosmos-types"; +import { MsgExecuteContract } from "interchainjs/cosmwasm/wasm/v1/tx"; +import { toUtf8 } from "@interchainjs/encoding"; +import { Addr, Timestamp, Uint64, Uint128, Config, Coin, ConfigResponse, ExecuteMsg, Decimal, InstantiateMsg, InstantiateMsg1, CollectionInfoForRoyaltyInfoResponse, RoyaltyInfoResponse, MintCountResponse, MintPriceResponse, MintableNumTokensResponse, QueryMsg, StartTimeResponse } from "./Minter.types"; +export interface MinterMsg { + contractAddress: string; + sender: string; + mint: (funds_?: Coin[]) => EncodeObject; + setWhitelist: ({ + whitelist + }: { + whitelist: string; + }, funds_?: Coin[]) => EncodeObject; + updateStartTime: (funds_?: Coin[]) => EncodeObject; + updatePerAddressLimit: ({ + perAddressLimit + }: { + perAddressLimit: number; + }, funds_?: Coin[]) => EncodeObject; + mintTo: ({ + recipient + }: { + recipient: string; + }, funds_?: Coin[]) => EncodeObject; + mintFor: ({ + recipient, + tokenId + }: { + recipient: string; + tokenId: number; + }, funds_?: Coin[]) => EncodeObject; + withdraw: (funds_?: Coin[]) => EncodeObject; +} +export class MinterMsgComposer implements MinterMsg { + sender: string; + contractAddress: string; + constructor(sender: string, contractAddress: string) { + this.sender = sender; + this.contractAddress = contractAddress; + this.mint = this.mint.bind(this); + this.setWhitelist = this.setWhitelist.bind(this); + this.updateStartTime = this.updateStartTime.bind(this); + this.updatePerAddressLimit = this.updatePerAddressLimit.bind(this); + this.mintTo = this.mintTo.bind(this); + this.mintFor = this.mintFor.bind(this); + this.withdraw = this.withdraw.bind(this); + } + mint = (funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + mint: {} + })), + funds: funds_ + }) + }; + }; + setWhitelist = ({ + whitelist + }: { + whitelist: string; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + set_whitelist: { + whitelist + } + })), + funds: funds_ + }) + }; + }; + updateStartTime = (funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + update_start_time: {} + })), + funds: funds_ + }) + }; + }; + updatePerAddressLimit = ({ + perAddressLimit + }: { + perAddressLimit: number; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + update_per_address_limit: { + per_address_limit: perAddressLimit + } + })), + funds: funds_ + }) + }; + }; + mintTo = ({ + recipient + }: { + recipient: string; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + mint_to: { + recipient + } + })), + funds: funds_ + }) + }; + }; + mintFor = ({ + recipient, + tokenId + }: { + recipient: string; + tokenId: number; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + mint_for: { + recipient, + token_id: tokenId + } + })), + funds: funds_ + }) + }; + }; + withdraw = (funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + withdraw: {} + })), + funds: funds_ + }) + }; + }; +} \ No newline at end of file diff --git a/examples/cosmwasm/codegen/Minter.types.ts b/examples/cosmwasm/codegen/Minter.types.ts new file mode 100644 index 00000000..a728435f --- /dev/null +++ b/examples/cosmwasm/codegen/Minter.types.ts @@ -0,0 +1,143 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@1.13.1. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +export type Addr = string; +export type Timestamp = Uint64; +export type Uint64 = string; +export type Uint128 = string; +export interface Config { + admin: Addr; + base_token_uri: string; + num_tokens: number; + per_address_limit: number; + sg721_code_id: number; + start_time: Timestamp; + unit_price: Coin; + whitelist?: Addr | null; + [k: string]: unknown; +} +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} +export interface ConfigResponse { + admin: string; + base_token_uri: string; + num_tokens: number; + per_address_limit: number; + sg721_address: string; + sg721_code_id: number; + start_time: Timestamp; + unit_price: Coin; + whitelist?: string | null; + [k: string]: unknown; +} +export type ExecuteMsg = { + mint: { + [k: string]: unknown; + }; +} | { + set_whitelist: { + whitelist: string; + [k: string]: unknown; + }; +} | { + update_start_time: Timestamp; +} | { + update_per_address_limit: { + per_address_limit: number; + [k: string]: unknown; + }; +} | { + mint_to: { + recipient: string; + [k: string]: unknown; + }; +} | { + mint_for: { + recipient: string; + token_id: number; + [k: string]: unknown; + }; +} | { + withdraw: { + [k: string]: unknown; + }; +}; +export type Decimal = string; +export interface InstantiateMsg { + base_token_uri: string; + num_tokens: number; + per_address_limit: number; + sg721_code_id: number; + sg721_instantiate_msg: InstantiateMsg1; + start_time: Timestamp; + unit_price: Coin; + whitelist?: string | null; + [k: string]: unknown; +} +export interface InstantiateMsg1 { + collection_info: CollectionInfoForRoyaltyInfoResponse; + minter: string; + name: string; + symbol: string; + [k: string]: unknown; +} +export interface CollectionInfoForRoyaltyInfoResponse { + creator: string; + description: string; + external_link?: string | null; + image: string; + royalty_info?: RoyaltyInfoResponse | null; + [k: string]: unknown; +} +export interface RoyaltyInfoResponse { + payment_address: string; + share: Decimal; + [k: string]: unknown; +} +export interface MintCountResponse { + address: string; + count: number; + [k: string]: unknown; +} +export interface MintPriceResponse { + current_price: Coin; + public_price: Coin; + whitelist_price?: Coin | null; + [k: string]: unknown; +} +export interface MintableNumTokensResponse { + count: number; + [k: string]: unknown; +} +export type QueryMsg = { + config: { + [k: string]: unknown; + }; +} | { + mintable_num_tokens: { + [k: string]: unknown; + }; +} | { + start_time: { + [k: string]: unknown; + }; +} | { + mint_price: { + [k: string]: unknown; + }; +} | { + mint_count: { + address: string; + [k: string]: unknown; + }; +}; +export interface StartTimeResponse { + start_time: string; + [k: string]: unknown; +} \ No newline at end of file diff --git a/examples/cosmwasm/codegen/Sg721.client.ts b/examples/cosmwasm/codegen/Sg721.client.ts new file mode 100644 index 00000000..ca3147a1 --- /dev/null +++ b/examples/cosmwasm/codegen/Sg721.client.ts @@ -0,0 +1,440 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@1.13.1. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +import { ICosmWasmClient, ISigningCosmWasmClient } from "./baseClient"; +import { Coin, StdFee } from "@interchainjs/types"; +import { Expiration, Timestamp, Uint64, AllNftInfoResponse, OwnerOfResponse, Approval, NftInfoResponseForEmpty, Empty, AllOperatorsResponse, AllTokensResponse, ApprovalResponse, ApprovalsResponse, Binary, Decimal, CollectionInfoResponse, RoyaltyInfoResponse, ContractInfoResponse, ExecuteMsgForEmpty, MintMsgForEmpty, InstantiateMsg, CollectionInfoForRoyaltyInfoResponse, MinterResponse, NftInfoResponse, NumTokensResponse, OperatorsResponse, QueryMsg, TokensResponse } from "./Sg721.types"; +export interface Sg721ReadOnlyInterface { + contractAddress: string; + ownerOf: ({ + includeExpired, + tokenId + }: { + includeExpired?: boolean; + tokenId: string; + }) => Promise; + approval: ({ + includeExpired, + spender, + tokenId + }: { + includeExpired?: boolean; + spender: string; + tokenId: string; + }) => Promise; + approvals: ({ + includeExpired, + tokenId + }: { + includeExpired?: boolean; + tokenId: string; + }) => Promise; + allOperators: ({ + includeExpired, + limit, + owner, + startAfter + }: { + includeExpired?: boolean; + limit?: number; + owner: string; + startAfter?: string; + }) => Promise; + numTokens: () => Promise; + contractInfo: () => Promise; + nftInfo: ({ + tokenId + }: { + tokenId: string; + }) => Promise; + allNftInfo: ({ + includeExpired, + tokenId + }: { + includeExpired?: boolean; + tokenId: string; + }) => Promise; + tokens: ({ + limit, + owner, + startAfter + }: { + limit?: number; + owner: string; + startAfter?: string; + }) => Promise; + allTokens: ({ + limit, + startAfter + }: { + limit?: number; + startAfter?: string; + }) => Promise; + minter: () => Promise; + collectionInfo: () => Promise; +} +export class Sg721QueryClient implements Sg721ReadOnlyInterface { + client: ICosmWasmClient; + contractAddress: string; + constructor(client: ICosmWasmClient, contractAddress: string) { + this.client = client; + this.contractAddress = contractAddress; + this.ownerOf = this.ownerOf.bind(this); + this.approval = this.approval.bind(this); + this.approvals = this.approvals.bind(this); + this.allOperators = this.allOperators.bind(this); + this.numTokens = this.numTokens.bind(this); + this.contractInfo = this.contractInfo.bind(this); + this.nftInfo = this.nftInfo.bind(this); + this.allNftInfo = this.allNftInfo.bind(this); + this.tokens = this.tokens.bind(this); + this.allTokens = this.allTokens.bind(this); + this.minter = this.minter.bind(this); + this.collectionInfo = this.collectionInfo.bind(this); + } + ownerOf = async ({ + includeExpired, + tokenId + }: { + includeExpired?: boolean; + tokenId: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + owner_of: { + include_expired: includeExpired, + token_id: tokenId + } + }); + }; + approval = async ({ + includeExpired, + spender, + tokenId + }: { + includeExpired?: boolean; + spender: string; + tokenId: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + approval: { + include_expired: includeExpired, + spender, + token_id: tokenId + } + }); + }; + approvals = async ({ + includeExpired, + tokenId + }: { + includeExpired?: boolean; + tokenId: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + approvals: { + include_expired: includeExpired, + token_id: tokenId + } + }); + }; + allOperators = async ({ + includeExpired, + limit, + owner, + startAfter + }: { + includeExpired?: boolean; + limit?: number; + owner: string; + startAfter?: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_operators: { + include_expired: includeExpired, + limit, + owner, + start_after: startAfter + } + }); + }; + numTokens = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + num_tokens: {} + }); + }; + contractInfo = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + contract_info: {} + }); + }; + nftInfo = async ({ + tokenId + }: { + tokenId: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + nft_info: { + token_id: tokenId + } + }); + }; + allNftInfo = async ({ + includeExpired, + tokenId + }: { + includeExpired?: boolean; + tokenId: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_nft_info: { + include_expired: includeExpired, + token_id: tokenId + } + }); + }; + tokens = async ({ + limit, + owner, + startAfter + }: { + limit?: number; + owner: string; + startAfter?: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + tokens: { + limit, + owner, + start_after: startAfter + } + }); + }; + allTokens = async ({ + limit, + startAfter + }: { + limit?: number; + startAfter?: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_tokens: { + limit, + start_after: startAfter + } + }); + }; + minter = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + minter: {} + }); + }; + collectionInfo = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + collection_info: {} + }); + }; +} +export interface Sg721Interface extends Sg721ReadOnlyInterface { + contractAddress: string; + sender: string; + transferNft: ({ + recipient, + tokenId + }: { + recipient: string; + tokenId: string; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + sendNft: ({ + contract, + msg, + tokenId + }: { + contract: string; + msg: Binary; + tokenId: string; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + approve: ({ + expires, + spender, + tokenId + }: { + expires?: Expiration; + spender: string; + tokenId: string; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + revoke: ({ + spender, + tokenId + }: { + spender: string; + tokenId: string; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + approveAll: ({ + expires, + operator + }: { + expires?: Expiration; + operator: string; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + revokeAll: ({ + operator + }: { + operator: string; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + mint: ({ + extension, + owner, + tokenId, + tokenUri + }: { + extension: Empty; + owner: string; + tokenId: string; + tokenUri?: string; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; + burn: ({ + tokenId + }: { + tokenId: string; + }, fee_?: number | StdFee | "auto", memo_?: string, funds_?: Coin[]) => Promise; +} +export class Sg721Client extends Sg721QueryClient implements Sg721Interface { + client: ISigningCosmWasmClient; + sender: string; + contractAddress: string; + constructor(client: ISigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress); + this.client = client; + this.sender = sender; + this.contractAddress = contractAddress; + this.transferNft = this.transferNft.bind(this); + this.sendNft = this.sendNft.bind(this); + this.approve = this.approve.bind(this); + this.revoke = this.revoke.bind(this); + this.approveAll = this.approveAll.bind(this); + this.revokeAll = this.revokeAll.bind(this); + this.mint = this.mint.bind(this); + this.burn = this.burn.bind(this); + } + transferNft = async ({ + recipient, + tokenId + }: { + recipient: string; + tokenId: string; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + transfer_nft: { + recipient, + token_id: tokenId + } + }, fee_, memo_, funds_); + }; + sendNft = async ({ + contract, + msg, + tokenId + }: { + contract: string; + msg: Binary; + tokenId: string; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + send_nft: { + contract, + msg, + token_id: tokenId + } + }, fee_, memo_, funds_); + }; + approve = async ({ + expires, + spender, + tokenId + }: { + expires?: Expiration; + spender: string; + tokenId: string; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + approve: { + expires, + spender, + token_id: tokenId + } + }, fee_, memo_, funds_); + }; + revoke = async ({ + spender, + tokenId + }: { + spender: string; + tokenId: string; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + revoke: { + spender, + token_id: tokenId + } + }, fee_, memo_, funds_); + }; + approveAll = async ({ + expires, + operator + }: { + expires?: Expiration; + operator: string; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + approve_all: { + expires, + operator + } + }, fee_, memo_, funds_); + }; + revokeAll = async ({ + operator + }: { + operator: string; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + revoke_all: { + operator + } + }, fee_, memo_, funds_); + }; + mint = async ({ + extension, + owner, + tokenId, + tokenUri + }: { + extension: Empty; + owner: string; + tokenId: string; + tokenUri?: string; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + mint: { + extension, + owner, + token_id: tokenId, + token_uri: tokenUri + } + }, fee_, memo_, funds_); + }; + burn = async ({ + tokenId + }: { + tokenId: string; + }, fee_: number | StdFee | "auto" = "auto", memo_?: string, funds_?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + burn: { + token_id: tokenId + } + }, fee_, memo_, funds_); + }; +} \ No newline at end of file diff --git a/examples/cosmwasm/codegen/Sg721.message-composer.ts b/examples/cosmwasm/codegen/Sg721.message-composer.ts new file mode 100644 index 00000000..3e18aed9 --- /dev/null +++ b/examples/cosmwasm/codegen/Sg721.message-composer.ts @@ -0,0 +1,273 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@1.13.1. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +import { Coin } from "@interchainjs/types"; +import { EncodeObject } from "@interchainjs/cosmos-types"; +import { MsgExecuteContract } from "interchainjs/cosmwasm/wasm/v1/tx"; +import { toUtf8 } from "@interchainjs/encoding"; +import { Expiration, Timestamp, Uint64, AllNftInfoResponse, OwnerOfResponse, Approval, NftInfoResponseForEmpty, Empty, AllOperatorsResponse, AllTokensResponse, ApprovalResponse, ApprovalsResponse, Binary, Decimal, CollectionInfoResponse, RoyaltyInfoResponse, ContractInfoResponse, ExecuteMsgForEmpty, MintMsgForEmpty, InstantiateMsg, CollectionInfoForRoyaltyInfoResponse, MinterResponse, NftInfoResponse, NumTokensResponse, OperatorsResponse, QueryMsg, TokensResponse } from "./Sg721.types"; +export interface Sg721Msg { + contractAddress: string; + sender: string; + transferNft: ({ + recipient, + tokenId + }: { + recipient: string; + tokenId: string; + }, funds_?: Coin[]) => EncodeObject; + sendNft: ({ + contract, + msg, + tokenId + }: { + contract: string; + msg: Binary; + tokenId: string; + }, funds_?: Coin[]) => EncodeObject; + approve: ({ + expires, + spender, + tokenId + }: { + expires?: Expiration; + spender: string; + tokenId: string; + }, funds_?: Coin[]) => EncodeObject; + revoke: ({ + spender, + tokenId + }: { + spender: string; + tokenId: string; + }, funds_?: Coin[]) => EncodeObject; + approveAll: ({ + expires, + operator + }: { + expires?: Expiration; + operator: string; + }, funds_?: Coin[]) => EncodeObject; + revokeAll: ({ + operator + }: { + operator: string; + }, funds_?: Coin[]) => EncodeObject; + mint: ({ + extension, + owner, + tokenId, + tokenUri + }: { + extension: Empty; + owner: string; + tokenId: string; + tokenUri?: string; + }, funds_?: Coin[]) => EncodeObject; + burn: ({ + tokenId + }: { + tokenId: string; + }, funds_?: Coin[]) => EncodeObject; +} +export class Sg721MsgComposer implements Sg721Msg { + sender: string; + contractAddress: string; + constructor(sender: string, contractAddress: string) { + this.sender = sender; + this.contractAddress = contractAddress; + this.transferNft = this.transferNft.bind(this); + this.sendNft = this.sendNft.bind(this); + this.approve = this.approve.bind(this); + this.revoke = this.revoke.bind(this); + this.approveAll = this.approveAll.bind(this); + this.revokeAll = this.revokeAll.bind(this); + this.mint = this.mint.bind(this); + this.burn = this.burn.bind(this); + } + transferNft = ({ + recipient, + tokenId + }: { + recipient: string; + tokenId: string; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + transfer_nft: { + recipient, + token_id: tokenId + } + })), + funds: funds_ + }) + }; + }; + sendNft = ({ + contract, + msg, + tokenId + }: { + contract: string; + msg: Binary; + tokenId: string; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + send_nft: { + contract, + msg, + token_id: tokenId + } + })), + funds: funds_ + }) + }; + }; + approve = ({ + expires, + spender, + tokenId + }: { + expires?: Expiration; + spender: string; + tokenId: string; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + approve: { + expires, + spender, + token_id: tokenId + } + })), + funds: funds_ + }) + }; + }; + revoke = ({ + spender, + tokenId + }: { + spender: string; + tokenId: string; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + revoke: { + spender, + token_id: tokenId + } + })), + funds: funds_ + }) + }; + }; + approveAll = ({ + expires, + operator + }: { + expires?: Expiration; + operator: string; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + approve_all: { + expires, + operator + } + })), + funds: funds_ + }) + }; + }; + revokeAll = ({ + operator + }: { + operator: string; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + revoke_all: { + operator + } + })), + funds: funds_ + }) + }; + }; + mint = ({ + extension, + owner, + tokenId, + tokenUri + }: { + extension: Empty; + owner: string; + tokenId: string; + tokenUri?: string; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + mint: { + extension, + owner, + token_id: tokenId, + token_uri: tokenUri + } + })), + funds: funds_ + }) + }; + }; + burn = ({ + tokenId + }: { + tokenId: string; + }, funds_?: Coin[]): EncodeObject => { + return { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8(JSON.stringify({ + burn: { + token_id: tokenId + } + })), + funds: funds_ + }) + }; + }; +} \ No newline at end of file diff --git a/examples/cosmwasm/codegen/Sg721.types.ts b/examples/cosmwasm/codegen/Sg721.types.ts new file mode 100644 index 00000000..3e969367 --- /dev/null +++ b/examples/cosmwasm/codegen/Sg721.types.ts @@ -0,0 +1,232 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@1.13.1. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +export type Expiration = { + at_height: number; +} | { + at_time: Timestamp; +} | { + never: { + [k: string]: unknown; + }; +}; +export type Timestamp = Uint64; +export type Uint64 = string; +export interface AllNftInfoResponse { + access: OwnerOfResponse; + info: NftInfoResponseForEmpty; + [k: string]: unknown; +} +export interface OwnerOfResponse { + approvals: Approval[]; + owner: string; + [k: string]: unknown; +} +export interface Approval { + expires: Expiration; + spender: string; + [k: string]: unknown; +} +export interface NftInfoResponseForEmpty { + extension: Empty; + token_uri?: string | null; + [k: string]: unknown; +} +export interface Empty { + [k: string]: unknown; +} +export interface AllOperatorsResponse { + operators: Approval[]; + [k: string]: unknown; +} +export interface AllTokensResponse { + tokens: string[]; + [k: string]: unknown; +} +export interface ApprovalResponse { + approval: Approval; + [k: string]: unknown; +} +export interface ApprovalsResponse { + approvals: Approval[]; + [k: string]: unknown; +} +export type Binary = string; +export type Decimal = string; +export interface CollectionInfoResponse { + creator: string; + description: string; + external_link?: string | null; + image: string; + royalty_info?: RoyaltyInfoResponse | null; + [k: string]: unknown; +} +export interface RoyaltyInfoResponse { + payment_address: string; + share: Decimal; + [k: string]: unknown; +} +export interface ContractInfoResponse { + name: string; + symbol: string; + [k: string]: unknown; +} +export type ExecuteMsgForEmpty = { + transfer_nft: { + recipient: string; + token_id: string; + [k: string]: unknown; + }; +} | { + send_nft: { + contract: string; + msg: Binary; + token_id: string; + [k: string]: unknown; + }; +} | { + approve: { + expires?: Expiration | null; + spender: string; + token_id: string; + [k: string]: unknown; + }; +} | { + revoke: { + spender: string; + token_id: string; + [k: string]: unknown; + }; +} | { + approve_all: { + expires?: Expiration | null; + operator: string; + [k: string]: unknown; + }; +} | { + revoke_all: { + operator: string; + [k: string]: unknown; + }; +} | { + mint: MintMsgForEmpty; +} | { + burn: { + token_id: string; + [k: string]: unknown; + }; +}; +export interface MintMsgForEmpty { + extension: Empty; + owner: string; + token_id: string; + token_uri?: string | null; + [k: string]: unknown; +} +export interface InstantiateMsg { + collection_info: CollectionInfoForRoyaltyInfoResponse; + minter: string; + name: string; + symbol: string; + [k: string]: unknown; +} +export interface CollectionInfoForRoyaltyInfoResponse { + creator: string; + description: string; + external_link?: string | null; + image: string; + royalty_info?: RoyaltyInfoResponse | null; + [k: string]: unknown; +} +export interface MinterResponse { + minter: string; + [k: string]: unknown; +} +export interface NftInfoResponse { + extension: Empty; + token_uri?: string | null; + [k: string]: unknown; +} +export interface NumTokensResponse { + count: number; + [k: string]: unknown; +} +export interface OperatorsResponse { + operators: Approval[]; + [k: string]: unknown; +} +export type QueryMsg = { + owner_of: { + include_expired?: boolean | null; + token_id: string; + [k: string]: unknown; + }; +} | { + approval: { + include_expired?: boolean | null; + spender: string; + token_id: string; + [k: string]: unknown; + }; +} | { + approvals: { + include_expired?: boolean | null; + token_id: string; + [k: string]: unknown; + }; +} | { + all_operators: { + include_expired?: boolean | null; + limit?: number | null; + owner: string; + start_after?: string | null; + [k: string]: unknown; + }; +} | { + num_tokens: { + [k: string]: unknown; + }; +} | { + contract_info: { + [k: string]: unknown; + }; +} | { + nft_info: { + token_id: string; + [k: string]: unknown; + }; +} | { + all_nft_info: { + include_expired?: boolean | null; + token_id: string; + [k: string]: unknown; + }; +} | { + tokens: { + limit?: number | null; + owner: string; + start_after?: string | null; + [k: string]: unknown; + }; +} | { + all_tokens: { + limit?: number | null; + start_after?: string | null; + [k: string]: unknown; + }; +} | { + minter: { + [k: string]: unknown; + }; +} | { + collection_info: { + [k: string]: unknown; + }; +}; +export interface TokensResponse { + tokens: string[]; + [k: string]: unknown; +} \ No newline at end of file diff --git a/examples/cosmwasm/codegen/baseClient.ts b/examples/cosmwasm/codegen/baseClient.ts new file mode 100644 index 00000000..f69e5c52 --- /dev/null +++ b/examples/cosmwasm/codegen/baseClient.ts @@ -0,0 +1,206 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@1.13.1. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + + +import { StdFee, Coin } from '@interchainjs/types'; +import { DirectSigner } from '@interchainjs/cosmos'; +import { getSmartContractState } from 'interchainjs/cosmwasm/wasm/v1/query.rpc.func'; +import { executeContract } from 'interchainjs/cosmwasm/wasm/v1/tx.rpc.func'; +import { QuerySmartContractStateRequest, QuerySmartContractStateResponse } from 'interchainjs/cosmwasm/wasm/v1/query'; +import { MsgExecuteContract } from 'interchainjs/cosmwasm/wasm/v1/tx'; +import { Chain } from '@chain-registry/v2-types'; + +// Encoding utility functions +const fromUint8Array = (uint8Array: Uint8Array): T => { + const text = new TextDecoder().decode(uint8Array); + return JSON.parse(text); +}; + +const toUint8Array = (obj: any): Uint8Array => { + const text = JSON.stringify(obj); + return new TextEncoder().encode(text); +}; + +// Chain registry configuration +// The amount under gasPrice represents gas price per unit +export interface ChainConfig { + chain?: Chain; + gasPrice?: { + denom: string; + amount: string; + }; +} + +// Gas fee calculation utilities +export const calculateGasFromChain = (chain: Chain, gasAmount: string): StdFee => { + try { + const feeTokens = chain.fees?.feeTokens; + + if (feeTokens && feeTokens.length > 0) { + const primaryToken = feeTokens[0]; + // v2 chain-registry uses camelCase: averageGasPrice, lowGasPrice, fixedMinGasPrice + const gasPrice = primaryToken.averageGasPrice || primaryToken.lowGasPrice || primaryToken.fixedMinGasPrice || 0.025; + const gasAmountNum = parseInt(gasAmount); + const feeAmount = Math.ceil(gasAmountNum * gasPrice).toString(); + + return { + amount: [{ + denom: primaryToken.denom, + amount: feeAmount + }], + gas: gasAmount + }; + } + } catch (error) { + console.warn('Failed to calculate gas from chain registry:', error); + } + + // Fallback to default + return { amount: [], gas: gasAmount }; +}; + +// Default gas amount - users can easily change this +export let DEFAULT_GAS_AMOUNT = '200000'; + +// Allow users to set their preferred default gas amount +export const setDefaultGasAmount = (gasAmount: string): void => { + DEFAULT_GAS_AMOUNT = gasAmount; +}; + +// Get current default gas amount +export const getDefaultGasAmount = (): string => DEFAULT_GAS_AMOUNT; + +export const getAutoGasFee = (chainConfig?: ChainConfig): StdFee => { + const gasAmount = DEFAULT_GAS_AMOUNT; + + if (chainConfig?.chain) { + return calculateGasFromChain(chainConfig.chain, gasAmount); + } + + if (chainConfig?.gasPrice) { + const gasAmountNum = parseInt(gasAmount); + const gasPriceNum = parseFloat(chainConfig.gasPrice.amount); + const feeAmount = Math.ceil(gasAmountNum * gasPriceNum).toString(); + + return { + amount: [{ + denom: chainConfig.gasPrice.denom, + amount: feeAmount + }], + gas: gasAmount + }; + } + + // Fallback: no fee tokens, just gas amount + return { amount: [], gas: gasAmount }; +}; + +// InterchainJS interfaces for CosmWasm clients +export interface ICosmWasmClient { + queryContractSmart(contractAddr: string, query: any): Promise; +} + +export interface ISigningCosmWasmClient extends ICosmWasmClient { + execute( + sender: string, + contractAddress: string, + msg: any, + fee?: number | StdFee | "auto", + memo?: string, + funds?: Coin[], + chainConfig?: ChainConfig + ): Promise; +} + +export interface ISigningClient { + signAndBroadcast( + signerAddress: string, + messages: any[], + fee: number | StdFee | "auto", + memo?: string + ): Promise; +} + +// Helper functions to create InterchainJS clients +export function getCosmWasmClient(rpcEndpoint: string): ICosmWasmClient { + return { + queryContractSmart: async (contractAddr: string, query: any) => { + // Create the request object + const request: QuerySmartContractStateRequest = { + address: contractAddr, + queryData: toUint8Array(query) + }; + + // Execute the query using InterchainJS + const response: QuerySmartContractStateResponse = await getSmartContractState(rpcEndpoint, request); + + // Parse and return the result + return fromUint8Array(response.data); + }, + }; +} + +export function getSigningCosmWasmClient(signingClient: DirectSigner, rpcEndpoint?: string): ISigningCosmWasmClient { + return { + queryContractSmart: async (contractAddr: string, query: any) => { + if (!rpcEndpoint) { + throw new Error('RPC endpoint is required for query operations'); + } + + // Create the request object + const request: QuerySmartContractStateRequest = { + address: contractAddr, + queryData: toUint8Array(query) + }; + + // Execute the query using InterchainJS + const response: QuerySmartContractStateResponse = await getSmartContractState(rpcEndpoint, request); + + // Parse and return the result + return fromUint8Array(response.data); + }, + execute: async ( + sender: string, + contractAddress: string, + msg: any, + fee?: number | StdFee | "auto", + memo?: string, + funds?: Coin[], + chainConfig?: ChainConfig + ) => { + // Handle fee conversion + let finalFee: StdFee; + if (typeof fee === 'number') { + finalFee = { amount: [], gas: fee.toString() }; + } else if (fee === 'auto') { + finalFee = getAutoGasFee(chainConfig); + } else if (fee) { + finalFee = fee; + } else { + finalFee = getAutoGasFee(chainConfig); + } + + // Create the message object + const message: MsgExecuteContract = { + sender, + contract: contractAddress, + msg: toUint8Array(msg), + funds: funds || [] + }; + + // Execute the transaction using InterchainJS + const result = await executeContract( + signingClient as any, + sender, + message, + finalFee, + memo || '' + ); + + return result; + }, + }; +} diff --git a/examples/cosmwasm/codegen/index.ts b/examples/cosmwasm/codegen/index.ts new file mode 100644 index 00000000..469ad3da --- /dev/null +++ b/examples/cosmwasm/codegen/index.ts @@ -0,0 +1,28 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@1.13.1. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +import * as _0 from "./Sg721.types"; +import * as _1 from "./Sg721.client"; +import * as _2 from "./Sg721.message-composer"; +import * as _3 from "./Minter.types"; +import * as _4 from "./Minter.client"; +import * as _5 from "./Minter.message-composer"; +import * as _6 from "./baseClient"; +export namespace contracts { + export const Sg721 = { + ..._0, + ..._1, + ..._2 + }; + export const Minter = { + ..._3, + ..._4, + ..._5 + }; + export const baseClient = { + ..._6 + }; +} \ No newline at end of file diff --git a/examples/cosmwasm/components/ContractTest.tsx b/examples/cosmwasm/components/ContractTest.tsx new file mode 100644 index 00000000..335f768b --- /dev/null +++ b/examples/cosmwasm/components/ContractTest.tsx @@ -0,0 +1,571 @@ +import React, { useState, useEffect } from 'react'; +import { useChain } from '@interchain-kit/react'; +import { useCustomSigningClient } from '../hooks/useCustomSigningClient'; +import { getCosmWasmClient, getSigningCosmWasmClient } from '../codegen/baseClient'; + +interface ContractTestProps { + chainName: string; +} + +const ContractTest: React.FC = ({ chainName }) => { + const { chain, address, getRpcEndpoint } = useChain(chainName); + const { data: signingClient } = useCustomSigningClient({ chainName }); + + const [contractAddress, setContractAddress] = useState(''); + const [queryMsg, setQueryMsg] = useState('{}'); + const [executeMsg, setExecuteMsg] = useState('{}'); + const [queryResult, setQueryResult] = useState(''); + const [executeResult, setExecuteResult] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [rpcEndpoint, setRpcEndpoint] = useState(null); + + useEffect(() => { + const fetchRpcEndpoint = async () => { + try { + const endpoint = await getRpcEndpoint(); + if (typeof endpoint === 'string') { + setRpcEndpoint(endpoint); + } else if (endpoint && typeof endpoint === 'object' && 'url' in endpoint) { + setRpcEndpoint((endpoint as any).url); + } + } catch (err) { + console.error('Failed to get RPC endpoint:', err); + } + }; + fetchRpcEndpoint(); + }, [chainName]); + + const validateJson = (jsonString: string): string | null => { + try { + JSON.parse(jsonString); + return null; + } catch (err) { + return (err as Error).message; + } + }; + + const validateContractAddress = (address: string, prefix: string): string | null => { + if (!address) return 'Contract address is required'; + if (!address.startsWith(prefix)) { + return `Contract address must start with ${prefix}`; + } + return null; + }; + + const prettifyJson = (jsonString: string): string => { + try { + return JSON.stringify(JSON.parse(jsonString), null, 2); + } catch { + return jsonString; + } + }; + + const handleQuery = async () => { + if (!rpcEndpoint || !contractAddress) { + setError('RPC endpoint and contract address are required'); + return; + } + + const addressError = validateContractAddress(contractAddress, chain.bech32Prefix || 'cosmos'); + if (addressError) { + setError(addressError); + return; + } + + const jsonError = validateJson(queryMsg); + if (jsonError) { + setError(`Invalid query JSON: ${jsonError}`); + return; + } + + setLoading(true); + setError(''); + + try { + const client = getCosmWasmClient(rpcEndpoint); + const result = await client.queryContractSmart(contractAddress, JSON.parse(queryMsg)); + setQueryResult(prettifyJson(JSON.stringify(result))); + } catch (err) { + setError(`Query failed: ${(err as Error).message}`); + } finally { + setLoading(false); + } + }; + + const handleExecute = async () => { + if (!signingClient || !address || !contractAddress) { + setError('Signing client, wallet address, and contract address are required'); + return; + } + + const addressError = validateContractAddress(contractAddress, chain.bech32Prefix || 'cosmos'); + if (addressError) { + setError(addressError); + return; + } + + const jsonError = validateJson(executeMsg); + if (jsonError) { + setError(`Invalid execute JSON: ${jsonError}`); + return; + } + + setLoading(true); + setError(''); + + try { + const cosmWasmClient = getSigningCosmWasmClient(signingClient, rpcEndpoint || undefined); + const result = await cosmWasmClient.execute( + address, + contractAddress, + JSON.parse(executeMsg), + 'auto', // fee + 'Test execution from cosmwasm example', // memo + [] // funds + ); + setExecuteResult(prettifyJson(JSON.stringify(result, null, 2))); + } catch (err) { + setError(`Execute failed: ${(err as Error).message}`); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
🔧
+

Contract Test Interface

+
+ +

Test the query and execute functionality of CosmWasm contracts

+ + {error && ( +
+
❌ {error}
+
+ )} + +
+ + setContractAddress(e.target.value)} + placeholder={`Enter contract address (${chain.bech32Prefix}...)`} + style={{ + width: '100%', + padding: '0.75rem', + border: '1px solid #d1d5db', + borderRadius: '8px', + fontSize: '0.875rem', + transition: 'border-color 0.2s ease', + outline: 'none' + }} + onFocus={(e) => { + e.currentTarget.style.borderColor = '#3b82f6'; + e.currentTarget.style.boxShadow = '0 0 0 3px rgba(59, 130, 246, 0.1)'; + }} + onBlur={(e) => { + e.currentTarget.style.borderColor = '#d1d5db'; + e.currentTarget.style.boxShadow = 'none'; + }} + /> +
+ +
+ {/* Query Section */} +
+

+ 🔍 + Query Contract +

+
+ +