Skip to content
26 changes: 26 additions & 0 deletions packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
53 changes: 49 additions & 4 deletions packages/cosmwasm-stargate/src/signingcosmwasmclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ export class SigningCosmWasmClient extends CosmWasmClient {
signerAddress: string,
messages: readonly EncodeObject[],
memo: string | undefined,
explicitSignerData?: Partial<SignerData>,
): Promise<number> {
const anyMsgs = messages.map((m) => this.registry.encodeAsAny(m));
const accountFromSigner = (await this.signer.getAccounts()).find(
Expand All @@ -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();
Expand Down Expand Up @@ -612,17 +621,35 @@ export class SigningCosmWasmClient extends CosmWasmClient {
fee: StdFee | "auto" | number,
memo = "",
timeoutHeight?: bigint,
explicitSignerData?: SignerData,
): Promise<DeliverTxResponse> {
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);
}
Expand All @@ -648,8 +675,15 @@ export class SigningCosmWasmClient extends CosmWasmClient {
fee: StdFee | "auto" | number,
memo = "",
timeoutHeight?: bigint,
explicitSignerData?: SignerData,
): Promise<string> {
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);
Expand All @@ -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);
}
Expand Down
71 changes: 71 additions & 0 deletions packages/stargate/src/signingstargateclient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
Expand Down Expand Up @@ -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);
Expand Down
55 changes: 50 additions & 5 deletions packages/stargate/src/signingstargateclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export class SigningStargateClient extends StargateClient {
signerAddress: string,
messages: readonly EncodeObject[],
memo: string | undefined,
explicitSignerData?: Partial<SignerData>,
): Promise<number> {
const anyMsgs = messages.map((m) => this.registry.encodeAsAny(m));
const accountFromSigner = (await this.signer.getAccounts()).find(
Expand All @@ -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();
Expand Down Expand Up @@ -306,17 +315,35 @@ export class SigningStargateClient extends StargateClient {
fee: StdFee | "auto" | number,
memo = "",
timeoutHeight?: bigint,
explicitSignerData?: SignerData,
): Promise<DeliverTxResponse> {
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);
}
Expand All @@ -333,17 +360,35 @@ export class SigningStargateClient extends StargateClient {
fee: StdFee | "auto" | number,
memo = "",
timeoutHeight?: bigint,
explicitSignerData?: SignerData,
): Promise<string> {
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);
}
Expand Down
Loading