stargate: Add broadcastTx method to client
This commit is contained in:
parent
d9520990dd
commit
3cb3bf14a3
@ -1,7 +1,23 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { Bech32, fromBase64 } from "@cosmjs/encoding";
|
||||
import { Secp256k1Wallet } from "@cosmjs/launchpad";
|
||||
import { makeSignBytes, omitDefaults, Registry } from "@cosmjs/proto-signing";
|
||||
import { assert, sleep } from "@cosmjs/utils";
|
||||
|
||||
import { PrivateStargateClient, StargateClient } from "./stargateclient";
|
||||
import { nonExistentAddress, pendingWithoutSimapp, simapp, unused, validator } from "./testutils.spec";
|
||||
import { cosmos } from "./generated/codecimpl";
|
||||
import { assertIsBroadcastTxSuccess, PrivateStargateClient, StargateClient } from "./stargateclient";
|
||||
import {
|
||||
faucet,
|
||||
makeRandomAddressBytes,
|
||||
nonExistentAddress,
|
||||
pendingWithoutSimapp,
|
||||
simapp,
|
||||
unused,
|
||||
validator,
|
||||
} from "./testutils.spec";
|
||||
|
||||
const { AuthInfo, SignDoc, Tx, TxBody } = cosmos.tx;
|
||||
const { PublicKey } = cosmos.crypto;
|
||||
|
||||
describe("StargateClient", () => {
|
||||
describe("connect", () => {
|
||||
@ -182,4 +198,79 @@ describe("StargateClient", () => {
|
||||
expect(balances).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("broadcastTx", () => {
|
||||
it("broadcasts a transaction", async () => {
|
||||
pendingWithoutSimapp();
|
||||
const client = await StargateClient.connect(simapp.tendermintUrl);
|
||||
const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic);
|
||||
const [{ address, pubkey: pubkeyBytes }] = await wallet.getAccounts();
|
||||
const publicKey = PublicKey.create({ secp256k1: pubkeyBytes });
|
||||
const registry = new Registry();
|
||||
const txBodyFields = {
|
||||
typeUrl: "/cosmos.tx.TxBody",
|
||||
value: {
|
||||
messages: [
|
||||
{
|
||||
typeUrl: "/cosmos.bank.MsgSend",
|
||||
value: {
|
||||
fromAddress: Bech32.decode(address).data,
|
||||
toAddress: makeRandomAddressBytes(),
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const txBodyBytes = registry.encode(txBodyFields);
|
||||
const txBody = TxBody.decode(txBodyBytes);
|
||||
const authInfo = {
|
||||
signerInfos: [
|
||||
{
|
||||
publicKey: publicKey,
|
||||
modeInfo: {
|
||||
single: {
|
||||
mode: cosmos.tx.signing.SignMode.SIGN_MODE_DIRECT,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
fee: {
|
||||
gasLimit: 200000,
|
||||
},
|
||||
};
|
||||
const authInfoBytes = Uint8Array.from(AuthInfo.encode(authInfo).finish());
|
||||
|
||||
const chainId = await client.getChainId();
|
||||
const { accountNumber, sequence } = (await client.getSequence(address))!;
|
||||
const signDoc = SignDoc.create(
|
||||
omitDefaults({
|
||||
bodyBytes: txBodyBytes,
|
||||
authInfoBytes: authInfoBytes,
|
||||
chainId: chainId,
|
||||
accountNumber: accountNumber,
|
||||
accountSequence: sequence,
|
||||
}),
|
||||
);
|
||||
const signDocBytes = makeSignBytes(signDoc);
|
||||
const signature = await wallet.sign(address, signDocBytes);
|
||||
const txRaw = Tx.create({
|
||||
body: txBody,
|
||||
authInfo: authInfo,
|
||||
signatures: [fromBase64(signature.signature)],
|
||||
});
|
||||
const txRawBytes = Uint8Array.from(Tx.encode(txRaw).finish());
|
||||
const txResult = await client.broadcastTx(txRawBytes);
|
||||
assertIsBroadcastTxSuccess(txResult);
|
||||
|
||||
const { rawLog, transactionHash } = txResult;
|
||||
expect(rawLog).toMatch(/{"key":"amount","value":"1234567ucosm"}/);
|
||||
expect(transactionHash).toMatch(/^[0-9A-F]{64}$/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@ import { Bech32, toAscii, toHex } from "@cosmjs/encoding";
|
||||
import { Coin, decodeAminoPubkey, PubKey } from "@cosmjs/launchpad";
|
||||
import { Uint64 } from "@cosmjs/math";
|
||||
import { decodeAny } from "@cosmjs/proto-signing";
|
||||
import { Client as TendermintClient } from "@cosmjs/tendermint-rpc";
|
||||
import { broadcastTxCommitSuccess, Client as TendermintClient } from "@cosmjs/tendermint-rpc";
|
||||
import { arrayContentEquals, assert, assertDefined } from "@cosmjs/utils";
|
||||
import Long from "long";
|
||||
|
||||
@ -22,6 +22,44 @@ export interface SequenceResponse {
|
||||
readonly sequence: number;
|
||||
}
|
||||
|
||||
export interface BroadcastTxFailure {
|
||||
readonly height: number;
|
||||
readonly code: number;
|
||||
readonly transactionHash: string;
|
||||
readonly rawLog?: string;
|
||||
readonly data?: Uint8Array;
|
||||
}
|
||||
|
||||
export interface BroadcastTxSuccess {
|
||||
readonly height: number;
|
||||
readonly transactionHash: string;
|
||||
readonly rawLog?: string;
|
||||
readonly data?: Uint8Array;
|
||||
}
|
||||
|
||||
export type BroadcastTxResponse = BroadcastTxSuccess | BroadcastTxFailure;
|
||||
|
||||
export function isBroadcastTxFailure(result: BroadcastTxResponse): result is BroadcastTxFailure {
|
||||
return !!(result as BroadcastTxFailure).code;
|
||||
}
|
||||
|
||||
export function isBroadcastTxSuccess(result: BroadcastTxResponse): result is BroadcastTxSuccess {
|
||||
return !isBroadcastTxFailure(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the given result is a success. Throws a detailed error message otherwise.
|
||||
*/
|
||||
export function assertIsBroadcastTxSuccess(
|
||||
result: BroadcastTxResponse,
|
||||
): asserts result is BroadcastTxSuccess {
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(
|
||||
`Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function uint64FromProto(input: number | Long): Uint64 {
|
||||
return Uint64.fromString(input.toString());
|
||||
}
|
||||
@ -155,6 +193,24 @@ export class StargateClient {
|
||||
this.tmClient.disconnect();
|
||||
}
|
||||
|
||||
public async broadcastTx(tx: Uint8Array): Promise<BroadcastTxResponse> {
|
||||
const response = await this.tmClient.broadcastTxCommit({ tx });
|
||||
return broadcastTxCommitSuccess(response)
|
||||
? {
|
||||
height: response.height,
|
||||
transactionHash: toHex(response.hash).toUpperCase(),
|
||||
rawLog: response.deliverTx?.log,
|
||||
data: response.deliverTx?.data,
|
||||
}
|
||||
: {
|
||||
height: response.height,
|
||||
code: response.checkTx.code,
|
||||
transactionHash: toHex(response.hash).toUpperCase(),
|
||||
rawLog: response.checkTx.log,
|
||||
data: response.checkTx.data,
|
||||
};
|
||||
}
|
||||
|
||||
private async queryVerified(store: string, key: Uint8Array): Promise<Uint8Array> {
|
||||
const response = await this.tmClient.abciQuery({
|
||||
// we need the StoreKey for the module, not the module name
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
import { Random } from "@cosmjs/crypto";
|
||||
|
||||
export function pendingWithoutSimapp(): void {
|
||||
if (!process.env.SIMAPP_ENABLED) {
|
||||
return pending("Set SIMAPP_ENABLED to enable Simapp based tests");
|
||||
}
|
||||
}
|
||||
|
||||
export function makeRandomAddressBytes(): Uint8Array {
|
||||
return Random.getBytes(20);
|
||||
}
|
||||
|
||||
export const simapp = {
|
||||
tendermintUrl: "localhost:26657",
|
||||
chainId: "simd-testing",
|
||||
@ -12,6 +18,16 @@ export const simapp = {
|
||||
blockTime: 1_000, // ms
|
||||
};
|
||||
|
||||
export const faucet = {
|
||||
mnemonic:
|
||||
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone",
|
||||
pubkey0: {
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
|
||||
},
|
||||
address0: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
|
||||
};
|
||||
|
||||
/** Unused account */
|
||||
export const unused = {
|
||||
pubkey: {
|
||||
|
||||
23
packages/stargate/types/stargateclient.d.ts
vendored
23
packages/stargate/types/stargateclient.d.ts
vendored
@ -11,6 +11,28 @@ export interface SequenceResponse {
|
||||
readonly accountNumber: number;
|
||||
readonly sequence: number;
|
||||
}
|
||||
export interface BroadcastTxFailure {
|
||||
readonly height: number;
|
||||
readonly code: number;
|
||||
readonly transactionHash: string;
|
||||
readonly rawLog?: string;
|
||||
readonly data?: Uint8Array;
|
||||
}
|
||||
export interface BroadcastTxSuccess {
|
||||
readonly height: number;
|
||||
readonly transactionHash: string;
|
||||
readonly rawLog?: string;
|
||||
readonly data?: Uint8Array;
|
||||
}
|
||||
export declare type BroadcastTxResponse = BroadcastTxSuccess | BroadcastTxFailure;
|
||||
export declare function isBroadcastTxFailure(result: BroadcastTxResponse): result is BroadcastTxFailure;
|
||||
export declare function isBroadcastTxSuccess(result: BroadcastTxResponse): result is BroadcastTxSuccess;
|
||||
/**
|
||||
* Ensures the given result is a success. Throws a detailed error message otherwise.
|
||||
*/
|
||||
export declare function assertIsBroadcastTxSuccess(
|
||||
result: BroadcastTxResponse,
|
||||
): asserts result is BroadcastTxSuccess;
|
||||
/** Use for testing only */
|
||||
export interface PrivateStargateClient {
|
||||
readonly tmClient: TendermintClient;
|
||||
@ -33,6 +55,7 @@ export declare class StargateClient {
|
||||
*/
|
||||
getAllBalancesUnverified(address: string): Promise<readonly Coin[]>;
|
||||
disconnect(): void;
|
||||
broadcastTx(tx: Uint8Array): Promise<BroadcastTxResponse>;
|
||||
private queryVerified;
|
||||
private queryUnverified;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user