mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-10 13:47:12 +00:00
Let StargateClient build on Client
This commit is contained in:
parent
dfc2317817
commit
f58530f449
@ -11,26 +11,175 @@ import {
|
||||
Registry,
|
||||
TxBodyEncodeObject,
|
||||
} from "@cosmjs/proto-signing";
|
||||
import { CometClient, connectComet, HttpEndpoint } from "@cosmjs/tendermint-rpc";
|
||||
import { CometClient, connectComet, HttpEndpoint, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc";
|
||||
import { assert, assertDefined, sleep } from "@cosmjs/utils";
|
||||
import { TxMsgData } from "cosmjs-types/cosmos/base/abci/v1beta1/abci";
|
||||
import { MsgData, TxMsgData } from "cosmjs-types/cosmos/base/abci/v1beta1/abci";
|
||||
import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing";
|
||||
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
|
||||
|
||||
import { Account, accountFromAny, AccountParser } from "./accounts";
|
||||
import { AminoTypes } from "./aminotypes";
|
||||
import { fromTendermintEvent } from "./events";
|
||||
import { Event, fromTendermintEvent } from "./events";
|
||||
import { calculateFee, GasPrice } from "./fee";
|
||||
import { AuthExtension, setupAuthExtension, setupTxExtension, TxExtension } from "./modules";
|
||||
import { QueryClient } from "./queryclient";
|
||||
import { SearchTxQuery } from "./search";
|
||||
import {
|
||||
BroadcastTxError,
|
||||
DeliverTxResponse,
|
||||
IndexedTx,
|
||||
SequenceResponse,
|
||||
TimeoutError,
|
||||
} from "./stargateclient";
|
||||
|
||||
export interface BlockHeader {
|
||||
readonly version: {
|
||||
readonly block: string;
|
||||
readonly app: string;
|
||||
};
|
||||
readonly height: number;
|
||||
readonly chainId: string;
|
||||
/** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */
|
||||
readonly time: string;
|
||||
}
|
||||
|
||||
export interface Block {
|
||||
/** The ID is a hash of the block header (uppercase hex) */
|
||||
readonly id: string;
|
||||
readonly header: BlockHeader;
|
||||
/** Array of raw transactions */
|
||||
readonly txs: readonly Uint8Array[];
|
||||
}
|
||||
|
||||
export interface SequenceResponse {
|
||||
readonly accountNumber: number;
|
||||
readonly sequence: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The response after successfully broadcasting a transaction.
|
||||
* Success or failure refer to the execution result.
|
||||
*/
|
||||
export interface DeliverTxResponse {
|
||||
readonly height: number;
|
||||
/** The position of the transaction within the block. This is a 0-based index. */
|
||||
readonly txIndex: number;
|
||||
/** Error code. The transaction suceeded iff code is 0. */
|
||||
readonly code: number;
|
||||
readonly transactionHash: string;
|
||||
readonly events: readonly Event[];
|
||||
/**
|
||||
* A string-based log document.
|
||||
*
|
||||
* This currently seems to merge attributes of multiple events into one event per type
|
||||
* (https://github.com/tendermint/tendermint/issues/9595). You might want to use the `events`
|
||||
* field instead.
|
||||
*/
|
||||
readonly rawLog?: string;
|
||||
/** @deprecated Use `msgResponses` instead. */
|
||||
readonly data?: readonly MsgData[];
|
||||
/**
|
||||
* The message responses of the [TxMsgData](https://github.com/cosmos/cosmos-sdk/blob/v0.46.3/proto/cosmos/base/abci/v1beta1/abci.proto#L128-L140)
|
||||
* as `Any`s.
|
||||
* This field is an empty list for chains running Cosmos SDK < 0.46.
|
||||
*/
|
||||
readonly msgResponses: Array<{ readonly typeUrl: string; readonly value: Uint8Array }>;
|
||||
readonly gasUsed: number;
|
||||
readonly gasWanted: number;
|
||||
}
|
||||
|
||||
export function isDeliverTxFailure(result: DeliverTxResponse): boolean {
|
||||
return !!result.code;
|
||||
}
|
||||
|
||||
export function isDeliverTxSuccess(result: DeliverTxResponse): boolean {
|
||||
return !isDeliverTxFailure(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the given result is a success. Throws a detailed error message otherwise.
|
||||
*/
|
||||
export function assertIsDeliverTxSuccess(result: DeliverTxResponse): void {
|
||||
if (isDeliverTxFailure(result)) {
|
||||
throw new Error(
|
||||
`Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the given result is a failure. Throws a detailed error message otherwise.
|
||||
*/
|
||||
export function assertIsDeliverTxFailure(result: DeliverTxResponse): void {
|
||||
if (isDeliverTxSuccess(result)) {
|
||||
throw new Error(
|
||||
`Transaction ${result.transactionHash} did not fail at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** A transaction that is indexed as part of the transaction history */
|
||||
export interface IndexedTx {
|
||||
readonly height: number;
|
||||
/** The position of the transaction within the block. This is a 0-based index. */
|
||||
readonly txIndex: number;
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly hash: string;
|
||||
/** Transaction execution error code. 0 on success. */
|
||||
readonly code: number;
|
||||
readonly events: readonly Event[];
|
||||
/**
|
||||
* A string-based log document.
|
||||
*
|
||||
* This currently seems to merge attributes of multiple events into one event per type
|
||||
* (https://github.com/tendermint/tendermint/issues/9595). You might want to use the `events`
|
||||
* field instead.
|
||||
*/
|
||||
readonly rawLog: string;
|
||||
/**
|
||||
* Raw transaction bytes stored in Tendermint.
|
||||
*
|
||||
* If you hash this, you get the transaction hash (= transaction ID):
|
||||
*
|
||||
* ```js
|
||||
* import { sha256 } from "@cosmjs/crypto";
|
||||
* import { toHex } from "@cosmjs/encoding";
|
||||
*
|
||||
* const transactionId = toHex(sha256(indexTx.tx)).toUpperCase();
|
||||
* ```
|
||||
*
|
||||
* Use `decodeTxRaw` from @cosmjs/proto-signing to decode this.
|
||||
*/
|
||||
readonly tx: Uint8Array;
|
||||
/**
|
||||
* The message responses of the [TxMsgData](https://github.com/cosmos/cosmos-sdk/blob/v0.46.3/proto/cosmos/base/abci/v1beta1/abci.proto#L128-L140)
|
||||
* as `Any`s.
|
||||
* This field is an empty list for chains running Cosmos SDK < 0.46.
|
||||
*/
|
||||
readonly msgResponses: Array<{ readonly typeUrl: string; readonly value: Uint8Array }>;
|
||||
readonly gasUsed: number;
|
||||
readonly gasWanted: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* An error when broadcasting the transaction. This contains the CheckTx errors
|
||||
* from the blockchain. Once a transaction is included in a block no BroadcastTxError
|
||||
* is thrown, even if the execution fails (DeliverTx errors).
|
||||
*/
|
||||
export class BroadcastTxError extends Error {
|
||||
public readonly code: number;
|
||||
public readonly codespace: string;
|
||||
public readonly log: string | undefined;
|
||||
|
||||
public constructor(code: number, codespace: string, log: string | undefined) {
|
||||
super(`Broadcasting transaction failed with code ${code} (codespace: ${codespace}). Log: ${log}`);
|
||||
this.code = code;
|
||||
this.codespace = codespace;
|
||||
this.log = log;
|
||||
}
|
||||
}
|
||||
|
||||
export class TimeoutError extends Error {
|
||||
public readonly txId: string;
|
||||
|
||||
public constructor(message: string, txId: string) {
|
||||
super(message);
|
||||
this.txId = txId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signing information for a single signer that is not included in the transaction.
|
||||
@ -67,7 +216,7 @@ export class Client {
|
||||
/** Chain ID cache */
|
||||
private chainId: string | undefined;
|
||||
private readonly accountParser: AccountParser;
|
||||
private readonly signer: OfflineSigner;
|
||||
private readonly signer: OfflineSigner | undefined;
|
||||
private readonly aminoTypes: AminoTypes;
|
||||
private readonly gasPrice: GasPrice | undefined;
|
||||
|
||||
@ -87,7 +236,7 @@ export class Client {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance from a manually created Tendermint client.
|
||||
* Creates an instance from a manually created Comet client.
|
||||
* Use this to use `Tendermint37Client` instead of `Tendermint34Client`.
|
||||
*/
|
||||
public static async createWithSigner(
|
||||
@ -98,6 +247,13 @@ export class Client {
|
||||
return new Client(cometClient, signer, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance without signer from a manually created Comet client.
|
||||
*/
|
||||
public static async create(cometClient: CometClient, options: ClientOptions = {}): Promise<Client> {
|
||||
return new Client(cometClient, undefined, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a client in offline mode.
|
||||
*
|
||||
@ -111,14 +267,21 @@ export class Client {
|
||||
return new Client(undefined, signer, options);
|
||||
}
|
||||
|
||||
protected constructor(cometClient: CometClient | undefined, signer: OfflineSigner, options: ClientOptions) {
|
||||
public constructor(
|
||||
cometClient: CometClient | undefined,
|
||||
signer: OfflineSigner | undefined,
|
||||
options: ClientOptions,
|
||||
) {
|
||||
if (cometClient) {
|
||||
this.cometClient = cometClient;
|
||||
this.queryClient = QueryClient.withExtensions(cometClient, setupAuthExtension, setupTxExtension);
|
||||
}
|
||||
const { accountParser = accountFromAny } = options;
|
||||
const {
|
||||
registry = new Registry(),
|
||||
aminoTypes = new AminoTypes({}),
|
||||
accountParser = accountFromAny,
|
||||
} = options;
|
||||
this.accountParser = accountParser;
|
||||
const { registry = new Registry(), aminoTypes = new AminoTypes({}) } = options;
|
||||
this.registry = registry;
|
||||
this.aminoTypes = aminoTypes;
|
||||
this.signer = signer;
|
||||
@ -127,11 +290,11 @@ export class Client {
|
||||
this.gasPrice = options.gasPrice;
|
||||
}
|
||||
|
||||
protected getTmClient(): CometClient | undefined {
|
||||
public getCometClient(): CometClient | undefined {
|
||||
return this.cometClient;
|
||||
}
|
||||
|
||||
protected forceGetTmClient(): CometClient {
|
||||
public forceGetCometClient(): CometClient {
|
||||
if (!this.cometClient) {
|
||||
throw new Error("Comet client not available. You cannot use online functionality in offline mode.");
|
||||
}
|
||||
@ -174,12 +337,33 @@ export class Client {
|
||||
};
|
||||
}
|
||||
|
||||
public async getBlock(height?: number): Promise<Block> {
|
||||
const response = await this.forceGetCometClient().block(height);
|
||||
return {
|
||||
id: toHex(response.blockId.hash).toUpperCase(),
|
||||
header: {
|
||||
version: {
|
||||
block: new Uint53(response.block.header.version.block).toString(),
|
||||
app: new Uint53(response.block.header.version.app).toString(),
|
||||
},
|
||||
height: response.block.header.height,
|
||||
chainId: response.block.header.chainId,
|
||||
time: toRfc3339WithNanoseconds(response.block.header.time),
|
||||
},
|
||||
txs: response.block.txs,
|
||||
};
|
||||
}
|
||||
|
||||
public async simulate(
|
||||
signerAddress: string,
|
||||
messages: readonly EncodeObject[],
|
||||
memo: string | undefined,
|
||||
): Promise<number> {
|
||||
const anyMsgs = messages.map((m) => this.registry.encodeAsAny(m));
|
||||
assert(
|
||||
this.signer,
|
||||
"Simulation requires a signer. FIXME: create workaround for this limitation (https://github.com/cosmos/cosmjs/issues/1213).",
|
||||
);
|
||||
const accountFromSigner = (await this.signer.getAccounts()).find(
|
||||
(account) => account.address === signerAddress,
|
||||
);
|
||||
@ -195,7 +379,7 @@ export class Client {
|
||||
|
||||
public async getChainId(): Promise<string> {
|
||||
if (!this.chainId) {
|
||||
const response = await this.forceGetTmClient().status();
|
||||
const response = await this.forceGetCometClient().status();
|
||||
const chainId = response.nodeInfo.network;
|
||||
if (!chainId) throw new Error("Chain ID must not be empty");
|
||||
this.chainId = chainId;
|
||||
@ -269,6 +453,7 @@ export class Client {
|
||||
memo: string,
|
||||
explicitSignerData?: SignerData,
|
||||
): Promise<TxRaw> {
|
||||
assert(this.signer, "No signer set for this client instance");
|
||||
let signerData: SignerData;
|
||||
if (explicitSignerData) {
|
||||
signerData = explicitSignerData;
|
||||
@ -281,7 +466,6 @@ export class Client {
|
||||
chainId: chainId,
|
||||
};
|
||||
}
|
||||
|
||||
return isOfflineDirectSigner(this.signer)
|
||||
? this.signDirect(signerAddress, messages, fee, memo, signerData)
|
||||
: this.signAmino(signerAddress, messages, fee, memo, signerData);
|
||||
@ -294,6 +478,7 @@ export class Client {
|
||||
memo: string,
|
||||
{ accountNumber, sequence, chainId }: SignerData,
|
||||
): Promise<TxRaw> {
|
||||
assert(this.signer, "No signer set for this client instance");
|
||||
assert(!isOfflineDirectSigner(this.signer));
|
||||
const accountFromSigner = (await this.signer.getAccounts()).find(
|
||||
(account) => account.address === signerAddress,
|
||||
@ -339,6 +524,7 @@ export class Client {
|
||||
memo: string,
|
||||
{ accountNumber, sequence, chainId }: SignerData,
|
||||
): Promise<TxRaw> {
|
||||
assert(this.signer, "No signer set for this client instance");
|
||||
assert(isOfflineDirectSigner(this.signer));
|
||||
const accountFromSigner = (await this.signer.getAccounts()).find(
|
||||
(account) => account.address === signerAddress,
|
||||
@ -468,7 +654,7 @@ export class Client {
|
||||
* @returns Returns the hash of the transaction
|
||||
*/
|
||||
public async broadcastTxSync(tx: Uint8Array): Promise<string> {
|
||||
const broadcasted = await this.forceGetTmClient().broadcastTxSync({ tx });
|
||||
const broadcasted = await this.forceGetCometClient().broadcastTxSync({ tx });
|
||||
|
||||
if (broadcasted.code) {
|
||||
return Promise.reject(
|
||||
@ -481,8 +667,8 @@ export class Client {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
private async txsQuery(query: string): Promise<IndexedTx[]> {
|
||||
const results = await this.forceGetTmClient().txSearchAll({ query: query });
|
||||
public async txsQuery(query: string): Promise<IndexedTx[]> {
|
||||
const results = await this.forceGetCometClient().txSearchAll({ query: query });
|
||||
return results.txs.map((tx): IndexedTx => {
|
||||
const txMsgData = TxMsgData.decode(tx.result.data ?? new Uint8Array());
|
||||
return {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { fromUtf8 } from "@cosmjs/encoding";
|
||||
import { tendermint34, tendermint37 } from "@cosmjs/tendermint-rpc";
|
||||
import { comet38, tendermint34, tendermint37 } from "@cosmjs/tendermint-rpc";
|
||||
|
||||
/**
|
||||
* An event attribute.
|
||||
@ -33,7 +33,7 @@ export interface Event {
|
||||
* Takes a Tendermint 0.34 or 0.37 event with binary encoded key and value
|
||||
* and converts it into an `Event` with string attributes.
|
||||
*/
|
||||
export function fromTendermintEvent(event: tendermint34.Event | tendermint37.Event): Event {
|
||||
export function fromTendermintEvent(event: tendermint34.Event | tendermint37.Event | comet38.Event): Event {
|
||||
return {
|
||||
type: event.type,
|
||||
attributes: event.attributes.map(
|
||||
|
@ -1,5 +1,20 @@
|
||||
export { Account, accountFromAny, AccountParser } from "./accounts";
|
||||
export { AminoConverter, AminoConverters, AminoTypes } from "./aminotypes";
|
||||
export {
|
||||
assertIsDeliverTxFailure,
|
||||
assertIsDeliverTxSuccess,
|
||||
Block,
|
||||
BlockHeader,
|
||||
BroadcastTxError,
|
||||
Client,
|
||||
ClientOptions,
|
||||
DeliverTxResponse,
|
||||
IndexedTx,
|
||||
isDeliverTxFailure,
|
||||
isDeliverTxSuccess,
|
||||
SequenceResponse,
|
||||
TimeoutError,
|
||||
} from "./client";
|
||||
export { Attribute, Event, fromTendermintEvent } from "./events";
|
||||
export { calculateFee, GasPrice } from "./fee";
|
||||
export * as logs from "./logs";
|
||||
@ -121,21 +136,7 @@ export {
|
||||
SigningStargateClient,
|
||||
SigningStargateClientOptions,
|
||||
} from "./signingstargateclient";
|
||||
export {
|
||||
assertIsDeliverTxFailure,
|
||||
assertIsDeliverTxSuccess,
|
||||
Block,
|
||||
BlockHeader,
|
||||
BroadcastTxError,
|
||||
DeliverTxResponse,
|
||||
IndexedTx,
|
||||
isDeliverTxFailure,
|
||||
isDeliverTxSuccess,
|
||||
SequenceResponse,
|
||||
StargateClient,
|
||||
StargateClientOptions,
|
||||
TimeoutError,
|
||||
} from "./stargateclient";
|
||||
export { StargateClient, StargateClientOptions } from "./stargateclient";
|
||||
export { StdFee } from "@cosmjs/amino";
|
||||
export { Coin, coin, coins, makeCosmoshubPath, parseCoins } from "@cosmjs/proto-signing";
|
||||
|
||||
|
@ -4,9 +4,9 @@ import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
|
||||
import { assertDefined, sleep } from "@cosmjs/utils";
|
||||
import { GenericAuthorization } from "cosmjs-types/cosmos/authz/v1beta1/authz";
|
||||
|
||||
import { assertIsDeliverTxSuccess } from "../../client";
|
||||
import { QueryClient } from "../../queryclient";
|
||||
import { SigningStargateClient } from "../../signingstargateclient";
|
||||
import { assertIsDeliverTxSuccess } from "../../stargateclient";
|
||||
import {
|
||||
defaultSigningClientOptions,
|
||||
faucet,
|
||||
|
@ -4,9 +4,9 @@ import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
|
||||
import { sleep } from "@cosmjs/utils";
|
||||
import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx";
|
||||
|
||||
import { assertIsDeliverTxSuccess } from "../../client";
|
||||
import { QueryClient } from "../../queryclient";
|
||||
import { SigningStargateClient } from "../../signingstargateclient";
|
||||
import { assertIsDeliverTxSuccess } from "../../stargateclient";
|
||||
import {
|
||||
defaultSigningClientOptions,
|
||||
faucet,
|
||||
|
@ -4,9 +4,9 @@ import { assert, sleep } from "@cosmjs/utils";
|
||||
import { TextProposal, VoteOption } from "cosmjs-types/cosmos/gov/v1beta1/gov";
|
||||
import { Any } from "cosmjs-types/google/protobuf/any";
|
||||
|
||||
import { assertIsDeliverTxSuccess } from "../../client";
|
||||
import { longify } from "../../queryclient";
|
||||
import { SigningStargateClient } from "../../signingstargateclient";
|
||||
import { assertIsDeliverTxSuccess } from "../../stargateclient";
|
||||
import {
|
||||
defaultSigningClientOptions,
|
||||
faucet,
|
||||
|
@ -13,9 +13,9 @@ import {
|
||||
import { Any } from "cosmjs-types/google/protobuf/any";
|
||||
import Long from "long";
|
||||
|
||||
import { assertIsDeliverTxSuccess } from "../../client";
|
||||
import { longify, QueryClient } from "../../queryclient";
|
||||
import { SigningStargateClient } from "../../signingstargateclient";
|
||||
import { assertIsDeliverTxSuccess } from "../../stargateclient";
|
||||
import {
|
||||
defaultSigningClientOptions,
|
||||
faucet,
|
||||
|
@ -3,9 +3,9 @@ import { Random } from "@cosmjs/crypto";
|
||||
import { fromBech32, toBase64, toBech32 } from "@cosmjs/encoding";
|
||||
import { DirectSecp256k1HdWallet, encodePubkey } from "@cosmjs/proto-signing";
|
||||
|
||||
import { assertIsDeliverTxSuccess } from "../../client";
|
||||
import { calculateFee } from "../../fee";
|
||||
import { SigningStargateClient } from "../../signingstargateclient";
|
||||
import { assertIsDeliverTxSuccess } from "../../stargateclient";
|
||||
import {
|
||||
defaultGasPrice,
|
||||
defaultSigningClientOptions,
|
||||
|
@ -4,9 +4,9 @@ import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
|
||||
import { sleep } from "@cosmjs/utils";
|
||||
import { MsgDelegate, MsgUndelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx";
|
||||
|
||||
import { assertIsDeliverTxSuccess } from "../../client";
|
||||
import { QueryClient } from "../../queryclient";
|
||||
import { SigningStargateClient } from "../../signingstargateclient";
|
||||
import { assertIsDeliverTxSuccess } from "../../stargateclient";
|
||||
import {
|
||||
defaultSigningClientOptions,
|
||||
faucet,
|
||||
|
@ -4,9 +4,10 @@ import { assertDefined, sleep } from "@cosmjs/utils";
|
||||
import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx";
|
||||
import Long from "long";
|
||||
|
||||
import { assertIsDeliverTxSuccess } from "../../client";
|
||||
import { QueryClient } from "../../queryclient";
|
||||
import { defaultRegistryTypes, SigningStargateClient } from "../../signingstargateclient";
|
||||
import { assertIsDeliverTxSuccess, StargateClient } from "../../stargateclient";
|
||||
import { StargateClient } from "../../stargateclient";
|
||||
import {
|
||||
defaultSigningClientOptions,
|
||||
faucet,
|
||||
|
@ -3,8 +3,8 @@ import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
|
||||
import { MsgCreateVestingAccount } from "cosmjs-types/cosmos/vesting/v1beta1/tx";
|
||||
import Long from "long";
|
||||
|
||||
import { assertIsDeliverTxSuccess } from "../../client";
|
||||
import { SigningStargateClient } from "../../signingstargateclient";
|
||||
import { assertIsDeliverTxSuccess } from "../../stargateclient";
|
||||
import {
|
||||
defaultSigningClientOptions,
|
||||
faucet,
|
||||
|
@ -9,10 +9,11 @@ import { coins } from "@cosmjs/proto-signing";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx";
|
||||
|
||||
import { assertIsDeliverTxSuccess } from "./client";
|
||||
import { MsgSendEncodeObject } from "./modules";
|
||||
import { makeCompactBitArray, makeMultisignedTxBytes } from "./multisignature";
|
||||
import { SignerData, SigningStargateClient } from "./signingstargateclient";
|
||||
import { assertIsDeliverTxSuccess, StargateClient } from "./stargateclient";
|
||||
import { StargateClient } from "./stargateclient";
|
||||
import { faucet, pendingWithoutSimapp, simapp } from "./testutils.spec";
|
||||
|
||||
describe("multisignature", () => {
|
||||
|
@ -22,6 +22,7 @@ import Long from "long";
|
||||
import protobuf from "protobufjs/minimal";
|
||||
|
||||
import { AminoTypes } from "./aminotypes";
|
||||
import { assertIsDeliverTxFailure, assertIsDeliverTxSuccess, isDeliverTxFailure } from "./client";
|
||||
import {
|
||||
AminoMsgDelegate,
|
||||
MsgDelegateEncodeObject,
|
||||
@ -34,7 +35,6 @@ import {
|
||||
SigningStargateClient,
|
||||
SigningStargateClientOptions,
|
||||
} from "./signingstargateclient";
|
||||
import { assertIsDeliverTxFailure, assertIsDeliverTxSuccess, isDeliverTxFailure } from "./stargateclient";
|
||||
import {
|
||||
defaultGasPrice,
|
||||
defaultSendFee,
|
||||
|
@ -29,6 +29,7 @@ import { Height } from "cosmjs-types/ibc/core/client/v1/client";
|
||||
import Long from "long";
|
||||
|
||||
import { AminoConverters, AminoTypes } from "./aminotypes";
|
||||
import { DeliverTxResponse } from "./client";
|
||||
import { calculateFee, GasPrice } from "./fee";
|
||||
import {
|
||||
authzTypes,
|
||||
@ -56,7 +57,7 @@ import {
|
||||
createStakingAminoConverters,
|
||||
createVestingAminoConverters,
|
||||
} from "./modules";
|
||||
import { DeliverTxResponse, StargateClient, StargateClientOptions } from "./stargateclient";
|
||||
import { StargateClient, StargateClientOptions } from "./stargateclient";
|
||||
|
||||
export const defaultRegistryTypes: ReadonlyArray<[string, GeneratedType]> = [
|
||||
["/cosmos.base.v1beta1.Coin", Coin],
|
||||
|
@ -14,8 +14,9 @@ import { MsgSendResponse } from "cosmjs-types/cosmos/bank/v1beta1/tx";
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
|
||||
|
||||
import { DeliverTxResponse, isDeliverTxFailure, isDeliverTxSuccess } from "./client";
|
||||
import { isMsgSendEncodeObject } from "./modules";
|
||||
import { DeliverTxResponse, isDeliverTxFailure, isDeliverTxSuccess, StargateClient } from "./stargateclient";
|
||||
import { StargateClient } from "./stargateclient";
|
||||
import {
|
||||
defaultSigningClientOptions,
|
||||
faucet,
|
||||
|
@ -19,10 +19,9 @@ import {
|
||||
DeliverTxResponse,
|
||||
isDeliverTxFailure,
|
||||
isDeliverTxSuccess,
|
||||
PrivateStargateClient,
|
||||
StargateClient,
|
||||
TimeoutError,
|
||||
} from "./stargateclient";
|
||||
} from "./client";
|
||||
import { PrivateStargateClient, StargateClient } from "./stargateclient";
|
||||
import {
|
||||
faucet,
|
||||
makeRandomAddress,
|
||||
|
@ -1,16 +1,13 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { addCoins } from "@cosmjs/amino";
|
||||
import { toHex } from "@cosmjs/encoding";
|
||||
import { Uint53 } from "@cosmjs/math";
|
||||
import { CometClient, connectComet, HttpEndpoint, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc";
|
||||
import { assert, sleep } from "@cosmjs/utils";
|
||||
import { MsgData, TxMsgData } from "cosmjs-types/cosmos/base/abci/v1beta1/abci";
|
||||
import { CometClient, connectComet, HttpEndpoint } from "@cosmjs/tendermint-rpc";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
import { QueryDelegatorDelegationsResponse } from "cosmjs-types/cosmos/staking/v1beta1/query";
|
||||
import { DelegationResponse } from "cosmjs-types/cosmos/staking/v1beta1/staking";
|
||||
|
||||
import { Account, accountFromAny, AccountParser } from "./accounts";
|
||||
import { Event, fromTendermintEvent } from "./events";
|
||||
import { Account, AccountParser } from "./accounts";
|
||||
import { Block, Client, DeliverTxResponse, IndexedTx, SequenceResponse } from "./client";
|
||||
import {
|
||||
AuthExtension,
|
||||
BankExtension,
|
||||
@ -24,162 +21,6 @@ import {
|
||||
import { QueryClient } from "./queryclient";
|
||||
import { SearchTxQuery } from "./search";
|
||||
|
||||
export class TimeoutError extends Error {
|
||||
public readonly txId: string;
|
||||
|
||||
public constructor(message: string, txId: string) {
|
||||
super(message);
|
||||
this.txId = txId;
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockHeader {
|
||||
readonly version: {
|
||||
readonly block: string;
|
||||
readonly app: string;
|
||||
};
|
||||
readonly height: number;
|
||||
readonly chainId: string;
|
||||
/** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */
|
||||
readonly time: string;
|
||||
}
|
||||
|
||||
export interface Block {
|
||||
/** The ID is a hash of the block header (uppercase hex) */
|
||||
readonly id: string;
|
||||
readonly header: BlockHeader;
|
||||
/** Array of raw transactions */
|
||||
readonly txs: readonly Uint8Array[];
|
||||
}
|
||||
|
||||
/** A transaction that is indexed as part of the transaction history */
|
||||
export interface IndexedTx {
|
||||
readonly height: number;
|
||||
/** The position of the transaction within the block. This is a 0-based index. */
|
||||
readonly txIndex: number;
|
||||
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
|
||||
readonly hash: string;
|
||||
/** Transaction execution error code. 0 on success. */
|
||||
readonly code: number;
|
||||
readonly events: readonly Event[];
|
||||
/**
|
||||
* A string-based log document.
|
||||
*
|
||||
* This currently seems to merge attributes of multiple events into one event per type
|
||||
* (https://github.com/tendermint/tendermint/issues/9595). You might want to use the `events`
|
||||
* field instead.
|
||||
*/
|
||||
readonly rawLog: string;
|
||||
/**
|
||||
* Raw transaction bytes stored in Tendermint.
|
||||
*
|
||||
* If you hash this, you get the transaction hash (= transaction ID):
|
||||
*
|
||||
* ```js
|
||||
* import { sha256 } from "@cosmjs/crypto";
|
||||
* import { toHex } from "@cosmjs/encoding";
|
||||
*
|
||||
* const transactionId = toHex(sha256(indexTx.tx)).toUpperCase();
|
||||
* ```
|
||||
*
|
||||
* Use `decodeTxRaw` from @cosmjs/proto-signing to decode this.
|
||||
*/
|
||||
readonly tx: Uint8Array;
|
||||
/**
|
||||
* The message responses of the [TxMsgData](https://github.com/cosmos/cosmos-sdk/blob/v0.46.3/proto/cosmos/base/abci/v1beta1/abci.proto#L128-L140)
|
||||
* as `Any`s.
|
||||
* This field is an empty list for chains running Cosmos SDK < 0.46.
|
||||
*/
|
||||
readonly msgResponses: Array<{ readonly typeUrl: string; readonly value: Uint8Array }>;
|
||||
readonly gasUsed: number;
|
||||
readonly gasWanted: number;
|
||||
}
|
||||
|
||||
export interface SequenceResponse {
|
||||
readonly accountNumber: number;
|
||||
readonly sequence: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The response after successfully broadcasting a transaction.
|
||||
* Success or failure refer to the execution result.
|
||||
*/
|
||||
export interface DeliverTxResponse {
|
||||
readonly height: number;
|
||||
/** The position of the transaction within the block. This is a 0-based index. */
|
||||
readonly txIndex: number;
|
||||
/** Error code. The transaction suceeded iff code is 0. */
|
||||
readonly code: number;
|
||||
readonly transactionHash: string;
|
||||
readonly events: readonly Event[];
|
||||
/**
|
||||
* A string-based log document.
|
||||
*
|
||||
* This currently seems to merge attributes of multiple events into one event per type
|
||||
* (https://github.com/tendermint/tendermint/issues/9595). You might want to use the `events`
|
||||
* field instead.
|
||||
*/
|
||||
readonly rawLog?: string;
|
||||
/** @deprecated Use `msgResponses` instead. */
|
||||
readonly data?: readonly MsgData[];
|
||||
/**
|
||||
* The message responses of the [TxMsgData](https://github.com/cosmos/cosmos-sdk/blob/v0.46.3/proto/cosmos/base/abci/v1beta1/abci.proto#L128-L140)
|
||||
* as `Any`s.
|
||||
* This field is an empty list for chains running Cosmos SDK < 0.46.
|
||||
*/
|
||||
readonly msgResponses: Array<{ readonly typeUrl: string; readonly value: Uint8Array }>;
|
||||
readonly gasUsed: number;
|
||||
readonly gasWanted: number;
|
||||
}
|
||||
|
||||
export function isDeliverTxFailure(result: DeliverTxResponse): boolean {
|
||||
return !!result.code;
|
||||
}
|
||||
|
||||
export function isDeliverTxSuccess(result: DeliverTxResponse): boolean {
|
||||
return !isDeliverTxFailure(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the given result is a success. Throws a detailed error message otherwise.
|
||||
*/
|
||||
export function assertIsDeliverTxSuccess(result: DeliverTxResponse): void {
|
||||
if (isDeliverTxFailure(result)) {
|
||||
throw new Error(
|
||||
`Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the given result is a failure. Throws a detailed error message otherwise.
|
||||
*/
|
||||
export function assertIsDeliverTxFailure(result: DeliverTxResponse): void {
|
||||
if (isDeliverTxSuccess(result)) {
|
||||
throw new Error(
|
||||
`Transaction ${result.transactionHash} did not fail at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An error when broadcasting the transaction. This contains the CheckTx errors
|
||||
* from the blockchain. Once a transaction is included in a block no BroadcastTxError
|
||||
* is thrown, even if the execution fails (DeliverTx errors).
|
||||
*/
|
||||
export class BroadcastTxError extends Error {
|
||||
public readonly code: number;
|
||||
public readonly codespace: string;
|
||||
public readonly log: string | undefined;
|
||||
|
||||
public constructor(code: number, codespace: string, log: string | undefined) {
|
||||
super(`Broadcasting transaction failed with code ${code} (codespace: ${codespace}). Log: ${log}`);
|
||||
this.code = code;
|
||||
this.codespace = codespace;
|
||||
this.log = log;
|
||||
}
|
||||
}
|
||||
|
||||
/** Use for testing only */
|
||||
export interface PrivateStargateClient {
|
||||
readonly cometClient: CometClient | undefined;
|
||||
@ -190,12 +31,12 @@ export interface StargateClientOptions {
|
||||
}
|
||||
|
||||
export class StargateClient {
|
||||
private readonly cometClient: CometClient | undefined;
|
||||
private readonly client: Client;
|
||||
|
||||
/** We maintain out own query client since the Client instance does not offer what we need here */
|
||||
private readonly queryClient:
|
||||
| (QueryClient & AuthExtension & BankExtension & StakingExtension & TxExtension)
|
||||
| undefined;
|
||||
private chainId: string | undefined;
|
||||
private readonly accountParser: AccountParser;
|
||||
|
||||
/**
|
||||
* Creates an instance by connecting to the given Tendermint RPC endpoint.
|
||||
@ -224,7 +65,6 @@ export class StargateClient {
|
||||
|
||||
protected constructor(cometClient: CometClient | undefined, options: StargateClientOptions) {
|
||||
if (cometClient) {
|
||||
this.cometClient = cometClient;
|
||||
this.queryClient = QueryClient.withExtensions(
|
||||
cometClient,
|
||||
setupAuthExtension,
|
||||
@ -233,19 +73,15 @@ export class StargateClient {
|
||||
setupTxExtension,
|
||||
);
|
||||
}
|
||||
const { accountParser = accountFromAny } = options;
|
||||
this.accountParser = accountParser;
|
||||
this.client = new Client(cometClient, undefined, options);
|
||||
}
|
||||
|
||||
protected getTmClient(): CometClient | undefined {
|
||||
return this.cometClient;
|
||||
return this.client.getCometClient();
|
||||
}
|
||||
|
||||
protected forceGetTmClient(): CometClient {
|
||||
if (!this.cometClient) {
|
||||
throw new Error("Comet client not available. You cannot use online functionality in offline mode.");
|
||||
}
|
||||
return this.cometClient;
|
||||
return this.client.forceGetCometClient();
|
||||
}
|
||||
|
||||
protected getQueryClient():
|
||||
@ -266,14 +102,7 @@ export class StargateClient {
|
||||
}
|
||||
|
||||
public async getChainId(): Promise<string> {
|
||||
if (!this.chainId) {
|
||||
const response = await this.forceGetTmClient().status();
|
||||
const chainId = response.nodeInfo.network;
|
||||
if (!chainId) throw new Error("Chain ID must not be empty");
|
||||
this.chainId = chainId;
|
||||
}
|
||||
|
||||
return this.chainId;
|
||||
return this.client.getChainId();
|
||||
}
|
||||
|
||||
public async getHeight(): Promise<number> {
|
||||
@ -282,45 +111,15 @@ export class StargateClient {
|
||||
}
|
||||
|
||||
public async getAccount(searchAddress: string): Promise<Account | null> {
|
||||
try {
|
||||
const account = await this.forceGetQueryClient().auth.account(searchAddress);
|
||||
return account ? this.accountParser(account) : null;
|
||||
} catch (error: any) {
|
||||
if (/rpc error: code = NotFound/i.test(error.toString())) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return this.client.getAccount(searchAddress);
|
||||
}
|
||||
|
||||
public async getSequence(address: string): Promise<SequenceResponse> {
|
||||
const account = await this.getAccount(address);
|
||||
if (!account) {
|
||||
throw new Error(
|
||||
`Account '${address}' does not exist on chain. Send some tokens there before trying to query sequence.`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
accountNumber: account.accountNumber,
|
||||
sequence: account.sequence,
|
||||
};
|
||||
return this.client.getSequence(address);
|
||||
}
|
||||
|
||||
public async getBlock(height?: number): Promise<Block> {
|
||||
const response = await this.forceGetTmClient().block(height);
|
||||
return {
|
||||
id: toHex(response.blockId.hash).toUpperCase(),
|
||||
header: {
|
||||
version: {
|
||||
block: new Uint53(response.block.header.version.block).toString(),
|
||||
app: new Uint53(response.block.header.version.app).toString(),
|
||||
},
|
||||
height: response.block.header.height,
|
||||
chainId: response.block.header.chainId,
|
||||
time: toRfc3339WithNanoseconds(response.block.header.time),
|
||||
},
|
||||
txs: response.block.txs,
|
||||
};
|
||||
return this.client.getBlock(height);
|
||||
}
|
||||
|
||||
public async getBalance(address: string, searchDenom: string): Promise<Coin> {
|
||||
@ -378,24 +177,15 @@ export class StargateClient {
|
||||
}
|
||||
|
||||
public async getTx(id: string): Promise<IndexedTx | null> {
|
||||
const results = await this.txsQuery(`tx.hash='${id}'`);
|
||||
return results[0] ?? null;
|
||||
return this.client.getTx(id);
|
||||
}
|
||||
|
||||
public async searchTx(query: SearchTxQuery): Promise<IndexedTx[]> {
|
||||
let rawQuery: string;
|
||||
if (typeof query === "string") {
|
||||
rawQuery = query;
|
||||
} else if (Array.isArray(query)) {
|
||||
rawQuery = query.map((t) => `${t.key}='${t.value}'`).join(" AND ");
|
||||
} else {
|
||||
throw new Error("Got unsupported query type. See CosmJS 0.31 CHANGELOG for API breaking changes here.");
|
||||
}
|
||||
return this.txsQuery(rawQuery);
|
||||
return this.client.searchTx(query);
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
if (this.cometClient) this.cometClient.disconnect();
|
||||
this.client.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -414,51 +204,7 @@ export class StargateClient {
|
||||
timeoutMs = 60_000,
|
||||
pollIntervalMs = 3_000,
|
||||
): Promise<DeliverTxResponse> {
|
||||
let timedOut = false;
|
||||
const txPollTimeout = setTimeout(() => {
|
||||
timedOut = true;
|
||||
}, timeoutMs);
|
||||
|
||||
const pollForTx = async (txId: string): Promise<DeliverTxResponse> => {
|
||||
if (timedOut) {
|
||||
throw new TimeoutError(
|
||||
`Transaction with ID ${txId} was submitted but was not yet found on the chain. You might want to check later. There was a wait of ${
|
||||
timeoutMs / 1000
|
||||
} seconds.`,
|
||||
txId,
|
||||
);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
const result = await this.getTx(txId);
|
||||
return result
|
||||
? {
|
||||
code: result.code,
|
||||
height: result.height,
|
||||
txIndex: result.txIndex,
|
||||
events: result.events,
|
||||
rawLog: result.rawLog,
|
||||
transactionHash: txId,
|
||||
msgResponses: result.msgResponses,
|
||||
gasUsed: result.gasUsed,
|
||||
gasWanted: result.gasWanted,
|
||||
}
|
||||
: pollForTx(txId);
|
||||
};
|
||||
|
||||
const transactionId = await this.broadcastTxSync(tx);
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
pollForTx(transactionId).then(
|
||||
(value) => {
|
||||
clearTimeout(txPollTimeout);
|
||||
resolve(value);
|
||||
},
|
||||
(error) => {
|
||||
clearTimeout(txPollTimeout);
|
||||
reject(error);
|
||||
},
|
||||
),
|
||||
);
|
||||
return this.client.broadcastTx(tx, timeoutMs, pollIntervalMs);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -473,35 +219,10 @@ export class StargateClient {
|
||||
* @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;
|
||||
return this.client.broadcastTxSync(tx);
|
||||
}
|
||||
|
||||
private async txsQuery(query: string): Promise<IndexedTx[]> {
|
||||
const results = await this.forceGetTmClient().txSearchAll({ query: query });
|
||||
return results.txs.map((tx): IndexedTx => {
|
||||
const txMsgData = TxMsgData.decode(tx.result.data ?? new Uint8Array());
|
||||
return {
|
||||
height: tx.height,
|
||||
txIndex: tx.index,
|
||||
hash: toHex(tx.hash).toUpperCase(),
|
||||
code: tx.result.code,
|
||||
events: tx.result.events.map(fromTendermintEvent),
|
||||
rawLog: tx.result.log || "",
|
||||
tx: tx.tx,
|
||||
msgResponses: txMsgData.msgResponses,
|
||||
gasUsed: tx.result.gasUsed,
|
||||
gasWanted: tx.result.gasWanted,
|
||||
};
|
||||
});
|
||||
return this.client.txsQuery(query);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user