Merge pull request #355 from CosmWasm/balance-queries
Add getUnverifiedAllBalances
This commit is contained in:
commit
0cb2c9fae7
@ -1,3 +1,3 @@
|
||||
export { BaseAccount } from "./accounts";
|
||||
export { decodeAny } from "./any";
|
||||
export { Coin } from "./msgs";
|
||||
export { cosmosField } from "./decorator";
|
||||
|
||||
2
packages/proto-signing/types/index.d.ts
vendored
2
packages/proto-signing/types/index.d.ts
vendored
@ -1,3 +1,3 @@
|
||||
export { BaseAccount } from "./accounts";
|
||||
export { decodeAny } from "./any";
|
||||
export { Coin } from "./msgs";
|
||||
export { cosmosField } from "./decorator";
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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;
|
||||
23
packages/stargate/src/query/allbalances.ts
Normal file
23
packages/stargate/src/query/allbalances.ts
Normal file
@ -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;
|
||||
}
|
||||
18
packages/stargate/src/query/pagination.ts
Normal file
18
packages/stargate/src/query/pagination.ts
Normal file
@ -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;
|
||||
}
|
||||
@ -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,41 @@ 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();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAllBalancesUnverified", () => {
|
||||
it("returns all balances for unused account", async () => {
|
||||
pendingWithoutSimapp();
|
||||
const client = await StargateClient.connect(simapp.tendermintUrl);
|
||||
|
||||
const balances = await client.getAllBalancesUnverified(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.getAllBalancesUnverified(nonExistentAddress);
|
||||
expect(balances).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
/* 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";
|
||||
|
||||
import { BaseAccount } from "./query/accounts";
|
||||
import { QueryAllBalancesRequest, QueryAllBalancesResponse } from "./query/allbalances";
|
||||
|
||||
export interface GetSequenceResult {
|
||||
readonly accountNumber: number;
|
||||
readonly sequence: number;
|
||||
@ -15,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;
|
||||
|
||||
@ -33,7 +46,7 @@ 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);
|
||||
@ -49,13 +62,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<Coin | null> {
|
||||
// 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 +73,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);
|
||||
|
||||
@ -80,6 +87,20 @@ 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 getAllBalancesUnverified(address: string): Promise<readonly Coin[]> {
|
||||
const path = "/cosmos.bank.Query/AllBalances";
|
||||
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(coinFromProto);
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
this.tmClient.disconnect();
|
||||
}
|
||||
@ -107,4 +128,18 @@ export class StargateClient {
|
||||
|
||||
return response.value;
|
||||
}
|
||||
|
||||
private async queryUnverified(path: string, request: Uint8Array): Promise<Uint8Array> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,3 +23,5 @@ export const unused = {
|
||||
balanceStaking: "10000000", // 10 STAKE
|
||||
balanceFee: "1000000000", // 1000 COSM
|
||||
};
|
||||
|
||||
export const nonExistentAddress = "cosmos1p79apjaufyphcmsn4g07cynqf0wyjuezqu84hd";
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"baseUrl": ".",
|
||||
"outDir": "build",
|
||||
"declarationDir": "build/types",
|
||||
"experimentalDecorators": true,
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
|
||||
11
packages/stargate/types/query/allbalances.d.ts
vendored
Normal file
11
packages/stargate/types/query/allbalances.d.ts
vendored
Normal file
@ -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;
|
||||
}
|
||||
7
packages/stargate/types/query/pagination.d.ts
vendored
Normal file
7
packages/stargate/types/query/pagination.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/// <reference types="long" />
|
||||
import { Message } from "protobufjs";
|
||||
export declare class PageRequest extends Message {}
|
||||
export declare class PageResponse extends Message {
|
||||
readonly nextKey?: Uint8Array;
|
||||
readonly total?: Long | number;
|
||||
}
|
||||
17
packages/stargate/types/stargateclient.d.ts
vendored
17
packages/stargate/types/stargateclient.d.ts
vendored
@ -1,3 +1,4 @@
|
||||
import { Coin } from "@cosmjs/launchpad";
|
||||
export interface GetSequenceResult {
|
||||
readonly accountNumber: number;
|
||||
readonly sequence: number;
|
||||
@ -7,13 +8,15 @@ export declare class StargateClient {
|
||||
static connect(endpoint: string): Promise<StargateClient>;
|
||||
private constructor();
|
||||
getSequence(address: string): Promise<GetSequenceResult>;
|
||||
getBalance(
|
||||
address: string,
|
||||
searchDenom: string,
|
||||
): Promise<{
|
||||
readonly denom: string;
|
||||
readonly amount: string;
|
||||
} | null>;
|
||||
getBalance(address: string, searchDenom: string): Promise<Coin | null>;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
getAllBalancesUnverified(address: string): Promise<readonly Coin[]>;
|
||||
disconnect(): void;
|
||||
private queryVerified;
|
||||
private queryUnverified;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user