From f68ef5572ad18abde26de9724d791787891eef98 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 25 Mar 2021 14:47:35 +0100 Subject: [PATCH 1/7] Make signing Stargate client constructors protected --- packages/cosmwasm-stargate/src/signingcosmwasmclient.ts | 2 +- packages/stargate/src/signingstargateclient.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index 02b0c765..7ce59df0 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -133,7 +133,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { return new SigningCosmWasmClient(tmClient, signer, options); } - private constructor( + protected constructor( tmClient: Tendermint34Client, signer: OfflineSigner, options: SigningCosmWasmClientOptions, diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index b12e65d5..0732a8b6 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -171,7 +171,7 @@ export class SigningStargateClient extends StargateClient { return new SigningStargateClient(undefined, signer, options); } - private constructor( + protected constructor( tmClient: Tendermint34Client | undefined, signer: OfflineSigner, options: SigningStargateClientOptions, From 9a2b61bfa818ffdb182fff8d57722c160f9e95bb Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 25 Mar 2021 14:48:46 +0100 Subject: [PATCH 2/7] Update CHANGELOG for protected constructors --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d96c6ae9..3d2a90c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,9 @@ and this project adheres to `withdrawRewards` methods to `SigningStargateClient`. - @cosmjs/stargate: Export `defaultGasLimits` and `defaultGasPrice`. - @cosmjs/cosmwasm-stargate: Export `defaultGasLimits`. +- @cosmjs/stargate: `SigningStargateClient` constructor is now `protected`. +- @cosmjs/cosmwasm-stargate: `SigningCosmWasmClient` constructor is now + `protected`. ### Changed From 203c811eb700384ea5c2bad77dd5b3fd81dde6c1 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 25 Mar 2021 14:53:32 +0100 Subject: [PATCH 3/7] stargate: Update PrivateStargateClient type --- packages/stargate/src/stargateclient.spec.ts | 2 +- packages/stargate/src/stargateclient.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stargate/src/stargateclient.spec.ts b/packages/stargate/src/stargateclient.spec.ts index eef1a218..c28e67b0 100644 --- a/packages/stargate/src/stargateclient.spec.ts +++ b/packages/stargate/src/stargateclient.spec.ts @@ -45,7 +45,7 @@ describe("StargateClient", () => { pendingWithoutSimapp(); const client = await StargateClient.connect(simapp.tendermintUrl); const openedClient = (client as unknown) as PrivateStargateClient; - const getCodeSpy = spyOn(openedClient.tmClient, "status").and.callThrough(); + const getCodeSpy = spyOn(openedClient.tmClient!, "status").and.callThrough(); expect(await client.getChainId()).toEqual(simapp.chainId); // from network expect(await client.getChainId()).toEqual(simapp.chainId); // from cache diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 8ee069bd..1837b7ca 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -110,7 +110,7 @@ export function coinFromProto(input: Coin): Coin { /** Use for testing only */ export interface PrivateStargateClient { - readonly tmClient: Tendermint34Client; + readonly tmClient: Tendermint34Client | undefined; } export class StargateClient { From a973cbb69dfb511e3f59cc094039dddf257cb54c Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 25 Mar 2021 14:57:51 +0100 Subject: [PATCH 4/7] stargate: Add getTmClient and getQueryClient methods to StargateClient --- packages/stargate/src/stargateclient.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 1837b7ca..2869a0b4 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -130,6 +130,10 @@ export class StargateClient { } } + protected getTmClient(): Tendermint34Client | undefined { + return this.tmClient; + } + protected forceGetTmClient(): Tendermint34Client { if (!this.tmClient) { throw new Error( @@ -139,6 +143,10 @@ export class StargateClient { return this.tmClient; } + protected getQueryClient(): (QueryClient & AuthExtension & BankExtension) | undefined { + return this.queryClient; + } + protected forceGetQueryClient(): QueryClient & AuthExtension & BankExtension { if (!this.queryClient) { throw new Error("Query client not available. You cannot use online functionality in offline mode."); From 7b8a802f480f54fada78c0a245e69fec243f6adf Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 25 Mar 2021 15:06:31 +0100 Subject: [PATCH 5/7] cosmwasm-stargate: Make tmClient optional and add methods for getting clients --- .../src/cosmwasmclient.spec.ts | 4 +- .../cosmwasm-stargate/src/cosmwasmclient.ts | 80 ++++++++++++------- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts index cf8b03b7..ce064371 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts @@ -57,7 +57,7 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); const client = await CosmWasmClient.connect(wasmd.endpoint); const openedClient = (client as unknown) as PrivateCosmWasmClient; - const getCodeSpy = spyOn(openedClient.tmClient, "status").and.callThrough(); + const getCodeSpy = spyOn(openedClient.tmClient!, "status").and.callThrough(); expect(await client.getChainId()).toEqual(wasmd.chainId); // from network expect(await client.getChainId()).toEqual(wasmd.chainId); // from cache @@ -256,7 +256,7 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); const client = await CosmWasmClient.connect(wasmd.endpoint); const openedClient = (client as unknown) as PrivateCosmWasmClient; - const getCodeSpy = spyOn(openedClient.queryClient.unverified.wasm, "getCode").and.callThrough(); + const getCodeSpy = spyOn(openedClient.queryClient!.unverified.wasm, "getCode").and.callThrough(); const result1 = await client.getCodeDetails(deployedHackatom.codeId); // from network const result2 = await client.getCodeDetails(deployedHackatom.codeId); // from cache diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.ts index fb2af52d..e0d1aca0 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.ts @@ -42,13 +42,13 @@ import { setupWasmExtension, WasmExtension } from "./queries"; /** Use for testing only */ export interface PrivateCosmWasmClient { - readonly tmClient: Tendermint34Client; - readonly queryClient: QueryClient & AuthExtension & BankExtension & WasmExtension; + readonly tmClient: Tendermint34Client | undefined; + readonly queryClient: (QueryClient & AuthExtension & BankExtension & WasmExtension) | undefined; } export class CosmWasmClient { - private readonly tmClient: Tendermint34Client; - private readonly queryClient: QueryClient & AuthExtension & BankExtension & WasmExtension; + private readonly tmClient: Tendermint34Client | undefined; + private readonly queryClient: (QueryClient & AuthExtension & BankExtension & WasmExtension) | undefined; private readonly codesCache = new Map(); private chainId: string | undefined; @@ -57,19 +57,45 @@ export class CosmWasmClient { return new CosmWasmClient(tmClient); } - protected constructor(tmClient: Tendermint34Client) { - this.tmClient = tmClient; - this.queryClient = QueryClient.withExtensions( - tmClient, - setupAuthExtension, - setupBankExtension, - setupWasmExtension, - ); + protected constructor(tmClient: Tendermint34Client | undefined) { + if (tmClient) { + this.tmClient = tmClient; + this.queryClient = QueryClient.withExtensions( + tmClient, + setupAuthExtension, + setupBankExtension, + setupWasmExtension, + ); + } + } + + protected getTmClient(): Tendermint34Client | undefined { + return this.tmClient; + } + + protected forceGetTmClient(): Tendermint34Client { + if (!this.tmClient) { + throw new Error( + "Tendermint client not available. You cannot use online functionality in offline mode.", + ); + } + return this.tmClient; + } + + protected getQueryClient(): (QueryClient & AuthExtension & BankExtension & WasmExtension) | undefined { + return this.queryClient; + } + + protected forceGetQueryClient(): QueryClient & AuthExtension & BankExtension & WasmExtension { + if (!this.queryClient) { + throw new Error("Query client not available. You cannot use online functionality in offline mode."); + } + return this.queryClient; } public async getChainId(): Promise { if (!this.chainId) { - const response = await this.tmClient.status(); + const response = await this.forceGetTmClient().status(); const chainId = response.nodeInfo.network; if (!chainId) throw new Error("Chain ID must not be empty"); this.chainId = chainId; @@ -79,12 +105,12 @@ export class CosmWasmClient { } public async getHeight(): Promise { - const status = await this.tmClient.status(); + const status = await this.forceGetTmClient().status(); return status.syncInfo.latestBlockHeight; } public async getAccount(searchAddress: string): Promise { - const account = await this.queryClient.auth.account(searchAddress); + const account = await this.forceGetQueryClient().auth.account(searchAddress); return account ? accountFromAny(account) : null; } @@ -101,7 +127,7 @@ export class CosmWasmClient { } public async getBlock(height?: number): Promise { - const response = await this.tmClient.block(height); + const response = await this.forceGetTmClient().block(height); return { id: toHex(response.blockId.hash).toUpperCase(), header: { @@ -118,7 +144,7 @@ export class CosmWasmClient { } public async getBalance(address: string, searchDenom: string): Promise { - const balance = await this.queryClient.bank.balance(address, searchDenom); + const balance = await this.forceGetQueryClient().bank.balance(address, searchDenom); return balance ? coinFromProto(balance) : null; } @@ -166,11 +192,11 @@ export class CosmWasmClient { } public disconnect(): void { - this.tmClient.disconnect(); + if (this.tmClient) this.tmClient.disconnect(); } public async broadcastTx(tx: Uint8Array): Promise { - const response = await this.tmClient.broadcastTxCommit({ tx }); + const response = await this.forceGetTmClient().broadcastTxCommit({ tx }); if (broadcastTxCommitSuccess(response)) { return { height: response.height, @@ -197,7 +223,7 @@ export class CosmWasmClient { } public async getCodes(): Promise { - const { codeInfos } = await this.queryClient.unverified.wasm.listCodeInfo(); + const { codeInfos } = await this.forceGetQueryClient().unverified.wasm.listCodeInfo(); return (codeInfos || []).map( (entry: CodeInfoResponse): Code => { assert(entry.creator && entry.codeId && entry.dataHash, "entry incomplete"); @@ -216,7 +242,7 @@ export class CosmWasmClient { const cached = this.codesCache.get(codeId); if (cached) return cached; - const { codeInfo, data } = await this.queryClient.unverified.wasm.getCode(codeId); + const { codeInfo, data } = await this.forceGetQueryClient().unverified.wasm.getCode(codeId); assert( codeInfo && codeInfo.codeId && codeInfo.creator && codeInfo.dataHash && data, "codeInfo missing or incomplete", @@ -234,7 +260,7 @@ export class CosmWasmClient { } public async getContracts(codeId: number): Promise { - const { contractInfos } = await this.queryClient.unverified.wasm.listContractsByCodeId(codeId); + const { contractInfos } = await this.forceGetQueryClient().unverified.wasm.listContractsByCodeId(codeId); return (contractInfos || []).map( ({ address, contractInfo }): Contract => { assert(address, "address missing"); @@ -260,7 +286,7 @@ export class CosmWasmClient { const { address: retrievedAddress, contractInfo, - } = await this.queryClient.unverified.wasm.getContractInfo(address); + } = await this.forceGetQueryClient().unverified.wasm.getContractInfo(address); if (!contractInfo) throw new Error(`No contract found at address "${address}"`); assert(retrievedAddress, "address missing"); assert(contractInfo.codeId && contractInfo.creator && contractInfo.label, "contractInfo incomplete"); @@ -277,7 +303,7 @@ export class CosmWasmClient { * Throws an error if no contract was found at the address */ public async getContractCodeHistory(address: string): Promise { - const result = await this.queryClient.unverified.wasm.getContractCodeHistory(address); + const result = await this.forceGetQueryClient().unverified.wasm.getContractCodeHistory(address); if (!result) throw new Error(`No contract history found for address "${address}"`); const operations: Record = { [ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT]: "Init", @@ -306,7 +332,7 @@ export class CosmWasmClient { // just test contract existence await this.getContract(address); - const { data } = await this.queryClient.unverified.wasm.queryContractRaw(address, key); + const { data } = await this.forceGetQueryClient().unverified.wasm.queryContractRaw(address, key); return data ?? null; } @@ -319,7 +345,7 @@ export class CosmWasmClient { */ public async queryContractSmart(address: string, queryMsg: Record): Promise { try { - return await this.queryClient.unverified.wasm.queryContractSmart(address, queryMsg); + return await this.forceGetQueryClient().unverified.wasm.queryContractSmart(address, queryMsg); } catch (error) { if (error instanceof Error) { if (error.message.startsWith("not found: contract")) { @@ -334,7 +360,7 @@ export class CosmWasmClient { } private async txsQuery(query: string): Promise { - const results = await this.tmClient.txSearchAll({ query: query }); + const results = await this.forceGetTmClient().txSearchAll({ query: query }); return results.txs.map((tx) => { return { height: tx.height, From c7087a8afbf7f98eabd6c175bd54a67439c3a8c1 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 25 Mar 2021 15:09:16 +0100 Subject: [PATCH 6/7] cosmwasm-stargate: Add offline mode to SigningCosmWasmClient --- .../src/signingcosmwasmclient.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index 7ce59df0..9184905a 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -133,8 +133,24 @@ export class SigningCosmWasmClient extends CosmWasmClient { return new SigningCosmWasmClient(tmClient, signer, options); } + /** + * Creates a client in offline mode. + * + * This should only be used in niche cases where you know exactly what you're doing, + * e.g. when building an offline signing application. + * + * When you try to use online functionality with such a signer, an + * exception will be raised. + */ + public static async offline( + signer: OfflineSigner, + options: SigningCosmWasmClientOptions = {}, + ): Promise { + return new SigningCosmWasmClient(undefined, signer, options); + } + protected constructor( - tmClient: Tendermint34Client, + tmClient: Tendermint34Client | undefined, signer: OfflineSigner, options: SigningCosmWasmClientOptions, ) { From 46cdef2756d514bd51a5d3b6e8edb2750977ff3c Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 30 Mar 2021 11:08:17 +0200 Subject: [PATCH 7/7] Update CHANGELOG for SigningCosmWasmClient.offline --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d2a90c2..5ee56bfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,8 @@ and this project adheres to - @cosmjs/stargate: `SigningStargateClient` constructor is now `protected`. - @cosmjs/cosmwasm-stargate: `SigningCosmWasmClient` constructor is now `protected`. +- @cosmjs/cosmwasm-stargate: Add `SigningCosmWasmClient.offline` static method + for constructing offline clients without a Tendermint client. ### Changed