Remove BCP package

This commit is contained in:
Simon Warta 2020-06-10 15:43:54 +02:00
parent dd120779aa
commit fa5c62a797
58 changed files with 5 additions and 3533 deletions

View File

@ -1 +0,0 @@
../../.eslintignore

View File

@ -1,3 +0,0 @@
build/
dist/
docs/

View File

@ -1,12 +0,0 @@
# @iov/cosmos-sdk
[![npm version](https://img.shields.io/npm/v/@iov/cosmos-sdk.svg)](https://www.npmjs.com/package/@iov/cosmos-sdk)
A [BCP](https://github.com/iov-one/iov-core/tree/master/packages/iov-bcp) implementation for Cosmos SDK 0.38.
## License
This package is part of the cosmjs repository, licensed under the Apache
License 2.0 (see
[NOTICE](https://github.com/CosmWasm/cosmjs/blob/master/NOTICE) and
[LICENSE](https://github.com/CosmWasm/cosmjs/blob/master/LICENSE)).

View File

@ -1,26 +0,0 @@
#!/usr/bin/env node
require("source-map-support").install();
const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json");
// setup Jasmine
const Jasmine = require("jasmine");
const jasmine = new Jasmine();
jasmine.loadConfig({
spec_dir: "build",
spec_files: ["**/*.spec.js"],
helpers: [],
random: false,
seed: null,
stopSpecOnExpectationFailure: false,
});
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000;
// setup reporter
const { SpecReporter } = require("jasmine-spec-reporter");
const reporter = new SpecReporter({ ...defaultSpecReporterConfig });
// initialize and execute
jasmine.env.clearReporters();
jasmine.addReporter(reporter);
jasmine.execute();

View File

@ -1,47 +0,0 @@
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: ".",
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ["jasmine"],
// list of files / patterns to load in the browser
files: ["dist/web/tests.js"],
client: {
jasmine: {
random: false,
timeoutInterval: 15000,
},
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ["progress", "kjhtml"],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ["Firefox"],
browserNoActivityTimeout: 90000,
// Keep brower open for debugging. This is overridden by yarn scripts
singleRun: false,
});
};

View File

@ -1 +0,0 @@
Directory used to trigger lerna package updates for all packages

View File

@ -1,56 +0,0 @@
{
"name": "@iov/cosmos-sdk",
"version": "0.20.0",
"description": "Transaction codec and client to communicate with a Cosmos SDK 0.38 blockchain",
"contributors": [
"Ethan Frey <ethanfrey@users.noreply.github.com>",
"Simon Warta"
],
"license": "Apache-2.0",
"main": "build/index.js",
"types": "types/index.d.ts",
"files": [
"build/",
"types/",
"*.md",
"!*.spec.*",
"!**/testdata/"
],
"repository": {
"type": "git",
"url": "https://github.com/CosmWasm/cosmjs/tree/master/packages/bcp"
},
"private": true,
"scripts": {
"docs": "shx rm -rf docs && typedoc --options typedoc.js",
"format": "prettier --write --loglevel warn \"./src/**/*.ts\"",
"lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"",
"lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix",
"move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts",
"format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"",
"build": "shx rm -rf ./build && tsc && yarn move-types && yarn format-types",
"build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build",
"test-node": "node jasmine-testrunner.js",
"test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox",
"test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless",
"test": "yarn build-or-skip && yarn test-node",
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
},
"dependencies": {
"@cosmjs/crypto": "^0.20.0",
"@cosmjs/encoding": "^0.20.0",
"@cosmjs/math": "^0.20.0",
"@cosmjs/sdk38": "^0.20.0",
"@cosmjs/utils": "^0.20.0",
"@iov/bcp": "^2.3.2",
"@iov/stream": "^2.3.2",
"bn.js": "^5.1.1",
"fast-deep-equal": "^3.1.1",
"readonly-date": "^1.0.0",
"xstream": "^11.11.0"
},
"devDependencies": {
"@iov/keycontrol": "^2.3.2",
"@types/bn.js": "^4.11.6"
}
}

View File

@ -1,37 +0,0 @@
import { fromBase64, fromHex } from "@cosmjs/encoding";
import { Algorithm, PubkeyBytes } from "@iov/bcp";
import { pubkeyToAddress } from "./address";
describe("address", () => {
describe("pubkeyToAddress", () => {
it("works for Secp256k1 compressed", () => {
const prefix = "cosmos";
const pubkey = {
algo: Algorithm.Secp256k1,
data: fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP") as PubkeyBytes,
};
expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r");
});
it("works for Secp256k1 uncompressed", () => {
const prefix = "cosmos";
const pubkey = {
algo: Algorithm.Secp256k1,
data: fromBase64(
"BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=",
) as PubkeyBytes,
};
expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6");
});
it("works for Ed25519", () => {
const prefix = "cosmos";
const pubkey = {
algo: Algorithm.Ed25519,
data: fromHex("12ee6f581fe55673a1e9e1382a0829e32075a0aa4763c968bc526e1852e78c95") as PubkeyBytes,
};
expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz");
});
});
});

View File

@ -1,24 +0,0 @@
import { Secp256k1 } from "@cosmjs/crypto";
import { toBase64 } from "@cosmjs/encoding";
import { PubKey, pubkeyToAddress as sdkPubkeyToAddress, pubkeyType } from "@cosmjs/sdk38";
import { Address, Algorithm, PubkeyBundle } from "@iov/bcp";
// See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography
export function pubkeyToAddress(pubkey: PubkeyBundle, prefix: string): Address {
let sdkKey: PubKey;
if (pubkey.algo === Algorithm.Secp256k1) {
sdkKey = {
type: pubkeyType.secp256k1,
value: toBase64(pubkey.data.length > 33 ? Secp256k1.compressPubkey(pubkey.data) : pubkey.data),
};
} else if (pubkey.algo === Algorithm.Ed25519) {
sdkKey = {
type: pubkeyType.ed25519,
value: toBase64(pubkey.data),
};
} else {
throw new Error(`Unsupported algorithm: ${pubkey.algo}`);
}
return sdkPubkeyToAddress(sdkKey, prefix) as Address;
}

View File

@ -1,79 +0,0 @@
import { ChainId } from "@iov/bcp";
import { Caip5 } from "./caip5";
describe("Caip5", () => {
describe("encode", () => {
it("works for direct format", () => {
expect(Caip5.encode("foo")).toEqual("cosmos:foo");
expect(Caip5.encode("aA1-")).toEqual("cosmos:aA1-");
expect(Caip5.encode("12345678901234567890123456789012345678901234567")).toEqual(
"cosmos:12345678901234567890123456789012345678901234567",
);
// Test vectors from CAIP-5
expect(Caip5.encode("cosmoshub-3")).toEqual("cosmos:cosmoshub-3");
expect(Caip5.encode("Binance-Chain-Tigris")).toEqual("cosmos:Binance-Chain-Tigris");
expect(Caip5.encode("x")).toEqual("cosmos:x");
expect(Caip5.encode("hash-")).toEqual("cosmos:hash-");
expect(Caip5.encode("hashed")).toEqual("cosmos:hashed");
});
it("works for hashed format", () => {
// Test vectors from CAIP-5
expect(Caip5.encode("hashed-")).toEqual("cosmos:hashed-c904589232422def");
expect(Caip5.encode("hashed-123")).toEqual("cosmos:hashed-99df5cd68192b33e");
expect(Caip5.encode("123456789012345678901234567890123456789012345678")).toEqual(
"cosmos:hashed-0204c92a0388779d",
);
expect(Caip5.encode(" ")).toEqual("cosmos:hashed-36a9e7f1c95b82ff");
expect(Caip5.encode("wonderland🧝")).toEqual("cosmos:hashed-843d2fc87f40eeb9");
});
it("throws for empty input", () => {
expect(() => Caip5.encode("")).toThrowError(/must not be empty/i);
});
});
describe("decode", () => {
it("works for valid format", () => {
expect(Caip5.decode("cosmos:x" as ChainId)).toEqual("x");
expect(Caip5.decode("cosmos:foo" as ChainId)).toEqual("foo");
expect(Caip5.decode("cosmos:aA1-" as ChainId)).toEqual("aA1-");
expect(Caip5.decode("cosmos:12345678901234567890123456789012345678901234567" as ChainId)).toEqual(
"12345678901234567890123456789012345678901234567",
);
});
it("throws for invalid format", () => {
// wrong namespace
expect(() => Caip5.decode(":foobar" as ChainId)).toThrowError(/not compatible with CAIP-5/i);
expect(() => Caip5.decode("xyz:foobar" as ChainId)).toThrowError(/not compatible with CAIP-5/i);
expect(() => Caip5.decode("cosmos-hash:foobar" as ChainId)).toThrowError(/not compatible with CAIP-5/i);
// reference too short
expect(() => Caip5.decode("cosmos:" as ChainId)).toThrowError(/not compatible with CAIP-5/i);
// reference too long
expect(() =>
Caip5.decode("cosmos:123456789012345678901234567890123456789012345678" as ChainId),
).toThrowError(/not compatible with CAIP-5/i);
// invalid chars
expect(() => Caip5.decode("cosmos:foo bar" as ChainId)).toThrowError(/not compatible with CAIP-5/i);
expect(() => Caip5.decode("cosmos:wonder🧝" as ChainId)).toThrowError(/not compatible with CAIP-5/i);
});
it("throws for hashed chain IDs", () => {
expect(() => Caip5.decode("cosmos:hashed-" as ChainId)).toThrowError(
/hashed chain IDs cannot be decoded/i,
);
expect(() => Caip5.decode("cosmos:hashed-abab" as ChainId)).toThrowError(
/hashed chain IDs cannot be decoded/i,
);
expect(() => Caip5.decode("cosmos:hashed-6abb36860ec76c5a" as ChainId)).toThrowError(
/hashed chain IDs cannot be decoded/i,
);
});
});
});

View File

@ -1,40 +0,0 @@
import { Sha256 } from "@cosmjs/crypto";
import { toHex, toUtf8 } from "@cosmjs/encoding";
import { ChainId } from "@iov/bcp";
const hashedPrefix = "hashed-";
/**
* Conversion between native chain IDs and CAIP-5 format
*
* @see https://github.com/ChainAgnostic/CAIPs/pull/9
*/
export class Caip5 {
/**
* @param native The `chain_id` field from Tendermint's genesis file
*/
public static encode(native: string): ChainId {
if (!native) throw new Error("Input must not be empty");
if (!native.match(/^[-a-zA-Z0-9]{1,47}$/) || native.startsWith(hashedPrefix)) {
const hash = toHex(new Sha256(toUtf8(native)).digest()).slice(0, 16);
return `cosmos:${hashedPrefix}${hash}` as ChainId;
} else {
return `cosmos:${native}` as ChainId;
}
}
public static decode(chainId: ChainId): string {
const match = chainId.match(/^cosmos:([-a-zA-Z0-9]{1,47})$/);
if (!match) {
throw new Error("Chain ID not compatible with CAIP-5");
}
const reference = match[1];
if (reference.startsWith(hashedPrefix)) {
throw new Error("Hashed chain IDs cannot be decoded");
}
return reference;
}
}

View File

@ -1,75 +0,0 @@
import { toUtf8 } from "@cosmjs/encoding";
import { PostableBytes, PrehashType } from "@iov/bcp";
import { CosmosCodec } from "./cosmoscodec";
import { chainId, nonce, sendTxJson, signedTxBin, signedTxEncodedJson, signedTxJson } from "./testdata.spec";
import { BankToken } from "./types";
const defaultPrefix = "cosmos";
const defaultBankTokens: readonly BankToken[] = [
{
fractionalDigits: 6,
ticker: "ATOM",
denom: "uatom",
},
];
describe("CosmosCodec", () => {
const codec = new CosmosCodec(defaultPrefix, defaultBankTokens);
describe("isValidAddress", () => {
it("accepts valid addresses", () => {
expect(codec.isValidAddress("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6")).toEqual(true);
});
it("rejects invalid addresses", () => {
// Bad size
expect(codec.isValidAddress("cosmos10q82zkzzmaku5lazhsvxv7hsg4ntpuhh8289f")).toEqual(false);
// Bad checksum
expect(codec.isValidAddress("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs7")).toEqual(false);
// Bad prefix
expect(codec.isValidAddress("cosmot10q82zkzzmaku5lazhsvxv7hsg4ntpuhd8j5266")).toEqual(false);
expect(codec.isValidAddress("cosmosvalcons10q82zkzzmaku5lazhsvxv7hsg4ntpuhdwadmss")).toEqual(false);
expect(codec.isValidAddress("cosmosvaloper17mggn4znyeyg25wd7498qxl7r2jhgue8u4qjcq")).toEqual(false);
});
});
describe("bytesToSign", () => {
it("works for SendTransaction via bank module", () => {
const expected = {
bytes: toUtf8(
'{"account_number":"0","chain_id":"cosmoshub-3","fee":{"amount":[{"amount":"2500","denom":"uatom"}],"gas":"100000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"35997500","denom":"uatom"}],"from_address":"cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq","to_address":"cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae"}}],"sequence":"99"}',
),
prehashType: PrehashType.Sha256,
};
expect(codec.bytesToSign(sendTxJson, nonce)).toEqual(expected);
});
});
describe("bytesToPost", () => {
it("works for SendTransaction via bank module", () => {
const encoded = codec.bytesToPost(signedTxJson);
expect(encoded).toEqual(signedTxEncodedJson);
});
});
describe("parseBytes", () => {
it("throws when trying to decode a transaction without a nonce", () => {
expect(() => codec.parseBytes(signedTxBin as PostableBytes, chainId)).toThrowError(
/nonce is required/i,
);
});
it("properly decodes transactions", () => {
const decoded = codec.parseBytes(signedTxEncodedJson as PostableBytes, chainId, nonce);
expect(decoded).toEqual(signedTxJson);
});
it("round trip works", () => {
const encoded = codec.bytesToPost(signedTxJson);
const decoded = codec.parseBytes(encoded, chainId, nonce);
expect(decoded).toEqual(signedTxJson);
});
});
});

View File

@ -1,104 +0,0 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Bech32, fromUtf8, toUtf8 } from "@cosmjs/encoding";
import { isStdTx, makeSignBytes, StdTx } from "@cosmjs/sdk38";
import {
Address,
ChainId,
Identity,
Nonce,
PostableBytes,
PrehashType,
SignableBytes,
SignedTransaction,
SigningJob,
TransactionId,
TxCodec,
UnsignedTransaction,
} from "@iov/bcp";
import { pubkeyToAddress } from "./address";
import { Caip5 } from "./caip5";
import { parseSignedTx } from "./decode";
import { buildSignedTx, buildUnsignedTx } from "./encode";
import { BankToken, nonceToAccountNumber, nonceToSequence } from "./types";
function marshalTx(tx: StdTx): Uint8Array {
const json = JSON.stringify(tx);
return toUtf8(json);
}
function unmarshalTx(data: Uint8Array): StdTx {
const decoded = JSON.parse(fromUtf8(data));
if (!isStdTx(decoded)) {
throw new Error("Must be json encoded StdTx");
}
return decoded;
}
export class CosmosCodec implements TxCodec {
private readonly addressPrefix: string;
private readonly bankTokens: readonly BankToken[];
public constructor(addressPrefix: string, bankTokens: readonly BankToken[]) {
this.addressPrefix = addressPrefix;
this.bankTokens = bankTokens;
}
public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob {
const built = buildUnsignedTx(unsigned, this.bankTokens);
const signBytes = makeSignBytes(
built.value.msg,
built.value.fee,
Caip5.decode(unsigned.chainId),
built.value.memo || "",
nonceToAccountNumber(nonce),
nonceToSequence(nonce),
);
return {
bytes: signBytes as SignableBytes,
prehashType: PrehashType.Sha256,
};
}
// PostableBytes are JSON-encoded StdTx
public bytesToPost(signed: SignedTransaction): PostableBytes {
// TODO: change this as well (return StdTx, not AminoTx)?
const built = buildSignedTx(signed, this.bankTokens);
return marshalTx(built.value) as PostableBytes;
}
// 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 {
if (nonce === undefined) {
throw new Error("Nonce is required");
}
const parsed = unmarshalTx(bytes);
return parseSignedTx(parsed, chainId, nonce, this.bankTokens);
}
public identityToAddress(identity: Identity): Address {
return pubkeyToAddress(identity.pubkey, this.addressPrefix);
}
public isValidAddress(address: string): boolean {
try {
const { prefix, data } = Bech32.decode(address);
if (prefix !== this.addressPrefix) {
return false;
}
return data.length === 20;
} catch {
return false;
}
}
}

View File

@ -1,893 +0,0 @@
import { Random, Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
import { Bech32, fromBase64 } from "@cosmjs/encoding";
import { decodeSignature } from "@cosmjs/sdk38";
import { assert } from "@cosmjs/utils";
import {
Account,
Address,
Algorithm,
Amount,
ChainId,
ConfirmedTransaction,
isBlockInfoPending,
isBlockInfoSucceeded,
isConfirmedTransaction,
isSendTransaction,
PubkeyBytes,
SendTransaction,
TokenTicker,
TransactionId,
TransactionState,
UnsignedTransaction,
} from "@iov/bcp";
import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol";
import BN from "bn.js";
import { CosmosConnection, TokenConfiguration } from "./cosmosconnection";
import { encodeFullSignature } from "./encode";
import * as testdata from "./testdata.spec";
function pendingWithoutWasmd(): void {
if (!process.env.WASMD_ENABLED) {
return pending("Set WASMD_ENABLED to enable Cosmos node-based tests");
}
}
const defaultAddressPrefix = "cosmos";
function makeRandomAddress(): Address {
return Bech32.encode(defaultAddressPrefix, Random.getBytes(20)) as Address;
}
const alice = {
address0: "cosmos14qemq0vw6y3gc3u3e0aty2e764u4gs5le3hada" as Address,
pubkey0: {
algo: Algorithm.Secp256k1,
data: fromBase64("A9cXhWb8ZpqCzkA8dQCPV29KdeRLV3rUYxrkHudLbQtS") as PubkeyBytes,
},
};
/**
* We use a different test account than the faucet, since BCP errors when transactions
* with multiple messages are found.
*/
const bob = {
mnemonic: "remain fragile remove stamp quiz bus country dress critic mammal office need",
path0: HdPaths.cosmosHub(0),
pubkey: {
algo: Algorithm.Secp256k1,
data: fromBase64("A0d/GxY+UALE+miWJP0qyq4/EayG1G6tsg24v+cbD6By") as PubkeyBytes,
},
address: "cosmos1lvrwcvrqlc5ktzp2c4t22xgkx29q3y83lktgzl" as Address,
};
describe("CosmosConnection", () => {
const cosm = "COSM" as TokenTicker;
const httpUrl = "http://localhost:1317";
const defaultChainId = "cosmos:testing" as ChainId;
const defaultEmptyAddress = "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address;
const defaultRecipient = "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" as Address;
const defaultAmount: Amount = {
quantity: "7744887",
fractionalDigits: 6,
tokenTicker: cosm,
};
const unusedAccount = {
pubkey: {
algo: Algorithm.Secp256k1,
data: fromBase64("ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ") as PubkeyBytes,
},
address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u" as Address,
};
// this is for wasmd blockchain
const defaultConfig: TokenConfiguration = {
bankTokens: [
{
fractionalDigits: 6,
name: "Fee Token",
ticker: "COSM",
denom: "ucosm",
},
{
fractionalDigits: 6,
name: "Staking Token",
ticker: "STAKE",
denom: "ustake",
},
],
};
const atomConfig: TokenConfiguration = {
bankTokens: [
{
fractionalDigits: 6,
name: "Atom",
ticker: "ATOM",
denom: "uatom",
},
],
};
describe("establish", () => {
it("can connect to Cosmos via http", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
expect(connection).toBeTruthy();
connection.disconnect();
});
});
describe("chainId", () => {
it("displays the chain ID", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
expect(connection.chainId).toEqual(defaultChainId);
connection.disconnect();
});
});
describe("height", () => {
it("displays the current height", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const height = await connection.height();
expect(height).toBeGreaterThan(0);
connection.disconnect();
});
});
describe("getToken", () => {
it("displays a given token", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const token = await connection.getToken("COSM" as TokenTicker);
expect(token).toEqual({
fractionalDigits: 6,
tokenName: "Fee Token",
tokenTicker: "COSM" as TokenTicker,
});
connection.disconnect();
});
it("resolves to undefined if the token is not supported", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const token = await connection.getToken("whatever" as TokenTicker);
expect(token).toBeUndefined();
connection.disconnect();
});
});
describe("getAllTokens", () => {
it("resolves to a list of all supported tokens", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const tokens = await connection.getAllTokens();
expect(tokens).toEqual([
{
fractionalDigits: 6,
tokenName: "Fee Token",
tokenTicker: "COSM" as TokenTicker,
},
{
fractionalDigits: 6,
tokenName: "Staking Token",
tokenTicker: "STAKE" as TokenTicker,
},
]);
connection.disconnect();
});
});
describe("identifier", () => {
it("calculates tx hash from PostableBytes", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, atomConfig);
const id = await connection.identifier(testdata.signedTxJson);
expect(id).toMatch(/^[0-9A-F]{64}$/);
expect(id).toEqual(testdata.txId);
});
});
describe("getAccount", () => {
it("gets an empty account by address", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const account = await connection.getAccount({ address: defaultEmptyAddress });
expect(account).toBeUndefined();
connection.disconnect();
});
it("gets an account by address", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const account = await connection.getAccount({ address: unusedAccount.address });
assert(account, "Account must be defined");
expect(account.address).toEqual(unusedAccount.address);
expect(account.pubkey).toBeUndefined();
expect(account.balance).toEqual([
{
tokenTicker: "COSM" as TokenTicker,
quantity: "1000000000",
fractionalDigits: 6,
},
{
tokenTicker: "STAKE" as TokenTicker,
quantity: "1000000000",
fractionalDigits: 6,
},
]);
connection.disconnect();
});
it("gets an account by pubkey", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const byAddress = await connection.getAccount({ address: unusedAccount.address });
const byPubkey = await connection.getAccount({ pubkey: unusedAccount.pubkey });
expect(byPubkey).toEqual(byAddress); // above we verified that by address works as expected
connection.disconnect();
});
it("has a pubkey when getting account with transactions", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const account = await connection.getAccount({ address: alice.address0 });
expect(account?.pubkey).toEqual(alice.pubkey0);
connection.disconnect();
});
});
describe("watchAccount", () => {
it("can watch account by address", (done) => {
pendingWithoutWasmd();
const recipient = makeRandomAddress();
const events = new Array<Account | undefined>();
(async () => {
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const subscription = connection.watchAccount({ address: recipient }).subscribe({
next: (event) => {
events.push(event);
if (events.length === 3) {
const [event1, event2, event3] = events;
expect(event1).toBeUndefined();
assert(event2, "Second event must not be undefined");
expect(event2.address).toEqual(recipient);
expect(event2.pubkey).toBeUndefined();
expect(event2.balance.length).toEqual(1);
expect(event2.balance[0].quantity).toEqual(defaultAmount.quantity);
expect(event2.balance[0].tokenTicker).toEqual(defaultAmount.tokenTicker);
assert(event3, "Third event must not be undefined");
expect(event3.address).toEqual(recipient);
expect(event3.pubkey).toBeUndefined();
expect(event3.balance.length).toEqual(1);
expect(event3.balance[0].quantity).toEqual(new BN(defaultAmount.quantity).imuln(2).toString());
expect(event3.balance[0].tokenTicker).toEqual(defaultAmount.tokenTicker);
subscription.unsubscribe();
connection.disconnect();
done();
}
},
complete: done.fail,
error: done.fail,
});
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, bob.path0);
const senderAddress = connection.codec.identityToAddress(sender);
for (const i of [0, 1]) {
const sendTx = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: senderAddress,
recipient: recipient,
amount: defaultAmount,
memo: `Trigger for new event ${i}`,
});
const nonce = await connection.getNonce({ address: senderAddress });
const signedTransaction = await profile.signTransaction(sender, sendTx, connection.codec, nonce);
const result = await connection.postTx(connection.codec.bytesToPost(signedTransaction));
await result.blockInfo.waitFor((info) => !isBlockInfoPending(info));
}
})().catch(done.fail);
});
});
describe("getTx", () => {
it("can get a recently posted bank send transaction", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
const senderIdentity = await profile.createIdentity(wallet.id, defaultChainId, bob.path0);
const senderAddress = connection.codec.identityToAddress(senderIdentity);
const unsigned = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
sender: senderAddress,
recipient: defaultRecipient,
memo: "My first payment",
amount: {
quantity: "75000",
fractionalDigits: 6,
tokenTicker: cosm,
},
});
const nonce = await connection.getNonce({ address: senderAddress });
const signed = await profile.signTransaction(senderIdentity, unsigned, connection.codec, nonce);
const postableBytes = connection.codec.bytesToPost(signed);
const response = await connection.postTx(postableBytes);
const { transactionId } = response;
await response.blockInfo.waitFor((info) => isBlockInfoSucceeded(info));
const getResponse = await connection.getTx(transactionId);
expect(getResponse.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(getResponse), "Expected transaction to succeed");
assert(getResponse.log, "Log must be available");
// we get a json response in the log for each msg, multiple events is good (transfer succeeded)
const [firstLog] = JSON.parse(getResponse.log);
expect(firstLog.events.length).toEqual(2);
const { transaction, signatures } = getResponse;
assert(isSendTransaction(transaction), "Expected send transaction");
expect(transaction).toEqual(unsigned);
expect(signatures.length).toEqual(1);
expect(signatures[0]).toEqual({
nonce: signed.signatures[0].nonce,
pubkey: {
algo: signed.signatures[0].pubkey.algo,
data: Secp256k1.compressPubkey(signed.signatures[0].pubkey.data),
},
signature: Secp256k1.trimRecoveryByte(signed.signatures[0].signature),
});
connection.disconnect();
});
it("can get an old transaction", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const results = await connection.searchTx({ sentFromOrTo: bob.address });
const firstSearchResult = results.find(() => true);
assert(firstSearchResult, "At least one transaction sent by the faucet must be available.");
assert(isConfirmedTransaction(firstSearchResult), "Transaction must be confirmed.");
const {
transaction: searchedTransaction,
transactionId: searchedTransactionId,
height: searchedHeight,
} = firstSearchResult;
const getResponse = await connection.getTx(searchedTransactionId);
assert(isConfirmedTransaction(getResponse), "Expected transaction to succeed");
const { height, transactionId, log, transaction, signatures } = getResponse;
// Test properties of getTx result: height, transactionId, log, transaction
expect(height).toEqual(searchedHeight);
expect(transactionId).toEqual(searchedTransactionId);
assert(log, "Log must be available");
const [firstLog] = JSON.parse(log);
expect(firstLog.events.length).toEqual(2);
expect(transaction).toEqual(searchedTransaction);
// Signature test ensures the nonce is correct
expect(signatures.length).toEqual(1);
const signBytes = connection.codec.bytesToSign(getResponse.transaction, signatures[0].nonce).bytes;
const { pubkey, signature } = decodeSignature(encodeFullSignature(signatures[0]));
const prehashed = new Sha256(signBytes).digest();
const valid = await Secp256k1.verifySignature(
Secp256k1Signature.fromFixedLength(signature),
prehashed,
pubkey,
);
expect(valid).toEqual(true);
connection.disconnect();
});
it("throws for non-existent transaction", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000" as TransactionId;
await connection.getTx(nonExistentId).then(
() => fail("this must not succeed"),
(error) => expect(error).toMatch(/transaction does not exist/i),
);
connection.disconnect();
});
});
describe("searchTx", () => {
it("can post and search for a transaction", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, bob.path0);
const senderAddress = connection.codec.identityToAddress(sender);
const unsigned = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
sender: senderAddress,
recipient: defaultRecipient,
memo: "My first payment",
amount: {
quantity: "75000",
fractionalDigits: 6,
tokenTicker: cosm,
},
});
const nonce = await connection.getNonce({ address: senderAddress });
const signed = await profile.signTransaction(sender, unsigned, connection.codec, nonce);
const postableBytes = connection.codec.bytesToPost(signed);
const response = await connection.postTx(postableBytes);
const { transactionId } = response;
const blockInfo = await response.blockInfo.waitFor((info) => !isBlockInfoPending(info));
expect(blockInfo.state).toEqual(TransactionState.Succeeded);
// search by id
const byIdResults = await connection.searchTx({ id: transactionId });
expect(byIdResults.length).toEqual(1);
const byIdResult = byIdResults[0];
expect(byIdResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(byIdResult), "Expected transaction to succeed");
assert(byIdResult.log, "Log must be available");
const [firstByIdlog] = JSON.parse(byIdResult.log);
expect(firstByIdlog.events.length).toEqual(2);
expect(firstByIdlog.events[0].type).toEqual("message");
expect(firstByIdlog.events[1].type).toEqual("transfer");
const byIdTransaction = byIdResult.transaction;
assert(isSendTransaction(byIdTransaction), "Expected send transaction");
expect(byIdTransaction).toEqual(unsigned);
// search by sender address
const bySenderResults = await connection.searchTx({ sentFromOrTo: senderAddress });
expect(bySenderResults).toBeTruthy();
expect(bySenderResults.length).toBeGreaterThanOrEqual(1);
const bySenderResult = bySenderResults[bySenderResults.length - 1];
expect(bySenderResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(bySenderResult), "Expected transaction to succeed");
assert(bySenderResult.log, "Log must be available");
const [firstBySenderLog] = JSON.parse(bySenderResult.log);
expect(firstBySenderLog.events.length).toEqual(2);
expect(firstBySenderLog.events[0].type).toEqual("message");
expect(firstBySenderLog.events[1].type).toEqual("transfer");
const bySenderTransaction = bySenderResult.transaction;
assert(isSendTransaction(bySenderTransaction), "Expected send transaction");
expect(bySenderTransaction).toEqual(unsigned);
// search by recipient address
const byRecipientResults = await connection.searchTx({ sentFromOrTo: defaultRecipient });
expect(byRecipientResults.length).toBeGreaterThanOrEqual(1);
const byRecipientResult = byRecipientResults[byRecipientResults.length - 1];
expect(byRecipientResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(byRecipientResult), "Expected transaction to succeed");
assert(byRecipientResult.log, "Log must be available");
const [firstByRecipientLog] = JSON.parse(bySenderResult.log);
expect(firstByRecipientLog.events.length).toEqual(2);
expect(firstByRecipientLog.events[0].type).toEqual("message");
expect(firstByRecipientLog.events[1].type).toEqual("transfer");
const byRecipeintTransaction = byRecipientResult.transaction;
assert(isSendTransaction(byRecipeintTransaction), "Expected send transaction");
expect(byRecipeintTransaction).toEqual(unsigned);
// search by height
const heightResults = await connection.searchTx({ height: byIdResult.height });
expect(heightResults.length).toBeGreaterThanOrEqual(1);
expect(heightResults).toContain(
jasmine.objectContaining({
transactionId: transactionId,
transaction: unsigned,
}),
);
connection.disconnect();
});
it("can search by minHeight and maxHeight", async () => {
pendingWithoutWasmd();
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, bob.path0);
const senderAddress = connection.codec.identityToAddress(sender);
const recipient = makeRandomAddress();
const unsigned = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
sender: senderAddress,
recipient: recipient,
memo: "My first payment",
amount: {
quantity: "75000",
fractionalDigits: 6,
tokenTicker: cosm,
},
});
const nonce = await connection.getNonce({ address: senderAddress });
const signed = await profile.signTransaction(sender, unsigned, connection.codec, nonce);
const postableBytes = connection.codec.bytesToPost(signed);
const response = await connection.postTx(postableBytes);
const { transactionId } = response;
const blockInfo = await response.blockInfo.waitFor((info) => !isBlockInfoPending(info));
assert(isBlockInfoSucceeded(blockInfo));
const { height } = blockInfo;
// search by ID
{
const results = await connection.searchTx({ id: transactionId });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ id: transactionId, minHeight: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ id: transactionId, minHeight: height - 2 });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ id: transactionId, maxHeight: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ id: transactionId, maxHeight: height + 2 });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({
id: transactionId,
minHeight: height,
maxHeight: height,
});
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ id: transactionId, minHeight: height + 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ id: transactionId, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
id: transactionId,
minHeight: height + 1,
maxHeight: Number.MAX_SAFE_INTEGER,
});
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ id: transactionId, minHeight: 0, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
// search by recipient
{
const results = await connection.searchTx({ sentFromOrTo: recipient });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, minHeight: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, minHeight: height - 2 });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, maxHeight: height });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, maxHeight: height + 2 });
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, minHeight: height + 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
sentFromOrTo: recipient,
minHeight: height,
maxHeight: height,
});
expect(results.length).toEqual(1);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, minHeight: height + 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ sentFromOrTo: recipient, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
sentFromOrTo: recipient,
minHeight: height + 1,
maxHeight: Number.MAX_SAFE_INTEGER,
});
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
sentFromOrTo: recipient,
minHeight: 0,
maxHeight: height - 1,
});
expect(results.length).toEqual(0);
}
// search by height
{
const results = await connection.searchTx({ height: height });
expect(results.length).toBeGreaterThanOrEqual(1);
}
{
const results = await connection.searchTx({ height: height, minHeight: height });
expect(results.length).toBeGreaterThanOrEqual(1);
}
{
const results = await connection.searchTx({ height: height, minHeight: height - 2 });
expect(results.length).toBeGreaterThanOrEqual(1);
}
{
const results = await connection.searchTx({ height: height, maxHeight: height });
expect(results.length).toBeGreaterThanOrEqual(1);
}
{
const results = await connection.searchTx({ height: height, maxHeight: height + 2 });
expect(results.length).toBeGreaterThanOrEqual(1);
}
{
const results = await connection.searchTx({ height: height, minHeight: height + 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ height: height, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
height: height,
minHeight: height,
maxHeight: height,
});
expect(results.length).toBeGreaterThanOrEqual(1);
}
{
const results = await connection.searchTx({ height: height, minHeight: height + 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({ height: height, maxHeight: height - 1 });
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
height: height,
minHeight: height + 1,
maxHeight: Number.MAX_SAFE_INTEGER,
});
expect(results.length).toEqual(0);
}
{
const results = await connection.searchTx({
height: height,
minHeight: 0,
maxHeight: height - 1,
});
expect(results.length).toEqual(0);
}
connection.disconnect();
});
});
describe("liveTx", () => {
it("can listen to transactions by recipient address (transactions in history and updates)", (done) => {
pendingWithoutWasmd();
(async () => {
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, bob.path0);
// send transactions
const recipientAddress = makeRandomAddress();
const sendA = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: connection.codec.identityToAddress(sender),
recipient: recipientAddress,
amount: defaultAmount,
memo: `liveTx() test A ${Math.random()}`,
});
const sendB = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: connection.codec.identityToAddress(sender),
recipient: recipientAddress,
amount: defaultAmount,
memo: `liveTx() test B ${Math.random()}`,
});
const sendC = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: connection.codec.identityToAddress(sender),
recipient: recipientAddress,
amount: defaultAmount,
memo: `liveTx() test C ${Math.random()}`,
});
const [nonceA, nonceB, nonceC] = await connection.getNonces({ pubkey: sender.pubkey }, 3);
const signedA = await profile.signTransaction(sender, sendA, connection.codec, nonceA);
const signedB = await profile.signTransaction(sender, sendB, connection.codec, nonceB);
const signedC = await profile.signTransaction(sender, sendC, connection.codec, nonceC);
const bytesToPostA = connection.codec.bytesToPost(signedA);
const bytesToPostB = connection.codec.bytesToPost(signedB);
const bytesToPostC = connection.codec.bytesToPost(signedC);
// Post A and B. Unfortunately the REST server API does not support sending them in parallel because the sequence check fails.
const postResultA = await connection.postTx(bytesToPostA);
await postResultA.blockInfo.waitFor((info) => !isBlockInfoPending(info));
const postResultB = await connection.postTx(bytesToPostB);
await postResultB.blockInfo.waitFor((info) => !isBlockInfoPending(info));
// setup listener after A and B are in block
const events = new Array<ConfirmedTransaction<UnsignedTransaction>>();
const subscription = connection.liveTx({ sentFromOrTo: recipientAddress }).subscribe({
next: (event) => {
assert(isConfirmedTransaction(event), "Confirmed transaction expected");
events.push(event);
assert(isSendTransaction(event.transaction), "Unexpected transaction type");
expect(event.transaction.recipient).toEqual(recipientAddress);
if (events.length === 3) {
expect(events[1].height).toEqual(events[0].height + 1);
expect(events[2].height).toBeGreaterThan(events[1].height);
subscription.unsubscribe();
connection.disconnect();
done();
}
},
});
// Post C
await connection.postTx(bytesToPostC);
})().catch(done.fail);
});
it("can listen to transactions by ID (transaction in history)", (done) => {
pendingWithoutWasmd();
(async () => {
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, bob.path0);
const recipientAddress = makeRandomAddress();
const send = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: connection.codec.identityToAddress(sender),
recipient: recipientAddress,
amount: defaultAmount,
memo: `liveTx() test ${Math.random()}`,
});
const nonce = await connection.getNonce({ pubkey: sender.pubkey });
const signed = await profile.signTransaction(sender, send, connection.codec, nonce);
const bytesToPost = connection.codec.bytesToPost(signed);
const postResult = await connection.postTx(bytesToPost);
const transactionId = postResult.transactionId;
// Wait for a block
await postResult.blockInfo.waitFor((info) => !isBlockInfoPending(info));
// setup listener after transaction is in block
const events = new Array<ConfirmedTransaction<UnsignedTransaction>>();
const subscription = connection.liveTx({ id: transactionId }).subscribe({
next: (event) => {
assert(isConfirmedTransaction(event), "Confirmed transaction expected");
events.push(event);
assert(isSendTransaction(event.transaction), "Unexpected transaction type");
expect(event.transaction.recipient).toEqual(recipientAddress);
expect(event.transactionId).toEqual(transactionId);
subscription.unsubscribe();
connection.disconnect();
done();
},
});
})().catch(done.fail);
});
it("can listen to transactions by ID (transaction in updates)", (done) => {
pendingWithoutWasmd();
(async () => {
const connection = await CosmosConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, bob.path0);
// send transactions
const recipientAddress = makeRandomAddress();
const send = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
senderPubkey: sender.pubkey,
sender: connection.codec.identityToAddress(sender),
recipient: recipientAddress,
amount: defaultAmount,
memo: `liveTx() test ${Math.random()}`,
});
const nonce = await connection.getNonce({ pubkey: sender.pubkey });
const signed = await profile.signTransaction(sender, send, connection.codec, nonce);
const bytesToPost = connection.codec.bytesToPost(signed);
const postResult = await connection.postTx(bytesToPost);
const transactionId = postResult.transactionId;
// setup listener before transaction is in block
const events = new Array<ConfirmedTransaction<UnsignedTransaction>>();
const subscription = connection.liveTx({ id: transactionId }).subscribe({
next: (event) => {
assert(isConfirmedTransaction(event), "Confirmed transaction expected");
events.push(event);
assert(isSendTransaction(event.transaction), "Unexpected transaction type");
expect(event.transaction.recipient).toEqual(recipientAddress);
expect(event.transactionId).toEqual(transactionId);
subscription.unsubscribe();
connection.disconnect();
done();
},
});
})().catch(done.fail);
});
});
});

