diff --git a/packages/bcp/src/address.ts b/packages/bcp/src/address.ts index c702edea..82046fdf 100644 --- a/packages/bcp/src/address.ts +++ b/packages/bcp/src/address.ts @@ -5,9 +5,7 @@ import { Encoding } from "@iov/encoding"; const { fromBase64, toBase64 } = Encoding; -export function decodeCosmosPubkey( - encodedPubkey: string, -): { readonly algo: Algorithm; readonly data: PubkeyBytes } { +export function decodeCosmosPubkey(encodedPubkey: string): PubkeyBundle { const sdkPubKey = decodeBech32Pubkey(encodedPubkey); switch (sdkPubKey.type) { case types.pubkeyType.secp256k1: diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index 25122b27..afd6ea70 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -3,7 +3,6 @@ import { CosmosAddressBech32Prefix, CosmWasmClient, findSequenceForSignedTx, - RestClient, TxsResponse, types, } from "@cosmwasm/sdk"; @@ -74,10 +73,9 @@ export class CosmWasmConnection implements BlockchainConnection { addressPrefix: CosmosAddressBech32Prefix, tokens: TokenConfiguration, ): Promise { - const restClient = new RestClient(url); const cosmWasmClient = CosmWasmClient.makeReadOnly(url); const chainData = await this.initialize(cosmWasmClient); - return new CosmWasmConnection(restClient, cosmWasmClient, chainData, addressPrefix, tokens); + return new CosmWasmConnection(cosmWasmClient, chainData, addressPrefix, tokens); } private static async initialize(cosmWasmClient: CosmWasmClient): Promise { @@ -88,8 +86,6 @@ export class CosmWasmConnection implements BlockchainConnection { public readonly chainId: ChainId; public readonly codec: TxCodec; - /** @deprecated everything we use from RestClient should be available in CosmWasmClient */ - private readonly restClient: RestClient; private readonly cosmWasmClient: CosmWasmClient; private readonly addressPrefix: CosmosAddressBech32Prefix; private readonly bankTokens: readonly BankToken[]; @@ -100,14 +96,11 @@ export class CosmWasmConnection implements BlockchainConnection { private readonly supportedTokens: readonly Token[]; private constructor( - restClient: RestClient, cosmWasmClient: CosmWasmClient, chainId: ChainId, addressPrefix: CosmosAddressBech32Prefix, tokens: TokenConfiguration, ) { - // tslint:disable-next-line: deprecation - this.restClient = restClient; this.cosmWasmClient = cosmWasmClient; this.chainId = chainId; this.codec = new CosmWasmCodec(addressPrefix, tokens.bankTokens, tokens.erc20Tokens); @@ -154,12 +147,9 @@ export class CosmWasmConnection implements BlockchainConnection { public async getAccount(query: AccountQuery): Promise { const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.addressPrefix) : query.address; - // tslint:disable-next-line: deprecation - const { result } = await this.restClient.authAccounts(address); - const bankAccount = result.value; - const hasBankAccount = !!bankAccount.address; + const bankAccount = await this.cosmWasmClient.getAccount(address); - const supportedBankCoins = bankAccount.coins.filter(({ denom }) => + const supportedBankCoins = (bankAccount?.coins || []).filter(({ denom }) => this.bankTokens.find(token => token.denom === denom), ); const erc20Amounts = await Promise.all( @@ -179,14 +169,14 @@ export class CosmWasmConnection implements BlockchainConnection { ); const nonZeroErc20Amounts = erc20Amounts.filter(amount => amount.quantity !== "0"); - if (!hasBankAccount && nonZeroErc20Amounts.length === 0) { + if (!bankAccount && nonZeroErc20Amounts.length === 0) { return undefined; } else { const balance = [ ...supportedBankCoins.map(coin => decodeAmount(this.bankTokens, coin)), ...nonZeroErc20Amounts, ].sort((a, b) => a.tokenTicker.localeCompare(b.tokenTicker)); - const pubkey = !bankAccount.public_key ? undefined : decodeCosmosPubkey(bankAccount.public_key); + const pubkey = bankAccount?.public_key ? decodeCosmosPubkey(bankAccount.public_key) : undefined; return { address: address, balance: balance, diff --git a/packages/bcp/types/address.d.ts b/packages/bcp/types/address.d.ts index 7a2c395c..10e6c57a 100644 --- a/packages/bcp/types/address.d.ts +++ b/packages/bcp/types/address.d.ts @@ -1,9 +1,4 @@ import { CosmosAddressBech32Prefix } from "@cosmwasm/sdk"; -import { Address, Algorithm, PubkeyBundle, PubkeyBytes } from "@iov/bcp"; -export declare function decodeCosmosPubkey( - encodedPubkey: string, -): { - readonly algo: Algorithm; - readonly data: PubkeyBytes; -}; +import { Address, PubkeyBundle } from "@iov/bcp"; +export declare function decodeCosmosPubkey(encodedPubkey: string): PubkeyBundle; export declare function pubkeyToAddress(pubkey: PubkeyBundle, prefix: CosmosAddressBech32Prefix): Address; diff --git a/packages/bcp/types/cosmwasmconnection.d.ts b/packages/bcp/types/cosmwasmconnection.d.ts index 0274f4c8..dff506c5 100644 --- a/packages/bcp/types/cosmwasmconnection.d.ts +++ b/packages/bcp/types/cosmwasmconnection.d.ts @@ -47,8 +47,6 @@ export declare class CosmWasmConnection implements BlockchainConnection { private static initialize; readonly chainId: ChainId; readonly codec: TxCodec; - /** @deprecated everything we use from RestClient should be available in CosmWasmClient */ - private readonly restClient; private readonly cosmWasmClient; private readonly addressPrefix; private readonly bankTokens; diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index b2098969..319610a8 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -73,6 +73,40 @@ describe("CosmWasmClient", () => { sequence: 0, }); }); + + it("throws for missing accounts", async () => { + pendingWithoutCosmos(); + const client = CosmWasmClient.makeReadOnly(httpUrl); + const missing = makeRandomAddress(); + await client.getNonce(missing).then( + () => fail("this must not succeed"), + error => expect(error).toMatch(/account does not exist on chain/i), + ); + }); + }); + + describe("getAccount", () => { + it("works", async () => { + pendingWithoutCosmos(); + const client = CosmWasmClient.makeReadOnly(httpUrl); + expect(await client.getAccount(unusedAccount.address)).toEqual({ + address: unusedAccount.address, + account_number: 5, + sequence: 0, + public_key: "", + coins: [ + { denom: "ucosm", amount: "1000000000" }, + { denom: "ustake", amount: "1000000000" }, + ], + }); + }); + + it("returns undefined for missing accounts", async () => { + pendingWithoutCosmos(); + const client = CosmWasmClient.makeReadOnly(httpUrl); + const missing = makeRandomAddress(); + expect(await client.getAccount(missing)).toBeUndefined(); + }); }); describe("getBlock", () => { diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index b80e86f8..be66846d 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -6,6 +6,7 @@ import { findAttribute, Log, parseLogs } from "./logs"; import { BlockResponse, RestClient, TxsResponse } from "./restclient"; import { Coin, + CosmosSdkAccount, CosmosSdkTx, MsgExecuteContract, MsgInstantiateContract, @@ -147,16 +148,29 @@ export class CosmWasmClient { /** * Returns account number and sequence. * + * Throws if the account does not exist on chain. + * * @param address returns data for this address. When unset, the client's sender adddress is used. */ public async getNonce(address?: string): Promise { - const account = (await this.restClient.authAccounts(address || this.senderAddress)).result.value; + const account = await this.getAccount(address); + if (!account) { + throw new Error( + "Account does not exist on chain. Send some tokens there before trying to query nonces.", + ); + } return { accountNumber: account.account_number, sequence: account.sequence, }; } + public async getAccount(address?: string): Promise { + const account = await this.restClient.authAccounts(address || this.senderAddress); + const value = account.result.value; + return value.address === "" ? undefined : value; + } + /** * Gets block header and meta * diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 09ca66b5..1e45b8e9 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -290,6 +290,18 @@ describe("RestClient", () => { }), ); }); + + // This property is used by CosmWasmClient.getAccount + it("returns empty address for non-existent account", async () => { + pendingWithoutCosmos(); + const client = new RestClient(httpUrl); + const nonExistentAccount = makeRandomAddress(); + const { result } = await client.authAccounts(nonExistentAccount); + expect(result).toEqual({ + type: "cosmos-sdk/Account", + value: jasmine.objectContaining({ address: "" }), + }); + }); }); describe("encodeTx", () => { diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index bd66f743..93fd5216 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -1,6 +1,6 @@ import { Log } from "./logs"; import { BlockResponse, TxsResponse } from "./restclient"; -import { Coin, CosmosSdkTx, StdSignature } from "./types"; +import { Coin, CosmosSdkAccount, CosmosSdkTx, StdSignature } from "./types"; export interface SigningCallback { (signBytes: Uint8Array): Promise; } @@ -43,9 +43,12 @@ export declare class CosmWasmClient { /** * Returns account number and sequence. * + * Throws if the account does not exist on chain. + * * @param address returns data for this address. When unset, the client's sender adddress is used. */ getNonce(address?: string): Promise; + getAccount(address?: string): Promise; /** * Gets block header and meta *