From 25556ae4a8db35ac49ab584d6278865dd1cf0cd8 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 17 Nov 2021 16:26:54 +0100 Subject: [PATCH 01/11] Add Registry.encodeAsAny --- packages/proto-signing/src/registry.ts | 27 +++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/proto-signing/src/registry.ts b/packages/proto-signing/src/registry.ts index 7ed3cbd5..b40df4b2 100644 --- a/packages/proto-signing/src/registry.ts +++ b/packages/proto-signing/src/registry.ts @@ -117,6 +117,13 @@ export class Registry { return type; } + /** + * Takes a typeUrl/value pair and encodes the value to protobuf if + * the given type was previously registered. + * + * If the value has to be wrapped in an Any, this needs to be done + * manually after this call. Or use `encodeAsAny` instead. + */ public encode(encodeObject: EncodeObject): Uint8Array { const { value, typeUrl } = encodeObject; if (isTxBodyEncodeObject(encodeObject)) { @@ -127,14 +134,20 @@ export class Registry { return type.encode(instance).finish(); } - public encodeTxBody(txBodyFields: TxBodyValue): Uint8Array { - const wrappedMessages = txBodyFields.messages.map((message) => { - const messageBytes = this.encode(message); - return Any.fromPartial({ - typeUrl: message.typeUrl, - value: messageBytes, - }); + /** + * Takes a typeUrl/value pair and encodes the value to an Any if + * the given type was previously registered. + */ + public encodeAsAny(encodeObject: EncodeObject): Any { + const binaryValue = this.encode(encodeObject); + return Any.fromPartial({ + typeUrl: encodeObject.typeUrl, + value: binaryValue, }); + } + + public encodeTxBody(txBodyFields: TxBodyValue): Uint8Array { + const wrappedMessages = txBodyFields.messages.map((message) => this.encodeAsAny(message)); const txBody = TxBody.fromPartial({ ...txBodyFields, messages: wrappedMessages, From 8a944ddf21711121824f0918e8320c0f0f1a2338 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 17 Nov 2021 16:28:16 +0100 Subject: [PATCH 02/11] Add query extension tx for simulation --- packages/stargate/src/index.ts | 2 + packages/stargate/src/queries/index.ts | 1 + packages/stargate/src/queries/tx.spec.ts | 104 +++++++++++++++++++++++ packages/stargate/src/queries/tx.ts | 81 ++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 packages/stargate/src/queries/tx.spec.ts create mode 100644 packages/stargate/src/queries/tx.ts diff --git a/packages/stargate/src/index.ts b/packages/stargate/src/index.ts index 6b11c991..ac91862d 100644 --- a/packages/stargate/src/index.ts +++ b/packages/stargate/src/index.ts @@ -78,7 +78,9 @@ export { setupGovExtension, setupIbcExtension, setupStakingExtension, + setupTxExtension, StakingExtension, + TxExtension, } from "./queries"; export { SearchByHeightQuery, diff --git a/packages/stargate/src/queries/index.ts b/packages/stargate/src/queries/index.ts index cc8b1609..8101bbb4 100644 --- a/packages/stargate/src/queries/index.ts +++ b/packages/stargate/src/queries/index.ts @@ -10,4 +10,5 @@ export { DistributionExtension, setupDistributionExtension } from "./distributio export { setupGovExtension, GovExtension, GovProposalId, GovParamsType } from "./gov"; export { IbcExtension, setupIbcExtension } from "./ibc"; export { setupStakingExtension, StakingExtension } from "./staking"; +export { setupTxExtension, TxExtension } from "./tx"; export { createPagination, createProtobufRpcClient, ProtobufRpcClient } from "./utils"; diff --git a/packages/stargate/src/queries/tx.spec.ts b/packages/stargate/src/queries/tx.spec.ts new file mode 100644 index 00000000..7b4351b7 --- /dev/null +++ b/packages/stargate/src/queries/tx.spec.ts @@ -0,0 +1,104 @@ +import { coin, coins, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { assertDefined, sleep } from "@cosmjs/utils"; +import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; +import Long from "long"; + +import { defaultRegistryTypes, SigningStargateClient } from "../signingstargateclient"; +import { assertIsBroadcastTxSuccess, StargateClient } from "../stargateclient"; +import { + defaultSigningClientOptions, + faucet, + makeRandomAddress, + pendingWithoutSimapp, + simapp, + simappEnabled, + validator, +} from "../testutils.spec"; +import { QueryClient } from "./queryclient"; +import { setupTxExtension, TxExtension } from "./tx"; +import { longify } from "./utils"; + +async function makeClientWithTx(rpcUrl: string): Promise<[QueryClient & TxExtension, Tendermint34Client]> { + const tmClient = await Tendermint34Client.connect(rpcUrl); + return [QueryClient.withExtensions(tmClient, setupTxExtension), tmClient]; +} + +describe("TxExtension", () => { + const defaultFee = { + amount: coins(25000, "ucosm"), + gas: "1500000", // 1.5 million + }; + let txHash: string | undefined; + let memo: string | undefined; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + { + const recipient = makeRandomAddress(); + memo = `Test tx ${Date.now()}`; + const result = await client.sendTokens( + faucet.address0, + recipient, + coins(25000, "ucosm"), + defaultFee, + memo, + ); + assertIsBroadcastTxSuccess(result); + txHash = result.transactionHash; + } + + await sleep(75); // wait until transactions are indexed + } + }); + + describe("getTx", () => { + it("works", async () => { + pendingWithoutSimapp(); + assertDefined(txHash); + assertDefined(memo); + const [client, tmClient] = await makeClientWithTx(simapp.tendermintUrl); + + const response = await client.tx.getTx(txHash); + expect(response.tx?.body?.memo).toEqual(memo); + + tmClient.disconnect(); + }); + }); + + describe("simulate", () => { + it("works", async () => { + pendingWithoutSimapp(); + assertDefined(txHash); + assertDefined(memo); + const [client, tmClient] = await makeClientWithTx(simapp.tendermintUrl); + const sequenceClient = await StargateClient.connect(simapp.tendermintUrl); + + const registry = new Registry(defaultRegistryTypes); + const msg: MsgDelegate = { + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(25000, "ustake"), + }; + const msgAny = registry.encodeAsAny({ + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }); + + const { sequence } = await sequenceClient.getSequence(faucet.address0); + const response = await client.tx.simulate([msgAny], "foo", faucet.pubkey0, sequence); + expect(response.gasInfo?.gasUsed.toNumber()).toBeGreaterThanOrEqual(101_000); + expect(response.gasInfo?.gasUsed.toNumber()).toBeLessThanOrEqual(107_000); + expect(response.gasInfo?.gasWanted).toEqual(longify(Long.UZERO)); + + tmClient.disconnect(); + }); + }); +}); diff --git a/packages/stargate/src/queries/tx.ts b/packages/stargate/src/queries/tx.ts new file mode 100644 index 00000000..596b1f1a --- /dev/null +++ b/packages/stargate/src/queries/tx.ts @@ -0,0 +1,81 @@ +import { Pubkey } from "@cosmjs/amino"; +import { encodePubkey } from "@cosmjs/proto-signing"; +import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; +import { + GetTxRequest, + GetTxResponse, + ServiceClientImpl, + SimulateRequest, + SimulateResponse, +} from "cosmjs-types/cosmos/tx/v1beta1/service"; +import { AuthInfo, Fee, Tx, TxBody } from "cosmjs-types/cosmos/tx/v1beta1/tx"; +import { Any } from "cosmjs-types/google/protobuf/any"; +import Long from "long"; + +import { QueryClient } from "./queryclient"; +import { createProtobufRpcClient } from "./utils"; + +export interface TxExtension { + readonly tx: { + getTx: (txId: string) => Promise; + simulate: ( + messages: readonly Any[], + memo: string | undefined, + signer: Pubkey, + sequence: number, + ) => Promise; + // Add here with tests: + // - broadcastTx + // - getTxsEvent + }; +} + +export function setupTxExtension(base: QueryClient): TxExtension { + // Use this service to get easy typed access to query methods + // This cannot be used for proof verification + const rpc = createProtobufRpcClient(base); + const queryService = new ServiceClientImpl(rpc); + + return { + tx: { + getTx: async (txId: string) => { + const request: GetTxRequest = { + hash: txId, + }; + const response = await queryService.GetTx(request); + return response; + }, + simulate: async ( + messages: readonly Any[], + memo: string | undefined, + signer: Pubkey, + sequence: number, + ) => { + const request = SimulateRequest.fromPartial({ + tx: Tx.fromPartial({ + authInfo: AuthInfo.fromPartial({ + fee: Fee.fromPartial({}), + signerInfos: [ + { + publicKey: encodePubkey(signer), + sequence: Long.fromNumber(sequence, true), + modeInfo: { single: { mode: SignMode.SIGN_MODE_DIRECT } }, + }, + ], + }), + body: TxBody.fromPartial({ + messages: Array.from(messages), + memo: memo, + }), + signatures: [new Uint8Array()], + }), + // Sending serialized `txBytes` is the future. But + // this is not available in Comsos SDK 0.42. + txBytes: undefined, + }); + const response = await queryService.Simulate(request); + return response; + }, + }, + }; +} From 5511c242c1ab8d431dbb55a7da5871c1d2ab6823 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 17 Nov 2021 18:59:00 +0100 Subject: [PATCH 03/11] Add SigningStargateClient.simulate --- .../src/signingstargateclient.spec.ts | 28 +++++++++++++++++++ .../stargate/src/signingstargateclient.ts | 23 +++++++++++++-- packages/stargate/src/stargateclient.ts | 17 +++++++++-- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index 8996a29f..dbe54498 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -42,6 +42,34 @@ describe("SigningStargateClient", () => { }); }); + describe("simulate", () => { + it("works", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + const msg = MsgDelegate.fromPartial({ + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "ustake"), + }); + const msgAny: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }; + const memo = "Use your power wisely"; + const gasUsed = await client.simulate(faucet.address0, [msgAny], memo); + expect(gasUsed).toBeGreaterThanOrEqual(101_000); + expect(gasUsed).toBeLessThanOrEqual(106_000); + + client.disconnect(); + }); + }); + describe("sendTokens", () => { it("works with direct signer", async () => { pendingWithoutSimapp(); diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index 607cc212..120603f8 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -1,6 +1,6 @@ import { encodeSecp256k1Pubkey, makeSignDoc as makeSignDocAmino, StdFee } from "@cosmjs/amino"; import { fromBase64 } from "@cosmjs/encoding"; -import { Int53 } from "@cosmjs/math"; +import { Int53, Uint53 } from "@cosmjs/math"; import { EncodeObject, encodePubkey, @@ -13,7 +13,7 @@ import { TxBodyEncodeObject, } from "@cosmjs/proto-signing"; import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; -import { assert } from "@cosmjs/utils"; +import { assert, assertDefined } from "@cosmjs/utils"; import { MsgMultiSend } from "cosmjs-types/cosmos/bank/v1beta1/tx"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; import { @@ -181,6 +181,25 @@ export class SigningStargateClient extends StargateClient { this.broadcastPollIntervalMs = options.broadcastPollIntervalMs; } + public async simulate( + signerAddress: string, + messages: readonly EncodeObject[], + memo: string | undefined, + ): Promise { + const anyMsgs = messages.map((m) => this.registry.encodeAsAny(m)); + const accountFromSigner = (await this.signer.getAccounts()).find( + (account) => account.address === signerAddress, + ); + if (!accountFromSigner) { + throw new Error("Failed to retrieve account from signer"); + } + const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey); + const { sequence } = await this.getSequence(signerAddress); + const { gasInfo } = await this.forceGetQueryClient().tx.simulate(anyMsgs, memo, pubkey, sequence); + assertDefined(gasInfo); + return Uint53.fromString(gasInfo.gasUsed.toString()).toNumber(); + } + public async sendTokens( senderAddress: string, recipientAddress: string, diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index dde63ba6..0f1ebf3e 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -14,7 +14,9 @@ import { setupAuthExtension, setupBankExtension, setupStakingExtension, + setupTxExtension, StakingExtension, + TxExtension, } from "./queries"; import { isSearchByHeightQuery, @@ -146,7 +148,9 @@ export interface PrivateStargateClient { export class StargateClient { private readonly tmClient: Tendermint34Client | undefined; - private readonly queryClient: (QueryClient & AuthExtension & BankExtension & StakingExtension) | undefined; + private readonly queryClient: + | (QueryClient & AuthExtension & BankExtension & StakingExtension & TxExtension) + | undefined; private chainId: string | undefined; public static async connect(endpoint: string): Promise { @@ -162,6 +166,7 @@ export class StargateClient { setupAuthExtension, setupBankExtension, setupStakingExtension, + setupTxExtension, ); } } @@ -179,11 +184,17 @@ export class StargateClient { return this.tmClient; } - protected getQueryClient(): (QueryClient & AuthExtension & BankExtension & StakingExtension) | undefined { + protected getQueryClient(): + | (QueryClient & AuthExtension & BankExtension & StakingExtension & TxExtension) + | undefined { return this.queryClient; } - protected forceGetQueryClient(): QueryClient & AuthExtension & BankExtension & StakingExtension { + protected forceGetQueryClient(): QueryClient & + AuthExtension & + BankExtension & + StakingExtension & + TxExtension { if (!this.queryClient) { throw new Error("Query client not available. You cannot use online functionality in offline mode."); } From 4bf33f24eac7378b032b1bcf7b0bd02fa822b350 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 17 Nov 2021 20:20:41 +0100 Subject: [PATCH 04/11] Add SigningCosmWasmClient.simulate --- .../cosmwasm-stargate/src/cosmwasmclient.ts | 17 ++++++++--- .../src/signingcosmwasmclient.spec.ts | 30 +++++++++++++++++-- .../src/signingcosmwasmclient.ts | 21 ++++++++++++- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.ts index 1c5c252e..a48d4f15 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.ts @@ -19,7 +19,9 @@ import { SequenceResponse, setupAuthExtension, setupBankExtension, + setupTxExtension, TimeoutError, + TxExtension, } from "@cosmjs/stargate"; import { Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; import { assert, sleep } from "@cosmjs/utils"; @@ -75,12 +77,16 @@ export interface ContractCodeHistoryEntry { /** Use for testing only */ export interface PrivateCosmWasmClient { readonly tmClient: Tendermint34Client | undefined; - readonly queryClient: (QueryClient & AuthExtension & BankExtension & WasmExtension) | undefined; + readonly queryClient: + | (QueryClient & AuthExtension & BankExtension & TxExtension & WasmExtension) + | undefined; } export class CosmWasmClient { private readonly tmClient: Tendermint34Client | undefined; - private readonly queryClient: (QueryClient & AuthExtension & BankExtension & WasmExtension) | undefined; + private readonly queryClient: + | (QueryClient & AuthExtension & BankExtension & TxExtension & WasmExtension) + | undefined; private readonly codesCache = new Map(); private chainId: string | undefined; @@ -97,6 +103,7 @@ export class CosmWasmClient { setupAuthExtension, setupBankExtension, setupWasmExtension, + setupTxExtension, ); } } @@ -114,11 +121,13 @@ export class CosmWasmClient { return this.tmClient; } - protected getQueryClient(): (QueryClient & AuthExtension & BankExtension & WasmExtension) | undefined { + protected getQueryClient(): + | (QueryClient & AuthExtension & BankExtension & TxExtension & WasmExtension) + | undefined { return this.queryClient; } - protected forceGetQueryClient(): QueryClient & AuthExtension & BankExtension & WasmExtension { + protected forceGetQueryClient(): QueryClient & AuthExtension & BankExtension & TxExtension & WasmExtension { if (!this.queryClient) { throw new Error("Query client not available. You cannot use online functionality in offline mode."); } diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index 0bca4ead..7310b3c5 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Secp256k1HdWallet } from "@cosmjs/amino"; import { sha256 } from "@cosmjs/crypto"; -import { toHex } from "@cosmjs/encoding"; +import { toHex, toUtf8 } from "@cosmjs/encoding"; import { decodeTxRaw, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; import { AminoMsgDelegate, @@ -17,12 +17,12 @@ import { DeepPartial, MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; import { AuthInfo, TxBody, TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; -import { MsgStoreCode } from "cosmjs-types/cosmwasm/wasm/v1/tx"; +import { MsgExecuteContract, MsgStoreCode } from "cosmjs-types/cosmwasm/wasm/v1/tx"; import Long from "long"; import pako from "pako"; import protobuf from "protobufjs/minimal"; -import { MsgStoreCodeEncodeObject } from "./encodeobjects"; +import { MsgExecuteContractEncodeObject, MsgStoreCodeEncodeObject } from "./encodeobjects"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import { alice, @@ -71,6 +71,30 @@ describe("SigningCosmWasmClient", () => { }); }); + describe("simulate", () => { + it("works", async () => { + pendingWithoutWasmd(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix }); + const options = { ...defaultSigningClientOptions, prefix: wasmd.prefix }; + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options); + + const executeContractMsg: MsgExecuteContractEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: alice.address0, + contract: deployedHackatom.instances[0].address, + msg: toUtf8(`{"release":{}}`), + funds: [], + }), + }; + const memo = "Go go go"; + const gasUsed = await client.simulate(alice.address0, [executeContractMsg], memo); + expect(gasUsed).toBeGreaterThanOrEqual(101_000); + expect(gasUsed).toBeLessThanOrEqual(106_000); + client.disconnect(); + }); + }); + describe("upload", () => { it("works", async () => { pendingWithoutWasmd(); diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index 76175a08..b7a007fc 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -29,7 +29,7 @@ import { StdFee, } from "@cosmjs/stargate"; import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; -import { assert } from "@cosmjs/utils"; +import { assert, assertDefined } from "@cosmjs/utils"; import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; import { MsgDelegate, MsgUndelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; @@ -196,6 +196,25 @@ export class SigningCosmWasmClient extends CosmWasmClient { this.broadcastPollIntervalMs = options.broadcastPollIntervalMs; } + public async simulate( + signerAddress: string, + messages: readonly EncodeObject[], + memo: string | undefined, + ): Promise { + const anyMsgs = messages.map((m) => this.registry.encodeAsAny(m)); + const accountFromSigner = (await this.signer.getAccounts()).find( + (account) => account.address === signerAddress, + ); + if (!accountFromSigner) { + throw new Error("Failed to retrieve account from signer"); + } + const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey); + const { sequence } = await this.getSequence(signerAddress); + const { gasInfo } = await this.forceGetQueryClient().tx.simulate(anyMsgs, memo, pubkey, sequence); + assertDefined(gasInfo); + return Uint53.fromString(gasInfo.gasUsed.toString()).toNumber(); + } + /** Uploads code and returns a receipt, including the code ID */ public async upload( senderAddress: string, From 2a16375eabd67b537fcba14fffb66042354b87e4 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 18 Nov 2021 19:33:25 +0100 Subject: [PATCH 05/11] Increase accepted gas range in simulation tests --- packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts | 2 +- packages/stargate/src/queries/tx.spec.ts | 2 +- packages/stargate/src/signingstargateclient.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index 7310b3c5..f6fd0dd6 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -90,7 +90,7 @@ describe("SigningCosmWasmClient", () => { const memo = "Go go go"; const gasUsed = await client.simulate(alice.address0, [executeContractMsg], memo); expect(gasUsed).toBeGreaterThanOrEqual(101_000); - expect(gasUsed).toBeLessThanOrEqual(106_000); + expect(gasUsed).toBeLessThanOrEqual(150_000); client.disconnect(); }); }); diff --git a/packages/stargate/src/queries/tx.spec.ts b/packages/stargate/src/queries/tx.spec.ts index 7b4351b7..fa225c05 100644 --- a/packages/stargate/src/queries/tx.spec.ts +++ b/packages/stargate/src/queries/tx.spec.ts @@ -95,7 +95,7 @@ describe("TxExtension", () => { const { sequence } = await sequenceClient.getSequence(faucet.address0); const response = await client.tx.simulate([msgAny], "foo", faucet.pubkey0, sequence); expect(response.gasInfo?.gasUsed.toNumber()).toBeGreaterThanOrEqual(101_000); - expect(response.gasInfo?.gasUsed.toNumber()).toBeLessThanOrEqual(107_000); + expect(response.gasInfo?.gasUsed.toNumber()).toBeLessThanOrEqual(150_000); expect(response.gasInfo?.gasWanted).toEqual(longify(Long.UZERO)); tmClient.disconnect(); diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index dbe54498..bcc2e138 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -64,7 +64,7 @@ describe("SigningStargateClient", () => { const memo = "Use your power wisely"; const gasUsed = await client.simulate(faucet.address0, [msgAny], memo); expect(gasUsed).toBeGreaterThanOrEqual(101_000); - expect(gasUsed).toBeLessThanOrEqual(106_000); + expect(gasUsed).toBeLessThanOrEqual(150_000); client.disconnect(); }); From 926767d1e186841970c40e48fd50413a1cd69b53 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 18 Nov 2021 23:32:27 +0100 Subject: [PATCH 06/11] Add simulate example --- packages/cli/examples/simulate.ts | 48 +++++++++++++++++++++++++++++++ packages/cli/run_examples.sh | 1 + 2 files changed, 49 insertions(+) create mode 100644 packages/cli/examples/simulate.ts diff --git a/packages/cli/examples/simulate.ts b/packages/cli/examples/simulate.ts new file mode 100644 index 00000000..19bb8724 --- /dev/null +++ b/packages/cli/examples/simulate.ts @@ -0,0 +1,48 @@ +import { makeCosmoshubPath } from "@cosmjs/amino"; +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; +import { + assertIsBroadcastTxSuccess, + calculateFee, + GasPrice, + MsgSendEncodeObject, + SigningStargateClient, +} from "@cosmjs/stargate"; + +// Wallet +const mnemonic = + "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone"; +const path = makeCosmoshubPath(3); +const prefix = "cosmos"; +const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { hdPaths: [path], prefix: prefix }); +const [account] = await wallet.getAccounts(); +console.log("Signer address:", account.address); + +// Network config +const rpcEndpoint = "ws://localhost:26658"; +const gasPrice = GasPrice.fromString("0.025ucosm"); + +// Setup client +const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet); + +// Send transaction (using simulate) +const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; +const amount = { + denom: "ucosm", + amount: "1234567", +}; +const sendMsg: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: account.address, + toAddress: recipient, + amount: [amount], + }, +}; +const memo = "With simulate"; +const gasEstimation = await client.simulate(account.address, [sendMsg], memo); +const fee = calculateFee(Math.floor(gasEstimation * 1.3), gasPrice); +const result = await client.sendTokens(account.address, recipient, [amount], fee, memo); +assertIsBroadcastTxSuccess(result); +console.log("Successfully broadcasted:", result); + +client.disconnect(); diff --git a/packages/cli/run_examples.sh b/packages/cli/run_examples.sh index 897442ca..c8fcd5c6 100755 --- a/packages/cli/run_examples.sh +++ b/packages/cli/run_examples.sh @@ -15,4 +15,5 @@ yarn node ./bin/cosmwasm-cli --init examples/mask.ts --code "process.exit(0)" yarn node ./bin/cosmwasm-cli --init examples/multisig_address.ts --code "process.exit(0)" if [ -n "${SIMAPP42_ENABLED:-}" ]; then yarn node ./bin/cosmwasm-cli --init examples/stargate.ts --code "process.exit(0)" + yarn node ./bin/cosmwasm-cli --init examples/simulate.ts --code "process.exit(0)" fi From 20e49746c5d51721f1087ecb4c0e50db0213100b Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 18 Nov 2021 23:34:26 +0100 Subject: [PATCH 07/11] Use coins helper in examples --- packages/cli/examples/simulate.ts | 11 ++++------- packages/cli/examples/stargate.ts | 9 +++------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/cli/examples/simulate.ts b/packages/cli/examples/simulate.ts index 19bb8724..035d8e24 100644 --- a/packages/cli/examples/simulate.ts +++ b/packages/cli/examples/simulate.ts @@ -1,4 +1,4 @@ -import { makeCosmoshubPath } from "@cosmjs/amino"; +import { coins, makeCosmoshubPath } from "@cosmjs/amino"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { assertIsBroadcastTxSuccess, @@ -26,22 +26,19 @@ const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet // Send transaction (using simulate) const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; -const amount = { - denom: "ucosm", - amount: "1234567", -}; +const amount = coins(1234567, "ucosm"); const sendMsg: MsgSendEncodeObject = { typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: { fromAddress: account.address, toAddress: recipient, - amount: [amount], + amount: amount, }, }; const memo = "With simulate"; const gasEstimation = await client.simulate(account.address, [sendMsg], memo); const fee = calculateFee(Math.floor(gasEstimation * 1.3), gasPrice); -const result = await client.sendTokens(account.address, recipient, [amount], fee, memo); +const result = await client.sendTokens(account.address, recipient, amount, fee, memo); assertIsBroadcastTxSuccess(result); console.log("Successfully broadcasted:", result); diff --git a/packages/cli/examples/stargate.ts b/packages/cli/examples/stargate.ts index fabd8c11..c3fce377 100644 --- a/packages/cli/examples/stargate.ts +++ b/packages/cli/examples/stargate.ts @@ -1,4 +1,4 @@ -import { makeCosmoshubPath } from "@cosmjs/amino"; +import { coins, makeCosmoshubPath } from "@cosmjs/amino"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { assertIsBroadcastTxSuccess, calculateFee, GasPrice, SigningStargateClient } from "@cosmjs/stargate"; @@ -20,15 +20,12 @@ const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet // Send transaction const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; -const amount = { - denom: "ucosm", - amount: "1234567", -}; +const amount = coins(1234567, "ucosm"); const fee = calculateFee(200_000, gasPrice); const result = await client.sendTokens( account.address, recipient, - [amount], + amount, fee, "Have fun with your star coins", ); From e3b18c385935e5208e697f008d2e5e7e246b6c30 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 18 Nov 2021 23:40:02 +0100 Subject: [PATCH 08/11] Use sendTokens and signAndBroadcast in example --- packages/cli/examples/simulate.ts | 56 +++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/packages/cli/examples/simulate.ts b/packages/cli/examples/simulate.ts index 035d8e24..d027a713 100644 --- a/packages/cli/examples/simulate.ts +++ b/packages/cli/examples/simulate.ts @@ -24,22 +24,44 @@ const gasPrice = GasPrice.fromString("0.025ucosm"); // Setup client const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet); -// Send transaction (using simulate) -const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; -const amount = coins(1234567, "ucosm"); -const sendMsg: MsgSendEncodeObject = { - typeUrl: "/cosmos.bank.v1beta1.MsgSend", - value: { - fromAddress: account.address, - toAddress: recipient, - amount: amount, - }, -}; -const memo = "With simulate"; -const gasEstimation = await client.simulate(account.address, [sendMsg], memo); -const fee = calculateFee(Math.floor(gasEstimation * 1.3), gasPrice); -const result = await client.sendTokens(account.address, recipient, amount, fee, memo); -assertIsBroadcastTxSuccess(result); -console.log("Successfully broadcasted:", result); +// Send transaction (using sendTokens) +{ + const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; + const amount = coins(1234567, "ucosm"); + const sendMsg: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: account.address, + toAddress: recipient, + amount: amount, + }, + }; + const memo = "With simulate"; + const gasEstimation = await client.simulate(account.address, [sendMsg], memo); + const fee = calculateFee(Math.floor(gasEstimation * 1.3), gasPrice); + const result = await client.sendTokens(account.address, recipient, amount, fee, memo); + assertIsBroadcastTxSuccess(result); + console.log("Successfully broadcasted:", result); +} + +// Send transaction (using signAndBroadcast) +{ + const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; + const amount = coins(1234567, "ucosm"); + const sendMsg: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: account.address, + toAddress: recipient, + amount: amount, + }, + }; + const memo = "With simulate"; + const gasEstimation = await client.simulate(account.address, [sendMsg], memo); + const fee = calculateFee(Math.floor(gasEstimation * 1.3), gasPrice); + const result = await client.signAndBroadcast(account.address, [sendMsg], fee, memo); + assertIsBroadcastTxSuccess(result); + console.log("Successfully broadcasted:", result); +} client.disconnect(); From 86467822f9bb78af2b832b0ab38f684bd7887687 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 19 Nov 2021 00:11:10 +0100 Subject: [PATCH 09/11] Add "auto" option to fee --- packages/cli/examples/simulate.ts | 38 ++++++++++++++++--- .../src/signingcosmwasmclient.spec.ts | 27 +++++++++++++ .../src/signingcosmwasmclient.ts | 37 ++++++++++++------ .../src/signingstargateclient.spec.ts | 22 +++++++++++ .../stargate/src/signingstargateclient.ts | 26 +++++++++---- 5 files changed, 126 insertions(+), 24 deletions(-) diff --git a/packages/cli/examples/simulate.ts b/packages/cli/examples/simulate.ts index d027a713..3bec3df2 100644 --- a/packages/cli/examples/simulate.ts +++ b/packages/cli/examples/simulate.ts @@ -22,9 +22,19 @@ const rpcEndpoint = "ws://localhost:26658"; const gasPrice = GasPrice.fromString("0.025ucosm"); // Setup client -const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet); +const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet, { gasPrice: gasPrice }); -// Send transaction (using sendTokens) +// Send transaction (using sendTokens with auto gas) +{ + const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; + const amount = coins(1234567, "ucosm"); + const memo = "With simulate"; + const result = await client.sendTokens(account.address, recipient, amount, "auto", memo); + assertIsBroadcastTxSuccess(result); + console.log("Successfully broadcasted:", result); +} + +// Send transaction (using sendTokens with manual gas) { const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; const amount = coins(1234567, "ucosm"); @@ -38,13 +48,31 @@ const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet }; const memo = "With simulate"; const gasEstimation = await client.simulate(account.address, [sendMsg], memo); - const fee = calculateFee(Math.floor(gasEstimation * 1.3), gasPrice); + const fee = calculateFee(Math.round(gasEstimation * 1.3), gasPrice); const result = await client.sendTokens(account.address, recipient, amount, fee, memo); assertIsBroadcastTxSuccess(result); console.log("Successfully broadcasted:", result); } -// Send transaction (using signAndBroadcast) +// Send transaction (using signAndBroadcast with auto gas) +{ + const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; + const amount = coins(1234567, "ucosm"); + const sendMsg: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: account.address, + toAddress: recipient, + amount: amount, + }, + }; + const memo = "With simulate"; + const result = await client.signAndBroadcast(account.address, [sendMsg], "auto", memo); + assertIsBroadcastTxSuccess(result); + console.log("Successfully broadcasted:", result); +} + +// Send transaction (using signAndBroadcast with manual gas) { const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; const amount = coins(1234567, "ucosm"); @@ -58,7 +86,7 @@ const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet }; const memo = "With simulate"; const gasEstimation = await client.simulate(account.address, [sendMsg], memo); - const fee = calculateFee(Math.floor(gasEstimation * 1.3), gasPrice); + const fee = calculateFee(Math.round(gasEstimation * 1.3), gasPrice); const result = await client.signAndBroadcast(account.address, [sendMsg], fee, memo); assertIsBroadcastTxSuccess(result); console.log("Successfully broadcasted:", result); diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index f6fd0dd6..02a40a74 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -28,6 +28,7 @@ import { alice, defaultClearAdminFee, defaultExecuteFee, + defaultGasPrice, defaultInstantiateFee, defaultMigrateFee, defaultSendFee, @@ -600,6 +601,32 @@ describe("SigningCosmWasmClient", () => { client.disconnect(); }); + it("works with auto gas", async () => { + pendingWithoutWasmd(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix }); + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, { + ...defaultSigningClientOptions, + prefix: wasmd.prefix, + gasPrice: defaultGasPrice, + }); + const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; + + const msg = MsgDelegate.fromPartial({ + delegatorAddress: alice.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "ustake"), + }); + const msgAny: MsgDelegateEncodeObject = { + typeUrl: msgDelegateTypeUrl, + value: msg, + }; + const memo = "Use your power wisely"; + const result = await client.signAndBroadcast(alice.address0, [msgAny], "auto", memo); + assertIsBroadcastTxSuccess(result); + + client.disconnect(); + }); + it("works with a modifying signer", async () => { pendingWithoutWasmd(); const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index b7a007fc..b8465523 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -17,8 +17,10 @@ import { AminoTypes, BroadcastTxFailure, BroadcastTxResponse, + calculateFee, Coin, defaultRegistryTypes, + GasPrice, isBroadcastTxFailure, logs, MsgDelegateEncodeObject, @@ -144,6 +146,7 @@ export interface SigningCosmWasmClientOptions { readonly prefix?: string; readonly broadcastTimeoutMs?: number; readonly broadcastPollIntervalMs?: number; + readonly gasPrice?: GasPrice; } export class SigningCosmWasmClient extends CosmWasmClient { @@ -153,6 +156,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { private readonly signer: OfflineSigner; private readonly aminoTypes: AminoTypes; + private readonly gasPrice: GasPrice | undefined; public static async connectWithSigner( endpoint: string, @@ -194,6 +198,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { this.signer = signer; this.broadcastTimeoutMs = options.broadcastTimeoutMs; this.broadcastPollIntervalMs = options.broadcastPollIntervalMs; + this.gasPrice = options.gasPrice; } public async simulate( @@ -219,7 +224,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async upload( senderAddress: string, wasmCode: Uint8Array, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const compressed = pako.gzip(wasmCode, { level: 9 }); @@ -253,7 +258,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { codeId: number, msg: Record, label: string, - fee: StdFee, + fee: StdFee | "auto", options: InstantiateOptions = {}, ): Promise { const instantiateContractMsg: MsgInstantiateContractEncodeObject = { @@ -284,7 +289,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { senderAddress: string, contractAddress: string, newAdmin: string, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const updateAdminMsg: MsgUpdateAdminEncodeObject = { @@ -308,7 +313,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async clearAdmin( senderAddress: string, contractAddress: string, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const clearAdminMsg: MsgClearAdminEncodeObject = { @@ -333,7 +338,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { contractAddress: string, codeId: number, migrateMsg: Record, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const migrateContractMsg: MsgMigrateContractEncodeObject = { @@ -359,7 +364,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { senderAddress: string, contractAddress: string, msg: Record, - fee: StdFee, + fee: StdFee | "auto", memo = "", funds?: readonly Coin[], ): Promise { @@ -386,7 +391,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { senderAddress: string, recipientAddress: string, amount: readonly Coin[], - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const sendMsg: MsgSendEncodeObject = { @@ -404,7 +409,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const delegateMsg: MsgDelegateEncodeObject = { @@ -418,7 +423,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const undelegateMsg: MsgUndelegateEncodeObject = { @@ -431,7 +436,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async withdrawRewards( delegatorAddress: string, validatorAddress: string, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const withdrawDelegatorRewardMsg: MsgWithdrawDelegatorRewardEncodeObject = { @@ -452,10 +457,18 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async signAndBroadcast( signerAddress: string, messages: readonly EncodeObject[], - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { - const txRaw = await this.sign(signerAddress, messages, fee, memo); + let usedFee: StdFee; + if (fee == "auto") { + assertDefined(this.gasPrice, "Gas price must be set in the client options when auto gas is used."); + const gasEstimation = await this.simulate(signerAddress, messages, memo); + usedFee = calculateFee(Math.round(gasEstimation * 1.3), this.gasPrice); + } else { + usedFee = fee; + } + const txRaw = await this.sign(signerAddress, messages, usedFee, memo); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); } diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index bcc2e138..dd25f997 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -16,6 +16,7 @@ import { MsgDelegateEncodeObject, MsgSendEncodeObject } from "./encodeobjects"; import { PrivateSigningStargateClient, SigningStargateClient } from "./signingstargateclient"; import { assertIsBroadcastTxSuccess, isBroadcastTxFailure } from "./stargateclient"; import { + defaultGasPrice, defaultSendFee, defaultSigningClientOptions, faucet, @@ -273,6 +274,27 @@ describe("SigningStargateClient", () => { assertIsBroadcastTxSuccess(result); }); + it("works with auto gas", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + gasPrice: defaultGasPrice, + }); + + const msg = MsgDelegate.fromPartial({ + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "ustake"), + }); + const msgAny: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], "auto"); + assertIsBroadcastTxSuccess(result); + }); + it("works with a modifying signer", async () => { pendingWithoutSimapp(); const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index 120603f8..ec8d55af 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -68,6 +68,7 @@ import { MsgUndelegateEncodeObject, MsgWithdrawDelegatorRewardEncodeObject, } from "./encodeobjects"; +import { calculateFee, GasPrice } from "./fee"; import { BroadcastTxResponse, StargateClient } from "./stargateclient"; export const defaultRegistryTypes: ReadonlyArray<[string, GeneratedType]> = [ @@ -131,6 +132,7 @@ export interface SigningStargateClientOptions { readonly prefix?: string; readonly broadcastTimeoutMs?: number; readonly broadcastPollIntervalMs?: number; + readonly gasPrice?: GasPrice; } export class SigningStargateClient extends StargateClient { @@ -140,6 +142,7 @@ export class SigningStargateClient extends StargateClient { private readonly signer: OfflineSigner; private readonly aminoTypes: AminoTypes; + private readonly gasPrice: GasPrice | undefined; public static async connectWithSigner( endpoint: string, @@ -179,6 +182,7 @@ export class SigningStargateClient extends StargateClient { this.signer = signer; this.broadcastTimeoutMs = options.broadcastTimeoutMs; this.broadcastPollIntervalMs = options.broadcastPollIntervalMs; + this.gasPrice = options.gasPrice; } public async simulate( @@ -204,7 +208,7 @@ export class SigningStargateClient extends StargateClient { senderAddress: string, recipientAddress: string, amount: readonly Coin[], - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const sendMsg: MsgSendEncodeObject = { @@ -222,7 +226,7 @@ export class SigningStargateClient extends StargateClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const delegateMsg: MsgDelegateEncodeObject = { @@ -240,7 +244,7 @@ export class SigningStargateClient extends StargateClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const undelegateMsg: MsgUndelegateEncodeObject = { @@ -257,7 +261,7 @@ export class SigningStargateClient extends StargateClient { public async withdrawRewards( delegatorAddress: string, validatorAddress: string, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const withdrawMsg: MsgWithdrawDelegatorRewardEncodeObject = { @@ -279,7 +283,7 @@ export class SigningStargateClient extends StargateClient { timeoutHeight: Height | undefined, /** timeout in seconds */ timeoutTimestamp: number | undefined, - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { const timeoutTimestampNanoseconds = timeoutTimestamp @@ -303,10 +307,18 @@ export class SigningStargateClient extends StargateClient { public async signAndBroadcast( signerAddress: string, messages: readonly EncodeObject[], - fee: StdFee, + fee: StdFee | "auto", memo = "", ): Promise { - const txRaw = await this.sign(signerAddress, messages, fee, memo); + let usedFee: StdFee; + if (fee == "auto") { + assertDefined(this.gasPrice, "Gas price must be set in the client options when auto gas is used."); + const gasEstimation = await this.simulate(signerAddress, messages, memo); + usedFee = calculateFee(Math.round(gasEstimation * 1.3), this.gasPrice); + } else { + usedFee = fee; + } + const txRaw = await this.sign(signerAddress, messages, usedFee, memo); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); } From 37b472af44b15e585ee6df64d1f98c8d12f1fe30 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 19 Nov 2021 11:26:56 +0100 Subject: [PATCH 10/11] Allow the use of a custom gas multiplier --- packages/cli/examples/simulate.ts | 28 +++++++++++++++++++ .../src/signingcosmwasmclient.ts | 27 +++++++++--------- .../stargate/src/signingstargateclient.ts | 17 +++++------ 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/packages/cli/examples/simulate.ts b/packages/cli/examples/simulate.ts index 3bec3df2..c01c6119 100644 --- a/packages/cli/examples/simulate.ts +++ b/packages/cli/examples/simulate.ts @@ -34,6 +34,16 @@ const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet console.log("Successfully broadcasted:", result); } +// Send transaction (using sendTokens with auto gas and custom muliplier) +{ + const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; + const amount = coins(1234567, "ucosm"); + const memo = "With simulate"; + const result = await client.sendTokens(account.address, recipient, amount, 1.2, memo); + assertIsBroadcastTxSuccess(result); + console.log("Successfully broadcasted:", result); +} + // Send transaction (using sendTokens with manual gas) { const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; @@ -72,6 +82,24 @@ const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet console.log("Successfully broadcasted:", result); } +// Send transaction (using signAndBroadcast with auto gas and custom muliplier) +{ + const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; + const amount = coins(1234567, "ucosm"); + const sendMsg: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: account.address, + toAddress: recipient, + amount: amount, + }, + }; + const memo = "With simulate"; + const result = await client.signAndBroadcast(account.address, [sendMsg], 1.4, memo); + assertIsBroadcastTxSuccess(result); + console.log("Successfully broadcasted:", result); +} + // Send transaction (using signAndBroadcast with manual gas) { const recipient = "cosmos1xv9tklw7d82sezh9haa573wufgy59vmwe6xxe5"; diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index b8465523..0e6582b7 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -224,7 +224,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async upload( senderAddress: string, wasmCode: Uint8Array, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const compressed = pako.gzip(wasmCode, { level: 9 }); @@ -258,7 +258,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { codeId: number, msg: Record, label: string, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, options: InstantiateOptions = {}, ): Promise { const instantiateContractMsg: MsgInstantiateContractEncodeObject = { @@ -289,7 +289,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { senderAddress: string, contractAddress: string, newAdmin: string, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const updateAdminMsg: MsgUpdateAdminEncodeObject = { @@ -313,7 +313,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async clearAdmin( senderAddress: string, contractAddress: string, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const clearAdminMsg: MsgClearAdminEncodeObject = { @@ -338,7 +338,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { contractAddress: string, codeId: number, migrateMsg: Record, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const migrateContractMsg: MsgMigrateContractEncodeObject = { @@ -364,7 +364,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { senderAddress: string, contractAddress: string, msg: Record, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", funds?: readonly Coin[], ): Promise { @@ -391,7 +391,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { senderAddress: string, recipientAddress: string, amount: readonly Coin[], - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const sendMsg: MsgSendEncodeObject = { @@ -409,7 +409,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const delegateMsg: MsgDelegateEncodeObject = { @@ -423,7 +423,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const undelegateMsg: MsgUndelegateEncodeObject = { @@ -436,7 +436,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async withdrawRewards( delegatorAddress: string, validatorAddress: string, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const withdrawDelegatorRewardMsg: MsgWithdrawDelegatorRewardEncodeObject = { @@ -457,14 +457,15 @@ export class SigningCosmWasmClient extends CosmWasmClient { public async signAndBroadcast( signerAddress: string, messages: readonly EncodeObject[], - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { let usedFee: StdFee; - if (fee == "auto") { + if (fee == "auto" || typeof fee === "number") { assertDefined(this.gasPrice, "Gas price must be set in the client options when auto gas is used."); const gasEstimation = await this.simulate(signerAddress, messages, memo); - usedFee = calculateFee(Math.round(gasEstimation * 1.3), this.gasPrice); + const muliplier = typeof fee === "number" ? fee : 1.3; + usedFee = calculateFee(Math.round(gasEstimation * muliplier), this.gasPrice); } else { usedFee = fee; } diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index ec8d55af..bb8895b0 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -208,7 +208,7 @@ export class SigningStargateClient extends StargateClient { senderAddress: string, recipientAddress: string, amount: readonly Coin[], - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const sendMsg: MsgSendEncodeObject = { @@ -226,7 +226,7 @@ export class SigningStargateClient extends StargateClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const delegateMsg: MsgDelegateEncodeObject = { @@ -244,7 +244,7 @@ export class SigningStargateClient extends StargateClient { delegatorAddress: string, validatorAddress: string, amount: Coin, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const undelegateMsg: MsgUndelegateEncodeObject = { @@ -261,7 +261,7 @@ export class SigningStargateClient extends StargateClient { public async withdrawRewards( delegatorAddress: string, validatorAddress: string, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const withdrawMsg: MsgWithdrawDelegatorRewardEncodeObject = { @@ -283,7 +283,7 @@ export class SigningStargateClient extends StargateClient { timeoutHeight: Height | undefined, /** timeout in seconds */ timeoutTimestamp: number | undefined, - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { const timeoutTimestampNanoseconds = timeoutTimestamp @@ -307,14 +307,15 @@ export class SigningStargateClient extends StargateClient { public async signAndBroadcast( signerAddress: string, messages: readonly EncodeObject[], - fee: StdFee | "auto", + fee: StdFee | "auto" | number, memo = "", ): Promise { let usedFee: StdFee; - if (fee == "auto") { + if (fee == "auto" || typeof fee === "number") { assertDefined(this.gasPrice, "Gas price must be set in the client options when auto gas is used."); const gasEstimation = await this.simulate(signerAddress, messages, memo); - usedFee = calculateFee(Math.round(gasEstimation * 1.3), this.gasPrice); + const muliplier = typeof fee === "number" ? fee : 1.3; + usedFee = calculateFee(Math.round(gasEstimation * muliplier), this.gasPrice); } else { usedFee = fee; } From b4aa877843c1206104b0207f3053a7d59b2d734f Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sat, 20 Nov 2021 09:44:25 +0100 Subject: [PATCH 11/11] Try SIGN_MODE_UNSPECIFIED --- packages/stargate/src/queries/tx.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stargate/src/queries/tx.ts b/packages/stargate/src/queries/tx.ts index 596b1f1a..ca591d3b 100644 --- a/packages/stargate/src/queries/tx.ts +++ b/packages/stargate/src/queries/tx.ts @@ -59,7 +59,7 @@ export function setupTxExtension(base: QueryClient): TxExtension { { publicKey: encodePubkey(signer), sequence: Long.fromNumber(sequence, true), - modeInfo: { single: { mode: SignMode.SIGN_MODE_DIRECT } }, + modeInfo: { single: { mode: SignMode.SIGN_MODE_UNSPECIFIED } }, }, ], }),