View File

@ -1,489 +0,0 @@
import { fromUtf8 } from "@cosmjs/encoding";
import { Uint53 } from "@cosmjs/math";
import {
CosmosClient,
findSequenceForSignedTx,
IndexedTx,
isMsgSend,
isStdTx,
SearchTxFilter,
} from "@cosmjs/sdk38";
import {
Account,
AccountQuery,
AddressQuery,
BlockchainConnection,
BlockHeader,
BlockId,
BlockInfo,
ChainId,
ConfirmedAndSignedTransaction,
ConfirmedTransaction,
FailedTransaction,
Fee,
isConfirmedTransaction,
isPubkeyQuery,
isSendTransaction,
Nonce,
PostableBytes,
PostTxResponse,
PubkeyQuery,
SignedTransaction,
Token,
TokenTicker,
TransactionId,
TransactionQuery,
TransactionState,
TxCodec,
UnsignedTransaction,
} from "@iov/bcp";
import { concat, DefaultValueProducer, ValueAndUpdates } from "@iov/stream";
import equal from "fast-deep-equal";
import { ReadonlyDate } from "readonly-date";
import { Producer, Stream } from "xstream";
import { pubkeyToAddress } from "./address";
import { Caip5 } from "./caip5";
import { CosmosCodec } from "./cosmoscodec";
import { decodeAmount, decodePubkey, parseTxsResponseSigned, parseTxsResponseUnsigned } from "./decode";
import { buildSignedTx } from "./encode";
import { accountToNonce, BankToken } from "./types";
// poll every 0.5 seconds (block time 1s)
const defaultPollInterval = 500;
export interface TokenConfiguration {
/** Supported tokens of the Cosmos SDK bank module */
readonly bankTokens: ReadonlyArray<BankToken & { readonly name: string }>;
}
function isDefined<X>(value: X | undefined): value is X {
return value !== undefined;
}
function deduplicate<T>(input: ReadonlyArray<T>, comparator: (a: T, b: T) => number): Array<T> {
const out = new Array<T>();
for (const element of input) {
if (out.find((o) => comparator(o, element) === 0) === undefined) {
out.push(element);
}
}
return out;
}
/** Compares transaxtion by height. If the height is equal, compare by hash to ensure deterministic order */
function compareByHeightAndHash(a: IndexedTx, b: IndexedTx): number {
if (a.height === b.height) {
return a.hash.localeCompare(b.hash);
} else {
return a.height - b.height;
}
}
/** Account and undefined are valid events. The third option means no event fired yet */
type LastWatchAccountEvent = Account | undefined | "no_event_fired_yet";
export class CosmosConnection implements BlockchainConnection {
// we must know prefix and tokens a priori to understand the chain
public static async establish(
url: string,
addressPrefix: string,
tokens: TokenConfiguration,
): Promise<CosmosConnection> {
const cosmosClient = new CosmosClient(url);
const chainData = await this.initialize(cosmosClient);
return new CosmosConnection(cosmosClient, chainData, addressPrefix, tokens);
}
private static async initialize(cosmosClient: CosmosClient): Promise<ChainId> {
const rawChainId = await cosmosClient.getChainId();
return Caip5.encode(rawChainId);
}
public readonly chainId: ChainId;
public readonly codec: TxCodec;
private readonly cosmosClient: CosmosClient;
private readonly addressPrefix: string;
private readonly bankTokens: readonly BankToken[];
// these are derived from arguments (cached for use in multiple functions)
private readonly feeToken: BankToken | undefined;
private readonly supportedTokens: readonly Token[];
private constructor(
cosmosClient: CosmosClient,
chainId: ChainId,
addressPrefix: string,
tokens: TokenConfiguration,
) {
this.cosmosClient = cosmosClient;
this.chainId = chainId;
this.codec = new CosmosCodec(addressPrefix, tokens.bankTokens);
this.addressPrefix = addressPrefix;
this.bankTokens = tokens.bankTokens;
this.feeToken = this.bankTokens.find(() => true);
this.supportedTokens = tokens.bankTokens
.map((info) => ({
tokenTicker: info.ticker as TokenTicker,
tokenName: info.name,
fractionalDigits: info.fractionalDigits,
}))
.sort((a, b) => a.tokenTicker.localeCompare(b.tokenTicker));
}
public disconnect(): void {
return;
}
public async height(): Promise<number> {
return this.cosmosClient.getHeight();
}
public async getToken(searchTicker: TokenTicker): Promise<Token | undefined> {
return (await this.getAllTokens()).find(({ tokenTicker }) => tokenTicker === searchTicker);
}
public async getAllTokens(): Promise<readonly Token[]> {
return this.supportedTokens;
}
/**
* This is a replacement for the unimplemented CosmosCodec.identifier. Here we have more
* context and network available, which we might use to implement the API in an async way.
*/
public async identifier(signed: SignedTransaction): Promise<TransactionId> {
const tx = buildSignedTx(signed, this.bankTokens);
const id = await this.cosmosClient.getIdentifier(tx);
return id as TransactionId;
}
public async getAccount(query: AccountQuery): Promise<Account | undefined> {
const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.addressPrefix) : query.address;
const bankAccount = await this.cosmosClient.getAccount(address);
const supportedBankCoins = (bankAccount?.balance || []).filter(({ denom }) =>
this.bankTokens.find((token) => token.denom === denom),
);
if (!bankAccount) {
return undefined;
} else {
const balance = [
...supportedBankCoins.map((coin) => decodeAmount(this.bankTokens, coin)),
].sort((a, b) => a.tokenTicker.localeCompare(b.tokenTicker));
const pubkey = bankAccount?.pubkey ? decodePubkey(bankAccount.pubkey) : undefined;
return {
address: address,
balance: balance,
pubkey: pubkey,
};
}
}
public watchAccount(query: AccountQuery): Stream<Account | undefined> {
let lastEvent: LastWatchAccountEvent = "no_event_fired_yet";
let pollInternal: NodeJS.Timeout | undefined;
const producer: Producer<Account | undefined> = {
start: async (listener) => {
const poll = async (): Promise<void> => {
try {
const event = await this.getAccount(query);
if (!equal(event, lastEvent)) {
listener.next(event);
lastEvent = event;
}
} catch (error) {
listener.error(error);
}
};
pollInternal = setInterval(poll, defaultPollInterval);
await poll();
},
stop: () => {
if (pollInternal) {
clearInterval(pollInternal);
pollInternal = undefined;
}
},
};
return Stream.create(producer);
}
public async getNonce(query: AddressQuery | PubkeyQuery): Promise<Nonce> {
const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.addressPrefix) : query.address;
const { accountNumber, sequence } = await this.cosmosClient.getNonce(address);
return accountToNonce(accountNumber, sequence);
}
public async getNonces(query: AddressQuery | PubkeyQuery, count: number): Promise<readonly Nonce[]> {
const checkedCount = new Uint53(count).toNumber();
if (checkedCount === 0) {
return [];
}
const firstNonce = await this.getNonce(query);
// Note: this still works with the encoded format (see types/accountToNonce) as least-significant digits are sequence
return [...new Array(checkedCount)].map((_, i) => (firstNonce + i) as Nonce);
}
public async getBlockHeader(height: number): Promise<BlockHeader> {
const { id, header, txs } = await this.cosmosClient.getBlock(height);
return {
id: id as BlockId,
height: header.height,
time: new ReadonlyDate(header.time),
transactionCount: txs.length,
};
}
public watchBlockHeaders(): Stream<BlockHeader> {
throw new Error("not implemented");
}
public async getTx(
id: TransactionId,
): Promise<ConfirmedAndSignedTransaction<UnsignedTransaction> | FailedTransaction> {
const results = await this.cosmosClient.searchTx({ id: id });
switch (results.length) {
case 0:
throw new Error("Transaction does not exist");
case 1:
return this.parseAndPopulateTxResponseSigned(results[0]);
default:
throw new Error("Got unexpected amount of search results");
}
}
public async postTx(tx: PostableBytes): Promise<PostTxResponse> {
const txAsJson = JSON.parse(fromUtf8(tx));
if (!isStdTx(txAsJson)) throw new Error("Postable bytes must contain a JSON encoded StdTx");
const { transactionHash, rawLog } = await this.cosmosClient.postTx(txAsJson);
const transactionId = transactionHash as TransactionId;
const firstEvent: BlockInfo = { state: TransactionState.Pending };
let blockInfoInterval: NodeJS.Timeout;
let lastEventSent: BlockInfo;
const producer = new DefaultValueProducer<BlockInfo>(firstEvent, {
onStarted: () => {
blockInfoInterval = setInterval(async () => {
const searchResult = (await this.searchTx({ id: transactionId })).find(() => true);
if (searchResult) {
const event: BlockInfo = isConfirmedTransaction(searchResult)
? {
state: TransactionState.Succeeded,
height: searchResult.height,
confirmations: searchResult.confirmations,
}
: {
state: TransactionState.Failed,
height: searchResult.height,
code: searchResult.code,
message: searchResult.message,
};
if (!equal(event, lastEventSent)) {
producer.update(event);
lastEventSent = event;
}
}
}, defaultPollInterval);
},
onStop: () => clearInterval(blockInfoInterval),
});
return {
blockInfo: new ValueAndUpdates<BlockInfo>(producer),
transactionId: transactionId,
log: rawLog,
};
}
public async searchTx({
height,
id,
maxHeight,
minHeight,
sentFromOrTo,
signedBy,
tags,
}: TransactionQuery): Promise<readonly (ConfirmedTransaction<UnsignedTransaction> | FailedTransaction)[]> {
if ([signedBy, tags].some(isDefined)) {
throw new Error("Transaction query by signedBy or tags not yet supported");
}
if ([id, height, sentFromOrTo].filter(isDefined).length !== 1) {
throw new Error(
"Transaction query by id, height and sentFromOrTo is mutually exclusive. Exactly one must be set.",
);
}
const filter: SearchTxFilter = { minHeight: minHeight, maxHeight: maxHeight };
let txs: readonly IndexedTx[];
if (id) {
txs = await this.cosmosClient.searchTx({ id: id }, filter);
} else if (height) {
txs = await this.cosmosClient.searchTx({ height: height }, filter);
} else if (sentFromOrTo) {
const pendingRequests = new Array<Promise<readonly IndexedTx[]>>();
pendingRequests.push(this.cosmosClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter));
const responses = await Promise.all(pendingRequests);
const allResults = responses.reduce((accumulator, results) => accumulator.concat(results), []);
txs = deduplicate(allResults, (a, b) => a.hash.localeCompare(b.hash)).sort(compareByHeightAndHash);
} else {
throw new Error("Unsupported query");
}
return txs.map((tx) => this.parseAndPopulateTxResponseUnsigned(tx));
}
public listenTx(
_query: TransactionQuery,
): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction> {
throw new Error("not implemented");
}
public liveTx(
query: TransactionQuery,
): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction> {
if ([query.height, query.signedBy, query.tags].some(isDefined)) {
throw new Error("Transaction query by height, signedBy or tags not yet supported");
}
if (query.id) {
if (query.minHeight || query.maxHeight) {
throw new Error("Query by minHeight/maxHeight not supported together with ID");
}
// concat never() because we want non-completing streams consistently
return concat(this.waitForTransaction(query.id), Stream.never());
} else if (query.sentFromOrTo) {
let pollInternal: NodeJS.Timeout | undefined;
const producer: Producer<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction> = {
start: async (listener) => {
let minHeight = query.minHeight || 0;
const maxHeight = query.maxHeight || Number.MAX_SAFE_INTEGER;
const poll = async (): Promise<void> => {
const result = await this.searchTx({
sentFromOrTo: query.sentFromOrTo,
minHeight: minHeight,
maxHeight: maxHeight,
});
for (const item of result) {
listener.next(item);
if (item.height >= minHeight) {
// we assume we got all matching transactions from block `item.height` now
minHeight = item.height + 1;
}
}
};
await poll();
pollInternal = setInterval(poll, defaultPollInterval);
},
stop: () => {
if (pollInternal) {
clearInterval(pollInternal);
pollInternal = undefined;
}
},
};
return Stream.create(producer);
} else {
throw new Error("Unsupported query.");
}
}
public async getFeeQuote(tx: UnsignedTransaction): Promise<Fee> {
if (!isSendTransaction(tx)) {
throw new Error("Received transaction of unsupported kind.");
}
if (!this.feeToken) throw new Error("This connection has no fee token configured.");
return {
tokens: {
fractionalDigits: this.feeToken.fractionalDigits,
quantity: "5000",
tokenTicker: this.feeToken.ticker as TokenTicker,
},
gasLimit: "200000",
};
}
public async withDefaultFee<T extends UnsignedTransaction>(tx: T): Promise<T> {
return {
...tx,
fee: await this.getFeeQuote(tx),
};
}
private parseAndPopulateTxResponseUnsigned(
response: IndexedTx,
): ConfirmedTransaction<UnsignedTransaction> | FailedTransaction {
return parseTxsResponseUnsigned(this.chainId, response.height, response, this.bankTokens);
}
private async parseAndPopulateTxResponseSigned(
response: IndexedTx,
): Promise<ConfirmedAndSignedTransaction<UnsignedTransaction> | FailedTransaction> {
const firstMsg = response.tx.value.msg.find(() => true);
if (!firstMsg) throw new Error("Got transaction without a first message. What is going on here?");
let senderAddress: string;
if (isMsgSend(firstMsg)) {
senderAddress = firstMsg.value.from_address;
} else {
throw new Error(`Got unsupported type of message: ${firstMsg.type}`);
}
const { accountNumber, sequence: currentSequence } = await this.cosmosClient.getNonce(senderAddress);
const sequenceForTx = await findSequenceForSignedTx(
response.tx,
Caip5.decode(this.chainId),
accountNumber,
currentSequence,
);
if (sequenceForTx === undefined) throw new Error("Cound not find matching sequence for this transaction");
const nonce = accountToNonce(accountNumber, sequenceForTx);
return parseTxsResponseSigned(this.chainId, response.height, nonce, response, this.bankTokens);
}
private waitForTransaction(
id: TransactionId,
): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction> {
let pollInternal: NodeJS.Timeout | undefined;
const producer: Producer<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction> = {
start: (listener) => {
setInterval(async () => {
try {
const results = await this.searchTx({ id: id });
switch (results.length) {
case 0:
// okay, we'll try again
break;
case 1:
listener.next(results[0]);
listener.complete();
break;
default:
throw new Error(`Got unexpected number of search results: ${results.length}`);
}
} catch (error) {
if (pollInternal) {
clearTimeout(pollInternal);
pollInternal = undefined;
}
listener.error(error);
}
}, defaultPollInterval);
},
stop: () => {
if (pollInternal) {
clearTimeout(pollInternal);
pollInternal = undefined;
}
},
};
return Stream.create(producer);
}
}

