Merge pull request #571 from cosmos/491-wasm-stargate
Add cosmwasm-stargate package
This commit is contained in:
commit
cd3b99bda5
@ -6,6 +6,7 @@
|
||||
@cosmjs/launchpad instead.
|
||||
- @cosmjs/cosmwasm: Export `JsonObject`, `ChangeAdminResult` and `WasmData`
|
||||
types as well as `isValidBuilder` and `parseWasmData` functions.
|
||||
- @cosmjs/cosmwasm-stargate: Add new package for CosmWasm Stargate support.
|
||||
- @cosmjs/launchpad: Add `Secp256k1Wallet` to manage a single raw secp256k1
|
||||
keypair.
|
||||
- @cosmjs/launchpad: `OfflineSigner` type’s `sign` method renamed `signAmino`
|
||||
@ -25,6 +26,7 @@
|
||||
which allows skipping the auto-detection.
|
||||
- @cosmjs/tendermint-rpc: Remove export `v0_33` in favour of `adaptor33` and
|
||||
`adaptor34`. Export the `Adaptor` type.
|
||||
- @cosmjs/tendermint-rpc: Export `DateTime` class.
|
||||
|
||||
## 0.23.1 (2020-10-27)
|
||||
|
||||
|
||||
1
packages/cosmwasm-stargate/.eslintignore
Symbolic link
1
packages/cosmwasm-stargate/.eslintignore
Symbolic link
@ -0,0 +1 @@
|
||||
../../.eslintignore
|
||||
7
packages/cosmwasm-stargate/.gitignore
vendored
Normal file
7
packages/cosmwasm-stargate/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
build/
|
||||
dist/
|
||||
docs/
|
||||
|
||||
# protobuf code generation
|
||||
proto/
|
||||
tmp/
|
||||
1
packages/cosmwasm-stargate/.nycrc.yml
Symbolic link
1
packages/cosmwasm-stargate/.nycrc.yml
Symbolic link
@ -0,0 +1 @@
|
||||
../../.nycrc.yml
|
||||
27
packages/cosmwasm-stargate/README.md
Normal file
27
packages/cosmwasm-stargate/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# @cosmjs/cosmwasm-stargate
|
||||
|
||||
[](https://www.npmjs.com/package/@cosmjs/cosmwasm-stargate)
|
||||
|
||||
An SDK to build CosmWasm clients.
|
||||
|
||||
## Compatibility
|
||||
|
||||
| CosmWasm | x/wasm | @cosmjs/cosmwasm-stargate |
|
||||
| -------- | ------ | ------------------------- |
|
||||
| 0.12 | 0.13 | `^0.24.0` |
|
||||
|
||||
## Development
|
||||
|
||||
Updating Hackatom development contract in `src/testdata/contract.json`:
|
||||
|
||||
```sh
|
||||
cd packages/cosmwasm-stargate
|
||||
export HACKATOM_URL=https://github.com/CosmWasm/cosmwasm/releases/download/v0.12.0/hackatom.wasm
|
||||
echo "{\"// source\": \"$HACKATOM_URL\", \"data\": \"$(curl -sS --location $HACKATOM_URL | base64)\" }" | jq > src/testdata/contract.json
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This package is part of the cosmjs repository, licensed under the Apache License
|
||||
2.0 (see [NOTICE](https://github.com/CosmWasm/cosmjs/blob/master/NOTICE) and
|
||||
[LICENSE](https://github.com/CosmWasm/cosmjs/blob/master/LICENSE)).
|
||||
33
packages/cosmwasm-stargate/jasmine-testrunner.js
Executable file
33
packages/cosmwasm-stargate/jasmine-testrunner.js
Executable file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
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,
|
||||
spec: {
|
||||
...defaultSpecReporterConfig.spec,
|
||||
displaySuccessful: !process.argv.includes("--quiet"),
|
||||
},
|
||||
});
|
||||
|
||||
// initialize and execute
|
||||
jasmine.env.clearReporters();
|
||||
jasmine.addReporter(reporter);
|
||||
jasmine.execute();
|
||||
47
packages/cosmwasm-stargate/karma.conf.js
Normal file
47
packages/cosmwasm-stargate/karma.conf.js
Normal file
@ -0,0 +1,47 @@
|
||||
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,
|
||||
});
|
||||
};
|
||||
68
packages/cosmwasm-stargate/package.json
Normal file
68
packages/cosmwasm-stargate/package.json
Normal file
@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "@cosmjs/cosmwasm-stargate",
|
||||
"version": "0.24.0-alpha.10",
|
||||
"description": "CosmWasm SDK",
|
||||
"contributors": [
|
||||
"Will Clark <willclarktech@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/CosmWasm/cosmjs/tree/master/packages/cosmwasm-stargate"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docs": "typedoc --options typedoc.js",
|
||||
"format": "prettier --write --loglevel warn \"./src/**/*.ts\"",
|
||||
"format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"",
|
||||
"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 ./types/*.spec.d.ts ./types/*/*.spec.d.ts",
|
||||
"format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"",
|
||||
"prebuild": "shx rm -rf ./build",
|
||||
"build": "tsc && shx mkdir -p build/codec/generated && shx cp ./src/codec/generated/*.js ./build/codec/generated",
|
||||
"postbuild": "shx mkdir -p ./build/types/codec/generated && shx cp ./src/codec/generated/*.d.ts ./build/types/codec/generated && yarn move-types && yarn format-types",
|
||||
"build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build",
|
||||
"test-node": "node jasmine-testrunner.js",
|
||||
"test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox",
|
||||
"test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless",
|
||||
"test": "yarn build-or-skip && yarn test-node",
|
||||
"coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet",
|
||||
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js",
|
||||
"preget-proto": "shx rm -rf proto",
|
||||
"get-proto": "WASM_REF=v0.13.0 COSMOS_REF=v0.40.0-rc3 ./scripts/get-proto.sh",
|
||||
"predefine-proto": "./scripts/predefine-proto.sh",
|
||||
"define-proto": "./scripts/define-proto.sh",
|
||||
"postdefine-proto": "prettier --write \"src/codec/generated/codecimpl.*\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/cosmwasm": "^0.24.0-alpha.10",
|
||||
"@cosmjs/crypto": "^0.24.0-alpha.10",
|
||||
"@cosmjs/encoding": "^0.24.0-alpha.10",
|
||||
"@cosmjs/launchpad": "^0.24.0-alpha.10",
|
||||
"@cosmjs/math": "^0.24.0-alpha.10",
|
||||
"@cosmjs/proto-signing": "^0.24.0-alpha.10",
|
||||
"@cosmjs/stargate": "^0.24.0-alpha.10",
|
||||
"@cosmjs/tendermint-rpc": "^0.24.0-alpha.10",
|
||||
"@cosmjs/utils": "^0.24.0-alpha.10",
|
||||
"long": "^4.0.0",
|
||||
"pako": "^2.0.2",
|
||||
"protobufjs": "~6.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pako": "^1.0.1",
|
||||
"readonly-date": "^1.0.0"
|
||||
}
|
||||
}
|
||||
16
packages/cosmwasm-stargate/scripts/define-proto.sh
Executable file
16
packages/cosmwasm-stargate/scripts/define-proto.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
set -o errexit -o nounset -o pipefail
|
||||
command -v shellcheck >/dev/null && shellcheck "$0"
|
||||
|
||||
TMP_DIR="./tmp"
|
||||
JS_SOURCE_FILE="$TMP_DIR/codecimpl.js"
|
||||
DEFINITIONS_FILE="$TMP_DIR/codecimpl.d.ts"
|
||||
OUTPUT_DIR="./src/codec/generated/"
|
||||
|
||||
yarn pbts "$JS_SOURCE_FILE" -o "$DEFINITIONS_FILE"
|
||||
# Remove comments after using them for the .d.ts
|
||||
# Note "When input files are specified on the command line, tsconfig.json files are ignored." (https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)
|
||||
yarn tsc --removeComments --target es2017 --module commonjs --outDir "$OUTPUT_DIR" --allowJs "$JS_SOURCE_FILE"
|
||||
|
||||
cp "$DEFINITIONS_FILE" "$OUTPUT_DIR"
|
||||
rm "$DEFINITIONS_FILE" "$JS_SOURCE_FILE"
|
||||
31
packages/cosmwasm-stargate/scripts/get-proto.sh
Executable file
31
packages/cosmwasm-stargate/scripts/get-proto.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
set -o errexit -o nounset -o pipefail
|
||||
command -v shellcheck >/dev/null && shellcheck "$0"
|
||||
|
||||
PROTO_DIR="./proto"
|
||||
|
||||
COSMOS_DIR="$PROTO_DIR/cosmos"
|
||||
COSMOS_SDK_DIR="$COSMOS_DIR/cosmos-sdk"
|
||||
COSMOS_SDK_ZIP_FILE="$COSMOS_DIR/tmp.zip"
|
||||
COSMOS_REF=${COSMOS_REF:-"master"}
|
||||
COSMOS_SUFFIX=${COSMOS_REF}
|
||||
[[ $COSMOS_SUFFIX =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]] && COSMOS_SUFFIX=${COSMOS_SUFFIX#v}
|
||||
|
||||
COSMWASM_DIR="$PROTO_DIR/cosmwasm"
|
||||
WASMD_DIR="$COSMWASM_DIR/wasmd"
|
||||
WASMD_ZIP_FILE="$COSMWASM_DIR/tmp.zip"
|
||||
WASM_REF=${WASM_REF:-"master"}
|
||||
WASM_SUFFIX=${WASM_REF}
|
||||
[[ $WASM_SUFFIX =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]] && WASM_SUFFIX=${WASM_SUFFIX#v}
|
||||
|
||||
mkdir -p "$COSMOS_DIR"
|
||||
wget -qO "$COSMOS_SDK_ZIP_FILE" "https://github.com/cosmos/cosmos-sdk/archive/$COSMOS_REF.zip"
|
||||
unzip "$COSMOS_SDK_ZIP_FILE" "*.proto" -d "$COSMOS_DIR"
|
||||
mv "$COSMOS_SDK_DIR-$COSMOS_SUFFIX" "$COSMOS_SDK_DIR"
|
||||
rm "$COSMOS_SDK_ZIP_FILE"
|
||||
|
||||
mkdir -p "$COSMWASM_DIR"
|
||||
wget -qO "$WASMD_ZIP_FILE" "https://github.com/cosmwasm/wasmd/archive/$WASM_REF.zip"
|
||||
unzip "$WASMD_ZIP_FILE" "*.proto" -d "$COSMWASM_DIR"
|
||||
mv "$WASMD_DIR-$WASM_SUFFIX" "$WASMD_DIR"
|
||||
rm "$WASMD_ZIP_FILE"
|
||||
30
packages/cosmwasm-stargate/scripts/predefine-proto.sh
Executable file
30
packages/cosmwasm-stargate/scripts/predefine-proto.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
set -o errexit -o nounset -o pipefail
|
||||
command -v shellcheck >/dev/null && shellcheck "$0"
|
||||
|
||||
GENERATED_DIR="./tmp"
|
||||
ROOT_PROTO_DIR="./proto"
|
||||
WASM_PROTO_DIR="$ROOT_PROTO_DIR/cosmwasm/wasmd/x/wasm"
|
||||
COSMOS_PROTO_DIR="$ROOT_PROTO_DIR/cosmos/cosmos-sdk/proto/cosmos"
|
||||
|
||||
mkdir -p "$GENERATED_DIR"
|
||||
# Can't use --sparse for some reason. Seems related to https://github.com/protobufjs/protobuf.js/issues/1165
|
||||
yarn pbjs \
|
||||
-t static-module \
|
||||
--es6 \
|
||||
-w commonjs \
|
||||
-o "$GENERATED_DIR/codecimpl.js" \
|
||||
--no-beautify \
|
||||
--no-delimited \
|
||||
--no-verify \
|
||||
--no-convert \
|
||||
--force-long \
|
||||
"$WASM_PROTO_DIR/internal/types/msg.proto" \
|
||||
"$WASM_PROTO_DIR/internal/types/query.proto" \
|
||||
"$WASM_PROTO_DIR/internal/types/types.proto" \
|
||||
"$COSMOS_PROTO_DIR/base/v1beta1/coin.proto" \
|
||||
"$COSMOS_PROTO_DIR/base/query/v1beta1/pagination.proto"
|
||||
|
||||
# Work around https://github.com/protobufjs/protobuf.js/issues/1477
|
||||
# shellcheck disable=SC2016
|
||||
sed -i "" -e 's/^const \$root =.*$/const \$root = {};/' "$GENERATED_DIR/codecimpl.js"
|
||||
4435
packages/cosmwasm-stargate/src/codec/generated/codecimpl.d.ts
vendored
Normal file
4435
packages/cosmwasm-stargate/src/codec/generated/codecimpl.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3615
packages/cosmwasm-stargate/src/codec/generated/codecimpl.js
Normal file
3615
packages/cosmwasm-stargate/src/codec/generated/codecimpl.js
Normal file
File diff suppressed because it is too large
Load Diff
9
packages/cosmwasm-stargate/src/codec/index.ts
Normal file
9
packages/cosmwasm-stargate/src/codec/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import Long from "long";
|
||||
import protobuf from "protobufjs/minimal";
|
||||
|
||||
// Ensure the protobuf module has a Long implementation, which otherwise only works
|
||||
// in Node.js (see https://github.com/protobufjs/protobuf.js/issues/921#issuecomment-334925145)
|
||||
protobuf.util.Long = Long;
|
||||
protobuf.configure();
|
||||
|
||||
export { cosmwasm } from "./generated/codecimpl";
|
||||
437
packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts
Normal file
437
packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts
Normal file
@ -0,0 +1,437 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { Code } from "@cosmjs/cosmwasm";
|
||||
import { sha256 } from "@cosmjs/crypto";
|
||||
import { Bech32, fromAscii, fromBase64, fromHex, toAscii, toBase64 } from "@cosmjs/encoding";
|
||||
import { coins, logs, StdFee } from "@cosmjs/launchpad";
|
||||
import { Int53 } from "@cosmjs/math";
|
||||
import {
|
||||
DirectSecp256k1HdWallet,
|
||||
encodePubkey,
|
||||
makeAuthInfoBytes,
|
||||
makeSignDoc,
|
||||
Registry,
|
||||
} from "@cosmjs/proto-signing";
|
||||
import { assertIsBroadcastTxSuccess, codec, parseRawLog } from "@cosmjs/stargate";
|
||||
import { assert, sleep } from "@cosmjs/utils";
|
||||
import { ReadonlyDate } from "readonly-date";
|
||||
|
||||
import { CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient";
|
||||
import { SigningCosmWasmClient } from "./signingcosmwasmclient";
|
||||
import {
|
||||
alice,
|
||||
deployedHackatom,
|
||||
getHackatom,
|
||||
makeRandomAddress,
|
||||
pendingWithoutWasmd,
|
||||
tendermintIdMatcher,
|
||||
unused,
|
||||
wasmd,
|
||||
wasmdEnabled,
|
||||
} from "./testutils.spec";
|
||||
|
||||
const { TxRaw } = codec.cosmos.tx.v1beta1;
|
||||
|
||||
interface HackatomInstance {
|
||||
readonly initMsg: {
|
||||
readonly verifier: string;
|
||||
readonly beneficiary: string;
|
||||
};
|
||||
readonly address: string;
|
||||
}
|
||||
|
||||
describe("CosmWasmClient", () => {
|
||||
describe("connect", () => {
|
||||
it("can be constructed", async () => {
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getChainId", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
expect(await client.getChainId()).toEqual(wasmd.chainId);
|
||||
});
|
||||
|
||||
it("caches chain ID", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const openedClient = (client as unknown) as PrivateCosmWasmClient;
|
||||
const getCodeSpy = spyOn(openedClient.tmClient, "status").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("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
|
||||
const height1 = await client.getHeight();
|
||||
expect(height1).toBeGreaterThan(0);
|
||||
|
||||
await sleep(wasmd.blockTime * 1.4); // tolerate chain being 40% slower than expected
|
||||
|
||||
const height2 = await client.getHeight();
|
||||
expect(height2).toBeGreaterThanOrEqual(height1 + 1);
|
||||
expect(height2).toBeLessThanOrEqual(height1 + 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSequence", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
expect(await client.getSequence(unused.address)).toEqual({
|
||||
accountNumber: unused.accountNumber,
|
||||
sequence: unused.sequence,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null for missing accounts", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const missing = makeRandomAddress();
|
||||
expect(await client.getSequence(missing)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAccount", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
expect(await client.getAccount(unused.address)).toEqual({
|
||||
address: unused.address,
|
||||
accountNumber: unused.accountNumber,
|
||||
sequence: unused.sequence,
|
||||
pubkey: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null for missing accounts", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const missing = makeRandomAddress();
|
||||
expect(await client.getAccount(missing)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getBlock", () => {
|
||||
it("works for latest block", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(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 = await CosmWasmClient.connect(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("broadcastTx", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const registry = new Registry();
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
const sendMsg = {
|
||||
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
|
||||
value: {
|
||||
fromAddress: alice.address0,
|
||||
toAddress: makeRandomAddress(),
|
||||
amount: coins(1234567, "ucosm"),
|
||||
},
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: coins(5000, "ucosm"),
|
||||
gas: "890000",
|
||||
};
|
||||
|
||||
const chainId = await client.getChainId();
|
||||
const sequenceResponse = await client.getSequence(alice.address0);
|
||||
assert(sequenceResponse);
|
||||
const { accountNumber, sequence } = sequenceResponse;
|
||||
const pubkeyAny = encodePubkey(alice.pubkey0);
|
||||
const txBody = {
|
||||
messages: [sendMsg],
|
||||
memo: memo,
|
||||
};
|
||||
const txBodyBytes = registry.encode({
|
||||
typeUrl: "/cosmos.tx.v1beta1.TxBody",
|
||||
value: txBody,
|
||||
});
|
||||
const gasLimit = Int53.fromString(fee.gas).toNumber();
|
||||
const authInfoBytes = makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence);
|
||||
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||
const { signed, signature } = await wallet.signDirect(alice.address0, signDoc);
|
||||
const txRaw = TxRaw.create({
|
||||
bodyBytes: signed.bodyBytes,
|
||||
authInfoBytes: signed.authInfoBytes,
|
||||
signatures: [fromBase64(signature.signature)],
|
||||
});
|
||||
const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish());
|
||||
const result = await client.broadcastTx(signedTx);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
const amountAttr = logs.findAttribute(parseRawLog(result.rawLog), "transfer", "amount");
|
||||
expect(amountAttr.value).toEqual("1234567ucosm");
|
||||
expect(result.transactionHash).toMatch(/^[0-9A-F]{64}$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCodes", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const result = await client.getCodes();
|
||||
expect(result.length).toBeGreaterThanOrEqual(1);
|
||||
const [first] = result;
|
||||
expect(first).toEqual({
|
||||
id: deployedHackatom.codeId,
|
||||
source: deployedHackatom.source,
|
||||
builder: deployedHackatom.builder,
|
||||
checksum: deployedHackatom.checksum,
|
||||
creator: alice.address0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCodeDetails", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const result = await client.getCodeDetails(1);
|
||||
|
||||
const expectedInfo: Code = {
|
||||
id: deployedHackatom.codeId,
|
||||
source: deployedHackatom.source,
|
||||
builder: deployedHackatom.builder,
|
||||
checksum: deployedHackatom.checksum,
|
||||
creator: alice.address0,
|
||||
};
|
||||
|
||||
// check info
|
||||
expect(result).toEqual(jasmine.objectContaining(expectedInfo));
|
||||
// check data
|
||||
expect(sha256(result.data)).toEqual(fromHex(expectedInfo.checksum));
|
||||
});
|
||||
|
||||
it("caches downloads", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const openedClient = (client as unknown) as PrivateCosmWasmClient;
|
||||
const getCodeSpy = spyOn(openedClient.queryClient.unverified.wasm, "getCode").and.callThrough();
|
||||
|
||||
const result1 = await client.getCodeDetails(deployedHackatom.codeId); // from network
|
||||
const result2 = await client.getCodeDetails(deployedHackatom.codeId); // from cache
|
||||
expect(result2).toEqual(result1);
|
||||
|
||||
expect(getCodeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getContracts", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const result = await client.getContracts(1);
|
||||
expect(result.length).toBeGreaterThanOrEqual(3);
|
||||
const [zero, one, two] = result;
|
||||
expect(zero).toEqual({
|
||||
address: deployedHackatom.instances[0].address,
|
||||
codeId: deployedHackatom.codeId,
|
||||
creator: alice.address0,
|
||||
admin: undefined,
|
||||
label: deployedHackatom.instances[0].label,
|
||||
});
|
||||
expect(one).toEqual({
|
||||
address: deployedHackatom.instances[1].address,
|
||||
codeId: deployedHackatom.codeId,
|
||||
creator: alice.address0,
|
||||
admin: undefined,
|
||||
label: deployedHackatom.instances[1].label,
|
||||
});
|
||||
expect(two).toEqual({
|
||||
address: deployedHackatom.instances[2].address,
|
||||
codeId: deployedHackatom.codeId,
|
||||
creator: alice.address0,
|
||||
admin: alice.address1,
|
||||
label: deployedHackatom.instances[2].label,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getContract", () => {
|
||||
it("works for instance without admin", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const zero = await client.getContract(deployedHackatom.instances[0].address);
|
||||
expect(zero).toEqual({
|
||||
address: deployedHackatom.instances[0].address,
|
||||
codeId: deployedHackatom.codeId,
|
||||
creator: alice.address0,
|
||||
label: deployedHackatom.instances[0].label,
|
||||
admin: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("works for instance with admin", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const two = await client.getContract(deployedHackatom.instances[2].address);
|
||||
expect(two).toEqual(
|
||||
jasmine.objectContaining({
|
||||
address: deployedHackatom.instances[2].address,
|
||||
codeId: deployedHackatom.codeId,
|
||||
creator: alice.address0,
|
||||
label: deployedHackatom.instances[2].label,
|
||||
admin: alice.address1,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("queryContractRaw", () => {
|
||||
const configKey = toAscii("config");
|
||||
const otherKey = toAscii("this_does_not_exist");
|
||||
let contract: HackatomInstance | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (wasmdEnabled()) {
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const { codeId } = await client.upload(alice.address0, getHackatom().data);
|
||||
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
|
||||
const { contractAddress } = await client.instantiate(
|
||||
alice.address0,
|
||||
codeId,
|
||||
initMsg,
|
||||
"random hackatom",
|
||||
);
|
||||
contract = { initMsg: initMsg, address: contractAddress };
|
||||
}
|
||||
});
|
||||
|
||||
it("can query existing key", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(contract);
|
||||
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const raw = await client.queryContractRaw(contract.address, configKey);
|
||||
assert(raw, "must get result");
|
||||
expect(JSON.parse(fromAscii(raw))).toEqual({
|
||||
verifier: toBase64(Bech32.decode(contract.initMsg.verifier).data),
|
||||
beneficiary: toBase64(Bech32.decode(contract.initMsg.beneficiary).data),
|
||||
funder: toBase64(Bech32.decode(alice.address0).data),
|
||||
});
|
||||
});
|
||||
|
||||
it("can query non-existent key", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(contract);
|
||||
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const raw = await client.queryContractRaw(contract.address, otherKey);
|
||||
assert(raw, "must get result");
|
||||
expect(Uint8Array.from(raw)).toEqual(new Uint8Array());
|
||||
});
|
||||
|
||||
it("errors for non-existent contract", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(contract);
|
||||
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
await expectAsync(client.queryContractRaw(nonExistentAddress, configKey)).toBeRejectedWithError(
|
||||
/not found/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("queryContractSmart", () => {
|
||||
let contract: HackatomInstance | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (wasmdEnabled()) {
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const { codeId } = await client.upload(alice.address0, getHackatom().data);
|
||||
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
|
||||
const { contractAddress } = await client.instantiate(
|
||||
alice.address0,
|
||||
codeId,
|
||||
initMsg,
|
||||
"a different hackatom",
|
||||
);
|
||||
contract = { initMsg: initMsg, address: contractAddress };
|
||||
}
|
||||
});
|
||||
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(contract);
|
||||
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
const { data } = await client.queryContractSmart(contract.address, { verifier: {} });
|
||||
const parsedData = JSON.parse(fromAscii(data));
|
||||
expect(parsedData).toEqual({ verifier: contract.initMsg.verifier });
|
||||
});
|
||||
|
||||
it("errors for malformed query message", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(contract);
|
||||
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
await expectAsync(client.queryContractSmart(contract.address, { broken: {} })).toBeRejectedWithError(
|
||||
/Error parsing into type hackatom::contract::QueryMsg: unknown variant/i,
|
||||
);
|
||||
});
|
||||
|
||||
it("errors for non-existent contract", async () => {
|
||||
pendingWithoutWasmd();
|
||||
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const client = await CosmWasmClient.connect(wasmd.endpoint);
|
||||
await expectAsync(
|
||||
client.queryContractSmart(nonExistentAddress, { verifier: {} }),
|
||||
).toBeRejectedWithError(/not found/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
343
packages/cosmwasm-stargate/src/cosmwasmclient.ts
Normal file
343
packages/cosmwasm-stargate/src/cosmwasmclient.ts
Normal file
@ -0,0 +1,343 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry, JsonObject } from "@cosmjs/cosmwasm";
|
||||
import { fromAscii, toHex } from "@cosmjs/encoding";
|
||||
import {
|
||||
Block,
|
||||
Coin,
|
||||
isSearchByHeightQuery,
|
||||
isSearchByIdQuery,
|
||||
isSearchBySentFromOrToQuery,
|
||||
isSearchByTagsQuery,
|
||||
SearchTxFilter,
|
||||
SearchTxQuery,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { Uint53 } from "@cosmjs/math";
|
||||
import {
|
||||
Account,
|
||||
accountFromProto,
|
||||
AuthExtension,
|
||||
BankExtension,
|
||||
BroadcastTxResponse,
|
||||
codec,
|
||||
coinFromProto,
|
||||
IndexedTx,
|
||||
QueryClient,
|
||||
SequenceResponse,
|
||||
setupAuthExtension,
|
||||
setupBankExtension,
|
||||
} from "@cosmjs/stargate";
|
||||
import {
|
||||
adaptor34,
|
||||
broadcastTxCommitSuccess,
|
||||
Client as TendermintClient,
|
||||
DateTime,
|
||||
QueryString,
|
||||
} from "@cosmjs/tendermint-rpc";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
|
||||
import { cosmwasm } from "./codec";
|
||||
import { setupWasmExtension, WasmExtension } from "./queries";
|
||||
|
||||
type ICodeInfoResponse = cosmwasm.wasm.v1beta1.ICodeInfoResponse;
|
||||
type ContractCodeHistoryOperationType = cosmwasm.wasm.v1beta1.ContractCodeHistoryOperationType;
|
||||
|
||||
const { TxMsgData } = codec.cosmos.base.abci.v1beta1;
|
||||
const { ContractCodeHistoryOperationType } = cosmwasm.wasm.v1beta1;
|
||||
|
||||
/** Use for testing only */
|
||||
export interface PrivateCosmWasmClient {
|
||||
readonly tmClient: TendermintClient;
|
||||
readonly queryClient: QueryClient & AuthExtension & BankExtension & WasmExtension;
|
||||
}
|
||||
|
||||
export class CosmWasmClient {
|
||||
private readonly tmClient: TendermintClient;
|
||||
private readonly queryClient: QueryClient & AuthExtension & BankExtension & WasmExtension;
|
||||
private readonly codesCache = new Map<number, CodeDetails>();
|
||||
private chainId: string | undefined;
|
||||
|
||||
public static async connect(endpoint: string): Promise<CosmWasmClient> {
|
||||
const tmClient = await TendermintClient.connect(endpoint, adaptor34);
|
||||
return new CosmWasmClient(tmClient);
|
||||
}
|
||||
|
||||
protected constructor(tmClient: TendermintClient) {
|
||||
this.tmClient = tmClient;
|
||||
this.queryClient = QueryClient.withExtensions(
|
||||
tmClient,
|
||||
setupAuthExtension,
|
||||
setupBankExtension,
|
||||
setupWasmExtension,
|
||||
);
|
||||
}
|
||||
|
||||
public async getChainId(): Promise<string> {
|
||||
if (!this.chainId) {
|
||||
const response = await this.tmClient.status();
|
||||
const chainId = response.nodeInfo.network;
|
||||
if (!chainId) throw new Error("Chain ID must not be empty");
|
||||
this.chainId = chainId;
|
||||
}
|
||||
|
||||
return this.chainId;
|
||||
}
|
||||
|
||||
public async getHeight(): Promise<number> {
|
||||
const status = await this.tmClient.status();
|
||||
return status.syncInfo.latestBlockHeight;
|
||||
}
|
||||
|
||||
public async getAccount(searchAddress: string): Promise<Account | null> {
|
||||
const account = await this.queryClient.auth.account(searchAddress);
|
||||
return account ? accountFromProto(account) : null;
|
||||
}
|
||||
|
||||
public async getSequence(address: string): Promise<SequenceResponse | null> {
|
||||
const account = await this.getAccount(address);
|
||||
if (account) {
|
||||
return {
|
||||
accountNumber: account.accountNumber,
|
||||
sequence: account.sequence,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async getBlock(height?: number): Promise<Block> {
|
||||
const response = await this.tmClient.block(height);
|
||||
return {
|
||||
id: toHex(response.blockId.hash).toUpperCase(),
|
||||
header: {
|
||||
version: {
|
||||
block: new Uint53(response.block.header.version.block).toString(),
|
||||
app: new Uint53(response.block.header.version.app).toString(),
|
||||
},
|
||||
height: response.block.header.height,
|
||||
chainId: response.block.header.chainId,
|
||||
time: DateTime.encode(response.block.header.time),
|
||||
},
|
||||
txs: response.block.txs,
|
||||
};
|
||||
}
|
||||
|
||||
public async getBalance(address: string, searchDenom: string): Promise<Coin | null> {
|
||||
const balance = await this.queryClient.bank.balance(address, searchDenom);
|
||||
return balance ? coinFromProto(balance) : null;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
let txs: readonly IndexedTx[];
|
||||
|
||||
if (isSearchByIdQuery(query)) {
|
||||
txs = await this.txsQuery(`tx.hash='${query.id}'`);
|
||||
} else if (isSearchByHeightQuery(query)) {
|
||||
txs =
|
||||
query.height >= minHeight && query.height <= maxHeight
|
||||
? await this.txsQuery(`tx.height=${query.height}`)
|
||||
: [];
|
||||
} else if (isSearchBySentFromOrToQuery(query)) {
|
||||
throw new Error(
|
||||
"This type of search query is not yet implemented. See https://github.com/cosmos/cosmjs/issues/533.",
|
||||
);
|
||||
} else if (isSearchByTagsQuery(query)) {
|
||||
throw new Error(
|
||||
"This type of search query is not yet implemented. See https://github.com/cosmos/cosmjs/issues/532.",
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unknown query type");
|
||||
}
|
||||
|
||||
const filtered = txs.filter((tx) => tx.height >= minHeight && tx.height <= maxHeight);
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
this.tmClient.disconnect();
|
||||
}
|
||||
|
||||
public async broadcastTx(tx: Uint8Array): Promise<BroadcastTxResponse> {
|
||||
const response = await this.tmClient.broadcastTxCommit({ tx });
|
||||
if (broadcastTxCommitSuccess(response)) {
|
||||
return {
|
||||
height: response.height,
|
||||
transactionHash: toHex(response.hash).toUpperCase(),
|
||||
rawLog: response.deliverTx?.log,
|
||||
data: response.deliverTx?.data ? TxMsgData.decode(response.deliverTx?.data).data : undefined,
|
||||
};
|
||||
}
|
||||
return response.checkTx.code !== 0
|
||||
? {
|
||||
height: response.height,
|
||||
code: response.checkTx.code,
|
||||
transactionHash: toHex(response.hash).toUpperCase(),
|
||||
rawLog: response.checkTx.log,
|
||||
data: response.checkTx.data ? TxMsgData.decode(response.checkTx.data).data : undefined,
|
||||
}
|
||||
: {
|
||||
height: response.height,
|
||||
code: response.deliverTx?.code,
|
||||
transactionHash: toHex(response.hash).toUpperCase(),
|
||||
rawLog: response.deliverTx?.log,
|
||||
data: response.deliverTx?.data ? TxMsgData.decode(response.deliverTx?.data).data : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
public async getCodes(): Promise<readonly Code[]> {
|
||||
const { codeInfos } = await this.queryClient.unverified.wasm.listCodeInfo();
|
||||
return (codeInfos || []).map(
|
||||
(entry: ICodeInfoResponse): Code => {
|
||||
assert(entry.creator && entry.codeId && entry.dataHash, "entry incomplete");
|
||||
return {
|
||||
id: entry.codeId.toNumber(),
|
||||
creator: entry.creator,
|
||||
checksum: toHex(entry.dataHash),
|
||||
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 { codeInfo, data } = await this.queryClient.unverified.wasm.getCode(codeId);
|
||||
assert(
|
||||
codeInfo && codeInfo.codeId && codeInfo.creator && codeInfo.dataHash && data,
|
||||
"codeInfo missing or incomplete",
|
||||
);
|
||||
const codeDetails: CodeDetails = {
|
||||
id: codeInfo.codeId.toNumber(),
|
||||
creator: codeInfo.creator,
|
||||
checksum: toHex(codeInfo.dataHash),
|
||||
source: codeInfo.source || undefined,
|
||||
builder: codeInfo.builder || undefined,
|
||||
data: data,
|
||||
};
|
||||
this.codesCache.set(codeId, codeDetails);
|
||||
return codeDetails;
|
||||
}
|
||||
|
||||
public async getContracts(codeId: number): Promise<readonly Contract[]> {
|
||||
const { contractInfos } = await this.queryClient.unverified.wasm.listContractsByCodeId(codeId);
|
||||
return (contractInfos || []).map(
|
||||
({ address, contractInfo }): Contract => {
|
||||
assert(address, "address missing");
|
||||
assert(
|
||||
contractInfo && contractInfo.codeId && contractInfo.creator && contractInfo.label,
|
||||
"contractInfo missing or incomplete",
|
||||
);
|
||||
return {
|
||||
address: address,
|
||||
codeId: contractInfo.codeId.toNumber(),
|
||||
creator: contractInfo.creator,
|
||||
admin: contractInfo.admin || undefined,
|
||||
label: contractInfo.label,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error if no contract was found at the address
|
||||
*/
|
||||
public async getContract(address: string): Promise<Contract> {
|
||||
const {
|
||||
address: retrievedAddress,
|
||||
contractInfo,
|
||||
} = await this.queryClient.unverified.wasm.getContractInfo(address);
|
||||
if (!contractInfo) throw new Error(`No contract found at address "${address}"`);
|
||||
assert(retrievedAddress, "address missing");
|
||||
assert(contractInfo.codeId && contractInfo.creator && contractInfo.label, "contractInfo incomplete");
|
||||
return {
|
||||
address: retrievedAddress,
|
||||
codeId: contractInfo.codeId.toNumber(),
|
||||
creator: contractInfo.creator,
|
||||
admin: contractInfo.admin || undefined,
|
||||
label: contractInfo.label,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error if no contract was found at the address
|
||||
*/
|
||||
public async getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]> {
|
||||
const result = await this.queryClient.unverified.wasm.getContractCodeHistory(address);
|
||||
if (!result) throw new Error(`No contract history found for address "${address}"`);
|
||||
const operations: Record<number, "Init" | "Genesis" | "Migrate"> = {
|
||||
[ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT]: "Init",
|
||||
[ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_GENESIS]: "Genesis",
|
||||
[ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_MIGRATE]: "Migrate",
|
||||
};
|
||||
return (result.entries || []).map(
|
||||
(entry): ContractCodeHistoryEntry => {
|
||||
assert(entry.operation && entry.codeId && entry.msg);
|
||||
return {
|
||||
operation: operations[entry.operation],
|
||||
codeId: entry.codeId.toNumber(),
|
||||
msg: JSON.parse(fromAscii(entry.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
|
||||
await this.getContract(address);
|
||||
|
||||
const { data } = await this.queryClient.unverified.wasm.queryContractRaw(address, key);
|
||||
return data ?? 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.
|
||||
*/
|
||||
public async queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject> {
|
||||
try {
|
||||
return await this.queryClient.unverified.wasm.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[]> {
|
||||
const params = {
|
||||
query: query as QueryString,
|
||||
};
|
||||
const results = await this.tmClient.txSearchAll(params);
|
||||
return results.txs.map((tx) => {
|
||||
return {
|
||||
height: tx.height,
|
||||
hash: toHex(tx.hash).toUpperCase(),
|
||||
code: tx.result.code,
|
||||
rawLog: tx.result.log || "",
|
||||
tx: tx.tx,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
3
packages/cosmwasm-stargate/src/index.ts
Normal file
3
packages/cosmwasm-stargate/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * as codec from "./codec";
|
||||
export { CosmWasmClient } from "./cosmwasmclient";
|
||||
export { SigningCosmWasmClient, SigningCosmWasmClientOptions } from "./signingcosmwasmclient";
|
||||
1
packages/cosmwasm-stargate/src/queries/index.ts
Normal file
1
packages/cosmwasm-stargate/src/queries/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { setupWasmExtension, WasmExtension } from "./wasm";
|
||||
447
packages/cosmwasm-stargate/src/queries/wasm.spec.ts
Normal file
447
packages/cosmwasm-stargate/src/queries/wasm.spec.ts
Normal file
@ -0,0 +1,447 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { sha256 } from "@cosmjs/crypto";
|
||||
import { fromAscii, fromHex, toAscii, toHex } from "@cosmjs/encoding";
|
||||
import { Coin, coin, coins, logs, StdFee } from "@cosmjs/launchpad";
|
||||
import { DirectSecp256k1HdWallet, OfflineDirectSigner, Registry } from "@cosmjs/proto-signing";
|
||||
import {
|
||||
assertIsBroadcastTxSuccess,
|
||||
BroadcastTxResponse,
|
||||
parseRawLog,
|
||||
SigningStargateClient,
|
||||
} from "@cosmjs/stargate";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import Long from "long";
|
||||
|
||||
import { cosmwasm } from "../codec";
|
||||
import { SigningCosmWasmClient } from "../signingcosmwasmclient";
|
||||
import {
|
||||
alice,
|
||||
base64Matcher,
|
||||
bech32AddressMatcher,
|
||||
ContractUploadInstructions,
|
||||
getHackatom,
|
||||
makeRandomAddress,
|
||||
makeWasmClient,
|
||||
pendingWithoutWasmd,
|
||||
wasmd,
|
||||
wasmdEnabled,
|
||||
} from "../testutils.spec";
|
||||
|
||||
const { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } = cosmwasm.wasm.v1beta1;
|
||||
|
||||
const registry = new Registry([
|
||||
["/cosmwasm.wasm.v1beta1.MsgExecuteContract", MsgExecuteContract],
|
||||
["/cosmwasm.wasm.v1beta1.MsgStoreCode", MsgStoreCode],
|
||||
["/cosmwasm.wasm.v1beta1.MsgInstantiateContract", MsgInstantiateContract],
|
||||
]);
|
||||
|
||||
async function uploadContract(
|
||||
signer: OfflineDirectSigner,
|
||||
contract: ContractUploadInstructions,
|
||||
): Promise<BroadcastTxResponse> {
|
||||
const memo = "My first contract on chain";
|
||||
const theMsg = {
|
||||
typeUrl: "/cosmwasm.wasm.v1beta1.MsgStoreCode",
|
||||
value: MsgStoreCode.create({
|
||||
sender: alice.address0,
|
||||
wasmByteCode: contract.data,
|
||||
source: contract.source || "",
|
||||
builder: contract.builder || "",
|
||||
}),
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: coins(5000000, "ucosm"),
|
||||
gas: "89000000",
|
||||
};
|
||||
const firstAddress = (await signer.getAccounts())[0].address;
|
||||
const client = await SigningStargateClient.connectWithWallet(wasmd.endpoint, signer, { registry });
|
||||
return client.signAndBroadcast(firstAddress, [theMsg], fee, memo);
|
||||
}
|
||||
|
||||
async function instantiateContract(
|
||||
signer: OfflineDirectSigner,
|
||||
codeId: number,
|
||||
beneficiaryAddress: string,
|
||||
transferAmount?: readonly Coin[],
|
||||
): Promise<BroadcastTxResponse> {
|
||||
const memo = "Create an escrow instance";
|
||||
const theMsg = {
|
||||
typeUrl: "/cosmwasm.wasm.v1beta1.MsgInstantiateContract",
|
||||
value: MsgInstantiateContract.create({
|
||||
sender: alice.address0,
|
||||
codeId: Long.fromNumber(codeId),
|
||||
label: "my escrow",
|
||||
initMsg: toAscii(
|
||||
JSON.stringify({
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
}),
|
||||
),
|
||||
initFunds: transferAmount ? [...transferAmount] : [],
|
||||
}),
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: coins(5000000, "ucosm"),
|
||||
gas: "89000000",
|
||||
};
|
||||
|
||||
const firstAddress = (await signer.getAccounts())[0].address;
|
||||
const client = await SigningStargateClient.connectWithWallet(wasmd.endpoint, signer, { registry });
|
||||
return client.signAndBroadcast(firstAddress, [theMsg], fee, memo);
|
||||
}
|
||||
|
||||
async function executeContract(
|
||||
signer: OfflineDirectSigner,
|
||||
contractAddress: string,
|
||||
msg: Record<string, unknown>,
|
||||
): Promise<BroadcastTxResponse> {
|
||||
const memo = "Time for action";
|
||||
const theMsg = {
|
||||
typeUrl: "/cosmwasm.wasm.v1beta1.MsgExecuteContract",
|
||||
value: MsgExecuteContract.create({
|
||||
sender: alice.address0,
|
||||
contract: contractAddress,
|
||||
msg: toAscii(JSON.stringify(msg)),
|
||||
sentFunds: [],
|
||||
}),
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: coins(5000000, "ucosm"),
|
||||
gas: "89000000",
|
||||
};
|
||||
|
||||
const firstAddress = (await signer.getAccounts())[0].address;
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, signer, { registry });
|
||||
return client.signAndBroadcast(firstAddress, [theMsg], fee, memo);
|
||||
}
|
||||
|
||||
describe("WasmExtension", () => {
|
||||
const hackatom = getHackatom();
|
||||
const hackatomConfigKey = toAscii("config");
|
||||
let hackatomCodeId: number | undefined;
|
||||
let hackatomContractAddress: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (wasmdEnabled()) {
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, "wasm");
|
||||
const result = await uploadContract(wallet, hackatom);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
hackatomCodeId = Number.parseInt(
|
||||
JSON.parse(result.rawLog!)[0].events[0].attributes.find(
|
||||
(attribute: any) => attribute.key === "code_id",
|
||||
).value,
|
||||
10,
|
||||
);
|
||||
|
||||
const instantiateResult = await instantiateContract(wallet, hackatomCodeId, makeRandomAddress());
|
||||
assertIsBroadcastTxSuccess(instantiateResult);
|
||||
hackatomContractAddress = JSON.parse(instantiateResult.rawLog!)[0]
|
||||
.events.find((event: any) => event.type === "message")
|
||||
.attributes.find((attribute: any) => attribute.key === "contract_address").value;
|
||||
}
|
||||
});
|
||||
|
||||
describe("listCodeInfo", () => {
|
||||
it("has recently uploaded contract as last entry", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomCodeId);
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const { codeInfos } = await client.unverified.wasm.listCodeInfo();
|
||||
assert(codeInfos);
|
||||
const lastCode = codeInfos[codeInfos.length - 1];
|
||||
expect(lastCode.codeId!.toNumber()).toEqual(hackatomCodeId);
|
||||
expect(lastCode.creator).toEqual(alice.address0);
|
||||
expect(lastCode.source).toEqual(hackatom.source);
|
||||
expect(lastCode.builder).toEqual(hackatom.builder);
|
||||
expect(toHex(lastCode.dataHash!)).toEqual(toHex(sha256(hackatom.data)));
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCode", () => {
|
||||
it("contains fill code information", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomCodeId);
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const { codeInfo, data } = await client.unverified.wasm.getCode(hackatomCodeId);
|
||||
expect(codeInfo!.codeId!.toNumber()).toEqual(hackatomCodeId);
|
||||
expect(codeInfo!.creator).toEqual(alice.address0);
|
||||
expect(codeInfo!.source).toEqual(hackatom.source);
|
||||
expect(codeInfo!.builder).toEqual(hackatom.builder);
|
||||
expect(toHex(codeInfo!.dataHash!)).toEqual(toHex(sha256(hackatom.data)));
|
||||
expect(data).toEqual(hackatom.data);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: move listContractsByCodeId tests out of here
|
||||
describe("getContractInfo", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomCodeId);
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, "wasm");
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const transferAmount = coins(707707, "ucosm");
|
||||
|
||||
// create new instance and compare before and after
|
||||
const { contractInfos: existingContractInfos } = await client.unverified.wasm.listContractsByCodeId(
|
||||
hackatomCodeId,
|
||||
);
|
||||
assert(existingContractInfos);
|
||||
for (const { address, contractInfo } of existingContractInfos) {
|
||||
expect(address).toMatch(bech32AddressMatcher);
|
||||
expect(contractInfo!.codeId!.toNumber()).toEqual(hackatomCodeId);
|
||||
expect(contractInfo!.creator).toMatch(bech32AddressMatcher);
|
||||
expect(contractInfo!.label).toMatch(/^.+$/);
|
||||
}
|
||||
|
||||
const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, transferAmount);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
const myAddress = JSON.parse(result.rawLog!)[0]
|
||||
.events.find((event: any) => event.type === "message")
|
||||
.attributes!.find((attribute: any) => attribute.key === "contract_address").value;
|
||||
|
||||
const { contractInfos: newContractInfos } = await client.unverified.wasm.listContractsByCodeId(
|
||||
hackatomCodeId,
|
||||
);
|
||||
assert(newContractInfos);
|
||||
expect(newContractInfos.length).toEqual(existingContractInfos.length + 1);
|
||||
const newContract = newContractInfos[newContractInfos.length - 1];
|
||||
expect({ ...newContract.contractInfo }).toEqual({
|
||||
codeId: Long.fromNumber(hackatomCodeId, true),
|
||||
creator: alice.address0,
|
||||
label: "my escrow",
|
||||
});
|
||||
|
||||
const { contractInfo } = await client.unverified.wasm.getContractInfo(myAddress);
|
||||
assert(contractInfo);
|
||||
expect({ ...contractInfo }).toEqual({
|
||||
codeId: Long.fromNumber(hackatomCodeId, true),
|
||||
creator: alice.address0,
|
||||
label: "my escrow",
|
||||
});
|
||||
expect(contractInfo.admin).toEqual("");
|
||||
});
|
||||
|
||||
it("rejects for non-existent address", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomCodeId);
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
await expectAsync(client.unverified.wasm.getContractInfo(nonExistentAddress)).toBeRejectedWithError(
|
||||
/not found/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getContractCodeHistory", () => {
|
||||
it("can list contract history", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomCodeId);
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, "wasm");
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const transferAmount = coins(707707, "ucosm");
|
||||
|
||||
// create new instance and compare before and after
|
||||
const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, transferAmount);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
|
||||
const myAddress = JSON.parse(result.rawLog!)[0]
|
||||
.events.find((event: any) => event.type === "message")
|
||||
.attributes!.find((attribute: any) => attribute.key === "contract_address").value;
|
||||
|
||||
const history = await client.unverified.wasm.getContractCodeHistory(myAddress);
|
||||
assert(history.entries);
|
||||
expect(history.entries).toContain(
|
||||
jasmine.objectContaining({
|
||||
codeId: Long.fromNumber(hackatomCodeId, true),
|
||||
operation:
|
||||
cosmwasm.wasm.v1beta1.ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT,
|
||||
msg: toAscii(
|
||||
JSON.stringify({
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns empty list for non-existent address", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomCodeId);
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const history = await client.unverified.wasm.getContractCodeHistory(nonExistentAddress);
|
||||
expect(history.entries).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAllContractState", () => {
|
||||
it("can get all state", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomContractAddress);
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const { models } = await client.unverified.wasm.getAllContractState(hackatomContractAddress);
|
||||
assert(models);
|
||||
expect(models.length).toEqual(1);
|
||||
const data = models[0];
|
||||
expect(data.key).toEqual(hackatomConfigKey);
|
||||
const value = JSON.parse(fromAscii(data.value!));
|
||||
expect(value.verifier).toMatch(base64Matcher);
|
||||
expect(value.beneficiary).toMatch(base64Matcher);
|
||||
});
|
||||
|
||||
it("rejects for non-existent address", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
await expectAsync(client.unverified.wasm.getAllContractState(nonExistentAddress)).toBeRejectedWithError(
|
||||
/not found/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("queryContractRaw", () => {
|
||||
it("can query by key", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomContractAddress);
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const raw = await client.unverified.wasm.queryContractRaw(hackatomContractAddress, hackatomConfigKey);
|
||||
assert(raw.data, "must get result");
|
||||
const model = JSON.parse(fromAscii(raw.data));
|
||||
expect(model.verifier).toMatch(base64Matcher);
|
||||
expect(model.beneficiary).toMatch(base64Matcher);
|
||||
});
|
||||
|
||||
it("returns empty object for missing key", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomContractAddress);
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const response = await client.unverified.wasm.queryContractRaw(
|
||||
hackatomContractAddress,
|
||||
fromHex("cafe0dad"),
|
||||
);
|
||||
expect({ ...response }).toEqual({});
|
||||
});
|
||||
|
||||
it("returns null for non-existent address", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
await expectAsync(
|
||||
client.unverified.wasm.queryContractRaw(nonExistentAddress, hackatomConfigKey),
|
||||
).toBeRejectedWithError(/not found/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("queryContractSmart", () => {
|
||||
it("can make smart queries", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomContractAddress);
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const request = { verifier: {} };
|
||||
const { data } = await client.unverified.wasm.queryContractSmart(hackatomContractAddress, request);
|
||||
assert(data);
|
||||
const parsedData = JSON.parse(fromAscii(data));
|
||||
expect(parsedData).toEqual({ verifier: alice.address0 });
|
||||
});
|
||||
|
||||
it("throws for invalid query requests", async () => {
|
||||
pendingWithoutWasmd();
|
||||
assert(hackatomContractAddress);
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const request = { nosuchkey: {} };
|
||||
await expectAsync(
|
||||
client.unverified.wasm.queryContractSmart(hackatomContractAddress, request),
|
||||
).toBeRejectedWithError(/Error parsing into type hackatom::contract::QueryMsg: unknown variant/i);
|
||||
});
|
||||
|
||||
it("throws for non-existent address", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
const request = { verifier: {} };
|
||||
await expectAsync(
|
||||
client.unverified.wasm.queryContractSmart(nonExistentAddress, request),
|
||||
).toBeRejectedWithError(/not found/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("broadcastTx", () => {
|
||||
it("can upload, instantiate and execute wasm", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await makeWasmClient(wasmd.endpoint);
|
||||
|
||||
const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
|
||||
let codeId: number;
|
||||
|
||||
// upload
|
||||
{
|
||||
const result = await uploadContract(wallet, getHackatom());
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
const parsedLogs = logs.parseLogs(parseRawLog(result.rawLog));
|
||||
const codeIdAttr = logs.findAttribute(parsedLogs, "message", "code_id");
|
||||
codeId = Number.parseInt(codeIdAttr.value, 10);
|
||||
expect(codeId).toBeGreaterThanOrEqual(1);
|
||||
expect(codeId).toBeLessThanOrEqual(200);
|
||||
expect(result.data!.length).toEqual(1);
|
||||
expect({ ...result.data![0] }).toEqual({
|
||||
msgType: "store-code",
|
||||
data: toAscii(`${codeId}`),
|
||||
});
|
||||
}
|
||||
|
||||
let contractAddress: string;
|
||||
|
||||
// instantiate
|
||||
{
|
||||
const result = await instantiateContract(wallet, codeId, beneficiaryAddress, transferAmount);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
const parsedLogs = logs.parseLogs(parseRawLog(result.rawLog));
|
||||
const contractAddressAttr = logs.findAttribute(parsedLogs, "message", "contract_address");
|
||||
contractAddress = contractAddressAttr.value;
|
||||
const amountAttr = logs.findAttribute(parsedLogs, "transfer", "amount");
|
||||
expect(amountAttr.value).toEqual("1234ucosm,321ustake");
|
||||
expect(result.data!.length).toEqual(1);
|
||||
expect({ ...result.data![0] }).toEqual({
|
||||
msgType: "instantiate",
|
||||
data: toAscii(contractAddress),
|
||||
});
|
||||
|
||||
const balanceUcosm = await client.bank.balance(contractAddress, "ucosm");
|
||||
expect(balanceUcosm).toEqual(transferAmount[0]);
|
||||
const balanceUstake = await client.bank.balance(contractAddress, "ustake");
|
||||
expect(balanceUstake).toEqual(transferAmount[1]);
|
||||
}
|
||||
|
||||
// execute
|
||||
{
|
||||
const result = await executeContract(wallet, contractAddress, { release: {} });
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
expect(result.data!.length).toEqual(1);
|
||||
expect({ ...result.data![0] }).toEqual({
|
||||
msgType: "execute",
|
||||
data: fromHex("F00BAA"),
|
||||
});
|
||||
const parsedLogs = logs.parseLogs(parseRawLog(result.rawLog));
|
||||
const wasmEvent = parsedLogs.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 beneficiaryBalanceUcosm = await client.bank.balance(beneficiaryAddress, "ucosm");
|
||||
expect(beneficiaryBalanceUcosm).toEqual(transferAmount[0]);
|
||||
const beneficiaryBalanceUstake = await client.bank.balance(beneficiaryAddress, "ustake");
|
||||
expect(beneficiaryBalanceUstake).toEqual(transferAmount[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
125
packages/cosmwasm-stargate/src/queries/wasm.ts
Normal file
125
packages/cosmwasm-stargate/src/queries/wasm.ts
Normal file
@ -0,0 +1,125 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { toAscii } from "@cosmjs/encoding";
|
||||
import { QueryClient } from "@cosmjs/stargate";
|
||||
import Long from "long";
|
||||
|
||||
import { cosmwasm } from "../codec";
|
||||
|
||||
type IQueryAllContractStateResponse = cosmwasm.wasm.v1beta1.IQueryAllContractStateResponse;
|
||||
type IQueryCodesResponse = cosmwasm.wasm.v1beta1.IQueryCodesResponse;
|
||||
type IQueryCodeResponse = cosmwasm.wasm.v1beta1.IQueryCodeResponse;
|
||||
type IQueryContractHistoryResponse = cosmwasm.wasm.v1beta1.IQueryContractHistoryResponse;
|
||||
type IQueryContractInfoResponse = cosmwasm.wasm.v1beta1.IQueryContractInfoResponse;
|
||||
type IQueryContractsByCodeResponse = cosmwasm.wasm.v1beta1.IQueryContractsByCodeResponse;
|
||||
type IQueryRawContractStateResponse = cosmwasm.wasm.v1beta1.IQueryRawContractStateResponse;
|
||||
type IQuerySmartContractStateResponse = cosmwasm.wasm.v1beta1.IQuerySmartContractStateResponse;
|
||||
|
||||
const { Query } = cosmwasm.wasm.v1beta1;
|
||||
|
||||
export interface WasmExtension {
|
||||
readonly unverified: {
|
||||
readonly wasm: {
|
||||
readonly listCodeInfo: (paginationKey?: Uint8Array) => Promise<IQueryCodesResponse>;
|
||||
/**
|
||||
* Downloads the original wasm bytecode by code ID.
|
||||
*
|
||||
* Throws an error if no code with this id
|
||||
*/
|
||||
readonly getCode: (id: number) => Promise<IQueryCodeResponse>;
|
||||
readonly listContractsByCodeId: (
|
||||
id: number,
|
||||
paginationKey?: Uint8Array,
|
||||
) => Promise<IQueryContractsByCodeResponse>;
|
||||
/**
|
||||
* Returns null when contract was not found at this address.
|
||||
*/
|
||||
readonly getContractInfo: (address: string) => Promise<IQueryContractInfoResponse>;
|
||||
/**
|
||||
* Returns null when contract history was not found for this address.
|
||||
*/
|
||||
readonly getContractCodeHistory: (
|
||||
address: string,
|
||||
paginationKey?: Uint8Array,
|
||||
) => Promise<IQueryContractHistoryResponse>;
|
||||
/**
|
||||
* Returns all contract state.
|
||||
* This is an empty array if no such contract, or contract has no data.
|
||||
*/
|
||||
readonly getAllContractState: (
|
||||
address: string,
|
||||
paginationKey?: Uint8Array,
|
||||
) => Promise<IQueryAllContractStateResponse>;
|
||||
/**
|
||||
* Returns the data at the key if present (unknown decoded json),
|
||||
* or null if no data at this (contract address, key) pair
|
||||
*/
|
||||
readonly queryContractRaw: (
|
||||
address: string,
|
||||
key: Uint8Array,
|
||||
) => Promise<IQueryRawContractStateResponse>;
|
||||
/**
|
||||
* Makes a smart query on the contract and parses the response as JSON.
|
||||
* Throws error if no such contract exists, the query format is invalid or the response is invalid.
|
||||
*/
|
||||
readonly queryContractSmart: (
|
||||
address: string,
|
||||
query: Record<string, unknown>,
|
||||
) => Promise<IQuerySmartContractStateResponse>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function setupWasmExtension(base: QueryClient): WasmExtension {
|
||||
const queryService = Query.create((method: any, requestData, callback) => {
|
||||
const path = `/cosmwasm.wasm.v1beta1.Query/${method.name}`;
|
||||
base
|
||||
.queryUnverified(path, requestData)
|
||||
.then((response) => callback(null, response))
|
||||
.catch((error) => callback(error));
|
||||
});
|
||||
return {
|
||||
unverified: {
|
||||
wasm: {
|
||||
listCodeInfo: async (paginationKey?: Uint8Array) => {
|
||||
const request = paginationKey ? { pagination: { key: paginationKey } } : {};
|
||||
return queryService.codes(request);
|
||||
},
|
||||
getCode: async (id: number) => {
|
||||
const request = { codeId: Long.fromNumber(id) };
|
||||
return queryService.code(request);
|
||||
},
|
||||
listContractsByCodeId: async (id: number, paginationKey?: Uint8Array) => {
|
||||
const pagination = paginationKey ? { pagination: { key: paginationKey } } : {};
|
||||
const request = { ...pagination, codeId: Long.fromNumber(id) };
|
||||
return queryService.contractsByCode(request);
|
||||
},
|
||||
getContractInfo: async (address: string) => {
|
||||
const request = { address: address };
|
||||
return queryService.contractInfo(request);
|
||||
},
|
||||
|
||||
getContractCodeHistory: async (address: string, paginationKey?: Uint8Array) => {
|
||||
const pagination = paginationKey ? { pagination: { key: paginationKey } } : {};
|
||||
const request = { ...pagination, address: address };
|
||||
return queryService.contractHistory(request);
|
||||
},
|
||||
|
||||
getAllContractState: async (address: string, paginationKey?: Uint8Array) => {
|
||||
const pagination = paginationKey ? { pagination: { key: paginationKey } } : {};
|
||||
const request = { ...pagination, address: address };
|
||||
return queryService.allContractState(request);
|
||||
},
|
||||
|
||||
queryContractRaw: async (address: string, key: Uint8Array) => {
|
||||
const request = { address: address, queryData: key };
|
||||
return queryService.rawContractState(request);
|
||||
},
|
||||
|
||||
queryContractSmart: async (address: string, query: Record<string, unknown>) => {
|
||||
const request = { address: address, queryData: toAscii(JSON.stringify(query)) };
|
||||
return queryService.smartContractState(request);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
583
packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts
Normal file
583
packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts
Normal file
@ -0,0 +1,583 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { UploadMeta } from "@cosmjs/cosmwasm";
|
||||
import { sha256 } from "@cosmjs/crypto";
|
||||
import { toHex } from "@cosmjs/encoding";
|
||||
import { coin, coins, GasPrice } from "@cosmjs/launchpad";
|
||||
import { Coin, cosmosField, DirectSecp256k1HdWallet, registered, Registry } from "@cosmjs/proto-signing";
|
||||
import { assertIsBroadcastTxSuccess, codec } from "@cosmjs/stargate";
|
||||
import { assert, sleep } from "@cosmjs/utils";
|
||||
import Long from "long";
|
||||
import { Message } from "protobufjs";
|
||||
|
||||
import { PrivateSigningCosmWasmClient, SigningCosmWasmClient } from "./signingcosmwasmclient";
|
||||
import {
|
||||
alice,
|
||||
getHackatom,
|
||||
makeRandomAddress,
|
||||
makeWasmClient,
|
||||
ModifyingDirectSecp256k1HdWallet,
|
||||
ModifyingSecp256k1HdWallet,
|
||||
pendingWithoutWasmd,
|
||||
unused,
|
||||
validator,
|
||||
wasmd,
|
||||
} from "./testutils.spec";
|
||||
|
||||
const { MsgDelegate } = codec.cosmos.staking.v1beta1;
|
||||
const { Tx } = codec.cosmos.tx.v1beta1;
|
||||
|
||||
describe("SigningCosmWasmClient", () => {
|
||||
describe("connectWithWallet", () => {
|
||||
it("can be constructed", async () => {
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
|
||||
it("can be constructed with custom gas price", async () => {
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const gasPrice = GasPrice.fromString("3.14utest");
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, { gasPrice });
|
||||
const openedClient = (client as unknown) as PrivateSigningCosmWasmClient;
|
||||
expect(openedClient.fees).toEqual({
|
||||
upload: {
|
||||
amount: coins(4710000, "utest"),
|
||||
gas: "1500000",
|
||||
},
|
||||
init: {
|
||||
amount: coins(1570000, "utest"),
|
||||
gas: "500000",
|
||||
},
|
||||
migrate: {
|
||||
amount: coins(628000, "utest"),
|
||||
gas: "200000",
|
||||
},
|
||||
exec: {
|
||||
amount: coins(628000, "utest"),
|
||||
gas: "200000",
|
||||
},
|
||||
send: {
|
||||
amount: coins(251200, "utest"),
|
||||
gas: "80000",
|
||||
},
|
||||
changeAdmin: {
|
||||
amount: coins(251200, "utest"),
|
||||
gas: "80000",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("can be constructed with custom gas limits", async () => {
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const gasLimits = {
|
||||
send: 160000,
|
||||
};
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, { gasLimits });
|
||||
const openedClient = (client as unknown) as PrivateSigningCosmWasmClient;
|
||||
expect(openedClient.fees).toEqual({
|
||||
upload: {
|
||||
amount: coins(37500, "ucosm"),
|
||||
gas: "1500000",
|
||||
},
|
||||
init: {
|
||||
amount: coins(12500, "ucosm"),
|
||||
gas: "500000",
|
||||
},
|
||||
migrate: {
|
||||
amount: coins(5000, "ucosm"),
|
||||
gas: "200000",
|
||||
},
|
||||
exec: {
|
||||
amount: coins(5000, "ucosm"),
|
||||
gas: "200000",
|
||||
},
|
||||
send: {
|
||||
amount: coins(4000, "ucosm"),
|
||||
gas: "160000",
|
||||
},
|
||||
changeAdmin: {
|
||||
amount: coins(2000, "ucosm"),
|
||||
gas: "80000",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("can be constructed with custom gas price and gas limits", async () => {
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const gasPrice = GasPrice.fromString("3.14utest");
|
||||
const gasLimits = {
|
||||
send: 160000,
|
||||
};
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, {
|
||||
gasPrice,
|
||||
gasLimits,
|
||||
});
|
||||
const openedClient = (client as unknown) as PrivateSigningCosmWasmClient;
|
||||
expect(openedClient.fees).toEqual({
|
||||
upload: {
|
||||
amount: coins(4710000, "utest"),
|
||||
gas: "1500000",
|
||||
},
|
||||
init: {
|
||||
amount: coins(1570000, "utest"),
|
||||
gas: "500000",
|
||||
},
|
||||
migrate: {
|
||||
amount: coins(628000, "utest"),
|
||||
gas: "200000",
|
||||
},
|
||||
exec: {
|
||||
amount: coins(628000, "utest"),
|
||||
gas: "200000",
|
||||
},
|
||||
send: {
|
||||
amount: coins(502400, "utest"),
|
||||
gas: "160000",
|
||||
},
|
||||
changeAdmin: {
|
||||
amount: coins(251200, "utest"),
|
||||
gas: "80000",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("upload", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const wasm = getHackatom().data;
|
||||
const {
|
||||
codeId,
|
||||
originalChecksum,
|
||||
originalSize,
|
||||
compressedChecksum,
|
||||
compressedSize,
|
||||
} = await client.upload(alice.address0, wasm);
|
||||
expect(originalChecksum).toEqual(toHex(sha256(wasm)));
|
||||
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 wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const hackatom = 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(alice.address0, hackatom.data, 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 wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const { codeId } = await client.upload(alice.address0, getHackatom().data);
|
||||
const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
alice.address0,
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
memo: "Let's see if the memo is used",
|
||||
transferAmount,
|
||||
},
|
||||
);
|
||||
const wasmClient = await makeWasmClient(wasmd.endpoint);
|
||||
const ucosmBalance = await wasmClient.bank.balance(contractAddress, "ucosm");
|
||||
expect(ucosmBalance).toEqual(transferAmount[0]);
|
||||
const ustakeBalance = await wasmClient.bank.balance(contractAddress, "ustake");
|
||||
expect(ustakeBalance).toEqual(transferAmount[1]);
|
||||
});
|
||||
|
||||
it("works with admin", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const { codeId } = await client.upload(alice.address0, getHackatom().data);
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
alice.address0,
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{ admin: unused.address },
|
||||
);
|
||||
const wasmClient = await makeWasmClient(wasmd.endpoint);
|
||||
const { contractInfo } = await wasmClient.unverified.wasm.getContractInfo(contractAddress);
|
||||
assert(contractInfo);
|
||||
expect(contractInfo.admin).toEqual(unused.address);
|
||||
});
|
||||
|
||||
it("can instantiate one code multiple times", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const { codeId } = await client.upload(alice.address0, getHackatom().data);
|
||||
const contractAddress1 = await client.instantiate(
|
||||
alice.address0,
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: makeRandomAddress(),
|
||||
},
|
||||
"contract 1",
|
||||
);
|
||||
const contractAddress2 = await client.instantiate(
|
||||
alice.address0,
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: makeRandomAddress(),
|
||||
},
|
||||
"contract 2",
|
||||
);
|
||||
expect(contractAddress1).not.toEqual(contractAddress2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateAdmin", () => {
|
||||
it("can update an admin", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const { codeId } = await client.upload(alice.address0, getHackatom().data);
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
alice.address0,
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
admin: alice.address0,
|
||||
},
|
||||
);
|
||||
const wasmClient = await makeWasmClient(wasmd.endpoint);
|
||||
const { contractInfo: contractInfo1 } = await wasmClient.unverified.wasm.getContractInfo(
|
||||
contractAddress,
|
||||
);
|
||||
assert(contractInfo1);
|
||||
expect(contractInfo1.admin).toEqual(alice.address0);
|
||||
|
||||
await client.updateAdmin(alice.address0, contractAddress, unused.address);
|
||||
const { contractInfo: contractInfo2 } = await wasmClient.unverified.wasm.getContractInfo(
|
||||
contractAddress,
|
||||
);
|
||||
assert(contractInfo2);
|
||||
expect(contractInfo2.admin).toEqual(unused.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe("clearAdmin", () => {
|
||||
it("can clear an admin", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const { codeId } = await client.upload(alice.address0, getHackatom().data);
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
alice.address0,
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
admin: alice.address0,
|
||||
},
|
||||
);
|
||||
const wasmClient = await makeWasmClient(wasmd.endpoint);
|
||||
const { contractInfo: contractInfo1 } = await wasmClient.unverified.wasm.getContractInfo(
|
||||
contractAddress,
|
||||
);
|
||||
assert(contractInfo1);
|
||||
expect(contractInfo1.admin).toEqual(alice.address0);
|
||||
|
||||
await client.clearAdmin(alice.address0, contractAddress);
|
||||
const { contractInfo: contractInfo2 } = await wasmClient.unverified.wasm.getContractInfo(
|
||||
contractAddress,
|
||||
);
|
||||
assert(contractInfo2);
|
||||
expect(contractInfo2.admin).toEqual("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("migrate", () => {
|
||||
it("can can migrate from one code ID to another", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const { codeId: codeId1 } = await client.upload(alice.address0, getHackatom().data);
|
||||
const { codeId: codeId2 } = await client.upload(alice.address0, getHackatom().data);
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
alice.address0,
|
||||
codeId1,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"My cool label",
|
||||
{
|
||||
admin: alice.address0,
|
||||
},
|
||||
);
|
||||
const wasmClient = await makeWasmClient(wasmd.endpoint);
|
||||
const { contractInfo: contractInfo1 } = await wasmClient.unverified.wasm.getContractInfo(
|
||||
contractAddress,
|
||||
);
|
||||
assert(contractInfo1);
|
||||
expect(contractInfo1.admin).toEqual(alice.address0);
|
||||
|
||||
const newVerifier = makeRandomAddress();
|
||||
await client.migrate(alice.address0, contractAddress, codeId2, { verifier: newVerifier });
|
||||
const { contractInfo: contractInfo2 } = await wasmClient.unverified.wasm.getContractInfo(
|
||||
contractAddress,
|
||||
);
|
||||
assert(contractInfo2);
|
||||
expect({ ...contractInfo2 }).toEqual({
|
||||
...contractInfo1,
|
||||
codeId: Long.fromNumber(codeId2, true),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
const { codeId } = await client.upload(alice.address0, getHackatom().data);
|
||||
// instantiate
|
||||
const transferAmount = [coin(233444, "ucosm"), coin(5454, "ustake")];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const { contractAddress } = await client.instantiate(
|
||||
alice.address0,
|
||||
codeId,
|
||||
{
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"amazing random contract",
|
||||
{
|
||||
transferAmount,
|
||||
},
|
||||
);
|
||||
// execute
|
||||
const result = await client.execute(alice.address0, contractAddress, { release: {} }, undefined);
|
||||
const wasmEvent = result.logs[0].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 wasmClient = await makeWasmClient(wasmd.endpoint);
|
||||
const beneficiaryBalanceUcosm = await wasmClient.bank.balance(beneficiaryAddress, "ucosm");
|
||||
expect(beneficiaryBalanceUcosm).toEqual(transferAmount[0]);
|
||||
const beneficiaryBalanceUstake = await wasmClient.bank.balance(beneficiaryAddress, "ustake");
|
||||
expect(beneficiaryBalanceUstake).toEqual(transferAmount[1]);
|
||||
const contractBalanceUcosm = await wasmClient.bank.balance(contractAddress, "ucosm");
|
||||
expect(contractBalanceUcosm).toEqual(coin(0, "ucosm"));
|
||||
const contractBalanceUstake = await wasmClient.bank.balance(contractAddress, "ustake");
|
||||
expect(contractBalanceUstake).toEqual(coin(0, "ustake"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendTokens", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
|
||||
|
||||
const transferAmount = coins(7890, "ucosm");
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const memo = "for dinner";
|
||||
|
||||
// no tokens here
|
||||
const before = await client.getBalance(beneficiaryAddress, "ucosm");
|
||||
expect(before).toBeNull();
|
||||
|
||||
// send
|
||||
const result = await client.sendTokens(alice.address0, beneficiaryAddress, transferAmount, memo);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
expect(result.rawLog).toBeTruthy();
|
||||
|
||||
// got tokens
|
||||
const after = await client.getBalance(beneficiaryAddress, "ucosm");
|
||||
assert(after);
|
||||
expect(after).toEqual(transferAmount[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("signAndBroadcast", () => {
|
||||
describe("direct mode", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
|
||||
const registry = new Registry();
|
||||
registry.register(msgDelegateTypeUrl, MsgDelegate);
|
||||
const options = { registry: registry };
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, options);
|
||||
|
||||
const msg = MsgDelegate.create({
|
||||
delegatorAddress: alice.address0,
|
||||
validatorAddress: validator.validatorAddress,
|
||||
amount: coin(1234, "ustake"),
|
||||
});
|
||||
const msgAny = {
|
||||
typeUrl: msgDelegateTypeUrl,
|
||||
value: msg,
|
||||
};
|
||||
const fee = {
|
||||
amount: coins(2000, "ucosm"),
|
||||
gas: "180000", // 180k
|
||||
};
|
||||
const memo = "Use your power wisely";
|
||||
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
});
|
||||
|
||||
it("works with a modifying signer", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(
|
||||
alice.mnemonic,
|
||||
undefined,
|
||||
wasmd.prefix,
|
||||
);
|
||||
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
|
||||
const registry = new Registry();
|
||||
registry.register(msgDelegateTypeUrl, MsgDelegate);
|
||||
const options = { registry: registry };
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, options);
|
||||
|
||||
const msg = MsgDelegate.create({
|
||||
delegatorAddress: alice.address0,
|
||||
validatorAddress: validator.validatorAddress,
|
||||
amount: coin(1234, "ustake"),
|
||||
});
|
||||
const msgAny = {
|
||||
typeUrl: msgDelegateTypeUrl,
|
||||
value: msg,
|
||||
};
|
||||
const fee = {
|
||||
amount: coins(2000, "ucosm"),
|
||||
gas: "180000", // 180k
|
||||
};
|
||||
const memo = "Use your power wisely";
|
||||
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
const searchResult = await client.searchTx({ id: result.transactionHash });
|
||||
const tx = Tx.decode(searchResult[0].tx);
|
||||
// From ModifyingDirectSecp256k1HdWallet
|
||||
expect(tx.body!.memo).toEqual("This was modified");
|
||||
expect({ ...tx.authInfo!.fee!.amount![0] }).toEqual(coin(3000, "ucosm"));
|
||||
expect(tx.authInfo!.fee!.gasLimit!.toNumber()).toEqual(333333);
|
||||
});
|
||||
});
|
||||
|
||||
describe("legacy Amino mode", () => {
|
||||
// NOTE: One registry shared between tests
|
||||
// See https://github.com/protobufjs/protobuf.js#using-decorators
|
||||
// > Decorated types reside in protobuf.roots["decorated"] using a flat structure, so no duplicate names.
|
||||
const registry = new Registry();
|
||||
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
|
||||
|
||||
@registered(registry, msgDelegateTypeUrl)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
class CustomMsgDelegate extends Message {
|
||||
@cosmosField.string(1)
|
||||
public readonly delegator_address?: string;
|
||||
@cosmosField.string(2)
|
||||
public readonly validator_address?: string;
|
||||
@cosmosField.message(3, Coin)
|
||||
public readonly amount?: Coin;
|
||||
}
|
||||
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const options = { registry: registry };
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, options);
|
||||
|
||||
const msg = {
|
||||
delegator_address: alice.address0,
|
||||
validator_address: validator.validatorAddress,
|
||||
amount: coin(1234, "ustake"),
|
||||
};
|
||||
const msgAny = {
|
||||
typeUrl: msgDelegateTypeUrl,
|
||||
value: msg,
|
||||
};
|
||||
const fee = {
|
||||
amount: coins(2000, "ucosm"),
|
||||
gas: "200000",
|
||||
};
|
||||
const memo = "Use your power wisely";
|
||||
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
});
|
||||
|
||||
it("works with a modifying signer", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const wallet = await ModifyingSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
|
||||
const options = { registry: registry };
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, options);
|
||||
|
||||
const msg = {
|
||||
delegator_address: alice.address0,
|
||||
validator_address: validator.validatorAddress,
|
||||
amount: coin(1234, "ustake"),
|
||||
};
|
||||
const msgAny = {
|
||||
typeUrl: msgDelegateTypeUrl,
|
||||
value: msg,
|
||||
};
|
||||
const fee = {
|
||||
amount: coins(2000, "ucosm"),
|
||||
gas: "200000",
|
||||
};
|
||||
const memo = "Use your power wisely";
|
||||
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
|
||||
assertIsBroadcastTxSuccess(result);
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
const searchResult = await client.searchTx({ id: result.transactionHash });
|
||||
const tx = Tx.decode(searchResult[0].tx);
|
||||
// From ModifyingSecp256k1HdWallet
|
||||
expect(tx.body!.memo).toEqual("This was modified");
|
||||
expect({ ...tx.authInfo!.fee!.amount![0] }).toEqual(coin(3000, "ucosm"));
|
||||
expect(tx.authInfo!.fee!.gasLimit!.toNumber()).toEqual(333333);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
404
packages/cosmwasm-stargate/src/signingcosmwasmclient.ts
Normal file
404
packages/cosmwasm-stargate/src/signingcosmwasmclient.ts
Normal file
@ -0,0 +1,404 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import {
|
||||
ChangeAdminResult,
|
||||
CosmWasmFeeTable,
|
||||
ExecuteResult,
|
||||
InstantiateOptions,
|
||||
InstantiateResult,
|
||||
isValidBuilder,
|
||||
MigrateResult,
|
||||
UploadMeta,
|
||||
UploadResult,
|
||||
} from "@cosmjs/cosmwasm";
|
||||
import { sha256 } from "@cosmjs/crypto";
|
||||
import { fromBase64, toAscii, toHex } from "@cosmjs/encoding";
|
||||
import {
|
||||
AccountData,
|
||||
buildFeeTable,
|
||||
Coin,
|
||||
CosmosFeeTable,
|
||||
encodeSecp256k1Pubkey,
|
||||
GasLimits,
|
||||
GasPrice,
|
||||
logs,
|
||||
makeSignDoc as makeSignDocAmino,
|
||||
StdFee,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { Int53, Uint53 } from "@cosmjs/math";
|
||||
import {
|
||||
EncodeObject,
|
||||
encodePubkey,
|
||||
isOfflineDirectSigner,
|
||||
makeAuthInfoBytes,
|
||||
makeSignDoc,
|
||||
OfflineSigner,
|
||||
Registry,
|
||||
} from "@cosmjs/proto-signing";
|
||||
import {
|
||||
BroadcastTxFailure,
|
||||
BroadcastTxResponse,
|
||||
codec,
|
||||
getMsgType,
|
||||
getMsgTypeUrl,
|
||||
isBroadcastTxFailure,
|
||||
parseRawLog,
|
||||
} from "@cosmjs/stargate";
|
||||
import { adaptor34, Client as TendermintClient } from "@cosmjs/tendermint-rpc";
|
||||
import Long from "long";
|
||||
import pako from "pako";
|
||||
|
||||
import { cosmwasm } from "./codec";
|
||||
import { CosmWasmClient } from "./cosmwasmclient";
|
||||
|
||||
const { TxRaw } = codec.cosmos.tx.v1beta1;
|
||||
const { SignMode } = codec.cosmos.tx.signing.v1beta1;
|
||||
const {
|
||||
MsgClearAdmin,
|
||||
MsgExecuteContract,
|
||||
MsgInstantiateContract,
|
||||
MsgMigrateContract,
|
||||
MsgStoreCode,
|
||||
MsgUpdateAdmin,
|
||||
} = cosmwasm.wasm.v1beta1;
|
||||
|
||||
function prepareBuilder(builder: string | undefined): string {
|
||||
if (builder === undefined) {
|
||||
return ""; // normalization needed by backend
|
||||
} else {
|
||||
if (!isValidBuilder(builder)) throw new Error("The builder (Docker Hub image with tag) is not valid");
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultGasPrice = GasPrice.fromString("0.025ucosm");
|
||||
const defaultGasLimits: GasLimits<CosmWasmFeeTable> = {
|
||||
upload: 1_500_000,
|
||||
init: 500_000,
|
||||
migrate: 200_000,
|
||||
exec: 200_000,
|
||||
send: 80_000,
|
||||
changeAdmin: 80_000,
|
||||
};
|
||||
|
||||
function createBroadcastTxErrorMessage(result: BroadcastTxFailure): string {
|
||||
return `Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`;
|
||||
}
|
||||
|
||||
function createDefaultRegistry(): Registry {
|
||||
return new Registry([
|
||||
["/cosmwasm.wasm.v1beta1.MsgClearAdmin", MsgClearAdmin],
|
||||
["/cosmwasm.wasm.v1beta1.MsgExecuteContract", MsgExecuteContract],
|
||||
["/cosmwasm.wasm.v1beta1.MsgMigrateContract", MsgMigrateContract],
|
||||
["/cosmwasm.wasm.v1beta1.MsgStoreCode", MsgStoreCode],
|
||||
["/cosmwasm.wasm.v1beta1.MsgInstantiateContract", MsgInstantiateContract],
|
||||
["/cosmwasm.wasm.v1beta1.MsgUpdateAdmin", MsgUpdateAdmin],
|
||||
]);
|
||||
}
|
||||
|
||||
export interface SigningCosmWasmClientOptions {
|
||||
readonly registry?: Registry;
|
||||
readonly gasPrice?: GasPrice;
|
||||
readonly gasLimits?: GasLimits<CosmosFeeTable>;
|
||||
}
|
||||
|
||||
/** Use for testing only */
|
||||
export interface PrivateSigningCosmWasmClient {
|
||||
readonly fees: CosmWasmFeeTable;
|
||||
}
|
||||
|
||||
export class SigningCosmWasmClient extends CosmWasmClient {
|
||||
private readonly fees: CosmosFeeTable;
|
||||
private readonly registry: Registry;
|
||||
private readonly signer: OfflineSigner;
|
||||
|
||||
public static async connectWithWallet(
|
||||
endpoint: string,
|
||||
signer: OfflineSigner,
|
||||
options: SigningCosmWasmClientOptions = {},
|
||||
): Promise<SigningCosmWasmClient> {
|
||||
const tmClient = await TendermintClient.connect(endpoint, adaptor34);
|
||||
return new SigningCosmWasmClient(tmClient, signer, options);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
tmClient: TendermintClient,
|
||||
signer: OfflineSigner,
|
||||
options: SigningCosmWasmClientOptions,
|
||||
) {
|
||||
super(tmClient);
|
||||
const {
|
||||
registry = createDefaultRegistry(),
|
||||
gasPrice = defaultGasPrice,
|
||||
gasLimits = defaultGasLimits,
|
||||
} = options;
|
||||
this.fees = buildFeeTable<CosmosFeeTable>(gasPrice, defaultGasLimits, gasLimits);
|
||||
this.registry = registry;
|
||||
this.signer = signer;
|
||||
}
|
||||
|
||||
/** Uploads code and returns a receipt, including the code ID */
|
||||
public async upload(
|
||||
senderAddress: string,
|
||||
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 = {
|
||||
typeUrl: "/cosmwasm.wasm.v1beta1.MsgStoreCode",
|
||||
value: MsgStoreCode.create({
|
||||
sender: senderAddress,
|
||||
wasmByteCode: compressed,
|
||||
source: source,
|
||||
builder: builder,
|
||||
}),
|
||||
};
|
||||
|
||||
const result = await this.signAndBroadcast(senderAddress, [storeCodeMsg], this.fees.upload, memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
const parsedLogs = parseRawLog(result.rawLog);
|
||||
const codeIdAttr = logs.findAttribute(parsedLogs, "message", "code_id");
|
||||
return {
|
||||
originalSize: wasmCode.length,
|
||||
originalChecksum: toHex(sha256(wasmCode)),
|
||||
compressedSize: compressed.length,
|
||||
compressedChecksum: toHex(sha256(compressed)),
|
||||
codeId: Number.parseInt(codeIdAttr.value, 10),
|
||||
logs: parsedLogs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async instantiate(
|
||||
senderAddress: string,
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
options: InstantiateOptions = {},
|
||||
): Promise<InstantiateResult> {
|
||||
const instantiateMsg = {
|
||||
typeUrl: "/cosmwasm.wasm.v1beta1.MsgInstantiateContract",
|
||||
value: MsgInstantiateContract.create({
|
||||
sender: senderAddress,
|
||||
codeId: Long.fromString(new Uint53(codeId).toString()),
|
||||
label: label,
|
||||
initMsg: toAscii(JSON.stringify(initMsg)),
|
||||
initFunds: [...(options.transferAmount || [])],
|
||||
admin: options.admin,
|
||||
}),
|
||||
};
|
||||
const result = await this.signAndBroadcast(senderAddress, [instantiateMsg], this.fees.init, options.memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
const parsedLogs = parseRawLog(result.rawLog);
|
||||
const contractAddressAttr = logs.findAttribute(parsedLogs, "message", "contract_address");
|
||||
return {
|
||||
contractAddress: contractAddressAttr.value,
|
||||
logs: parsedLogs,
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async updateAdmin(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
newAdmin: string,
|
||||
memo = "",
|
||||
): Promise<ChangeAdminResult> {
|
||||
const updateAdminMsg = {
|
||||
typeUrl: "/cosmwasm.wasm.v1beta1.MsgUpdateAdmin",
|
||||
value: MsgUpdateAdmin.create({
|
||||
sender: senderAddress,
|
||||
contract: contractAddress,
|
||||
newAdmin: newAdmin,
|
||||
}),
|
||||
};
|
||||
const result = await this.signAndBroadcast(senderAddress, [updateAdminMsg], this.fees.changeAdmin, memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: parseRawLog(result.rawLog),
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async clearAdmin(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
memo = "",
|
||||
): Promise<ChangeAdminResult> {
|
||||
const clearAdminMsg = {
|
||||
typeUrl: "/cosmwasm.wasm.v1beta1.MsgClearAdmin",
|
||||
value: MsgClearAdmin.create({
|
||||
sender: senderAddress,
|
||||
contract: contractAddress,
|
||||
}),
|
||||
};
|
||||
const result = await this.signAndBroadcast(senderAddress, [clearAdminMsg], this.fees.changeAdmin, memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: parseRawLog(result.rawLog),
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async migrate(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
codeId: number,
|
||||
migrateMsg: Record<string, unknown>,
|
||||
memo = "",
|
||||
): Promise<MigrateResult> {
|
||||
const msg = {
|
||||
typeUrl: "/cosmwasm.wasm.v1beta1.MsgMigrateContract",
|
||||
value: MsgMigrateContract.create({
|
||||
sender: senderAddress,
|
||||
contract: contractAddress,
|
||||
codeId: Long.fromString(new Uint53(codeId).toString()),
|
||||
migrateMsg: toAscii(JSON.stringify(migrateMsg)),
|
||||
}),
|
||||
};
|
||||
const result = await this.signAndBroadcast(senderAddress, [msg], this.fees.migrate, memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: parseRawLog(result.rawLog),
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async execute(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
handleMsg: Record<string, unknown>,
|
||||
memo = "",
|
||||
transferAmount?: readonly Coin[],
|
||||
): Promise<ExecuteResult> {
|
||||
const executeMsg = {
|
||||
typeUrl: "/cosmwasm.wasm.v1beta1.MsgExecuteContract",
|
||||
value: MsgExecuteContract.create({
|
||||
sender: senderAddress,
|
||||
contract: contractAddress,
|
||||
msg: toAscii(JSON.stringify(handleMsg)),
|
||||
sentFunds: [...(transferAmount || [])],
|
||||
}),
|
||||
};
|
||||
const result = await this.signAndBroadcast(senderAddress, [executeMsg], this.fees.exec, memo);
|
||||
if (isBroadcastTxFailure(result)) {
|
||||
throw new Error(createBroadcastTxErrorMessage(result));
|
||||
}
|
||||
return {
|
||||
logs: parseRawLog(result.rawLog),
|
||||
transactionHash: result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
public async sendTokens(
|
||||
senderAddress: string,
|
||||
recipientAddress: string,
|
||||
transferAmount: readonly Coin[],
|
||||
memo = "",
|
||||
): Promise<BroadcastTxResponse> {
|
||||
const sendMsg = {
|
||||
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
|
||||
value: {
|
||||
fromAddress: senderAddress,
|
||||
toAddress: recipientAddress,
|
||||
amount: transferAmount,
|
||||
},
|
||||
};
|
||||
return this.signAndBroadcast(senderAddress, [sendMsg], this.fees.send, memo);
|
||||
}
|
||||
|
||||
public async signAndBroadcast(
|
||||
address: string,
|
||||
messages: readonly EncodeObject[],
|
||||
fee: StdFee,
|
||||
memo = "",
|
||||
): Promise<BroadcastTxResponse> {
|
||||
const accountFromSigner = (await this.signer.getAccounts()).find(
|
||||
(account: AccountData) => account.address === address,
|
||||
);
|
||||
if (!accountFromSigner) {
|
||||
throw new Error("Failed to retrieve account from signer");
|
||||
}
|
||||
const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey);
|
||||
const accountFromChain = await this.getAccount(address);
|
||||
if (!accountFromChain) {
|
||||
throw new Error("Account not found");
|
||||
}
|
||||
const { accountNumber, sequence } = accountFromChain;
|
||||
if (!pubkey) {
|
||||
throw new Error("Pubkey not known");
|
||||
}
|
||||
const chainId = await this.getChainId();
|
||||
const pubkeyAny = encodePubkey(pubkey);
|
||||
const txBody = {
|
||||
messages: messages,
|
||||
memo: memo,
|
||||
};
|
||||
const txBodyBytes = this.registry.encode({
|
||||
typeUrl: "/cosmos.tx.v1beta1.TxBody",
|
||||
value: txBody,
|
||||
});
|
||||
const gasLimit = Int53.fromString(fee.gas).toNumber();
|
||||
|
||||
if (isOfflineDirectSigner(this.signer)) {
|
||||
const authInfoBytes = makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence);
|
||||
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||
const { signature, signed } = await this.signer.signDirect(address, signDoc);
|
||||
const txRaw = TxRaw.create({
|
||||
bodyBytes: signed.bodyBytes,
|
||||
authInfoBytes: signed.authInfoBytes,
|
||||
signatures: [fromBase64(signature.signature)],
|
||||
});
|
||||
const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish());
|
||||
return this.broadcastTx(signedTx);
|
||||
}
|
||||
|
||||
// Amino signer
|
||||
const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON;
|
||||
const msgs = messages.map((msg) => ({
|
||||
type: getMsgType(msg.typeUrl),
|
||||
value: msg.value,
|
||||
}));
|
||||
const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence);
|
||||
const { signature, signed } = await this.signer.signAmino(address, signDoc);
|
||||
const signedTxBody = {
|
||||
messages: signed.msgs.map((msg) => ({
|
||||
typeUrl: getMsgTypeUrl(msg.type),
|
||||
value: msg.value,
|
||||
})),
|
||||
memo: signed.memo,
|
||||
};
|
||||
const signedTxBodyBytes = this.registry.encode({
|
||||
typeUrl: "/cosmos.tx.v1beta1.TxBody",
|
||||
value: signedTxBody,
|
||||
});
|
||||
const signedGasLimit = Int53.fromString(signed.fee.gas).toNumber();
|
||||
const signedSequence = Int53.fromString(signed.sequence).toNumber();
|
||||
const signedAuthInfoBytes = makeAuthInfoBytes(
|
||||
[pubkeyAny],
|
||||
signed.fee.amount,
|
||||
signedGasLimit,
|
||||
signedSequence,
|
||||
signMode,
|
||||
);
|
||||
const txRaw = TxRaw.create({
|
||||
bodyBytes: signedTxBodyBytes,
|
||||
authInfoBytes: signedAuthInfoBytes,
|
||||
signatures: [fromBase64(signature.signature)],
|
||||
});
|
||||
const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish());
|
||||
return this.broadcastTx(signedTx);
|
||||
}
|
||||
}
|
||||
4
packages/cosmwasm-stargate/src/testdata/contract.json
vendored
Normal file
4
packages/cosmwasm-stargate/src/testdata/contract.json
vendored
Normal file
File diff suppressed because one or more lines are too long
302
packages/cosmwasm-stargate/src/testutils.spec.ts
Normal file
302
packages/cosmwasm-stargate/src/testutils.spec.ts
Normal file
@ -0,0 +1,302 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { Bip39, EnglishMnemonic, Random, Secp256k1, Slip10, Slip10Curve } from "@cosmjs/crypto";
|
||||
import { Bech32, fromBase64 } from "@cosmjs/encoding";
|
||||
import {
|
||||
AminoSignResponse,
|
||||
coins,
|
||||
makeCosmoshubPath,
|
||||
Secp256k1HdWallet,
|
||||
StdSignDoc,
|
||||
} from "@cosmjs/launchpad";
|
||||
import { DirectSecp256k1HdWallet, DirectSignResponse, makeAuthInfoBytes } from "@cosmjs/proto-signing";
|
||||
import {
|
||||
AuthExtension,
|
||||
BankExtension,
|
||||
codec,
|
||||
QueryClient,
|
||||
setupAuthExtension,
|
||||
setupBankExtension,
|
||||
} from "@cosmjs/stargate";
|
||||
import { adaptor34, Client as TendermintClient } from "@cosmjs/tendermint-rpc";
|
||||
|
||||
import { setupWasmExtension, WasmExtension } from "./queries";
|
||||
import hackatom from "./testdata/contract.json";
|
||||
|
||||
type ISignDoc = codec.cosmos.tx.v1beta1.ISignDoc;
|
||||
const { AuthInfo, TxBody } = codec.cosmos.tx.v1beta1;
|
||||
const { SignMode } = codec.cosmos.tx.signing.v1beta1;
|
||||
|
||||
/** An internal testing type. SigningCosmWasmClient has a similar but different interface */
|
||||
export interface ContractUploadInstructions {
|
||||
/** The wasm bytecode */
|
||||
readonly data: Uint8Array;
|
||||
readonly source?: string;
|
||||
readonly builder?: string;
|
||||
}
|
||||
|
||||
export const wasmd = {
|
||||
blockTime: 1_000, // ms
|
||||
chainId: "testing",
|
||||
endpoint: "http://localhost:26659",
|
||||
prefix: "wasm",
|
||||
validator: {
|
||||
address: "wasmvaloper1m4vhsgne6u74ff78vf0tvkjq3q4hjf9vjfrmy2",
|
||||
},
|
||||
};
|
||||
|
||||
export function getHackatom(): ContractUploadInstructions {
|
||||
return {
|
||||
data: fromBase64(hackatom.data),
|
||||
source: "https://crates.io/api/v1/crates/hackatom/not-yet-released/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.9.1",
|
||||
};
|
||||
}
|
||||
|
||||
export function makeRandomAddress(): string {
|
||||
return Bech32.encode("wasm", Random.getBytes(20));
|
||||
}
|
||||
|
||||
export const tendermintIdMatcher = /^[0-9A-F]{64}$/;
|
||||
/** @see https://rgxdb.com/r/1NUN74O6 */
|
||||
export const base64Matcher = /^(?:[a-zA-Z0-9+/]{4})*(?:|(?:[a-zA-Z0-9+/]{3}=)|(?:[a-zA-Z0-9+/]{2}==)|(?:[a-zA-Z0-9+/]{1}===))$/;
|
||||
// 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}$/;
|
||||
|
||||
export const alice = {
|
||||
mnemonic: "enlist hip relief stomach skate base shallow young switch frequent cry park",
|
||||
pubkey0: {
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
value: "A9cXhWb8ZpqCzkA8dQCPV29KdeRLV3rUYxrkHudLbQtS",
|
||||
},
|
||||
address0: "wasm14qemq0vw6y3gc3u3e0aty2e764u4gs5lndxgyk",
|
||||
address1: "wasm1hhg2rlu9jscacku2wwckws7932qqqu8xm5ca8y",
|
||||
address2: "wasm1xv9tklw7d82sezh9haa573wufgy59vmwnxhnsl",
|
||||
address3: "wasm17yg9mssjenmc3jkqth6ulcwj9cxujrxxg9nmzk",
|
||||
address4: "wasm1f7j7ryulwjfe9ljplvhtcaxa6wqgula3nh873j",
|
||||
};
|
||||
|
||||
/** Unused account */
|
||||
export const unused = {
|
||||
pubkey: {
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
value: "ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ",
|
||||
},
|
||||
address: "wasm1cjsxept9rkggzxztslae9ndgpdyt240842kpxh",
|
||||
accountNumber: 16,
|
||||
sequence: 0,
|
||||
};
|
||||
|
||||
export const validator = {
|
||||
/** From first gentx's auth_info.signer_infos in scripts/wasmd/template/.wasmd/config/genesis.json */
|
||||
pubkey: {
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
value: "AsYCD9IZsnY3BhSrR3k7mf5iaJD0KkQdwqzLLl9PT+05",
|
||||
},
|
||||
/** delegator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/wasmd/template/.wasmd/config/genesis.json */
|
||||
delegatorAddress: "wasm1m4vhsgne6u74ff78vf0tvkjq3q4hjf9v84k82s",
|
||||
/** validator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/wasmd/template/.wasmd/config/genesis.json */
|
||||
validatorAddress: "wasmvaloper1m4vhsgne6u74ff78vf0tvkjq3q4hjf9vjfrmy2",
|
||||
accountNumber: 0,
|
||||
sequence: 1,
|
||||
};
|
||||
|
||||
/** Deployed as part of scripts/wasmd/init.sh */
|
||||
export const deployedHackatom = {
|
||||
codeId: 1,
|
||||
source: "https://crates.io/api/v1/crates/hackatom/not-yet-released/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.9.1",
|
||||
checksum: "3defc33a41f58c71d38b176d521c411d8e74d26403fde7660486930c7579a016",
|
||||
instances: [
|
||||
{
|
||||
beneficiary: alice.address0,
|
||||
address: "wasm18vd8fpwxzck93qlwghaj6arh4p7c5n89k7fvsl",
|
||||
label: "From deploy_hackatom.js (0)",
|
||||
},
|
||||
{
|
||||
beneficiary: alice.address1,
|
||||
address: "wasm1hqrdl6wstt8qzshwc6mrumpjk9338k0lffu40x",
|
||||
label: "From deploy_hackatom.js (1)",
|
||||
},
|
||||
{
|
||||
beneficiary: alice.address2,
|
||||
address: "wasm18r5szma8hm93pvx6lwpjwyxruw27e0k5kjkyan",
|
||||
label: "From deploy_hackatom.js (2)",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// /** Deployed as part of scripts/wasmd/init.sh */
|
||||
// export const deployedErc20 = {
|
||||
// codeId: 2,
|
||||
// source: "https://crates.io/api/v1/crates/cw-erc20/0.7.0/download",
|
||||
// builder: "cosmwasm/rust-optimizer:0.10.4",
|
||||
// checksum: "d04368320ad55089384adb171aaea39e43d710d7608829adba0300ed30aa2988",
|
||||
// instances: [
|
||||
// // TODO: Update this address
|
||||
// "cosmos1vjecguu37pmd577339wrdp208ddzymkudc46zj", // HASH
|
||||
// // TODO: Update this address
|
||||
// "cosmos1ym5m5dw7pttft5w430nxx6uat8f84ck4algmhg", // ISA
|
||||
// // TODO: Update this address
|
||||
// "cosmos1gv07846a3867ezn3uqkk082c5ftke7hpllcu8q", // JADE
|
||||
// ],
|
||||
// };
|
||||
|
||||
// /** Deployed as part of scripts/wasmd/init.sh */
|
||||
// export const deployedCw3 = {
|
||||
// codeId: 3,
|
||||
// source: "https://crates.io/api/v1/crates/cw3-fixed-multisig/0.3.1/download",
|
||||
// builder: "cosmwasm/rust-optimizer:0.10.4",
|
||||
// instances: [
|
||||
// // TODO: Update this address
|
||||
// "cosmos1xqeym28j9xgv0p93pwwt6qcxf9tdvf9zddufdw", // Multisig (1/3)
|
||||
// // TODO: Update this address
|
||||
// "cosmos1jka38ckju8cpjap00jf9xdvdyttz9caujtd6t5", // Multisig (2/3)
|
||||
// // TODO: Update this address
|
||||
// "cosmos12dnl585uxzddjw9hw4ca694f054shgpgr4zg80", // Multisig (uneven weights)
|
||||
// ],
|
||||
// };
|
||||
|
||||
// /** Deployed as part of scripts/wasmd/init.sh */
|
||||
// export const deployedCw1 = {
|
||||
// codeId: 4,
|
||||
// source: "https://crates.io/api/v1/crates/cw1-subkeys/0.3.1/download",
|
||||
// builder: "cosmwasm/rust-optimizer:0.10.4",
|
||||
// // TODO: Update this address
|
||||
// instances: ["cosmos1vs2vuks65rq7xj78mwtvn7vvnm2gn7ad5me0d2"],
|
||||
// };
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
// export function erc20Enabled(): boolean {
|
||||
// return !!process.env.ERC20_ENABLED;
|
||||
// }
|
||||
|
||||
// export function pendingWithoutErc20(): void {
|
||||
// if (!erc20Enabled()) {
|
||||
// return pending("Set ERC20_ENABLED to enable ERC20-based tests");
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function cw3Enabled(): boolean {
|
||||
// return !!process.env.CW3_ENABLED;
|
||||
// }
|
||||
|
||||
// export function pendingWithoutCw3(): void {
|
||||
// if (!cw3Enabled()) {
|
||||
// return pending("Set CW3_ENABLED to enable CW3-based tests");
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function cw1Enabled(): boolean {
|
||||
// return !!process.env.CW1_ENABLED;
|
||||
// }
|
||||
|
||||
// export function pendingWithoutCw1(): void {
|
||||
// if (!cw1Enabled()) {
|
||||
// return pending("Set CW1_ENABLED to enable CW1-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];
|
||||
}
|
||||
|
||||
export async function makeWasmClient(
|
||||
endpoint: string,
|
||||
): Promise<QueryClient & AuthExtension & BankExtension & WasmExtension> {
|
||||
const tmClient = await TendermintClient.connect(endpoint, adaptor34);
|
||||
return QueryClient.withExtensions(tmClient, setupAuthExtension, setupBankExtension, setupWasmExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for testing clients using an Amino signer which modifies the transaction it receives before signing
|
||||
*/
|
||||
export class ModifyingSecp256k1HdWallet extends Secp256k1HdWallet {
|
||||
public static async fromMnemonic(
|
||||
mnemonic: string,
|
||||
hdPath = makeCosmoshubPath(0),
|
||||
prefix = "cosmos",
|
||||
): Promise<ModifyingSecp256k1HdWallet> {
|
||||
const mnemonicChecked = new EnglishMnemonic(mnemonic);
|
||||
const seed = await Bip39.mnemonicToSeed(mnemonicChecked);
|
||||
const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath);
|
||||
const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey;
|
||||
return new ModifyingSecp256k1HdWallet(
|
||||
mnemonicChecked,
|
||||
hdPath,
|
||||
privkey,
|
||||
Secp256k1.compressPubkey(uncompressed),
|
||||
prefix,
|
||||
);
|
||||
}
|
||||
|
||||
public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> {
|
||||
const modifiedSignDoc = {
|
||||
...signDoc,
|
||||
fee: {
|
||||
amount: coins(3000, "ucosm"),
|
||||
gas: "333333",
|
||||
},
|
||||
memo: "This was modified",
|
||||
};
|
||||
return super.signAmino(signerAddress, modifiedSignDoc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for testing clients using a direct signer which modifies the transaction it receives before signing
|
||||
*/
|
||||
export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet {
|
||||
public static async fromMnemonic(
|
||||
mnemonic: string,
|
||||
hdPath = makeCosmoshubPath(0),
|
||||
prefix = "cosmos",
|
||||
): Promise<DirectSecp256k1HdWallet> {
|
||||
const mnemonicChecked = new EnglishMnemonic(mnemonic);
|
||||
const seed = await Bip39.mnemonicToSeed(mnemonicChecked);
|
||||
const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath);
|
||||
const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey;
|
||||
return new ModifyingDirectSecp256k1HdWallet(
|
||||
mnemonicChecked,
|
||||
hdPath,
|
||||
privkey,
|
||||
Secp256k1.compressPubkey(uncompressed),
|
||||
prefix,
|
||||
);
|
||||
}
|
||||
|
||||
public async signDirect(address: string, signDoc: ISignDoc): Promise<DirectSignResponse> {
|
||||
const txBody = TxBody.decode(signDoc.bodyBytes!);
|
||||
const modifiedTxBody = TxBody.create({
|
||||
...txBody,
|
||||
memo: "This was modified",
|
||||
});
|
||||
const authInfo = AuthInfo.decode(signDoc.authInfoBytes!);
|
||||
const pubkeys = authInfo.signerInfos.map((signerInfo) => signerInfo.publicKey!);
|
||||
const sequence = authInfo.signerInfos[0].sequence!.toNumber();
|
||||
const modifiedFeeAmount = coins(3000, "ucosm");
|
||||
const modifiedGasLimit = 333333;
|
||||
const modifiedSignDoc = {
|
||||
...signDoc,
|
||||
bodyBytes: Uint8Array.from(TxBody.encode(modifiedTxBody).finish()),
|
||||
authInfoBytes: makeAuthInfoBytes(
|
||||
pubkeys,
|
||||
modifiedFeeAmount,
|
||||
modifiedGasLimit,
|
||||
sequence,
|
||||
SignMode.SIGN_MODE_DIRECT,
|
||||
),
|
||||
};
|
||||
return super.signDirect(address, modifiedSignDoc);
|
||||
}
|
||||
}
|
||||
13
packages/cosmwasm-stargate/tsconfig.json
Normal file
13
packages/cosmwasm-stargate/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "build",
|
||||
"declarationDir": "build/types",
|
||||
"experimentalDecorators": true,
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
13
packages/cosmwasm-stargate/typedoc.js
Normal file
13
packages/cosmwasm-stargate/typedoc.js
Normal file
@ -0,0 +1,13 @@
|
||||
const packageJson = require("./package.json");
|
||||
|
||||
module.exports = {
|
||||
inputFiles: ["./src"],
|
||||
out: "docs",
|
||||
exclude: "**/*.spec.ts",
|
||||
name: `${packageJson.name} Documentation`,
|
||||
readme: "README.md",
|
||||
mode: "file",
|
||||
excludeExternals: true,
|
||||
excludeNotExported: true,
|
||||
excludePrivate: true,
|
||||
};
|
||||
4435
packages/cosmwasm-stargate/types/codec/generated/codecimpl.d.ts
vendored
Normal file
4435
packages/cosmwasm-stargate/types/codec/generated/codecimpl.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
packages/cosmwasm-stargate/types/codec/index.d.ts
vendored
Normal file
1
packages/cosmwasm-stargate/types/codec/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { cosmwasm } from "./generated/codecimpl";
|
||||
62
packages/cosmwasm-stargate/types/cosmwasmclient.d.ts
vendored
Normal file
62
packages/cosmwasm-stargate/types/cosmwasmclient.d.ts
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry, JsonObject } from "@cosmjs/cosmwasm";
|
||||
import { Block, Coin, SearchTxFilter, SearchTxQuery } from "@cosmjs/launchpad";
|
||||
import {
|
||||
Account,
|
||||
AuthExtension,
|
||||
BankExtension,
|
||||
BroadcastTxResponse,
|
||||
IndexedTx,
|
||||
QueryClient,
|
||||
SequenceResponse,
|
||||
} from "@cosmjs/stargate";
|
||||
import { Client as TendermintClient } from "@cosmjs/tendermint-rpc";
|
||||
import { WasmExtension } from "./queries";
|
||||
/** Use for testing only */
|
||||
export interface PrivateCosmWasmClient {
|
||||
readonly tmClient: TendermintClient;
|
||||
readonly queryClient: QueryClient & AuthExtension & BankExtension & WasmExtension;
|
||||
}
|
||||
export declare class CosmWasmClient {
|
||||
private readonly tmClient;
|
||||
private readonly queryClient;
|
||||
private readonly codesCache;
|
||||
private chainId;
|
||||
static connect(endpoint: string): Promise<CosmWasmClient>;
|
||||
protected constructor(tmClient: TendermintClient);
|
||||
getChainId(): Promise<string>;
|
||||
getHeight(): Promise<number>;
|
||||
getAccount(searchAddress: string): Promise<Account | null>;
|
||||
getSequence(address: string): Promise<SequenceResponse | null>;
|
||||
getBlock(height?: number): Promise<Block>;
|
||||
getBalance(address: string, searchDenom: string): Promise<Coin | null>;
|
||||
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
|
||||
disconnect(): void;
|
||||
broadcastTx(tx: Uint8Array): Promise<BroadcastTxResponse>;
|
||||
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<Contract>;
|
||||
/**
|
||||
* Throws an error if no contract was found at the address
|
||||
*/
|
||||
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
|
||||
/**
|
||||
* 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: Record<string, unknown>): Promise<JsonObject>;
|
||||
private txsQuery;
|
||||
}
|
||||
3
packages/cosmwasm-stargate/types/index.d.ts
vendored
Normal file
3
packages/cosmwasm-stargate/types/index.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
export * as codec from "./codec";
|
||||
export { CosmWasmClient } from "./cosmwasmclient";
|
||||
export { SigningCosmWasmClient, SigningCosmWasmClientOptions } from "./signingcosmwasmclient";
|
||||
1
packages/cosmwasm-stargate/types/queries/index.d.ts
vendored
Normal file
1
packages/cosmwasm-stargate/types/queries/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { setupWasmExtension, WasmExtension } from "./wasm";
|
||||
64
packages/cosmwasm-stargate/types/queries/wasm.d.ts
vendored
Normal file
64
packages/cosmwasm-stargate/types/queries/wasm.d.ts
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
import { QueryClient } from "@cosmjs/stargate";
|
||||
import { cosmwasm } from "../codec";
|
||||
declare type IQueryAllContractStateResponse = cosmwasm.wasm.v1beta1.IQueryAllContractStateResponse;
|
||||
declare type IQueryCodesResponse = cosmwasm.wasm.v1beta1.IQueryCodesResponse;
|
||||
declare type IQueryCodeResponse = cosmwasm.wasm.v1beta1.IQueryCodeResponse;
|
||||
declare type IQueryContractHistoryResponse = cosmwasm.wasm.v1beta1.IQueryContractHistoryResponse;
|
||||
declare type IQueryContractInfoResponse = cosmwasm.wasm.v1beta1.IQueryContractInfoResponse;
|
||||
declare type IQueryContractsByCodeResponse = cosmwasm.wasm.v1beta1.IQueryContractsByCodeResponse;
|
||||
declare type IQueryRawContractStateResponse = cosmwasm.wasm.v1beta1.IQueryRawContractStateResponse;
|
||||
declare type IQuerySmartContractStateResponse = cosmwasm.wasm.v1beta1.IQuerySmartContractStateResponse;
|
||||
export interface WasmExtension {
|
||||
readonly unverified: {
|
||||
readonly wasm: {
|
||||
readonly listCodeInfo: (paginationKey?: Uint8Array) => Promise<IQueryCodesResponse>;
|
||||
/**
|
||||
* Downloads the original wasm bytecode by code ID.
|
||||
*
|
||||
* Throws an error if no code with this id
|
||||
*/
|
||||
readonly getCode: (id: number) => Promise<IQueryCodeResponse>;
|
||||
readonly listContractsByCodeId: (
|
||||
id: number,
|
||||
paginationKey?: Uint8Array,
|
||||
) => Promise<IQueryContractsByCodeResponse>;
|
||||
/**
|
||||
* Returns null when contract was not found at this address.
|
||||
*/
|
||||
readonly getContractInfo: (address: string) => Promise<IQueryContractInfoResponse>;
|
||||
/**
|
||||
* Returns null when contract history was not found for this address.
|
||||
*/
|
||||
readonly getContractCodeHistory: (
|
||||
address: string,
|
||||
paginationKey?: Uint8Array,
|
||||
) => Promise<IQueryContractHistoryResponse>;
|
||||
/**
|
||||
* Returns all contract state.
|
||||
* This is an empty array if no such contract, or contract has no data.
|
||||
*/
|
||||
readonly getAllContractState: (
|
||||
address: string,
|
||||
paginationKey?: Uint8Array,
|
||||
) => Promise<IQueryAllContractStateResponse>;
|
||||
/**
|
||||
* Returns the data at the key if present (unknown decoded json),
|
||||
* or null if no data at this (contract address, key) pair
|
||||
*/
|
||||
readonly queryContractRaw: (
|
||||
address: string,
|
||||
key: Uint8Array,
|
||||
) => Promise<IQueryRawContractStateResponse>;
|
||||
/**
|
||||
* Makes a smart query on the contract and parses the response as JSON.
|
||||
* Throws error if no such contract exists, the query format is invalid or the response is invalid.
|
||||
*/
|
||||
readonly queryContractSmart: (
|
||||
address: string,
|
||||
query: Record<string, unknown>,
|
||||
) => Promise<IQuerySmartContractStateResponse>;
|
||||
};
|
||||
};
|
||||
}
|
||||
export declare function setupWasmExtension(base: QueryClient): WasmExtension;
|
||||
export {};
|
||||
81
packages/cosmwasm-stargate/types/signingcosmwasmclient.d.ts
vendored
Normal file
81
packages/cosmwasm-stargate/types/signingcosmwasmclient.d.ts
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
import {
|
||||
ChangeAdminResult,
|
||||
CosmWasmFeeTable,
|
||||
ExecuteResult,
|
||||
InstantiateOptions,
|
||||
InstantiateResult,
|
||||
MigrateResult,
|
||||
UploadMeta,
|
||||
UploadResult,
|
||||
} from "@cosmjs/cosmwasm";
|
||||
import { Coin, CosmosFeeTable, GasLimits, GasPrice, StdFee } from "@cosmjs/launchpad";
|
||||
import { EncodeObject, OfflineSigner, Registry } from "@cosmjs/proto-signing";
|
||||
import { BroadcastTxResponse } from "@cosmjs/stargate";
|
||||
import { CosmWasmClient } from "./cosmwasmclient";
|
||||
export interface SigningCosmWasmClientOptions {
|
||||
readonly registry?: Registry;
|
||||
readonly gasPrice?: GasPrice;
|
||||
readonly gasLimits?: GasLimits<CosmosFeeTable>;
|
||||
}
|
||||
/** Use for testing only */
|
||||
export interface PrivateSigningCosmWasmClient {
|
||||
readonly fees: CosmWasmFeeTable;
|
||||
}
|
||||
export declare class SigningCosmWasmClient extends CosmWasmClient {
|
||||
private readonly fees;
|
||||
private readonly registry;
|
||||
private readonly signer;
|
||||
static connectWithWallet(
|
||||
endpoint: string,
|
||||
signer: OfflineSigner,
|
||||
options?: SigningCosmWasmClientOptions,
|
||||
): Promise<SigningCosmWasmClient>;
|
||||
private constructor();
|
||||
/** Uploads code and returns a receipt, including the code ID */
|
||||
upload(
|
||||
senderAddress: string,
|
||||
wasmCode: Uint8Array,
|
||||
meta?: UploadMeta,
|
||||
memo?: string,
|
||||
): Promise<UploadResult>;
|
||||
instantiate(
|
||||
senderAddress: string,
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
options?: InstantiateOptions,
|
||||
): Promise<InstantiateResult>;
|
||||
updateAdmin(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
newAdmin: string,
|
||||
memo?: string,
|
||||
): Promise<ChangeAdminResult>;
|
||||
clearAdmin(senderAddress: string, contractAddress: string, memo?: string): Promise<ChangeAdminResult>;
|
||||
migrate(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
codeId: number,
|
||||
migrateMsg: Record<string, unknown>,
|
||||
memo?: string,
|
||||
): Promise<MigrateResult>;
|
||||
execute(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
handleMsg: Record<string, unknown>,
|
||||
memo?: string,
|
||||
transferAmount?: readonly Coin[],
|
||||
): Promise<ExecuteResult>;
|
||||
sendTokens(
|
||||
senderAddress: string,
|
||||
recipientAddress: string,
|
||||
transferAmount: readonly Coin[],
|
||||
memo?: string,
|
||||
): Promise<BroadcastTxResponse>;
|
||||
signAndBroadcast(
|
||||
address: string,
|
||||
messages: readonly EncodeObject[],
|
||||
fee: StdFee,
|
||||
memo?: string,
|
||||
): Promise<BroadcastTxResponse>;
|
||||
}
|
||||
21
packages/cosmwasm-stargate/webpack.web.config.js
Normal file
21
packages/cosmwasm-stargate/webpack.web.config.js
Normal file
@ -0,0 +1,21 @@
|
||||
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", "ERC20_ENABLED", "CW3_ENABLED", "CW1_ENABLED"]),
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -45,7 +45,7 @@
|
||||
"@cosmjs/launchpad": "^0.24.0-alpha.10",
|
||||
"@cosmjs/math": "^0.24.0-alpha.10",
|
||||
"@cosmjs/utils": "^0.24.0-alpha.10",
|
||||
"pako": "^1.0.11"
|
||||
"pako": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pako": "^1.0.1",
|
||||
|
||||
@ -14,6 +14,8 @@ module.exports = [
|
||||
path: distdir,
|
||||
filename: "tests.js",
|
||||
},
|
||||
plugins: [new webpack.EnvironmentPlugin(["LAUNCHPAD_ENABLED", "ERC20_ENABLED"])],
|
||||
plugins: [
|
||||
new webpack.EnvironmentPlugin(["LAUNCHPAD_ENABLED", "ERC20_ENABLED", "CW3_ENABLED", "CW1_ENABLED"]),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
"test": "yarn build-or-skip && yarn test-node",
|
||||
"coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet",
|
||||
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js",
|
||||
"preget-proto": "rm -rf proto",
|
||||
"preget-proto": "shx rm -rf proto",
|
||||
"get-proto": "REF=v0.40.0-rc3 ./scripts/get-proto.sh",
|
||||
"predefine-proto": "./scripts/predefine-proto.sh",
|
||||
"define-proto": "./scripts/define-proto.sh",
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
"test": "yarn build-or-skip && yarn test-node",
|
||||
"coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet",
|
||||
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js",
|
||||
"preget-proto": "rm -rf proto",
|
||||
"preget-proto": "shx rm -rf proto",
|
||||
"get-proto": "REF=v0.40.0-rc3 ./scripts/get-proto.sh",
|
||||
"predefine-proto": "./scripts/predefine-proto.sh",
|
||||
"define-proto": "./scripts/define-proto.sh",
|
||||
|
||||
@ -13,7 +13,6 @@ import {
|
||||
Base64,
|
||||
Base64String,
|
||||
DateTime,
|
||||
DateTimeString,
|
||||
dictionaryToStringMap,
|
||||
Hex,
|
||||
HexString,
|
||||
@ -286,7 +285,7 @@ interface RpcHeader {
|
||||
readonly version: RpcBlockVersion;
|
||||
readonly chain_id: string;
|
||||
readonly height: IntegerString;
|
||||
readonly time: DateTimeString;
|
||||
readonly time: string;
|
||||
readonly num_txs: IntegerString;
|
||||
readonly total_txs: IntegerString;
|
||||
|
||||
@ -382,7 +381,7 @@ function decodeBroadcastTxCommit(data: RpcBroadcastTxCommitResponse): responses.
|
||||
type RpcSignature = {
|
||||
readonly block_id_flag: number;
|
||||
readonly validator_address: HexString;
|
||||
readonly timestamp: DateTimeString;
|
||||
readonly timestamp: string;
|
||||
readonly signature: Base64String;
|
||||
};
|
||||
|
||||
@ -436,7 +435,7 @@ function decodeValidatorGenesis(data: RpcValidatorGenesis): responses.Validator
|
||||
}
|
||||
|
||||
interface RpcGenesisResponse {
|
||||
readonly genesis_time: DateTimeString;
|
||||
readonly genesis_time: string;
|
||||
readonly chain_id: string;
|
||||
readonly consensus_params: RpcConsensusParams;
|
||||
// The validators key is used to specify a set of validators for testnets or PoA blockchains.
|
||||
@ -519,7 +518,7 @@ interface RpcSyncInfo {
|
||||
readonly latest_block_hash: HexString;
|
||||
readonly latest_app_hash: HexString;
|
||||
readonly latest_block_height: IntegerString;
|
||||
readonly latest_block_time: DateTimeString;
|
||||
readonly latest_block_time: string;
|
||||
readonly catching_up: boolean;
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,53 @@
|
||||
import { ReadonlyDate } from "readonly-date";
|
||||
|
||||
import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "./encodings";
|
||||
import {
|
||||
DateTime,
|
||||
encodeBlockId,
|
||||
encodeBytes,
|
||||
encodeInt,
|
||||
encodeString,
|
||||
encodeTime,
|
||||
encodeVersion,
|
||||
} from "./encodings";
|
||||
import { ReadonlyDateWithNanoseconds } from "./responses";
|
||||
|
||||
describe("encodings", () => {
|
||||
describe("DateTime", () => {
|
||||
it("decodes a string", () => {
|
||||
expect(DateTime.decode("2020-12-15T10:57:26.778Z").nanoseconds).toEqual(0);
|
||||
expect(DateTime.decode("2020-12-15T10:57:26.7789Z").nanoseconds).toEqual(900000);
|
||||
expect(DateTime.decode("2020-12-15T10:57:26.77809Z").nanoseconds).toEqual(90000);
|
||||
expect(DateTime.decode("2020-12-15T10:57:26.778009Z").nanoseconds).toEqual(9000);
|
||||
expect(DateTime.decode("2020-12-15T10:57:26.7780009Z").nanoseconds).toEqual(900);
|
||||
expect(DateTime.decode("2020-12-15T10:57:26.77800009Z").nanoseconds).toEqual(90);
|
||||
expect(DateTime.decode("2020-12-15T10:57:26.778000009Z").nanoseconds).toEqual(9);
|
||||
});
|
||||
|
||||
it("encodes a string", () => {
|
||||
const date1 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
|
||||
(date1 as any).nanoseconds = 0;
|
||||
expect(DateTime.encode(date1)).toEqual("2020-12-15T10:57:26.778000000Z");
|
||||
const date2 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
|
||||
(date2 as any).nanoseconds = 900000;
|
||||
expect(DateTime.encode(date2)).toEqual("2020-12-15T10:57:26.778900000Z");
|
||||
const date3 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
|
||||
(date3 as any).nanoseconds = 90000;
|
||||
expect(DateTime.encode(date3)).toEqual("2020-12-15T10:57:26.778090000Z");
|
||||
const date4 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
|
||||
(date4 as any).nanoseconds = 9000;
|
||||
expect(DateTime.encode(date4)).toEqual("2020-12-15T10:57:26.778009000Z");
|
||||
const date5 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
|
||||
(date5 as any).nanoseconds = 900;
|
||||
expect(DateTime.encode(date5)).toEqual("2020-12-15T10:57:26.778000900Z");
|
||||
const date6 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
|
||||
(date6 as any).nanoseconds = 90;
|
||||
expect(DateTime.encode(date6)).toEqual("2020-12-15T10:57:26.778000090Z");
|
||||
const date7 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
|
||||
(date7 as any).nanoseconds = 9;
|
||||
expect(DateTime.encode(date7)).toEqual("2020-12-15T10:57:26.778000009Z");
|
||||
});
|
||||
});
|
||||
|
||||
describe("encodeString", () => {
|
||||
it("works", () => {
|
||||
expect(encodeString("")).toEqual(Uint8Array.from([0]));
|
||||
|
||||
@ -7,7 +7,6 @@ import { BlockId, ReadonlyDateWithNanoseconds, Version } from "./responses";
|
||||
export type Base64String = string & As<"base64">;
|
||||
export type HexString = string & As<"hex">;
|
||||
export type IntegerString = string & As<"integer">;
|
||||
export type DateTimeString = string & As<"datetime">;
|
||||
|
||||
/**
|
||||
* A runtime checker that ensures a given value is set (i.e. not undefined or null)
|
||||
@ -172,13 +171,19 @@ export class Base64 {
|
||||
}
|
||||
|
||||
export class DateTime {
|
||||
public static decode(dateTimeString: DateTimeString): ReadonlyDateWithNanoseconds {
|
||||
public static decode(dateTimeString: string): ReadonlyDateWithNanoseconds {
|
||||
const readonlyDate = fromRfc3339(dateTimeString);
|
||||
const nanosecondsMatch = dateTimeString.match(/\.(\d+)Z$/);
|
||||
const nanoseconds = nanosecondsMatch ? nanosecondsMatch[1].slice(3) : "";
|
||||
(readonlyDate as any).nanoseconds = parseInt(nanoseconds.padEnd(6, "0"), 10);
|
||||
return readonlyDate as ReadonlyDateWithNanoseconds;
|
||||
}
|
||||
|
||||
public static encode(dateTime: ReadonlyDateWithNanoseconds): string {
|
||||
const millisecondIso = dateTime.toISOString();
|
||||
const nanoseconds = dateTime.nanoseconds?.toString() ?? "";
|
||||
return `${millisecondIso.slice(0, -1)}${nanoseconds.padStart(6, "0")}Z`;
|
||||
}
|
||||
}
|
||||
|
||||
export class Hex {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
export { Adaptor } from "./adaptor";
|
||||
export { adaptor33, adaptor34 } from "./adaptors";
|
||||
export { Client } from "./client";
|
||||
export { DateTime } from "./encodings";
|
||||
export {
|
||||
AbciInfoRequest,
|
||||
AbciQueryParams,
|
||||
|
||||
4
packages/tendermint-rpc/types/encodings.d.ts
vendored
4
packages/tendermint-rpc/types/encodings.d.ts
vendored
@ -3,7 +3,6 @@ import { BlockId, ReadonlyDateWithNanoseconds, Version } from "./responses";
|
||||
export declare type Base64String = string & As<"base64">;
|
||||
export declare type HexString = string & As<"hex">;
|
||||
export declare type IntegerString = string & As<"integer">;
|
||||
export declare type DateTimeString = string & As<"datetime">;
|
||||
/**
|
||||
* A runtime checker that ensures a given value is set (i.e. not undefined or null)
|
||||
*
|
||||
@ -67,7 +66,8 @@ export declare class Base64 {
|
||||
static decode(base64String: Base64String): Uint8Array;
|
||||
}
|
||||
export declare class DateTime {
|
||||
static decode(dateTimeString: DateTimeString): ReadonlyDateWithNanoseconds;
|
||||
static decode(dateTimeString: string): ReadonlyDateWithNanoseconds;
|
||||
static encode(dateTime: ReadonlyDateWithNanoseconds): string;
|
||||
}
|
||||
export declare class Hex {
|
||||
static encode(data: Uint8Array): HexString;
|
||||
|
||||
1
packages/tendermint-rpc/types/index.d.ts
vendored
1
packages/tendermint-rpc/types/index.d.ts
vendored
@ -1,6 +1,7 @@
|
||||
export { Adaptor } from "./adaptor";
|
||||
export { adaptor33, adaptor34 } from "./adaptors";
|
||||
export { Client } from "./client";
|
||||
export { DateTime } from "./encodings";
|
||||
export {
|
||||
AbciInfoRequest,
|
||||
AbciQueryParams,
|
||||
|
||||
76
scripts/wasmd/deploy_hackatom.js
Executable file
76
scripts/wasmd/deploy_hackatom.js
Executable file
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
const { DirectSecp256k1HdWallet } = require("@cosmjs/proto-signing");
|
||||
const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm-stargate");
|
||||
const fs = require("fs");
|
||||
|
||||
const endpoint = "http://localhost:26659";
|
||||
const alice = {
|
||||
mnemonic: "enlist hip relief stomach skate base shallow young switch frequent cry park",
|
||||
address0: "wasm14qemq0vw6y3gc3u3e0aty2e764u4gs5lndxgyk",
|
||||
address1: "wasm1hhg2rlu9jscacku2wwckws7932qqqu8xm5ca8y",
|
||||
address2: "wasm1xv9tklw7d82sezh9haa573wufgy59vmwnxhnsl",
|
||||
address3: "wasm17yg9mssjenmc3jkqth6ulcwj9cxujrxxg9nmzk",
|
||||
address4: "wasm1f7j7ryulwjfe9ljplvhtcaxa6wqgula3nh873j",
|
||||
};
|
||||
|
||||
const codeMeta = {
|
||||
source: "https://crates.io/api/v1/crates/hackatom/not-yet-released/download",
|
||||
builder: "cosmwasm/rust-optimizer:0.9.1",
|
||||
};
|
||||
|
||||
const inits = [
|
||||
{
|
||||
label: "From deploy_hackatom.js (0)",
|
||||
msg: {
|
||||
beneficiary: alice.address0,
|
||||
verifier: alice.address0,
|
||||
},
|
||||
admin: undefined,
|
||||
},
|
||||
{
|
||||
label: "From deploy_hackatom.js (1)",
|
||||
msg: {
|
||||
beneficiary: alice.address1,
|
||||
verifier: alice.address1,
|
||||
},
|
||||
admin: undefined,
|
||||
},
|
||||
{
|
||||
label: "From deploy_hackatom.js (2)",
|
||||
msg: {
|
||||
beneficiary: alice.address2,
|
||||
verifier: alice.address2,
|
||||
},
|
||||
admin: alice.address1,
|
||||
},
|
||||
];
|
||||
|
||||
async function main() {
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, "wasm");
|
||||
const client = await SigningCosmWasmClient.connectWithWallet(endpoint, wallet);
|
||||
|
||||
const wasm = fs.readFileSync(__dirname + "/contracts/hackatom.wasm");
|
||||
const uploadReceipt = await client.upload(alice.address0, wasm, codeMeta, "Upload hackatom contract");
|
||||
console.info(`Upload succeeded. Receipt: ${JSON.stringify(uploadReceipt)}`);
|
||||
|
||||
for (const { label, msg, admin } of inits) {
|
||||
const { contractAddress } = await client.instantiate(alice.address0, uploadReceipt.codeId, msg, label, {
|
||||
memo: `Create a hackatom instance in deploy_hackatom.js`,
|
||||
admin: admin,
|
||||
});
|
||||
console.info(`Contract instantiated at ${contractAddress}`);
|
||||
}
|
||||
}
|
||||
|
||||
main().then(
|
||||
() => {
|
||||
console.info("All done, let the coins flow.");
|
||||
process.exit(0);
|
||||
},
|
||||
(error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
},
|
||||
);
|
||||
@ -5,7 +5,7 @@ command -v shellcheck >/dev/null && shellcheck "$0"
|
||||
echo "Waiting for blockchain and REST server to be available ..."
|
||||
timeout 60 bash -c "until curl -s http://localhost:1319/node_info > /dev/null; do sleep 0.5; done"
|
||||
# The chain is unreliable in the first second of its existence (https://gist.github.com/webmaster128/8175692d4af5e6c572fddda7a9ef437c)
|
||||
sleep 1
|
||||
sleep 2
|
||||
echo "Waiting for height to be >= 1 ..."
|
||||
timeout 20 bash -c "until [ \"\$( curl -s http://localhost:1319/blocks/latest | jq -r '.block.header.height // 0' )\" -ge 1 ]; do sleep 0.5; done"
|
||||
echo "Okay, thank you for your patience."
|
||||
@ -25,7 +25,7 @@ SCRIPT_DIR="$(realpath "$(dirname "$0")")"
|
||||
cd "$SCRIPT_DIR/contracts"
|
||||
sha256sum --check checksums.sha256
|
||||
)
|
||||
# "$SCRIPT_DIR/deploy_hackatom.js"
|
||||
"$SCRIPT_DIR/deploy_hackatom.js"
|
||||
# "$SCRIPT_DIR/deploy_erc20.js"
|
||||
# "$SCRIPT_DIR/deploy_cw3.js"
|
||||
# "$SCRIPT_DIR/deploy_cw1.js"
|
||||
|
||||
@ -6802,7 +6802,12 @@ package-hash@^4.0.0:
|
||||
lodash.flattendeep "^4.4.0"
|
||||
release-zalgo "^1.0.0"
|
||||
|
||||
pako@^1.0.11, pako@~1.0.5:
|
||||
pako@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.2.tgz#8a72af7a93431ef22aa97e0d53b0acaa3c689220"
|
||||
integrity sha512-9e8DRI3+dRLomCmMBAH30B2ejh+blwXr7VmMEx/pVFZlSDA7oyI8uKMhKXr8IrZpoxBF2YlxUvhqRXzTT1i0NA==
|
||||
|
||||
pako@~1.0.5:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
|
||||
Loading…
Reference in New Issue
Block a user