Remove @cosmwasm/sdk
This commit is contained in:
parent
cd632a290e
commit
0fe3e5eb4a
@ -1 +0,0 @@
|
||||
../../.eslintignore
|
||||
3
packages/sdk/.gitignore
vendored
3
packages/sdk/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
build/
|
||||
dist/
|
||||
docs/
|
||||
@ -1,12 +0,0 @@
|
||||
# @cosmwasm/sdk
|
||||
|
||||
[](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)).
|
||||
@ -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();
|
||||
@ -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"],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
Directory used to trigger lerna package updates for all packages
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
@ -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,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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}"`),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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 +0,0 @@
|
||||
describe("encoding", () => {});
|
||||
@ -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));
|
||||
}
|
||||
@ -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";
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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
@ -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)));
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
@ -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==",
|
||||
),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
4
packages/sdk/src/testdata/contract.json
vendored
4
packages/sdk/src/testdata/contract.json
vendored
File diff suppressed because one or more lines are too long
44
packages/sdk/src/testdata/cosmoshub.json
vendored
44
packages/sdk/src/testdata/cosmoshub.json
vendored
@ -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
packages/sdk/src/testdata/txresponse1.json
vendored
57
packages/sdk/src/testdata/txresponse1.json
vendored
@ -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
packages/sdk/src/testdata/txresponse2.json
vendored
57
packages/sdk/src/testdata/txresponse2.json
vendored
@ -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
packages/sdk/src/testdata/txresponse3.json
vendored
57
packages/sdk/src/testdata/txresponse3.json
vendored
@ -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"
|
||||
}
|
||||
@ -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];
|
||||
}
|
||||
@ -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;
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "build",
|
||||
"declarationDir": "build/types",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
@ -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
packages/sdk/types/address.d.ts
vendored
3
packages/sdk/types/address.d.ts
vendored
@ -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
packages/sdk/types/builder.d.ts
vendored
1
packages/sdk/types/builder.d.ts
vendored
@ -1 +0,0 @@
|
||||
export declare function isValidBuilder(builder: string): boolean;
|
||||
8
packages/sdk/types/coins.d.ts
vendored
8
packages/sdk/types/coins.d.ts
vendored
@ -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
packages/sdk/types/cosmwasmclient.d.ts
vendored
175
packages/sdk/types/cosmwasmclient.d.ts
vendored
@ -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
packages/sdk/types/decoding.d.ts
vendored
2
packages/sdk/types/decoding.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
import { StdTx } from "./types";
|
||||
export declare function unmarshalTx(data: Uint8Array): StdTx;
|
||||
10
packages/sdk/types/encoding.d.ts
vendored
10
packages/sdk/types/encoding.d.ts
vendored
@ -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
packages/sdk/types/index.d.ts
vendored
40
packages/sdk/types/index.d.ts
vendored
@ -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
packages/sdk/types/logs.d.ts
vendored
28
packages/sdk/types/logs.d.ts
vendored
@ -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
packages/sdk/types/pen.d.ts
vendored
33
packages/sdk/types/pen.d.ts
vendored
@ -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
packages/sdk/types/pubkey.d.ts
vendored
4
packages/sdk/types/pubkey.d.ts
vendored
@ -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
packages/sdk/types/restclient.d.ts
vendored
231
packages/sdk/types/restclient.d.ts
vendored
@ -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
packages/sdk/types/sequence.d.ts
vendored
19
packages/sdk/types/sequence.d.ts
vendored
@ -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
packages/sdk/types/signature.d.ts
vendored
14
packages/sdk/types/signature.d.ts
vendored
@ -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
packages/sdk/types/signingcosmwasmclient.d.ts
vendored
89
packages/sdk/types/signingcosmwasmclient.d.ts
vendored
@ -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
packages/sdk/types/types.d.ts
vendored
123
packages/sdk/types/types.d.ts
vendored
@ -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 {};
|
||||
@ -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"])],
|
||||
},
|
||||
];
|
||||
Loading…
Reference in New Issue
Block a user