diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b4772c1..9443653d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - @cosmjs/crypto: Export new convenience functions `keccak256`, `ripemd160`, `sha1`, `sha256` and `sha512`. +- @cosmjs/faucet-client: Add new package which exports `FaucetClient` class. ## 0.23.0 (2020-10-09) diff --git a/packages/cli/examples/helpers.ts b/packages/cli/examples/helpers.ts index 572d56ed..9187b353 100644 --- a/packages/cli/examples/helpers.ts +++ b/packages/cli/examples/helpers.ts @@ -16,14 +16,6 @@ const defaultOptions: Options = { const defaultFaucetUrl = "https://faucet.demo-10.cosmwasm.com/credit"; -// TODO: hit faucet -// if (config.faucetUrl) { -// const acct = await client.getAccount(); -// if (!acct?.balance?.length) { -// await ky.post(config.faucetUrl, { json: { ticker: "COSM", address } }); -// } -// } - const connect = async ( mnemonic: string, opts: Partial, @@ -56,12 +48,6 @@ const loadOrCreateMnemonic = (filename: string): string => { } }; -const hitFaucet = async (faucetUrl: string, address: string, ticker: string): Promise => { - const r = await axios.post(defaultFaucetUrl, { ticker, address }); - console.log(r.status); - console.log(r.data); -}; - const randomAddress = async (prefix: string): Promise => { const mnemonic = Bip39.encode(Random.getBytes(16)).toString(); return mnemonicToAddress(prefix, mnemonic); diff --git a/packages/cli/package.json b/packages/cli/package.json index d2a48db5..54aff0c4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -43,6 +43,7 @@ "@cosmjs/cosmwasm": "^0.23.0", "@cosmjs/crypto": "^0.23.0", "@cosmjs/encoding": "^0.23.0", + "@cosmjs/faucet-client": "^0.23.0", "@cosmjs/launchpad": "^0.23.0", "@cosmjs/math": "^0.23.0", "@cosmjs/utils": "^0.23.0", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 47cbac29..2633a8e9 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -27,7 +27,7 @@ export async function main(originalArgs: readonly string[]): Promise { type: "boolean", }, selftest: { - describe: "Run a selftext and exit", + describe: "Run a selftest and exit", type: "boolean", }, }) @@ -87,6 +87,7 @@ export async function main(originalArgs: readonly string[]): Promise { "@cosmjs/encoding", ["fromAscii", "fromBase64", "fromHex", "fromUtf8", "toAscii", "toBase64", "toHex", "toUtf8", "Bech32"], ], + ["@cosmjs/faucet-client", ["FaucetClient"]], [ "@cosmjs/launchpad", [ diff --git a/packages/faucet-client/.eslintignore b/packages/faucet-client/.eslintignore new file mode 120000 index 00000000..86039baf --- /dev/null +++ b/packages/faucet-client/.eslintignore @@ -0,0 +1 @@ +../../.eslintignore \ No newline at end of file diff --git a/packages/faucet-client/.gitignore b/packages/faucet-client/.gitignore new file mode 100644 index 00000000..68bf3735 --- /dev/null +++ b/packages/faucet-client/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +docs/ diff --git a/packages/faucet-client/.nycrc.yml b/packages/faucet-client/.nycrc.yml new file mode 120000 index 00000000..1f95ac55 --- /dev/null +++ b/packages/faucet-client/.nycrc.yml @@ -0,0 +1 @@ +../../.nycrc.yml \ No newline at end of file diff --git a/packages/faucet-client/README.md b/packages/faucet-client/README.md new file mode 100644 index 00000000..1f041488 --- /dev/null +++ b/packages/faucet-client/README.md @@ -0,0 +1,36 @@ +# @cosmjs/faucet-client + +[![npm version](https://img.shields.io/npm/v/@cosmjs/faucet-client.svg)](https://www.npmjs.com/package/@cosmjs/faucet-client) + +## Running the tests + +First of all you will need an instance of wasmd running. From the root directory of this repository: + +```sh +./scripts/wasmd/start.sh && ./scripts/wasmd/init.sh +``` + +You will also need a faucet. From the root directory of this repository: + +```sh +cd packages/faucet +yarn start-dev +``` + +The tests need to be told you are running the faucet via an environmental variable: + +```sh +export FAUCET_ENABLED=1 +``` + +Finally run the tests from this directory: + +```sh +yarn test +``` + +## 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)). diff --git a/packages/faucet-client/jasmine-testrunner.js b/packages/faucet-client/jasmine-testrunner.js new file mode 100755 index 00000000..7a17962e --- /dev/null +++ b/packages/faucet-client/jasmine-testrunner.js @@ -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(); diff --git a/packages/faucet-client/karma.conf.js b/packages/faucet-client/karma.conf.js new file mode 100644 index 00000000..006da5fe --- /dev/null +++ b/packages/faucet-client/karma.conf.js @@ -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, + }); +}; diff --git a/packages/faucet-client/nonces/README.txt b/packages/faucet-client/nonces/README.txt new file mode 100644 index 00000000..092fe732 --- /dev/null +++ b/packages/faucet-client/nonces/README.txt @@ -0,0 +1 @@ +Directory used to trigger lerna package updates for all packages diff --git a/packages/faucet-client/package.json b/packages/faucet-client/package.json new file mode 100644 index 00000000..117715d3 --- /dev/null +++ b/packages/faucet-client/package.json @@ -0,0 +1,49 @@ +{ + "name": "@cosmjs/faucet-client", + "version": "0.23.0", + "description": "The faucet client", + "contributors": [ + "Will Clark " + ], + "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/faucet-client" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "docs": "typedoc --options typedoc.js", + "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", + "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", + "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", + "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", + "test-node": "node jasmine-testrunner.js", + "test-edge": "yarn pack-web && karma start --single-run --browsers Edge", + "test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox", + "test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless", + "test-safari": "yarn pack-web && karma start --single-run --browsers Safari", + "test": "yarn build-or-skip && yarn test-node", + "coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet", + "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts", + "format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"", + "prebuild": "shx rm -rf ./build", + "build": "tsc", + "postbuild": "yarn move-types && yarn format-types", + "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", + "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" + }, + "dependencies": { + "axios": "^0.19.2" + } +} diff --git a/packages/faucet-client/src/faucetclient.spec.ts b/packages/faucet-client/src/faucetclient.spec.ts new file mode 100644 index 00000000..4d7386cb --- /dev/null +++ b/packages/faucet-client/src/faucetclient.spec.ts @@ -0,0 +1,60 @@ +import { FaucetClient } from "./faucetclient"; + +function pendingWithoutFaucet(): void { + if (!process.env.FAUCET_ENABLED) { + pending("Set FAUCET_ENABLED to enable tests that need a faucet"); + } +} + +describe("FaucetClient", () => { + const faucetUrl = "http://localhost:8000"; + const primaryToken = "ucosm"; + const secondaryToken = "ustake"; + const defaultAddress = "cosmos14qemq0vw6y3gc3u3e0aty2e764u4gs5le3hada"; + + it("can be constructed", () => { + // http + expect(new FaucetClient("http://localhost:8000")).toBeTruthy(); + expect(new FaucetClient("http://localhost:8000/")).toBeTruthy(); + expect(new FaucetClient("http://localhost")).toBeTruthy(); + expect(new FaucetClient("http://localhost/")).toBeTruthy(); + // https + expect(new FaucetClient("https://localhost:8000")).toBeTruthy(); + expect(new FaucetClient("https://localhost:8000/")).toBeTruthy(); + expect(new FaucetClient("https://localhost")).toBeTruthy(); + expect(new FaucetClient("https://localhost/")).toBeTruthy(); + }); + + it("can be used to credit a wallet", async () => { + pendingWithoutFaucet(); + const faucet = new FaucetClient(faucetUrl); + await faucet.credit(defaultAddress, primaryToken); + }); + + it("can be used to credit a wallet with a different token", async () => { + pendingWithoutFaucet(); + const faucet = new FaucetClient(faucetUrl); + await faucet.credit(defaultAddress, secondaryToken); + }); + + it("throws for invalid ticker", async () => { + pendingWithoutFaucet(); + const faucet = new FaucetClient(faucetUrl); + await faucet.credit(defaultAddress, "ETH").then( + () => fail("must not resolve"), + (error) => expect(error).toMatch(/token is not available/i), + ); + }); + + it("throws for invalid address", async () => { + pendingWithoutFaucet(); + const faucet = new FaucetClient(faucetUrl); + + for (const address of ["be5cc2cc05db2cdb4313c18306a5157291cfdcd1", "1234L"]) { + await faucet.credit(address, primaryToken).then( + () => fail("must not resolve"), + (error) => expect(error).toMatch(/address is not in the expected format for this chain/i), + ); + } + }); +}); diff --git a/packages/faucet-client/src/faucetclient.ts b/packages/faucet-client/src/faucetclient.ts new file mode 100644 index 00000000..03cc13fc --- /dev/null +++ b/packages/faucet-client/src/faucetclient.ts @@ -0,0 +1,33 @@ +import axios from "axios"; + +export class FaucetClient { + private readonly baseUrl: string; + + public constructor(baseUrl: string) { + if (!baseUrl.match(/^https?:\/\//)) { + throw new Error("Expected base url to start with http:// or https://"); + } + + // Strip trailing / + const strippedBaseUrl = baseUrl.replace(/(\/)+$/, ""); + this.baseUrl = strippedBaseUrl; + } + + public async credit(address: string, denom: string): Promise { + const body = { + address: address, + denom: denom, + }; + + try { + await axios.post(this.baseUrl + "/credit", body); + } catch (error) { + if (error.response) { + // append response body to error message + throw new Error(`${error}; response body: ${JSON.stringify(error.response.data)}`); + } else { + throw error; + } + } + } +} diff --git a/packages/faucet-client/src/index.ts b/packages/faucet-client/src/index.ts new file mode 100644 index 00000000..7a2633a5 --- /dev/null +++ b/packages/faucet-client/src/index.ts @@ -0,0 +1 @@ +export { FaucetClient } from "./faucetclient"; diff --git a/packages/faucet-client/tsconfig.json b/packages/faucet-client/tsconfig.json new file mode 100644 index 00000000..167e8c02 --- /dev/null +++ b/packages/faucet-client/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "declarationDir": "build/types", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/faucet-client/typedoc.js b/packages/faucet-client/typedoc.js new file mode 100644 index 00000000..4dfbe49d --- /dev/null +++ b/packages/faucet-client/typedoc.js @@ -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, +}; diff --git a/packages/faucet-client/types/faucetclient.d.ts b/packages/faucet-client/types/faucetclient.d.ts new file mode 100644 index 00000000..3f7c74da --- /dev/null +++ b/packages/faucet-client/types/faucetclient.d.ts @@ -0,0 +1,5 @@ +export declare class FaucetClient { + private readonly baseUrl; + constructor(baseUrl: string); + credit(address: string, denom: string): Promise; +} diff --git a/packages/faucet-client/types/index.d.ts b/packages/faucet-client/types/index.d.ts new file mode 100644 index 00000000..7a2633a5 --- /dev/null +++ b/packages/faucet-client/types/index.d.ts @@ -0,0 +1 @@ +export { FaucetClient } from "./faucetclient"; diff --git a/packages/faucet-client/webpack.web.config.js b/packages/faucet-client/webpack.web.config.js new file mode 100644 index 00000000..539c7a9c --- /dev/null +++ b/packages/faucet-client/webpack.web.config.js @@ -0,0 +1,19 @@ +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(["FAUCET_ENABLED"])], + }, +];