diff --git a/CHANGELOG.md b/CHANGELOG.md index 874feff9..df8f1d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,21 @@ and this project adheres to ## [Unreleased] +### Added + +- @cosmjs/stargate: Add support for different account types in `accountFromAny` + and `StargateClient`. Added `ModuleAccount` and vesting accounts + `BaseVestingAccount`, `ContinuousVestingAccount`, `DelayedVestingAccount` and + `PeriodicVestingAccount`. + ### Changed - @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/README.md b/README.md index 67ee8867..21b78451 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,15 @@ discussion please reach out to the team. [cosmwasm community call]: https://github.com/CosmWasm/cosmwasm/issues?q=label%3A%22Community+Call+%F0%9F%97%BA%F0%9F%93%9E%22 +## Known limitations + +### 0.24 + +1. `AuthExtension` and all higher level Stargate clients only support + `BaseAccount`s for all functionality, including getting account numbers and + sequences for transaction signing. This will be implemented for all common + Cosmos SDK account types in the 0.25 series. + ## Get in touch The CosmJS development team is happy to get in touch with you for all questions diff --git a/packages/cli/examples/get_akash_vesting_account.ts b/packages/cli/examples/get_akash_vesting_account.ts new file mode 100644 index 00000000..89134810 --- /dev/null +++ b/packages/cli/examples/get_akash_vesting_account.ts @@ -0,0 +1,11 @@ +import { QueryClient, setupAuthExtension } from "@cosmjs/stargate"; +import { Any } from "@cosmjs/stargate/build/codec/google/protobuf/any"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; + +// https://github.com/ovrclk/net/blob/24ddbb427/mainnet/rpc-nodes.txt +const tmClient = await Tendermint34Client.connect("http://rpc.akash.forbole.com:80"); +const client = QueryClient.withExtensions(tmClient, setupAuthExtension); + +// Arbitrary entry from https://raw.githubusercontent.com/ovrclk/net/24ddbb427/mainnet/genesis.json +const account = await client.auth.unverified.account("akash1qy0vur3fl2ucztpzcrfea7mc8jwz8xjmvq7qvy"); +console.log(Any.toJSON(account)) 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/scripts/define-proto.sh b/packages/stargate/scripts/define-proto.sh index 32bc6338..d5a88522 100755 --- a/packages/stargate/scripts/define-proto.sh +++ b/packages/stargate/scripts/define-proto.sh @@ -33,6 +33,7 @@ protoc \ "$COSMOS_PROTO_DIR/cosmos/staking/v1beta1/tx.proto" \ "$COSMOS_PROTO_DIR/cosmos/tx/signing/v1beta1/signing.proto" \ "$COSMOS_PROTO_DIR/cosmos/tx/v1beta1/tx.proto" \ + "$COSMOS_PROTO_DIR/cosmos/vesting/v1beta1/vesting.proto" \ "$COSMOS_PROTO_DIR/ibc/core/channel/v1/channel.proto" \ "$COSMOS_PROTO_DIR/ibc/core/channel/v1/query.proto" \ "$COSMOS_PROTO_DIR/ibc/core/client/v1/client.proto" \ diff --git a/packages/stargate/src/accounts.spec.ts b/packages/stargate/src/accounts.spec.ts new file mode 100644 index 00000000..62fafec0 --- /dev/null +++ b/packages/stargate/src/accounts.spec.ts @@ -0,0 +1,26 @@ +import { accountFromAny } from "./accounts"; +import { Any } from "./codec/google/protobuf/any"; + +describe("accounts", () => { + describe("accountFromAny", () => { + it("works for PeriodicVestingAccount", () => { + // Queried from chain via `packages/cli/examples/get_akash_vesting_account.ts`. + const any = Any.fromJSON({ + typeUrl: "/cosmos.vesting.v1beta1.PeriodicVestingAccount", + value: + "CsMBCnoKLGFrYXNoMXF5MHZ1cjNmbDJ1Y3p0cHpjcmZlYTdtYzhqd3o4eGptdnE3cXZ5EkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/XsdhwSIKU73TltD9STcaS07FNw0szR4a+oDLr6vikaGDggGxIUCgR1YWt0EgwxNjY2NjY2NzAwMDAaEwoEdWFrdBILMzcxOTAzMzAwMDAiFAoEdWFrdBIMMTY2NjY2NjcwMDAwKOC9wZkGEODvt/sFGhoIgOeEDxITCgR1YWt0Egs4MzMzMzMzNTAwMBoaCIC/ugcSEwoEdWFrdBILNDE2NjY2Njc1MDAaGgiAqMoHEhMKBHVha3QSCzQxNjY2NjY3NTAw", + }); + + const account = accountFromAny(any); + expect(account).toEqual({ + address: "akash1qy0vur3fl2ucztpzcrfea7mc8jwz8xjmvq7qvy", + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "A/XsdhwSIKU73TltD9STcaS07FNw0szR4a+oDLr6vika", + }, + accountNumber: 56, + sequence: 27, + }); + }); + }); +}); diff --git a/packages/stargate/src/accounts.ts b/packages/stargate/src/accounts.ts new file mode 100644 index 00000000..58280523 --- /dev/null +++ b/packages/stargate/src/accounts.ts @@ -0,0 +1,85 @@ +import { PubKey } from "@cosmjs/launchpad"; +import { Uint64 } from "@cosmjs/math"; +import { decodePubkey } from "@cosmjs/proto-signing"; +import { assert } from "@cosmjs/utils"; +import Long from "long"; + +import { BaseAccount, ModuleAccount } from "./codec/cosmos/auth/v1beta1/auth"; +import { + BaseVestingAccount, + ContinuousVestingAccount, + DelayedVestingAccount, + PeriodicVestingAccount, +} from "./codec/cosmos/vesting/v1beta1/vesting"; +import { Any } from "./codec/google/protobuf/any"; + +export interface Account { + /** Bech32 account address */ + readonly address: string; + readonly pubkey: PubKey | null; + readonly accountNumber: number; + readonly sequence: number; +} + +function uint64FromProto(input: number | Long): Uint64 { + return Uint64.fromString(input.toString()); +} + +function accountFromBaseAccount(input: BaseAccount): Account { + const { address, pubKey, accountNumber, sequence } = input; + const pubkey = decodePubkey(pubKey); + return { + address: address, + pubkey: pubkey, + accountNumber: uint64FromProto(accountNumber).toNumber(), + sequence: uint64FromProto(sequence).toNumber(), + }; +} + +/** + * 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 exotic account types, + * you'll need to write your own account decoder. + */ +export function accountFromAny(input: Any): Account { + const { typeUrl, value } = input; + + switch (typeUrl) { + // auth + + case "/cosmos.auth.v1beta1.BaseAccount": + return accountFromBaseAccount(BaseAccount.decode(value)); + case "/cosmos.auth.v1beta1.ModuleAccount": { + const baseAccount = ModuleAccount.decode(value).baseAccount; + assert(baseAccount); + return accountFromBaseAccount(baseAccount); + } + + // vesting + + case "/cosmos.vesting.v1beta1.BaseVestingAccount": { + const baseAccount = BaseVestingAccount.decode(value)?.baseAccount; + assert(baseAccount); + return accountFromBaseAccount(baseAccount); + } + case "/cosmos.vesting.v1beta1.ContinuousVestingAccount": { + const baseAccount = ContinuousVestingAccount.decode(value)?.baseVestingAccount?.baseAccount; + assert(baseAccount); + return accountFromBaseAccount(baseAccount); + } + case "/cosmos.vesting.v1beta1.DelayedVestingAccount": { + const baseAccount = DelayedVestingAccount.decode(value)?.baseVestingAccount?.baseAccount; + assert(baseAccount); + return accountFromBaseAccount(baseAccount); + } + case "/cosmos.vesting.v1beta1.PeriodicVestingAccount": { + const baseAccount = PeriodicVestingAccount.decode(value)?.baseVestingAccount?.baseAccount; + assert(baseAccount); + return accountFromBaseAccount(baseAccount); + } + + default: + throw new Error(`Unsupported type: '${typeUrl}'`); + } +} diff --git a/packages/stargate/src/codec/cosmos/vesting/v1beta1/vesting.ts b/packages/stargate/src/codec/cosmos/vesting/v1beta1/vesting.ts new file mode 100644 index 00000000..f9f2d0ea --- /dev/null +++ b/packages/stargate/src/codec/cosmos/vesting/v1beta1/vesting.ts @@ -0,0 +1,520 @@ +/* eslint-disable */ +import { BaseAccount } from "../../../cosmos/auth/v1beta1/auth"; +import Long from "long"; +import { Coin } from "../../../cosmos/base/v1beta1/coin"; +import _m0 from "protobufjs/minimal"; + +export const protobufPackage = "cosmos.vesting.v1beta1"; + +/** + * BaseVestingAccount implements the VestingAccount interface. It contains all + * the necessary fields needed for any vesting account implementation. + */ +export interface BaseVestingAccount { + baseAccount?: BaseAccount; + originalVesting: Coin[]; + delegatedFree: Coin[]; + delegatedVesting: Coin[]; + endTime: Long; +} + +/** + * ContinuousVestingAccount implements the VestingAccount interface. It + * continuously vests by unlocking coins linearly with respect to time. + */ +export interface ContinuousVestingAccount { + baseVestingAccount?: BaseVestingAccount; + startTime: Long; +} + +/** + * DelayedVestingAccount implements the VestingAccount interface. It vests all + * coins after a specific time, but non prior. In other words, it keeps them + * locked until a specified time. + */ +export interface DelayedVestingAccount { + baseVestingAccount?: BaseVestingAccount; +} + +/** Period defines a length of time and amount of coins that will vest. */ +export interface Period { + length: Long; + amount: Coin[]; +} + +/** + * PeriodicVestingAccount implements the VestingAccount interface. It + * periodically vests by unlocking coins during each specified period. + */ +export interface PeriodicVestingAccount { + baseVestingAccount?: BaseVestingAccount; + startTime: Long; + vestingPeriods: Period[]; +} + +const baseBaseVestingAccount: object = { endTime: Long.ZERO }; + +export const BaseVestingAccount = { + encode(message: BaseVestingAccount, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.baseAccount !== undefined) { + BaseAccount.encode(message.baseAccount, writer.uint32(10).fork()).ldelim(); + } + for (const v of message.originalVesting) { + Coin.encode(v!, writer.uint32(18).fork()).ldelim(); + } + for (const v of message.delegatedFree) { + Coin.encode(v!, writer.uint32(26).fork()).ldelim(); + } + for (const v of message.delegatedVesting) { + Coin.encode(v!, writer.uint32(34).fork()).ldelim(); + } + if (!message.endTime.isZero()) { + writer.uint32(40).int64(message.endTime); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): BaseVestingAccount { + const reader = input instanceof Uint8Array ? new _m0.Reader(input) : input; + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseBaseVestingAccount } as BaseVestingAccount; + message.originalVesting = []; + message.delegatedFree = []; + message.delegatedVesting = []; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.baseAccount = BaseAccount.decode(reader, reader.uint32()); + break; + case 2: + message.originalVesting.push(Coin.decode(reader, reader.uint32())); + break; + case 3: + message.delegatedFree.push(Coin.decode(reader, reader.uint32())); + break; + case 4: + message.delegatedVesting.push(Coin.decode(reader, reader.uint32())); + break; + case 5: + message.endTime = reader.int64() as Long; + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): BaseVestingAccount { + const message = { ...baseBaseVestingAccount } as BaseVestingAccount; + message.originalVesting = []; + message.delegatedFree = []; + message.delegatedVesting = []; + if (object.baseAccount !== undefined && object.baseAccount !== null) { + message.baseAccount = BaseAccount.fromJSON(object.baseAccount); + } else { + message.baseAccount = undefined; + } + if (object.originalVesting !== undefined && object.originalVesting !== null) { + for (const e of object.originalVesting) { + message.originalVesting.push(Coin.fromJSON(e)); + } + } + if (object.delegatedFree !== undefined && object.delegatedFree !== null) { + for (const e of object.delegatedFree) { + message.delegatedFree.push(Coin.fromJSON(e)); + } + } + if (object.delegatedVesting !== undefined && object.delegatedVesting !== null) { + for (const e of object.delegatedVesting) { + message.delegatedVesting.push(Coin.fromJSON(e)); + } + } + if (object.endTime !== undefined && object.endTime !== null) { + message.endTime = Long.fromString(object.endTime); + } else { + message.endTime = Long.ZERO; + } + return message; + }, + + toJSON(message: BaseVestingAccount): unknown { + const obj: any = {}; + message.baseAccount !== undefined && + (obj.baseAccount = message.baseAccount ? BaseAccount.toJSON(message.baseAccount) : undefined); + if (message.originalVesting) { + obj.originalVesting = message.originalVesting.map((e) => (e ? Coin.toJSON(e) : undefined)); + } else { + obj.originalVesting = []; + } + if (message.delegatedFree) { + obj.delegatedFree = message.delegatedFree.map((e) => (e ? Coin.toJSON(e) : undefined)); + } else { + obj.delegatedFree = []; + } + if (message.delegatedVesting) { + obj.delegatedVesting = message.delegatedVesting.map((e) => (e ? Coin.toJSON(e) : undefined)); + } else { + obj.delegatedVesting = []; + } + message.endTime !== undefined && (obj.endTime = (message.endTime || Long.ZERO).toString()); + return obj; + }, + + fromPartial(object: DeepPartial): BaseVestingAccount { + const message = { ...baseBaseVestingAccount } as BaseVestingAccount; + message.originalVesting = []; + message.delegatedFree = []; + message.delegatedVesting = []; + if (object.baseAccount !== undefined && object.baseAccount !== null) { + message.baseAccount = BaseAccount.fromPartial(object.baseAccount); + } else { + message.baseAccount = undefined; + } + if (object.originalVesting !== undefined && object.originalVesting !== null) { + for (const e of object.originalVesting) { + message.originalVesting.push(Coin.fromPartial(e)); + } + } + if (object.delegatedFree !== undefined && object.delegatedFree !== null) { + for (const e of object.delegatedFree) { + message.delegatedFree.push(Coin.fromPartial(e)); + } + } + if (object.delegatedVesting !== undefined && object.delegatedVesting !== null) { + for (const e of object.delegatedVesting) { + message.delegatedVesting.push(Coin.fromPartial(e)); + } + } + if (object.endTime !== undefined && object.endTime !== null) { + message.endTime = object.endTime as Long; + } else { + message.endTime = Long.ZERO; + } + return message; + }, +}; + +const baseContinuousVestingAccount: object = { startTime: Long.ZERO }; + +export const ContinuousVestingAccount = { + encode(message: ContinuousVestingAccount, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.baseVestingAccount !== undefined) { + BaseVestingAccount.encode(message.baseVestingAccount, writer.uint32(10).fork()).ldelim(); + } + if (!message.startTime.isZero()) { + writer.uint32(16).int64(message.startTime); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): ContinuousVestingAccount { + const reader = input instanceof Uint8Array ? new _m0.Reader(input) : input; + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseContinuousVestingAccount } as ContinuousVestingAccount; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.baseVestingAccount = BaseVestingAccount.decode(reader, reader.uint32()); + break; + case 2: + message.startTime = reader.int64() as Long; + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): ContinuousVestingAccount { + const message = { ...baseContinuousVestingAccount } as ContinuousVestingAccount; + if (object.baseVestingAccount !== undefined && object.baseVestingAccount !== null) { + message.baseVestingAccount = BaseVestingAccount.fromJSON(object.baseVestingAccount); + } else { + message.baseVestingAccount = undefined; + } + if (object.startTime !== undefined && object.startTime !== null) { + message.startTime = Long.fromString(object.startTime); + } else { + message.startTime = Long.ZERO; + } + return message; + }, + + toJSON(message: ContinuousVestingAccount): unknown { + const obj: any = {}; + message.baseVestingAccount !== undefined && + (obj.baseVestingAccount = message.baseVestingAccount + ? BaseVestingAccount.toJSON(message.baseVestingAccount) + : undefined); + message.startTime !== undefined && (obj.startTime = (message.startTime || Long.ZERO).toString()); + return obj; + }, + + fromPartial(object: DeepPartial): ContinuousVestingAccount { + const message = { ...baseContinuousVestingAccount } as ContinuousVestingAccount; + if (object.baseVestingAccount !== undefined && object.baseVestingAccount !== null) { + message.baseVestingAccount = BaseVestingAccount.fromPartial(object.baseVestingAccount); + } else { + message.baseVestingAccount = undefined; + } + if (object.startTime !== undefined && object.startTime !== null) { + message.startTime = object.startTime as Long; + } else { + message.startTime = Long.ZERO; + } + return message; + }, +}; + +const baseDelayedVestingAccount: object = {}; + +export const DelayedVestingAccount = { + encode(message: DelayedVestingAccount, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.baseVestingAccount !== undefined) { + BaseVestingAccount.encode(message.baseVestingAccount, writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): DelayedVestingAccount { + const reader = input instanceof Uint8Array ? new _m0.Reader(input) : input; + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseDelayedVestingAccount } as DelayedVestingAccount; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.baseVestingAccount = BaseVestingAccount.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): DelayedVestingAccount { + const message = { ...baseDelayedVestingAccount } as DelayedVestingAccount; + if (object.baseVestingAccount !== undefined && object.baseVestingAccount !== null) { + message.baseVestingAccount = BaseVestingAccount.fromJSON(object.baseVestingAccount); + } else { + message.baseVestingAccount = undefined; + } + return message; + }, + + toJSON(message: DelayedVestingAccount): unknown { + const obj: any = {}; + message.baseVestingAccount !== undefined && + (obj.baseVestingAccount = message.baseVestingAccount + ? BaseVestingAccount.toJSON(message.baseVestingAccount) + : undefined); + return obj; + }, + + fromPartial(object: DeepPartial): DelayedVestingAccount { + const message = { ...baseDelayedVestingAccount } as DelayedVestingAccount; + if (object.baseVestingAccount !== undefined && object.baseVestingAccount !== null) { + message.baseVestingAccount = BaseVestingAccount.fromPartial(object.baseVestingAccount); + } else { + message.baseVestingAccount = undefined; + } + return message; + }, +}; + +const basePeriod: object = { length: Long.ZERO }; + +export const Period = { + encode(message: Period, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (!message.length.isZero()) { + writer.uint32(8).int64(message.length); + } + for (const v of message.amount) { + Coin.encode(v!, writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Period { + const reader = input instanceof Uint8Array ? new _m0.Reader(input) : input; + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...basePeriod } as Period; + message.amount = []; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.length = reader.int64() as Long; + break; + case 2: + message.amount.push(Coin.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Period { + const message = { ...basePeriod } as Period; + message.amount = []; + if (object.length !== undefined && object.length !== null) { + message.length = Long.fromString(object.length); + } else { + message.length = Long.ZERO; + } + if (object.amount !== undefined && object.amount !== null) { + for (const e of object.amount) { + message.amount.push(Coin.fromJSON(e)); + } + } + return message; + }, + + toJSON(message: Period): unknown { + const obj: any = {}; + message.length !== undefined && (obj.length = (message.length || Long.ZERO).toString()); + if (message.amount) { + obj.amount = message.amount.map((e) => (e ? Coin.toJSON(e) : undefined)); + } else { + obj.amount = []; + } + return obj; + }, + + fromPartial(object: DeepPartial): Period { + const message = { ...basePeriod } as Period; + message.amount = []; + if (object.length !== undefined && object.length !== null) { + message.length = object.length as Long; + } else { + message.length = Long.ZERO; + } + if (object.amount !== undefined && object.amount !== null) { + for (const e of object.amount) { + message.amount.push(Coin.fromPartial(e)); + } + } + return message; + }, +}; + +const basePeriodicVestingAccount: object = { startTime: Long.ZERO }; + +export const PeriodicVestingAccount = { + encode(message: PeriodicVestingAccount, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.baseVestingAccount !== undefined) { + BaseVestingAccount.encode(message.baseVestingAccount, writer.uint32(10).fork()).ldelim(); + } + if (!message.startTime.isZero()) { + writer.uint32(16).int64(message.startTime); + } + for (const v of message.vestingPeriods) { + Period.encode(v!, writer.uint32(26).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): PeriodicVestingAccount { + const reader = input instanceof Uint8Array ? new _m0.Reader(input) : input; + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...basePeriodicVestingAccount } as PeriodicVestingAccount; + message.vestingPeriods = []; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.baseVestingAccount = BaseVestingAccount.decode(reader, reader.uint32()); + break; + case 2: + message.startTime = reader.int64() as Long; + break; + case 3: + message.vestingPeriods.push(Period.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): PeriodicVestingAccount { + const message = { ...basePeriodicVestingAccount } as PeriodicVestingAccount; + message.vestingPeriods = []; + if (object.baseVestingAccount !== undefined && object.baseVestingAccount !== null) { + message.baseVestingAccount = BaseVestingAccount.fromJSON(object.baseVestingAccount); + } else { + message.baseVestingAccount = undefined; + } + if (object.startTime !== undefined && object.startTime !== null) { + message.startTime = Long.fromString(object.startTime); + } else { + message.startTime = Long.ZERO; + } + if (object.vestingPeriods !== undefined && object.vestingPeriods !== null) { + for (const e of object.vestingPeriods) { + message.vestingPeriods.push(Period.fromJSON(e)); + } + } + return message; + }, + + toJSON(message: PeriodicVestingAccount): unknown { + const obj: any = {}; + message.baseVestingAccount !== undefined && + (obj.baseVestingAccount = message.baseVestingAccount + ? BaseVestingAccount.toJSON(message.baseVestingAccount) + : undefined); + message.startTime !== undefined && (obj.startTime = (message.startTime || Long.ZERO).toString()); + if (message.vestingPeriods) { + obj.vestingPeriods = message.vestingPeriods.map((e) => (e ? Period.toJSON(e) : undefined)); + } else { + obj.vestingPeriods = []; + } + return obj; + }, + + fromPartial(object: DeepPartial): PeriodicVestingAccount { + const message = { ...basePeriodicVestingAccount } as PeriodicVestingAccount; + message.vestingPeriods = []; + if (object.baseVestingAccount !== undefined && object.baseVestingAccount !== null) { + message.baseVestingAccount = BaseVestingAccount.fromPartial(object.baseVestingAccount); + } else { + message.baseVestingAccount = undefined; + } + if (object.startTime !== undefined && object.startTime !== null) { + message.startTime = object.startTime as Long; + } else { + message.startTime = Long.ZERO; + } + if (object.vestingPeriods !== undefined && object.vestingPeriods !== null) { + for (const e of object.vestingPeriods) { + message.vestingPeriods.push(Period.fromPartial(e)); + } + } + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | undefined | Long; +export type DeepPartial = T extends Builtin + ? T + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; diff --git a/packages/stargate/src/index.ts b/packages/stargate/src/index.ts index c2af3e81..718109f5 100644 --- a/packages/stargate/src/index.ts +++ b/packages/stargate/src/index.ts @@ -1,3 +1,4 @@ +export { Account, accountFromAny } from "./accounts"; export { AminoConverter, AminoTypes } from "./aminotypes"; export { parseRawLog } from "./logs"; export { @@ -17,8 +18,6 @@ export { StakingExtension, } from "./queries"; export { - Account, - accountFromProto, 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..9eb781a4 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 expected 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 expected and supported + * `typeUrl` and decode the `value` using its own type decoder. + */ + readonly account: (address: string) => Promise; }; }; } @@ -29,27 +39,12 @@ 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 ?? null; }, }, }, diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index c79f9bec..be1426d6 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -5,21 +5,18 @@ import { isSearchByHeightQuery, isSearchBySentFromOrToQuery, isSearchByTagsQuery, - PubKey, SearchTxFilter, SearchTxQuery, } from "@cosmjs/launchpad"; -import { Uint53, Uint64 } from "@cosmjs/math"; -import { decodePubkey } from "@cosmjs/proto-signing"; +import { Uint53 } from "@cosmjs/math"; import { broadcastTxCommitSuccess, Tendermint34Client, toRfc3339WithNanoseconds, } from "@cosmjs/tendermint-rpc"; -import { assert, assertDefinedAndNotNull } from "@cosmjs/utils"; -import Long from "long"; +import { assertDefinedAndNotNull } from "@cosmjs/utils"; -import { BaseAccount } from "./codec/cosmos/auth/v1beta1/auth"; +import { Account, accountFromAny } from "./accounts"; import { MsgData, TxMsgData } from "./codec/cosmos/base/abci/v1beta1/abci"; import { Coin } from "./codec/cosmos/base/v1beta1/coin"; import { AuthExtension, BankExtension, QueryClient, setupAuthExtension, setupBankExtension } from "./queries"; @@ -35,14 +32,6 @@ export interface IndexedTx { readonly tx: Uint8Array; } -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; @@ -86,23 +75,6 @@ export function assertIsBroadcastTxSuccess( } } -function uint64FromProto(input: number | Long | null | undefined): Uint64 { - if (!input) return Uint64.fromNumber(0); - return Uint64.fromString(input.toString()); -} - -export function accountFromProto(input: BaseAccount): Account { - const { address, pubKey, accountNumber, sequence } = input; - const pubkey = decodePubkey(pubKey); - assert(address); - return { - address: address, - pubkey: pubkey, - accountNumber: uint64FromProto(accountNumber).toNumber(), - sequence: uint64FromProto(sequence).toNumber(), - }; -} - export function coinFromProto(input: Coin): Coin { assertDefinedAndNotNull(input.amount); assertDefinedAndNotNull(input.denom); @@ -151,14 +123,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 {