Remove @cosmwasm/sdk

This commit is contained in:
Simon Warta
2020-06-02 14:58:15 +02:00
parent cd632a290e
commit 0fe3e5eb4a
77 changed files with 0 additions and 6153 deletions
-1
View File
@@ -1 +0,0 @@
../../.eslintignore
-3
View File
@@ -1,3 +0,0 @@
build/
dist/
docs/
-12
View File
@@ -1,12 +0,0 @@
# @cosmwasm/sdk
[![npm version](https://img.shields.io/npm/v/@cosmwasm/sdk.svg)](https://www.npmjs.com/package/@cosmwasm/sdk)
An SDK to build CosmWasm clients.
## License
This package is part of the cosmwasm-js repository, licensed under the Apache
License 2.0 (see
[NOTICE](https://github.com/confio/cosmwasm-js/blob/master/NOTICE) and
[LICENSE](https://github.com/confio/cosmwasm-js/blob/master/LICENSE)).
-26
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();
-54
View File
@@ -1,54 +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,
customLaunchers: {
ChromeHeadlessInsecure: {
base: "ChromeHeadless",
flags: ["--disable-web-security"],
},
},
});
};
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
-1
View File
@@ -1 +0,0 @@
Directory used to trigger lerna package updates for all packages
-50
View File
@@ -1,50 +0,0 @@
{
"name": "@cosmwasm/sdk",
"version": "0.8.0",
"description": "CosmWasm SDK",
"author": "Ethan Frey <ethanfrey@users.noreply.github.com>",
"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/confio/cosmwasm-js/tree/master/packages/sdk"
},
"publishConfig": {
"access": "public"
},
"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 ChromeHeadlessInsecure",
"test": "yarn build-or-skip && yarn test-node",
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
},
"dependencies": {
"@iov/crypto": "^2.1.0",
"@iov/encoding": "^2.1.0",
"@iov/utils": "^2.0.2",
"axios": "^0.19.0",
"fast-deep-equal": "^3.1.1",
"pako": "^1.0.11"
},
"devDependencies": {
"@types/pako": "^1.0.1",
"readonly-date": "^1.0.0"
}
}
-27
View File
@@ -1,27 +0,0 @@
import { Encoding } from "@iov/encoding";
import { pubkeyToAddress } from "./address";
const { toBase64, fromHex } = Encoding;
describe("address", () => {
describe("pubkeyToAddress", () => {
it("works for Secp256k1 compressed", () => {
const prefix = "cosmos";
const pubkey = {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
};
expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r");
});
it("works for Ed25519", () => {
const prefix = "cosmos";
const pubkey = {
type: "tendermint/PubKeyEd25519",
value: toBase64(fromHex("12ee6f581fe55673a1e9e1382a0829e32075a0aa4763c968bc526e1852e78c95")),
};
expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz");
});
});
});
-42
View File
@@ -1,42 +0,0 @@
import { Ripemd160, Sha256 } from "@iov/crypto";
import { Bech32, Encoding } from "@iov/encoding";
import { PubKey, pubkeyType } from "./types";
const { fromBase64 } = Encoding;
export function rawSecp256k1PubkeyToAddress(pubkeyRaw: Uint8Array, prefix: string): string {
if (pubkeyRaw.length !== 33) {
throw new Error(`Invalid Secp256k1 pubkey length (compressed): ${pubkeyRaw.length}`);
}
const hash1 = new Sha256(pubkeyRaw).digest();
const hash2 = new Ripemd160(hash1).digest();
return Bech32.encode(prefix, hash2);
}
// See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography
// This assumes we already have a cosmos-compressed pubkey
export function pubkeyToAddress(pubkey: PubKey, prefix: string): string {
const pubkeyBytes = fromBase64(pubkey.value);
switch (pubkey.type) {
case pubkeyType.secp256k1: {
return rawSecp256k1PubkeyToAddress(pubkeyBytes, prefix);
}
case pubkeyType.ed25519: {
if (pubkeyBytes.length !== 32) {
throw new Error(`Invalid Ed25519 pubkey length: ${pubkeyBytes.length}`);
}
const hash = new Sha256(pubkeyBytes).digest();
return Bech32.encode(prefix, hash.slice(0, 20));
}
case pubkeyType.sr25519: {
if (pubkeyBytes.length !== 32) {
throw new Error(`Invalid Sr25519 pubkey length: ${pubkeyBytes.length}`);
}
const hash = new Sha256(pubkeyBytes).digest();
return Bech32.encode(prefix, hash.slice(0, 20));
}
default:
throw new Error("Unrecognized public key algorithm");
}
}
-63
View File
@@ -1,63 +0,0 @@
import { isValidBuilder } from "./builder";
describe("builder", () => {
describe("isValidBuilder", () => {
// Valid cases
it("returns true for simple examples", () => {
expect(isValidBuilder("myorg/super-optimizer:0.1.2")).toEqual(true);
expect(isValidBuilder("myorg/super-optimizer:42")).toEqual(true);
});
it("supports images with multi level names", () => {
expect(isValidBuilder("myorg/department-x/office-y/technology-z/super-optimizer:0.1.2")).toEqual(true);
});
it("returns true for tags with lower and upper chars", () => {
expect(isValidBuilder("myorg/super-optimizer:0.1.2-alpha")).toEqual(true);
expect(isValidBuilder("myorg/super-optimizer:0.1.2-Alpha")).toEqual(true);
});
// Invalid cases
it("returns false for missing or empty tag", () => {
expect(isValidBuilder("myorg/super-optimizer")).toEqual(false);
expect(isValidBuilder("myorg/super-optimizer:")).toEqual(false);
});
it("returns false for name components starting or ending with a separator", () => {
expect(isValidBuilder(".myorg/super-optimizer:42")).toEqual(false);
expect(isValidBuilder("-myorg/super-optimizer:42")).toEqual(false);
expect(isValidBuilder("_myorg/super-optimizer:42")).toEqual(false);
expect(isValidBuilder("myorg./super-optimizer:42")).toEqual(false);
expect(isValidBuilder("myorg-/super-optimizer:42")).toEqual(false);
expect(isValidBuilder("myorg_/super-optimizer:42")).toEqual(false);
expect(isValidBuilder("myorg/.super-optimizer:42")).toEqual(false);
expect(isValidBuilder("myorg/-super-optimizer:42")).toEqual(false);
expect(isValidBuilder("myorg/_super-optimizer:42")).toEqual(false);
expect(isValidBuilder("myorg/super-optimizer.:42")).toEqual(false);
expect(isValidBuilder("myorg/super-optimizer-:42")).toEqual(false);
expect(isValidBuilder("myorg/super-optimizer_:42")).toEqual(false);
});
it("returns false for upper case character in name component", () => {
expect(isValidBuilder("mYorg/super-optimizer:42")).toEqual(false);
expect(isValidBuilder("myorg/super-Optimizer:42")).toEqual(false);
});
it("returns false for long images", () => {
expect(
isValidBuilder(
"myorgisnicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenice/super-optimizer:42",
),
).toEqual(false);
});
it("returns false for images with no organization", () => {
// Those are valid dockerhub images from https://hub.docker.com/_/ubuntu and https://hub.docker.com/_/rust
// but not valid in the context of CosmWasm Verify
expect(isValidBuilder("ubuntu:xenial-20200212")).toEqual(false);
expect(isValidBuilder("rust:1.40.0")).toEqual(false);
});
});
});
-20
View File
@@ -1,20 +0,0 @@
// A docker image regexp. We remove support for non-standard registries for simplicity.
// https://docs.docker.com/engine/reference/commandline/tag/#extended-description
//
// An image name is made up of slash-separated name components (optionally prefixed by a registry hostname).
// Name components may contain lowercase characters, digits and separators.
// A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator.
//
// A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits, underscores, periods and dashes.
// A tag name may not start with a period or a dash and may contain a maximum of 128 characters.
const dockerImagePattern = new RegExp(
"^[a-z0-9][a-z0-9._-]*[a-z0-9](/[a-z0-9][a-z0-9._-]*[a-z0-9])+:[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$",
);
/** Max length in bytes/characters (regexp enforces all ASCII, even if that is not required by the standard) */
const builderMaxLength = 128;
export function isValidBuilder(builder: string): boolean {
if (builder.length > builderMaxLength) return false;
return !!builder.match(dockerImagePattern);
}
-14
View File
@@ -1,14 +0,0 @@
export interface Coin {
readonly denom: string;
readonly amount: string;
}
/** Creates a coin */
export function coin(amount: number, denom: string): Coin {
return { amount: amount.toString(), denom: denom };
}
/** Creates a list of coins with one element */
export function coins(amount: number, denom: string): Coin[] {
return [coin(amount, denom)];
}
@@ -1,467 +0,0 @@
/* eslint-disable @typescript-eslint/camelcase */
import { assert, sleep } from "@iov/utils";
import { Coin } from "./coins";
import { CosmWasmClient } from "./cosmwasmclient";
import { makeSignBytes } from "./encoding";
import { Secp256k1Pen } from "./pen";
import { RestClient } from "./restclient";
import { SigningCosmWasmClient } from "./signingcosmwasmclient";
import {
deployedErc20,
faucet,
fromOneElementArray,
makeRandomAddress,
pendingWithoutWasmd,
wasmd,
wasmdEnabled,
} from "./testutils.spec";
import { CosmosSdkTx, isMsgExecuteContract, isMsgInstantiateContract, isMsgSend, MsgSend } from "./types";
describe("CosmWasmClient.searchTx", () => {
let sendSuccessful:
| {
readonly sender: string;
readonly recipient: string;
readonly hash: string;
readonly height: number;
readonly tx: CosmosSdkTx;
}
| undefined;
let sendUnsuccessful:
| {
readonly sender: string;
readonly recipient: string;
readonly hash: string;
readonly height: number;
readonly tx: CosmosSdkTx;
}
| undefined;
let postedExecute:
| {
readonly sender: string;
readonly contract: string;
readonly hash: string;
readonly height: number;
readonly tx: CosmosSdkTx;
}
| undefined;
beforeAll(async () => {
if (wasmdEnabled()) {
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) =>
pen.sign(signBytes),
);
{
const recipient = makeRandomAddress();
const transferAmount: Coin = {
denom: "ucosm",
amount: "1234567",
};
const result = await client.sendTokens(recipient, [transferAmount]);
await sleep(50); // wait until tx is indexed
const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash);
sendSuccessful = {
sender: faucet.address,
recipient: recipient,
hash: result.transactionHash,
height: Number.parseInt(txDetails.height, 10),
tx: txDetails.tx,
};
}
{
const memo = "Sending more than I can afford";
const recipient = makeRandomAddress();
const transferAmount = [
{
denom: "ucosm",
amount: "123456700000000",
},
];
const sendMsg: MsgSend = {
type: "cosmos-sdk/MsgSend",
value: {
// eslint-disable-next-line @typescript-eslint/camelcase
from_address: faucet.address,
// eslint-disable-next-line @typescript-eslint/camelcase
to_address: recipient,
amount: transferAmount,
},
};
const fee = {
amount: [
{
denom: "ucosm",
amount: "2000",
},
],
gas: "80000", // 80k
};
const { accountNumber, sequence } = await client.getNonce();
const chainId = await client.getChainId();
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await pen.sign(signBytes);
const tx: CosmosSdkTx = {
type: "cosmos-sdk/StdTx",
value: {
msg: [sendMsg],
fee: fee,
memo: memo,
signatures: [signature],
},
};
const transactionId = await client.getIdentifier(tx);
const heightBeforeThis = await client.getHeight();
try {
await client.postTx(tx.value);
} catch (error) {
// postTx() throws on execution failures, which is a questionable design. Ignore for now.
// console.log(error);
}
sendUnsuccessful = {
sender: faucet.address,
recipient: recipient,
hash: transactionId,
height: heightBeforeThis + 1,
tx: tx,
};
}
{
const hashInstance = deployedErc20.instances[0];
const msg = {
approve: {
spender: makeRandomAddress(),
amount: "12",
},
};
const result = await client.execute(hashInstance, msg);
await sleep(50); // wait until tx is indexed
const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash);
postedExecute = {
sender: faucet.address,
contract: hashInstance,
hash: result.transactionHash,
height: Number.parseInt(txDetails.height, 10),
tx: txDetails.tx,
};
}
}
});
describe("with SearchByIdQuery", () => {
it("can search successful tx by ID", async () => {
pendingWithoutWasmd();
assert(sendSuccessful, "value must be set in beforeAll()");
const client = new CosmWasmClient(wasmd.endpoint);
const result = await client.searchTx({ id: sendSuccessful.hash });
expect(result.length).toEqual(1);
expect(result[0]).toEqual(
jasmine.objectContaining({
height: sendSuccessful.height,
hash: sendSuccessful.hash,
code: 0,
tx: sendSuccessful.tx,
}),
);
});
it("can search unsuccessful tx by ID", async () => {
pendingWithoutWasmd();
assert(sendUnsuccessful, "value must be set in beforeAll()");
const client = new CosmWasmClient(wasmd.endpoint);
const result = await client.searchTx({ id: sendUnsuccessful.hash });
expect(result.length).toEqual(1);
expect(result[0]).toEqual(
jasmine.objectContaining({
height: sendUnsuccessful.height,
hash: sendUnsuccessful.hash,
code: 5,
tx: sendUnsuccessful.tx,
}),
);
});
it("can search by ID (non existent)", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000";
const result = await client.searchTx({ id: nonExistentId });
expect(result.length).toEqual(0);
});
it("can search by ID and filter by minHeight", async () => {
pendingWithoutWasmd();
assert(sendSuccessful);
const client = new CosmWasmClient(wasmd.endpoint);
const query = { id: sendSuccessful.hash };
{
const result = await client.searchTx(query, { minHeight: 0 });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { minHeight: sendSuccessful.height - 1 });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { minHeight: sendSuccessful.height });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { minHeight: sendSuccessful.height + 1 });
expect(result.length).toEqual(0);
}
});
});
describe("with SearchByHeightQuery", () => {
it("can search successful tx by height", async () => {
pendingWithoutWasmd();
assert(sendSuccessful, "value must be set in beforeAll()");
const client = new CosmWasmClient(wasmd.endpoint);
const result = await client.searchTx({ height: sendSuccessful.height });
expect(result.length).toEqual(1);
expect(result[0]).toEqual(
jasmine.objectContaining({
height: sendSuccessful.height,
hash: sendSuccessful.hash,
code: 0,
tx: sendSuccessful.tx,
}),
);
});
it("can search unsuccessful tx by height", async () => {
pendingWithoutWasmd();
assert(sendUnsuccessful, "value must be set in beforeAll()");
const client = new CosmWasmClient(wasmd.endpoint);
const result = await client.searchTx({ height: sendUnsuccessful.height });
expect(result.length).toEqual(1);
expect(result[0]).toEqual(
jasmine.objectContaining({
height: sendUnsuccessful.height,
hash: sendUnsuccessful.hash,
code: 5,
tx: sendUnsuccessful.tx,
}),
);
});
});
describe("with SearchBySentFromOrToQuery", () => {
it("can search by sender", async () => {
pendingWithoutWasmd();
assert(sendSuccessful, "value must be set in beforeAll()");
const client = new CosmWasmClient(wasmd.endpoint);
const results = await client.searchTx({ sentFromOrTo: sendSuccessful.sender });
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
for (const result of results) {
const containsMsgWithSender = !!result.tx.value.msg.find(
(msg) => isMsgSend(msg) && msg.value.from_address == sendSuccessful!.sender,
);
const containsMsgWithRecipient = !!result.tx.value.msg.find(
(msg) => isMsgSend(msg) && msg.value.to_address === sendSuccessful!.sender,
);
expect(containsMsgWithSender || containsMsgWithRecipient).toEqual(true);
}
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: sendSuccessful.height,
hash: sendSuccessful.hash,
tx: sendSuccessful.tx,
}),
);
});
it("can search by recipient", async () => {
pendingWithoutWasmd();
assert(sendSuccessful, "value must be set in beforeAll()");
const client = new CosmWasmClient(wasmd.endpoint);
const results = await client.searchTx({ sentFromOrTo: sendSuccessful.recipient });
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
for (const result of results) {
const msg = fromOneElementArray(result.tx.value.msg);
assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`);
expect(
msg.value.to_address === sendSuccessful.recipient ||
msg.value.from_address == sendSuccessful.recipient,
).toEqual(true);
}
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: sendSuccessful.height,
hash: sendSuccessful.hash,
tx: sendSuccessful.tx,
}),
);
});
it("can search by recipient and filter by minHeight", async () => {
pendingWithoutWasmd();
assert(sendSuccessful);
const client = new CosmWasmClient(wasmd.endpoint);
const query = { sentFromOrTo: sendSuccessful.recipient };
{
const result = await client.searchTx(query, { minHeight: 0 });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { minHeight: sendSuccessful.height - 1 });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { minHeight: sendSuccessful.height });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { minHeight: sendSuccessful.height + 1 });
expect(result.length).toEqual(0);
}
});
it("can search by recipient and filter by maxHeight", async () => {
pendingWithoutWasmd();
assert(sendSuccessful);
const client = new CosmWasmClient(wasmd.endpoint);
const query = { sentFromOrTo: sendSuccessful.recipient };
{
const result = await client.searchTx(query, { maxHeight: 9999999999999 });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { maxHeight: sendSuccessful.height + 1 });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { maxHeight: sendSuccessful.height });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { maxHeight: sendSuccessful.height - 1 });
expect(result.length).toEqual(0);
}
});
});
describe("with SearchByTagsQuery", () => {
it("can search by transfer.recipient", async () => {
pendingWithoutWasmd();
assert(sendSuccessful, "value must be set in beforeAll()");
const client = new CosmWasmClient(wasmd.endpoint);
const results = await client.searchTx({
tags: [{ key: "transfer.recipient", value: sendSuccessful.recipient }],
});
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
for (const result of results) {
const msg = fromOneElementArray(result.tx.value.msg);
assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`);
expect(msg.value.to_address).toEqual(sendSuccessful.recipient);
}
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: sendSuccessful.height,
hash: sendSuccessful.hash,
tx: sendSuccessful.tx,
}),
);
});
it("can search by message.contract_address", async () => {
pendingWithoutWasmd();
assert(postedExecute, "value must be set in beforeAll()");
const client = new CosmWasmClient(wasmd.endpoint);
const results = await client.searchTx({
tags: [{ key: "message.contract_address", value: postedExecute.contract }],
});
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
for (const result of results) {
const msg = fromOneElementArray(result.tx.value.msg);
assert(
isMsgExecuteContract(msg) || isMsgInstantiateContract(msg),
`${result.hash} (at ${result.height}) not an execute or instantiate msg`,
);
}
// Check that the first result is the instantiation
const first = fromOneElementArray(results[0].tx.value.msg);
assert(isMsgInstantiateContract(first), "First contract search result must be an instantiation");
expect(first).toEqual({
type: "wasm/instantiate",
value: {
sender: faucet.address,
code_id: deployedErc20.codeId.toString(),
label: "HASH",
init_msg: jasmine.objectContaining({ symbol: "HASH" }),
init_funds: [],
},
});
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: postedExecute.height,
hash: postedExecute.hash,
tx: postedExecute.tx,
}),
);
});
it("can search by message.contract_address + message.action", async () => {
pendingWithoutWasmd();
assert(postedExecute, "value must be set in beforeAll()");
const client = new CosmWasmClient(wasmd.endpoint);
const results = await client.searchTx({
tags: [
{ key: "message.contract_address", value: postedExecute.contract },
{ key: "message.action", value: "execute" },
],
});
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
for (const result of results) {
const msg = fromOneElementArray(result.tx.value.msg);
assert(isMsgExecuteContract(msg), `${result.hash} (at ${result.height}) not an execute msg`);
expect(msg.value.contract).toEqual(postedExecute.contract);
}
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: postedExecute.height,
hash: postedExecute.hash,
tx: postedExecute.tx,
}),
);
});
});
});
-467
View File
@@ -1,467 +0,0 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Sha256 } from "@iov/crypto";
import { Bech32, Encoding } from "@iov/encoding";
import { assert, sleep } from "@iov/utils";
import { ReadonlyDate } from "readonly-date";
import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient";
import { makeSignBytes } from "./encoding";
import { findAttribute } from "./logs";
import { Secp256k1Pen } from "./pen";
import { SigningCosmWasmClient } from "./signingcosmwasmclient";
import cosmoshub from "./testdata/cosmoshub.json";
import {
deployedErc20,
faucet,
getHackatom,
makeRandomAddress,
pendingWithoutWasmd,
tendermintIdMatcher,
unused,
wasmd,
wasmdEnabled,
} from "./testutils.spec";
import { MsgSend, StdFee } from "./types";
const { fromHex, fromUtf8, toAscii, toBase64 } = Encoding;
const guest = {
address: "cosmos17d0jcz59jf68g52vq38tuuncmwwjk42u6mcxej",
};
interface HackatomInstance {
readonly initMsg: {
readonly verifier: string;
readonly beneficiary: string;
};
readonly address: string;
}
describe("CosmWasmClient", () => {
describe("makeReadOnly", () => {
it("can be constructed", () => {
const client = new CosmWasmClient(wasmd.endpoint);
expect(client).toBeTruthy();
});
});
describe("getChainId", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
expect(await client.getChainId()).toEqual(wasmd.chainId);
});
it("caches chain ID", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const openedClient = (client as unknown) as PrivateCosmWasmClient;
const getCodeSpy = spyOn(openedClient.restClient, "nodeInfo").and.callThrough();
expect(await client.getChainId()).toEqual(wasmd.chainId); // from network
expect(await client.getChainId()).toEqual(wasmd.chainId); // from cache
expect(getCodeSpy).toHaveBeenCalledTimes(1);
});
});
describe("getHeight", () => {
it("gets height via last block", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const openedClient = (client as unknown) as PrivateCosmWasmClient;
const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough();
const height1 = await client.getHeight();
expect(height1).toBeGreaterThan(0);
await sleep(1_000);
const height2 = await client.getHeight();
expect(height2).toEqual(height1 + 1);
expect(blockLatestSpy).toHaveBeenCalledTimes(2);
});
it("gets height via authAccount once an address is known", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const openedClient = (client as unknown) as PrivateCosmWasmClient;
const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough();
const authAccountsSpy = spyOn(openedClient.restClient, "authAccounts").and.callThrough();
const height1 = await client.getHeight();
expect(height1).toBeGreaterThan(0);
await client.getCodes(); // warm up the client
const height2 = await client.getHeight();
expect(height2).toBeGreaterThan(0);
await sleep(1_000);
const height3 = await client.getHeight();
expect(height3).toEqual(height2 + 1);
expect(blockLatestSpy).toHaveBeenCalledTimes(1);
expect(authAccountsSpy).toHaveBeenCalledTimes(2);
});
});
describe("getNonce", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
expect(await client.getNonce(unused.address)).toEqual({
accountNumber: unused.accountNumber,
sequence: unused.sequence,
});
});
it("throws for missing accounts", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const missing = makeRandomAddress();
await client.getNonce(missing).then(
() => fail("this must not succeed"),
(error) => expect(error).toMatch(/account does not exist on chain/i),
);
});
});
describe("getAccount", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
expect(await client.getAccount(unused.address)).toEqual({
address: unused.address,
accountNumber: unused.accountNumber,
sequence: unused.sequence,
pubkey: undefined,
balance: [
{ denom: "ucosm", amount: "1000000000" },
{ denom: "ustake", amount: "1000000000" },
],
});
});
it("returns undefined for missing accounts", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const missing = makeRandomAddress();
expect(await client.getAccount(missing)).toBeUndefined();
});
});
describe("getBlock", () => {
it("works for latest block", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const response = await client.getBlock();
// id
expect(response.id).toMatch(tendermintIdMatcher);
// header
expect(response.header.height).toBeGreaterThanOrEqual(1);
expect(response.header.chainId).toEqual(await client.getChainId());
expect(new ReadonlyDate(response.header.time).getTime()).toBeLessThan(ReadonlyDate.now());
expect(new ReadonlyDate(response.header.time).getTime()).toBeGreaterThanOrEqual(
ReadonlyDate.now() - 5_000,
);
// txs
expect(Array.isArray(response.txs)).toEqual(true);
});
it("works for block by height", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const height = (await client.getBlock()).header.height;
const response = await client.getBlock(height - 1);
// id
expect(response.id).toMatch(tendermintIdMatcher);
// header
expect(response.header.height).toEqual(height - 1);
expect(response.header.chainId).toEqual(await client.getChainId());
expect(new ReadonlyDate(response.header.time).getTime()).toBeLessThan(ReadonlyDate.now());
expect(new ReadonlyDate(response.header.time).getTime()).toBeGreaterThanOrEqual(
ReadonlyDate.now() - 5_000,
);
// txs
expect(Array.isArray(response.txs)).toEqual(true);
});
});
describe("getIdentifier", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
expect(await client.getIdentifier(cosmoshub.tx)).toEqual(cosmoshub.id);
});
});
describe("postTx", () => {
it("works", async () => {
pendingWithoutWasmd();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new CosmWasmClient(wasmd.endpoint);
const memo = "My first contract on chain";
const sendMsg: MsgSend = {
type: "cosmos-sdk/MsgSend",
value: {
from_address: faucet.address,
to_address: makeRandomAddress(),
amount: [
{
denom: "ucosm",
amount: "1234567",
},
],
},
};
const fee: StdFee = {
amount: [
{
amount: "5000",
denom: "ucosm",
},
],
gas: "890000",
};
const chainId = await client.getChainId();
const { accountNumber, sequence } = await client.getNonce(faucet.address);
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await pen.sign(signBytes);
const signedTx = {
msg: [sendMsg],
fee: fee,
memo: memo,
signatures: [signature],
};
const { logs, transactionHash } = await client.postTx(signedTx);
const amountAttr = findAttribute(logs, "transfer", "amount");
expect(amountAttr.value).toEqual("1234567ucosm");
expect(transactionHash).toMatch(/^[0-9A-F]{64}$/);
});
});
describe("getCodes", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const result = await client.getCodes();
expect(result.length).toBeGreaterThanOrEqual(1);
const [first] = result;
expect(first).toEqual({
id: deployedErc20.codeId,
source: deployedErc20.source,
builder: deployedErc20.builder,
checksum: deployedErc20.checksum,
creator: faucet.address,
});
});
});
describe("getCodeDetails", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const result = await client.getCodeDetails(1);
const expectedInfo: Code = {
id: deployedErc20.codeId,
source: deployedErc20.source,
builder: deployedErc20.builder,
checksum: deployedErc20.checksum,
creator: faucet.address,
};
// check info
expect(result).toEqual(jasmine.objectContaining(expectedInfo));
// check data
expect(new Sha256(result.data).digest()).toEqual(fromHex(expectedInfo.checksum));
});
it("caches downloads", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const openedClient = (client as unknown) as PrivateCosmWasmClient;
const getCodeSpy = spyOn(openedClient.restClient, "getCode").and.callThrough();
const result1 = await client.getCodeDetails(deployedErc20.codeId); // from network
const result2 = await client.getCodeDetails(deployedErc20.codeId); // from cache
expect(result2).toEqual(result1);
expect(getCodeSpy).toHaveBeenCalledTimes(1);
});
});
describe("getContracts", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const result = await client.getContracts(1);
expect(result.length).toBeGreaterThanOrEqual(3);
const [hash, isa, jade] = result;
expect(hash).toEqual({
address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
codeId: 1,
creator: faucet.address,
label: "HASH",
});
expect(isa).toEqual({
address: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
codeId: 1,
creator: faucet.address,
label: "ISA",
});
expect(jade).toEqual({
address: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
codeId: 1,
creator: faucet.address,
label: "JADE",
});
});
});
describe("getContract", () => {
it("works for HASH instance", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
const hash = await client.getContract("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5");
expect(hash).toEqual({
address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
codeId: 1,
creator: faucet.address,
label: "HASH",
initMsg: {
decimals: 5,
name: "Hash token",
symbol: "HASH",
initial_balances: jasmine.arrayContaining([
{
address: faucet.address,
amount: "11",
},
{
address: unused.address,
amount: "12812345",
},
{
address: guest.address,
amount: "22004000000",
},
]),
},
});
});
});
describe("queryContractRaw", () => {
const configKey = toAscii("config");
const otherKey = toAscii("this_does_not_exist");
let contract: HackatomInstance | undefined;
beforeAll(async () => {
if (wasmdEnabled()) {
pendingWithoutWasmd();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) =>
pen.sign(signBytes),
);
const { codeId } = await client.upload(getHackatom());
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
const { contractAddress } = await client.instantiate(codeId, initMsg, "random hackatom");
contract = { initMsg: initMsg, address: contractAddress };
}
});
it("can query existing key", async () => {
pendingWithoutWasmd();
assert(contract);
const client = new CosmWasmClient(wasmd.endpoint);
const raw = await client.queryContractRaw(contract.address, configKey);
assert(raw, "must get result");
expect(JSON.parse(fromUtf8(raw))).toEqual({
verifier: toBase64(Bech32.decode(contract.initMsg.verifier).data),
beneficiary: toBase64(Bech32.decode(contract.initMsg.beneficiary).data),
funder: toBase64(Bech32.decode(faucet.address).data),
});
});
it("can query non-existent key", async () => {
pendingWithoutWasmd();
assert(contract);
const client = new CosmWasmClient(wasmd.endpoint);
const raw = await client.queryContractRaw(contract.address, otherKey);
expect(raw).toBeNull();
});
it("errors for non-existent contract", async () => {
pendingWithoutWasmd();
assert(contract);
const nonExistentAddress = makeRandomAddress();
const client = new CosmWasmClient(wasmd.endpoint);
await client.queryContractRaw(nonExistentAddress, configKey).then(
() => fail("must not succeed"),
(error) => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`),
);
});
});
describe("queryContractSmart", () => {
let contract: HackatomInstance | undefined;
beforeAll(async () => {
if (wasmdEnabled()) {
pendingWithoutWasmd();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) =>
pen.sign(signBytes),
);
const { codeId } = await client.upload(getHackatom());
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
const { contractAddress } = await client.instantiate(codeId, initMsg, "a different hackatom");
contract = { initMsg: initMsg, address: contractAddress };
}
});
it("works", async () => {
pendingWithoutWasmd();
assert(contract);
const client = new CosmWasmClient(wasmd.endpoint);
const resultDocument = await client.queryContractSmart(contract.address, { verifier: {} });
expect(resultDocument).toEqual({ verifier: contract.initMsg.verifier });
});
it("errors for malformed query message", async () => {
pendingWithoutWasmd();
assert(contract);
const client = new CosmWasmClient(wasmd.endpoint);
await client.queryContractSmart(contract.address, { broken: {} }).then(
() => fail("must not succeed"),
(error) => expect(error).toMatch(/query wasm contract failed: parsing hackatom::contract::QueryMsg/i),
);
});
it("errors for non-existent contract", async () => {
pendingWithoutWasmd();
const nonExistentAddress = makeRandomAddress();
const client = new CosmWasmClient(wasmd.endpoint);
await client.queryContractSmart(nonExistentAddress, { verifier: {} }).then(
() => fail("must not succeed"),
(error) => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`),
);
});
});
});
-439
View File
@@ -1,439 +0,0 @@
import { Sha256 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { Coin } from "./coins";
import { Log, parseLogs } from "./logs";
import { decodeBech32Pubkey } from "./pubkey";
import { BroadcastMode, RestClient } from "./restclient";
import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types";
export interface GetNonceResult {
readonly accountNumber: number;
readonly sequence: number;
}
export interface Account {
/** Bech32 account address */
readonly address: string;
readonly balance: ReadonlyArray<Coin>;
readonly pubkey: PubKey | undefined;
readonly accountNumber: number;
readonly sequence: number;
}
export interface PostTxResult {
readonly logs: readonly Log[];
readonly rawLog: string;
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export interface SearchByIdQuery {
readonly id: string;
}
export interface SearchByHeightQuery {
readonly height: number;
}
export interface SearchBySentFromOrToQuery {
readonly sentFromOrTo: string;
}
/**
* This query type allows you to pass arbitrary key/value pairs to the backend. It is
* more powerful and slightly lower level than the other search options.
*/
export interface SearchByTagsQuery {
readonly tags: readonly { readonly key: string; readonly value: string }[];
}
export type SearchTxQuery =
| SearchByIdQuery
| SearchByHeightQuery
| SearchBySentFromOrToQuery
| SearchByTagsQuery;
function isSearchByIdQuery(query: SearchTxQuery): query is SearchByIdQuery {
return (query as SearchByIdQuery).id !== undefined;
}
function isSearchByHeightQuery(query: SearchTxQuery): query is SearchByHeightQuery {
return (query as SearchByHeightQuery).height !== undefined;
}
function isSearchBySentFromOrToQuery(query: SearchTxQuery): query is SearchBySentFromOrToQuery {
return (query as SearchBySentFromOrToQuery).sentFromOrTo !== undefined;
}
function isSearchByTagsQuery(query: SearchTxQuery): query is SearchByTagsQuery {
return (query as SearchByTagsQuery).tags !== undefined;
}
export interface SearchTxFilter {
readonly minHeight?: number;
readonly maxHeight?: number;
}
export interface Code {
readonly id: number;
/** Bech32 account address */
readonly creator: string;
/** Hex-encoded sha256 hash of the code stored here */
readonly checksum: string;
readonly source?: string;
readonly builder?: string;
}
export interface CodeDetails extends Code {
/** The original wasm bytes */
readonly data: Uint8Array;
}
export interface Contract {
readonly address: string;
readonly codeId: number;
/** Bech32 account address */
readonly creator: string;
readonly label: string;
}
export interface ContractDetails extends Contract {
/** Argument passed on initialization of the contract */
readonly initMsg: object;
}
/** A transaction that is indexed as part of the transaction history */
export interface IndexedTx {
readonly height: number;
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly hash: string;
/** Transaction execution error code. 0 on success. */
readonly code: number;
readonly rawLog: string;
readonly logs: readonly Log[];
readonly tx: CosmosSdkTx;
/** The gas limit as set by the user */
readonly gasWanted?: number;
/** The gas used by the execution */
readonly gasUsed?: number;
/** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */
readonly timestamp: string;
}
export interface BlockHeader {
readonly version: {
readonly block: string;
readonly app: string;
};
readonly height: number;
readonly chainId: string;
/** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */
readonly time: string;
}
export interface Block {
/** The ID is a hash of the block header (uppercase hex) */
readonly id: string;
readonly header: BlockHeader;
/** Array of raw transactions */
readonly txs: ReadonlyArray<Uint8Array>;
}
/** Use for testing only */
export interface PrivateCosmWasmClient {
readonly restClient: RestClient;
}
export class CosmWasmClient {
protected readonly restClient: RestClient;
/** Any address the chain considers valid (valid bech32 with proper prefix) */
protected anyValidAddress: string | undefined;
private readonly codesCache = new Map<number, CodeDetails>();
private chainId: string | undefined;
/**
* Creates a new client to interact with a CosmWasm blockchain.
*
* This instance does a lot of caching. In order to benefit from that you should try to use one instance
* for the lifetime of your application. When switching backends, a new instance must be created.
*
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
* @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns
*/
public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) {
this.restClient = new RestClient(apiUrl, broadcastMode);
}
public async getChainId(): Promise<string> {
if (!this.chainId) {
const response = await this.restClient.nodeInfo();
const chainId = response.node_info.network;
if (!chainId) throw new Error("Chain ID must not be empty");
this.chainId = chainId;
}
return this.chainId;
}
public async getHeight(): Promise<number> {
if (this.anyValidAddress) {
const { height } = await this.restClient.authAccounts(this.anyValidAddress);
return parseInt(height, 10);
} else {
// Note: this gets inefficient when blocks contain a lot of transactions since it
// requires downloading and deserializing all transactions in the block.
const latest = await this.restClient.blocksLatest();
return parseInt(latest.block.header.height, 10);
}
}
/**
* Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID)
*/
public async getIdentifier(tx: CosmosSdkTx): Promise<string> {
// We consult the REST API because we don't have a local amino encoder
const bytes = await this.restClient.encodeTx(tx);
const hash = new Sha256(bytes).digest();
return Encoding.toHex(hash).toUpperCase();
}
/**
* Returns account number and sequence.
*
* Throws if the account does not exist on chain.
*
* @param address returns data for this address. When unset, the client's sender adddress is used.
*/
public async getNonce(address: string): Promise<GetNonceResult> {
const account = await this.getAccount(address);
if (!account) {
throw new Error(
"Account does not exist on chain. Send some tokens there before trying to query nonces.",
);
}
return {
accountNumber: account.accountNumber,
sequence: account.sequence,
};
}
public async getAccount(address: string): Promise<Account | undefined> {
const account = await this.restClient.authAccounts(address);
const value = account.result.value;
if (value.address === "") {
return undefined;
} else {
this.anyValidAddress = value.address;
return {
address: value.address,
balance: value.coins,
pubkey: value.public_key ? decodeBech32Pubkey(value.public_key) : undefined,
accountNumber: value.account_number,
sequence: value.sequence,
};
}
}
/**
* Gets block header and meta
*
* @param height The height of the block. If undefined, the latest height is used.
*/
public async getBlock(height?: number): Promise<Block> {
const response =
height !== undefined ? await this.restClient.blocks(height) : await this.restClient.blocksLatest();
return {
id: response.block_id.hash,
header: {
version: response.block.header.version,
time: response.block.header.time,
height: parseInt(response.block.header.height, 10),
chainId: response.block.header.chain_id,
},
txs: (response.block.data.txs || []).map((encoded) => Encoding.fromBase64(encoded)),
};
}
public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise<readonly IndexedTx[]> {
const minHeight = filter.minHeight || 0;
const maxHeight = filter.maxHeight || Number.MAX_SAFE_INTEGER;
if (maxHeight < minHeight) return []; // optional optimization
function withFilters(originalQuery: string): string {
return `${originalQuery}&tx.minheight=${minHeight}&tx.maxheight=${maxHeight}`;
}
let txs: readonly IndexedTx[];
if (isSearchByIdQuery(query)) {
txs = await this.txsQuery(`tx.hash=${query.id}`);
} else if (isSearchByHeightQuery(query)) {
// optional optimization to avoid network request
if (query.height < minHeight || query.height > maxHeight) {
txs = [];
} else {
txs = await this.txsQuery(`tx.height=${query.height}`);
}
} else if (isSearchBySentFromOrToQuery(query)) {
// We cannot get both in one request (see https://github.com/cosmos/gaia/issues/75)
const sentQuery = withFilters(`message.module=bank&message.sender=${query.sentFromOrTo}`);
const receivedQuery = withFilters(`message.module=bank&transfer.recipient=${query.sentFromOrTo}`);
const sent = await this.txsQuery(sentQuery);
const received = await this.txsQuery(receivedQuery);
const sentHashes = sent.map((t) => t.hash);
txs = [...sent, ...received.filter((t) => !sentHashes.includes(t.hash))];
} else if (isSearchByTagsQuery(query)) {
const rawQuery = withFilters(query.tags.map((t) => `${t.key}=${t.value}`).join("&"));
txs = await this.txsQuery(rawQuery);
} else {
throw new Error("Unknown query type");
}
// backend sometimes messes up with min/max height filtering
const filtered = txs.filter((tx) => tx.height >= minHeight && tx.height <= maxHeight);
return filtered;
}
public async postTx(tx: StdTx): Promise<PostTxResult> {
const result = await this.restClient.postTx(tx);
if (!result.txhash.match(/^([0-9A-F][0-9A-F])+$/)) {
throw new Error("Received ill-formatted txhash. Must be non-empty upper-case hex");
}
if (result.code) {
throw new Error(
`Error when posting tx ${result.txhash}. Code: ${result.code}; Raw log: ${result.raw_log}`,
);
}
return {
logs: result.logs ? parseLogs(result.logs) : [],
rawLog: result.raw_log || "",
transactionHash: result.txhash,
};
}
public async getCodes(): Promise<readonly Code[]> {
const result = await this.restClient.listCodeInfo();
return result.map(
(entry): Code => {
this.anyValidAddress = entry.creator;
return {
id: entry.id,
creator: entry.creator,
checksum: Encoding.toHex(Encoding.fromHex(entry.data_hash)),
source: entry.source || undefined,
builder: entry.builder || undefined,
};
},
);
}
public async getCodeDetails(codeId: number): Promise<CodeDetails> {
const cached = this.codesCache.get(codeId);
if (cached) return cached;
const getCodeResult = await this.restClient.getCode(codeId);
const codeDetails: CodeDetails = {
id: getCodeResult.id,
creator: getCodeResult.creator,
checksum: Encoding.toHex(Encoding.fromHex(getCodeResult.data_hash)),
source: getCodeResult.source || undefined,
builder: getCodeResult.builder || undefined,
data: Encoding.fromBase64(getCodeResult.data),
};
this.codesCache.set(codeId, codeDetails);
return codeDetails;
}
public async getContracts(codeId: number): Promise<readonly Contract[]> {
const result = await this.restClient.listContractsByCodeId(codeId);
return result.map(
(entry): Contract => ({
address: entry.address,
codeId: entry.code_id,
creator: entry.creator,
label: entry.label,
}),
);
}
/**
* Throws an error if no contract was found at the address
*/
public async getContract(address: string): Promise<ContractDetails> {
const result = await this.restClient.getContractInfo(address);
if (!result) throw new Error(`No contract found at address "${address}"`);
return {
address: result.address,
codeId: result.code_id,
creator: result.creator,
label: result.label,
initMsg: result.init_msg,
};
}
/**
* Returns the data at the key if present (raw contract dependent storage data)
* or null if no data at this key.
*
* Promise is rejected when contract does not exist.
*/
public async queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null> {
// just test contract existence
const _info = await this.getContract(address);
return this.restClient.queryContractRaw(address, key);
}
/**
* Makes a smart query on the contract, returns the parsed JSON document.
*
* Promise is rejected when contract does not exist.
* Promise is rejected for invalid query format.
* Promise is rejected for invalid response format.
*/
public async queryContractSmart(address: string, queryMsg: object): Promise<JsonObject> {
try {
return await this.restClient.queryContractSmart(address, queryMsg);
} catch (error) {
if (error instanceof Error) {
if (error.message.startsWith("not found: contract")) {
throw new Error(`No contract found at address "${address}"`);
} else {
throw error;
}
} else {
throw error;
}
}
}
private async txsQuery(query: string): Promise<readonly IndexedTx[]> {
// TODO: we need proper pagination support
const limit = 100;
const result = await this.restClient.txsQuery(`${query}&limit=${limit}`);
const pages = parseInt(result.page_total, 10);
if (pages > 1) {
throw new Error(
`Found more results on the backend than we can process currently. Results: ${result.total_count}, supported: ${limit}`,
);
}
return result.txs.map(
(restItem): IndexedTx => ({
height: parseInt(restItem.height, 10),
hash: restItem.txhash,
code: restItem.code || 0,
rawLog: restItem.raw_log,
logs: parseLogs(restItem.logs || []),
tx: restItem.tx,
timestamp: restItem.timestamp,
}),
);
}
}
-11
View File
@@ -1,11 +0,0 @@
import { Encoding } from "@iov/encoding";
import { isStdTx, StdTx } from "./types";
export function unmarshalTx(data: Uint8Array): StdTx {
const decoded = JSON.parse(Encoding.fromUtf8(data));
if (!isStdTx(decoded)) {
throw new Error("Must be json encoded StdTx");
}
return decoded;
}
-1
View File
@@ -1 +0,0 @@
describe("encoding", () => {});
-59
View File
@@ -1,59 +0,0 @@
import { Encoding } from "@iov/encoding";
import { Msg, StdFee, StdTx } from "./types";
const { toUtf8 } = Encoding;
function sortJson(json: any): any {
if (typeof json !== "object" || json === null) {
return json;
}
if (Array.isArray(json)) {
return json.map(sortJson);
}
const sortedKeys = Object.keys(json).sort();
const result = sortedKeys.reduce(
(accumulator, key) => ({
...accumulator,
[key]: sortJson(json[key]),
}),
{},
);
return result;
}
export function marshalTx(tx: StdTx): Uint8Array {
const json = JSON.stringify(tx);
return Encoding.toUtf8(json);
}
interface SignJson {
readonly account_number: string;
readonly chain_id: string;
readonly fee: StdFee;
readonly memo: string;
readonly msgs: readonly Msg[];
readonly sequence: string;
}
export function makeSignBytes(
msgs: readonly Msg[],
fee: StdFee,
chainId: string,
memo: string,
accountNumber: number,
sequence: number,
): Uint8Array {
const signJson: SignJson = {
// eslint-disable-next-line @typescript-eslint/camelcase
account_number: accountNumber.toString(),
// eslint-disable-next-line @typescript-eslint/camelcase
chain_id: chainId,
fee: fee,
memo: memo,
msgs: msgs,
sequence: sequence.toString(),
};
const signMsg = sortJson(signJson);
return toUtf8(JSON.stringify(signMsg));
}
-41
View File
@@ -1,41 +0,0 @@
import * as logs from "./logs";
import * as types from "./types";
export { logs, types };
export { pubkeyToAddress } from "./address";
export { Coin, coin, coins } from "./coins";
export { unmarshalTx } from "./decoding";
export { makeSignBytes, marshalTx } from "./encoding";
export { BroadcastMode, RestClient, TxsResponse } from "./restclient";
export {
Account,
Block,
BlockHeader,
Code,
CodeDetails,
Contract,
ContractDetails,
CosmWasmClient,
GetNonceResult,
IndexedTx,
PostTxResult,
SearchByHeightQuery,
SearchByIdQuery,
SearchBySentFromOrToQuery,
SearchByTagsQuery,
SearchTxQuery,
SearchTxFilter,
} from "./cosmwasmclient";
export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen";
export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey";
export { findSequenceForSignedTx } from "./sequence";
export { encodeSecp256k1Signature, decodeSignature } from "./signature";
export {
ExecuteResult,
FeeTable,
InstantiateResult,
SigningCallback,
SigningCosmWasmClient,
UploadMeta,
UploadResult,
} from "./signingcosmwasmclient";
-165
View File
@@ -1,165 +0,0 @@
/* eslint-disable @typescript-eslint/camelcase */
import { parseAttribute, parseEvent, parseLog, parseLogs } from "./logs";
describe("logs", () => {
describe("parseAttribute", () => {
it("works", () => {
const attr = parseAttribute({ key: "a", value: "b" });
expect(attr).toEqual({ key: "a", value: "b" });
});
it("works for empty value", () => {
const attr = parseAttribute({ key: "foobar", value: "" });
expect(attr).toEqual({ key: "foobar", value: "" });
});
it("normalized unset value to empty string", () => {
const attr = parseAttribute({ key: "amount" });
expect(attr).toEqual({ key: "amount", value: "" });
});
});
describe("parseEvent", () => {
it("works", () => {
const original = {
type: "message",
attributes: [
{
key: "action",
value: "store-code",
},
{
key: "module",
value: "wasm",
},
{
key: "action",
value: "store-code",
},
{
key: "sender",
value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
},
{
key: "code_id",
value: "1",
},
],
} as const;
const event = parseEvent(original);
expect(event).toEqual(original);
});
it("works for transfer event", () => {
const original = {
type: "transfer",
attributes: [
{
key: "recipient",
value: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
},
{
key: "amount",
},
],
} as const;
const expected = {
type: "transfer",
attributes: [
{
key: "recipient",
value: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
},
{
key: "amount",
value: "",
},
],
} as const;
const event = parseEvent(original);
expect(event).toEqual(expected);
});
});
describe("parseLog", () => {
it("works", () => {
const original = {
msg_index: 0,
log: "",
events: [
{
type: "message",
attributes: [
{
key: "action",
value: "store-code",
},
{
key: "module",
value: "wasm",
},
{
key: "action",
value: "store-code",
},
{
key: "sender",
value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
},
{
key: "code_id",
value: "1",
},
],
},
],
} as const;
const log = parseLog(original);
expect(log).toEqual(original);
});
});
describe("parseLogs", () => {
it("works", () => {
const original = [
{
msg_index: 0,
log: "",
events: [
{
type: "message",
attributes: [
{
key: "action",
value: "store-code",
},
{
key: "module",
value: "wasm",
},
{
key: "action",
value: "store-code",
},
{
key: "sender",
value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
},
{
key: "code_id",
value: "1",
},
],
},
],
},
] as const;
const logs = parseLogs(original);
expect(logs).toEqual(original);
});
});
});
-86
View File
@@ -1,86 +0,0 @@
/* eslint-disable @typescript-eslint/camelcase */
import { isNonNullObject } from "@iov/encoding";
export interface Attribute {
readonly key: string;
readonly value: string;
}
export interface Event {
readonly type: string;
readonly attributes: readonly Attribute[];
}
export interface Log {
readonly msg_index: number;
readonly log: string;
readonly events: readonly Event[];
}
export function parseAttribute(input: unknown): Attribute {
if (!isNonNullObject(input)) throw new Error("Attribute must be a non-null object");
const { key, value } = input as any;
if (typeof key !== "string" || !key) throw new Error("Attribute's key must be a non-empty string");
if (typeof value !== "string" && typeof value !== "undefined") {
throw new Error("Attribute's value must be a string or unset");
}
return {
key: key,
value: value || "",
};
}
export function parseEvent(input: unknown): Event {
if (!isNonNullObject(input)) throw new Error("Event must be a non-null object");
const { type, attributes } = input as any;
if (typeof type !== "string" || type === "") {
throw new Error(`Event type must be a non-empty string`);
}
if (!Array.isArray(attributes)) throw new Error("Event's attributes must be an array");
return {
type: type,
attributes: attributes.map(parseAttribute),
};
}
export function parseLog(input: unknown): Log {
if (!isNonNullObject(input)) throw new Error("Log must be a non-null object");
const { msg_index, log, events } = input as any;
if (typeof msg_index !== "number") throw new Error("Log's msg_index must be a number");
if (typeof log !== "string") throw new Error("Log's log must be a string");
if (!Array.isArray(events)) throw new Error("Log's events must be an array");
return {
msg_index: msg_index,
log: log,
events: events.map(parseEvent),
};
}
export function parseLogs(input: unknown): readonly Log[] {
if (!Array.isArray(input)) throw new Error("Logs must be an array");
return input.map(parseLog);
}
/**
* Searches in logs for the first event of the given event type and in that event
* for the first first attribute with the given attribute key.
*
* Throws if the attribute was not found.
*/
export function findAttribute(
logs: readonly Log[],
eventType: "message" | "transfer",
attrKey: string,
): Attribute {
const firstLogs = logs.find(() => true);
const out = firstLogs?.events
.find((event) => event.type === eventType)
?.attributes.find((attr) => attr.key === attrKey);
if (!out) {
throw new Error(
`Could not find attribute '${attrKey}' in first event of type '${eventType}' in first log.`,
);
}
return out;
}
-56
View File
@@ -1,56 +0,0 @@
import { Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { Secp256k1Pen } from "./pen";
import { decodeSignature } from "./signature";
const { fromHex } = Encoding;
describe("Sec256k1Pen", () => {
it("can be constructed", async () => {
const pen = await Secp256k1Pen.fromMnemonic(
"zebra slush diet army arrest purpose hawk source west glimpse custom record",
);
expect(pen).toBeTruthy();
});
describe("pubkey", () => {
it("returns compressed pubkey", async () => {
// special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling
// m/44'/118'/0'/0/0
// pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6
const pen = await Secp256k1Pen.fromMnemonic(
"special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling",
);
expect(pen.pubkey).toEqual(
fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"),
);
});
});
describe("sign", () => {
it("creates correct signatures", async () => {
const pen = await Secp256k1Pen.fromMnemonic(
"special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling",
);
const data = Encoding.toAscii("foo bar");
const { pubkey, signature } = decodeSignature(await pen.sign(data));
const valid = await Secp256k1.verifySignature(
Secp256k1Signature.fromFixedLength(signature),
new Sha256(data).digest(),
pubkey,
);
expect(valid).toEqual(true);
});
});
describe("address", () => {
it("creates same address as Go imlementation", async () => {
const pen = await Secp256k1Pen.fromMnemonic(
"oyster design unusual machine spread century engine gravity focus cave carry slot",
);
expect(pen.address("cosmos")).toEqual("cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u");
});
});
});
-92
View File
@@ -1,92 +0,0 @@
import {
Bip39,
EnglishMnemonic,
Secp256k1,
Sha256,
Sha512,
Slip10,
Slip10Curve,
Slip10RawIndex,
} from "@iov/crypto";
import { rawSecp256k1PubkeyToAddress } from "./address";
import { encodeSecp256k1Signature } from "./signature";
import { StdSignature } from "./types";
export type PrehashType = "sha256" | "sha512" | null;
/**
* A pen is the most basic tool you can think of for signing. It works
* everywhere and can be used intuitively by everyone. However, it does not
* come with a great amount of features. End of semi suitable metaphor.
*
* This wraps a single keypair and allows for signing.
*
* Non-goals of this types are: multi account support, persistency, data migrations,
* obfuscation of sensitive data.
*/
export interface Pen {
readonly pubkey: Uint8Array;
readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<StdSignature>;
}
function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array {
switch (type) {
case null:
return new Uint8Array([...bytes]);
case "sha256":
return new Sha256(bytes).digest();
case "sha512":
return new Sha512(bytes).digest();
default:
throw new Error("Unknown prehash type");
}
}
/**
* The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a`
* with 0-based account index `a`.
*/
export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] {
return [
Slip10RawIndex.hardened(44),
Slip10RawIndex.hardened(118),
Slip10RawIndex.hardened(0),
Slip10RawIndex.normal(0),
Slip10RawIndex.normal(a),
];
}
export class Secp256k1Pen implements Pen {
public static async fromMnemonic(
mnemonic: string,
hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0),
): Promise<Secp256k1Pen> {
const seed = await Bip39.mnemonicToSeed(new EnglishMnemonic(mnemonic));
const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath);
const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey;
return new Secp256k1Pen(privkey, Secp256k1.compressPubkey(uncompressed));
}
public readonly pubkey: Uint8Array;
private readonly privkey: Uint8Array;
private constructor(privkey: Uint8Array, pubkey: Uint8Array) {
this.privkey = privkey;
this.pubkey = pubkey;
}
/**
* Creates and returns a signature
*/
public async sign(signBytes: Uint8Array, prehashType: PrehashType = "sha256"): Promise<StdSignature> {
const message = prehash(signBytes, prehashType);
const signature = await Secp256k1.createSignature(message, this.privkey);
const fixedLengthSignature = new Uint8Array([...signature.r(32), ...signature.s(32)]);
return encodeSecp256k1Signature(this.pubkey, fixedLengthSignature);
}
public address(prefix: string): string {
return rawSecp256k1PubkeyToAddress(this.pubkey, prefix);
}
}
-57
View File
@@ -1,57 +0,0 @@
import { Encoding } from "@iov/encoding";
import { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey";
import { PubKey } from "./types";
const { fromBase64 } = Encoding;
describe("pubkey", () => {
describe("encodeSecp256k1Pubkey", () => {
it("encodes a compresed pubkey", () => {
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
expect(encodeSecp256k1Pubkey(pubkey)).toEqual({
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
});
});
it("throws for uncompressed public keys", () => {
const pubkey = fromBase64(
"BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=",
);
expect(() => encodeSecp256k1Pubkey(pubkey)).toThrowError(/public key must be compressed secp256k1/i);
});
});
describe("decodeBech32Pubkey", () => {
it("works", () => {
expect(
decodeBech32Pubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"),
).toEqual({
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
});
});
it("works for enigma pubkey", () => {
expect(
decodeBech32Pubkey("enigmapub1addwnpepqw5k9p439nw0zpg2aundx4umwx4nw233z5prpjqjv5anl5grmnchzp2xwvv"),
).toEqual({
type: "tendermint/PubKeySecp256k1",
value: "A6lihrEs3PEFCu8m01ebcas3KjEVAjDIEmU7P9ED3PFx",
});
});
});
describe("encodeBech32Pubkey", () => {
it("works for secp256k1", () => {
const pubkey: PubKey = {
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
};
expect(encodeBech32Pubkey(pubkey, "cosmospub")).toEqual(
"cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5",
);
});
});
});
-71
View File
@@ -1,71 +0,0 @@
import { Bech32, Encoding } from "@iov/encoding";
import equal from "fast-deep-equal";
import { PubKey, pubkeyType } from "./types";
export function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey {
if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) {
throw new Error("Public key must be compressed secp256k1, i.e. 33 bytes starting with 0x02 or 0x03");
}
return {
type: pubkeyType.secp256k1,
value: Encoding.toBase64(pubkey),
};
}
// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163
// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography
// Last bytes is varint-encoded length prefix
const pubkeyAminoPrefixSecp256k1 = Encoding.fromHex("eb5ae98721");
const pubkeyAminoPrefixEd25519 = Encoding.fromHex("1624de6420");
const pubkeyAminoPrefixSr25519 = Encoding.fromHex("0dfb1005");
const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length;
export function decodeBech32Pubkey(bechEncoded: string): PubKey {
const { data } = Bech32.decode(bechEncoded);
const aminoPrefix = data.slice(0, pubkeyAminoPrefixLength);
const rest = data.slice(pubkeyAminoPrefixLength);
if (equal(aminoPrefix, pubkeyAminoPrefixSecp256k1)) {
if (rest.length !== 33) {
throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey).");
}
return {
type: pubkeyType.secp256k1,
value: Encoding.toBase64(rest),
};
} else if (equal(aminoPrefix, pubkeyAminoPrefixEd25519)) {
if (rest.length !== 32) {
throw new Error("Invalid rest data length. Expected 32 bytes (Ed25519 pubkey).");
}
return {
type: pubkeyType.ed25519,
value: Encoding.toBase64(rest),
};
} else if (equal(aminoPrefix, pubkeyAminoPrefixSr25519)) {
if (rest.length !== 32) {
throw new Error("Invalid rest data length. Expected 32 bytes (Sr25519 pubkey).");
}
return {
type: pubkeyType.sr25519,
value: Encoding.toBase64(rest),
};
} else {
throw new Error("Unsupported Pubkey type. Amino prefix: " + Encoding.toHex(aminoPrefix));
}
}
export function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string {
let aminoPrefix: Uint8Array;
switch (pubkey.type) {
// Note: please don't add cases here without writing additional unit tests
case pubkeyType.secp256k1:
aminoPrefix = pubkeyAminoPrefixSecp256k1;
break;
default:
throw new Error("Unsupported pubkey type");
}
const data = new Uint8Array([...aminoPrefix, ...Encoding.fromBase64(pubkey.value)]);
return Bech32.encode(prefix, data);
}
File diff suppressed because it is too large Load Diff
-457
View File
@@ -1,457 +0,0 @@
import { Encoding, isNonNullObject } from "@iov/encoding";
import axios, { AxiosError, AxiosInstance } from "axios";
import { Coin } from "./coins";
import { CosmosSdkTx, JsonObject, Model, parseWasmData, StdTx, WasmData } from "./types";
const { fromBase64, fromUtf8, toHex, toUtf8 } = Encoding;
export interface CosmosSdkAccount {
/** Bech32 account address */
readonly address: string;
readonly coins: ReadonlyArray<Coin>;
/** Bech32 encoded pubkey */
readonly public_key: string;
readonly account_number: number;
readonly sequence: number;
}
export interface NodeInfo {
readonly protocol_version: {
readonly p2p: string;
readonly block: string;
readonly app: string;
};
readonly id: string;
readonly listen_addr: string;
readonly network: string;
readonly version: string;
readonly channels: string;
readonly moniker: string;
readonly other: {
readonly tx_index: string;
readonly rpc_address: string;
};
}
export interface ApplicationVersion {
readonly name: string;
readonly server_name: string;
readonly client_name: string;
readonly version: string;
readonly commit: string;
readonly build_tags: string;
readonly go: string;
}
export interface NodeInfoResponse {
readonly node_info: NodeInfo;
readonly application_version: ApplicationVersion;
}
export interface BlockId {
readonly hash: string;
// TODO: here we also have this
// parts: {
// total: '1',
// hash: '7AF200C78FBF9236944E1AB270F4045CD60972B7C265E3A9DA42973397572931'
// }
}
export interface BlockHeader {
readonly version: {
readonly block: string;
readonly app: string;
};
readonly height: string;
readonly chain_id: string;
/** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */
readonly time: string;
readonly last_commit_hash: string;
readonly last_block_id: BlockId;
/** Can be empty */
readonly data_hash: string;
readonly validators_hash: string;
readonly next_validators_hash: string;
readonly consensus_hash: string;
readonly app_hash: string;
/** Can be empty */
readonly last_results_hash: string;
/** Can be empty */
readonly evidence_hash: string;
readonly proposer_address: string;
}
export interface Block {
readonly header: BlockHeader;
readonly data: {
/** Array of base64 encoded transactions */
readonly txs: ReadonlyArray<string> | null;
};
}
export interface BlockResponse {
readonly block_id: BlockId;
readonly block: Block;
}
interface AuthAccountsResponse {
readonly height: string;
readonly result: {
readonly type: "cosmos-sdk/Account";
readonly value: CosmosSdkAccount;
};
}
// Currently all wasm query responses return json-encoded strings...
// later deprecate this and use the specific types for result
// (assuming it is inlined, no second parse needed)
type WasmResponse<T = string> = WasmSuccess<T> | WasmError;
interface WasmSuccess<T = string> {
readonly height: string;
readonly result: T;
}
interface WasmError {
readonly error: string;
}
export interface TxsResponse {
readonly height: string;
readonly txhash: string;
/** 🤷‍♂️ */
readonly codespace?: string;
/** Falsy when transaction execution succeeded. Contains error code on error. */
readonly code?: number;
readonly raw_log: string;
readonly logs?: object;
readonly tx: CosmosSdkTx;
/** The gas limit as set by the user */
readonly gas_wanted?: string;
/** The gas used by the execution */
readonly gas_used?: string;
readonly timestamp: string;
}
interface SearchTxsResponse {
readonly total_count: string;
readonly count: string;
readonly page_number: string;
readonly page_total: string;
readonly limit: string;
readonly txs: readonly TxsResponse[];
}
export interface PostTxsResponse {
readonly height: string;
readonly txhash: string;
readonly code?: number;
readonly raw_log?: string;
/** The same as `raw_log` but deserialized? */
readonly logs?: object;
/** The gas limit as set by the user */
readonly gas_wanted?: string;
/** The gas used by the execution */
readonly gas_used?: string;
}
interface EncodeTxResponse {
// base64-encoded amino-binary encoded representation
readonly tx: string;
}
export interface CodeInfo {
readonly id: number;
/** Bech32 account address */
readonly creator: string;
/** Hex-encoded sha256 hash of the code stored here */
readonly data_hash: string;
// TODO: these are not supported in current wasmd
readonly source?: string;
readonly builder?: string;
}
export interface CodeDetails extends CodeInfo {
/** Base64 encoded raw wasm data */
readonly data: string;
}
// This is list view, without contract info
export interface ContractInfo {
readonly address: string;
readonly code_id: number;
/** Bech32 account address */
readonly creator: string;
readonly label: string;
}
export interface ContractDetails extends ContractInfo {
/** Argument passed on initialization of the contract */
readonly init_msg: object;
}
interface SmartQueryResponse {
// base64 encoded response
readonly smart: string;
}
type RestClientResponse =
| NodeInfoResponse
| BlockResponse
| AuthAccountsResponse
| TxsResponse
| SearchTxsResponse
| PostTxsResponse
| EncodeTxResponse
| WasmResponse<string>
| WasmResponse<CodeInfo[]>
| WasmResponse<CodeDetails>
| WasmResponse<ContractInfo[] | null>
| WasmResponse<ContractDetails | null>;
/** Unfortunately, Cosmos SDK encodes empty arrays as null */
type CosmosSdkArray<T> = ReadonlyArray<T> | null;
function normalizeArray<T>(backend: CosmosSdkArray<T>): ReadonlyArray<T> {
return backend || [];
}
/**
* The mode used to send transaction
*
* @see https://cosmos.network/rpc/#/Transactions/post_txs
*/
export enum BroadcastMode {
/** Return after tx commit */
Block = "block",
/** Return afer CheckTx */
Sync = "sync",
/** Return right away */
Async = "async",
}
function isWasmError<T>(resp: WasmResponse<T>): resp is WasmError {
return (resp as WasmError).error !== undefined;
}
function unwrapWasmResponse<T>(response: WasmResponse<T>): T {
if (isWasmError(response)) {
throw new Error(response.error);
}
return response.result;
}
// We want to get message data from 500 errors
// https://stackoverflow.com/questions/56577124/how-to-handle-500-error-message-with-axios
// this should be chained to catch one error and throw a more informative one
function parseAxiosError(err: AxiosError): never {
// use the error message sent from server, not default 500 msg
if (err.response?.data) {
let errorText: string;
const data = err.response.data;
// expect { error: string }, but otherwise dump
if (data.error && typeof data.error === "string") {
errorText = data.error;
} else if (typeof data === "string") {
errorText = data;
} else {
errorText = JSON.stringify(data);
}
throw new Error(`${errorText} (HTTP ${err.response.status})`);
} else {
throw err;
}
}
export class RestClient {
private readonly client: AxiosInstance;
private readonly broadcastMode: BroadcastMode;
/**
* Creates a new client to interact with a Cosmos SDK light client daemon.
* This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done
* but things like caching are done at a higher level.
*
* When building apps, you should not need to use this class directly. If you do, this indicates a missing feature
* in higher level components. Feel free to raise an issue in this case.
*
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
* @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns
*/
public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) {
const headers = {
post: { "Content-Type": "application/json" },
};
this.client = axios.create({
baseURL: apiUrl,
headers: headers,
});
this.broadcastMode = broadcastMode;
}
public async get(path: string): Promise<RestClientResponse> {
const { data } = await this.client.get(path).catch(parseAxiosError);
if (data === null) {
throw new Error("Received null response from server");
}
return data;
}
public async post(path: string, params: any): Promise<RestClientResponse> {
if (!isNonNullObject(params)) throw new Error("Got unexpected type of params. Expected object.");
const { data } = await this.client.post(path, params).catch(parseAxiosError);
if (data === null) {
throw new Error("Received null response from server");
}
return data;
}
// The /auth endpoints
public async authAccounts(address: string): Promise<AuthAccountsResponse> {
const path = `/auth/accounts/${address}`;
const responseData = await this.get(path);
if ((responseData as any).result.type !== "cosmos-sdk/Account") {
throw new Error("Unexpected response data format");
}
return responseData as AuthAccountsResponse;
}
// The /blocks endpoints
public async blocksLatest(): Promise<BlockResponse> {
const responseData = await this.get("/blocks/latest");
if (!(responseData as any).block) {
throw new Error("Unexpected response data format");
}
return responseData as BlockResponse;
}
public async blocks(height: number): Promise<BlockResponse> {
const responseData = await this.get(`/blocks/${height}`);
if (!(responseData as any).block) {
throw new Error("Unexpected response data format");
}
return responseData as BlockResponse;
}
// The /node_info endpoint
public async nodeInfo(): Promise<NodeInfoResponse> {
const responseData = await this.get("/node_info");
if (!(responseData as any).node_info) {
throw new Error("Unexpected response data format");
}
return responseData as NodeInfoResponse;
}
// The /txs endpoints
public async txById(id: string): Promise<TxsResponse> {
const responseData = await this.get(`/txs/${id}`);
if (!(responseData as any).tx) {
throw new Error("Unexpected response data format");
}
return responseData as TxsResponse;
}
public async txsQuery(query: string): Promise<SearchTxsResponse> {
const responseData = await this.get(`/txs?${query}`);
if (!(responseData as any).txs) {
throw new Error("Unexpected response data format");
}
return responseData as SearchTxsResponse;
}
/** returns the amino-encoding of the transaction performed by the server */
public async encodeTx(tx: CosmosSdkTx): Promise<Uint8Array> {
const responseData = await this.post("/txs/encode", tx);
if (!(responseData as any).tx) {
throw new Error("Unexpected response data format");
}
return Encoding.fromBase64((responseData as EncodeTxResponse).tx);
}
/**
* Broadcasts a signed transaction to into the transaction pool.
* Depending on the RestClient's broadcast mode, this might or might
* wait for checkTx or deliverTx to be executed before returning.
*
* @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container)
*/
public async postTx(tx: StdTx): Promise<PostTxsResponse> {
const params = {
tx: tx,
mode: this.broadcastMode,
};
const responseData = await this.post("/txs", params);
if (!(responseData as any).txhash) {
throw new Error("Unexpected response data format");
}
return responseData as PostTxsResponse;
}
// The /wasm endpoints
// wasm rest queries are listed here: https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27
public async listCodeInfo(): Promise<readonly CodeInfo[]> {
const path = `/wasm/code`;
const responseData = (await this.get(path)) as WasmResponse<CosmosSdkArray<CodeInfo>>;
return normalizeArray(unwrapWasmResponse(responseData));
}
// this will download the original wasm bytecode by code id
// throws error if no code with this id
public async getCode(id: number): Promise<CodeDetails> {
const path = `/wasm/code/${id}`;
const responseData = (await this.get(path)) as WasmResponse<CodeDetails>;
return unwrapWasmResponse(responseData);
}
public async listContractsByCodeId(id: number): Promise<readonly ContractInfo[]> {
const path = `/wasm/code/${id}/contracts`;
const responseData = (await this.get(path)) as WasmResponse<CosmosSdkArray<ContractInfo>>;
return normalizeArray(unwrapWasmResponse(responseData));
}
/**
* Returns null when contract was not found at this address.
*/
public async getContractInfo(address: string): Promise<ContractDetails | null> {
const path = `/wasm/contract/${address}`;
const response = (await this.get(path)) as WasmResponse<ContractDetails | null>;
return unwrapWasmResponse(response);
}
// Returns all contract state.
// This is an empty array if no such contract, or contract has no data.
public async getAllContractState(address: string): Promise<readonly Model[]> {
const path = `/wasm/contract/${address}/state`;
const responseData = (await this.get(path)) as WasmResponse<CosmosSdkArray<WasmData>>;
return normalizeArray(unwrapWasmResponse(responseData)).map(parseWasmData);
}
// Returns the data at the key if present (unknown decoded json),
// or null if no data at this (contract address, key) pair
public async queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null> {
const hexKey = toHex(key);
const path = `/wasm/contract/${address}/raw/${hexKey}?encoding=hex`;
const responseData = (await this.get(path)) as WasmResponse<WasmData[]>;
const data = unwrapWasmResponse(responseData);
return data.length === 0 ? null : fromBase64(data[0].val);
}
/**
* Makes a smart query on the contract and parses the reponse as JSON.
* Throws error if no such contract exists, the query format is invalid or the response is invalid.
*/
public async queryContractSmart(address: string, query: object): Promise<JsonObject> {
const encoded = toHex(toUtf8(JSON.stringify(query)));
const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`;
const responseData = (await this.get(path)) as WasmResponse<SmartQueryResponse>;
const result = unwrapWasmResponse(responseData);
// By convention, smart queries must return a valid JSON document (see https://github.com/CosmWasm/cosmwasm/issues/144)
return JSON.parse(fromUtf8(fromBase64(result.smart)));
}
}
-30
View File
@@ -1,30 +0,0 @@
import { findSequenceForSignedTx } from "./sequence";
import response1 from "./testdata/txresponse1.json";
import response2 from "./testdata/txresponse2.json";
import response3 from "./testdata/txresponse3.json";
// Those values must match ./testdata/txresponse*.json
const chainId = "testing";
const accountNumber = 4;
describe("sequence", () => {
describe("findSequenceForSignedTx", () => {
it("works", async () => {
const current = 100; // what we get from GET /auth/accounts/{address}
expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, current)).toEqual(10);
// We know response3.height > response1.height, so the sequence must be at least 10+1
expect(await findSequenceForSignedTx(response3.tx, chainId, accountNumber, current, 11)).toEqual(19);
// We know response3.height > response2.height > response1.height, so the sequence must be at least 10+1 and smaller than 19
expect(await findSequenceForSignedTx(response2.tx, chainId, accountNumber, 19, 11)).toEqual(13);
});
it("returns undefined when sequence is not in range", async () => {
expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 5)).toBeUndefined();
expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 20, 11)).toBeUndefined();
expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 20, 50)).toBeUndefined();
// upper bound is not included in the possible results
expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 10)).toBeUndefined();
});
});
});
-46
View File
@@ -1,46 +0,0 @@
import { Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto";
import { makeSignBytes } from "./encoding";
import { decodeSignature } from "./signature";
import { CosmosSdkTx } from "./types";
/**
* Serach for sequence s with `min` <= `s` < `upperBound` to find the sequence that was used to sign the transaction
*
* @param tx The signed transaction
* @param chainId The chain ID for which this transaction was signed
* @param accountNumber The account number for which this transaction was signed
* @param upperBound The upper bound for the testing, i.e. sequence must be lower than this value
* @param min The lowest sequence that is tested
*
* @returns the sequence if a match was found and undefined otherwise
*/
export async function findSequenceForSignedTx(
tx: CosmosSdkTx,
chainId: string,
accountNumber: number,
upperBound: number,
min = 0,
): Promise<number | undefined> {
const firstSignature = tx.value.signatures.find(() => true);
if (!firstSignature) throw new Error("Signature missing in tx");
const { pubkey, signature } = decodeSignature(firstSignature);
const secp256keSignature = Secp256k1Signature.fromFixedLength(signature);
for (let s = min; s < upperBound; s++) {
// console.log(`Trying sequence ${s}`);
const signBytes = makeSignBytes(
tx.value.msg,
tx.value.fee,
chainId,
tx.value.memo || "",
accountNumber,
s,
);
const prehashed = new Sha256(signBytes).digest();
const valid = await Secp256k1.verifySignature(secp256keSignature, prehashed, pubkey);
if (valid) return s;
}
return undefined;
}
-69
View File
@@ -1,69 +0,0 @@
import { Encoding } from "@iov/encoding";
import { decodeSignature, encodeSecp256k1Signature } from "./signature";
import { StdSignature } from "./types";
const { fromBase64 } = Encoding;
describe("signature", () => {
describe("encodeSecp256k1Signature", () => {
it("encodes a full signature", () => {
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
const signature = fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
);
expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
it("throws when getting uncompressed public keys", () => {
const pubkey = fromBase64(
"BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=",
);
const signature = fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
);
expect(() => encodeSecp256k1Signature(pubkey, signature)).toThrowError(
/public key must be compressed secp256k1/i,
);
});
it("throws if signature contains recovery byte", () => {
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
const signature = Uint8Array.from([
...fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
),
99,
]);
expect(() => encodeSecp256k1Signature(pubkey, signature)).toThrowError(
/signature must be 64 bytes long/i,
);
});
});
describe("decodeSignature", () => {
it("works for secp256k1", () => {
const signature: StdSignature = {
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
};
expect(decodeSignature(signature)).toEqual({
pubkey: fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"),
signature: fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
),
});
});
});
});
-39
View File
@@ -1,39 +0,0 @@
import { Encoding } from "@iov/encoding";
import { encodeSecp256k1Pubkey } from "./pubkey";
import { pubkeyType, StdSignature } from "./types";
/**
* Takes a binary pubkey and signature to create a signature object
*
* @param pubkey a compressed secp256k1 public key
* @param signature a 64 byte fixed length representation of secp256k1 signature components r and s
*/
export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature {
if (signature.length !== 64) {
throw new Error(
"Signature must be 64 bytes long. Cosmos SDK uses a 2x32 byte fixed length encoding for the secp256k1 signature integers r and s.",
);
}
return {
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: encodeSecp256k1Pubkey(pubkey),
signature: Encoding.toBase64(signature),
};
}
export function decodeSignature(
signature: StdSignature,
): { readonly pubkey: Uint8Array; readonly signature: Uint8Array } {
switch (signature.pub_key.type) {
// Note: please don't add cases here without writing additional unit tests
case pubkeyType.secp256k1:
return {
pubkey: Encoding.fromBase64(signature.pub_key.value),
signature: Encoding.fromBase64(signature.signature),
};
default:
throw new Error("Unsupported pubkey type");
}
}
@@ -1,230 +0,0 @@
import { Sha256 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { assert } from "@iov/utils";
import { Coin } from "./coins";
import { PrivateCosmWasmClient } from "./cosmwasmclient";
import { Secp256k1Pen } from "./pen";
import { RestClient } from "./restclient";
import { SigningCosmWasmClient, UploadMeta } from "./signingcosmwasmclient";
import { getHackatom, makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec";
const { toHex } = Encoding;
const httpUrl = "http://localhost:1317";
const faucet = {
mnemonic:
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone",
pubkey: {
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
},
address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
};
describe("SigningCosmWasmClient", () => {
describe("makeReadOnly", () => {
it("can be constructed", async () => {
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
expect(client).toBeTruthy();
});
});
describe("getHeight", () => {
it("always uses authAccount implementation", async () => {
pendingWithoutWasmd();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
const openedClient = (client as unknown) as PrivateCosmWasmClient;
const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough();
const authAccountsSpy = spyOn(openedClient.restClient, "authAccounts").and.callThrough();
const height = await client.getHeight();
expect(height).toBeGreaterThan(0);
expect(blockLatestSpy).toHaveBeenCalledTimes(0);
expect(authAccountsSpy).toHaveBeenCalledTimes(1);
});
});
describe("upload", () => {
it("works", async () => {
pendingWithoutWasmd();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
const wasm = getHackatom();
const {
codeId,
originalChecksum,
originalSize,
compressedChecksum,
compressedSize,
} = await client.upload(wasm);
expect(originalChecksum).toEqual(toHex(new Sha256(wasm).digest()));
expect(originalSize).toEqual(wasm.length);
expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/);
expect(compressedSize).toBeLessThan(wasm.length * 0.5);
expect(codeId).toBeGreaterThanOrEqual(1);
});
it("can set builder and source", async () => {
pendingWithoutWasmd();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
const wasm = getHackatom();
const meta: UploadMeta = {
source: "https://crates.io/api/v1/crates/cw-nameservice/0.1.0/download",
builder: "confio/cosmwasm-opt:0.6.2",
};
const { codeId } = await client.upload(wasm, meta);
const codeDetails = await client.getCodeDetails(codeId);
expect(codeDetails.source).toEqual(meta.source);
expect(codeDetails.builder).toEqual(meta.builder);
});
});
describe("instantiate", () => {
it("works with transfer amount", async () => {
pendingWithoutWasmd();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
const { codeId } = await client.upload(getHackatom());
const transferAmount: readonly Coin[] = [
{
amount: "1234",
denom: "ucosm",
},
{
amount: "321",
denom: "ustake",
},
];
const beneficiaryAddress = makeRandomAddress();
const { contractAddress } = await client.instantiate(
codeId,
{
verifier: faucet.address,
beneficiary: beneficiaryAddress,
},
"My cool label",
"Let's see if the memo is used",
transferAmount,
);
const rest = new RestClient(httpUrl);
const balance = (await rest.authAccounts(contractAddress)).result.value.coins;
expect(balance).toEqual(transferAmount);
});
it("can instantiate one code multiple times", async () => {
pendingWithoutWasmd();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
const { codeId } = await client.upload(getHackatom());
const contractAddress1 = await client.instantiate(
codeId,
{
verifier: faucet.address,
beneficiary: makeRandomAddress(),
},
"contract 1",
);
const contractAddress2 = await client.instantiate(
codeId,
{
verifier: faucet.address,
beneficiary: makeRandomAddress(),
},
"contract 2",
);
expect(contractAddress1).not.toEqual(contractAddress2);
});
});
describe("execute", () => {
it("works", async () => {
pendingWithoutWasmd();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
const { codeId } = await client.upload(getHackatom());
// instantiate
const transferAmount: readonly Coin[] = [
{
amount: "233444",
denom: "ucosm",
},
{
amount: "5454",
denom: "ustake",
},
];
const beneficiaryAddress = makeRandomAddress();
const { contractAddress } = await client.instantiate(
codeId,
{
verifier: faucet.address,
beneficiary: beneficiaryAddress,
},
"amazing random contract",
undefined,
transferAmount,
);
// execute
const result = await client.execute(contractAddress, { release: {} }, undefined);
const wasmEvent = result.logs.find(() => true)?.events.find((e) => e.type === "wasm");
assert(wasmEvent, "Event of type wasm expected");
expect(wasmEvent.attributes).toContain({ key: "action", value: "release" });
expect(wasmEvent.attributes).toContain({
key: "destination",
value: beneficiaryAddress,
});
// Verify token transfer from contract to beneficiary
const rest = new RestClient(httpUrl);
const beneficiaryBalance = (await rest.authAccounts(beneficiaryAddress)).result.value.coins;
expect(beneficiaryBalance).toEqual(transferAmount);
const contractBalance = (await rest.authAccounts(contractAddress)).result.value.coins;
expect(contractBalance).toEqual([]);
});
});
describe("sendTokens", () => {
it("works", async () => {
pendingWithoutWasmd();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
// instantiate
const transferAmount: readonly Coin[] = [
{
amount: "7890",
denom: "ucosm",
},
];
const beneficiaryAddress = makeRandomAddress();
// no tokens here
const before = await client.getAccount(beneficiaryAddress);
expect(before).toBeUndefined();
// send
const result = await client.sendTokens(beneficiaryAddress, transferAmount, "for dinner");
const [firstLog] = result.logs;
expect(firstLog).toBeTruthy();
// got tokens
const after = await client.getAccount(beneficiaryAddress);
assert(after);
expect(after.balance).toEqual(transferAmount);
});
});
});
-284
View File
@@ -1,284 +0,0 @@
import { Sha256 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import pako from "pako";
import { isValidBuilder } from "./builder";
import { Coin, coins } from "./coins";
import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient";
import { makeSignBytes } from "./encoding";
import { findAttribute, Log } from "./logs";
import { BroadcastMode } from "./restclient";
import {
MsgExecuteContract,
MsgInstantiateContract,
MsgSend,
MsgStoreCode,
StdFee,
StdSignature,
} from "./types";
export interface SigningCallback {
(signBytes: Uint8Array): Promise<StdSignature>;
}
export interface FeeTable {
readonly upload: StdFee;
readonly init: StdFee;
readonly exec: StdFee;
readonly send: StdFee;
}
function prepareBuilder(buider: string | undefined): string {
if (buider === undefined) {
return ""; // normalization needed by backend
} else {
if (!isValidBuilder(buider)) throw new Error("The builder (Docker Hub image with tag) is not valid");
return buider;
}
}
const defaultFees: FeeTable = {
upload: {
amount: coins(25000, "ucosm"),
gas: "1000000", // one million
},
init: {
amount: coins(12500, "ucosm"),
gas: "500000", // 500k
},
exec: {
amount: coins(5000, "ucosm"),
gas: "200000", // 200k
},
send: {
amount: coins(2000, "ucosm"),
gas: "80000", // 80k
},
};
export interface UploadMeta {
/** The source URL */
readonly source?: string;
/** The builder tag */
readonly builder?: string;
}
export interface UploadResult {
/** Size of the original wasm code in bytes */
readonly originalSize: number;
/** A hex encoded sha256 checksum of the original wasm code (that is stored on chain) */
readonly originalChecksum: string;
/** Size of the compressed wasm code in bytes */
readonly compressedSize: number;
/** A hex encoded sha256 checksum of the compressed wasm code (that stored in the transaction) */
readonly compressedChecksum: string;
/** The ID of the code asigned by the chain */
readonly codeId: number;
readonly logs: readonly Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export interface InstantiateResult {
/** The address of the newly instantiated contract */
readonly contractAddress: string;
readonly logs: readonly Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export interface ExecuteResult {
readonly logs: readonly Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export class SigningCosmWasmClient extends CosmWasmClient {
public readonly senderAddress: string;
private readonly signCallback: SigningCallback;
private readonly fees: FeeTable;
/**
* Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient.
*
* This instance does a lot of caching. In order to benefit from that you should try to use one instance
* for the lifetime of your application. When switching backends, a new instance must be created.
*
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
* @param senderAddress The address that will sign and send transactions using this instance
* @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction.
* @param customFees The fees that are paid for transactions
* @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns
*/
public constructor(
apiUrl: string,
senderAddress: string,
signCallback: SigningCallback,
customFees?: Partial<FeeTable>,
broadcastMode = BroadcastMode.Block,
) {
super(apiUrl, broadcastMode);
this.anyValidAddress = senderAddress;
this.senderAddress = senderAddress;
this.signCallback = signCallback;
this.fees = { ...defaultFees, ...(customFees || {}) };
}
public async getNonce(address?: string): Promise<GetNonceResult> {
return super.getNonce(address || this.senderAddress);
}
public async getAccount(address?: string): Promise<Account | undefined> {
return super.getAccount(address || this.senderAddress);
}
/** Uploads code and returns a receipt, including the code ID */
public async upload(wasmCode: Uint8Array, meta: UploadMeta = {}, memo = ""): Promise<UploadResult> {
const source = meta.source || "";
const builder = prepareBuilder(meta.builder);
const compressed = pako.gzip(wasmCode, { level: 9 });
const storeCodeMsg: MsgStoreCode = {
type: "wasm/store-code",
value: {
sender: this.senderAddress,
// eslint-disable-next-line @typescript-eslint/camelcase
wasm_byte_code: Encoding.toBase64(compressed),
source: source,
builder: builder,
},
};
const fee = this.fees.upload;
const { accountNumber, sequence } = await this.getNonce();
const chainId = await this.getChainId();
const signBytes = makeSignBytes([storeCodeMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await this.signCallback(signBytes);
const signedTx = {
msg: [storeCodeMsg],
fee: fee,
memo: memo,
signatures: [signature],
};
const result = await this.postTx(signedTx);
const codeIdAttr = findAttribute(result.logs, "message", "code_id");
return {
originalSize: wasmCode.length,
originalChecksum: Encoding.toHex(new Sha256(wasmCode).digest()),
compressedSize: compressed.length,
compressedChecksum: Encoding.toHex(new Sha256(compressed).digest()),
codeId: Number.parseInt(codeIdAttr.value, 10),
logs: result.logs,
transactionHash: result.transactionHash,
};
}
public async instantiate(
codeId: number,
initMsg: object,
label: string,
memo = "",
transferAmount?: readonly Coin[],
): Promise<InstantiateResult> {
const instantiateMsg: MsgInstantiateContract = {
type: "wasm/instantiate",
value: {
sender: this.senderAddress,
// eslint-disable-next-line @typescript-eslint/camelcase
code_id: codeId.toString(),
label: label,
// eslint-disable-next-line @typescript-eslint/camelcase
init_msg: initMsg,
// eslint-disable-next-line @typescript-eslint/camelcase
init_funds: transferAmount || [],
},
};
const fee = this.fees.init;
const { accountNumber, sequence } = await this.getNonce();
const chainId = await this.getChainId();
const signBytes = makeSignBytes([instantiateMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await this.signCallback(signBytes);
const signedTx = {
msg: [instantiateMsg],
fee: fee,
memo: memo,
signatures: [signature],
};
const result = await this.postTx(signedTx);
const contractAddressAttr = findAttribute(result.logs, "message", "contract_address");
return {
contractAddress: contractAddressAttr.value,
logs: result.logs,
transactionHash: result.transactionHash,
};
}
public async execute(
contractAddress: string,
handleMsg: object,
memo = "",
transferAmount?: readonly Coin[],
): Promise<ExecuteResult> {
const executeMsg: MsgExecuteContract = {
type: "wasm/execute",
value: {
sender: this.senderAddress,
contract: contractAddress,
msg: handleMsg,
// eslint-disable-next-line @typescript-eslint/camelcase
sent_funds: transferAmount || [],
},
};
const fee = this.fees.exec;
const { accountNumber, sequence } = await this.getNonce();
const chainId = await this.getChainId();
const signBytes = makeSignBytes([executeMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await this.signCallback(signBytes);
const signedTx = {
msg: [executeMsg],
fee: fee,
memo: memo,
signatures: [signature],
};
const result = await this.postTx(signedTx);
return {
logs: result.logs,
transactionHash: result.transactionHash,
};
}
public async sendTokens(
recipientAddress: string,
transferAmount: readonly Coin[],
memo = "",
): Promise<PostTxResult> {
const sendMsg: MsgSend = {
type: "cosmos-sdk/MsgSend",
value: {
// eslint-disable-next-line @typescript-eslint/camelcase
from_address: this.senderAddress,
// eslint-disable-next-line @typescript-eslint/camelcase
to_address: recipientAddress,
amount: transferAmount,
},
};
const fee = this.fees.send;
const { accountNumber, sequence } = await this.getNonce();
const chainId = await this.getChainId();
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await this.signCallback(signBytes);
const signedTx = {
msg: [sendMsg],
fee: fee,
memo: memo,
signatures: [signature],
};
return this.postTx(signedTx);
}
}
File diff suppressed because one or more lines are too long
-44
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"
}
-57
View File
@@ -1,57 +0,0 @@
{
"height": "15888",
"txhash": "672DEDE8EF4DE8B5818959F417CCA357079D4D7A19C4B65443C7FBF8176AABF9",
"raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]",
"logs": [
{
"msg_index": 0,
"log": "",
"events": [
{
"type": "message",
"attributes": [
{ "key": "action", "value": "send" },
{ "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" },
{ "key": "module", "value": "bank" }
]
},
{
"type": "transfer",
"attributes": [
{ "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" },
{ "key": "amount", "value": "75000ucosm" }
]
}
]
}
],
"gas_wanted": "200000",
"gas_used": "65407",
"tx": {
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "cosmos-sdk/MsgSend",
"value": {
"from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
"to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2",
"amount": [{ "denom": "ucosm", "amount": "75000" }]
}
}
],
"fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" },
"signatures": [
{
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"
},
"signature": "US7oH8S/8TxVrtBQkOhHxAM+oDB2spNAEawgh6H8CCFLRMOJK+uvQZZ6ceUgUsvDbxwCz7re1RU272fymMYRZQ=="
}
],
"memo": "My first payment"
}
},
"timestamp": "2020-02-14T11:25:55Z"
}
-57
View File
@@ -1,57 +0,0 @@
{
"height": "16456",
"txhash": "7BFE4B93AF190F60132C62D08FDF50BE462FBCE374EB13D3FD0C32461E771EC0",
"raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]",
"logs": [
{
"msg_index": 0,
"log": "",
"events": [
{
"type": "message",
"attributes": [
{ "key": "action", "value": "send" },
{ "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" },
{ "key": "module", "value": "bank" }
]
},
{
"type": "transfer",
"attributes": [
{ "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" },
{ "key": "amount", "value": "75000ucosm" }
]
}
]
}
],
"gas_wanted": "200000",
"gas_used": "65407",
"tx": {
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "cosmos-sdk/MsgSend",
"value": {
"from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
"to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2",
"amount": [{ "denom": "ucosm", "amount": "75000" }]
}
}
],
"fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" },
"signatures": [
{
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"
},
"signature": "ltvd9Rb3RF4zjbUVrpDpkok34g+py7XR8ZcM0tZUYRxxVdcMEin010x+ZFd/mOuutPj9fDmSENnienc/yi4msw=="
}
],
"memo": "My first payment"
}
},
"timestamp": "2020-02-14T11:35:41Z"
}
-57
View File
@@ -1,57 +0,0 @@
{
"height": "20730",
"txhash": "625BC75E697F73DA037387C34002BB2F682E7ACDCC4E015D3E90420516C6D0C8",
"raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]",
"logs": [
{
"msg_index": 0,
"log": "",
"events": [
{
"type": "message",
"attributes": [
{ "key": "action", "value": "send" },
{ "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" },
{ "key": "module", "value": "bank" }
]
},
{
"type": "transfer",
"attributes": [
{ "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" },
{ "key": "amount", "value": "75000ucosm" }
]
}
]
}
],
"gas_wanted": "200000",
"gas_used": "65407",
"tx": {
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "cosmos-sdk/MsgSend",
"value": {
"from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
"to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2",
"amount": [{ "denom": "ucosm", "amount": "75000" }]
}
}
],
"fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" },
"signatures": [
{
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"
},
"signature": "eOFGl1tIHDMv3JdCK9fRSikVbYUD8+B0ksb3dJFya8MPYgpEpdSA7zZc+5n/cW6LR/BJdib4nqmJQv1yD9lm3g=="
}
],
"memo": "My first payment"
}
},
"timestamp": "2020-02-14T12:48:56Z"
}
-77
View File
@@ -1,77 +0,0 @@
import { Random } from "@iov/crypto";
import { Bech32, Encoding } from "@iov/encoding";
import hackatom from "./testdata/contract.json";
export function getHackatom(): Uint8Array {
return Encoding.fromBase64(hackatom.data);
}
export function makeRandomAddress(): string {
return Bech32.encode("cosmos", Random.getBytes(20));
}
export const nonNegativeIntegerMatcher = /^[0-9]+$/;
export const tendermintIdMatcher = /^[0-9A-F]{64}$/;
export const tendermintOptionalIdMatcher = /^([0-9A-F]{64}|)$/;
export const tendermintAddressMatcher = /^[0-9A-F]{40}$/;
export const tendermintShortHashMatcher = /^[0-9a-f]{40}$/;
export const semverMatcher = /^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/;
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32
export const bech32AddressMatcher = /^[\x21-\x7e]{1,83}1[02-9ac-hj-np-z]{38}$/;
/** Deployed as part of scripts/wasmd/init.sh */
export const deployedErc20 = {
codeId: 1,
source: "https://crates.io/api/v1/crates/cw-erc20/0.4.0/download",
builder: "cosmwasm/rust-optimizer:0.8.0",
checksum: "41b3bafd7f9a3870bbfb0a0620508df564c52499cdcdc67bf9df72262f3958a6",
instances: [
"cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", // HASH
"cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd", // ISA
"cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c", // JADE
],
};
export const wasmd = {
endpoint: "http://localhost:1317",
chainId: "testing",
};
export const faucet = {
mnemonic:
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone",
pubkey: {
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
},
address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
};
/** Unused account */
export const unused = {
pubkey: {
type: "tendermint/PubKeySecp256k1",
value: "ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ",
},
address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u",
accountNumber: 9,
sequence: 0,
};
export function wasmdEnabled(): boolean {
return !!process.env.WASMD_ENABLED;
}
export function pendingWithoutWasmd(): void {
if (!wasmdEnabled()) {
return pending("Set WASMD_ENABLED to enable Wasmd based tests");
}
}
/** Returns first element. Throws if array has a different length than 1. */
export function fromOneElementArray<T>(elements: ArrayLike<T>): T {
if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`);
return elements[0];
}
-174
View File
@@ -1,174 +0,0 @@
import { Encoding } from "@iov/encoding";
import { Coin } from "./coins";
const { fromBase64, fromHex } = Encoding;
/** An Amino/Cosmos SDK StdTx */
export interface StdTx {
readonly msg: ReadonlyArray<Msg>;
readonly fee: StdFee;
readonly signatures: ReadonlyArray<StdSignature>;
readonly memo: string | undefined;
}
export function isStdTx(txValue: unknown): txValue is StdTx {
const { memo, msg, fee, signatures } = txValue as StdTx;
return (
typeof memo === "string" && Array.isArray(msg) && typeof fee === "object" && Array.isArray(signatures)
);
}
export interface CosmosSdkTx {
readonly type: string;
readonly value: StdTx;
}
interface MsgTemplate {
readonly type: string;
readonly value: any;
}
/** A Cosmos SDK token transfer message */
export interface MsgSend extends MsgTemplate {
readonly type: "cosmos-sdk/MsgSend";
readonly value: {
/** Bech32 account address */
readonly from_address: string;
/** Bech32 account address */
readonly to_address: string;
readonly amount: ReadonlyArray<Coin>;
};
}
/**
* Uploads Wam code to the chain
*
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17
*/
export interface MsgStoreCode extends MsgTemplate {
readonly type: "wasm/store-code";
readonly value: {
/** Bech32 account address */
readonly sender: string;
/** Base64 encoded Wasm */
readonly wasm_byte_code: string;
/** A valid URI reference to the contract's source code. Can be empty. */
readonly source: string;
/** A docker tag. Can be empty. */
readonly builder: string;
};
}
/**
* Creates an instance of contract that was uploaded before.
*
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73
*/
export interface MsgInstantiateContract extends MsgTemplate {
readonly type: "wasm/instantiate";
readonly value: {
/** Bech32 account address */
readonly sender: string;
/** ID of the Wasm code that was uploaded before */
readonly code_id: string;
/** Human-readable label for this contract */
readonly label: string;
/** Init message as JavaScript object */
readonly init_msg: any;
readonly init_funds: ReadonlyArray<Coin>;
};
}
/**
* Creates an instance of contract that was uploaded before.
*
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103
*/
export interface MsgExecuteContract extends MsgTemplate {
readonly type: "wasm/execute";
readonly value: {
/** Bech32 account address */
readonly sender: string;
/** Bech32 account address */
readonly contract: string;
/** Handle message as JavaScript object */
readonly msg: any;
readonly sent_funds: ReadonlyArray<Coin>;
};
}
export type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate;
export function isMsgSend(msg: Msg): msg is MsgSend {
return (msg as MsgSend).type === "cosmos-sdk/MsgSend";
}
export function isMsgStoreCode(msg: Msg): msg is MsgStoreCode {
return (msg as MsgStoreCode).type === "wasm/store-code";
}
export function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract {
return (msg as MsgInstantiateContract).type === "wasm/instantiate";
}
export function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract {
return (msg as MsgExecuteContract).type === "wasm/execute";
}
export interface StdFee {
readonly amount: ReadonlyArray<Coin>;
readonly gas: string;
}
export interface StdSignature {
readonly pub_key: PubKey;
readonly signature: string;
}
export interface PubKey {
// type is one of the strings defined in pubkeyTypes
// I don't use a string literal union here as that makes trouble with json test data:
// https://github.com/confio/cosmwasm-js/pull/44#pullrequestreview-353280504
readonly type: string;
// Value field is base64-encoded in all cases
// Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first
readonly value: string;
}
export const pubkeyType = {
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */
secp256k1: "tendermint/PubKeySecp256k1" as const,
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */
ed25519: "tendermint/PubKeyEd25519" as const,
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */
sr25519: "tendermint/PubKeySr25519" as const,
};
export const pubkeyTypes: readonly string[] = [pubkeyType.secp256k1, pubkeyType.ed25519, pubkeyType.sr25519];
export interface WasmData {
// key is hex-encoded
readonly key: string;
// value is base64 encoded
readonly val: string;
}
// Model is a parsed WasmData object
export interface Model {
readonly key: Uint8Array;
readonly val: Uint8Array;
}
export function parseWasmData({ key, val }: WasmData): Model {
return {
key: fromHex(key),
val: fromBase64(val),
};
}
/**
* An object containing a parsed JSON document. The result of JSON.parse().
* This doen't privide any type safety over `any` but expresses intent in the code.
*/
export type JsonObject = any;
-12
View File
@@ -1,12 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "build",
"declarationDir": "build/types",
"rootDir": "src"
},
"include": [
"src/**/*"
]
}
-14
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,
};
-3
View File
@@ -1,3 +0,0 @@
import { PubKey } from "./types";
export declare function rawSecp256k1PubkeyToAddress(pubkeyRaw: Uint8Array, prefix: string): string;
export declare function pubkeyToAddress(pubkey: PubKey, prefix: string): string;
-1
View File
@@ -1 +0,0 @@
export declare function isValidBuilder(builder: string): boolean;
-8
View File
@@ -1,8 +0,0 @@
export interface Coin {
readonly denom: string;
readonly amount: string;
}
/** Creates a coin */
export declare function coin(amount: number, denom: string): Coin;
/** Creates a list of coins with one element */
export declare function coins(amount: number, denom: string): Coin[];
-175
View File
@@ -1,175 +0,0 @@
import { Coin } from "./coins";
import { Log } from "./logs";
import { BroadcastMode, RestClient } from "./restclient";
import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types";
export interface GetNonceResult {
readonly accountNumber: number;
readonly sequence: number;
}
export interface Account {
/** Bech32 account address */
readonly address: string;
readonly balance: ReadonlyArray<Coin>;
readonly pubkey: PubKey | undefined;
readonly accountNumber: number;
readonly sequence: number;
}
export interface PostTxResult {
readonly logs: readonly Log[];
readonly rawLog: string;
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export interface SearchByIdQuery {
readonly id: string;
}
export interface SearchByHeightQuery {
readonly height: number;
}
export interface SearchBySentFromOrToQuery {
readonly sentFromOrTo: string;
}
/**
* This query type allows you to pass arbitrary key/value pairs to the backend. It is
* more powerful and slightly lower level than the other search options.
*/
export interface SearchByTagsQuery {
readonly tags: readonly {
readonly key: string;
readonly value: string;
}[];
}
export declare type SearchTxQuery =
| SearchByIdQuery
| SearchByHeightQuery
| SearchBySentFromOrToQuery
| SearchByTagsQuery;
export interface SearchTxFilter {
readonly minHeight?: number;
readonly maxHeight?: number;
}
export interface Code {
readonly id: number;
/** Bech32 account address */
readonly creator: string;
/** Hex-encoded sha256 hash of the code stored here */
readonly checksum: string;
readonly source?: string;
readonly builder?: string;
}
export interface CodeDetails extends Code {
/** The original wasm bytes */
readonly data: Uint8Array;
}
export interface Contract {
readonly address: string;
readonly codeId: number;
/** Bech32 account address */
readonly creator: string;
readonly label: string;
}
export interface ContractDetails extends Contract {
/** Argument passed on initialization of the contract */
readonly initMsg: object;
}
/** A transaction that is indexed as part of the transaction history */
export interface IndexedTx {
readonly height: number;
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly hash: string;
/** Transaction execution error code. 0 on success. */
readonly code: number;
readonly rawLog: string;
readonly logs: readonly Log[];
readonly tx: CosmosSdkTx;
/** The gas limit as set by the user */
readonly gasWanted?: number;
/** The gas used by the execution */
readonly gasUsed?: number;
/** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */
readonly timestamp: string;
}
export interface BlockHeader {
readonly version: {
readonly block: string;
readonly app: string;
};
readonly height: number;
readonly chainId: string;
/** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */
readonly time: string;
}
export interface Block {
/** The ID is a hash of the block header (uppercase hex) */
readonly id: string;
readonly header: BlockHeader;
/** Array of raw transactions */
readonly txs: ReadonlyArray<Uint8Array>;
}
/** Use for testing only */
export interface PrivateCosmWasmClient {
readonly restClient: RestClient;
}
export declare class CosmWasmClient {
protected readonly restClient: RestClient;
/** Any address the chain considers valid (valid bech32 with proper prefix) */
protected anyValidAddress: string | undefined;
private readonly codesCache;
private chainId;
/**
* Creates a new client to interact with a CosmWasm blockchain.
*
* This instance does a lot of caching. In order to benefit from that you should try to use one instance
* for the lifetime of your application. When switching backends, a new instance must be created.
*
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
* @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns
*/
constructor(apiUrl: string, broadcastMode?: BroadcastMode);
getChainId(): Promise<string>;
getHeight(): Promise<number>;
/**
* Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID)
*/
getIdentifier(tx: CosmosSdkTx): Promise<string>;
/**
* Returns account number and sequence.
*
* Throws if the account does not exist on chain.
*
* @param address returns data for this address. When unset, the client's sender adddress is used.
*/
getNonce(address: string): Promise<GetNonceResult>;
getAccount(address: string): Promise<Account | undefined>;
/**
* Gets block header and meta
*
* @param height The height of the block. If undefined, the latest height is used.
*/
getBlock(height?: number): Promise<Block>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
postTx(tx: StdTx): Promise<PostTxResult>;
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>;
getContracts(codeId: number): Promise<readonly Contract[]>;
/**
* Throws an error if no contract was found at the address
*/
getContract(address: string): Promise<ContractDetails>;
/**
* Returns the data at the key if present (raw contract dependent storage data)
* or null if no data at this key.
*
* Promise is rejected when contract does not exist.
*/
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
/**
* Makes a smart query on the contract, returns the parsed JSON document.
*
* Promise is rejected when contract does not exist.
* Promise is rejected for invalid query format.
* Promise is rejected for invalid response format.
*/
queryContractSmart(address: string, queryMsg: object): Promise<JsonObject>;
private txsQuery;
}
-2
View File
@@ -1,2 +0,0 @@
import { StdTx } from "./types";
export declare function unmarshalTx(data: Uint8Array): StdTx;
-10
View File
@@ -1,10 +0,0 @@
import { Msg, StdFee, StdTx } from "./types";
export declare function marshalTx(tx: StdTx): Uint8Array;
export declare function makeSignBytes(
msgs: readonly Msg[],
fee: StdFee,
chainId: string,
memo: string,
accountNumber: number,
sequence: number,
): Uint8Array;
-40
View File
@@ -1,40 +0,0 @@
import * as logs from "./logs";
import * as types from "./types";
export { logs, types };
export { pubkeyToAddress } from "./address";
export { Coin, coin, coins } from "./coins";
export { unmarshalTx } from "./decoding";
export { makeSignBytes, marshalTx } from "./encoding";
export { BroadcastMode, RestClient, TxsResponse } from "./restclient";
export {
Account,
Block,
BlockHeader,
Code,
CodeDetails,
Contract,
ContractDetails,
CosmWasmClient,
GetNonceResult,
IndexedTx,
PostTxResult,
SearchByHeightQuery,
SearchByIdQuery,
SearchBySentFromOrToQuery,
SearchByTagsQuery,
SearchTxQuery,
SearchTxFilter,
} from "./cosmwasmclient";
export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen";
export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey";
export { findSequenceForSignedTx } from "./sequence";
export { encodeSecp256k1Signature, decodeSignature } from "./signature";
export {
ExecuteResult,
FeeTable,
InstantiateResult,
SigningCallback,
SigningCosmWasmClient,
UploadMeta,
UploadResult,
} from "./signingcosmwasmclient";
-28
View File
@@ -1,28 +0,0 @@
export interface Attribute {
readonly key: string;
readonly value: string;
}
export interface Event {
readonly type: string;
readonly attributes: readonly Attribute[];
}
export interface Log {
readonly msg_index: number;
readonly log: string;
readonly events: readonly Event[];
}
export declare function parseAttribute(input: unknown): Attribute;
export declare function parseEvent(input: unknown): Event;
export declare function parseLog(input: unknown): Log;
export declare function parseLogs(input: unknown): readonly Log[];
/**
* Searches in logs for the first event of the given event type and in that event
* for the first first attribute with the given attribute key.
*
* Throws if the attribute was not found.
*/
export declare function findAttribute(
logs: readonly Log[],
eventType: "message" | "transfer",
attrKey: string,
): Attribute;
-33
View File
@@ -1,33 +0,0 @@
import { Slip10RawIndex } from "@iov/crypto";
import { StdSignature } from "./types";
export declare type PrehashType = "sha256" | "sha512" | null;
/**
* A pen is the most basic tool you can think of for signing. It works
* everywhere and can be used intuitively by everyone. However, it does not
* come with a great amount of features. End of semi suitable metaphor.
*
* This wraps a single keypair and allows for signing.
*
* Non-goals of this types are: multi account support, persistency, data migrations,
* obfuscation of sensitive data.
*/
export interface Pen {
readonly pubkey: Uint8Array;
readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<StdSignature>;
}
/**
* The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a`
* with 0-based account index `a`.
*/
export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[];
export declare class Secp256k1Pen implements Pen {
static fromMnemonic(mnemonic: string, hdPath?: readonly Slip10RawIndex[]): Promise<Secp256k1Pen>;
readonly pubkey: Uint8Array;
private readonly privkey;
private constructor();
/**
* Creates and returns a signature
*/
sign(signBytes: Uint8Array, prehashType?: PrehashType): Promise<StdSignature>;
address(prefix: string): string;
}
-4
View File
@@ -1,4 +0,0 @@
import { PubKey } from "./types";
export declare function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey;
export declare function decodeBech32Pubkey(bechEncoded: string): PubKey;
export declare function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string;
-231
View File
@@ -1,231 +0,0 @@
import { Coin } from "./coins";
import { CosmosSdkTx, JsonObject, Model, StdTx } from "./types";
export interface CosmosSdkAccount {
/** Bech32 account address */
readonly address: string;
readonly coins: ReadonlyArray<Coin>;
/** Bech32 encoded pubkey */
readonly public_key: string;
readonly account_number: number;
readonly sequence: number;
}
export interface NodeInfo {
readonly protocol_version: {
readonly p2p: string;
readonly block: string;
readonly app: string;
};
readonly id: string;
readonly listen_addr: string;
readonly network: string;
readonly version: string;
readonly channels: string;
readonly moniker: string;
readonly other: {
readonly tx_index: string;
readonly rpc_address: string;
};
}
export interface ApplicationVersion {
readonly name: string;
readonly server_name: string;
readonly client_name: string;
readonly version: string;
readonly commit: string;
readonly build_tags: string;
readonly go: string;
}
export interface NodeInfoResponse {
readonly node_info: NodeInfo;
readonly application_version: ApplicationVersion;
}
export interface BlockId {
readonly hash: string;
}
export interface BlockHeader {
readonly version: {
readonly block: string;
readonly app: string;
};
readonly height: string;
readonly chain_id: string;
/** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */
readonly time: string;
readonly last_commit_hash: string;
readonly last_block_id: BlockId;
/** Can be empty */
readonly data_hash: string;
readonly validators_hash: string;
readonly next_validators_hash: string;
readonly consensus_hash: string;
readonly app_hash: string;
/** Can be empty */
readonly last_results_hash: string;
/** Can be empty */
readonly evidence_hash: string;
readonly proposer_address: string;
}
export interface Block {
readonly header: BlockHeader;
readonly data: {
/** Array of base64 encoded transactions */
readonly txs: ReadonlyArray<string> | null;
};
}
export interface BlockResponse {
readonly block_id: BlockId;
readonly block: Block;
}
interface AuthAccountsResponse {
readonly height: string;
readonly result: {
readonly type: "cosmos-sdk/Account";
readonly value: CosmosSdkAccount;
};
}
declare type WasmResponse<T = string> = WasmSuccess<T> | WasmError;
interface WasmSuccess<T = string> {
readonly height: string;
readonly result: T;
}
interface WasmError {
readonly error: string;
}
export interface TxsResponse {
readonly height: string;
readonly txhash: string;
/** 🤷‍♂️ */
readonly codespace?: string;
/** Falsy when transaction execution succeeded. Contains error code on error. */
readonly code?: number;
readonly raw_log: string;
readonly logs?: object;
readonly tx: CosmosSdkTx;
/** The gas limit as set by the user */
readonly gas_wanted?: string;
/** The gas used by the execution */
readonly gas_used?: string;
readonly timestamp: string;
}
interface SearchTxsResponse {
readonly total_count: string;
readonly count: string;
readonly page_number: string;
readonly page_total: string;
readonly limit: string;
readonly txs: readonly TxsResponse[];
}
export interface PostTxsResponse {
readonly height: string;
readonly txhash: string;
readonly code?: number;
readonly raw_log?: string;
/** The same as `raw_log` but deserialized? */
readonly logs?: object;
/** The gas limit as set by the user */
readonly gas_wanted?: string;
/** The gas used by the execution */
readonly gas_used?: string;
}
interface EncodeTxResponse {
readonly tx: string;
}
export interface CodeInfo {
readonly id: number;
/** Bech32 account address */
readonly creator: string;
/** Hex-encoded sha256 hash of the code stored here */
readonly data_hash: string;
readonly source?: string;
readonly builder?: string;
}
export interface CodeDetails extends CodeInfo {
/** Base64 encoded raw wasm data */
readonly data: string;
}
export interface ContractInfo {
readonly address: string;
readonly code_id: number;
/** Bech32 account address */
readonly creator: string;
readonly label: string;
}
export interface ContractDetails extends ContractInfo {
/** Argument passed on initialization of the contract */
readonly init_msg: object;
}
declare type RestClientResponse =
| NodeInfoResponse
| BlockResponse
| AuthAccountsResponse
| TxsResponse
| SearchTxsResponse
| PostTxsResponse
| EncodeTxResponse
| WasmResponse<string>
| WasmResponse<CodeInfo[]>
| WasmResponse<CodeDetails>
| WasmResponse<ContractInfo[] | null>
| WasmResponse<ContractDetails | null>;
/**
* The mode used to send transaction
*
* @see https://cosmos.network/rpc/#/Transactions/post_txs
*/
export declare enum BroadcastMode {
/** Return after tx commit */
Block = "block",
/** Return afer CheckTx */
Sync = "sync",
/** Return right away */
Async = "async",
}
export declare class RestClient {
private readonly client;
private readonly broadcastMode;
/**
* Creates a new client to interact with a Cosmos SDK light client daemon.
* This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done
* but things like caching are done at a higher level.
*
* When building apps, you should not need to use this class directly. If you do, this indicates a missing feature
* in higher level components. Feel free to raise an issue in this case.
*
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
* @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns
*/
constructor(apiUrl: string, broadcastMode?: BroadcastMode);
get(path: string): Promise<RestClientResponse>;
post(path: string, params: any): Promise<RestClientResponse>;
authAccounts(address: string): Promise<AuthAccountsResponse>;
blocksLatest(): Promise<BlockResponse>;
blocks(height: number): Promise<BlockResponse>;
nodeInfo(): Promise<NodeInfoResponse>;
txById(id: string): Promise<TxsResponse>;
txsQuery(query: string): Promise<SearchTxsResponse>;
/** returns the amino-encoding of the transaction performed by the server */
encodeTx(tx: CosmosSdkTx): Promise<Uint8Array>;
/**
* Broadcasts a signed transaction to into the transaction pool.
* Depending on the RestClient's broadcast mode, this might or might
* wait for checkTx or deliverTx to be executed before returning.
*
* @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container)
*/
postTx(tx: StdTx): Promise<PostTxsResponse>;
listCodeInfo(): Promise<readonly CodeInfo[]>;
getCode(id: number): Promise<CodeDetails>;
listContractsByCodeId(id: number): Promise<readonly ContractInfo[]>;
/**
* Returns null when contract was not found at this address.
*/
getContractInfo(address: string): Promise<ContractDetails | null>;
getAllContractState(address: string): Promise<readonly Model[]>;
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
/**
* Makes a smart query on the contract and parses the reponse as JSON.
* Throws error if no such contract exists, the query format is invalid or the response is invalid.
*/
queryContractSmart(address: string, query: object): Promise<JsonObject>;
}
export {};
-19
View File
@@ -1,19 +0,0 @@
import { CosmosSdkTx } from "./types";
/**
* Serach for sequence s with `min` <= `s` < `upperBound` to find the sequence that was used to sign the transaction
*
* @param tx The signed transaction
* @param chainId The chain ID for which this transaction was signed
* @param accountNumber The account number for which this transaction was signed
* @param upperBound The upper bound for the testing, i.e. sequence must be lower than this value
* @param min The lowest sequence that is tested
*
* @returns the sequence if a match was found and undefined otherwise
*/
export declare function findSequenceForSignedTx(
tx: CosmosSdkTx,
chainId: string,
accountNumber: number,
upperBound: number,
min?: number,
): Promise<number | undefined>;
-14
View File
@@ -1,14 +0,0 @@
import { StdSignature } from "./types";
/**
* Takes a binary pubkey and signature to create a signature object
*
* @param pubkey a compressed secp256k1 public key
* @param signature a 64 byte fixed length representation of secp256k1 signature components r and s
*/
export declare function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature;
export declare function decodeSignature(
signature: StdSignature,
): {
readonly pubkey: Uint8Array;
readonly signature: Uint8Array;
};
-89
View File
@@ -1,89 +0,0 @@
import { Coin } from "./coins";
import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient";
import { Log } from "./logs";
import { BroadcastMode } from "./restclient";
import { StdFee, StdSignature } from "./types";
export interface SigningCallback {
(signBytes: Uint8Array): Promise<StdSignature>;
}
export interface FeeTable {
readonly upload: StdFee;
readonly init: StdFee;
readonly exec: StdFee;
readonly send: StdFee;
}
export interface UploadMeta {
/** The source URL */
readonly source?: string;
/** The builder tag */
readonly builder?: string;
}
export interface UploadResult {
/** Size of the original wasm code in bytes */
readonly originalSize: number;
/** A hex encoded sha256 checksum of the original wasm code (that is stored on chain) */
readonly originalChecksum: string;
/** Size of the compressed wasm code in bytes */
readonly compressedSize: number;
/** A hex encoded sha256 checksum of the compressed wasm code (that stored in the transaction) */
readonly compressedChecksum: string;
/** The ID of the code asigned by the chain */
readonly codeId: number;
readonly logs: readonly Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export interface InstantiateResult {
/** The address of the newly instantiated contract */
readonly contractAddress: string;
readonly logs: readonly Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export interface ExecuteResult {
readonly logs: readonly Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export declare class SigningCosmWasmClient extends CosmWasmClient {
readonly senderAddress: string;
private readonly signCallback;
private readonly fees;
/**
* Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient.
*
* This instance does a lot of caching. In order to benefit from that you should try to use one instance
* for the lifetime of your application. When switching backends, a new instance must be created.
*
* @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API)
* @param senderAddress The address that will sign and send transactions using this instance
* @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction.
* @param customFees The fees that are paid for transactions
* @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns
*/
constructor(
apiUrl: string,
senderAddress: string,
signCallback: SigningCallback,
customFees?: Partial<FeeTable>,
broadcastMode?: BroadcastMode,
);
getNonce(address?: string): Promise<GetNonceResult>;
getAccount(address?: string): Promise<Account | undefined>;
/** Uploads code and returns a receipt, including the code ID */
upload(wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise<UploadResult>;
instantiate(
codeId: number,
initMsg: object,
label: string,
memo?: string,
transferAmount?: readonly Coin[],
): Promise<InstantiateResult>;
execute(
contractAddress: string,
handleMsg: object,
memo?: string,
transferAmount?: readonly Coin[],
): Promise<ExecuteResult>;
sendTokens(recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise<PostTxResult>;
}
-123
View File
@@ -1,123 +0,0 @@
import { Coin } from "./coins";
/** An Amino/Cosmos SDK StdTx */
export interface StdTx {
readonly msg: ReadonlyArray<Msg>;
readonly fee: StdFee;
readonly signatures: ReadonlyArray<StdSignature>;
readonly memo: string | undefined;
}
export declare function isStdTx(txValue: unknown): txValue is StdTx;
export interface CosmosSdkTx {
readonly type: string;
readonly value: StdTx;
}
interface MsgTemplate {
readonly type: string;
readonly value: any;
}
/** A Cosmos SDK token transfer message */
export interface MsgSend extends MsgTemplate {
readonly type: "cosmos-sdk/MsgSend";
readonly value: {
/** Bech32 account address */
readonly from_address: string;
/** Bech32 account address */
readonly to_address: string;
readonly amount: ReadonlyArray<Coin>;
};
}
/**
* Uploads Wam code to the chain
*
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17
*/
export interface MsgStoreCode extends MsgTemplate {
readonly type: "wasm/store-code";
readonly value: {
/** Bech32 account address */
readonly sender: string;
/** Base64 encoded Wasm */
readonly wasm_byte_code: string;
/** A valid URI reference to the contract's source code. Can be empty. */
readonly source: string;
/** A docker tag. Can be empty. */
readonly builder: string;
};
}
/**
* Creates an instance of contract that was uploaded before.
*
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73
*/
export interface MsgInstantiateContract extends MsgTemplate {
readonly type: "wasm/instantiate";
readonly value: {
/** Bech32 account address */
readonly sender: string;
/** ID of the Wasm code that was uploaded before */
readonly code_id: string;
/** Human-readable label for this contract */
readonly label: string;
/** Init message as JavaScript object */
readonly init_msg: any;
readonly init_funds: ReadonlyArray<Coin>;
};
}
/**
* Creates an instance of contract that was uploaded before.
*
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103
*/
export interface MsgExecuteContract extends MsgTemplate {
readonly type: "wasm/execute";
readonly value: {
/** Bech32 account address */
readonly sender: string;
/** Bech32 account address */
readonly contract: string;
/** Handle message as JavaScript object */
readonly msg: any;
readonly sent_funds: ReadonlyArray<Coin>;
};
}
export declare type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate;
export declare function isMsgSend(msg: Msg): msg is MsgSend;
export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode;
export declare function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract;
export declare function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract;
export interface StdFee {
readonly amount: ReadonlyArray<Coin>;
readonly gas: string;
}
export interface StdSignature {
readonly pub_key: PubKey;
readonly signature: string;
}
export interface PubKey {
readonly type: string;
readonly value: string;
}
export declare const pubkeyType: {
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */
secp256k1: "tendermint/PubKeySecp256k1";
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */
ed25519: "tendermint/PubKeyEd25519";
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */
sr25519: "tendermint/PubKeySr25519";
};
export declare const pubkeyTypes: readonly string[];
export interface WasmData {
readonly key: string;
readonly val: string;
}
export interface Model {
readonly key: Uint8Array;
readonly val: Uint8Array;
}
export declare function parseWasmData({ key, val }: WasmData): Model;
/**
* An object containing a parsed JSON document. The result of JSON.parse().
* This doen't privide any type safety over `any` but expresses intent in the code.
*/
export declare type JsonObject = any;
export {};
-19
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"])],
},
];