Merge pull request #102 from confio/search-min-max-height

Implement liveTx and min/max height search filters
This commit is contained in:
Simon Warta 2020-02-18 17:32:30 +01:00 committed by GitHub
commit 2cbb6fed02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 552 additions and 69 deletions

View File

@ -5,6 +5,7 @@ import {
Algorithm,
Amount,
ChainId,
ConfirmedTransaction,
isBlockInfoPending,
isBlockInfoSucceeded,
isConfirmedTransaction,
@ -14,6 +15,7 @@ import {
TokenTicker,
TransactionId,
TransactionState,
UnsignedTransaction,
} from "@iov/bcp";
import { Random, Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto";
import { Bech32, Encoding } from "@iov/encoding";
@ -496,7 +498,7 @@ describe("CosmWasmConnection", () => {
});
});
describe("integration tests", () => {
describe("searchTx", () => {
it("can post and search for a transaction", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
@ -587,6 +589,399 @@ describe("CosmWasmConnection", () => {
connection.disconnect();
});
it("can search by minHeight and maxHeight", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucet.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, faucet.path);
const senderAddress = connection.codec.identityToAddress(sender);
const recipient = makeRandomAddress();
const unsigned = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
sender: senderAddress,
recipient: recipient,
memo: "My first payment",
amount: {
quantity: "75000",
fractionalDigits: 6,
tokenTicker: cosm,
},
});
const nonce = await connection.getNonce({ address: senderAddress });
const signed = await profile.signTransaction(sender, unsigned, connection.codec, nonce);
const postableBytes = connection.codec.bytesToPost(signed);
const response = await connection.postTx(postableBytes);
const { transactionId } = response;
const blockInfo = await response.blockInfo.waitFor(info => !isBlockInfoPending(info));
assert(isBlockInfoSucceeded(blockInfo));
const { height } = blockInfo;
// search by ID
{
const results = await connection.searchTx({ id: transactionId });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ id: transactionId, minHeight: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ id: transactionId, minHeight: height - 2 });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ id: transactionId, maxHeight: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ id: transactionId, maxHeight: height + 2 });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({
id: transactionId,
minHeight: height,
maxHeight: height,
});
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ id: transactionId, minHeight: height + 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ id: transactionId, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
id: transactionId,
minHeight: height + 1,
maxHeight: Number.MAX_SAFE_INTEGER,
});
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ id: transactionId, minHeight: 0, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
// search by recipient
{
const results = await connection.searchTx({ sentFromOrTo: recipient });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, minHeight: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, minHeight: height - 2 });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, maxHeight: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, maxHeight: height + 2 });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, minHeight: height + 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
sentFromOrTo: recipient,
minHeight: height,
maxHeight: height,
});
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, minHeight: height + 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
sentFromOrTo: recipient,
minHeight: height + 1,
maxHeight: Number.MAX_SAFE_INTEGER,
});
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
sentFromOrTo: recipient,
minHeight: 0,
maxHeight: height - 1,
});
expect(results.length).toEqual(0);
}
// search by height
{
const results = await connection.searchTx({ height: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ height: height, minHeight: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ height: height, minHeight: height - 2 });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ height: height, maxHeight: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ height: height, maxHeight: height + 2 });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ height: height, minHeight: height + 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ height: height, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
height: height,
minHeight: height,
maxHeight: height,
});
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ height: height, minHeight: height + 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ height: height, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
height: height,
minHeight: height + 1,
maxHeight: Number.MAX_SAFE_INTEGER,
});
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
height: height,
minHeight: 0,
maxHeight: height - 1,
});
expect(results.length).toEqual(0);
}
connection.disconnect();
});
});
describe("liveTx", () => {
it("can listen to transactions by recipient address (transactions in history and updates)", done => {
pendingWithoutWasmd();
(async () => {
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucet.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, faucet.path);
// send transactions
const recipientAddress = makeRandomAddress();
const sendA = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: connection.codec.identityToAddress(sender),
recipient: recipientAddress,
amount: defaultAmount,
memo: `liveTx() test A ${Math.random()}`,
});
const sendB = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: connection.codec.identityToAddress(sender),
recipient: recipientAddress,
amount: defaultAmount,
memo: `liveTx() test B ${Math.random()}`,
});
const sendC = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: connection.codec.identityToAddress(sender),
recipient: recipientAddress,
amount: defaultAmount,
memo: `liveTx() test C ${Math.random()}`,
});
const [nonceA, nonceB, nonceC] = await connection.getNonces({ pubkey: sender.pubkey }, 3);
const signedA = await profile.signTransaction(sender, sendA, connection.codec, nonceA);
const signedB = await profile.signTransaction(sender, sendB, connection.codec, nonceB);
const signedC = await profile.signTransaction(sender, sendC, connection.codec, nonceC);
const bytesToPostA = connection.codec.bytesToPost(signedA);
const bytesToPostB = connection.codec.bytesToPost(signedB);
const bytesToPostC = connection.codec.bytesToPost(signedC);
// Post A and B. Unfortunately the REST server API does not support sending them in parallel because the sequence check fails.
const postResultA = await connection.postTx(bytesToPostA);
await postResultA.blockInfo.waitFor(info => !isBlockInfoPending(info));
const postResultB = await connection.postTx(bytesToPostB);
await postResultB.blockInfo.waitFor(info => !isBlockInfoPending(info));
// setup listener after A and B are in block
const events = new Array<ConfirmedTransaction<UnsignedTransaction>>();
const subscription = connection.liveTx({ sentFromOrTo: recipientAddress }).subscribe({
next: event => {
assert(isConfirmedTransaction(event), "Confirmed transaction expected");
events.push(event);
assert(isSendTransaction(event.transaction), "Unexpected transaction type");
expect(event.transaction.recipient).toEqual(recipientAddress);
if (events.length === 3) {
expect(events[1].height).toEqual(events[0].height + 1);
expect(events[2].height).toBeGreaterThan(events[1].height);
subscription.unsubscribe();
connection.disconnect();
done();
}
},
});
// Post C
await connection.postTx(bytesToPostC);
})().catch(done.fail);
});
it("can listen to transactions by ID (transaction in history)", done => {
pendingWithoutWasmd();
(async () => {
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucet.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, faucet.path);
const recipientAddress = makeRandomAddress();
const send = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: connection.codec.identityToAddress(sender),
recipient: recipientAddress,
amount: defaultAmount,
memo: `liveTx() test ${Math.random()}`,
});
const nonce = await connection.getNonce({ pubkey: sender.pubkey });
const signed = await profile.signTransaction(sender, send, connection.codec, nonce);
const bytesToPost = connection.codec.bytesToPost(signed);
const postResult = await connection.postTx(bytesToPost);
const transactionId = postResult.transactionId;
// Wait for a block
await postResult.blockInfo.waitFor(info => !isBlockInfoPending(info));
// setup listener after transaction is in block
const events = new Array<ConfirmedTransaction<UnsignedTransaction>>();
const subscription = connection.liveTx({ id: transactionId }).subscribe({
next: event => {
assert(isConfirmedTransaction(event), "Confirmed transaction expected");
events.push(event);
assert(isSendTransaction(event.transaction), "Unexpected transaction type");
expect(event.transaction.recipient).toEqual(recipientAddress);
expect(event.transactionId).toEqual(transactionId);
subscription.unsubscribe();
connection.disconnect();
done();
},
});
})().catch(done.fail);
});
it("can listen to transactions by ID (transaction in updates)", done => {
pendingWithoutWasmd();
(async () => {
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucet.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, faucet.path);
// send transactions
const recipientAddress = makeRandomAddress();
const send = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: connection.codec.identityToAddress(sender),
recipient: recipientAddress,
amount: defaultAmount,
memo: `liveTx() test ${Math.random()}`,
});
const nonce = await connection.getNonce({ pubkey: sender.pubkey });
const signed = await profile.signTransaction(sender, send, connection.codec, nonce);
const bytesToPost = connection.codec.bytesToPost(signed);
const postResult = await connection.postTx(bytesToPost);
const transactionId = postResult.transactionId;
// setup listener before transaction is in block
const events = new Array<ConfirmedTransaction<UnsignedTransaction>>();
const subscription = connection.liveTx({ id: transactionId }).subscribe({
next: event => {
assert(isConfirmedTransaction(event), "Confirmed transaction expected");
events.push(event);
assert(isSendTransaction(event.transaction), "Unexpected transaction type");
expect(event.transaction.recipient).toEqual(recipientAddress);
expect(event.transactionId).toEqual(transactionId);
subscription.unsubscribe();
connection.disconnect();
done();
},
});
})().catch(done.fail);
});
});
describe("integration tests", () => {
it("can send ERC20 tokens", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);

View File

@ -37,7 +37,7 @@ import {
UnsignedTransaction,
} from "@iov/bcp";
import { Encoding, Uint53 } from "@iov/encoding";
import { DefaultValueProducer, ValueAndUpdates } from "@iov/stream";
import { concat, DefaultValueProducer, ValueAndUpdates } from "@iov/stream";
import BN from "bn.js";
import equal from "fast-deep-equal";
import { ReadonlyDate } from "readonly-date";
@ -304,8 +304,8 @@ export class CosmWasmConnection implements BlockchainConnection {
public async searchTx({
height,
id,
maxHeight,
minHeight,
maxHeight: maxHeightOptional,
minHeight: minHeightOptional,
sentFromOrTo,
signedBy,
tags,
@ -314,30 +314,38 @@ export class CosmWasmConnection implements BlockchainConnection {
throw new Error("Transaction query by signedBy or tags not yet supported");
}
if ([maxHeight, minHeight].some(isDefined)) {
throw new Error(
"Transaction query by minHeight/maxHeight not yet supported. This is due to missing flexibility of the Gaia REST API, see https://github.com/cosmos/gaia/issues/75",
);
}
if ([id, height, sentFromOrTo].filter(isDefined).length !== 1) {
throw new Error(
"Transaction query by id, height and sentFromOrTo is mutually exclusive. Exactly one must be set.",
);
}
const minHeight = minHeightOptional || 0;
const maxHeight = maxHeightOptional || Number.MAX_SAFE_INTEGER;
if (maxHeight < minHeight) return []; // optional optimization
let txs: readonly TxsResponse[];
if (id) {
txs = await this.cosmWasmClient.searchTx({ id: id });
} else if (height) {
if (height < minHeight) return []; // optional optimization
if (height > maxHeight) return []; // optional optimization
txs = await this.cosmWasmClient.searchTx({ height: height });
} else if (sentFromOrTo) {
// TODO: pass minHeight/maxHeight to server once we have
// https://github.com/cosmwasm/wasmd/issues/73
txs = await this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo });
} else {
throw new Error("Unsupported query");
}
return txs.map(tx => this.parseAndPopulateTxResponseUnsigned(tx));
const filtered = txs.filter(tx => {
const txHeight = parseInt(tx.height, 10);
return txHeight >= minHeight && txHeight <= maxHeight;
});
return filtered.map(tx => this.parseAndPopulateTxResponseUnsigned(tx));
}
public listenTx(
@ -347,9 +355,55 @@ export class CosmWasmConnection implements BlockchainConnection {
}
public liveTx(
_query: TransactionQuery,
query: TransactionQuery,
): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction> {
throw new Error("not implemented");
if ([query.height, query.signedBy, query.tags].some(isDefined)) {
throw new Error("Transaction query by height, signedBy or tags not yet supported");
}
if (query.id) {
if (query.minHeight || query.maxHeight) {
throw new Error("Query by minHeight/maxHeight not supported together with ID");
}
// concat never() because we want non-completing streams consistently
return concat(this.waitForTransaction(query.id), Stream.never());
} else if (query.sentFromOrTo) {
let pollInternal: NodeJS.Timeout | undefined;
const producer: Producer<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction> = {
start: async listener => {
let minHeight = query.minHeight || 0;
const maxHeight = query.maxHeight || Number.MAX_SAFE_INTEGER;
const poll = async (): Promise<void> => {
const result = await this.searchTx({
sentFromOrTo: query.sentFromOrTo,
minHeight: minHeight,
maxHeight: maxHeight,
});
for (const item of result) {
listener.next(item);
if (item.height >= minHeight) {
// we assume we got all matching transactions from block `item.height` now
minHeight = item.height + 1;
}
}
};
await poll();
pollInternal = setInterval(poll, defaultPollInterval);
},
stop: () => {
if (pollInternal) {
clearInterval(pollInternal);
pollInternal = undefined;
}
},
};
return Stream.create(producer);
} else {
throw new Error("Unsupported query.");
}
}
public async getFeeQuote(tx: UnsignedTransaction): Promise<Fee> {
@ -425,4 +479,43 @@ export class CosmWasmConnection implements BlockchainConnection {
this.erc20Tokens,
);
}
private waitForTransaction(
id: TransactionId,
): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction> {
let pollInternal: NodeJS.Timeout | undefined;
const producer: Producer<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction> = {
start: listener => {
setInterval(async () => {
try {
const results = await this.searchTx({ id: id });
switch (results.length) {
case 0:
// okay, we'll try again
break;
case 1:
listener.next(results[0]);
listener.complete();
break;
default:
throw new Error(`Got unexpected number of search results: ${results.length}`);
}
} catch (error) {
if (pollInternal) {
clearTimeout(pollInternal);
pollInternal = undefined;
}
listener.error(error);
}
}, defaultPollInterval);
},
stop: () => {
if (pollInternal) {
clearTimeout(pollInternal);
pollInternal = undefined;
}
},
};
return Stream.create(producer);
}
}

View File

@ -230,7 +230,8 @@ describe("encode", () => {
);
});
it("builds a send transaction without fee", () => {
it("throws for a send transaction without fee", () => {
// This will be rejected by the REST server. Better throw early to avoid hard to debug errors.
const tx = {
kind: "bcp/send",
chainId: defaultChainId,
@ -239,32 +240,7 @@ describe("encode", () => {
recipient: defaultRecipient,
memo: defaultMemo,
};
expect(buildUnsignedTx(tx, defaultTokens)).toEqual({
type: "cosmos-sdk/StdTx",
value: {
msg: [
{
type: "cosmos-sdk/MsgSend",
value: {
from_address: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r",
to_address: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e",
amount: [
{
denom: "uatom",
amount: "11657995",
},
],
},
},
],
signatures: [],
memo: defaultMemo,
fee: {
amount: [],
gas: "",
},
},
});
expect(() => buildUnsignedTx(tx, defaultTokens)).toThrowError(/transaction fee must be set/i);
});
it("builds a send transaction with fee", () => {

View File

@ -91,6 +91,8 @@ export function buildUnsignedTx(
const matchingBankToken = bankTokens.find(t => t.ticker === tx.amount.tokenTicker);
const matchingErc20Token = erc20Tokens.find(t => t.ticker === tx.amount.tokenTicker);
if (!tx.fee) throw new Error("Transaction fee must be set");
if (matchingBankToken) {
return {
type: "cosmos-sdk/StdTx",
@ -107,12 +109,7 @@ export function buildUnsignedTx(
],
memo: tx.memo || "",
signatures: [],
fee: tx.fee
? encodeFee(tx.fee, bankTokens)
: {
amount: [],
gas: "",
},
fee: encodeFee(tx.fee, bankTokens),
},
};
} else if (matchingErc20Token) {
@ -137,12 +134,7 @@ export function buildUnsignedTx(
],
memo: tx.memo || "",
signatures: [],
fee: tx.fee
? encodeFee(tx.fee, bankTokens)
: {
amount: [],
gas: "",
},
fee: encodeFee(tx.fee, bankTokens),
},
};
} else {

View File

@ -74,16 +74,17 @@ export declare class CosmWasmConnection implements BlockchainConnection {
searchTx({
height,
id,
maxHeight,
minHeight,
maxHeight: maxHeightOptional,
minHeight: minHeightOptional,
sentFromOrTo,
signedBy,
tags,
}: TransactionQuery): Promise<readonly (ConfirmedTransaction<UnsignedTransaction> | FailedTransaction)[]>;
listenTx(_query: TransactionQuery): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction>;
liveTx(_query: TransactionQuery): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction>;
liveTx(query: TransactionQuery): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction>;
getFeeQuote(tx: UnsignedTransaction): Promise<Fee>;
withDefaultFee<T extends UnsignedTransaction>(tx: T): Promise<T>;
private parseAndPopulateTxResponseUnsigned;
private parseAndPopulateTxResponseSigned;
private waitForTransaction;
}

View File

@ -2,7 +2,7 @@ import { Sha256 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { Log, parseLogs } from "./logs";
import { BlockResponse, RestClient, TxsResponse } from "./restclient";
import { BlockResponse, BroadcastMode, RestClient, TxsResponse } from "./restclient";
import { CosmosSdkAccount, CosmosSdkTx } from "./types";
export interface GetNonceResult {
@ -46,8 +46,8 @@ function isSearchBySentFromOrToQuery(query: SearchTxQuery): query is SearchBySen
export class CosmWasmClient {
protected readonly restClient: RestClient;
public constructor(url: string) {
this.restClient = new RestClient(url);
public constructor(url: string, broadcastMode = BroadcastMode.Block) {
this.restClient = new RestClient(url, broadcastMode);
}
public async chainId(): Promise<string> {
@ -136,7 +136,7 @@ export class CosmWasmClient {
}
return {
logs: parseLogs(result.logs) || [],
logs: result.logs ? parseLogs(result.logs) : [],
rawLog: result.raw_log || "",
transactionHash: result.txhash,
};

View File

@ -5,7 +5,7 @@ export { logs, types };
export { CosmosAddressBech32Prefix, encodeAddress, isValidAddress } from "./address";
export { unmarshalTx } from "./decoding";
export { makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { BroadcastMode, RestClient, TxsResponse } from "./restclient";
export {
CosmWasmClient,
GetNonceResult,

View File

@ -152,7 +152,19 @@ type RestClientResponse =
| WasmResponse<string>
| WasmResponse<GetCodeResult>;
type BroadcastMode = "block" | "sync" | "async";
/**
* The mode used to send transaction
*
* @see https://cosmos.network/rpc/#/Transactions/post_txs
*/
export enum BroadcastMode {
/** Return after tx commit */
Block = "block",
/** Return afer CheckTx */
Sync = "sync",
/** Return right away */
Async = "async",
}
function isWasmError<T>(resp: WasmResponse<T>): resp is WasmError {
return (resp as WasmError).error !== undefined;
@ -194,11 +206,9 @@ function parseAxios500error(err: AxiosError): never {
export class RestClient {
private readonly client: AxiosInstance;
// From https://cosmos.network/rpc/#/ICS0/post_txs
// The supported broadcast modes include "block"(return after tx commit), "sync"(return afer CheckTx) and "async"(return right away).
private readonly mode: BroadcastMode;
public constructor(url: string, mode: BroadcastMode = "block") {
public constructor(url: string, mode = BroadcastMode.Block) {
const headers = {
post: { "Content-Type": "application/json" },
};

View File

@ -3,6 +3,7 @@ import { Encoding } from "@iov/encoding";
import { CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient";
import { makeSignBytes, marshalTx } from "./encoding";
import { findAttribute, Log } from "./logs";
import { BroadcastMode } from "./restclient";
import {
Coin,
CosmosSdkAccount,
@ -63,8 +64,9 @@ export class SigningCosmWasmClient extends CosmWasmClient {
senderAddress: string,
signCallback: SigningCallback,
customFees?: Partial<FeeTable>,
broadcastMode = BroadcastMode.Block,
) {
super(url);
super(url, broadcastMode);
this.senderAddress = senderAddress;
this.signCallback = signCallback;
this.fees = { ...defaultFees, ...(customFees || {}) };

View File

@ -1,5 +1,5 @@
import { Log } from "./logs";
import { BlockResponse, RestClient, TxsResponse } from "./restclient";
import { BlockResponse, BroadcastMode, RestClient, TxsResponse } from "./restclient";
import { CosmosSdkAccount, CosmosSdkTx } from "./types";
export interface GetNonceResult {
readonly accountNumber: number;
@ -23,7 +23,7 @@ export interface SearchBySentFromOrToQuery {
export declare type SearchTxQuery = SearchByIdQuery | SearchByHeightQuery | SearchBySentFromOrToQuery;
export declare class CosmWasmClient {
protected readonly restClient: RestClient;
constructor(url: string);
constructor(url: string, broadcastMode?: BroadcastMode);
chainId(): Promise<string>;
/**
* Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID)

View File

@ -4,7 +4,7 @@ export { logs, types };
export { CosmosAddressBech32Prefix, encodeAddress, isValidAddress } from "./address";
export { unmarshalTx } from "./decoding";
export { makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { BroadcastMode, RestClient, TxsResponse } from "./restclient";
export {
CosmWasmClient,
GetNonceResult,

View File

@ -105,7 +105,19 @@ declare type RestClientResponse =
| EncodeTxResponse
| WasmResponse<string>
| WasmResponse<GetCodeResult>;
declare type BroadcastMode = "block" | "sync" | "async";
/**
* The mode used to send transaction
*
* @see https://cosmos.network/rpc/#/Transactions/post_txs
*/
export declare enum BroadcastMode {
/** Return after tx commit */
Block = "block",
/** Return afer CheckTx */
Sync = "sync",
/** Return right away */
Async = "async",
}
export declare class RestClient {
private readonly client;
private readonly mode;

View File

@ -1,5 +1,6 @@
import { CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient";
import { Log } from "./logs";
import { BroadcastMode } from "./restclient";
import { Coin, CosmosSdkAccount, StdFee, StdSignature } from "./types";
export interface SigningCallback {
(signBytes: Uint8Array): Promise<StdSignature>;
@ -22,6 +23,7 @@ export declare class SigningCosmWasmClient extends CosmWasmClient {
senderAddress: string,
signCallback: SigningCallback,
customFees?: Partial<FeeTable>,
broadcastMode?: BroadcastMode,
);
getNonce(address?: string): Promise<GetNonceResult>;
getAccount(address?: string): Promise<CosmosSdkAccount | undefined>;