From 7d62e8fc0d1c79e2ff5268d77584153256d4f769 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 6 Apr 2022 23:25:50 +0200 Subject: [PATCH] Create HttpEndpoint interface --- packages/cli/examples/figment.ts | 21 ++++++++ packages/cli/run_examples.sh | 3 ++ .../cosmwasm-stargate/src/cosmwasmclient.ts | 4 +- packages/cosmwasm-stargate/src/index.ts | 3 ++ .../src/signingcosmwasmclient.ts | 4 +- packages/stargate/src/index.ts | 3 ++ .../stargate/src/signingstargateclient.ts | 4 +- packages/stargate/src/stargateclient.ts | 4 +- packages/tendermint-rpc/src/index.ts | 4 ++ .../src/rpcclients/httpclient.spec.ts | 4 +- .../src/rpcclients/httpclient.ts | 48 ++++++++++++++++--- .../tendermint-rpc/src/rpcclients/index.ts | 2 +- .../src/tendermint34/tendermint34client.ts | 13 +++-- 13 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 packages/cli/examples/figment.ts diff --git a/packages/cli/examples/figment.ts b/packages/cli/examples/figment.ts new file mode 100644 index 00000000..8a7592ef --- /dev/null +++ b/packages/cli/examples/figment.ts @@ -0,0 +1,21 @@ +import { StargateClient } from "@cosmjs/stargate"; + +// Network config +const rpcEndpoint = { + // Note: we removed the /status patch from the examples because we use the HTTP POST API + url: "https://cosmoshub-4--rpc--full.datahub.figment.io/", + headers: { + "Authorization": "5195ebb0bfb7f0fe5c43409240c8b2c4", + } +}; + +// Setup client +const client = await StargateClient.connect(rpcEndpoint); + +// Get some data +const chainId = await client.getChainId(); +console.log("Chain ID:", chainId); +const balance = await client.getAllBalances("cosmos1ey69r37gfxvxg62sh4r0ktpuc46pzjrmz29g45"); +console.log("Balances:", balance); + +client.disconnect(); diff --git a/packages/cli/run_examples.sh b/packages/cli/run_examples.sh index 73dbafb2..5e301bca 100755 --- a/packages/cli/run_examples.sh +++ b/packages/cli/run_examples.sh @@ -17,3 +17,6 @@ if [ -n "${SIMAPP42_ENABLED:-}" ]; then yarn node ./bin/cosmjs-cli --init examples/stargate.ts --code "process.exit(0)" yarn node ./bin/cosmjs-cli --init examples/simulate.ts --code "process.exit(0)" fi + +# Disabled as this requires internet access +# yarn node ./bin/cosmjs-cli --init examples/figment.ts --code "process.exit(0)" diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.ts index e09c76e7..c308c598 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.ts @@ -23,7 +23,7 @@ import { TimeoutError, TxExtension, } from "@cosmjs/stargate"; -import { Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; import { assert, sleep } from "@cosmjs/utils"; import { CodeInfoResponse, @@ -89,7 +89,7 @@ export class CosmWasmClient { private readonly codesCache = new Map(); private chainId: string | undefined; - public static async connect(endpoint: string): Promise { + public static async connect(endpoint: string | HttpEndpoint): Promise { const tmClient = await Tendermint34Client.connect(endpoint); return new CosmWasmClient(tmClient); } diff --git a/packages/cosmwasm-stargate/src/index.ts b/packages/cosmwasm-stargate/src/index.ts index 91ab2f30..49908b5d 100644 --- a/packages/cosmwasm-stargate/src/index.ts +++ b/packages/cosmwasm-stargate/src/index.ts @@ -29,3 +29,6 @@ export { SigningCosmWasmClientOptions, UploadResult, } from "./signingcosmwasmclient"; + +// Re-exported because this is part of the CosmWasmClient/SigningCosmWasmClient APIs +export { HttpEndpoint } from "@cosmjs/tendermint-rpc"; diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index 1e940d88..69e2d71d 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -30,7 +30,7 @@ import { SignerData, StdFee, } from "@cosmjs/stargate"; -import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { assert, assertDefined } from "@cosmjs/utils"; import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; import { MsgDelegate, MsgUndelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; @@ -172,7 +172,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { private readonly gasPrice: GasPrice | undefined; public static async connectWithSigner( - endpoint: string, + endpoint: string | HttpEndpoint, signer: OfflineSigner, options: SigningCosmWasmClientOptions = {}, ): Promise { diff --git a/packages/stargate/src/index.ts b/packages/stargate/src/index.ts index 6af58edc..f3cd17f1 100644 --- a/packages/stargate/src/index.ts +++ b/packages/stargate/src/index.ts @@ -122,3 +122,6 @@ export { } from "./stargateclient"; export { StdFee } from "@cosmjs/amino"; export { Coin, coin, coins, makeCosmoshubPath, parseCoins } from "@cosmjs/proto-signing"; + +// Re-exported because this is part of the StargateClient/SigningStargateClient APIs +export { HttpEndpoint } from "@cosmjs/tendermint-rpc"; diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index e0e13c5a..9dec3293 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -12,7 +12,7 @@ import { Registry, TxBodyEncodeObject, } from "@cosmjs/proto-signing"; -import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { assert, assertDefined } from "@cosmjs/utils"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; @@ -116,7 +116,7 @@ export class SigningStargateClient extends StargateClient { private readonly gasPrice: GasPrice | undefined; public static async connectWithSigner( - endpoint: string, + endpoint: string | HttpEndpoint, signer: OfflineSigner, options: SigningStargateClientOptions = {}, ): Promise { diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index bbbfa251..859408b0 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { toHex } from "@cosmjs/encoding"; import { Uint53 } from "@cosmjs/math"; -import { Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; import { sleep } from "@cosmjs/utils"; import { MsgData } from "cosmjs-types/cosmos/base/abci/v1beta1/abci"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; @@ -149,7 +149,7 @@ export class StargateClient { private readonly accountParser: AccountParser; public static async connect( - endpoint: string, + endpoint: string | HttpEndpoint, options: StargateClientOptions = {}, ): Promise { const tmClient = await Tendermint34Client.connect(endpoint); diff --git a/packages/tendermint-rpc/src/index.ts b/packages/tendermint-rpc/src/index.ts index e56a3e5c..98515b4d 100644 --- a/packages/tendermint-rpc/src/index.ts +++ b/packages/tendermint-rpc/src/index.ts @@ -12,6 +12,10 @@ export { toRfc3339WithNanoseconds, toSeconds, } from "./dates"; +export { + // This type is part of the Tendermint34Client.connect API + HttpEndpoint, +} from "./rpcclients"; export { HttpClient, WebsocketClient } from "./rpcclients"; // TODO: Why do we export those outside of this package? export { AbciInfoRequest, diff --git a/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts b/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts index add473e9..a73972c8 100644 --- a/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts +++ b/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts @@ -13,13 +13,13 @@ const tendermintUrl = defaultInstance.url; describe("http", () => { it("can send a health request", async () => { pendingWithoutTendermint(); - const response = await http("POST", `http://${tendermintUrl}`, createJsonRpcRequest("health")); + const response = await http("POST", `http://${tendermintUrl}`, undefined, createJsonRpcRequest("health")); expect(response).toEqual(jasmine.objectContaining({ jsonrpc: "2.0" })); }); it("errors for non-open port", async () => { await expectAsync( - http("POST", `http://localhost:56745`, createJsonRpcRequest("health")), + http("POST", `http://localhost:56745`, undefined, createJsonRpcRequest("health")), ).toBeRejectedWithError(/(ECONNREFUSED|Failed to fetch)/i); }); }); diff --git a/packages/tendermint-rpc/src/rpcclients/httpclient.ts b/packages/tendermint-rpc/src/rpcclients/httpclient.ts index 293842df..83dd81bb 100644 --- a/packages/tendermint-rpc/src/rpcclients/httpclient.ts +++ b/packages/tendermint-rpc/src/rpcclients/httpclient.ts @@ -11,6 +11,8 @@ import { hasProtocol, RpcClient } from "./rpcclient"; // Global symbols in some environments // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch declare const fetch: any | undefined; +// eslint-disable-next-line @typescript-eslint/naming-convention +declare const Headers: any | undefined; function filterBadStatus(res: any): any { if (res.status >= 400) { @@ -25,23 +27,55 @@ function filterBadStatus(res: any): any { * For some reason, fetch does not complain about missing server-side CORS support. */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export async function http(method: "POST", url: string, request?: any): Promise { +export async function http( + method: "POST", + url: string, + headers: Record | undefined, + request?: any, +): Promise { if (typeof fetch !== "undefined") { const body = request ? JSON.stringify(request) : undefined; - return fetch(url, { method: method, body: body }) + const settings = { + method: method, + body: body, + headers: headers ? new Headers(headers) : undefined, + }; + return fetch(url, settings) .then(filterBadStatus) .then((res: any) => res.json()); } else { - return axios.request({ url: url, method: method, data: request }).then((res) => res.data); + return axios + .request({ url: url, method: method, data: request, headers: headers }) + .then((res) => res.data); } } +export interface HttpEndpoint { + /** + * The URL of the HTTP endpoint. + * + * For POST APIs like Tendermint RPC in CosmJS, + * this is without the method specific paths (e.g. https://cosmoshub-4--rpc--full.datahub.figment.io/) + */ + readonly url: string; + /** + * HTTP headers that are sent with every request, such as authorization information. + */ + readonly headers: Record; +} + export class HttpClient implements RpcClient { protected readonly url: string; + protected readonly headers: Record | undefined; - public constructor(url: string) { - // accept host.name:port and assume http protocol - this.url = hasProtocol(url) ? url : "http://" + url; + public constructor(endpoint: string | HttpEndpoint) { + if (typeof endpoint === "string") { + // accept host.name:port and assume http protocol + this.url = hasProtocol(endpoint) ? endpoint : "http://" + endpoint; + } else { + this.url = endpoint.url; + this.headers = endpoint.headers; + } } public disconnect(): void { @@ -49,7 +83,7 @@ export class HttpClient implements RpcClient { } public async execute(request: JsonRpcRequest): Promise { - const response = parseJsonRpcResponse(await http("POST", this.url, request)); + const response = parseJsonRpcResponse(await http("POST", this.url, this.headers, request)); if (isJsonRpcErrorResponse(response)) { throw new Error(JSON.stringify(response.error)); } diff --git a/packages/tendermint-rpc/src/rpcclients/index.ts b/packages/tendermint-rpc/src/rpcclients/index.ts index 31cff4b0..bff29531 100644 --- a/packages/tendermint-rpc/src/rpcclients/index.ts +++ b/packages/tendermint-rpc/src/rpcclients/index.ts @@ -1,5 +1,5 @@ // This folder contains Tendermint-specific RPC clients -export { HttpClient } from "./httpclient"; +export { HttpClient, HttpEndpoint } from "./httpclient"; export { instanceOfRpcStreamingClient, RpcClient, RpcStreamingClient, SubscriptionEvent } from "./rpcclient"; export { WebsocketClient } from "./websocketclient"; diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts index 05176d60..a0220eb4 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts @@ -4,6 +4,7 @@ import { Stream } from "xstream"; import { createJsonRpcRequest } from "../jsonrpc"; import { HttpClient, + HttpEndpoint, instanceOfRpcStreamingClient, RpcClient, SubscriptionEvent, @@ -19,10 +20,14 @@ export class Tendermint34Client { * * Uses HTTP when the URL schema is http or https. Uses WebSockets otherwise. */ - public static async connect(url: string): Promise { - const useHttp = url.startsWith("http://") || url.startsWith("https://"); - const rpcClient = useHttp ? new HttpClient(url) : new WebsocketClient(url); - return Tendermint34Client.create(rpcClient); + public static async connect(endpoint: string | HttpEndpoint): Promise { + if (typeof endpoint === "object") { + return Tendermint34Client.create(new HttpClient(endpoint)); + } else { + const useHttp = endpoint.startsWith("http://") || endpoint.startsWith("https://"); + const rpcClient = useHttp ? new HttpClient(endpoint) : new WebsocketClient(endpoint); + return Tendermint34Client.create(rpcClient); + } } /**