From 435660cb6775d1aeb0a9160559c8821fc4a8852d Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 6 Aug 2020 15:33:26 +0200 Subject: [PATCH 1/7] Use Coin type from @cosmjs/launchpad --- packages/stargate/package.json | 1 + packages/stargate/src/stargateclient.ts | 17 ++++++----------- packages/stargate/types/stargateclient.d.ts | 9 ++------- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/stargate/package.json b/packages/stargate/package.json index 647c715b..0617276f 100644 --- a/packages/stargate/package.json +++ b/packages/stargate/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@cosmjs/encoding": "^0.22.0", + "@cosmjs/launchpad": "^0.22.0", "@cosmjs/math": "^0.22.0", "@cosmjs/proto-signing": "^0.22.0", "@cosmjs/tendermint-rpc": "^0.22.0", diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index ec23473c..9b582ca3 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Bech32, toAscii, toHex } from "@cosmjs/encoding"; +import { Coin } from "@cosmjs/launchpad"; import { Uint64 } from "@cosmjs/math"; -import { BaseAccount, Coin, decodeAny } from "@cosmjs/proto-signing"; +import * as proto from "@cosmjs/proto-signing"; import { Client as TendermintClient } from "@cosmjs/tendermint-rpc"; import { assertDefined } from "@cosmjs/utils"; import Long from "long"; @@ -33,10 +34,10 @@ export class StargateClient { const accountKey = Uint8Array.from([0x01, ...binAddress]); const responseData = await this.queryVerified("acc", accountKey); - const { typeUrl, value } = decodeAny(responseData); + const { typeUrl, value } = proto.decodeAny(responseData); switch (typeUrl) { case "/cosmos.auth.BaseAccount": { - const { account_number, sequence } = BaseAccount.decode(value); + const { account_number, sequence } = proto.BaseAccount.decode(value); assertDefined(account_number); assertDefined(sequence); return { @@ -49,13 +50,7 @@ export class StargateClient { } } - public async getBalance( - address: string, - searchDenom: string, - ): Promise<{ - readonly denom: string; - readonly amount: string; - } | null> { + public async getBalance(address: string, searchDenom: string): Promise { // balance key is a bit tricker, using some prefix stores // https://github.com/cosmwasm/cosmos-sdk/blob/80f7ff62f79777a487d0c7a53c64b0f7e43c47b9/x/bank/keeper/view.go#L74-L77 // ("balances", binAddress, denom) @@ -66,7 +61,7 @@ export class StargateClient { const bankKey = Uint8Array.from([...toAscii("balances"), ...binAddress, ...toAscii(searchDenom)]); const responseData = await this.queryVerified("bank", bankKey); - const { amount, denom } = Coin.decode(responseData); + const { amount, denom } = proto.Coin.decode(responseData); assertDefined(amount); assertDefined(denom); diff --git a/packages/stargate/types/stargateclient.d.ts b/packages/stargate/types/stargateclient.d.ts index 85cafca9..05a3a318 100644 --- a/packages/stargate/types/stargateclient.d.ts +++ b/packages/stargate/types/stargateclient.d.ts @@ -1,3 +1,4 @@ +import { Coin } from "@cosmjs/launchpad"; export interface GetSequenceResult { readonly accountNumber: number; readonly sequence: number; @@ -7,13 +8,7 @@ export declare class StargateClient { static connect(endpoint: string): Promise; private constructor(); getSequence(address: string): Promise; - getBalance( - address: string, - searchDenom: string, - ): Promise<{ - readonly denom: string; - readonly amount: string; - } | null>; + getBalance(address: string, searchDenom: string): Promise; disconnect(): void; private queryVerified; } From b24a8f52f7572b1727de9760281ab32a618bea67 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 6 Aug 2020 15:36:26 +0200 Subject: [PATCH 2/7] Test getBalance with non-existent address --- packages/stargate/src/stargateclient.spec.ts | 12 +++++++++++- packages/stargate/src/testutils.spec.ts | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/stargate/src/stargateclient.spec.ts b/packages/stargate/src/stargateclient.spec.ts index a376253c..99c11c8f 100644 --- a/packages/stargate/src/stargateclient.spec.ts +++ b/packages/stargate/src/stargateclient.spec.ts @@ -1,5 +1,5 @@ import { StargateClient } from "./stargateclient"; -import { pendingWithoutSimapp, simapp, unused } from "./testutils.spec"; +import { nonExistentAddress, pendingWithoutSimapp, simapp, unused } from "./testutils.spec"; describe("StargateClient", () => { describe("connect", () => { @@ -52,5 +52,15 @@ describe("StargateClient", () => { client.disconnect(); }); + + it("returns null for non-existent address", async () => { + pendingWithoutSimapp(); + const client = await StargateClient.connect(simapp.tendermintUrl); + + const response = await client.getBalance(nonExistentAddress, simapp.denomFee); + expect(response).toBeNull(); + + client.disconnect(); + }); }); }); diff --git a/packages/stargate/src/testutils.spec.ts b/packages/stargate/src/testutils.spec.ts index 1abc4554..5570c79b 100644 --- a/packages/stargate/src/testutils.spec.ts +++ b/packages/stargate/src/testutils.spec.ts @@ -23,3 +23,5 @@ export const unused = { balanceStaking: "10000000", // 10 STAKE balanceFee: "1000000000", // 1000 COSM }; + +export const nonExistentAddress = "cosmos1p79apjaufyphcmsn4g07cynqf0wyjuezqu84hd"; From 44c30d4aaafb9a11cd222ee655b4461123f23df9 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 6 Aug 2020 15:43:15 +0200 Subject: [PATCH 3/7] Move query response type to @cosmjs/stargate --- packages/proto-signing/src/index.ts | 2 +- packages/proto-signing/types/index.d.ts | 2 +- .../{proto-signing/src => stargate/src/query}/accounts.ts | 3 +-- packages/stargate/src/stargateclient.ts | 4 +++- packages/stargate/tsconfig.json | 1 + .../types => stargate/types/query}/accounts.d.ts | 0 6 files changed, 7 insertions(+), 5 deletions(-) rename packages/{proto-signing/src => stargate/src/query}/accounts.ts (88%) rename packages/{proto-signing/types => stargate/types/query}/accounts.d.ts (100%) diff --git a/packages/proto-signing/src/index.ts b/packages/proto-signing/src/index.ts index c52b350f..28e7bc70 100644 --- a/packages/proto-signing/src/index.ts +++ b/packages/proto-signing/src/index.ts @@ -1,3 +1,3 @@ -export { BaseAccount } from "./accounts"; export { decodeAny } from "./any"; export { Coin } from "./msgs"; +export { cosmosField } from "./decorator"; diff --git a/packages/proto-signing/types/index.d.ts b/packages/proto-signing/types/index.d.ts index c52b350f..28e7bc70 100644 --- a/packages/proto-signing/types/index.d.ts +++ b/packages/proto-signing/types/index.d.ts @@ -1,3 +1,3 @@ -export { BaseAccount } from "./accounts"; export { decodeAny } from "./any"; export { Coin } from "./msgs"; +export { cosmosField } from "./decorator"; diff --git a/packages/proto-signing/src/accounts.ts b/packages/stargate/src/query/accounts.ts similarity index 88% rename from packages/proto-signing/src/accounts.ts rename to packages/stargate/src/query/accounts.ts index c13d6728..8c48497b 100644 --- a/packages/proto-signing/src/accounts.ts +++ b/packages/stargate/src/query/accounts.ts @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { cosmosField } from "@cosmjs/proto-signing"; import { Message } from "protobufjs"; -import { cosmosField } from "./decorator"; - export class BaseAccount extends Message { @cosmosField.bytes(1) public readonly address?: Uint8Array; diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 9b582ca3..96be91f0 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -7,6 +7,8 @@ import { Client as TendermintClient } from "@cosmjs/tendermint-rpc"; import { assertDefined } from "@cosmjs/utils"; import Long from "long"; +import { BaseAccount } from "./query/accounts"; + export interface GetSequenceResult { readonly accountNumber: number; readonly sequence: number; @@ -37,7 +39,7 @@ export class StargateClient { const { typeUrl, value } = proto.decodeAny(responseData); switch (typeUrl) { case "/cosmos.auth.BaseAccount": { - const { account_number, sequence } = proto.BaseAccount.decode(value); + const { account_number, sequence } = BaseAccount.decode(value); assertDefined(account_number); assertDefined(sequence); return { diff --git a/packages/stargate/tsconfig.json b/packages/stargate/tsconfig.json index 167e8c02..c605e918 100644 --- a/packages/stargate/tsconfig.json +++ b/packages/stargate/tsconfig.json @@ -4,6 +4,7 @@ "baseUrl": ".", "outDir": "build", "declarationDir": "build/types", + "experimentalDecorators": true, "rootDir": "src" }, "include": [ diff --git a/packages/proto-signing/types/accounts.d.ts b/packages/stargate/types/query/accounts.d.ts similarity index 100% rename from packages/proto-signing/types/accounts.d.ts rename to packages/stargate/types/query/accounts.d.ts From 05a8ac9b71c4b414227f7ebd742399b8c4c15132 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 6 Aug 2020 16:00:51 +0200 Subject: [PATCH 4/7] Implement getUnverifiedAllBalances --- packages/stargate/src/query/allbalances.ts | 23 +++++++++++++ packages/stargate/src/query/pagination.ts | 18 ++++++++++ packages/stargate/src/stargateclient.spec.ts | 26 +++++++++++++++ packages/stargate/src/stargateclient.ts | 33 +++++++++++++++++++ .../stargate/types/query/allbalances.d.ts | 11 +++++++ packages/stargate/types/query/pagination.d.ts | 7 ++++ packages/stargate/types/stargateclient.d.ts | 7 ++++ 7 files changed, 125 insertions(+) create mode 100644 packages/stargate/src/query/allbalances.ts create mode 100644 packages/stargate/src/query/pagination.ts create mode 100644 packages/stargate/types/query/allbalances.d.ts create mode 100644 packages/stargate/types/query/pagination.d.ts diff --git a/packages/stargate/src/query/allbalances.ts b/packages/stargate/src/query/allbalances.ts new file mode 100644 index 00000000..396aeb12 --- /dev/null +++ b/packages/stargate/src/query/allbalances.ts @@ -0,0 +1,23 @@ +import { Coin, cosmosField } from "@cosmjs/proto-signing"; +import { Message } from "protobufjs"; + +import { PageRequest, PageResponse } from "./pagination"; + +// these grpc query types come from: +// https://github.com/cosmos/cosmos-sdk/blob/69bbb8b327c3cfb967d969bcadeb9b0aef144df6/proto/cosmos/bank/query.proto#L40-L55 + +export class QueryAllBalancesRequest extends Message { + @cosmosField.bytes(1) + public readonly address?: Uint8Array; + + @cosmosField.message(2, PageRequest) + public readonly pagination?: PageRequest; +} + +export class QueryAllBalancesResponse extends Message { + @cosmosField.repeatedMessage(1, Coin) + public readonly balances?: readonly Coin[]; + + @cosmosField.message(2, PageResponse) + public readonly pagination?: PageResponse; +} diff --git a/packages/stargate/src/query/pagination.ts b/packages/stargate/src/query/pagination.ts new file mode 100644 index 00000000..2ffca347 --- /dev/null +++ b/packages/stargate/src/query/pagination.ts @@ -0,0 +1,18 @@ +import { cosmosField } from "@cosmjs/proto-signing"; +import { Message } from "protobufjs"; + +export class PageRequest extends Message { + // TODO: implement +} + +export class PageResponse extends Message { + // next_key is the key to be passed to PageRequest.key to + // query the next page most efficiently + @cosmosField.bytes(1) + public readonly nextKey?: Uint8Array; + + // total is total number of results available if PageRequest.count_total + // was set, its value is undefined otherwise + @cosmosField.uint64(2) + public readonly total?: Long | number; +} diff --git a/packages/stargate/src/stargateclient.spec.ts b/packages/stargate/src/stargateclient.spec.ts index 99c11c8f..8d66063b 100644 --- a/packages/stargate/src/stargateclient.spec.ts +++ b/packages/stargate/src/stargateclient.spec.ts @@ -63,4 +63,30 @@ describe("StargateClient", () => { client.disconnect(); }); }); + + describe("getUnverifiedAllBalances", () => { + it("returns all balances for unused account", async () => { + pendingWithoutSimapp(); + const client = await StargateClient.connect(simapp.tendermintUrl); + + const balances = await client.getUnverifiedAllBalances(unused.address); + expect(balances).toEqual([ + { + amount: unused.balanceFee, + denom: simapp.denomFee, + }, + { + amount: unused.balanceStaking, + denom: simapp.denomStaking, + }, + ]); + }); + + it("returns an empty list for non-existent account", async () => { + pendingWithoutSimapp(); + const client = await StargateClient.connect(simapp.tendermintUrl); + const balances = await client.getUnverifiedAllBalances(nonExistentAddress); + expect(balances).toEqual([]); + }); + }); }); diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 96be91f0..cc3b20a6 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -8,6 +8,7 @@ import { assertDefined } from "@cosmjs/utils"; import Long from "long"; import { BaseAccount } from "./query/accounts"; +import { QueryAllBalancesRequest, QueryAllBalancesResponse } from "./query/allbalances"; export interface GetSequenceResult { readonly accountNumber: number; @@ -77,6 +78,38 @@ export class StargateClient { } } + /** + * Queries all balances for all denoms that belong to this address. + * + * Uses the grpc queries (which iterates over the store internally), and we cannot get + * proofs from such a method. + */ + public async getUnverifiedAllBalances(address: string): Promise { + const path = "/cosmos.bank.Query/AllBalances"; + const request = QueryAllBalancesRequest.encode({ address: Bech32.decode(address).data }).finish(); + const response = await this.tmClient.abciQuery({ + path: path, + data: request, + prove: false, + }); + + if (response.code) { + throw new Error(`Query failed with (${response.code}): ${response.log}`); + } + + const result = QueryAllBalancesResponse.decode(response.value); + return (result.balances || []).map( + (balance): Coin => { + assertDefined(balance.amount); + assertDefined(balance.denom); + return { + amount: balance.amount, + denom: balance.denom, + }; + }, + ); + } + public disconnect(): void { this.tmClient.disconnect(); } diff --git a/packages/stargate/types/query/allbalances.d.ts b/packages/stargate/types/query/allbalances.d.ts new file mode 100644 index 00000000..3e418fee --- /dev/null +++ b/packages/stargate/types/query/allbalances.d.ts @@ -0,0 +1,11 @@ +import { Coin } from "@cosmjs/proto-signing"; +import { Message } from "protobufjs"; +import { PageRequest, PageResponse } from "./pagination"; +export declare class QueryAllBalancesRequest extends Message { + readonly address?: Uint8Array; + readonly pagination?: PageRequest; +} +export declare class QueryAllBalancesResponse extends Message { + readonly balances?: readonly Coin[]; + readonly pagination?: PageResponse; +} diff --git a/packages/stargate/types/query/pagination.d.ts b/packages/stargate/types/query/pagination.d.ts new file mode 100644 index 00000000..472b205d --- /dev/null +++ b/packages/stargate/types/query/pagination.d.ts @@ -0,0 +1,7 @@ +/// +import { Message } from "protobufjs"; +export declare class PageRequest extends Message {} +export declare class PageResponse extends Message { + readonly nextKey?: Uint8Array; + readonly total?: Long | number; +} diff --git a/packages/stargate/types/stargateclient.d.ts b/packages/stargate/types/stargateclient.d.ts index 05a3a318..334447c8 100644 --- a/packages/stargate/types/stargateclient.d.ts +++ b/packages/stargate/types/stargateclient.d.ts @@ -9,6 +9,13 @@ export declare class StargateClient { private constructor(); getSequence(address: string): Promise; getBalance(address: string, searchDenom: string): Promise; + /** + * Queries all balances for all denoms that belong to this address. + * + * Uses the grpc queries (which iterates over the store internally), and we cannot get + * proofs from such a method. + */ + getUnverifiedAllBalances(address: string): Promise; disconnect(): void; private queryVerified; } From e587fd639f8deb6e96d80cdb6521201bbdeec09f Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 6 Aug 2020 16:15:15 +0200 Subject: [PATCH 5/7] Pull out queryUnverified --- packages/stargate/src/stargateclient.ts | 29 ++++++++++++--------- packages/stargate/types/stargateclient.d.ts | 1 + 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index cc3b20a6..3f0f7c45 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -87,18 +87,9 @@ export class StargateClient { public async getUnverifiedAllBalances(address: string): Promise { const path = "/cosmos.bank.Query/AllBalances"; const request = QueryAllBalancesRequest.encode({ address: Bech32.decode(address).data }).finish(); - const response = await this.tmClient.abciQuery({ - path: path, - data: request, - prove: false, - }); - - if (response.code) { - throw new Error(`Query failed with (${response.code}): ${response.log}`); - } - - const result = QueryAllBalancesResponse.decode(response.value); - return (result.balances || []).map( + const responseData = await this.queryUnverified(path, request); + const response = QueryAllBalancesResponse.decode(responseData); + return (response.balances || []).map( (balance): Coin => { assertDefined(balance.amount); assertDefined(balance.denom); @@ -137,4 +128,18 @@ export class StargateClient { return response.value; } + + private async queryUnverified(path: string, request: Uint8Array): Promise { + const response = await this.tmClient.abciQuery({ + path: path, + data: request, + prove: false, + }); + + if (response.code) { + throw new Error(`Query failed with (${response.code}): ${response.log}`); + } + + return response.value; + } } diff --git a/packages/stargate/types/stargateclient.d.ts b/packages/stargate/types/stargateclient.d.ts index 334447c8..335a0d75 100644 --- a/packages/stargate/types/stargateclient.d.ts +++ b/packages/stargate/types/stargateclient.d.ts @@ -18,4 +18,5 @@ export declare class StargateClient { getUnverifiedAllBalances(address: string): Promise; disconnect(): void; private queryVerified; + private queryUnverified; } From 78280fcae969ed85dcd55948902008c195dc158e Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 6 Aug 2020 18:08:00 +0200 Subject: [PATCH 6/7] Extract coinFromProto --- packages/stargate/src/stargateclient.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 3f0f7c45..b643b04c 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -19,6 +19,15 @@ function uint64FromProto(input: number | Long): Uint64 { return Uint64.fromString(input.toString()); } +function coinFromProto(input: proto.Coin): Coin { + assertDefined(input.amount); + assertDefined(input.denom); + return { + amount: input.amount, + denom: input.denom, + }; +} + export class StargateClient { private readonly tmClient: TendermintClient; @@ -89,16 +98,7 @@ export class StargateClient { const request = QueryAllBalancesRequest.encode({ address: Bech32.decode(address).data }).finish(); const responseData = await this.queryUnverified(path, request); const response = QueryAllBalancesResponse.decode(responseData); - return (response.balances || []).map( - (balance): Coin => { - assertDefined(balance.amount); - assertDefined(balance.denom); - return { - amount: balance.amount, - denom: balance.denom, - }; - }, - ); + return (response.balances || []).map(coinFromProto); } public disconnect(): void { From 6811a4574fee1d6643b7023e35621447c9f1a0ed Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 6 Aug 2020 18:09:23 +0200 Subject: [PATCH 7/7] Rename to getAllBalancesUnverified --- packages/stargate/src/stargateclient.spec.ts | 6 +++--- packages/stargate/src/stargateclient.ts | 2 +- packages/stargate/types/stargateclient.d.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/stargate/src/stargateclient.spec.ts b/packages/stargate/src/stargateclient.spec.ts index 8d66063b..484a1eb8 100644 --- a/packages/stargate/src/stargateclient.spec.ts +++ b/packages/stargate/src/stargateclient.spec.ts @@ -64,12 +64,12 @@ describe("StargateClient", () => { }); }); - describe("getUnverifiedAllBalances", () => { + describe("getAllBalancesUnverified", () => { it("returns all balances for unused account", async () => { pendingWithoutSimapp(); const client = await StargateClient.connect(simapp.tendermintUrl); - const balances = await client.getUnverifiedAllBalances(unused.address); + const balances = await client.getAllBalancesUnverified(unused.address); expect(balances).toEqual([ { amount: unused.balanceFee, @@ -85,7 +85,7 @@ describe("StargateClient", () => { it("returns an empty list for non-existent account", async () => { pendingWithoutSimapp(); const client = await StargateClient.connect(simapp.tendermintUrl); - const balances = await client.getUnverifiedAllBalances(nonExistentAddress); + const balances = await client.getAllBalancesUnverified(nonExistentAddress); expect(balances).toEqual([]); }); }); diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index b643b04c..4c9094f7 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -93,7 +93,7 @@ export class StargateClient { * Uses the grpc queries (which iterates over the store internally), and we cannot get * proofs from such a method. */ - public async getUnverifiedAllBalances(address: string): Promise { + public async getAllBalancesUnverified(address: string): Promise { const path = "/cosmos.bank.Query/AllBalances"; const request = QueryAllBalancesRequest.encode({ address: Bech32.decode(address).data }).finish(); const responseData = await this.queryUnverified(path, request); diff --git a/packages/stargate/types/stargateclient.d.ts b/packages/stargate/types/stargateclient.d.ts index 335a0d75..0503db45 100644 --- a/packages/stargate/types/stargateclient.d.ts +++ b/packages/stargate/types/stargateclient.d.ts @@ -15,7 +15,7 @@ export declare class StargateClient { * Uses the grpc queries (which iterates over the store internally), and we cannot get * proofs from such a method. */ - getUnverifiedAllBalances(address: string): Promise; + getAllBalancesUnverified(address: string): Promise; disconnect(): void; private queryVerified; private queryUnverified;