diff --git a/CHANGELOG.md b/CHANGELOG.md index f021d8a2..b0f155df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ - @cosmjs/launchpad: Add ed25519 support to `encodeBech32Pubkey`. - @cosmjs/launchpad: Add `encodeAminoPubkey` and `decodeAminoPubkey`. - @cosmjs/utils: Add `arrayContentEquals`. +- @cosmjs/faucet: Add config variables `FAUCET_ADDRESS_PREFIX` and + `FAUCET_TOKENS`. +- @cosmjs/faucet: Remove broken chain ID from `cosmwasm-faucet generate`. ## 0.22.0 (2020-08-03) diff --git a/packages/faucet/README.md b/packages/faucet/README.md index 35db2ad9..54981b72 100644 --- a/packages/faucet/README.md +++ b/packages/faucet/README.md @@ -38,7 +38,6 @@ help Shows a help text and exits version Prints the version and exits generate Generates a random mnemonic, shows derived faucet addresses and exits - 1 Chain ID start Starts the faucet 1 Node base URL, e.g. http://localhost:1317 @@ -49,6 +48,10 @@ FAUCET_CONCURRENCY Number of distributor accounts. Defaults to 5. FAUCET_PORT Port of the webserver. Defaults to 8000. FAUCET_MNEMONIC Secret mnemonic that serves as the base secret for the faucet HD accounts +FAUCET_ADDRESS_PREFIX The bech32 address prefix. Defaults to "cosmos". +FAUCET_TOKENS A comma separated list of tokens configs in the format + {DISPLAY}=10^{DIGITS}{base}, e.g. + "ATOM=10^6uatom" or "COSM = 10^6ucosm, STAKE = 10^3mstake". FAUCET_CREDIT_AMOUNT_TKN Send this amount of TKN to a user requesting TKN. TKN is a placeholder for the token ticker. Defaults to 10. FAUCET_REFILL_FACTOR Send factor times credit amount on refilling. Defauls to 8. diff --git a/packages/faucet/package.json b/packages/faucet/package.json index 96b972af..40bf7b5b 100644 --- a/packages/faucet/package.json +++ b/packages/faucet/package.json @@ -22,7 +22,7 @@ "access": "public" }, "scripts": { - "dev-start": "FAUCET_CREDIT_AMOUNT_COSM=10 FAUCET_CREDIT_AMOUNT_STAKE=5 FAUCET_CONCURRENCY=3 FAUCET_MNEMONIC=\"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone\" ./bin/cosmwasm-faucet start \"http://localhost:1317\"", + "dev-start": "yarn start-dev", "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}\"", @@ -31,7 +31,9 @@ "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", "test-node": "node jasmine-testrunner.js", "test": "yarn build-or-skip && yarn test-node", - "coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet" + "coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet", + "start-dev": "FAUCET_CREDIT_AMOUNT_COSM=10 FAUCET_CREDIT_AMOUNT_STAKE=5 FAUCET_CONCURRENCY=3 FAUCET_MNEMONIC=\"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone\" ./bin/cosmwasm-faucet start \"http://localhost:1317\"", + "start-coralnet": "FAUCET_ADDRESS_PREFIX=coral FAUCET_TOKENS=\"SHELL=10^6ushell, REEF=10^6ureef\" FAUCET_CREDIT_AMOUNT_SHELL=10 FAUCET_CREDIT_AMOUNT_REEF=2 FAUCET_CONCURRENCY=3 FAUCET_MNEMONIC=\"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone\" ./bin/cosmwasm-faucet start \"https://lcd.coralnet.cosmwasm.com\"" }, "dependencies": { "@cosmjs/crypto": "^0.22.0", diff --git a/packages/faucet/src/actions/generate.ts b/packages/faucet/src/actions/generate.ts index ad876dae..a7720b75 100644 --- a/packages/faucet/src/actions/generate.ts +++ b/packages/faucet/src/actions/generate.ts @@ -4,17 +4,15 @@ import * as constants from "../constants"; import { createWallets } from "../profile"; export async function generate(args: readonly string[]): Promise { - if (args.length < 1) { - throw Error( - `Not enough arguments for action 'generate'. See '${constants.binaryName} help' or README for arguments.`, + if (args.length > 0) { + console.warn( + `Warning: ${constants.binaryName} generate does not require positional arguments anymore. Use env variables FAUCET_ADDRESS_PREFIX or FAUCET_CONCURRENCY to configure how accounts are created.`, ); } - const chainId = args[0]; - const mnemonic = Bip39.encode(Random.getBytes(16)).toString(); console.info(`FAUCET_MNEMONIC="${mnemonic}"`); // Log the addresses - await createWallets(mnemonic, chainId, constants.concurrency, true); + await createWallets(mnemonic, constants.addressPrefix, constants.concurrency, true); } diff --git a/packages/faucet/src/actions/help.ts b/packages/faucet/src/actions/help.ts index 745e0ba3..c7bdea20 100644 --- a/packages/faucet/src/actions/help.ts +++ b/packages/faucet/src/actions/help.ts @@ -11,7 +11,6 @@ help Shows a help text and exits version Prints the version and exits generate Generates a random mnemonic, shows derived faucet addresses and exits - 1 Chain ID start Starts the faucet 1 Node base URL, e.g. http://localhost:1317 @@ -22,6 +21,10 @@ FAUCET_CONCURRENCY Number of distributor accounts. Defaults to 5. FAUCET_PORT Port of the webserver. Defaults to 8000. FAUCET_MNEMONIC Secret mnemonic that serves as the base secret for the faucet HD accounts +FAUCET_ADDRESS_PREFIX The bech32 address prefix. Defaults to "cosmos". +FAUCET_TOKENS A comma separated list of tokens configs in the format + {DISPLAY}=10^{DIGITS}{base}, e.g. + "ATOM=10^6uatom" or "COSM = 10^6ucosm, STAKE = 10^3mstake". FAUCET_CREDIT_AMOUNT_TKN Send this amount of TKN to a user requesting TKN. TKN is a placeholder for the token ticker. Defaults to 10. FAUCET_REFILL_FACTOR Send factor times credit amount on refilling. Defauls to 8. diff --git a/packages/faucet/src/actions/start/start.ts b/packages/faucet/src/actions/start.ts similarity index 80% rename from packages/faucet/src/actions/start/start.ts rename to packages/faucet/src/actions/start.ts index 877ae70b..63c13fef 100644 --- a/packages/faucet/src/actions/start/start.ts +++ b/packages/faucet/src/actions/start.ts @@ -1,9 +1,9 @@ import { CosmosClient } from "@cosmjs/launchpad"; -import { Webserver } from "../../api/webserver"; -import * as constants from "../../constants"; -import { logAccountsState } from "../../debugging"; -import { Faucet } from "../../faucet"; +import { Webserver } from "../api/webserver"; +import * as constants from "../constants"; +import { logAccountsState } from "../debugging"; +import { Faucet } from "../faucet"; export async function start(args: readonly string[]): Promise { if (args.length < 1) { @@ -23,7 +23,7 @@ export async function start(args: readonly string[]): Promise { const faucet = await Faucet.make( blockchainBaseUrl, constants.addressPrefix, - constants.developmentTokenConfig, + constants.tokenConfig, constants.mnemonic, constants.concurrency, true, @@ -31,7 +31,7 @@ export async function start(args: readonly string[]): Promise { const chainTokens = faucet.loadTokenTickers(); console.info("Chain tokens:", chainTokens); const accounts = await faucet.loadAccounts(); - logAccountsState(accounts, constants.developmentTokenConfig); + logAccountsState(accounts, constants.tokenConfig); let availableTokens = await faucet.availableTokens(); console.info("Available tokens:", availableTokens); setInterval(async () => { @@ -45,4 +45,5 @@ export async function start(args: readonly string[]): Promise { console.info("Creating webserver ..."); const server = new Webserver(faucet, { nodeUrl: blockchainBaseUrl, chainId: chainId }); server.start(constants.port); + console.info(`Try "curl -sS http://localhost:${constants.port}/status | jq" to check the status.`); } diff --git a/packages/faucet/src/actions/start/index.ts b/packages/faucet/src/actions/start/index.ts deleted file mode 100644 index 56e0e121..00000000 --- a/packages/faucet/src/actions/start/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { start } from "./start"; diff --git a/packages/faucet/src/constants.ts b/packages/faucet/src/constants.ts index b9528833..53bf0b83 100644 --- a/packages/faucet/src/constants.ts +++ b/packages/faucet/src/constants.ts @@ -1,24 +1,11 @@ -import { TokenConfiguration } from "./types"; +import { TokenConfiguration } from "./tokenmanager"; +import { parseBankTokens } from "./tokens"; export const binaryName = "cosmwasm-faucet"; export const concurrency: number = Number.parseInt(process.env.FAUCET_CONCURRENCY || "", 10) || 5; export const port: number = Number.parseInt(process.env.FAUCET_PORT || "", 10) || 8000; export const mnemonic: string | undefined = process.env.FAUCET_MNEMONIC; - -export const addressPrefix = "cosmos"; - -/** For the local development chain */ -export const developmentTokenConfig: TokenConfiguration = { - bankTokens: [ - { - fractionalDigits: 6, - tickerSymbol: "COSM", - denom: "ucosm", - }, - { - fractionalDigits: 6, - tickerSymbol: "STAKE", - denom: "ustake", - }, - ], +export const addressPrefix = process.env.FAUCET_ADDRESS_PREFIX || "cosmos"; +export const tokenConfig: TokenConfiguration = { + bankTokens: parseBankTokens(process.env.FAUCET_TOKENS || "COSM=10^6ucosm, STAKE=10^6ustake"), }; diff --git a/packages/faucet/src/debugging.ts b/packages/faucet/src/debugging.ts index fce36e0b..9f6bec7d 100644 --- a/packages/faucet/src/debugging.ts +++ b/packages/faucet/src/debugging.ts @@ -1,7 +1,8 @@ import { Coin } from "@cosmjs/launchpad"; import { Decimal } from "@cosmjs/math"; -import { MinimalAccount, SendJob, TokenConfiguration } from "./types"; +import { TokenConfiguration } from "./tokenmanager"; +import { MinimalAccount, SendJob } from "./types"; /** A string representation of a coin in a human-readable format that can change at any time */ function debugCoin(coin: Coin, tokens: TokenConfiguration): string { diff --git a/packages/faucet/src/faucet.spec.ts b/packages/faucet/src/faucet.spec.ts index 51732b15..5f664c48 100644 --- a/packages/faucet/src/faucet.spec.ts +++ b/packages/faucet/src/faucet.spec.ts @@ -4,7 +4,7 @@ import { CosmosClient } from "@cosmjs/launchpad"; import { assert } from "@cosmjs/utils"; import { Faucet } from "./faucet"; -import { TokenConfiguration } from "./types"; +import { TokenConfiguration } from "./tokenmanager"; function pendingWithoutWasmd(): void { if (!process.env.WASMD_ENABLED) { diff --git a/packages/faucet/src/faucet.ts b/packages/faucet/src/faucet.ts index 4f7289e7..efe71bb3 100644 --- a/packages/faucet/src/faucet.ts +++ b/packages/faucet/src/faucet.ts @@ -3,8 +3,8 @@ import { sleep } from "@cosmjs/utils"; import { debugAccount, logAccountsState, logSendJob } from "./debugging"; import { createWallets } from "./profile"; -import { TokenManager } from "./tokenmanager"; -import { MinimalAccount, SendJob, TokenConfiguration } from "./types"; +import { TokenConfiguration, TokenManager } from "./tokenmanager"; +import { MinimalAccount, SendJob } from "./types"; function isDefined(value: X | undefined): value is X { return value !== undefined; @@ -119,7 +119,7 @@ export class Faucet { public async refill(): Promise { if (this.logging) { - console.info(`Connected to network: ${this.readOnlyClient.getChainId()}`); + console.info(`Connected to network: ${await this.readOnlyClient.getChainId()}`); console.info(`Tokens on network: ${this.loadTokenTickers().join(", ")}`); } diff --git a/packages/faucet/src/tokenmanager.spec.ts b/packages/faucet/src/tokenmanager.spec.ts index 35ade7a7..43683eba 100644 --- a/packages/faucet/src/tokenmanager.spec.ts +++ b/packages/faucet/src/tokenmanager.spec.ts @@ -1,5 +1,5 @@ -import { TokenManager } from "./tokenmanager"; -import { MinimalAccount, TokenConfiguration } from "./types"; +import { TokenConfiguration, TokenManager } from "./tokenmanager"; +import { MinimalAccount } from "./types"; const dummyConfig: TokenConfiguration = { bankTokens: [ diff --git a/packages/faucet/src/tokenmanager.ts b/packages/faucet/src/tokenmanager.ts index 4022fcc4..cb4c8262 100644 --- a/packages/faucet/src/tokenmanager.ts +++ b/packages/faucet/src/tokenmanager.ts @@ -1,7 +1,8 @@ import { Coin } from "@cosmjs/launchpad"; import { Decimal, Uint53 } from "@cosmjs/math"; -import { BankTokenMeta, MinimalAccount, TokenConfiguration } from "./types"; +import { BankTokenMeta } from "./tokens"; +import { MinimalAccount } from "./types"; /** Send `factor` times credit amount on refilling */ const defaultRefillFactor = 20; @@ -9,6 +10,11 @@ const defaultRefillFactor = 20; /** refill when balance gets below `factor` times credit amount */ const defaultRefillThresholdFactor = 8; +export interface TokenConfiguration { + /** Supported tokens of the Cosmos SDK bank module */ + readonly bankTokens: readonly BankTokenMeta[]; +} + export class TokenManager { private readonly config: TokenConfiguration; diff --git a/packages/faucet/src/tokens.spec.ts b/packages/faucet/src/tokens.spec.ts new file mode 100644 index 00000000..39836c9e --- /dev/null +++ b/packages/faucet/src/tokens.spec.ts @@ -0,0 +1,78 @@ +import { parseBankToken, parseBankTokens } from "./tokens"; + +describe("tokens", () => { + describe("parseBankToken", () => { + it("works", () => { + expect(parseBankToken("COSM=10^6ucosm")).toEqual({ + tickerSymbol: "COSM", + fractionalDigits: 6, + denom: "ucosm", + }); + }); + + it("allows using whitespace", () => { + expect(parseBankToken("COSM = 10^6 ucosm")).toEqual({ + tickerSymbol: "COSM", + fractionalDigits: 6, + denom: "ucosm", + }); + }); + }); + + describe("parseBankTokens", () => { + it("works for one", () => { + expect(parseBankTokens("COSM=10^6ucosm")).toEqual([ + { + tickerSymbol: "COSM", + fractionalDigits: 6, + denom: "ucosm", + }, + ]); + }); + + it("works for two", () => { + expect(parseBankTokens("COSM=10^6ucosm,STAKE=10^3mstake")).toEqual([ + { + tickerSymbol: "COSM", + fractionalDigits: 6, + denom: "ucosm", + }, + { + tickerSymbol: "STAKE", + fractionalDigits: 3, + denom: "mstake", + }, + ]); + }); + + it("ignores whitespace", () => { + expect(parseBankTokens("COSM=10^6ucosm, STAKE=10^3mstake\n")).toEqual([ + { + tickerSymbol: "COSM", + fractionalDigits: 6, + denom: "ucosm", + }, + { + tickerSymbol: "STAKE", + fractionalDigits: 3, + denom: "mstake", + }, + ]); + }); + + it("ignores empty elements", () => { + expect(parseBankTokens("COSM=10^6ucosm,STAKE=10^3mstake,")).toEqual([ + { + tickerSymbol: "COSM", + fractionalDigits: 6, + denom: "ucosm", + }, + { + tickerSymbol: "STAKE", + fractionalDigits: 3, + denom: "mstake", + }, + ]); + }); + }); +}); diff --git a/packages/faucet/src/tokens.ts b/packages/faucet/src/tokens.ts new file mode 100644 index 00000000..a3ca321b --- /dev/null +++ b/packages/faucet/src/tokens.ts @@ -0,0 +1,41 @@ +import { Uint53 } from "@cosmjs/math"; + +export interface BankTokenMeta { + readonly denom: string; + /** + * The token ticker symbol, e.g. ATOM or ETH. + */ + readonly tickerSymbol: string; + /** + * The number of fractional digits the token supports. + * + * A quantity is expressed as atomic units. 10^fractionalDigits of those + * atomic units make up 1 token. + * + * E.g. in Ethereum 10^18 wei are 1 ETH and from the quantity 123000000000000000000 + * the last 18 digits are the fractional part and the rest the wole part. + */ + readonly fractionalDigits: number; +} + +const parseBankTokenPattern = /^([a-zA-Z]{2,20})=10\^([0-9]+)([a-zA-Z]{2,20})$/; + +export function parseBankToken(input: string): BankTokenMeta { + const match = input.replace(/\s/g, "").match(parseBankTokenPattern); + if (!match) { + throw new Error("Token could not be parsed. Format: {DISPLAY}=10^{DIGITS}{base}, e.g. ATOM=10^6uatom"); + } + return { + tickerSymbol: match[1], + fractionalDigits: Uint53.fromString(match[2]).toNumber(), + denom: match[3], + }; +} + +export function parseBankTokens(input: string): BankTokenMeta[] { + return input + .trim() + .split(",") + .filter((part) => part.trim() !== "") + .map((part) => parseBankToken(part)); +} diff --git a/packages/faucet/src/types.ts b/packages/faucet/src/types.ts index b5c68ac6..9241c572 100644 --- a/packages/faucet/src/types.ts +++ b/packages/faucet/src/types.ts @@ -6,27 +6,4 @@ export interface SendJob { readonly amount: Coin; } -export interface BankTokenMeta { - readonly denom: string; - /** - * The token ticker symbol, e.g. ATOM or ETH. - */ - readonly tickerSymbol: string; - /** - * The number of fractional digits the token supports. - * - * A quantity is expressed as atomic units. 10^fractionalDigits of those - * atomic units make up 1 token. - * - * E.g. in Ethereum 10^18 wei are 1 ETH and from the quantity 123000000000000000000 - * the last 18 digits are the fractional part and the rest the wole part. - */ - readonly fractionalDigits: number; -} - -export interface TokenConfiguration { - /** Supported tokens of the Cosmos SDK bank module */ - readonly bankTokens: readonly BankTokenMeta[]; -} - export type MinimalAccount = Pick;