diff --git a/packages/proto-signing/src/index.ts b/packages/proto-signing/src/index.ts index c279effd..c52b350f 100644 --- a/packages/proto-signing/src/index.ts +++ b/packages/proto-signing/src/index.ts @@ -1,2 +1,3 @@ export { BaseAccount } from "./accounts"; export { decodeAny } from "./any"; +export { Coin } from "./msgs"; diff --git a/packages/proto-signing/types/index.d.ts b/packages/proto-signing/types/index.d.ts index c279effd..c52b350f 100644 --- a/packages/proto-signing/types/index.d.ts +++ b/packages/proto-signing/types/index.d.ts @@ -1,2 +1,3 @@ export { BaseAccount } from "./accounts"; export { decodeAny } from "./any"; +export { Coin } from "./msgs"; diff --git a/packages/stargate/src/stargateclient.spec.ts b/packages/stargate/src/stargateclient.spec.ts index 6956aa71..94ed22e0 100644 --- a/packages/stargate/src/stargateclient.spec.ts +++ b/packages/stargate/src/stargateclient.spec.ts @@ -23,4 +23,34 @@ describe("StargateClient", () => { client.disconnect(); }); }); + + describe("getBalance", () => { + it("works for different existing balances", async () => { + pendingWithoutSimapp(); + const client = await StargateClient.connect(simapp.tendermintUrl); + + const response1 = await client.getBalance(unused.address, "ucosm"); + expect(response1).toEqual({ + amount: "1000000000", + denom: "ucosm", + }); + const response2 = await client.getBalance(unused.address, "ustake"); + expect(response2).toEqual({ + amount: "1000000000", + denom: "ustake", + }); + + client.disconnect(); + }); + + it("returns null for non-existent balance", async () => { + pendingWithoutSimapp(); + const client = await StargateClient.connect(simapp.tendermintUrl); + + const response = await client.getBalance(unused.address, "gintonic"); + expect(response).toBeNull(); + + client.disconnect(); + }); + }); }); diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 62a65356..4b96bf19 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { Bech32, toHex } from "@cosmjs/encoding"; +import { Bech32, toAscii, toHex } from "@cosmjs/encoding"; import { Uint64 } from "@cosmjs/math"; -import { BaseAccount, decodeAny } from "@cosmjs/proto-signing"; +import { BaseAccount, Coin, decodeAny } from "@cosmjs/proto-signing"; import { Client as TendermintClient } from "@cosmjs/tendermint-rpc"; import { assert } from "@cosmjs/utils"; import Long from "long"; @@ -49,6 +49,37 @@ export class StargateClient { } } + public async getBalance( + address: string, + searchDenom: string, + ): Promise<{ + readonly denom: string; + readonly amount: string; + } | 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) + // it seem like prefix stores just do a dumb concat with the keys (no tricks to avoid overlap) + // https://github.com/cosmos/cosmos-sdk/blob/2879c0702c87dc9dd828a8c42b9224dc054e28ad/store/prefix/store.go#L61-L64 + // https://github.com/cosmos/cosmos-sdk/blob/2879c0702c87dc9dd828a8c42b9224dc054e28ad/store/prefix/store.go#L37-L43 + const binAddress = Bech32.decode(address).data; + const bankKey = Uint8Array.from([...toAscii("balances"), ...binAddress, ...toAscii(searchDenom)]); + + const responseData = await this.queryVerified("bank", bankKey); + const { amount, denom } = Coin.decode(responseData); + assert(amount !== undefined); + assert(denom !== undefined); + + if (denom === "") { + return null; + } else { + return { + amount: amount, + denom: denom, + }; + } + } + public disconnect(): void { this.tmClient.disconnect(); } diff --git a/packages/stargate/types/stargateclient.d.ts b/packages/stargate/types/stargateclient.d.ts index 55abe8c4..85cafca9 100644 --- a/packages/stargate/types/stargateclient.d.ts +++ b/packages/stargate/types/stargateclient.d.ts @@ -7,6 +7,13 @@ 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>; disconnect(): void; private queryVerified; }