169 lines
5.8 KiB
TypeScript
169 lines
5.8 KiB
TypeScript
/* eslint-disable @typescript-eslint/naming-convention */
|
|
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 { arrayContentEquals, assert, assertDefined } from "@cosmjs/utils";
|
|
import Long from "long";
|
|
|
|
import { cosmos } from "./generated/codecimpl";
|
|
|
|
export interface Account {
|
|
/** Bech32 account address */
|
|
readonly address: string;
|
|
readonly pubkey: PubKey | null;
|
|
readonly accountNumber: number;
|
|
readonly sequence: number;
|
|
}
|
|
|
|
export interface SequenceResponse {
|
|
readonly accountNumber: number;
|
|
readonly sequence: number;
|
|
}
|
|
|
|
function uint64FromProto(input: number | Long): Uint64 {
|
|
return Uint64.fromString(input.toString());
|
|
}
|
|
|
|
function coinFromProto(input: cosmos.ICoin): Coin {
|
|
assertDefined(input.amount);
|
|
assertDefined(input.denom);
|
|
assert(input.amount !== null);
|
|
assert(input.denom !== null);
|
|
return {
|
|
amount: input.amount,
|
|
denom: input.denom,
|
|
};
|
|
}
|
|
|
|
export class StargateClient {
|
|
private readonly tmClient: TendermintClient;
|
|
|
|
public static async connect(endpoint: string): Promise<StargateClient> {
|
|
const tmClient = await TendermintClient.connect(endpoint);
|
|
return new StargateClient(tmClient);
|
|
}
|
|
|
|
private constructor(tmClient: TendermintClient) {
|
|
this.tmClient = tmClient;
|
|
}
|
|
|
|
public async getAccount(searchAddress: string): Promise<Account | null> {
|
|
const { prefix, data: binAddress } = Bech32.decode(searchAddress);
|
|
// https://github.com/cosmos/cosmos-sdk/blob/8cab43c8120fec5200c3459cbf4a92017bb6f287/x/auth/types/keys.go#L29-L32
|
|
const accountKey = Uint8Array.from([0x01, ...binAddress]);
|
|
const responseData = await this.queryVerified("acc", accountKey);
|
|
|
|
if (responseData.length === 0) return null;
|
|
|
|
const { typeUrl, value } = decodeAny(responseData);
|
|
switch (typeUrl) {
|
|
case "/cosmos.auth.BaseAccount": {
|
|
const { address, pubKey, accountNumber, sequence } = cosmos.auth.BaseAccount.decode(value);
|
|
// Pubkey is still Amino-encoded in BaseAccount (https://github.com/cosmos/cosmos-sdk/issues/6886)
|
|
const pubkey = pubKey.length ? decodeAminoPubkey(pubKey) : null;
|
|
return {
|
|
address: Bech32.encode(prefix, address),
|
|
pubkey: pubkey,
|
|
accountNumber: uint64FromProto(accountNumber).toNumber(),
|
|
sequence: uint64FromProto(sequence).toNumber(),
|
|
};
|
|
}
|
|
default:
|
|
throw new Error(`Unsupported type: '${typeUrl}'`);
|
|
}
|
|
}
|
|
|
|
public async getSequence(address: string): Promise<SequenceResponse | null> {
|
|
const account = await this.getAccount(address);
|
|
if (account) {
|
|
return {
|
|
accountNumber: uint64FromProto(account.accountNumber).toNumber(),
|
|
sequence: uint64FromProto(account.sequence).toNumber(),
|
|
};
|
|
} else {
|
|
return 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)
|
|
// 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 } = cosmos.Coin.decode(responseData);
|
|
if (denom === "") {
|
|
return null;
|
|
} else {
|
|
return {
|
|
amount: amount,
|
|
denom: denom,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 = cosmos.bank.QueryAllBalancesRequest.encode({
|
|
address: Bech32.decode(address).data,
|
|
}).finish();
|
|
const responseData = await this.queryUnverified(path, request);
|
|
const response = cosmos.bank.QueryAllBalancesResponse.decode(responseData);
|
|
return response.balances.map(coinFromProto);
|
|
}
|
|
|
|
public disconnect(): void {
|
|
this.tmClient.disconnect();
|
|
}
|
|
|
|
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
|
|
// https://github.com/cosmos/cosmos-sdk/blob/8cab43c8120fec5200c3459cbf4a92017bb6f287/x/auth/types/keys.go#L12
|
|
path: `/store/${store}/key`,
|
|
data: key,
|
|
prove: true,
|
|
});
|
|
|
|
if (response.code) {
|
|
throw new Error(`Query failed with (${response.code}): ${response.log}`);
|
|
}
|
|
|
|
if (!arrayContentEquals(response.key, key)) {
|
|
throw new Error(`Response key ${toHex(response.key)} doesn't match query key ${toHex(key)}`);
|
|
}
|
|
|
|
// TODO: implement proof verification
|
|
// https://github.com/CosmWasm/cosmjs/issues/347
|
|
|
|
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;
|
|
}
|
|
}
|