diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index 90f6c7a3a0..9ad835db53 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -102,6 +102,32 @@ describe("SigningCosmWasmClient", () => { expect(gasUsed).toBeLessThanOrEqual(140_000); client.disconnect(); }); + it("works with explicitSignerData", async () => { + pendingWithoutWasmd(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix }); + const client = await SigningCosmWasmClient.connectWithSigner( + wasmd.endpoint, + wallet, + defaultSigningClientOptions, + ); + + 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 { sequence } = await client.getSequence(alice.address0); + + const gasUsed = await client.simulate(alice.address0, [executeContractMsg], memo, { sequence }); + expect(gasUsed).toBeGreaterThanOrEqual(70_000); + expect(gasUsed).toBeLessThanOrEqual(140_000); + client.disconnect(); + }); }); describe("upload", () => { diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index bce739bbf2..2f2eb32c58 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -274,6 +274,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { signerAddress: string, messages: readonly EncodeObject[], memo: string | undefined, + explicitSignerData?: Partial, ): Promise { const anyMsgs = messages.map((m) => this.registry.encodeAsAny(m)); const accountFromSigner = (await this.signer.getAccounts()).find( @@ -283,7 +284,15 @@ export class SigningCosmWasmClient extends CosmWasmClient { throw new Error("Failed to retrieve account from signer"); } const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey); - const { sequence } = await this.getSequence(signerAddress); + + let sequence: number; + + if (explicitSignerData?.sequence !== undefined) { + sequence = explicitSignerData.sequence; + } else { + sequence = (await this.getSequence(signerAddress)).sequence; + } + const { gasInfo } = await this.forceGetQueryClient().tx.simulate(anyMsgs, memo, pubkey, sequence); assertDefined(gasInfo); return Uint53.fromString(gasInfo.gasUsed.toString()).toNumber(); @@ -612,17 +621,35 @@ export class SigningCosmWasmClient extends CosmWasmClient { fee: StdFee | "auto" | number, memo = "", timeoutHeight?: bigint, + explicitSignerData?: SignerData, ): Promise { let usedFee: StdFee; + + let signerData: SignerData | undefined = explicitSignerData; + const { sequence, accountNumber } = explicitSignerData + ? explicitSignerData + : await this.getSequence(signerAddress); + 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 gasEstimation = await this.simulate(signerAddress, messages, memo, { sequence }); const multiplier = typeof fee === "number" ? fee : this.defaultGasMultiplier; usedFee = calculateFee(Math.round(gasEstimation * multiplier), this.gasPrice); } else { usedFee = fee; } - const txRaw = await this.sign(signerAddress, messages, usedFee, memo, undefined, timeoutHeight); + + if (!signerData) { + const chainId = await this.getChainId(); + + signerData = { + accountNumber: accountNumber, + sequence: sequence, + chainId: chainId, + }; + } + + const txRaw = await this.sign(signerAddress, messages, usedFee, memo, signerData, timeoutHeight); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); } @@ -648,8 +675,15 @@ export class SigningCosmWasmClient extends CosmWasmClient { fee: StdFee | "auto" | number, memo = "", timeoutHeight?: bigint, + explicitSignerData?: SignerData, ): Promise { let usedFee: StdFee; + + let signerData: SignerData | undefined = explicitSignerData; + const { sequence, accountNumber } = explicitSignerData + ? explicitSignerData + : await this.getSequence(signerAddress); + 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); @@ -658,7 +692,18 @@ export class SigningCosmWasmClient extends CosmWasmClient { } else { usedFee = fee; } - const txRaw = await this.sign(signerAddress, messages, usedFee, memo, undefined, timeoutHeight); + + if (!signerData) { + const chainId = await this.getChainId(); + + signerData = { + accountNumber: accountNumber, + sequence: sequence, + chainId: chainId, + }; + } + + const txRaw = await this.sign(signerAddress, messages, usedFee, memo, signerData, timeoutHeight); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTxSync(txBytes); } diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index e8d7f8aedd..a6a7cd3215 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -86,6 +86,33 @@ describe("SigningStargateClient", () => { expect(gasUsed).toBeGreaterThanOrEqual(101_000); expect(gasUsed).toBeLessThanOrEqual(200_000); + client.disconnect(); + }); + it("works with explicitSignerData", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner( + simapp.tendermintUrlHttp, + 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 { sequence } = await client.getSequence(faucet.address0); + const gasUsed = await client.simulate(faucet.address0, [msgAny], memo, { sequence }); + expect(gasUsed).toBeGreaterThanOrEqual(101_000); + expect(gasUsed).toBeLessThanOrEqual(200_000); + client.disconnect(); }); }); @@ -739,6 +766,50 @@ describe("SigningStargateClient", () => { await sleep(simapp.blockTime * 1.5); }); + it("works with explicitSignerData", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner( + simapp.tendermintUrlHttp, + wallet, + defaultSigningClientOptions, + ); + + const msgSend: MsgSend = { + fromAddress: faucet.address0, + toAddress: makeRandomAddress(), + amount: coins(1234, "ucosm"), + }; + + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msgSend, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "222000", // 222k + }; + const memo = "Use your power wisely"; + const { sequence, accountNumber } = await client.getSequence(faucet.address0); + const explicitSignerData = { + chainId: simapp.chainId, + sequence, + accountNumber, + }; + const transactionHash = await client.signAndBroadcastSync( + faucet.address0, + [msgAny], + fee, + memo, + undefined, + explicitSignerData, + ); + + expect(transactionHash).toMatch(/^[0-9A-F]{64}$/); + + await sleep(simapp.blockTime * 1.5); + }); + it("works with auto gas", async () => { pendingWithoutSimapp(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index bb9e9715c2..3fdc0974fe 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -179,6 +179,7 @@ export class SigningStargateClient extends StargateClient { signerAddress: string, messages: readonly EncodeObject[], memo: string | undefined, + explicitSignerData?: Partial, ): Promise { const anyMsgs = messages.map((m) => this.registry.encodeAsAny(m)); const accountFromSigner = (await this.signer.getAccounts()).find( @@ -188,7 +189,15 @@ export class SigningStargateClient extends StargateClient { throw new Error("Failed to retrieve account from signer"); } const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey); - const { sequence } = await this.getSequence(signerAddress); + + let sequence: number; + + if (explicitSignerData?.sequence !== undefined) { + sequence = explicitSignerData.sequence; + } else { + sequence = (await this.getSequence(signerAddress)).sequence; + } + const { gasInfo } = await this.forceGetQueryClient().tx.simulate(anyMsgs, memo, pubkey, sequence); assertDefined(gasInfo); return Uint53.fromString(gasInfo.gasUsed.toString()).toNumber(); @@ -306,17 +315,35 @@ export class SigningStargateClient extends StargateClient { fee: StdFee | "auto" | number, memo = "", timeoutHeight?: bigint, + explicitSignerData?: SignerData, ): Promise { let usedFee: StdFee; + + let signerData: SignerData | undefined = explicitSignerData; + const { sequence, accountNumber } = explicitSignerData + ? explicitSignerData + : await this.getSequence(signerAddress); + 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 gasEstimation = await this.simulate(signerAddress, messages, memo, { sequence }); const multiplier = typeof fee === "number" ? fee : this.defaultGasMultiplier; usedFee = calculateFee(Math.round(gasEstimation * multiplier), this.gasPrice); } else { usedFee = fee; } - const txRaw = await this.sign(signerAddress, messages, usedFee, memo, undefined, timeoutHeight); + + if (!signerData) { + const chainId = await this.getChainId(); + + signerData = { + accountNumber: accountNumber, + sequence: sequence, + chainId: chainId, + }; + } + + const txRaw = await this.sign(signerAddress, messages, usedFee, memo, signerData, timeoutHeight); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); } @@ -333,17 +360,35 @@ export class SigningStargateClient extends StargateClient { fee: StdFee | "auto" | number, memo = "", timeoutHeight?: bigint, + explicitSignerData?: SignerData, ): Promise { let usedFee: StdFee; + + let signerData: SignerData | undefined = explicitSignerData; + const { sequence, accountNumber } = explicitSignerData + ? explicitSignerData + : await this.getSequence(signerAddress); + 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 gasEstimation = await this.simulate(signerAddress, messages, memo, { sequence }); const multiplier = typeof fee === "number" ? fee : this.defaultGasMultiplier; usedFee = calculateFee(Math.round(gasEstimation * multiplier), this.gasPrice); } else { usedFee = fee; } - const txRaw = await this.sign(signerAddress, messages, usedFee, memo, undefined, timeoutHeight); + + if (!signerData) { + const chainId = await this.getChainId(); + + signerData = { + accountNumber: accountNumber, + sequence: sequence, + chainId: chainId, + }; + } + + const txRaw = await this.sign(signerAddress, messages, usedFee, memo, signerData, timeoutHeight); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTxSync(txBytes); }