From 9bcc43977b50e24da0b076ce4c5dd97ed4d57952 Mon Sep 17 00:00:00 2001 From: DavideSegullo Date: Mon, 27 Mar 2023 11:55:49 +0200 Subject: [PATCH] feat: :sparkles: add sign and broadcast without polling --- .../cosmwasm-stargate/src/cosmwasmclient.ts | 27 +++++++++++++++ .../src/signingcosmwasmclient.ts | 34 +++++++++++++++++++ .../stargate/src/signingstargateclient.ts | 26 ++++++++++++++ packages/stargate/src/stargateclient.ts | 27 +++++++++++++++ 4 files changed, 114 insertions(+) diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.ts index b52d54e9..6ae7bb43 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.ts @@ -332,6 +332,33 @@ export class CosmWasmClient { ); } + /** + * Broadcasts a signed transaction to the network without monitoring it. + * + * If broadcasting is rejected by the node for some reason (e.g. because of a CheckTx failure), + * an error is thrown. + * + * If the transaction is broadcasted, a `string` containing the hash of the transaction is returned. The caller then + * usually needs to check if the transaction was included in a block and was successful. + * + * @returns Returns the hash of the transaction + */ + public async broadcastTxWithoutPolling( + tx: Uint8Array, + ): Promise { + const broadcasted = await this.forceGetTmClient().broadcastTxSync({ tx }); + + if (broadcasted.code) { + return Promise.reject( + new BroadcastTxError(broadcasted.code, broadcasted.codespace ?? "", broadcasted.log), + ); + } + + const transactionId = toHex(broadcasted.hash).toUpperCase(); + + return transactionId; + } + /** * getCodes() returns all codes and is just looping through all pagination pages. * diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index 03f6fe2f..8103fcd7 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -557,6 +557,40 @@ export class SigningCosmWasmClient extends CosmWasmClient { return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); } + /** + * Creates a transaction with the given messages, fee and memo. Then signs and broadcasts the transaction. + * + * This method is useful if you want to send a transaction in broadcast, + * without waiting for it to be placed inside a block, because for example + * I would like to receive the hash to later track the transaction with another tool. + * + * @param signerAddress The address that will sign transactions using this instance. The signer must be able to sign with this address. + * @param messages + * @param fee + * @param memo + * + * @returns Returns the hash of the transaction + */ + public async signAndBroadcastWithoutPolling( + signerAddress: string, + messages: readonly EncodeObject[], + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + let usedFee: StdFee; + 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); + const multiplier = typeof fee === "number" ? fee : 1.3; + usedFee = calculateFee(Math.round(gasEstimation * multiplier), this.gasPrice); + } else { + usedFee = fee; + } + const txRaw = await this.sign(signerAddress, messages, usedFee, memo); + const txBytes = TxRaw.encode(txRaw).finish(); + return this.broadcastTxWithoutPolling(txBytes); + } + public async sign( signerAddress: string, messages: readonly EncodeObject[], diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index d782e08c..84ab551c 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -310,6 +310,32 @@ export class SigningStargateClient extends StargateClient { return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); } + /** + * This method is useful if you want to send a transaction in broadcast, + * without waiting for it to be placed inside a block, because for example + * I would like to receive the hash to later track the transaction with another tool. + * @returns Returns the hash of the transaction + */ + public async signAndBroadcastWithoutPolling( + signerAddress: string, + messages: readonly EncodeObject[], + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + let usedFee: StdFee; + 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); + const multiplier = typeof fee === "number" ? fee : 1.3; + usedFee = calculateFee(Math.round(gasEstimation * multiplier), this.gasPrice); + } else { + usedFee = fee; + } + const txRaw = await this.sign(signerAddress, messages, usedFee, memo); + const txBytes = TxRaw.encode(txRaw).finish(); + return this.broadcastTxWithoutPolling(txBytes); + } + /** * Gets account number and sequence from the API, creates a sign doc, * creates a single signature and assembles the signed transaction. diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index d4969afa..56883816 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -491,6 +491,33 @@ export class StargateClient { ); } + /** + * Broadcasts a signed transaction to the network without monitoring it. + * + * If broadcasting is rejected by the node for some reason (e.g. because of a CheckTx failure), + * an error is thrown. + * + * If the transaction is broadcasted, a `string` containing the hash of the transaction is returned. The caller then + * usually needs to check if the transaction was included in a block and was successful. + * + * @returns Returns the hash of the transaction + */ + public async broadcastTxWithoutPolling( + tx: Uint8Array, + ): Promise { + const broadcasted = await this.forceGetTmClient().broadcastTxSync({ tx }); + + if (broadcasted.code) { + return Promise.reject( + new BroadcastTxError(broadcasted.code, broadcasted.codespace ?? "", broadcasted.log), + ); + } + + const transactionId = toHex(broadcasted.hash).toUpperCase(); + + return transactionId; + } + private async txsQuery(query: string): Promise { const results = await this.forceGetTmClient().txSearchAll({ query: query }); return results.txs.map((tx) => {