diff --git a/packages/proto-signing/src/accounts.ts b/packages/proto-signing/src/accounts.ts new file mode 100644 index 00000000..2e359a94 --- /dev/null +++ b/packages/proto-signing/src/accounts.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Message } from "protobufjs"; + +import { cosmosField, cosmosMessage } from "./decorator"; +import { defaultRegistry } from "./msgs"; + +@cosmosMessage(defaultRegistry, "/cosmos.auth.BaseAccount") +export class BaseAccount extends Message { + @cosmosField.bytes(1) + public readonly address?: Uint8Array; + + @cosmosField.bytes(2) + public readonly pub_key?: Uint8Array; + + @cosmosField.uint64(3) + public readonly account_number?: Long | number; + + @cosmosField.uint64(4) + public readonly sequence?: Long | number; +} diff --git a/packages/proto-signing/src/index.ts b/packages/proto-signing/src/index.ts index 73ae35c4..c279effd 100644 --- a/packages/proto-signing/src/index.ts +++ b/packages/proto-signing/src/index.ts @@ -1 +1,2 @@ +export { BaseAccount } from "./accounts"; export { decodeAny } from "./any"; diff --git a/packages/proto-signing/types/accounts.d.ts b/packages/proto-signing/types/accounts.d.ts new file mode 100644 index 00000000..2c888633 --- /dev/null +++ b/packages/proto-signing/types/accounts.d.ts @@ -0,0 +1,8 @@ +/// +import { Message } from "protobufjs"; +export declare class BaseAccount extends Message { + readonly address?: Uint8Array; + readonly pub_key?: Uint8Array; + readonly account_number?: Long | number; + readonly sequence?: Long | number; +} diff --git a/packages/proto-signing/types/index.d.ts b/packages/proto-signing/types/index.d.ts index 73ae35c4..c279effd 100644 --- a/packages/proto-signing/types/index.d.ts +++ b/packages/proto-signing/types/index.d.ts @@ -1 +1,2 @@ +export { BaseAccount } from "./accounts"; export { decodeAny } from "./any"; diff --git a/packages/stargate/package.json b/packages/stargate/package.json index a26e5fe4..d9393348 100644 --- a/packages/stargate/package.json +++ b/packages/stargate/package.json @@ -38,7 +38,9 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { + "@cosmjs/encoding": "^0.22.0", "@cosmjs/proto-signing": "^0.22.0", - "@cosmjs/tendermint-rpc": "^0.22.0" + "@cosmjs/tendermint-rpc": "^0.22.0", + "@cosmjs/utils": "^0.22.0" } } diff --git a/packages/stargate/src/stargateclient.spec.ts b/packages/stargate/src/stargateclient.spec.ts index da09f08a..6956aa71 100644 --- a/packages/stargate/src/stargateclient.spec.ts +++ b/packages/stargate/src/stargateclient.spec.ts @@ -1,5 +1,5 @@ import { StargateClient } from "./stargateclient"; -import { pendingWithoutSimapp, simapp } from "./testutils.spec"; +import { pendingWithoutSimapp, simapp, unused } from "./testutils.spec"; describe("StargateClient", () => { describe("connect", () => { @@ -10,4 +10,17 @@ describe("StargateClient", () => { client.disconnect(); }); }); + + describe("getSequence", () => { + it("works for unused account", async () => { + pendingWithoutSimapp(); + const client = await StargateClient.connect(simapp.tendermintUrl); + + const { accountNumber, sequence } = await client.getSequence(unused.address); + expect(accountNumber).toEqual(unused.accountNumber); + expect(sequence).toEqual(unused.sequence); + + client.disconnect(); + }); + }); }); diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 40ea0342..3affe0fc 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -1,4 +1,13 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Bech32 } from "@cosmjs/encoding"; +import { BaseAccount, decodeAny } from "@cosmjs/proto-signing"; import { Client as TendermintClient } from "@cosmjs/tendermint-rpc"; +import { assert } from "@cosmjs/utils"; + +export interface GetSequenceResult { + readonly accountNumber: number; + readonly sequence: number; +} export class StargateClient { private readonly tmClient: TendermintClient; @@ -12,6 +21,38 @@ export class StargateClient { this.tmClient = tmClient; } + public async getSequence(address: string): Promise { + const binAddress = Bech32.decode(address).data; + + // https://github.com/cosmos/cosmos-sdk/blob/8cab43c8120fec5200c3459cbf4a92017bb6f287/x/auth/types/keys.go#L29-L32 + const accountKey = Uint8Array.from([0x01, ...binAddress]); + + 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/acc/key", + data: accountKey, + prove: false, + }); + + const { typeUrl, value } = decodeAny(response.value); + + switch (typeUrl) { + case "/cosmos.auth.BaseAccount": { + const { account_number, sequence } = BaseAccount.decode(value); + assert(account_number !== undefined); + assert(sequence !== undefined); + return { + accountNumber: typeof account_number === "number" ? account_number : account_number.toNumber(), + sequence: typeof sequence === "number" ? sequence : sequence.toNumber(), + }; + } + + default: + throw new Error(`Unsupported type: ${typeUrl}`); + } + } + public disconnect(): void { this.tmClient.disconnect(); } diff --git a/packages/stargate/src/testutils.spec.ts b/packages/stargate/src/testutils.spec.ts index b02e5526..cfe1a60e 100644 --- a/packages/stargate/src/testutils.spec.ts +++ b/packages/stargate/src/testutils.spec.ts @@ -8,3 +8,14 @@ export const simapp = { tendermintUrl: "localhost:26657", chainId: "simd-testing", }; + +/** Unused account */ +export const unused = { + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ", + }, + address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u", + accountNumber: 16, + sequence: 0, +}; diff --git a/packages/stargate/types/stargateclient.d.ts b/packages/stargate/types/stargateclient.d.ts index f65d2e29..7b5269d6 100644 --- a/packages/stargate/types/stargateclient.d.ts +++ b/packages/stargate/types/stargateclient.d.ts @@ -1,6 +1,11 @@ +export interface GetSequenceResult { + readonly accountNumber: number; + readonly sequence: number; +} export declare class StargateClient { private readonly tmClient; static connect(endpoint: string): Promise; private constructor(); + getSequence(address: string): Promise; disconnect(): void; }