View File

@ -1,21 +0,0 @@
import { ChainConnector, ChainId } from "@iov/bcp";
import { CosmosCodec } from "./cosmoscodec";
import { CosmosConnection, TokenConfiguration } from "./cosmosconnection";
/**
* A helper to connect to a cosmos-based chain at a given url
*/
export function createCosmosConnector(
url: string,
addressPrefix: string,
tokenConfig: TokenConfiguration,
expectedChainId?: ChainId,
): ChainConnector<CosmosConnection> {
const codec = new CosmosCodec(addressPrefix, tokenConfig.bankTokens);
return {
establishConnection: async () => CosmosConnection.establish(url, addressPrefix, tokenConfig),
codec: codec,
expectedChainId: expectedChainId,
};
}

View File

@ -1,401 +0,0 @@
/* eslint-disable @typescript-eslint/camelcase */
import { fromBase64, fromHex } from "@cosmjs/encoding";
import { Coin, IndexedTx, Msg, PubKey, StdSignature } from "@cosmjs/sdk38";
import { Address, Algorithm, SendTransaction, TokenTicker } from "@iov/bcp";
import {
decodeAmount,
decodeFullSignature,
decodePubkey,
decodeSignature,
parseFee,
parseMsg,
parseSignedTx,
parseTxsResponseSigned,
parseTxsResponseUnsigned,
parseUnsignedTx,
} from "./decode";
import * as testdata from "./testdata.spec";
import cosmoshub from "./testdata/cosmoshub.json";
import { BankToken } from "./types";
describe("decode", () => {
const defaultPubkey = {
algo: Algorithm.Secp256k1,
data: fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"),
};
const defaultSignature = fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
);
const defaultFullSignature = {
nonce: testdata.nonce,
pubkey: defaultPubkey,
signature: defaultSignature,
};
const defaultAmount = {
fractionalDigits: 6,
quantity: "11657995",
tokenTicker: "ATOM" as TokenTicker,
};
const defaultMemo = "Best greetings";
const defaultSendTransaction: SendTransaction = {
kind: "bcp/send",
chainId: testdata.chainId,
sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address,
recipient: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e" as Address,
amount: defaultAmount,
memo: defaultMemo,
};
const defaultFee = {
tokens: {
fractionalDigits: 6,
quantity: "5000",
tokenTicker: "ATOM" as TokenTicker,
},
gasLimit: "200000",
};
const defaultTokens: readonly BankToken[] = [
{
fractionalDigits: 6,
ticker: "ATOM",
denom: "uatom",
},
];
describe("decodePubkey", () => {
it("works for secp256k1", () => {
const pubkey: PubKey = {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
};
expect(decodePubkey(pubkey)).toEqual(defaultPubkey);
});
it("works for ed25519", () => {
const pubkey: PubKey = {
type: "tendermint/PubKeyEd25519",
value: "s69CnMgLTpuRyEfecjws3mWssBrOICUx8C2O1DkKSto=",
};
expect(decodePubkey(pubkey)).toEqual({
algo: Algorithm.Ed25519,
data: fromHex("b3af429cc80b4e9b91c847de723c2cde65acb01ace202531f02d8ed4390a4ada"),
});
});
it("throws for unsupported types", () => {
// https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12
const pubkey: PubKey = {
type: "tendermint/PubKeySr25519",
value: "N4FJNPE5r/Twz55kO1QEIxyaGF5/HTXH6WgLQJWsy1o=",
};
expect(() => decodePubkey(pubkey)).toThrowError(/unsupported pubkey type/i);
});
});
describe("decodeSignature", () => {
it("works", () => {
const signature =
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==";
expect(decodeSignature(signature)).toEqual(defaultSignature);
});
});
describe("decodeFullSignature", () => {
it("works", () => {
const fullSignature: StdSignature = {
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
};
expect(decodeFullSignature(fullSignature, testdata.nonce)).toEqual(defaultFullSignature);
});
});
describe("decodeAmount", () => {
it("works", () => {
const amount: Coin = {
denom: "uatom",
amount: "11657995",
};
expect(decodeAmount(defaultTokens, amount)).toEqual(defaultAmount);
});
});
describe("parseMsg", () => {
it("works for bank send transaction", () => {
const msg: Msg = {
type: "cosmos-sdk/MsgSend",
value: {
from_address: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r",
to_address: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e",
amount: [
{
denom: "uatom",
amount: "11657995",
},
],
},
};
expect(parseMsg(msg, defaultMemo, testdata.chainId, defaultTokens)).toEqual(defaultSendTransaction);
});
});
describe("parseFee", () => {
it("works", () => {
const fee = {
amount: [
{
denom: "uatom",
amount: "5000",
},
],
gas: "200000",
};
expect(parseFee(fee, defaultTokens)).toEqual(defaultFee);
});
});
describe("parseUnsignedTx", () => {
it("works for bank send transaction", () => {
expect(parseUnsignedTx(cosmoshub.tx.value, testdata.chainId, defaultTokens)).toEqual(
testdata.sendTxJson,
);
});
});
describe("parseSignedTx", () => {
it("works", () => {
expect(parseSignedTx(cosmoshub.tx.value, testdata.chainId, testdata.nonce, defaultTokens)).toEqual(
testdata.signedTxJson,
);
});
});
describe("parseTxsResponseUnsigned", () => {
it("works", () => {
const currentHeight = 2923;
const txsResponse: IndexedTx = {
height: 2823,
hash: testdata.txId,
code: 0,
rawLog: '[{"msg_index":0,"success":true,"log":""}]',
logs: [],
tx: cosmoshub.tx,
timestamp: "2020-02-14T11:35:41Z",
};
const expected = {
transaction: testdata.sendTxJson,
height: 2823,
confirmations: 101,
transactionId: testdata.txId,
log: '[{"msg_index":0,"success":true,"log":""}]',
};
expect(parseTxsResponseUnsigned(testdata.chainId, currentHeight, txsResponse, defaultTokens)).toEqual(
expected,
);
});
});
describe("parseTxsResponseSigned", () => {
it("works", () => {
const currentHeight = 2923;
const txsResponse: IndexedTx = {
height: 2823,
hash: testdata.txId,
code: 0,
rawLog: '[{"msg_index":0,"success":true,"log":""}]',
logs: [],
tx: cosmoshub.tx,
timestamp: "2020-02-14T11:35:41Z",
};
const expected = {
...testdata.signedTxJson,
height: 2823,
confirmations: 101,
transactionId: testdata.txId,
log: '[{"msg_index":0,"success":true,"log":""}]',
};
expect(
parseTxsResponseSigned(testdata.chainId, currentHeight, testdata.nonce, txsResponse, defaultTokens),
).toEqual(expected);
});
});
});
/*
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,187 +0,0 @@
import { fromBase64 } from "@cosmjs/encoding";
import { Decimal } from "@cosmjs/math";
import {
Coin,
IndexedTx,
isMsgSend,
isStdTx,
Msg,
PubKey,
pubkeyType,
StdFee,
StdSignature,
StdTx,
} from "@cosmjs/sdk38";
import {
Address,
Algorithm,
Amount,
ChainId,
ConfirmedAndSignedTransaction,
ConfirmedTransaction,
Fee,
FullSignature,
Nonce,
PubkeyBundle,
PubkeyBytes,
SendTransaction,
SignatureBytes,
SignedTransaction,
TokenTicker,
TransactionId,
UnsignedTransaction,
} from "@iov/bcp";
import { BankToken } from "./types";
export function decodePubkey(pubkey: PubKey): PubkeyBundle {
switch (pubkey.type) {
case pubkeyType.secp256k1:
return {
algo: Algorithm.Secp256k1,
data: fromBase64(pubkey.value) as PubkeyBytes,
};
case pubkeyType.ed25519:
return {
algo: Algorithm.Ed25519,
data: fromBase64(pubkey.value) as PubkeyBytes,
};
default:
throw new Error("Unsupported pubkey type");
}
}
export function decodeSignature(signature: string): SignatureBytes {
return fromBase64(signature) as SignatureBytes;
}
export function decodeFullSignature(signature: StdSignature, nonce: number): FullSignature {
return {
nonce: nonce as Nonce,
pubkey: decodePubkey(signature.pub_key),
signature: decodeSignature(signature.signature),
};
}
export function coinToDecimal(tokens: readonly BankToken[], coin: Coin): readonly [Decimal, string] {
const match = tokens.find(({ denom }) => denom === coin.denom);
if (!match) {
throw Error(`unknown denom: ${coin.denom}`);
}
const value = Decimal.fromAtomics(coin.amount, match.fractionalDigits);
return [value, match.ticker];
}
export function decodeAmount(tokens: readonly BankToken[], coin: Coin): Amount {
const [value, ticker] = coinToDecimal(tokens, coin);
return {
quantity: value.atomics,
fractionalDigits: value.fractionalDigits,
tokenTicker: ticker as TokenTicker,
};
}
export function parseMsg(
msg: Msg,
memo: string | undefined,
chainId: ChainId,
tokens: readonly BankToken[],
): UnsignedTransaction {
if (isMsgSend(msg)) {
if (msg.value.amount.length !== 1) {
throw new Error("Only MsgSend with one amount is supported");
}
const send: SendTransaction = {
kind: "bcp/send",
chainId: chainId,
sender: msg.value.from_address as Address,
recipient: msg.value.to_address as Address,
amount: decodeAmount(tokens, msg.value.amount[0]),
memo: memo,
};
return send;
} else {
// Unknown transaction type
const unknown = {
chainId: chainId,
kind: "bcp/unknown",
};
return unknown;
}
}
export function parseFee(fee: StdFee, tokens: readonly BankToken[]): Fee {
if (fee.amount.length !== 1) {
throw new Error("Only fee with one amount is supported");
}
return {
tokens: decodeAmount(tokens, fee.amount[0]),
gasLimit: fee.gas,
};
}
export function parseUnsignedTx(
txValue: StdTx,
chainId: ChainId,
tokens: readonly BankToken[],
): UnsignedTransaction {
if (!isStdTx(txValue)) {
throw new Error("Only StdTx is supported");
}
if (txValue.msg.length !== 1) {
throw new Error("Only single-message transactions currently supported");
}
const msg = parseMsg(txValue.msg[0], txValue.memo, chainId, tokens);
const fee = parseFee(txValue.fee, tokens);
return {
...msg,
chainId: chainId,
fee: fee,
};
}
export function parseSignedTx(
txValue: StdTx,
chainId: ChainId,
nonce: Nonce,
tokens: readonly BankToken[],
): SignedTransaction {
const [primarySignature] = txValue.signatures.map((signature) => decodeFullSignature(signature, nonce));
return {
transaction: parseUnsignedTx(txValue, chainId, tokens),
signatures: [primarySignature],
};
}
export function parseTxsResponseUnsigned(
chainId: ChainId,
currentHeight: number,
response: IndexedTx,
tokens: readonly BankToken[],
): ConfirmedTransaction<UnsignedTransaction> {
return {
transaction: parseUnsignedTx(response.tx.value, chainId, tokens),
height: response.height,
confirmations: currentHeight - response.height + 1,
transactionId: response.hash as TransactionId,
log: response.rawLog,
};
}
export function parseTxsResponseSigned(
chainId: ChainId,
currentHeight: number,
nonce: Nonce,
response: IndexedTx,
tokens: readonly BankToken[],
): ConfirmedAndSignedTransaction<UnsignedTransaction> {
return {
...parseSignedTx(response.tx.value, chainId, nonce, tokens),
height: response.height,
confirmations: currentHeight - response.height + 1,
transactionId: response.hash as TransactionId,
log: response.rawLog,
};
}

View File

@ -1,310 +0,0 @@
/* eslint-disable @typescript-eslint/camelcase */
import { fromBase64 } from "@cosmjs/encoding";
import {
Address,
Algorithm,
Amount,
ChainId,
Nonce,
PubkeyBytes,
SendTransaction,
SignatureBytes,
SignedTransaction,
TokenTicker,
} from "@iov/bcp";
import {
buildSignedTx,
buildUnsignedTx,
encodeFee,
encodeFullSignature,
encodePubkey,
toBankCoin,
} from "./encode";
import { BankToken } from "./types";
describe("encode", () => {
const atom = "ATOM" as TokenTicker;
// https://rpc.cosmos.network:26657/tx?hash=0x2268EB5AB730B45F8426078827BB5BB49819CE2B0D74B2C1D191070BADB379F1&prove=true
const defaultPubkey = {
algo: Algorithm.Secp256k1,
data: fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP") as PubkeyBytes,
};
const defaultChainId = "not-used" as ChainId;
const defaultSender = "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address;
const defaultRecipient = "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e" as Address;
const defaultAmount: Amount = {
fractionalDigits: 6,
quantity: "11657995",
tokenTicker: atom,
};
const defaultMemo = "hello cosmos hub";
const defaultTokens: readonly BankToken[] = [
{
fractionalDigits: 6,
ticker: "ATOM",
denom: "uatom",
},
];
describe("encodePubkey", () => {
it("works for compressed public key", () => {
expect(encodePubkey(defaultPubkey)).toEqual({
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
});
});
});
describe("toBankCoin", () => {
it("encodes an amount", () => {
expect(toBankCoin(defaultAmount, defaultTokens)).toEqual({
denom: "uatom",
amount: "11657995",
});
});
});
describe("encodeFee", () => {
it("throws without tokens", () => {
const fee = {
gasLimit: "200000",
};
expect(() => encodeFee(fee, defaultTokens)).toThrowError(/cannot encode fee without tokens/i);
});
it("throws without gas limit", () => {
const fee = {
tokens: {
fractionalDigits: 6,
quantity: "5000",
tokenTicker: atom,
},
};
expect(() => encodeFee(fee, defaultTokens)).toThrowError(/cannot encode fee without gas limit/i);
});
it("encodes a fee", () => {
const fee = {
tokens: {
fractionalDigits: 6,
quantity: "5000",
tokenTicker: atom,
},
gasLimit: "200000",
};
expect(encodeFee(fee, defaultTokens)).toEqual({
amount: [{ denom: "uatom", amount: "5000" }],
gas: "200000",
});
});
});
describe("encodeFullSignature", () => {
it("encodes a full signature", () => {
const signature = {
nonce: 0 as Nonce,
pubkey: {
algo: Algorithm.Secp256k1,
data: fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP") as PubkeyBytes,
},
signature: fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
) as SignatureBytes,
};
expect(encodeFullSignature(signature)).toEqual({
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
it("compresses uncompressed public keys", () => {
const signature = {
nonce: 0 as Nonce,
pubkey: {
algo: Algorithm.Secp256k1,
data: fromBase64(
"BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=",
) as PubkeyBytes,
},
signature: fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
) as SignatureBytes,
};
expect(encodeFullSignature(signature)).toEqual({
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
it("removes recovery values from signature data", () => {
const signature = {
nonce: 0 as Nonce,
pubkey: {
algo: Algorithm.Secp256k1,
data: fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP") as PubkeyBytes,
},
signature: Uint8Array.from([
...fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
),
99,
]) as SignatureBytes,
};
expect(encodeFullSignature(signature)).toEqual({
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
});
describe("buildUnsignedTx", () => {
it("throws for unsupported transaction", () => {
const tx = {
kind: "bns/return_escrow",
chainId: defaultChainId,
escrowId: "defg",
};
expect(() => buildUnsignedTx(tx, defaultTokens)).toThrowError(
/received transaction of unsupported kind/i,
);
});
it("throws for a send transaction without fee", () => {
// This will be rejected by the REST server. Better throw early to avoid hard to debug errors.
const tx = {
kind: "bcp/send",
chainId: defaultChainId,
amount: defaultAmount,
sender: defaultSender,
recipient: defaultRecipient,
memo: defaultMemo,
};
expect(() => buildUnsignedTx(tx, defaultTokens)).toThrowError(/transaction fee must be set/i);
});
it("builds a send transaction with fee", () => {
const tx = {
kind: "bcp/send",
chainId: defaultChainId,
amount: defaultAmount,
sender: defaultSender,
recipient: defaultRecipient,
memo: defaultMemo,
fee: {
tokens: {
fractionalDigits: 6,
quantity: "5000",
tokenTicker: atom,
},
gasLimit: "200000",
},
};
expect(buildUnsignedTx(tx, defaultTokens)).toEqual({
type: "cosmos-sdk/StdTx",
value: {
msg: [
{
type: "cosmos-sdk/MsgSend",
value: {
from_address: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r",
to_address: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e",
amount: [
{
denom: "uatom",
amount: "11657995",
},
],
},
},
],
fee: {
amount: [{ denom: "uatom", amount: "5000" }],
gas: "200000",
},
signatures: [],
memo: defaultMemo,
},
});
});
});
describe("buildSignedTx", () => {
it("builds a send transaction", () => {
const tx: SignedTransaction<SendTransaction> = {
transaction: {
kind: "bcp/send",
chainId: defaultChainId,
amount: defaultAmount,
sender: defaultSender,
recipient: defaultRecipient,
memo: defaultMemo,
fee: {
tokens: {
fractionalDigits: 6,
quantity: "5000",
tokenTicker: atom,
},
gasLimit: "200000",
},
},
signatures: [
{
nonce: 0 as Nonce,
pubkey: {
algo: Algorithm.Secp256k1,
data: fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP") as PubkeyBytes,
},
signature: fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
) as SignatureBytes,
},
],
};
expect(buildSignedTx(tx, defaultTokens)).toEqual({
type: "cosmos-sdk/StdTx",
value: {
msg: [
{
type: "cosmos-sdk/MsgSend",
value: {
from_address: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r",
to_address: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e",
amount: [
{
denom: "uatom",
amount: "11657995",
},
],
},
},
],
fee: {
amount: [{ denom: "uatom", amount: "5000" }],
gas: "200000",
},
signatures: [
{
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature:
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
},
],
memo: defaultMemo,
},
});
});
});
});

View File

@ -1,123 +0,0 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Secp256k1 } from "@cosmjs/crypto";
import { toBase64 } from "@cosmjs/encoding";
import {
Coin,
CosmosSdkTx,
encodeSecp256k1Pubkey,
encodeSecp256k1Signature,
PubKey,
pubkeyType,
StdFee,
StdSignature,
} from "@cosmjs/sdk38";
import {
Algorithm,
Amount,
Fee,
FullSignature,
isSendTransaction,
PubkeyBundle,
SignedTransaction,
UnsignedTransaction,
} from "@iov/bcp";
import { BankToken } from "./types";
// TODO: This function seems to be unused and is not well tested (e.g. uncompressed secp256k1 or ed25519)
export function encodePubkey(pubkey: PubkeyBundle): PubKey {
switch (pubkey.algo) {
case Algorithm.Secp256k1:
return encodeSecp256k1Pubkey(pubkey.data);
case Algorithm.Ed25519:
return {
type: pubkeyType.ed25519,
value: toBase64(pubkey.data),
};
default:
throw new Error("Unsupported pubkey algo");
}
}
export function toBankCoin(amount: Amount, tokens: readonly BankToken[]): Coin {
const match = tokens.find((token) => token.ticker === amount.tokenTicker);
if (!match) throw Error(`unknown ticker: ${amount.tokenTicker}`);
if (match.fractionalDigits !== amount.fractionalDigits) {
throw new Error(
"Mismatch in fractional digits between token and value. If you really want, implement a conversion here. However, this indicates a bug in the caller code.",
);
}
return {
denom: match.denom,
amount: amount.quantity,
};
}
export function encodeFee(fee: Fee, tokens: readonly BankToken[]): StdFee {
if (fee.tokens === undefined) {
throw new Error("Cannot encode fee without tokens");
}
if (fee.gasLimit === undefined) {
throw new Error("Cannot encode fee without gas limit");
}
return {
amount: [toBankCoin(fee.tokens, tokens)],
gas: fee.gasLimit,
};
}
export function encodeFullSignature(fullSignature: FullSignature): StdSignature {
switch (fullSignature.pubkey.algo) {
case Algorithm.Secp256k1: {
const compressedPubkey = Secp256k1.compressPubkey(fullSignature.pubkey.data);
const normalizedSignature = Secp256k1.trimRecoveryByte(fullSignature.signature);
return encodeSecp256k1Signature(compressedPubkey, normalizedSignature);
}
default:
throw new Error("Unsupported signing algorithm");
}
}
export function buildUnsignedTx(tx: UnsignedTransaction, bankTokens: readonly BankToken[]): CosmosSdkTx {
if (!isSendTransaction(tx)) {
throw new Error("Received transaction of unsupported kind");
}
const matchingBankToken = bankTokens.find((t) => t.ticker === tx.amount.tokenTicker);
if (!tx.fee) throw new Error("Transaction fee must be set");
if (matchingBankToken) {
return {
type: "cosmos-sdk/StdTx",
value: {
msg: [
{
type: "cosmos-sdk/MsgSend",
value: {
from_address: tx.sender,
to_address: tx.recipient,
amount: [toBankCoin(tx.amount, bankTokens)],
},
},
],
memo: tx.memo || "",
signatures: [],
fee: encodeFee(tx.fee, bankTokens),
},
};
} else {
throw new Error("Cannot encode this type of transaction");
}
}
export function buildSignedTx(tx: SignedTransaction, bankTokens: readonly BankToken[]): CosmosSdkTx {
const built = buildUnsignedTx(tx.transaction, bankTokens);
return {
...built,
value: {
...built.value,
signatures: tx.signatures.map(encodeFullSignature),
},
};
}

View File

@ -1,4 +0,0 @@
export { CosmosCodec } from "./cosmoscodec";
export { CosmosConnection, TokenConfiguration } from "./cosmosconnection";
export { createCosmosConnector } from "./cosmosconnector";
export { BankToken } from "./types";

View File

@ -1,66 +0,0 @@
import { fromBase64, toUtf8 } from "@cosmjs/encoding";
import {
Address,
Algorithm,
ChainId,
FullSignature,
Nonce,
PubkeyBundle,
PubkeyBytes,
SendTransaction,
SignatureBytes,
SignedTransaction,
TokenTicker,
TransactionId,
} from "@iov/bcp";
import data from "./testdata/cosmoshub.json";
export const pubJson: PubkeyBundle = {
algo: Algorithm.Secp256k1,
data: fromBase64(data.tx.value.signatures[0].pub_key.value) as PubkeyBytes,
};
export const chainId = "cosmos:cosmoshub-3" as ChainId;
export const nonce = 99 as Nonce;
export const sendTxJson: SendTransaction = {
kind: "bcp/send",
chainId: chainId,
sender: data.tx.value.msg[0].value.from_address as Address,
recipient: data.tx.value.msg[0].value.to_address as Address,
memo: data.tx.value.memo,
amount: {
fractionalDigits: 6,
quantity: data.tx.value.msg[0].value.amount[0].amount,
tokenTicker: "ATOM" as TokenTicker,
},
fee: {
tokens: {
fractionalDigits: 6,
quantity: data.tx.value.fee.amount[0].amount,
tokenTicker: "ATOM" as TokenTicker,
},
gasLimit: data.tx.value.fee.gas,
},
};
export const signedTxSig: FullSignature = {
nonce: nonce,
pubkey: pubJson,
signature: fromBase64(data.tx.value.signatures[0].signature) as SignatureBytes,
};
export const signedTxJson: SignedTransaction = {
transaction: sendTxJson,
signatures: [signedTxSig],
};
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,44 +0,0 @@
{
"//source": "https://hubble.figment.network/cosmos/chains/cosmoshub-3/blocks/415777/transactions/2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4?format=json",
"tx": {
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "cosmos-sdk/MsgSend",
"value": {
"from_address": "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq",
"to_address": "cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae",
"amount": [
{
"denom": "uatom",
"amount": "35997500"
}
]
}
}
],
"fee": {
"amount": [
{
"denom": "uatom",
"amount": "2500"
}
],
"gas": "100000"
},
"signatures": [
{
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "A5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAq"
},
"signature": "NK1Oy4EUGAsoC03c1wi9GG03JC/39LEdautC5Jk643oIbEPqeXHMwaqbdvO/Jws0X/NAXaN8SAy2KNY5Qml+5Q=="
}
],
"memo": ""
}
},
"tx_data": "ygEoKBapCkOoo2GaChRZgJnSW8Lg8zwesNppHWhJTrk8uhIUmSc4HyYqQahKSZHt4pN2aKsALu8aEQoFdWF0b20SCDM1OTk3NTAwEhMKDQoFdWF0b20SBDI1MDAQoI0GGmoKJuta6YchA5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAqEkA0rU7LgRQYCygLTdzXCL0YbTckL/f0sR1q60LkmTrjeghsQ+p5cczBqpt2878nCzRf80Bdo3xIDLYo1jlCaX7l",
"id": "2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4"
}

View File

@ -1,15 +0,0 @@
/* eslint-disable @typescript-eslint/camelcase */
import { accountToNonce, nonceToAccountNumber, nonceToSequence } from "./types";
describe("nonceEncoding", () => {
it("works for input in range", () => {
const nonce = accountToNonce(1234, 7890);
expect(nonceToAccountNumber(nonce)).toEqual(1234);
expect(nonceToSequence(nonce)).toEqual(7890);
});
it("errors on input too large", () => {
expect(() => accountToNonce(1234567890, 7890)).toThrow();
expect(() => accountToNonce(178, 97320247923)).toThrow();
});
});

View File

@ -1,52 +0,0 @@
import { Nonce } from "@iov/bcp";
export interface BankToken {
readonly denom: string;
readonly ticker: string;
/**
* The number of fractional digits the token supports.
*
* A quantity is expressed as atomic units. 10^fractionalDigits of those
* atomic units make up 1 token.
*
* E.g. in Ethereum 10^18 wei are 1 ETH and from the quantity 123000000000000000000
* the last 18 digits are the fractional part and the rest the wole part.
*/
readonly fractionalDigits: number;
}
// eslint-disable-next-line no-bitwise
const maxAcct = 1 << 23;
// eslint-disable-next-line no-bitwise
const maxSeq = 1 << 20;
// this (lossily) encodes the two pieces of info (uint64) needed to sign into
// one (53-bit) number. Cross your fingers.
export function accountToNonce(accountNumber: number, sequence: number): Nonce {
// we allow 23 bits (8 million) for accounts, and 20 bits (1 million) for tx/account
// let's fix this soon
if (accountNumber > maxAcct) {
throw new Error("Account number is greater than 2^23, must update Nonce handler");
}
if (sequence > maxSeq) {
throw new Error("Sequence is greater than 2^20, must update Nonce handler");
}
const val = accountNumber * maxSeq + sequence;
return val as Nonce;
}
// this extracts info from nonce for signing
export function nonceToAccountNumber(nonce: Nonce): number {
const acct = nonce / maxSeq;
if (acct > maxAcct) {
throw new Error("Invalid Nonce, account number is higher than can safely be encoded in Nonce");
}
return Math.round(acct);
}
// this extracts info from nonce for signing
export function nonceToSequence(nonce: Nonce): number {
const seq = nonce % maxSeq;
return Math.round(seq);
}

View File

@ -1,12 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "build",
"declarationDir": "build/types",
"rootDir": "src"
},
"include": [
"src/**/*"
]
}

View File

@ -1,14 +0,0 @@
const packageJson = require("./package.json");
module.exports = {
src: ["./src"],
out: "docs",
exclude: "**/*.spec.ts",
target: "es6",
name: `${packageJson.name} Documentation`,
readme: "README.md",
mode: "file",
excludeExternals: true,
excludeNotExported: true,
excludePrivate: true,
};

View File

@ -1,2 +0,0 @@
import { Address, PubkeyBundle } from "@iov/bcp";
export declare function pubkeyToAddress(pubkey: PubkeyBundle, prefix: string): Address;

View File

@ -1,13 +0,0 @@
import { ChainId } from "@iov/bcp";
/**
* Conversion between native chain IDs and CAIP-5 format
*
* @see https://github.com/ChainAgnostic/CAIPs/pull/9
*/
export declare class Caip5 {
/**
* @param native The `chain_id` field from Tendermint's genesis file
*/
static encode(native: string): ChainId;
static decode(chainId: ChainId): string;
}

View File

@ -1,24 +0,0 @@
import {
Address,
ChainId,
Identity,
Nonce,
PostableBytes,
SignedTransaction,
SigningJob,
TransactionId,
TxCodec,
UnsignedTransaction,
} from "@iov/bcp";
import { BankToken } from "./types";
export declare class CosmosCodec implements TxCodec {
private readonly addressPrefix;
private readonly bankTokens;
constructor(addressPrefix: string, bankTokens: readonly BankToken[]);
bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob;
bytesToPost(signed: SignedTransaction): PostableBytes;
identifier(_signed: SignedTransaction): TransactionId;
parseBytes(bytes: PostableBytes, chainId: ChainId, nonce?: Nonce): SignedTransaction;
identityToAddress(identity: Identity): Address;
isValidAddress(address: string): boolean;
}

View File

@ -1,78 +0,0 @@
import {
Account,
AccountQuery,
AddressQuery,
BlockchainConnection,
BlockHeader,
ChainId,
ConfirmedAndSignedTransaction,
ConfirmedTransaction,
FailedTransaction,
Fee,
Nonce,
PostableBytes,
PostTxResponse,
PubkeyQuery,
SignedTransaction,
Token,
TokenTicker,
TransactionId,
TransactionQuery,
TxCodec,
UnsignedTransaction,
} from "@iov/bcp";
import { Stream } from "xstream";
import { BankToken } from "./types";
export interface TokenConfiguration {
/** Supported tokens of the Cosmos SDK bank module */
readonly bankTokens: ReadonlyArray<
BankToken & {
readonly name: string;
}
>;
}
export declare class CosmosConnection implements BlockchainConnection {
static establish(url: string, addressPrefix: string, tokens: TokenConfiguration): Promise<CosmosConnection>;
private static initialize;
readonly chainId: ChainId;
readonly codec: TxCodec;
private readonly cosmosClient;
private readonly addressPrefix;
private readonly bankTokens;
private readonly feeToken;
private readonly supportedTokens;
private constructor();
disconnect(): void;
height(): Promise<number>;
getToken(searchTicker: TokenTicker): Promise<Token | undefined>;
getAllTokens(): Promise<readonly Token[]>;
/**
* This is a replacement for the unimplemented CosmosCodec.identifier. Here we have more
* context and network available, which we might use to implement the API in an async way.
*/
identifier(signed: SignedTransaction): Promise<TransactionId>;
getAccount(query: AccountQuery): Promise<Account | undefined>;
watchAccount(query: AccountQuery): Stream<Account | undefined>;
getNonce(query: AddressQuery | PubkeyQuery): Promise<Nonce>;
getNonces(query: AddressQuery | PubkeyQuery, count: number): Promise<readonly Nonce[]>;
getBlockHeader(height: number): Promise<BlockHeader>;
watchBlockHeaders(): Stream<BlockHeader>;
getTx(id: TransactionId): Promise<ConfirmedAndSignedTransaction<UnsignedTransaction> | FailedTransaction>;
postTx(tx: PostableBytes): Promise<PostTxResponse>;
searchTx({
height,
id,
maxHeight,
minHeight,
sentFromOrTo,
signedBy,
tags,
}: TransactionQuery): Promise<readonly (ConfirmedTransaction<UnsignedTransaction> | FailedTransaction)[]>;
listenTx(_query: TransactionQuery): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction>;
liveTx(query: TransactionQuery): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction>;
getFeeQuote(tx: UnsignedTransaction): Promise<Fee>;
withDefaultFee<T extends UnsignedTransaction>(tx: T): Promise<T>;
private parseAndPopulateTxResponseUnsigned;
private parseAndPopulateTxResponseSigned;
private waitForTransaction;
}

View File

@ -1,11 +0,0 @@
import { ChainConnector, ChainId } from "@iov/bcp";
import { CosmosConnection, TokenConfiguration } from "./cosmosconnection";
/**
* A helper to connect to a cosmos-based chain at a given url
*/
export declare function createCosmosConnector(
url: string,
addressPrefix: string,
tokenConfig: TokenConfiguration,
expectedChainId?: ChainId,
): ChainConnector<CosmosConnection>;

View File

@ -1,52 +0,0 @@
import { Decimal } from "@cosmjs/math";
import { Coin, IndexedTx, Msg, PubKey, StdFee, StdSignature, StdTx } from "@cosmjs/sdk38";
import {
Amount,
ChainId,
ConfirmedAndSignedTransaction,
ConfirmedTransaction,
Fee,
FullSignature,
Nonce,
PubkeyBundle,
SignatureBytes,
SignedTransaction,
UnsignedTransaction,
} from "@iov/bcp";
import { BankToken } from "./types";
export declare function decodePubkey(pubkey: PubKey): PubkeyBundle;
export declare function decodeSignature(signature: string): SignatureBytes;
export declare function decodeFullSignature(signature: StdSignature, nonce: number): FullSignature;
export declare function coinToDecimal(tokens: readonly BankToken[], coin: Coin): readonly [Decimal, string];
export declare function decodeAmount(tokens: readonly BankToken[], coin: Coin): Amount;
export declare function parseMsg(
msg: Msg,
memo: string | undefined,
chainId: ChainId,
tokens: readonly BankToken[],
): UnsignedTransaction;
export declare function parseFee(fee: StdFee, tokens: readonly BankToken[]): Fee;
export declare function parseUnsignedTx(
txValue: StdTx,
chainId: ChainId,
tokens: readonly BankToken[],
): UnsignedTransaction;
export declare function parseSignedTx(
txValue: StdTx,
chainId: ChainId,
nonce: Nonce,
tokens: readonly BankToken[],
): SignedTransaction;
export declare function parseTxsResponseUnsigned(
chainId: ChainId,
currentHeight: number,
response: IndexedTx,
tokens: readonly BankToken[],
): ConfirmedTransaction<UnsignedTransaction>;
export declare function parseTxsResponseSigned(
chainId: ChainId,
currentHeight: number,
nonce: Nonce,
response: IndexedTx,
tokens: readonly BankToken[],
): ConfirmedAndSignedTransaction<UnsignedTransaction>;

View File

@ -1,12 +0,0 @@
import { Coin, CosmosSdkTx, PubKey, StdFee, StdSignature } from "@cosmjs/sdk38";
import { Amount, Fee, FullSignature, PubkeyBundle, SignedTransaction, UnsignedTransaction } from "@iov/bcp";
import { BankToken } from "./types";
export declare function encodePubkey(pubkey: PubkeyBundle): PubKey;
export declare function toBankCoin(amount: Amount, tokens: readonly BankToken[]): Coin;
export declare function encodeFee(fee: Fee, tokens: readonly BankToken[]): StdFee;
export declare function encodeFullSignature(fullSignature: FullSignature): StdSignature;
export declare function buildUnsignedTx(
tx: UnsignedTransaction,
bankTokens: readonly BankToken[],
): CosmosSdkTx;
export declare function buildSignedTx(tx: SignedTransaction, bankTokens: readonly BankToken[]): CosmosSdkTx;

View File

@ -1,4 +0,0 @@
export { CosmosCodec } from "./cosmoscodec";
export { CosmosConnection, TokenConfiguration } from "./cosmosconnection";
export { createCosmosConnector } from "./cosmosconnector";
export { BankToken } from "./types";

View File

@ -1,18 +0,0 @@
import { Nonce } from "@iov/bcp";
export interface BankToken {
readonly denom: string;
readonly ticker: string;
/**
* The number of fractional digits the token supports.
*
* A quantity is expressed as atomic units. 10^fractionalDigits of those
* atomic units make up 1 token.
*
* E.g. in Ethereum 10^18 wei are 1 ETH and from the quantity 123000000000000000000
* the last 18 digits are the fractional part and the rest the wole part.
*/
readonly fractionalDigits: number;
}
export declare function accountToNonce(accountNumber: number, sequence: number): Nonce;
export declare function nonceToAccountNumber(nonce: Nonce): number;
export declare function nonceToSequence(nonce: Nonce): number;

View File

@ -1,19 +0,0 @@
const glob = require("glob");
const path = require("path");
const webpack = require("webpack");
const target = "web";
const distdir = path.join(__dirname, "dist", "web");
module.exports = [
{
// bundle used for Karma tests
target: target,
entry: glob.sync("./build/**/*.spec.js"),
output: {
path: distdir,
filename: "tests.js",
},
plugins: [new webpack.EnvironmentPlugin(["WASMD_ENABLED"])],
},
];

169
yarn.lock
View File

@ -92,71 +92,6 @@
unique-filename "^1.1.1"
which "^1.3.1"
"@iov/bcp@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@iov/bcp/-/bcp-2.3.2.tgz#23dc92839951beb239ec6ed56490d7534d67451c"
integrity sha512-o9ae5Q8jHgGyxsUnyoRcL200YVbDEYXAb6ZaAFad5/aFjhVawjTDSgya+vL8zI+t8BBixJZcXOjeDaqLtWRQAw==
dependencies:
"@iov/crypto" "^2.3.2"
"@iov/encoding" "^2.3.2"
"@iov/stream" "^2.3.2"
type-tagger "^1.0.0"
xstream "^11.10.0"
"@iov/crypto@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@iov/crypto/-/crypto-2.3.2.tgz#0f65623d29d1df7e35620bbbeede70f1d0797ff9"
integrity sha512-inHHZa9kr+4K88ZhqOQC/LVlZBcZlE0Kkj97ynWGzaR4CCdCFa0KOkx2vef2CH0J6wbfC13e6b93/MuQyGiUAw==
dependencies:
"@iov/encoding" "^2.3.2"
bip39 "^3.0.2"
bn.js "^4.11.8"
elliptic "^6.4.0"
js-sha3 "^0.8.0"
libsodium-wrappers "^0.7.6"
pbkdf2 "^3.0.16"
ripemd160 "^2.0.2"
sha.js "^2.4.11"
type-tagger "^1.0.0"
unorm "^1.5.0"
"@iov/encoding@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@iov/encoding/-/encoding-2.3.2.tgz#4b37966af0345a6bc904bb58189dc1ea9d14ad9b"
integrity sha512-viioqo1flTkG4Oxb0PvoBXGozHq9fObAgAL4dRHJe9zmChE77EBX2Y5u0nabd2JwAhEbir56AtsrUe4dOrtd5w==
dependencies:
base64-js "^1.3.0"
bech32 "^1.1.4"
bn.js "^4.11.8"
readonly-date "^1.0.0"
"@iov/keycontrol@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@iov/keycontrol/-/keycontrol-2.3.2.tgz#42b556476c489e45b01ec47ec9ed28f287f5199e"
integrity sha512-Ile1c+ceAwefulNZ2pYu1TFeoFcx4S2oJTS57sFDNJzDDjn5xvEgNpdn4xkDDdGvkQOnexiMNifeCjInHBJpjQ==
dependencies:
"@iov/bcp" "^2.3.2"
"@iov/crypto" "^2.3.2"
"@iov/encoding" "^2.3.2"
"@iov/stream" "^2.3.2"
"@types/abstract-leveldown" "^5.0.1"
"@types/levelup" "^3.1.0"
"@types/node" "^10.12.18"
"@types/random-js" "^1.0.31"
levelup "^4.0.0"
long "^4.0.0"
random-js "^1.0.8"
readonly-date "^1.0.0"
type-tagger "^1.0.0"
xstream "^11.10.0"
"@iov/stream@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@iov/stream/-/stream-2.3.2.tgz#472063f3a4fcd1e97de0ae99189f98b94825afac"
integrity sha512-nOq5OKwK2rWnyzXpGTAKZToCU0jmTjEC05owVzmBfw3+7hmjlExrGGbLncjcMWzbvp2VVpHc2l01Tr+qpzr8Vw==
dependencies:
xstream "^11.10.0"
"@koa/cors@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.0.0.tgz#df021b4df2dadf1e2b04d7c8ddf93ba2d42519cb"
@ -955,11 +890,6 @@
dependencies:
"@types/node" ">= 8"
"@types/abstract-leveldown@*", "@types/abstract-leveldown@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@types/abstract-leveldown/-/abstract-leveldown-5.0.1.tgz#3c7750d0186b954c7f2d2f6acc8c3c7ba0c3412e"
integrity sha512-wYxU3kp5zItbxKmeRYCEplS2MW7DzyBnxPGj+GJVHZEUZiK/nn5Ei1sUFgURDh+X051+zsGe28iud3oHjrYWQQ==
"@types/accepts@*":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575"
@ -1122,14 +1052,6 @@
dependencies:
"@types/koa" "*"
"@types/levelup@^3.1.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@types/levelup/-/levelup-3.1.1.tgz#f7cc08f248f14cb6c92914e91bceb8761020e8f0"
integrity sha512-LjvlfctJYj23Xuqq3jCT8ZPSUSSgDcRJg8+XFDBasoYzefFbB4cHzlDmBVjc2rBOYvklpYHJRayD0jBsbJLD9w==
dependencies:
"@types/abstract-leveldown" "*"
"@types/node" "*"
"@types/libsodium-wrappers@^0.7.7":
version "0.7.7"
resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.7.tgz#cdb25e85458612ec80f0157c3815fac187d0b6d2"
@ -1155,11 +1077,6 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a"
integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==
"@types/node@^10.12.18":
version "10.17.17"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.17.tgz#7a183163a9e6ff720d86502db23ba4aade5999b8"
integrity sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==
"@types/pako@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.1.tgz#33b237f3c9aff44d0f82fe63acffa4a365ef4a61"
@ -1172,11 +1089,6 @@
dependencies:
"@types/node" "*"
"@types/random-js@^1.0.31":
version "1.0.31"
resolved "https://registry.yarnpkg.com/@types/random-js/-/random-js-1.0.31.tgz#18a8bcc075afa504421e638fcbe021f27e802941"
integrity sha512-EAM56DrKw3VhcE4HV0/YlVKeJI07We4Mz1ra6TNtZpaMoiBVMA2bkLEcoFpYOyxoDXfVZWojxkR617LTqtRI0A==
"@types/range-parser@*":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
@ -1442,15 +1354,6 @@ abbrev@1:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
abstract-leveldown@~6.2.1:
version "6.2.2"
resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.2.2.tgz#677425beeb28204367c7639e264e93ea4b49971a"
integrity sha512-/a+Iwj0rn//CX0EJOasNyZJd2o8xur8Ce9C57Sznti/Ilt/cb6Qd8/k98A4ZOklXgTG+iAYYUs1OTG0s1eH+zQ==
dependencies:
level-concat-iterator "~2.0.0"
level-supports "~1.0.0"
xtend "~4.0.0"
accepts@^1.3.5, accepts@~1.3.4:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@ -1906,11 +1809,6 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.8, bn.js@^4.4.0:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
bn.js@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.1.tgz#48efc4031a9c4041b9c99c6941d903463ab62eb5"
integrity sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA==
body-parser@^1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
@ -2834,14 +2732,6 @@ defaults@^1.0.3:
dependencies:
clone "^1.0.2"
deferred-leveldown@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz#27a997ad95408b61161aa69bd489b86c71b78058"
integrity sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==
dependencies:
abstract-leveldown "~6.2.1"
inherits "^2.0.3"
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@ -3155,7 +3045,7 @@ err-code@^1.0.0:
resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=
errno@^0.1.3, errno@~0.1.1, errno@~0.1.7:
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==
@ -4311,7 +4201,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -4967,45 +4857,6 @@ lerna@^3.20.2:
import-local "^2.0.0"
npmlog "^4.1.2"
level-concat-iterator@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz#1d1009cf108340252cb38c51f9727311193e6263"
integrity sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==
level-errors@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-2.0.1.tgz#2132a677bf4e679ce029f517c2f17432800c05c8"
integrity sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==
dependencies:
errno "~0.1.1"
level-iterator-stream@~4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz#7ceba69b713b0d7e22fcc0d1f128ccdc8a24f79c"
integrity sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==
dependencies:
inherits "^2.0.4"
readable-stream "^3.4.0"
xtend "^4.0.2"
level-supports@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-1.0.1.tgz#2f530a596834c7301622521988e2c36bb77d122d"
integrity sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==
dependencies:
xtend "^4.0.2"
levelup@^4.0.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/levelup/-/levelup-4.3.2.tgz#31c5b1b29f146d1d35d692e01a6da4d28fa55ebd"
integrity sha512-cRTjU4ktWo59wf13PHEiOayHC3n0dOh4i5+FHr4tv4MX9+l7mqETicNq3Aj07HKlLdk0z5muVoDL2RD+ovgiyA==
dependencies:
deferred-leveldown "~5.3.0"
level-errors "~2.0.0"
level-iterator-stream "~4.0.0"
level-supports "~1.0.0"
xtend "~4.0.0"
levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@ -5180,11 +5031,6 @@ log4js@^6.2.1:
rfdc "^1.1.4"
streamroller "^2.2.4"
long@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
loud-rejection@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
@ -6447,11 +6293,6 @@ quick-lru@^1.0.0:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8"
integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=
random-js@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/random-js/-/random-js-1.0.8.tgz#968fd689a6f25d6c0aac766283de2f688c9c190a"
integrity sha1-lo/WiabyXWwKrHZig94vaIycGQo=
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@ -6591,7 +6432,7 @@ read@1, read@~1.0.1:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
"readable-stream@2 || 3", readable-stream@^3.0.2, readable-stream@^3.4.0:
"readable-stream@2 || 3", readable-stream@^3.0.2:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@ -8155,14 +7996,14 @@ xmlhttprequest-ssl@~1.5.4:
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
xstream@^11.10.0, xstream@^11.11.0:
xstream@^11.11.0:
version "11.11.0"
resolved "https://registry.yarnpkg.com/xstream/-/xstream-11.11.0.tgz#2c963cf0a6cb3eafecb57eaeae16c9850235b422"
integrity sha512-0zF3PLsHrmbToPBgX1jYZFNw+t5octSFJgVRH44HGlGBBjY7gscvDQOZvty8IQV15Snia1RcLPECWDfEaE4aXg==
dependencies:
symbol-observable "1.2.0"
xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1:
xtend@^4.0.0, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==