mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-10 21:49:15 +00:00
Merge branch 'main' into issue334/implement_instantiate_permission
This commit is contained in:
commit
32c4057e1e
@ -20,9 +20,16 @@ and this project adheres to
|
|||||||
([#1266]).
|
([#1266]).
|
||||||
- @cosmjs/stargate: `IndexedTx` and `DeliverTxResponse` now have a
|
- @cosmjs/stargate: `IndexedTx` and `DeliverTxResponse` now have a
|
||||||
`msgResponses` field ([#1305]).
|
`msgResponses` field ([#1305]).
|
||||||
|
- @cosmjs/cosmwasm-stargate: Add `CosmWasmClient.broadcastTxSync` and
|
||||||
|
`SigningCosmWasmClient.signAndBroadcastSync` to allow broadcasting without
|
||||||
|
waiting for block inclusion. ([#1396])
|
||||||
|
- @cosmjs/stargateAdd `StargateClient.broadcastTxSync` and
|
||||||
|
`SigningStargateClient.signAndBroadcastSync` to allow broadcasting without
|
||||||
|
waiting for block inclusion. ([#1396])
|
||||||
|
|
||||||
[#1266]: https://github.com/cosmos/cosmjs/issues/1266
|
[#1266]: https://github.com/cosmos/cosmjs/issues/1266
|
||||||
[#1305]: https://github.com/cosmos/cosmjs/issues/1305
|
[#1305]: https://github.com/cosmos/cosmjs/issues/1305
|
||||||
|
[#1396]: https://github.com/cosmos/cosmjs/pull/1396
|
||||||
[#1407]: https://github.com/cosmos/cosmjs/pull/1407
|
[#1407]: https://github.com/cosmos/cosmjs/pull/1407
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -296,13 +296,8 @@ export class CosmWasmClient {
|
|||||||
: pollForTx(txId);
|
: pollForTx(txId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const broadcasted = await this.forceGetTmClient().broadcastTxSync({ tx });
|
const transactionId = await this.broadcastTxSync(tx);
|
||||||
if (broadcasted.code) {
|
|
||||||
return Promise.reject(
|
|
||||||
new BroadcastTxError(broadcasted.code, broadcasted.codespace ?? "", broadcasted.log),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const transactionId = toHex(broadcasted.hash).toUpperCase();
|
|
||||||
return new Promise((resolve, reject) =>
|
return new Promise((resolve, reject) =>
|
||||||
pollForTx(transactionId).then(
|
pollForTx(transactionId).then(
|
||||||
(value) => {
|
(value) => {
|
||||||
@ -317,6 +312,31 @@ 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 broadcastTxSync(tx: Uint8Array): Promise<string> {
|
||||||
|
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.
|
* getCodes() returns all codes and is just looping through all pagination pages.
|
||||||
*
|
*
|
||||||
|
@ -614,6 +614,40 @@ export class SigningCosmWasmClient extends CosmWasmClient {
|
|||||||
return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs);
|
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 signAndBroadcastSync(
|
||||||
|
signerAddress: string,
|
||||||
|
messages: readonly EncodeObject[],
|
||||||
|
fee: StdFee | "auto" | number,
|
||||||
|
memo = "",
|
||||||
|
): Promise<string> {
|
||||||
|
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.broadcastTxSync(txBytes);
|
||||||
|
}
|
||||||
|
|
||||||
public async sign(
|
public async sign(
|
||||||
signerAddress: string,
|
signerAddress: string,
|
||||||
messages: readonly EncodeObject[],
|
messages: readonly EncodeObject[],
|
||||||
|
@ -690,6 +690,160 @@ describe("SigningStargateClient", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("signAndBroadcastSync", () => {
|
||||||
|
describe("direct mode", () => {
|
||||||
|
it("works", async () => {
|
||||||
|
pendingWithoutSimapp();
|
||||||
|
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic);
|
||||||
|
const client = await SigningStargateClient.connectWithSigner(
|
||||||
|
simapp.tendermintUrl,
|
||||||
|
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 transactionHash = await client.signAndBroadcastSync(faucet.address0, [msgAny], fee, memo);
|
||||||
|
|
||||||
|
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);
|
||||||
|
const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet, {
|
||||||
|
...defaultSigningClientOptions,
|
||||||
|
gasPrice: defaultGasPrice,
|
||||||
|
});
|
||||||
|
|
||||||
|
const msgSend: MsgSend = {
|
||||||
|
fromAddress: faucet.address0,
|
||||||
|
toAddress: makeRandomAddress(),
|
||||||
|
amount: coins(1234, "ucosm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const msgAny: MsgSendEncodeObject = {
|
||||||
|
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
|
||||||
|
value: msgSend,
|
||||||
|
};
|
||||||
|
const transactionHash = await client.signAndBroadcastSync(faucet.address0, [msgAny], "auto");
|
||||||
|
|
||||||
|
expect(transactionHash).toMatch(/^[0-9A-F]{64}$/);
|
||||||
|
|
||||||
|
await sleep(simapp.blockTime * 1.5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works with a modifying signer", async () => {
|
||||||
|
pendingWithoutSimapp();
|
||||||
|
const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic);
|
||||||
|
const client = await SigningStargateClient.connectWithSigner(
|
||||||
|
simapp.tendermintUrl,
|
||||||
|
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 transactionHash = await client.signAndBroadcastSync(faucet.address0, [msgAny], fee, memo);
|
||||||
|
|
||||||
|
expect(transactionHash).toMatch(/^[0-9A-F]{64}$/);
|
||||||
|
|
||||||
|
await sleep(simapp.blockTime * 1.5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("legacy Amino mode", () => {
|
||||||
|
it("works with special characters in memo", async () => {
|
||||||
|
pendingWithoutSimapp();
|
||||||
|
const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic);
|
||||||
|
const client = await SigningStargateClient.connectWithSigner(
|
||||||
|
simapp.tendermintUrl,
|
||||||
|
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: "200000",
|
||||||
|
};
|
||||||
|
const memo = "ampersand:&,lt:<,gt:>";
|
||||||
|
const transactionHash = await client.signAndBroadcastSync(faucet.address0, [msgAny], fee, memo);
|
||||||
|
|
||||||
|
expect(transactionHash).toMatch(/^[0-9A-F]{64}$/);
|
||||||
|
|
||||||
|
await sleep(simapp.blockTime * 1.5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works with bank MsgSend", async () => {
|
||||||
|
pendingWithoutSimapp();
|
||||||
|
const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic);
|
||||||
|
const client = await SigningStargateClient.connectWithSigner(
|
||||||
|
simapp.tendermintUrl,
|
||||||
|
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: "200000",
|
||||||
|
};
|
||||||
|
const memo = "Use your tokens wisely";
|
||||||
|
const transactionHash = await client.signAndBroadcastSync(faucet.address0, [msgAny], fee, memo);
|
||||||
|
|
||||||
|
expect(transactionHash).toMatch(/^[0-9A-F]{64}$/);
|
||||||
|
|
||||||
|
await sleep(simapp.blockTime * 1.5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("sign", () => {
|
describe("sign", () => {
|
||||||
describe("direct mode", () => {
|
describe("direct mode", () => {
|
||||||
it("works", async () => {
|
it("works", async () => {
|
||||||
|
@ -326,6 +326,32 @@ export class SigningStargateClient extends StargateClient {
|
|||||||
return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs);
|
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 signAndBroadcastSync(
|
||||||
|
signerAddress: string,
|
||||||
|
messages: readonly EncodeObject[],
|
||||||
|
fee: StdFee | "auto" | number,
|
||||||
|
memo = "",
|
||||||
|
): Promise<string> {
|
||||||
|
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.broadcastTxSync(txBytes);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets account number and sequence from the API, creates a sign doc,
|
* Gets account number and sequence from the API, creates a sign doc,
|
||||||
* creates a single signature and assembles the signed transaction.
|
* creates a single signature and assembles the signed transaction.
|
||||||
|
@ -554,4 +554,68 @@ describe("StargateClient", () => {
|
|||||||
client.disconnect();
|
client.disconnect();
|
||||||
}, 30_000);
|
}, 30_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("broadcastTxSync", () => {
|
||||||
|
it("broadcasts sync a transaction, to get transaction hash", async () => {
|
||||||
|
pendingWithoutSimapp();
|
||||||
|
const client = await StargateClient.connect(simapp.tendermintUrl);
|
||||||
|
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic);
|
||||||
|
const [{ address, pubkey: pubkeyBytes }] = await wallet.getAccounts();
|
||||||
|
const pubkey = encodePubkey({
|
||||||
|
type: "tendermint/PubKeySecp256k1",
|
||||||
|
value: toBase64(pubkeyBytes),
|
||||||
|
});
|
||||||
|
const registry = new Registry();
|
||||||
|
const txBodyFields: TxBodyEncodeObject = {
|
||||||
|
typeUrl: "/cosmos.tx.v1beta1.TxBody",
|
||||||
|
value: {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
|
||||||
|
value: {
|
||||||
|
fromAddress: address,
|
||||||
|
toAddress: makeRandomAddress(),
|
||||||
|
amount: [
|
||||||
|
{
|
||||||
|
denom: "ucosm",
|
||||||
|
amount: "1234567",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const txBodyBytes = registry.encode(txBodyFields);
|
||||||
|
const { accountNumber, sequence } = (await client.getSequence(address))!;
|
||||||
|
const feeAmount = coins(2000, "ucosm");
|
||||||
|
const gasLimit = 200000;
|
||||||
|
const feeGranter = undefined;
|
||||||
|
const feePayer = undefined;
|
||||||
|
const authInfoBytes = makeAuthInfoBytes(
|
||||||
|
[{ pubkey, sequence }],
|
||||||
|
feeAmount,
|
||||||
|
gasLimit,
|
||||||
|
feeGranter,
|
||||||
|
feePayer,
|
||||||
|
);
|
||||||
|
|
||||||
|
const chainId = await client.getChainId();
|
||||||
|
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||||
|
const { signature } = await wallet.signDirect(address, signDoc);
|
||||||
|
const txRaw = TxRaw.fromPartial({
|
||||||
|
bodyBytes: txBodyBytes,
|
||||||
|
authInfoBytes: authInfoBytes,
|
||||||
|
signatures: [fromBase64(signature.signature)],
|
||||||
|
});
|
||||||
|
const txRawBytes = Uint8Array.from(TxRaw.encode(txRaw).finish());
|
||||||
|
const transactionHash = await client.broadcastTxSync(txRawBytes);
|
||||||
|
|
||||||
|
expect(transactionHash).toMatch(/^[0-9A-F]{64}$/);
|
||||||
|
|
||||||
|
await sleep(simapp.blockTime * 1.5);
|
||||||
|
|
||||||
|
client.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -464,13 +464,8 @@ export class StargateClient {
|
|||||||
: pollForTx(txId);
|
: pollForTx(txId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const broadcasted = await this.forceGetTmClient().broadcastTxSync({ tx });
|
const transactionId = await this.broadcastTxSync(tx);
|
||||||
if (broadcasted.code) {
|
|
||||||
return Promise.reject(
|
|
||||||
new BroadcastTxError(broadcasted.code, broadcasted.codespace ?? "", broadcasted.log),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const transactionId = toHex(broadcasted.hash).toUpperCase();
|
|
||||||
return new Promise((resolve, reject) =>
|
return new Promise((resolve, reject) =>
|
||||||
pollForTx(transactionId).then(
|
pollForTx(transactionId).then(
|
||||||
(value) => {
|
(value) => {
|
||||||
@ -485,6 +480,31 @@ 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 broadcastTxSync(tx: Uint8Array): Promise<string> {
|
||||||
|
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<IndexedTx[]> {
|
private async txsQuery(query: string): Promise<IndexedTx[]> {
|
||||||
const results = await this.forceGetTmClient().txSearchAll({ query: query });
|
const results = await this.forceGetTmClient().txSearchAll({ query: query });
|
||||||
return results.txs.map((tx): IndexedTx => {
|
return results.txs.map((tx): IndexedTx => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user