Merge pull request #36 from confio/remove-amino

Remove amino
This commit is contained in:
merge-when-green[bot] 2020-02-03 21:55:38 +00:00 committed by GitHub
commit 19c9c27e76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 479 additions and 118 deletions

View File

@ -43,7 +43,6 @@
"@iov/crypto": "^2.0.0-alpha.7",
"@iov/encoding": "^2.0.0-alpha.7",
"@iov/stream": "^2.0.0-alpha.7",
"@tendermint/amino-js": "^0.7.0-alpha.1",
"fast-deep-equal": "^3.1.1",
"readonly-date": "^1.0.0",
"xstream": "^11.11.0"

View File

@ -33,8 +33,8 @@ describe("address", () => {
expect(
decodeCosmosPubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"),
).toEqual({
prefix: "cosmospub",
data: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"),
algo: Algorithm.Secp256k1,
});
});
});

View File

@ -1,4 +1,4 @@
import { Address, Algorithm, PubkeyBundle } from "@iov/bcp";
import { Address, Algorithm, PubkeyBundle, PubkeyBytes } from "@iov/bcp";
import { Ripemd160, Secp256k1, Sha256 } from "@iov/crypto";
import { Bech32, Encoding } from "@iov/encoding";
import equal from "fast-deep-equal";
@ -8,7 +8,10 @@ export type CosmosPubkeyBech32Prefix = "cosmospub" | "cosmosvalconspub" | "cosmo
export type CosmosBech32Prefix = CosmosAddressBech32Prefix | CosmosPubkeyBech32Prefix;
// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163
const pubkeyAminoPrefix = Encoding.fromHex("eb5ae98721");
// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography
const pubkeyAminoPrefixSecp256k1 = Encoding.fromHex("eb5ae98721");
const pubkeyAminoPrefixEd25519 = Encoding.fromHex("1624de64");
const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length;
function isCosmosAddressBech32Prefix(prefix: string): prefix is CosmosAddressBech32Prefix {
return ["cosmos", "cosmosvalcons", "cosmosvaloper"].includes(prefix);
@ -33,22 +36,27 @@ export function decodeCosmosAddress(
export function decodeCosmosPubkey(
encodedPubkey: string,
): { readonly prefix: CosmosPubkeyBech32Prefix; readonly data: Uint8Array } {
): { readonly algo: Algorithm; readonly data: PubkeyBytes } {
const { prefix, data } = Bech32.decode(encodedPubkey);
if (!isCosmosPubkeyBech32Prefix(prefix)) {
throw new Error(`Invalid bech32 prefix. Must be one of cosmos, cosmosvalcons, or cosmosvaloper.`);
}
if (!equal(data.slice(0, pubkeyAminoPrefix.length), pubkeyAminoPrefix)) {
throw new Error("Pubkey does not have the expected amino prefix " + Encoding.toHex(pubkeyAminoPrefix));
const aminoPrefix = data.slice(0, pubkeyAminoPrefixLength);
const rest = data.slice(pubkeyAminoPrefixLength);
if (equal(aminoPrefix, pubkeyAminoPrefixSecp256k1)) {
if (rest.length !== 33) {
throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey).");
}
return { algo: Algorithm.Secp256k1, data: rest as PubkeyBytes };
} else if (equal(aminoPrefix, pubkeyAminoPrefixEd25519)) {
if (rest.length !== 32) {
throw new Error("Invalid rest data length. Expected 32 bytes (ed25519 pubkey).");
}
return { algo: Algorithm.Ed25519, data: rest as PubkeyBytes };
} else {
throw new Error("Unsupported Pubkey type. Amino prefix: " + Encoding.toHex(aminoPrefix));
}
const rest = data.slice(pubkeyAminoPrefix.length);
if (rest.length !== 33) {
throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey).");
}
return { prefix: prefix, data: rest };
}
export function isValidAddress(address: string): boolean {

View File

@ -2,7 +2,7 @@ import { PostableBytes, PrehashType } from "@iov/bcp";
import { Encoding } from "@iov/encoding";
import { cosmWasmCodec } from "./cosmwasmcodec";
import { chainId, nonce, sendTxJson, signedTxBin, signedTxJson, txId } from "./testdata.spec";
import { chainId, nonce, sendTxJson, signedTxBin, signedTxEncodedJson, signedTxJson } from "./testdata.spec";
const { toUtf8 } = Encoding;
@ -21,7 +21,7 @@ describe("cosmWasmCodec", () => {
it("properly encodes transactions", () => {
const encoded = cosmWasmCodec.bytesToPost(signedTxJson);
expect(encoded).toEqual(signedTxBin);
expect(encoded).toEqual(signedTxEncodedJson);
});
it("throws when trying to decode a transaction without a nonce", () => {
@ -31,16 +31,10 @@ describe("cosmWasmCodec", () => {
});
it("properly decodes transactions", () => {
const decoded = cosmWasmCodec.parseBytes(signedTxBin as PostableBytes, chainId, nonce);
const decoded = cosmWasmCodec.parseBytes(signedTxEncodedJson as PostableBytes, chainId, nonce);
expect(decoded).toEqual(signedTxJson);
});
it("generates transaction id", () => {
const id = cosmWasmCodec.identifier(signedTxJson);
expect(id).toMatch(/^[0-9A-F]{64}$/);
expect(id).toEqual(txId);
});
it("round trip works", () => {
const encoded = cosmWasmCodec.bytesToPost(signedTxJson);
const decoded = cosmWasmCodec.parseBytes(encoded, chainId, nonce);

View File

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/camelcase */
import { marshalTx, unmarshalTx } from "@cosmwasm/sdk";
import {
Address,
ChainId,
@ -13,9 +14,7 @@ import {
TxCodec,
UnsignedTransaction,
} from "@iov/bcp";
import { Sha256 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { marshalTx, unmarshalTx } from "@tendermint/amino-js";
import { CosmosBech32Prefix, isValidAddress, pubkeyToAddress } from "./address";
import { Caip5 } from "./caip5";
@ -23,7 +22,7 @@ import { parseTx } from "./decode";
import { buildSignedTx, buildUnsignedTx } from "./encode";
import { nonceToAccountNumber, nonceToSequence, TokenInfos } from "./types";
const { toHex, toUtf8 } = Encoding;
const { toUtf8 } = Encoding;
function sortJson(json: any): any {
if (typeof json !== "object" || json === null) {
@ -72,16 +71,20 @@ export class CosmWasmCodec implements TxCodec {
};
}
// PostableBytes are JSON-encoded StdTx
public bytesToPost(signed: SignedTransaction): PostableBytes {
// TODO: change this as well (return StdTx, not AminoTx)?
const built = buildSignedTx(signed, this.tokens);
const bytes = marshalTx(built, true);
return bytes as PostableBytes;
return marshalTx(built.value) as PostableBytes;
}
public identifier(signed: SignedTransaction): TransactionId {
const bytes = this.bytesToPost(signed);
const hash = new Sha256(bytes).digest();
return toHex(hash).toUpperCase() as TransactionId;
// TODO: this needs some marshalling going on...
// Do we need to support this??
public identifier(_signed: SignedTransaction): TransactionId {
throw new Error("Not yet implemented, requires amino encoding- talk to Ethan");
// const bytes = this.bytesToPost(signed);
// const hash = new Sha256(bytes).digest();
// return toHex(hash).toUpperCase() as TransactionId;
}
public parseBytes(bytes: PostableBytes, chainId: ChainId, nonce?: Nonce): SignedTransaction {

View File

@ -17,6 +17,7 @@ import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol";
import { CosmosBech32Prefix } from "./address";
import { CosmWasmCodec, cosmWasmCodec } from "./cosmwasmcodec";
import { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection";
import { signedTxJson, txId } from "./testdata.spec";
import { nonceToSequence } from "./types";
const { fromBase64, toHex } = Encoding;
@ -133,6 +134,17 @@ describe("CosmWasmConnection", () => {
});
});
describe("identifier", () => {
it("calculates tx hash from PostableBytes", async () => {
pendingWithoutCosmos();
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
const postable = cosmWasmCodec.bytesToPost(signedTxJson);
const id = await connection.identifier(postable);
expect(id).toMatch(/^[0-9A-F]{64}$/);
expect(id).toEqual(txId);
});
});
describe("getAccount", () => {
it("gets an empty account by address", async () => {
pendingWithoutCosmos();

View File

@ -1,10 +1,9 @@
/* eslint-disable @typescript-eslint/camelcase */
import { RestClient, TxsResponse } from "@cosmwasm/sdk";
import { RestClient, TxsResponse, unmarshalTx } from "@cosmwasm/sdk";
import {
Account,
AccountQuery,
AddressQuery,
Algorithm,
BlockchainConnection,
BlockHeader,
BlockId,
@ -20,7 +19,6 @@ import {
Nonce,
PostableBytes,
PostTxResponse,
PubkeyBytes,
PubkeyQuery,
Token,
TokenTicker,
@ -29,7 +27,8 @@ import {
TransactionState,
UnsignedTransaction,
} from "@iov/bcp";
import { Uint53 } from "@iov/encoding";
import { Sha256 } from "@iov/crypto";
import { Encoding, Uint53 } from "@iov/encoding";
import { DefaultValueProducer, ValueAndUpdates } from "@iov/stream";
import equal from "fast-deep-equal";
import { ReadonlyDate } from "readonly-date";
@ -40,6 +39,8 @@ import { Caip5 } from "./caip5";
import { decodeAmount, parseTxsResponse } from "./decode";
import { accountToNonce, TokenInfo } from "./types";
const { toHex } = Encoding;
interface ChainData {
readonly chainId: ChainId;
}
@ -68,7 +69,7 @@ function buildQueryString({
return components.filter(Boolean).join("&");
}
export type TokenConfiguration = readonly (TokenInfo & { readonly name: string })[];
export type TokenConfiguration = ReadonlyArray<TokenInfo & { readonly name: string }>;
export class CosmWasmConnection implements BlockchainConnection {
// we must know prefix and tokens a priori to understand the chain
@ -140,6 +141,13 @@ export class CosmWasmConnection implements BlockchainConnection {
return this.supportedTokens;
}
public async identifier(signed: PostableBytes): Promise<TransactionId> {
const tx = unmarshalTx(signed);
const bytes = await this.restClient.encodeTx(tx);
const hash = new Sha256(bytes).digest();
return toHex(hash).toUpperCase() as TransactionId;
}
public async getAccount(query: AccountQuery): Promise<Account | undefined> {
const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.prefix) : query.address;
const { result } = await this.restClient.authAccounts(address);
@ -151,13 +159,7 @@ export class CosmWasmConnection implements BlockchainConnection {
this.tokenInfo.find(token => token.denom === denom),
);
const pubkey = !account.public_key
? undefined
: {
algo: Algorithm.Secp256k1,
// amino-js has wrong (outdated) types
data: decodeCosmosPubkey(account.public_key as any).data as PubkeyBytes,
};
const pubkey = !account.public_key ? undefined : decodeCosmosPubkey(account.public_key);
return {
address: address,
balance: supportedCoins.map(coin => decodeAmount(this.tokenInfo, coin)),

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/camelcase */
import { types } from "@cosmwasm/sdk";
import { Address, Algorithm, TokenTicker } from "@iov/bcp";
import { Encoding } from "@iov/encoding";
import amino from "@tendermint/amino-js";
import {
decodeAmount,
@ -113,7 +113,7 @@ describe("decode", () => {
describe("decodeAmount", () => {
it("works", () => {
const amount: amino.Coin = {
const amount: types.Coin = {
denom: "uatom",
amount: "11657995",
};
@ -123,7 +123,7 @@ describe("decode", () => {
describe("parseMsg", () => {
it("works", () => {
const msg: amino.Msg = {
const msg: types.Msg = {
type: "cosmos-sdk/MsgSend",
value: {
from_address: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r",
@ -157,7 +157,7 @@ describe("decode", () => {
describe("parseTx", () => {
it("works", () => {
expect(parseTx(data.tx, chainId, nonce, defaultTokens)).toEqual(signedTxJson);
expect(parseTx(data.tx.value, chainId, nonce, defaultTokens)).toEqual(signedTxJson);
});
});
@ -181,3 +181,179 @@ describe("decode", () => {
});
});
});
/*
Some output from sample rest queries:
$ wasmcli tx send $(wasmcli keys show validator -a) $(wasmcli keys show fred -a) 98765stake -y
{
"height": "4",
"txhash": "8A4613D62884EF8BB9BCCDDA3833D560701908BF17FE82A570EECCBACEF94A91",
"raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos16qu479grzwanyzav6xvtzncgdjkwhqw7vy2pje\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k\"},{\"key\":\"amount\",\"value\":\"98765stake\"}]}]}]",
"logs": [
{
"msg_index": 0,
"log": "",
"events": [
{
"type": "message",
"attributes": [
{
"key": "action",
"value": "send"
},
{
"key": "sender",
"value": "cosmos16qu479grzwanyzav6xvtzncgdjkwhqw7vy2pje"
},
{
"key": "module",
"value": "bank"
}
]
},
{
"type": "transfer",
"attributes": [
{
"key": "recipient",
"value": "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k"
},
{
"key": "amount",
"value": "98765stake"
}
]
}
]
}
],
"gas_wanted": "200000",
"gas_used": "53254"
}
$ wasmcli query tx 8A4613D62884EF8BB9BCCDDA3833D560701908BF17FE82A570EECCBACEF94A91
{
"height": "4",
"txhash": "8A4613D62884EF8BB9BCCDDA3833D560701908BF17FE82A570EECCBACEF94A91",
"raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos16qu479grzwanyzav6xvtzncgdjkwhqw7vy2pje\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k\"},{\"key\":\"amount\",\"value\":\"98765stake\"}]}]}]",
"logs": [
{
"msg_index": 0,
"log": "",
"events": [
{
"type": "message",
"attributes": [
{
"key": "action",
"value": "send"
},
{
"key": "sender",
"value": "cosmos16qu479grzwanyzav6xvtzncgdjkwhqw7vy2pje"
},
{
"key": "module",
"value": "bank"
}
]
},
{
"type": "transfer",
"attributes": [
{
"key": "recipient",
"value": "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k"
},
{
"key": "amount",
"value": "98765stake"
}
]
}
]
}
],
"gas_wanted": "200000",
"gas_used": "53254",
"tx": {
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "cosmos-sdk/MsgSend",
"value": {
"from_address": "cosmos16qu479grzwanyzav6xvtzncgdjkwhqw7vy2pje",
"to_address": "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k",
"amount": [
{
"denom": "stake",
"amount": "98765"
}
]
}
}
],
"fee": {
"amount": [],
"gas": "200000"
},
"signatures": [
{
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "A11L8EitFnA6YsZ2QSnbMNmK+qI2kxyevDtSfhPqOwcp"
},
"signature": "qCeKoqZeaL0LThKrUXHLgu72jwTiF+DseSBjcKHtcONE0kIdybwYJpuYg3Jj71hmfync+daHNdqgJlPRma0pPA=="
}
],
"memo": ""
}
},
"timestamp": "2020-02-03T17:06:58Z"
}
$ wasmcli query account $(wasmcli keys show fred -a)
{
"type": "cosmos-sdk/Account",
"value": {
"address": "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k",
"coins": [
{
"denom": "stake",
"amount": "98765"
}
],
"public_key": "",
"account_number": 7,
"sequence": 0
}
}
$ wasmcli query account $(wasmcli keys show validator -a)
{
"type": "cosmos-sdk/Account",
"value": {
"address": "cosmos16qu479grzwanyzav6xvtzncgdjkwhqw7vy2pje",
"coins": [
{
"denom": "stake",
"amount": "899901235"
},
{
"denom": "validatortoken",
"amount": "1000000000"
}
],
"public_key": "cosmospub1addwnpepqdw5huzg45t8qwnzcemyz2wmxrvc474zx6f3e84u8df8uyl28vrjjnp9v4p",
"account_number": 3,
"sequence": 2
}
}
*/

View File

@ -1,4 +1,4 @@
import { isAminoStdTx, TxsResponse } from "@cosmwasm/sdk";
import { TxsResponse, types } from "@cosmwasm/sdk";
import {
Address,
Algorithm,
@ -18,13 +18,12 @@ import {
UnsignedTransaction,
} from "@iov/bcp";
import { Decimal, Encoding } from "@iov/encoding";
import amino from "@tendermint/amino-js";
import { TokenInfos } from "./types";
const { fromBase64 } = Encoding;
export function decodePubkey(pubkey: amino.PubKey): PubkeyBundle {
export function decodePubkey(pubkey: types.PubKey): PubkeyBundle {
switch (pubkey.type) {
// https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23
case "tendermint/PubKeySecp256k1":
@ -47,7 +46,7 @@ export function decodeSignature(signature: string): SignatureBytes {
return fromBase64(signature) as SignatureBytes;
}
export function decodeFullSignature(signature: amino.StdSignature, nonce: number): FullSignature {
export function decodeFullSignature(signature: types.StdSignature, nonce: number): FullSignature {
return {
nonce: nonce as Nonce,
pubkey: decodePubkey(signature.pub_key),
@ -55,7 +54,7 @@ export function decodeFullSignature(signature: amino.StdSignature, nonce: number
};
}
export function coinToDecimal(tokens: TokenInfos, coin: amino.Coin): readonly [Decimal, string] {
export function coinToDecimal(tokens: TokenInfos, coin: types.Coin): readonly [Decimal, string] {
const match = tokens.find(({ denom }) => denom === coin.denom);
if (!match) {
throw Error(`unknown denom: ${coin.denom}`);
@ -64,7 +63,7 @@ export function coinToDecimal(tokens: TokenInfos, coin: amino.Coin): readonly [D
return [value, match.ticker];
}
export function decodeAmount(tokens: TokenInfos, coin: amino.Coin): Amount {
export function decodeAmount(tokens: TokenInfos, coin: types.Coin): Amount {
const [value, ticker] = coinToDecimal(tokens, coin);
return {
quantity: value.atomics,
@ -73,14 +72,14 @@ export function decodeAmount(tokens: TokenInfos, coin: amino.Coin): Amount {
};
}
export function parseMsg(msg: amino.Msg, chainId: ChainId, tokens: TokenInfos): SendTransaction {
export function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos): SendTransaction {
if (msg.type !== "cosmos-sdk/MsgSend") {
throw new Error("Unknown message type in transaction");
}
if (!(msg.value as amino.MsgSend).from_address) {
if (!(msg.value as types.MsgSend).from_address) {
throw new Error("Only MsgSend is supported");
}
const msgValue = msg.value as amino.MsgSend;
const msgValue = msg.value as types.MsgSend;
if (msgValue.amount.length !== 1) {
throw new Error("Only MsgSend with one amount is supported");
}
@ -93,7 +92,7 @@ export function parseMsg(msg: amino.Msg, chainId: ChainId, tokens: TokenInfos):
};
}
export function parseFee(fee: amino.StdFee, tokens: TokenInfos): Fee {
export function parseFee(fee: types.StdFee, tokens: TokenInfos): Fee {
if (fee.amount.length !== 1) {
throw new Error("Only fee with one amount is supported");
}
@ -103,9 +102,13 @@ export function parseFee(fee: amino.StdFee, tokens: TokenInfos): Fee {
};
}
export function parseTx(tx: amino.Tx, chainId: ChainId, nonce: Nonce, tokens: TokenInfos): SignedTransaction {
const txValue = tx.value;
if (!isAminoStdTx(txValue)) {
export function parseTx(
txValue: types.StdTx,
chainId: ChainId,
nonce: Nonce,
tokens: TokenInfos,
): SignedTransaction {
if (!types.isAminoStdTx(txValue)) {
throw new Error("Only Amino StdTx is supported");
}
if (txValue.msg.length !== 1) {
@ -138,7 +141,7 @@ export function parseTxsResponse(
): ConfirmedAndSignedTransaction<UnsignedTransaction> {
const height = parseInt(response.height, 10);
return {
...parseTx(response.tx, chainId, nonce, tokens),
...parseTx(response.tx.value, chainId, nonce, tokens),
height: height,
confirmations: currentHeight - height + 1,
transactionId: response.txhash as TransactionId,

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/camelcase */
import { AminoTx } from "@cosmwasm/sdk";
import { types } from "@cosmwasm/sdk";
import {
Algorithm,
Amount,
@ -12,13 +12,12 @@ import {
} from "@iov/bcp";
import { Secp256k1 } from "@iov/crypto";
import { Decimal, Encoding } from "@iov/encoding";
import amino from "@tendermint/amino-js";
import { TokenInfos } from "./types";
const { toBase64 } = Encoding;
export function encodePubkey(pubkey: PubkeyBundle): amino.PubKey {
export function encodePubkey(pubkey: PubkeyBundle): types.PubKey {
switch (pubkey.algo) {
case Algorithm.Secp256k1:
return {
@ -35,7 +34,7 @@ export function encodePubkey(pubkey: PubkeyBundle): amino.PubKey {
}
}
export function decimalToCoin(lookup: TokenInfos, value: Decimal, ticker: string): amino.Coin {
export function decimalToCoin(lookup: TokenInfos, value: Decimal, ticker: string): types.Coin {
const match = lookup.find(token => token.ticker === ticker);
if (!match) {
throw Error(`unknown ticker: ${ticker}`);
@ -51,7 +50,7 @@ export function decimalToCoin(lookup: TokenInfos, value: Decimal, ticker: string
};
}
export function encodeAmount(amount: Amount, tokens: TokenInfos): amino.Coin {
export function encodeAmount(amount: Amount, tokens: TokenInfos): types.Coin {
return decimalToCoin(
tokens,
Decimal.fromAtomics(amount.quantity, amount.fractionalDigits),
@ -59,7 +58,7 @@ export function encodeAmount(amount: Amount, tokens: TokenInfos): amino.Coin {
);
}
export function encodeFee(fee: Fee, tokens: TokenInfos): amino.StdFee {
export function encodeFee(fee: Fee, tokens: TokenInfos): types.StdFee {
if (fee.tokens === undefined) {
throw new Error("Cannot encode fee without tokens");
}
@ -72,7 +71,7 @@ export function encodeFee(fee: Fee, tokens: TokenInfos): amino.StdFee {
};
}
export function encodeFullSignature(fullSignature: FullSignature): amino.StdSignature {
export function encodeFullSignature(fullSignature: FullSignature): types.StdSignature {
return {
pub_key: {
type: "tendermint/PubKeySecp256k1",
@ -83,7 +82,7 @@ export function encodeFullSignature(fullSignature: FullSignature): amino.StdSign
};
}
export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): AminoTx {
export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): types.AminoTx {
if (!isSendTransaction(tx)) {
throw new Error("Received transaction of unsupported kind");
}
@ -112,7 +111,7 @@ export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): Am
};
}
export function buildSignedTx(tx: SignedTransaction, tokens: TokenInfos): AminoTx {
export function buildSignedTx(tx: SignedTransaction, tokens: TokenInfos): types.AminoTx {
const built = buildUnsignedTx(tx.transaction, tokens);
return {
...built,

View File

@ -16,7 +16,7 @@ import { Encoding } from "@iov/encoding";
import data from "./testdata/cosmoshub.json";
const { fromBase64 } = Encoding;
const { fromBase64, toUtf8 } = Encoding;
export const pubJson: PubkeyBundle = {
algo: Algorithm.Secp256k1,
@ -62,3 +62,7 @@ export const signedTxJson: SignedTransaction = {
export const signedTxBin = fromBase64(data.tx_data);
export const txId = data.id as TransactionId;
export const signedTxEncodedJson = toUtf8(
`{"msg":[{"type":"cosmos-sdk/MsgSend","value":{"from_address":"cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq","to_address":"cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae","amount":[{"denom":"uatom","amount":"35997500"}]}}],"memo":"","signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAq"},"signature":"NK1Oy4EUGAsoC03c1wi9GG03JC/39LEdautC5Jk643oIbEPqeXHMwaqbdvO/Jws0X/NAXaN8SAy2KNY5Qml+5Q=="}],"fee":{"amount":[{"denom":"uatom","amount":"2500"}],"gas":"100000"}}`,
);

View File

@ -1,4 +1,4 @@
import { Address, PubkeyBundle } from "@iov/bcp";
import { Address, Algorithm, PubkeyBundle, PubkeyBytes } from "@iov/bcp";
export declare type CosmosAddressBech32Prefix = "cosmos" | "cosmosvalcons" | "cosmosvaloper";
export declare type CosmosPubkeyBech32Prefix = "cosmospub" | "cosmosvalconspub" | "cosmosvaloperpub";
export declare type CosmosBech32Prefix = CosmosAddressBech32Prefix | CosmosPubkeyBech32Prefix;
@ -11,8 +11,8 @@ export declare function decodeCosmosAddress(
export declare function decodeCosmosPubkey(
encodedPubkey: string,
): {
readonly prefix: CosmosPubkeyBech32Prefix;
readonly data: Uint8Array;
readonly algo: Algorithm;
readonly data: PubkeyBytes;
};
export declare function isValidAddress(address: string): boolean;
export declare function pubkeyToAddress(pubkey: PubkeyBundle, prefix: CosmosBech32Prefix): Address;

View File

@ -18,7 +18,7 @@ export declare class CosmWasmCodec implements TxCodec {
constructor(prefix: CosmosBech32Prefix, tokens: TokenInfos);
bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob;
bytesToPost(signed: SignedTransaction): PostableBytes;
identifier(signed: SignedTransaction): TransactionId;
identifier(_signed: SignedTransaction): TransactionId;
parseBytes(bytes: PostableBytes, chainId: ChainId, nonce?: Nonce): SignedTransaction;
identityToAddress(identity: Identity): Address;
isValidAddress(address: string): boolean;

View File

@ -22,9 +22,11 @@ import {
import { Stream } from "xstream";
import { CosmosBech32Prefix } from "./address";
import { TokenInfo } from "./types";
export declare type TokenConfiguration = readonly (TokenInfo & {
readonly name: string;
})[];
export declare type TokenConfiguration = ReadonlyArray<
TokenInfo & {
readonly name: string;
}
>;
export declare class CosmWasmConnection implements BlockchainConnection {
static establish(
url: string,
@ -45,6 +47,7 @@ export declare class CosmWasmConnection implements BlockchainConnection {
height(): Promise<number>;
getToken(searchTicker: TokenTicker): Promise<Token | undefined>;
getAllTokens(): Promise<readonly Token[]>;
identifier(signed: PostableBytes): Promise<TransactionId>;
getAccount(query: AccountQuery): Promise<Account | undefined>;
watchAccount(_account: AccountQuery): Stream<Account | undefined>;
getNonce(query: AddressQuery | PubkeyQuery): Promise<Nonce>;

View File

@ -1,4 +1,4 @@
import { TxsResponse } from "@cosmwasm/sdk";
import { TxsResponse, types } from "@cosmwasm/sdk";
import {
Amount,
ChainId,
@ -13,17 +13,16 @@ import {
UnsignedTransaction,
} from "@iov/bcp";
import { Decimal } from "@iov/encoding";
import amino from "@tendermint/amino-js";
import { TokenInfos } from "./types";
export declare function decodePubkey(pubkey: amino.PubKey): PubkeyBundle;
export declare function decodePubkey(pubkey: types.PubKey): PubkeyBundle;
export declare function decodeSignature(signature: string): SignatureBytes;
export declare function decodeFullSignature(signature: amino.StdSignature, nonce: number): FullSignature;
export declare function coinToDecimal(tokens: TokenInfos, coin: amino.Coin): readonly [Decimal, string];
export declare function 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 decodeFullSignature(signature: types.StdSignature, nonce: number): FullSignature;
export declare function coinToDecimal(tokens: TokenInfos, coin: types.Coin): readonly [Decimal, string];
export declare function decodeAmount(tokens: TokenInfos, coin: types.Coin): Amount;
export declare function parseMsg(msg: types.Msg, chainId: ChainId, tokens: TokenInfos): SendTransaction;
export declare function parseFee(fee: types.StdFee, tokens: TokenInfos): Fee;
export declare function parseTx(
tx: amino.Tx,
txValue: types.StdTx,
chainId: ChainId,
nonce: Nonce,
tokens: TokenInfos,

View File

@ -1,12 +1,11 @@
import { AminoTx } from "@cosmwasm/sdk";
import { types } from "@cosmwasm/sdk";
import { Amount, Fee, FullSignature, PubkeyBundle, SignedTransaction, UnsignedTransaction } from "@iov/bcp";
import { Decimal } from "@iov/encoding";
import amino from "@tendermint/amino-js";
import { TokenInfos } from "./types";
export declare function encodePubkey(pubkey: PubkeyBundle): amino.PubKey;
export declare function decimalToCoin(lookup: TokenInfos, value: Decimal, ticker: string): amino.Coin;
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;
export declare function encodePubkey(pubkey: PubkeyBundle): types.PubKey;
export declare function decimalToCoin(lookup: TokenInfos, value: Decimal, ticker: string): types.Coin;
export declare function encodeAmount(amount: Amount, tokens: TokenInfos): types.Coin;
export declare function encodeFee(fee: Fee, tokens: TokenInfos): types.StdFee;
export declare function encodeFullSignature(fullSignature: FullSignature): types.StdSignature;
export declare function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): types.AminoTx;
export declare function buildSignedTx(tx: SignedTransaction, tokens: TokenInfos): types.AminoTx;

View File

@ -39,7 +39,6 @@
},
"dependencies": {
"@iov/encoding": "^2.0.0-alpha.7",
"@tendermint/amino-js": "^0.7.0-alpha.1",
"axios": "^0.19.0"
},
"devDependencies": {

View File

@ -0,0 +1,11 @@
import { Encoding } from "@iov/encoding";
import { isAminoStdTx, StdTx } from "./types";
export function unmarshalTx(data: Uint8Array): StdTx {
const decoded = JSON.parse(Encoding.fromUtf8(data));
if (!isAminoStdTx(decoded)) {
throw new Error("Must be json encoded StdTx");
}
return decoded;
}

View File

@ -0,0 +1,8 @@
import { Encoding } from "@iov/encoding";
import { StdTx } from "./types";
export function marshalTx(tx: StdTx): Uint8Array {
const json = JSON.stringify(tx);
return Encoding.toUtf8(json);
}

View File

@ -1,2 +1,6 @@
import * as types from "./types";
export { unmarshalTx } from "./decoding";
export { marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { AminoTx, isAminoStdTx } from "./types";
export { types };

View File

@ -1,7 +1,9 @@
import amino, { unmarshalTx } from "@tendermint/amino-js";
import { Encoding } from "@iov/encoding";
import axios, { AxiosInstance } from "axios";
import { AminoTx } from "./types";
import { AminoTx, BaseAccount, isAminoStdTx, StdTx } from "./types";
const { fromUtf8 } = Encoding;
interface NodeInfo {
readonly network: string;
@ -35,7 +37,7 @@ interface BlocksResponse {
interface AuthAccountsResponse {
readonly result: {
readonly value: amino.BaseAccount;
readonly value: BaseAccount;
};
}
@ -64,13 +66,19 @@ interface PostTxsResponse {
readonly raw_log?: string;
}
interface EncodeTxResponse {
// base64-encoded amino-binary encoded representation
readonly tx: string;
}
type RestClientResponse =
| NodeInfoResponse
| BlocksResponse
| AuthAccountsResponse
| TxsResponse
| SearchTxsResponse
| PostTxsResponse;
| PostTxsResponse
| EncodeTxResponse;
type BroadcastMode = "block" | "sync" | "async";
@ -131,6 +139,16 @@ export class RestClient {
return responseData as BlocksResponse;
}
// encodeTx returns the amino-encoding of the transaction
public async encodeTx(stdTx: StdTx): Promise<Uint8Array> {
const tx = { type: "cosmos-sdk/StdTx", value: stdTx };
const responseData = await this.post("/txs/encode", tx);
if (!(responseData as any).tx) {
throw new Error("Unexpected response data format");
}
return Encoding.fromBase64((responseData as EncodeTxResponse).tx);
}
public async authAccounts(address: string, height?: string): Promise<AuthAccountsResponse> {
const path =
height === undefined ? `/auth/accounts/${address}` : `/auth/accounts/${address}?tx.height=${height}`;
@ -157,10 +175,15 @@ export class RestClient {
return responseData as TxsResponse;
}
// tx must be JSON encoded StdTx (no wrapper)
public async postTx(tx: Uint8Array): Promise<PostTxsResponse> {
const unmarshalled = unmarshalTx(tx, true);
// TODO: check this is StdTx
const decoded = JSON.parse(fromUtf8(tx));
if (!isAminoStdTx(decoded)) {
throw new Error("Must be json encoded StdTx");
}
const params = {
tx: unmarshalled.value,
tx: decoded,
mode: this.mode,
};
const responseData = await this.post("/txs", params);

View File

@ -1,10 +1,69 @@
import amino from "@tendermint/amino-js";
// We will move all needed *interfaces* from amino-js here
// This means bcp can just import them from here (if needed at all)
export interface Tx {
readonly type: string;
// TODO
readonly value: unknown;
}
export type AminoTx = amino.Tx & { readonly value: amino.StdTx };
export interface StdTx {
readonly msg: ReadonlyArray<Msg>;
readonly fee: StdFee;
readonly signatures: ReadonlyArray<StdSignature>;
readonly memo: string | undefined;
}
export function isAminoStdTx(txValue: amino.TxValue): txValue is amino.StdTx {
const { memo, msg, fee, signatures } = txValue as amino.StdTx;
export type AminoTx = Tx & { readonly value: StdTx };
export function isAminoStdTx(txValue: unknown): txValue is StdTx {
const { memo, msg, fee, signatures } = txValue as StdTx;
return (
typeof memo === "string" && Array.isArray(msg) && typeof fee === "object" && Array.isArray(signatures)
);
}
export interface Msg {
readonly type: string;
// TODO: make better union type
readonly value: MsgSend | unknown;
}
export interface MsgSend {
/** Bech32 account address */
readonly from_address: string;
/** Bech32 account address */
readonly to_address: string;
readonly amount: ReadonlyArray<Coin>;
}
export interface StdFee {
readonly amount: ReadonlyArray<Coin>;
readonly gas: string;
}
export interface Coin {
readonly denom: string;
readonly amount: string;
}
export interface StdSignature {
readonly pub_key: PubKey;
readonly signature: string;
}
export interface PubKey {
readonly type: string;
readonly value: string;
}
// AccountPubKey is bech32-encoded amino-binary encoded PubKey interface. oof.
export type AccountPubKey = string;
export interface BaseAccount {
/** Bech32 account address */
readonly address: string;
readonly coins: ReadonlyArray<Coin>;
readonly public_key: AccountPubKey;
readonly account_number: string;
readonly sequence: string;
}

View File

@ -0,0 +1,2 @@
import { StdTx } from "./types";
export declare function unmarshalTx(data: Uint8Array): StdTx;

View File

@ -0,0 +1,2 @@
import { StdTx } from "./types";
export declare function marshalTx(tx: StdTx): Uint8Array;

View File

@ -1,2 +1,5 @@
import * as types from "./types";
export { unmarshalTx } from "./decoding";
export { marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { AminoTx, isAminoStdTx } from "./types";
export { types };

View File

@ -1,5 +1,4 @@
import amino from "@tendermint/amino-js";
import { AminoTx } from "./types";
import { AminoTx, BaseAccount, StdTx } from "./types";
interface NodeInfo {
readonly network: string;
}
@ -27,7 +26,7 @@ interface BlocksResponse {
}
interface AuthAccountsResponse {
readonly result: {
readonly value: amino.BaseAccount;
readonly value: BaseAccount;
};
}
export interface TxsResponse {
@ -51,13 +50,17 @@ interface PostTxsResponse {
readonly code?: number;
readonly raw_log?: string;
}
interface EncodeTxResponse {
readonly tx: string;
}
declare type RestClientResponse =
| NodeInfoResponse
| BlocksResponse
| AuthAccountsResponse
| TxsResponse
| SearchTxsResponse
| PostTxsResponse;
| PostTxsResponse
| EncodeTxResponse;
declare type BroadcastMode = "block" | "sync" | "async";
export declare class RestClient {
private readonly client;
@ -68,6 +71,7 @@ export declare class RestClient {
nodeInfo(): Promise<NodeInfoResponse>;
blocksLatest(): Promise<BlocksResponse>;
blocks(height: number): Promise<BlocksResponse>;
encodeTx(stdTx: StdTx): Promise<Uint8Array>;
authAccounts(address: string, height?: string): Promise<AuthAccountsResponse>;
txs(query: string): Promise<SearchTxsResponse>;
txsById(id: string): Promise<TxsResponse>;

View File

@ -1,5 +1,50 @@
import amino from "@tendermint/amino-js";
export declare type AminoTx = amino.Tx & {
readonly value: amino.StdTx;
export interface Tx {
readonly type: string;
readonly value: unknown;
}
export interface StdTx {
readonly msg: ReadonlyArray<Msg>;
readonly fee: StdFee;
readonly signatures: ReadonlyArray<StdSignature>;
readonly memo: string | undefined;
}
export declare type AminoTx = Tx & {
readonly value: StdTx;
};
export declare function isAminoStdTx(txValue: amino.TxValue): txValue is amino.StdTx;
export declare function isAminoStdTx(txValue: unknown): txValue is StdTx;
export interface Msg {
readonly type: string;
readonly value: MsgSend | unknown;
}
export interface MsgSend {
/** Bech32 account address */
readonly from_address: string;
/** Bech32 account address */
readonly to_address: string;
readonly amount: ReadonlyArray<Coin>;
}
export interface StdFee {
readonly amount: ReadonlyArray<Coin>;
readonly gas: string;
}
export interface Coin {
readonly denom: string;
readonly amount: string;
}
export interface StdSignature {
readonly pub_key: PubKey;
readonly signature: string;
}
export interface PubKey {
readonly type: string;
readonly value: string;
}
export declare type AccountPubKey = string;
export interface BaseAccount {
/** Bech32 account address */
readonly address: string;
readonly coins: ReadonlyArray<Coin>;
readonly public_key: AccountPubKey;
readonly account_number: string;
readonly sequence: string;
}