diff --git a/CHANGELOG.md b/CHANGELOG.md index 874feff9..1ca99ac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to - @cosmjs/cosmwasm-stargate: Codec adapted to support wasmd 0.16. Older versions of wasmd are not supported anymore. +- @cosmjs/stargate: Let `AuthExtension.account` and + `AuthExtension.unverified.account` return an account of type `Any`. This makes + the caller responsible for decoding the type. +- @cosmjs/stargate: Remove `accountFromProto` in favour of `accountFromAny`. - @cosmjs/tendermint-rpc: The fields `CommitSignature.validatorAddress`, `.timestamp` and `.signature` are now optional. They are unset when `blockIdFlag` is `BlockIdFlag.Absent`. The decoding into `CommitSignature` is diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.ts index ace4fc0f..3d7ec1e9 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.ts @@ -19,7 +19,7 @@ import { import { Uint53 } from "@cosmjs/math"; import { Account, - accountFromProto, + accountFromAny, AuthExtension, BankExtension, BroadcastTxResponse, @@ -87,7 +87,7 @@ export class CosmWasmClient { public async getAccount(searchAddress: string): Promise { const account = await this.queryClient.auth.account(searchAddress); - return account ? accountFromProto(account) : null; + return account ? accountFromAny(account) : null; } public async getSequence(address: string): Promise { diff --git a/packages/stargate/src/index.ts b/packages/stargate/src/index.ts index c2af3e81..6fa057dd 100644 --- a/packages/stargate/src/index.ts +++ b/packages/stargate/src/index.ts @@ -18,7 +18,7 @@ export { } from "./queries"; export { Account, - accountFromProto, + accountFromAny, assertIsBroadcastTxSuccess, BroadcastTxFailure, BroadcastTxResponse, diff --git a/packages/stargate/src/queries/auth.spec.ts b/packages/stargate/src/queries/auth.spec.ts index 86d6f9d0..c95d3fca 100644 --- a/packages/stargate/src/queries/auth.spec.ts +++ b/packages/stargate/src/queries/auth.spec.ts @@ -4,6 +4,7 @@ import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { assert } from "@cosmjs/utils"; import Long from "long"; +import { BaseAccount } from "../codec/cosmos/auth/v1beta1/auth"; import { Any } from "../codec/google/protobuf/any"; import { nonExistentAddress, pendingWithoutSimapp, simapp, unused, validator } from "../testutils.spec"; import { AuthExtension, setupAuthExtension } from "./auth"; @@ -24,7 +25,8 @@ describe("AuthExtension", () => { const account = await client.auth.account(unused.address); assert(account); - expect(account).toEqual({ + expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount"); + expect(BaseAccount.decode(account.value)).toEqual({ address: unused.address, // pubKey not set accountNumber: Long.fromNumber(unused.accountNumber, true), @@ -40,10 +42,10 @@ describe("AuthExtension", () => { const account = await client.auth.account(validator.delegatorAddress); assert(account); - const pubkey = encodePubkey(validator.pubkey); - expect(account).toEqual({ + expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount"); + expect(BaseAccount.decode(account.value)).toEqual({ address: validator.delegatorAddress, - pubKey: Any.fromPartial(pubkey), + pubKey: Any.fromPartial(encodePubkey(validator.pubkey)), accountNumber: Long.fromNumber(0, true), sequence: Long.fromNumber(validator.sequence, true), }); @@ -70,7 +72,8 @@ describe("AuthExtension", () => { const account = await client.auth.unverified.account(unused.address); assert(account); - expect(account).toEqual({ + expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount"); + expect(BaseAccount.decode(account.value)).toEqual({ address: unused.address, // pubKey not set accountNumber: Long.fromNumber(unused.accountNumber, true), @@ -86,10 +89,10 @@ describe("AuthExtension", () => { const account = await client.auth.unverified.account(validator.delegatorAddress); assert(account); - const pubkey = encodePubkey(validator.pubkey); - expect(account).toEqual({ + expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount"); + expect(BaseAccount.decode(account.value)).toEqual({ address: validator.delegatorAddress, - pubKey: Any.fromPartial(pubkey), + pubKey: Any.fromPartial(encodePubkey(validator.pubkey)), accountNumber: Long.fromNumber(0, true), sequence: Long.fromNumber(validator.sequence, true), }); diff --git a/packages/stargate/src/queries/auth.ts b/packages/stargate/src/queries/auth.ts index 85531e63..366f46ce 100644 --- a/packages/stargate/src/queries/auth.ts +++ b/packages/stargate/src/queries/auth.ts @@ -1,7 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { assert } from "@cosmjs/utils"; - -import { BaseAccount } from "../codec/cosmos/auth/v1beta1/auth"; import { QueryClientImpl } from "../codec/cosmos/auth/v1beta1/query"; import { Any } from "../codec/google/protobuf/any"; import { QueryClient } from "./queryclient"; @@ -9,9 +5,23 @@ import { createRpc, toAccAddress } from "./utils"; export interface AuthExtension { readonly auth: { - readonly account: (address: string) => Promise; + /** + * Returns an account if it exists and `null` otherwise. + * + * The account is a protobuf Any in order to be able to support many different + * account types in one API. The caller needs to switch over the expeced and supported + * `typeUrl` and decode the `value` using its own type decoder. + */ + readonly account: (address: string) => Promise; readonly unverified: { - readonly account: (address: string) => Promise; + /** + * Returns an account if it exists and `null` otherwise. + * + * The account is a protobuf Any in order to be able to support many different + * account types in one API. The caller needs to switch over the expeced and supported + * `typeUrl` and decode the `value` using its own type decoder. + */ + readonly account: (address: string) => Promise; }; }; } @@ -29,27 +39,13 @@ export function setupAuthExtension(base: QueryClient): AuthExtension { const key = Uint8Array.from([0x01, ...toAccAddress(address)]); const responseData = await base.queryVerified("acc", key); if (responseData.length === 0) return null; - const account = Any.decode(responseData); - switch (account.typeUrl) { - case "/cosmos.auth.v1beta1.BaseAccount": { - return BaseAccount.decode(account.value); - } - default: - throw new Error(`Unsupported type: '${account.typeUrl}'`); - } + return Any.decode(responseData); }, unverified: { account: async (address: string) => { const { account } = await queryService.Account({ address: address }); if (!account) return null; - switch (account.typeUrl) { - case "/cosmos.auth.v1beta1.BaseAccount": { - assert(account.value); - return BaseAccount.decode(account.value); - } - default: - throw new Error(`Unsupported type: '${account.typeUrl}'`); - } + return account; }, }, }, diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index c79f9bec..12df5479 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -16,12 +16,13 @@ import { Tendermint34Client, toRfc3339WithNanoseconds, } from "@cosmjs/tendermint-rpc"; -import { assert, assertDefinedAndNotNull } from "@cosmjs/utils"; +import { assertDefinedAndNotNull } from "@cosmjs/utils"; import Long from "long"; import { BaseAccount } from "./codec/cosmos/auth/v1beta1/auth"; import { MsgData, TxMsgData } from "./codec/cosmos/base/abci/v1beta1/abci"; import { Coin } from "./codec/cosmos/base/v1beta1/coin"; +import { Any } from "./codec/google/protobuf/any"; import { AuthExtension, BankExtension, QueryClient, setupAuthExtension, setupBankExtension } from "./queries"; /** A transaction that is indexed as part of the transaction history */ @@ -91,10 +92,9 @@ function uint64FromProto(input: number | Long | null | undefined): Uint64 { return Uint64.fromString(input.toString()); } -export function accountFromProto(input: BaseAccount): Account { +function accountFromBaseAccount(input: BaseAccount): Account { const { address, pubKey, accountNumber, sequence } = input; const pubkey = decodePubkey(pubKey); - assert(address); return { address: address, pubkey: pubkey, @@ -103,6 +103,24 @@ export function accountFromProto(input: BaseAccount): Account { }; } +/** + * Takes an `Any` encoded account from the chain and extracts some common + * `Account` information from it. This is supposed to support the most relevant + * common Cosmos SDK account types. If you need support for exotix account types, + * you'll need to write your own account decoder. + */ +export function accountFromAny(input: Any): Account { + const { typeUrl, value } = input; + + switch (typeUrl) { + case "/cosmos.auth.v1beta1.BaseAccount": { + return accountFromBaseAccount(BaseAccount.decode(value)); + } + default: + throw new Error(`Unsupported type: '${typeUrl}'`); + } +} + export function coinFromProto(input: Coin): Coin { assertDefinedAndNotNull(input.amount); assertDefinedAndNotNull(input.denom); @@ -151,14 +169,14 @@ export class StargateClient { // this is nice to display data to the user, but is slower public async getAccount(searchAddress: string): Promise { const account = await this.queryClient.auth.account(searchAddress); - return account ? accountFromProto(account) : null; + return account ? accountFromAny(account) : null; } // if we just need to get the sequence for signing a transaction, let's make this faster // (no need to wait a block before submitting) public async getAccountUnverified(searchAddress: string): Promise { const account = await this.queryClient.auth.unverified.account(searchAddress); - return account ? accountFromProto(account) : null; + return account ? accountFromAny(account) : null; } public async getSequence(address: string): Promise {