From 35f0a0af7a7fc6c8524b7885387c37666bd03b10 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 22 Jan 2020 17:44:50 +0100 Subject: [PATCH 01/10] Start out coin<->Amount<->Ticker mapping --- src/types.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/types.ts b/src/types.ts index c8a0ef72..c74573d9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,6 @@ import amino from "@tendermint/amino-js"; +import {Amount, Token} from "@iov/bcp"; + export type AminoTx = amino.Tx & { readonly value: amino.StdTx }; @@ -8,3 +10,31 @@ export function isAminoStdTx(txValue: amino.TxValue): txValue is amino.StdTx { typeof memo === "string" && Array.isArray(msg) && typeof fee === "object" && Array.isArray(signatures) ); } + +export interface TokenInfo extends Token { + readonly denom: string; +} + +// TODO: alias amino types +export function amountToCoin(lookup: ReadonlyArray, amount: Amount): amino.Coin { + const match = lookup.find(({tokenTicker}) => tokenTicker === amount.tokenTicker); + if (!match) { + throw Error(`unknown ticker: ${amount.tokenTicker}`); + } + return { + denom: match.denom, + amount: amount.quantity, + }; +} + +export function coinToAmount(lookup: ReadonlyArray, coin: amino.Coin): Amount { + const match = lookup.find(({denom}) => denom === coin.denom); + if (!match) { + throw Error(`unknown denom: ${coin.denom}`); + } + return { + tokenTicker: match.tokenTicker, + fractionalDigits: match.fractionalDigits, + quantity: coin.amount, + }; +} From 00926c71dcc5930cb98d71a509d9b2fe1eb84812 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 22 Jan 2020 17:48:29 +0100 Subject: [PATCH 02/10] Set up format and lint configs --- .editorconfig | 12 ++++++++++ .eslintrc.json | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ .prettierrc.json | 4 ++++ 3 files changed, 77 insertions(+) create mode 100644 .editorconfig create mode 100644 .eslintrc.json create mode 100644 .prettierrc.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..697a5471 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..cbbaf4b5 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,61 @@ +{ + "env": { + "es6": true, + "jasmine": true, + "node": true, + "worker": true + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2018 + }, + "plugins": ["@typescript-eslint", "prettier", "simple-import-sort", "import"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier/@typescript-eslint", + "plugin:prettier/recommended", + "plugin:import/typescript" + ], + "rules": { + "curly": ["warn", "multi-line", "consistent"], + "no-console": ["warn", { "allow": ["error", "info", "warn"] }], + "no-param-reassign": "warn", + "no-shadow": "warn", + "prefer-const": "warn", + "spaced-comment": ["warn", "always", { "line": { "markers": ["/ Date: Wed, 22 Jan 2020 17:48:42 +0100 Subject: [PATCH 03/10] yarn format && yarn lint --- src/cosmosconnection.spec.ts | 2 +- src/cosmosconnection.ts | 13 ++++++++----- src/types.ts | 7 +++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/cosmosconnection.spec.ts b/src/cosmosconnection.spec.ts index 897b863e..11daaa5c 100644 --- a/src/cosmosconnection.spec.ts +++ b/src/cosmosconnection.spec.ts @@ -107,7 +107,7 @@ describe("CosmosConnection", () => { fractionalDigits: 6, tokenName: "Stake", tokenTicker: "stake" as TokenTicker, - } + }, ]); connection.disconnect(); }); diff --git a/src/cosmosconnection.ts b/src/cosmosconnection.ts index 45460485..8d7031ba 100644 --- a/src/cosmosconnection.ts +++ b/src/cosmosconnection.ts @@ -99,11 +99,14 @@ export class CosmosConnection implements BlockchainConnection { tokenName: "Cosm", tokenTicker: "cosm" as TokenTicker, }; - this.supportedTokens = [this.primaryToken, { - fractionalDigits: 6, - tokenName: "Stake", - tokenTicker: "stake" as TokenTicker, - }]; + this.supportedTokens = [ + this.primaryToken, + { + fractionalDigits: 6, + tokenName: "Stake", + tokenTicker: "stake" as TokenTicker, + }, + ]; } public disconnect(): void { diff --git a/src/types.ts b/src/types.ts index c74573d9..415c37ed 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,5 @@ +import { Amount, Token } from "@iov/bcp"; import amino from "@tendermint/amino-js"; -import {Amount, Token} from "@iov/bcp"; - export type AminoTx = amino.Tx & { readonly value: amino.StdTx }; @@ -17,7 +16,7 @@ export interface TokenInfo extends Token { // TODO: alias amino types export function amountToCoin(lookup: ReadonlyArray, amount: Amount): amino.Coin { - const match = lookup.find(({tokenTicker}) => tokenTicker === amount.tokenTicker); + const match = lookup.find(({ tokenTicker }) => tokenTicker === amount.tokenTicker); if (!match) { throw Error(`unknown ticker: ${amount.tokenTicker}`); } @@ -28,7 +27,7 @@ export function amountToCoin(lookup: ReadonlyArray, amount: Amount): } export function coinToAmount(lookup: ReadonlyArray, coin: amino.Coin): Amount { - const match = lookup.find(({denom}) => denom === coin.denom); + const match = lookup.find(({ denom }) => denom === coin.denom); if (!match) { throw Error(`unknown denom: ${coin.denom}`); } From fa40ff54a5946e98b68b8485af06096be2fa0c32 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 22 Jan 2020 17:56:37 +0100 Subject: [PATCH 04/10] Add todos for token mapping logic --- src/cosmoscodec.ts | 1 + src/decode.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cosmoscodec.ts b/src/cosmoscodec.ts index ba449e4e..920211a9 100644 --- a/src/cosmoscodec.ts +++ b/src/cosmoscodec.ts @@ -81,6 +81,7 @@ export class CosmosCodec implements TxCodec { throw new Error("Nonce is required"); } const parsed = unmarshalTx(bytes); + // TODO: this needs access to token list return parseTx(parsed, chainId, nonce); } diff --git a/src/decode.ts b/src/decode.ts index c368c804..46f00d0b 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -45,7 +45,9 @@ export function decodeFullSignature(signature: amino.StdSignature, nonce: number }; } -export function decodeAmount(amount: amino.Coin): Amount { + // TODO: this needs access to token list - we need something more like amountToCoin and coinToAmount here + // and wire that info all the way from both connection and codec. + export function decodeAmount(amount: amino.Coin): Amount { // TODO: more uglyness here (breaks unit tests) if (amount.denom !== "uatom") { throw new Error("Only ATOM amounts are supported"); @@ -74,6 +76,7 @@ export function parseMsg(msg: amino.Msg, chainId: ChainId): SendTransaction { chainId: chainId, sender: msgValue.from_address as Address, recipient: msgValue.to_address as Address, + // TODO: this needs access to token list amount: decodeAmount(msgValue.amount[0]), }; } @@ -98,7 +101,9 @@ export function parseTx(tx: amino.Tx, chainId: ChainId, nonce: Nonce): SignedTra } const [primarySignature] = txValue.signatures.map(signature => decodeFullSignature(signature, nonce)); + // TODO: this needs access to token list const msg = parseMsg(txValue.msg[0], chainId); + // TODO: this needs access to token list const fee = parseFee(txValue.fee); const transaction = { From 17468f5a75b4cdd30fbdda6059b51ec26e475a1b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 22 Jan 2020 22:40:34 +0100 Subject: [PATCH 05/10] Add prefix and tokens into Codec --- src/cosmoscodec.ts | 28 ++++++++++++++++++++++++---- src/decode.ts | 6 +++--- src/types.ts | 2 ++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/cosmoscodec.ts b/src/cosmoscodec.ts index 920211a9..ebfffabe 100644 --- a/src/cosmoscodec.ts +++ b/src/cosmoscodec.ts @@ -9,6 +9,7 @@ import { SignableBytes, SignedTransaction, SigningJob, + TokenTicker, TransactionId, TxCodec, UnsignedTransaction, @@ -17,10 +18,11 @@ import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import { marshalTx, unmarshalTx } from "@tendermint/amino-js"; -import { isValidAddress, pubkeyToAddress } from "./address"; +import { isValidAddress, pubkeyToAddress, CosmosBech32Prefix } from "./address"; import { Caip5 } from "./caip5"; import { parseTx } from "./decode"; import { buildSignedTx, buildUnsignedTx } from "./encode"; +import { TokenInfos } from "./types"; const { toHex, toUtf8 } = Encoding; @@ -43,6 +45,14 @@ function sortJson(json: any): any { } export class CosmosCodec implements TxCodec { + private readonly prefix: CosmosBech32Prefix; + private readonly tokens: TokenInfos; + + public constructor(prefix: CosmosBech32Prefix, tokens: TokenInfos) { + this.prefix = prefix; + this.tokens = tokens; + } + public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob { const accountNumber = 0; const memo = (unsigned as any).memo; @@ -86,8 +96,7 @@ export class CosmosCodec implements TxCodec { } public identityToAddress(identity: Identity): Address { - const prefix = "cosmos"; - return pubkeyToAddress(identity.pubkey, prefix); + return pubkeyToAddress(identity.pubkey, this.prefix); } public isValidAddress(address: string): boolean { @@ -95,4 +104,15 @@ export class CosmosCodec implements TxCodec { } } -export const cosmosCodec = new CosmosCodec(); +const defaultPrefix = "cosmos" as CosmosBech32Prefix; + +const defaultTokens: TokenInfos = [ + { + fractionalDigits: 6, + tokenName: "Atom (Cosmos Hub)", + tokenTicker: "ATOM" as TokenTicker, + denom: "uatom", + }, +]; + +export const cosmosCodec = new CosmosCodec(defaultPrefix, defaultTokens); diff --git a/src/decode.ts b/src/decode.ts index 46f00d0b..46c60b14 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -45,9 +45,9 @@ export function decodeFullSignature(signature: amino.StdSignature, nonce: number }; } - // TODO: this needs access to token list - we need something more like amountToCoin and coinToAmount here - // and wire that info all the way from both connection and codec. - export function decodeAmount(amount: amino.Coin): Amount { +// TODO: this needs access to token list - we need something more like amountToCoin and coinToAmount here +// and wire that info all the way from both connection and codec. +export function decodeAmount(amount: amino.Coin): Amount { // TODO: more uglyness here (breaks unit tests) if (amount.denom !== "uatom") { throw new Error("Only ATOM amounts are supported"); diff --git a/src/types.ts b/src/types.ts index 415c37ed..dabdc851 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,6 +14,8 @@ export interface TokenInfo extends Token { readonly denom: string; } +export type TokenInfos = ReadonlyArray; + // TODO: alias amino types export function amountToCoin(lookup: ReadonlyArray, amount: Amount): amino.Coin { const match = lookup.find(({ tokenTicker }) => tokenTicker === amount.tokenTicker); From c40f07144ad5cebb784eba855b827e7d1829c62f Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 22 Jan 2020 23:03:54 +0100 Subject: [PATCH 06/10] Pass prefix and token info into codec and connection --- src/cosmoscodec.ts | 2 +- src/cosmosconnection.spec.ts | 35 +++++++++++++++------- src/cosmosconnection.ts | 56 ++++++++++++++++++++---------------- src/cosmosconnector.ts | 6 +++- src/decode.spec.ts | 20 +++++++++---- src/decode.ts | 36 ++++++++++------------- src/types.ts | 4 +-- 7 files changed, 94 insertions(+), 65 deletions(-) diff --git a/src/cosmoscodec.ts b/src/cosmoscodec.ts index ebfffabe..b5497f01 100644 --- a/src/cosmoscodec.ts +++ b/src/cosmoscodec.ts @@ -92,7 +92,7 @@ export class CosmosCodec implements TxCodec { } const parsed = unmarshalTx(bytes); // TODO: this needs access to token list - return parseTx(parsed, chainId, nonce); + return parseTx(parsed, chainId, nonce, this.tokens); } public identityToAddress(identity: Identity): Address { diff --git a/src/cosmosconnection.spec.ts b/src/cosmosconnection.spec.ts index 11daaa5c..5acbeb2c 100644 --- a/src/cosmosconnection.spec.ts +++ b/src/cosmosconnection.spec.ts @@ -16,6 +16,8 @@ import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol"; import { cosmosCodec } from "./cosmoscodec"; import { CosmosConnection } from "./cosmosconnection"; +import { CosmosBech32Prefix } from "./address"; +import { TokenInfos } from "./types"; const { fromBase64, toHex } = Encoding; @@ -40,10 +42,21 @@ describe("CosmosConnection", () => { const faucetPath = HdPaths.cosmos(0); const defaultRecipient = "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" as Address; + const defaultPrefix = "cosmos" as CosmosBech32Prefix; + + const defaultTokens: TokenInfos = [ + { + fractionalDigits: 6, + tokenName: "Atom (Cosmos Hub)", + tokenTicker: "ATOM" as TokenTicker, + denom: "uatom", + }, + ]; + describe("establish", () => { it("can connect to Cosmos via http", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); expect(connection).toBeTruthy(); connection.disconnect(); }); @@ -52,7 +65,7 @@ describe("CosmosConnection", () => { describe("chainId", () => { it("displays the chain ID", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const chainId = connection.chainId(); expect(chainId).toEqual(defaultChainId); connection.disconnect(); @@ -62,7 +75,7 @@ describe("CosmosConnection", () => { describe("height", () => { it("displays the current height", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const height = await connection.height(); expect(height).toBeGreaterThan(0); connection.disconnect(); @@ -72,7 +85,7 @@ describe("CosmosConnection", () => { describe("getToken", () => { it("displays a given token", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const token = await connection.getToken("cosm" as TokenTicker); expect(token).toEqual({ fractionalDigits: 6, @@ -84,7 +97,7 @@ describe("CosmosConnection", () => { it("resolves to undefined if the token is not supported", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const token = await connection.getToken("whatever" as TokenTicker); expect(token).toBeUndefined(); connection.disconnect(); @@ -94,7 +107,7 @@ describe("CosmosConnection", () => { describe("getAllTokens", () => { it("resolves to a list of all supported tokens", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const tokens = await connection.getAllTokens(); // TODO: make this more flexible expect(tokens).toEqual([ @@ -116,7 +129,7 @@ describe("CosmosConnection", () => { describe("getAccount", () => { it("gets an empty account by address", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const account = await connection.getAccount({ address: defaultEmptyAddress }); expect(account).toBeUndefined(); connection.disconnect(); @@ -124,7 +137,7 @@ describe("CosmosConnection", () => { it("gets an account by address", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const account = await connection.getAccount({ address: defaultAddress }); if (account === undefined) { throw new Error("Expected account not to be undefined"); @@ -138,7 +151,7 @@ describe("CosmosConnection", () => { it("gets an account by pubkey", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const account = await connection.getAccount({ pubkey: defaultPubkey }); if (account === undefined) { throw new Error("Expected account not to be undefined"); @@ -157,7 +170,7 @@ describe("CosmosConnection", () => { describe("integration tests", () => { it("can post and get a transaction", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const profile = new UserProfile(); const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic)); const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath); @@ -216,7 +229,7 @@ describe("CosmosConnection", () => { it("can post and search for a transaction", async () => { pendingWithoutCosmos(); - const connection = await CosmosConnection.establish(httpUrl); + const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const profile = new UserProfile(); const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic)); const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath); diff --git a/src/cosmosconnection.ts b/src/cosmosconnection.ts index 8d7031ba..f4e8e6d5 100644 --- a/src/cosmosconnection.ts +++ b/src/cosmosconnection.ts @@ -38,6 +38,7 @@ import { CosmosBech32Prefix, pubkeyToAddress } from "./address"; import { Caip5 } from "./caip5"; import { decodeAmount, parseTxsResponse } from "./decode"; import { RestClient, TxsResponse } from "./restclient"; +import { TokenInfos } from "./types"; const { fromBase64 } = Encoding; @@ -70,10 +71,15 @@ function buildQueryString({ } export class CosmosConnection implements BlockchainConnection { - public static async establish(url: string): Promise { + // we must know prefix and tokens a priori to understand the chain + public static async establish( + url: string, + prefix: CosmosBech32Prefix, + tokenInfo: TokenInfos, + ): Promise { const restClient = new RestClient(url); const chainData = await this.initialize(restClient); - return new CosmosConnection(restClient, chainData); + return new CosmosConnection(restClient, chainData, prefix, tokenInfo); } private static async initialize(restClient: RestClient): Promise { @@ -84,29 +90,34 @@ export class CosmosConnection implements BlockchainConnection { private readonly restClient: RestClient; private readonly chainData: ChainData; private readonly primaryToken: Token; + + // TODO: deprecate this??? private readonly supportedTokens: readonly Token[]; + private readonly _prefix: CosmosBech32Prefix; + private readonly tokenInfo: TokenInfos; + private get prefix(): CosmosBech32Prefix { - return "cosmos"; + return this._prefix; } - private constructor(restClient: RestClient, chainData: ChainData) { + private constructor( + restClient: RestClient, + chainData: ChainData, + prefix: CosmosBech32Prefix, + tokenInfo: TokenInfos, + ) { this.restClient = restClient; this.chainData = chainData; - // TODO: this is an argument - this.primaryToken = { - fractionalDigits: 6, - tokenName: "Cosm", - tokenTicker: "cosm" as TokenTicker, - }; - this.supportedTokens = [ - this.primaryToken, - { - fractionalDigits: 6, - tokenName: "Stake", - tokenTicker: "stake" as TokenTicker, - }, - ]; + this._prefix = prefix; + this.tokenInfo = tokenInfo; + + this.supportedTokens = this.tokenInfo.map(info => ({ + tokenTicker: info.tokenTicker, + tokenName: info.tokenName, + fractionalDigits: info.fractionalDigits, + })); + this.primaryToken = this.supportedTokens[0]; } public disconnect(): void { @@ -135,16 +146,13 @@ export class CosmosConnection implements BlockchainConnection { const { result } = await this.restClient.authAccounts(address); const account = result.value; const supportedCoins = account.coins.filter(({ denom }) => - this.supportedTokens.find( - // TODO: ugly special case - fix this - ({ tokenTicker }) => (tokenTicker === "ATOM" && denom === "uatom") || tokenTicker === denom, - ), + this.tokenInfo.find(token => token.denom === denom), ); return account.public_key === null ? undefined : { address: address, - balance: supportedCoins.map(decodeAmount), + balance: supportedCoins.map(decodeAmount(this.tokenInfo)), pubkey: { algo: Algorithm.Secp256k1, data: fromBase64(account.public_key.value) as PubkeyBytes, @@ -289,6 +297,6 @@ export class CosmosConnection implements BlockchainConnection { const sender = (response.tx.value as any).msg[0].value.from_address; const accountForHeight = await this.restClient.authAccounts(sender, response.height); const nonce = (parseInt(accountForHeight.result.value.sequence, 10) - 1) as Nonce; - return parseTxsResponse(chainId, parseInt(response.height, 10), nonce, response); + return parseTxsResponse(chainId, parseInt(response.height, 10), nonce, response, this.tokenInfo); } } diff --git a/src/cosmosconnector.ts b/src/cosmosconnector.ts index 7e3d4ccd..e4b13716 100644 --- a/src/cosmosconnector.ts +++ b/src/cosmosconnector.ts @@ -2,16 +2,20 @@ import { ChainConnector, ChainId } from "@iov/bcp"; import { cosmosCodec } from "./cosmoscodec"; import { CosmosConnection } from "./cosmosconnection"; +import { CosmosBech32Prefix } from "./address"; +import { TokenInfos } from "./types"; /** * A helper to connect to a cosmos-based chain at a given url */ export function createCosmosConnector( url: string, + prefix: CosmosBech32Prefix, + tokenInfo: TokenInfos, expectedChainId?: ChainId, ): ChainConnector { return { - establishConnection: async () => CosmosConnection.establish(url), + establishConnection: async () => CosmosConnection.establish(url, prefix, tokenInfo), codec: cosmosCodec, expectedChainId: expectedChainId, }; diff --git a/src/decode.spec.ts b/src/decode.spec.ts index 52f29ab7..8ca2e5f1 100644 --- a/src/decode.spec.ts +++ b/src/decode.spec.ts @@ -15,9 +15,19 @@ import { } from "./decode"; import { chainId, nonce, signedTxJson, txId } from "./testdata.spec"; import data from "./testdata/cosmoshub.json"; +import { TokenInfos } from "./types"; const { fromBase64 } = Encoding; +const defaultTokens: TokenInfos = [ + { + fractionalDigits: 6, + tokenName: "Atom (Cosmos Hub)", + tokenTicker: "ATOM" as TokenTicker, + denom: "uatom", + }, +]; + describe("decode", () => { const defaultPubkey = { algo: Algorithm.Secp256k1, @@ -89,7 +99,7 @@ describe("decode", () => { denom: "uatom", amount: "11657995", }; - expect(decodeAmount(amount)).toEqual(defaultAmount); + expect(decodeAmount(defaultTokens)(amount)).toEqual(defaultAmount); }); }); @@ -108,7 +118,7 @@ describe("decode", () => { ], }, }; - expect(parseMsg(msg, chainId)).toEqual(defaultSendTransaction); + expect(parseMsg(msg, chainId, defaultTokens)).toEqual(defaultSendTransaction); }); }); @@ -123,13 +133,13 @@ describe("decode", () => { ], gas: "200000", }; - expect(parseFee(fee)).toEqual(defaultFee); + expect(parseFee(fee, defaultTokens)).toEqual(defaultFee); }); }); describe("parseTx", () => { it("works", () => { - expect(parseTx(data.tx, chainId, nonce)).toEqual(signedTxJson); + expect(parseTx(data.tx, chainId, nonce, defaultTokens)).toEqual(signedTxJson); }); }); @@ -149,7 +159,7 @@ describe("decode", () => { transactionId: txId, log: '[{"msg_index":0,"success":true,"log":""}]', }; - expect(parseTxsResponse(chainId, currentHeight, nonce, txsResponse)).toEqual(expected); + expect(parseTxsResponse(chainId, currentHeight, nonce, txsResponse, defaultTokens)).toEqual(expected); }); }); }); diff --git a/src/decode.ts b/src/decode.ts index 46c60b14..af0f2187 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -20,7 +20,7 @@ import { Encoding } from "@iov/encoding"; import amino from "@tendermint/amino-js"; import { TxsResponse } from "./restclient"; -import { isAminoStdTx } from "./types"; +import { isAminoStdTx, TokenInfos, coinToAmount } from "./types"; const { fromBase64 } = Encoding; @@ -47,20 +47,13 @@ export function decodeFullSignature(signature: amino.StdSignature, nonce: number // TODO: this needs access to token list - we need something more like amountToCoin and coinToAmount here // and wire that info all the way from both connection and codec. -export function decodeAmount(amount: amino.Coin): Amount { - // TODO: more uglyness here (breaks unit tests) - if (amount.denom !== "uatom") { - throw new Error("Only ATOM amounts are supported"); - } - return { - fractionalDigits: 6, - quantity: amount.amount, - tokenTicker: atom, - // tokenTicker: amount.denom as TokenTicker, - }; -} -export function parseMsg(msg: amino.Msg, chainId: ChainId): SendTransaction { +// TODO: return null vs throw exception for undefined??? +export const decodeAmount = (tokens: TokenInfos) => (coin: amino.Coin): Amount => { + return coinToAmount(tokens, coin); +}; + +export function parseMsg(msg: amino.Msg, chainId: ChainId, tokens: TokenInfos): SendTransaction { if (msg.type !== "cosmos-sdk/MsgSend") { throw new Error("Unknown message type in transaction"); } @@ -77,21 +70,21 @@ export function parseMsg(msg: amino.Msg, chainId: ChainId): SendTransaction { sender: msgValue.from_address as Address, recipient: msgValue.to_address as Address, // TODO: this needs access to token list - amount: decodeAmount(msgValue.amount[0]), + amount: decodeAmount(tokens)(msgValue.amount[0]), }; } -export function parseFee(fee: amino.StdFee): Fee { +export function parseFee(fee: amino.StdFee, tokens: TokenInfos): Fee { if (fee.amount.length !== 1) { throw new Error("Only fee with one amount is supported"); } return { - tokens: decodeAmount(fee.amount[0]), + tokens: decodeAmount(tokens)(fee.amount[0]), gasLimit: fee.gas, }; } -export function parseTx(tx: amino.Tx, chainId: ChainId, nonce: Nonce): SignedTransaction { +export function parseTx(tx: amino.Tx, chainId: ChainId, nonce: Nonce, tokens: TokenInfos): SignedTransaction { const txValue = tx.value; if (!isAminoStdTx(txValue)) { throw new Error("Only Amino StdTx is supported"); @@ -102,9 +95,9 @@ export function parseTx(tx: amino.Tx, chainId: ChainId, nonce: Nonce): SignedTra const [primarySignature] = txValue.signatures.map(signature => decodeFullSignature(signature, nonce)); // TODO: this needs access to token list - const msg = parseMsg(txValue.msg[0], chainId); + const msg = parseMsg(txValue.msg[0], chainId, tokens); // TODO: this needs access to token list - const fee = parseFee(txValue.fee); + const fee = parseFee(txValue.fee, tokens); const transaction = { ...msg, @@ -124,10 +117,11 @@ export function parseTxsResponse( currentHeight: number, nonce: Nonce, response: TxsResponse, + tokens: TokenInfos, ): ConfirmedAndSignedTransaction { const height = parseInt(response.height, 10); return { - ...parseTx(response.tx, chainId, nonce), + ...parseTx(response.tx, chainId, nonce, tokens), height: height, confirmations: currentHeight - height + 1, transactionId: response.txhash as TransactionId, diff --git a/src/types.ts b/src/types.ts index dabdc851..03996413 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,8 +28,8 @@ export function amountToCoin(lookup: ReadonlyArray, amount: Amount): }; } -export function coinToAmount(lookup: ReadonlyArray, coin: amino.Coin): Amount { - const match = lookup.find(({ denom }) => denom === coin.denom); +export function coinToAmount(tokens: TokenInfos, coin: amino.Coin): Amount { + const match = tokens.find(({ denom }) => denom === coin.denom); if (!match) { throw Error(`unknown denom: ${coin.denom}`); } From 69d9ef5ce8f02bd884bffebdea9853946bd47a7b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 22 Jan 2020 23:10:00 +0100 Subject: [PATCH 07/10] Fix up some integration tests --- src/cosmosconnection.spec.ts | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/cosmosconnection.spec.ts b/src/cosmosconnection.spec.ts index 5acbeb2c..cd67dec1 100644 --- a/src/cosmosconnection.spec.ts +++ b/src/cosmosconnection.spec.ts @@ -44,12 +44,19 @@ describe("CosmosConnection", () => { const defaultPrefix = "cosmos" as CosmosBech32Prefix; + // this is for wasmd blockchain const defaultTokens: TokenInfos = [ { fractionalDigits: 6, - tokenName: "Atom (Cosmos Hub)", - tokenTicker: "ATOM" as TokenTicker, - denom: "uatom", + tokenName: "Fee Token", + tokenTicker: "COSM" as TokenTicker, + denom: "cosm", + }, + { + fractionalDigits: 6, + tokenName: "Staking Token", + tokenTicker: "STAKE" as TokenTicker, + denom: "stake", }, ]; @@ -86,11 +93,11 @@ describe("CosmosConnection", () => { it("displays a given token", async () => { pendingWithoutCosmos(); const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); - const token = await connection.getToken("cosm" as TokenTicker); + const token = await connection.getToken("COSM" as TokenTicker); expect(token).toEqual({ - fractionalDigits: 6, - tokenName: "Cosm", - tokenTicker: "cosm" as TokenTicker, + fractionalDigits: 6, + tokenName: "Fee Token", + tokenTicker: "COSM" as TokenTicker, }); connection.disconnect(); }); @@ -113,13 +120,13 @@ describe("CosmosConnection", () => { expect(tokens).toEqual([ { fractionalDigits: 6, - tokenName: "Cosm", - tokenTicker: "cosm" as TokenTicker, + tokenName: "Fee Token", + tokenTicker: "COSM" as TokenTicker, }, { fractionalDigits: 6, - tokenName: "Stake", - tokenTicker: "stake" as TokenTicker, + tokenName: "Staking Token", + tokenTicker: "STAKE" as TokenTicker, }, ]); connection.disconnect(); From 344d980b5f28aad5c2091f699e8b679c37843505 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 22 Jan 2020 23:16:37 +0100 Subject: [PATCH 08/10] Fix getAccount --- src/cosmosconnection.spec.ts | 23 +++++++++++------------ src/cosmosconnection.ts | 18 +++++++++++------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/cosmosconnection.spec.ts b/src/cosmosconnection.spec.ts index cd67dec1..e11ea319 100644 --- a/src/cosmosconnection.spec.ts +++ b/src/cosmosconnection.spec.ts @@ -95,9 +95,9 @@ describe("CosmosConnection", () => { const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens); const token = await connection.getToken("COSM" as TokenTicker); expect(token).toEqual({ - fractionalDigits: 6, - tokenName: "Fee Token", - tokenTicker: "COSM" as TokenTicker, + fractionalDigits: 6, + tokenName: "Fee Token", + tokenTicker: "COSM" as TokenTicker, }); connection.disconnect(); }); @@ -150,9 +150,10 @@ describe("CosmosConnection", () => { throw new Error("Expected account not to be undefined"); } expect(account.address).toEqual(defaultAddress); - expect(account.pubkey).toEqual(defaultPubkey); - // Unsupported coins are filtered out - expect(account.balance.length).toEqual(1); + // Undefined until we sign a transaction + expect(account.pubkey).toEqual(undefined); + // Starts with two tokens + expect(account.balance.length).toEqual(2); connection.disconnect(); }); @@ -164,12 +165,10 @@ describe("CosmosConnection", () => { throw new Error("Expected account not to be undefined"); } expect(account.address).toEqual(defaultAddress); - expect(account.pubkey).toEqual({ - algo: Algorithm.Secp256k1, - data: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"), - }); - // Unsupported coins are filtered out - expect(account.balance.length).toEqual(1); + // Undefined until we sign a transaction + expect(account.pubkey).toEqual(undefined); + // Starts with two tokens + expect(account.balance.length).toEqual(2); connection.disconnect(); }); }); diff --git a/src/cosmosconnection.ts b/src/cosmosconnection.ts index f4e8e6d5..aec3825b 100644 --- a/src/cosmosconnection.ts +++ b/src/cosmosconnection.ts @@ -145,19 +145,23 @@ export class CosmosConnection implements BlockchainConnection { const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.prefix) : query.address; const { result } = await this.restClient.authAccounts(address); const account = result.value; + if (!account.address) { + return undefined; + } const supportedCoins = account.coins.filter(({ denom }) => this.tokenInfo.find(token => token.denom === denom), ); - return account.public_key === null + const pubkey = !account.public_key ? undefined : { - address: address, - balance: supportedCoins.map(decodeAmount(this.tokenInfo)), - pubkey: { - algo: Algorithm.Secp256k1, - data: fromBase64(account.public_key.value) as PubkeyBytes, - }, + algo: Algorithm.Secp256k1, + data: fromBase64(account.public_key.value) as PubkeyBytes, }; + return { + address: address, + balance: supportedCoins.map(decodeAmount(this.tokenInfo)), + pubkey: pubkey, + }; } public watchAccount(_account: AccountQuery): Stream { From f1ad81a05a5f2b9785a4a45259e906dbe0fca5ba Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 22 Jan 2020 23:33:00 +0100 Subject: [PATCH 09/10] Post tx tests work until invalid signature --- src/cosmoscodec.ts | 4 ++-- src/cosmosconnection.spec.ts | 23 +++++++++++++++-------- src/cosmosconnection.ts | 1 + src/decode.spec.ts | 17 ++++++++--------- src/encode.spec.ts | 27 +++++++++++++++++++-------- src/encode.ts | 26 ++++++++++---------------- 6 files changed, 55 insertions(+), 43 deletions(-) diff --git a/src/cosmoscodec.ts b/src/cosmoscodec.ts index b5497f01..9e122a8f 100644 --- a/src/cosmoscodec.ts +++ b/src/cosmoscodec.ts @@ -56,7 +56,7 @@ export class CosmosCodec implements TxCodec { public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob { const accountNumber = 0; const memo = (unsigned as any).memo; - const built = buildUnsignedTx(unsigned); + const built = buildUnsignedTx(unsigned, this.tokens); const signMsg = sortJson({ account_number: accountNumber.toString(), @@ -75,7 +75,7 @@ export class CosmosCodec implements TxCodec { } public bytesToPost(signed: SignedTransaction): PostableBytes { - const built = buildSignedTx(signed); + const built = buildSignedTx(signed, this.tokens); const bytes = marshalTx(built, true); return bytes as PostableBytes; } diff --git a/src/cosmosconnection.spec.ts b/src/cosmosconnection.spec.ts index e11ea319..0e5aa64a 100644 --- a/src/cosmosconnection.spec.ts +++ b/src/cosmosconnection.spec.ts @@ -14,7 +14,7 @@ import { Secp256k1 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol"; -import { cosmosCodec } from "./cosmoscodec"; +import { cosmosCodec, CosmosCodec } from "./cosmoscodec"; import { CosmosConnection } from "./cosmosconnection"; import { CosmosBech32Prefix } from "./address"; import { TokenInfos } from "./types"; @@ -28,7 +28,7 @@ function pendingWithoutCosmos(): void { } describe("CosmosConnection", () => { - const atom = "ATOM" as TokenTicker; + const cosm = "COSM" as TokenTicker; const httpUrl = "http://localhost:1317"; const defaultChainId = "cosmos:testing" as ChainId; const defaultEmptyAddress = "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address; @@ -191,13 +191,17 @@ describe("CosmosConnection", () => { amount: { quantity: "75000", fractionalDigits: 6, - tokenTicker: atom, + tokenTicker: cosm, }, }); const nonce = await connection.getNonce({ address: faucetAddress }); - const signed = await profile.signTransaction(faucet, unsigned, cosmosCodec, nonce); - const postableBytes = cosmosCodec.bytesToPost(signed); + // TODO: we need to use custom codecs everywhere + const codec = new CosmosCodec(defaultPrefix, defaultTokens); + console.log("nonce:", nonce); + const signed = await profile.signTransaction(faucet, unsigned, codec, nonce); + const postableBytes = codec.bytesToPost(signed); const response = await connection.postTx(postableBytes); + console.log(response); const { transactionId } = response; const blockInfo = await response.blockInfo.waitFor(info => !isBlockInfoPending(info)); expect(blockInfo.state).toEqual(TransactionState.Succeeded); @@ -250,13 +254,16 @@ describe("CosmosConnection", () => { amount: { quantity: "75000", fractionalDigits: 6, - tokenTicker: atom, + tokenTicker: cosm, }, }); const nonce = await connection.getNonce({ address: faucetAddress }); - const signed = await profile.signTransaction(faucet, unsigned, cosmosCodec, nonce); - const postableBytes = cosmosCodec.bytesToPost(signed); + // TODO: we need to use custom codecs everywhere + const codec = new CosmosCodec(defaultPrefix, defaultTokens); + const signed = await profile.signTransaction(faucet, unsigned, codec, nonce); + const postableBytes = codec.bytesToPost(signed); const response = await connection.postTx(postableBytes); + console.log(response); const { transactionId } = response; const blockInfo = await response.blockInfo.waitFor(info => !isBlockInfoPending(info)); expect(blockInfo.state).toEqual(TransactionState.Succeeded); diff --git a/src/cosmosconnection.ts b/src/cosmosconnection.ts index aec3825b..76f67f45 100644 --- a/src/cosmosconnection.ts +++ b/src/cosmosconnection.ts @@ -214,6 +214,7 @@ export class CosmosConnection implements BlockchainConnection { } public async postTx(tx: PostableBytes): Promise { + // TODO: we need to check errors here... bad chain-id breaks this const { txhash, raw_log } = await this.restClient.postTx(tx); const transactionId = txhash as TransactionId; const firstEvent: BlockInfo = { state: TransactionState.Pending }; diff --git a/src/decode.spec.ts b/src/decode.spec.ts index 8ca2e5f1..64fe28f4 100644 --- a/src/decode.spec.ts +++ b/src/decode.spec.ts @@ -19,15 +19,6 @@ import { TokenInfos } from "./types"; const { fromBase64 } = Encoding; -const defaultTokens: TokenInfos = [ - { - fractionalDigits: 6, - tokenName: "Atom (Cosmos Hub)", - tokenTicker: "ATOM" as TokenTicker, - denom: "uatom", - }, -]; - describe("decode", () => { const defaultPubkey = { algo: Algorithm.Secp256k1, @@ -61,6 +52,14 @@ describe("decode", () => { }, gasLimit: "200000", }; + const defaultTokens: TokenInfos = [ + { + fractionalDigits: 6, + tokenName: "Atom (Cosmos Hub)", + tokenTicker: "ATOM" as TokenTicker, + denom: "uatom", + }, + ]; describe("decodePubkey", () => { it("works", () => { diff --git a/src/encode.spec.ts b/src/encode.spec.ts index 4b4179e5..2eaaeb14 100644 --- a/src/encode.spec.ts +++ b/src/encode.spec.ts @@ -21,6 +21,7 @@ import { encodeFullSignature, encodePubkey, } from "./encode"; +import { TokenInfos } from "./types"; const { fromBase64 } = Encoding; @@ -40,6 +41,14 @@ describe("encode", () => { tokenTicker: atom, }; const defaultMemo = "hello cosmos hub"; + const defaultTokens: TokenInfos = [ + { + fractionalDigits: 6, + tokenName: "Atom (Cosmos Hub)", + tokenTicker: "ATOM" as TokenTicker, + denom: "uatom", + }, + ]; describe("encodePubKey", () => { it("encodes a Secp256k1 pubkey", () => { @@ -52,7 +61,7 @@ describe("encode", () => { describe("encodeAmount", () => { it("encodes an amount", () => { - expect(encodeAmount(defaultAmount)).toEqual({ + expect(encodeAmount(defaultAmount, defaultTokens)).toEqual({ denom: "uatom", amount: "11657995", }); @@ -64,7 +73,7 @@ describe("encode", () => { const fee = { gasLimit: "200000", }; - expect(() => encodeFee(fee)).toThrowError(/cannot encode fee without tokens/i); + expect(() => encodeFee(fee, defaultTokens)).toThrowError(/cannot encode fee without tokens/i); }); it("throws without gas limit", () => { @@ -75,7 +84,7 @@ describe("encode", () => { tokenTicker: atom, }, }; - expect(() => encodeFee(fee)).toThrowError(/cannot encode fee without gas limit/i); + expect(() => encodeFee(fee, defaultTokens)).toThrowError(/cannot encode fee without gas limit/i); }); it("encodes a fee", () => { @@ -87,7 +96,7 @@ describe("encode", () => { }, gasLimit: "200000", }; - expect(encodeFee(fee)).toEqual({ + expect(encodeFee(fee, defaultTokens)).toEqual({ amount: [{ denom: "uatom", amount: "5000" }], gas: "200000", }); @@ -168,7 +177,9 @@ describe("encode", () => { chainId: defaultChainId, escrowId: "defg", }; - expect(() => buildUnsignedTx(tx)).toThrowError(/received transaction of unsupported kind/i); + expect(() => buildUnsignedTx(tx, defaultTokens)).toThrowError( + /received transaction of unsupported kind/i, + ); }); it("builds a send transaction without fee", () => { @@ -180,7 +191,7 @@ describe("encode", () => { recipient: defaultRecipient, memo: defaultMemo, }; - expect(buildUnsignedTx(tx)).toEqual({ + expect(buildUnsignedTx(tx, defaultTokens)).toEqual({ type: "cosmos-sdk/StdTx", value: { msg: [ @@ -225,7 +236,7 @@ describe("encode", () => { gasLimit: "200000", }, }; - expect(buildUnsignedTx(tx)).toEqual({ + expect(buildUnsignedTx(tx, defaultTokens)).toEqual({ type: "cosmos-sdk/StdTx", value: { msg: [ @@ -286,7 +297,7 @@ describe("encode", () => { }, ], }; - expect(buildSignedTx(tx)).toEqual({ + expect(buildSignedTx(tx, defaultTokens)).toEqual({ type: "cosmos-sdk/StdTx", value: { msg: [ diff --git a/src/encode.ts b/src/encode.ts index 88ddb24d..1c098084 100644 --- a/src/encode.ts +++ b/src/encode.ts @@ -13,7 +13,7 @@ import { Secp256k1 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import amino from "@tendermint/amino-js"; -import { AminoTx } from "./types"; +import { AminoTx, TokenInfos, amountToCoin } from "./types"; const { toBase64 } = Encoding; @@ -34,17 +34,11 @@ export function encodePubkey(pubkey: PubkeyBundle): amino.PubKey { } } -export function encodeAmount(amount: Amount): amino.Coin { - if (amount.tokenTicker !== "ATOM") { - throw new Error("Only ATOM amounts are supported"); - } - return { - denom: "uatom", - amount: amount.quantity, - }; +export function encodeAmount(amount: Amount, tokens: TokenInfos): amino.Coin { + return amountToCoin(tokens, amount); } -export function encodeFee(fee: Fee): amino.StdFee { +export function encodeFee(fee: Fee, tokens: TokenInfos): amino.StdFee { if (fee.tokens === undefined) { throw new Error("Cannot encode fee without tokens"); } @@ -52,7 +46,7 @@ export function encodeFee(fee: Fee): amino.StdFee { throw new Error("Cannot encode fee without gas limit"); } return { - amount: [encodeAmount(fee.tokens)], + amount: [encodeAmount(fee.tokens, tokens)], gas: fee.gasLimit, }; } @@ -68,7 +62,7 @@ export function encodeFullSignature(fullSignature: FullSignature): amino.StdSign }; } -export function buildUnsignedTx(tx: UnsignedTransaction): AminoTx { +export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): AminoTx { if (!isSendTransaction(tx)) { throw new Error("Received transaction of unsupported kind"); } @@ -81,14 +75,14 @@ export function buildUnsignedTx(tx: UnsignedTransaction): AminoTx { value: { from_address: tx.sender, to_address: tx.recipient, - amount: [encodeAmount(tx.amount)], + amount: [encodeAmount(tx.amount, tokens)], }, }, ], memo: tx.memo || "", signatures: [], fee: tx.fee - ? encodeFee(tx.fee) + ? encodeFee(tx.fee, tokens) : { amount: [], gas: "", @@ -97,8 +91,8 @@ export function buildUnsignedTx(tx: UnsignedTransaction): AminoTx { }; } -export function buildSignedTx(tx: SignedTransaction): AminoTx { - const built = buildUnsignedTx(tx.transaction); +export function buildSignedTx(tx: SignedTransaction, tokens: TokenInfos): AminoTx { + const built = buildUnsignedTx(tx.transaction, tokens); return { ...built, value: { From 310dad2a0fcf6f68ade1d10f31cf25d0a90fb2aa Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 22 Jan 2020 23:33:17 +0100 Subject: [PATCH 10/10] Commit updated types --- types/address.d.ts | 21 +++++---------------- types/cosmoscodec.d.ts | 13 +++++++------ types/cosmosconnection.d.ts | 33 ++++++++++++--------------------- types/cosmosconnector.d.ts | 6 +++++- types/decode.d.ts | 23 ++++++++++------------- types/encode.d.ts | 23 +++++++---------------- types/restclient.d.ts | 5 +---- types/types.d.ts | 11 ++++++++--- 8 files changed, 55 insertions(+), 80 deletions(-) diff --git a/types/address.d.ts b/types/address.d.ts index c5cba906..776cb61d 100644 --- a/types/address.d.ts +++ b/types/address.d.ts @@ -1,23 +1,12 @@ import { Address, PubkeyBundle } from "@iov/bcp"; -export declare type CosmosAddressBech32Prefix = - | "cosmos" - | "cosmosvalcons" - | "cosmosvaloper"; -export declare type CosmosPubkeyBech32Prefix = - | "cosmospub" - | "cosmosvalconspub" - | "cosmosvaloperpub"; -export declare type CosmosBech32Prefix = - | CosmosAddressBech32Prefix - | CosmosPubkeyBech32Prefix; +export declare type CosmosAddressBech32Prefix = "cosmos" | "cosmosvalcons" | "cosmosvaloper"; +export declare type CosmosPubkeyBech32Prefix = "cosmospub" | "cosmosvalconspub" | "cosmosvaloperpub"; +export declare type CosmosBech32Prefix = CosmosAddressBech32Prefix | CosmosPubkeyBech32Prefix; export declare function decodeCosmosAddress( - address: Address + address: Address, ): { readonly prefix: CosmosAddressBech32Prefix; readonly data: Uint8Array; }; export declare function isValidAddress(address: string): boolean; -export declare function pubkeyToAddress( - pubkey: PubkeyBundle, - prefix: CosmosBech32Prefix -): Address; +export declare function pubkeyToAddress(pubkey: PubkeyBundle, prefix: CosmosBech32Prefix): Address; diff --git a/types/cosmoscodec.d.ts b/types/cosmoscodec.d.ts index 59482edc..64de7f38 100644 --- a/types/cosmoscodec.d.ts +++ b/types/cosmoscodec.d.ts @@ -8,17 +8,18 @@ import { SigningJob, TransactionId, TxCodec, - UnsignedTransaction + UnsignedTransaction, } from "@iov/bcp"; +import { CosmosBech32Prefix } from "./address"; +import { TokenInfos } from "./types"; export declare class CosmosCodec implements TxCodec { + private readonly prefix; + private readonly tokens; + constructor(prefix: CosmosBech32Prefix, tokens: TokenInfos); bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob; bytesToPost(signed: SignedTransaction): PostableBytes; identifier(signed: SignedTransaction): TransactionId; - parseBytes( - bytes: PostableBytes, - chainId: ChainId, - nonce?: Nonce - ): SignedTransaction; + parseBytes(bytes: PostableBytes, chainId: ChainId, nonce?: Nonce): SignedTransaction; identityToAddress(identity: Identity): Address; isValidAddress(address: string): boolean; } diff --git a/types/cosmosconnection.d.ts b/types/cosmosconnection.d.ts index 94efb242..4c8e8f66 100644 --- a/types/cosmosconnection.d.ts +++ b/types/cosmosconnection.d.ts @@ -17,16 +17,20 @@ import { TokenTicker, TransactionId, TransactionQuery, - UnsignedTransaction + UnsignedTransaction, } from "@iov/bcp"; import { Stream } from "xstream"; +import { CosmosBech32Prefix } from "./address"; +import { TokenInfos } from "./types"; export declare class CosmosConnection implements BlockchainConnection { - static establish(url: string): Promise; + static establish(url: string, prefix: CosmosBech32Prefix, tokenInfo: TokenInfos): Promise; private static initialize; private readonly restClient; private readonly chainData; private readonly primaryToken; private readonly supportedTokens; + private readonly _prefix; + private readonly tokenInfo; private get prefix(); private constructor(); disconnect(): void; @@ -37,29 +41,16 @@ export declare class CosmosConnection implements BlockchainConnection { getAccount(query: AccountQuery): Promise; watchAccount(_account: AccountQuery): Stream; getNonce(query: AddressQuery | PubkeyQuery): Promise; - getNonces( - query: AddressQuery | PubkeyQuery, - count: number - ): Promise; + getNonces(query: AddressQuery | PubkeyQuery, count: number): Promise; getBlockHeader(height: number): Promise; watchBlockHeaders(): Stream; - getTx( - id: TransactionId - ): Promise< - ConfirmedAndSignedTransaction | FailedTransaction - >; + getTx(id: TransactionId): Promise | FailedTransaction>; postTx(tx: PostableBytes): Promise; searchTx( - query: TransactionQuery - ): Promise< - readonly (ConfirmedTransaction | FailedTransaction)[] - >; - listenTx( - _query: TransactionQuery - ): Stream | FailedTransaction>; - liveTx( - _query: TransactionQuery - ): Stream | FailedTransaction>; + query: TransactionQuery, + ): Promise | FailedTransaction)[]>; + listenTx(_query: TransactionQuery): Stream | FailedTransaction>; + liveTx(_query: TransactionQuery): Stream | FailedTransaction>; getFeeQuote(tx: UnsignedTransaction): Promise; withDefaultFee(tx: T): Promise; private parseAndPopulateTxResponse; diff --git a/types/cosmosconnector.d.ts b/types/cosmosconnector.d.ts index 6a18915d..509a8068 100644 --- a/types/cosmosconnector.d.ts +++ b/types/cosmosconnector.d.ts @@ -1,9 +1,13 @@ import { ChainConnector, ChainId } from "@iov/bcp"; import { CosmosConnection } from "./cosmosconnection"; +import { CosmosBech32Prefix } from "./address"; +import { TokenInfos } from "./types"; /** * A helper to connect to a cosmos-based chain at a given url */ export declare function createCosmosConnector( url: string, - expectedChainId?: ChainId + prefix: CosmosBech32Prefix, + tokenInfo: TokenInfos, + expectedChainId?: ChainId, ): ChainConnector; diff --git a/types/decode.d.ts b/types/decode.d.ts index ac66c871..8b7332c7 100644 --- a/types/decode.d.ts +++ b/types/decode.d.ts @@ -9,30 +9,27 @@ import { SendTransaction, SignatureBytes, SignedTransaction, - UnsignedTransaction + UnsignedTransaction, } from "@iov/bcp"; import amino from "@tendermint/amino-js"; import { TxsResponse } from "./restclient"; +import { TokenInfos } from "./types"; export declare function decodePubkey(pubkey: amino.PubKey): PubkeyBundle; export declare function decodeSignature(signature: string): SignatureBytes; -export declare function decodeFullSignature( - signature: amino.StdSignature, - nonce: number -): FullSignature; -export declare function decodeAmount(amount: amino.Coin): Amount; -export declare function parseMsg( - msg: amino.Msg, - chainId: ChainId -): SendTransaction; -export declare function parseFee(fee: amino.StdFee): Fee; +export declare function decodeFullSignature(signature: amino.StdSignature, nonce: number): FullSignature; +export declare const decodeAmount: (tokens: TokenInfos) => (coin: amino.Coin) => Amount; +export declare function parseMsg(msg: amino.Msg, chainId: ChainId, tokens: TokenInfos): SendTransaction; +export declare function parseFee(fee: amino.StdFee, tokens: TokenInfos): Fee; export declare function parseTx( tx: amino.Tx, chainId: ChainId, - nonce: Nonce + nonce: Nonce, + tokens: TokenInfos, ): SignedTransaction; export declare function parseTxsResponse( chainId: ChainId, currentHeight: number, nonce: Nonce, - response: TxsResponse + response: TxsResponse, + tokens: TokenInfos, ): ConfirmedAndSignedTransaction; diff --git a/types/encode.d.ts b/types/encode.d.ts index e5931aef..2a0788df 100644 --- a/types/encode.d.ts +++ b/types/encode.d.ts @@ -1,18 +1,9 @@ -import { - Amount, - Fee, - FullSignature, - PubkeyBundle, - SignedTransaction, - UnsignedTransaction -} from "@iov/bcp"; +import { Amount, Fee, FullSignature, PubkeyBundle, SignedTransaction, UnsignedTransaction } from "@iov/bcp"; import amino from "@tendermint/amino-js"; -import { AminoTx } from "./types"; +import { AminoTx, TokenInfos } from "./types"; export declare function encodePubkey(pubkey: PubkeyBundle): amino.PubKey; -export declare function encodeAmount(amount: Amount): amino.Coin; -export declare function encodeFee(fee: Fee): amino.StdFee; -export declare function encodeFullSignature( - fullSignature: FullSignature -): amino.StdSignature; -export declare function buildUnsignedTx(tx: UnsignedTransaction): AminoTx; -export declare function buildSignedTx(tx: SignedTransaction): AminoTx; +export declare function encodeAmount(amount: Amount, tokens: TokenInfos): amino.Coin; +export declare function encodeFee(fee: Fee, tokens: TokenInfos): amino.StdFee; +export declare function encodeFullSignature(fullSignature: FullSignature): amino.StdSignature; +export declare function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): AminoTx; +export declare function buildSignedTx(tx: SignedTransaction, tokens: TokenInfos): AminoTx; diff --git a/types/restclient.d.ts b/types/restclient.d.ts index 9097d2cb..df1d9251 100644 --- a/types/restclient.d.ts +++ b/types/restclient.d.ts @@ -69,10 +69,7 @@ export declare class RestClient { nodeInfo(): Promise; blocksLatest(): Promise; blocks(height: number): Promise; - authAccounts( - address: Address, - height?: string - ): Promise; + authAccounts(address: Address, height?: string): Promise; txs(query: string): Promise; txsById(id: TransactionId): Promise; postTx(tx: PostableBytes): Promise; diff --git a/types/types.d.ts b/types/types.d.ts index adcda576..62d5ab9b 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -1,7 +1,12 @@ +import { Amount, Token } from "@iov/bcp"; import amino from "@tendermint/amino-js"; export declare type AminoTx = amino.Tx & { readonly value: amino.StdTx; }; -export declare function isAminoStdTx( - txValue: amino.TxValue -): txValue is amino.StdTx; +export declare function isAminoStdTx(txValue: amino.TxValue): txValue is amino.StdTx; +export interface TokenInfo extends Token { + readonly denom: string; +} +export declare type TokenInfos = ReadonlyArray; +export declare function amountToCoin(lookup: ReadonlyArray, amount: Amount): amino.Coin; +export declare function coinToAmount(tokens: TokenInfos, coin: amino.Coin): Amount;