mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-10 21:49:15 +00:00
commit
8fa2a15ed1
@ -5,6 +5,7 @@ workflows:
|
||||
jobs:
|
||||
- build
|
||||
- lint
|
||||
- faucet_docker
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -53,3 +54,18 @@ jobs:
|
||||
- ~/.cache/yarn
|
||||
- run:
|
||||
command: yarn lint
|
||||
faucet_docker:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: true
|
||||
- run:
|
||||
name: Build docker image
|
||||
command: docker build -t cosmwasm/faucet:manual --file faucet.Dockerfile .
|
||||
- run:
|
||||
name: Test docker image
|
||||
command: |
|
||||
docker run --read-only --rm cosmwasm/faucet:manual help
|
||||
docker run --read-only --rm cosmwasm/faucet:manual version
|
||||
|
@ -50,12 +50,6 @@
|
||||
"rules": {
|
||||
"@typescript-eslint/camelcase": ["error", { "properties": "never" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "packages/{iov-bns,iov-tendermint-rpc}/**/*.ts",
|
||||
"rules": {
|
||||
"@typescript-eslint/camelcase": ["error", { "allow": ["v0_[0-9]+"] }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
3
NOTICE
3
NOTICE
@ -2,6 +2,9 @@ This repository was forked from the folders packages/iov-cosmos and scripts/cosm
|
||||
of https://github.com/iov-one/iov-core at tag v2.0.0-alpha.7. It was repurposed
|
||||
and heavily modified from there on.
|
||||
|
||||
The code in packages/faucet was forked from https://github.com/iov-one/iov-faucet on
|
||||
2020-01-29 at commit 33e2d707e7.
|
||||
|
||||
Copyright 2018-2020 IOV SAS
|
||||
Copyright 2020 Confio UO
|
||||
Copyright 2020 Simon Warta
|
||||
|
23
faucet.Dockerfile
Normal file
23
faucet.Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
# Start the build environment
|
||||
# https://hub.docker.com/_/node/
|
||||
FROM node:12.14-alpine AS build-env
|
||||
|
||||
ADD package.json yarn.lock tsconfig.json lerna.json /build_repo_root/
|
||||
ADD packages/bcp /build_repo_root/packages/bcp
|
||||
ADD packages/faucet /build_repo_root/packages/faucet
|
||||
|
||||
WORKDIR /build_repo_root
|
||||
RUN yarn install --frozen-lockfile
|
||||
RUN yarn build
|
||||
|
||||
# Start the runtime environment
|
||||
FROM node:12.14-alpine
|
||||
COPY --from=build-env /build_repo_root/package.json /run_repo_root/
|
||||
COPY --from=build-env /build_repo_root/yarn.lock /run_repo_root/
|
||||
COPY --from=build-env /build_repo_root/packages /run_repo_root/packages
|
||||
WORKDIR /run_repo_root
|
||||
RUN yarn install --frozen-lockfile --production
|
||||
|
||||
EXPOSE 8000
|
||||
ENTRYPOINT ["/run_repo_root/packages/faucet/bin/cosm-faucet"]
|
||||
CMD [""]
|
@ -1,22 +0,0 @@
|
||||
import { ChainConnector, ChainId } from "@iov/bcp";
|
||||
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { cosmosCodec } from "./cosmoscodec";
|
||||
import { CosmosConnection } from "./cosmosconnection";
|
||||
import { TokenInfos } from "./types";
|
||||
|
||||
/**
|
||||
* A helper to connect to a cosmos-based chain at a given url
|
||||
*/
|
||||
export function createCosmosConnector(
|
||||
url: string,
|
||||
prefix: CosmosBech32Prefix,
|
||||
tokenInfo: TokenInfos,
|
||||
expectedChainId?: ChainId,
|
||||
): ChainConnector<CosmosConnection> {
|
||||
return {
|
||||
establishConnection: async () => CosmosConnection.establish(url, prefix, tokenInfo),
|
||||
codec: cosmosCodec,
|
||||
expectedChainId: expectedChainId,
|
||||
};
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
import { PostableBytes, PrehashType } from "@iov/bcp";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
|
||||
import { cosmosCodec } from "./cosmoscodec";
|
||||
import { cosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { chainId, nonce, sendTxJson, signedTxBin, signedTxJson, txId } from "./testdata.spec";
|
||||
|
||||
const { toUtf8 } = Encoding;
|
||||
|
||||
describe("cosmoscodec", () => {
|
||||
describe("cosmWasmCodec", () => {
|
||||
it("properly generates bytes to sign", () => {
|
||||
const expected = {
|
||||
bytes: toUtf8(
|
||||
@ -14,36 +14,36 @@ describe("cosmoscodec", () => {
|
||||
),
|
||||
prehashType: PrehashType.Sha256,
|
||||
};
|
||||
const bytesToSign = cosmosCodec.bytesToSign(sendTxJson, nonce);
|
||||
const bytesToSign = cosmWasmCodec.bytesToSign(sendTxJson, nonce);
|
||||
|
||||
expect(bytesToSign).toEqual(expected);
|
||||
});
|
||||
|
||||
it("properly encodes transactions", () => {
|
||||
const encoded = cosmosCodec.bytesToPost(signedTxJson);
|
||||
const encoded = cosmWasmCodec.bytesToPost(signedTxJson);
|
||||
expect(encoded).toEqual(signedTxBin);
|
||||
});
|
||||
|
||||
it("throws when trying to decode a transaction without a nonce", () => {
|
||||
expect(() => cosmosCodec.parseBytes(signedTxBin as PostableBytes, chainId)).toThrowError(
|
||||
expect(() => cosmWasmCodec.parseBytes(signedTxBin as PostableBytes, chainId)).toThrowError(
|
||||
/nonce is required/i,
|
||||
);
|
||||
});
|
||||
|
||||
it("properly decodes transactions", () => {
|
||||
const decoded = cosmosCodec.parseBytes(signedTxBin as PostableBytes, chainId, nonce);
|
||||
const decoded = cosmWasmCodec.parseBytes(signedTxBin as PostableBytes, chainId, nonce);
|
||||
expect(decoded).toEqual(signedTxJson);
|
||||
});
|
||||
|
||||
it("generates transaction id", () => {
|
||||
const id = cosmosCodec.identifier(signedTxJson);
|
||||
const id = cosmWasmCodec.identifier(signedTxJson);
|
||||
expect(id).toMatch(/^[0-9A-F]{64}$/);
|
||||
expect(id).toEqual(txId);
|
||||
});
|
||||
|
||||
it("round trip works", () => {
|
||||
const encoded = cosmosCodec.bytesToPost(signedTxJson);
|
||||
const decoded = cosmosCodec.parseBytes(encoded, chainId, nonce);
|
||||
const encoded = cosmWasmCodec.bytesToPost(signedTxJson);
|
||||
const decoded = cosmWasmCodec.parseBytes(encoded, chainId, nonce);
|
||||
expect(decoded).toEqual(signedTxJson);
|
||||
});
|
||||
});
|
@ -44,7 +44,7 @@ function sortJson(json: any): any {
|
||||
return result;
|
||||
}
|
||||
|
||||
export class CosmosCodec implements TxCodec {
|
||||
export class CosmWasmCodec implements TxCodec {
|
||||
private readonly prefix: CosmosBech32Prefix;
|
||||
private readonly tokens: TokenInfos;
|
||||
|
||||
@ -113,4 +113,5 @@ const defaultTokens: TokenInfos = [
|
||||
},
|
||||
];
|
||||
|
||||
export const cosmosCodec = new CosmosCodec(defaultPrefix, defaultTokens);
|
||||
/** Unconfigured codec is useful for testing only */
|
||||
export const cosmWasmCodec = new CosmWasmCodec(defaultPrefix, defaultTokens);
|
@ -15,8 +15,8 @@ import { Encoding } from "@iov/encoding";
|
||||
import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol";
|
||||
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { CosmosCodec, cosmosCodec } from "./cosmoscodec";
|
||||
import { CosmosConnection } from "./cosmosconnection";
|
||||
import { CosmWasmCodec, cosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { CosmWasmConnection } from "./cosmwasmconnection";
|
||||
import { nonceToSequence, TokenInfos } from "./types";
|
||||
|
||||
const { fromBase64, toHex } = Encoding;
|
||||
@ -27,7 +27,7 @@ function pendingWithoutCosmos(): void {
|
||||
}
|
||||
}
|
||||
|
||||
describe("CosmosConnection", () => {
|
||||
describe("CosmWasmConnection", () => {
|
||||
const cosm = "COSM" as TokenTicker;
|
||||
const httpUrl = "http://localhost:1317";
|
||||
const defaultChainId = "cosmos:testing" as ChainId;
|
||||
@ -63,7 +63,7 @@ describe("CosmosConnection", () => {
|
||||
describe("establish", () => {
|
||||
it("can connect to Cosmos via http", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
expect(connection).toBeTruthy();
|
||||
connection.disconnect();
|
||||
});
|
||||
@ -72,7 +72,7 @@ describe("CosmosConnection", () => {
|
||||
describe("chainId", () => {
|
||||
it("displays the chain ID", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const chainId = connection.chainId();
|
||||
expect(chainId).toEqual(defaultChainId);
|
||||
connection.disconnect();
|
||||
@ -82,7 +82,7 @@ describe("CosmosConnection", () => {
|
||||
describe("height", () => {
|
||||
it("displays the current height", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const height = await connection.height();
|
||||
expect(height).toBeGreaterThan(0);
|
||||
connection.disconnect();
|
||||
@ -92,7 +92,7 @@ describe("CosmosConnection", () => {
|
||||
describe("getToken", () => {
|
||||
it("displays a given token", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const token = await connection.getToken("COSM" as TokenTicker);
|
||||
expect(token).toEqual({
|
||||
fractionalDigits: 6,
|
||||
@ -104,7 +104,7 @@ describe("CosmosConnection", () => {
|
||||
|
||||
it("resolves to undefined if the token is not supported", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const token = await connection.getToken("whatever" as TokenTicker);
|
||||
expect(token).toBeUndefined();
|
||||
connection.disconnect();
|
||||
@ -114,7 +114,7 @@ describe("CosmosConnection", () => {
|
||||
describe("getAllTokens", () => {
|
||||
it("resolves to a list of all supported tokens", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const tokens = await connection.getAllTokens();
|
||||
// TODO: make this more flexible
|
||||
expect(tokens).toEqual([
|
||||
@ -136,7 +136,7 @@ describe("CosmosConnection", () => {
|
||||
describe("getAccount", () => {
|
||||
it("gets an empty account by address", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const account = await connection.getAccount({ address: defaultEmptyAddress });
|
||||
expect(account).toBeUndefined();
|
||||
connection.disconnect();
|
||||
@ -144,7 +144,7 @@ describe("CosmosConnection", () => {
|
||||
|
||||
it("gets an account by address", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const account = await connection.getAccount({ address: defaultAddress });
|
||||
if (account === undefined) {
|
||||
throw new Error("Expected account not to be undefined");
|
||||
@ -161,7 +161,7 @@ describe("CosmosConnection", () => {
|
||||
|
||||
it("gets an account by pubkey", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const account = await connection.getAccount({ pubkey: defaultPubkey });
|
||||
if (account === undefined) {
|
||||
throw new Error("Expected account not to be undefined");
|
||||
@ -180,11 +180,11 @@ describe("CosmosConnection", () => {
|
||||
describe("integration tests", () => {
|
||||
it("can post and get a transaction", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic));
|
||||
const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath);
|
||||
const faucetAddress = cosmosCodec.identityToAddress(faucet);
|
||||
const faucetAddress = cosmWasmCodec.identityToAddress(faucet);
|
||||
|
||||
const unsigned = await connection.withDefaultFee<SendTransaction>({
|
||||
kind: "bcp/send",
|
||||
@ -200,7 +200,7 @@ describe("CosmosConnection", () => {
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: faucetAddress });
|
||||
// TODO: we need to use custom codecs everywhere
|
||||
const codec = new CosmosCodec(defaultPrefix, defaultTokens);
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultTokens);
|
||||
const signed = await profile.signTransaction(faucet, unsigned, codec, nonce);
|
||||
const postableBytes = codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
||||
@ -243,11 +243,11 @@ describe("CosmosConnection", () => {
|
||||
|
||||
it("can post and search for a transaction", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic));
|
||||
const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath);
|
||||
const faucetAddress = cosmosCodec.identityToAddress(faucet);
|
||||
const faucetAddress = cosmWasmCodec.identityToAddress(faucet);
|
||||
|
||||
const unsigned = await connection.withDefaultFee<SendTransaction>({
|
||||
kind: "bcp/send",
|
||||
@ -263,7 +263,7 @@ describe("CosmosConnection", () => {
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: faucetAddress });
|
||||
// TODO: we need to use custom codecs everywhere
|
||||
const codec = new CosmosCodec(defaultPrefix, defaultTokens);
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultTokens);
|
||||
const signed = await profile.signTransaction(faucet, unsigned, codec, nonce);
|
||||
const postableBytes = codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
@ -68,16 +68,16 @@ function buildQueryString({
|
||||
return components.filter(Boolean).join("&");
|
||||
}
|
||||
|
||||
export class CosmosConnection implements BlockchainConnection {
|
||||
export class CosmWasmConnection implements BlockchainConnection {
|
||||
// we must know prefix and tokens a priori to understand the chain
|
||||
public static async establish(
|
||||
url: string,
|
||||
prefix: CosmosBech32Prefix,
|
||||
tokenInfo: TokenInfos,
|
||||
): Promise<CosmosConnection> {
|
||||
): Promise<CosmWasmConnection> {
|
||||
const restClient = new RestClient(url);
|
||||
const chainData = await this.initialize(restClient);
|
||||
return new CosmosConnection(restClient, chainData, prefix, tokenInfo);
|
||||
return new CosmWasmConnection(restClient, chainData, prefix, tokenInfo);
|
||||
}
|
||||
|
||||
private static async initialize(restClient: RestClient): Promise<ChainData> {
|
23
packages/bcp/src/cosmwasmconnector.ts
Normal file
23
packages/bcp/src/cosmwasmconnector.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ChainConnector, ChainId } from "@iov/bcp";
|
||||
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { CosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { CosmWasmConnection } from "./cosmwasmconnection";
|
||||
import { TokenInfo } from "./types";
|
||||
|
||||
/**
|
||||
* A helper to connect to a cosmos-based chain at a given url
|
||||
*/
|
||||
export function createCosmWasmConnector(
|
||||
url: string,
|
||||
prefix: CosmosBech32Prefix,
|
||||
tokenInfo: readonly TokenInfo[],
|
||||
expectedChainId?: ChainId,
|
||||
): ChainConnector<CosmWasmConnection> {
|
||||
const codec = new CosmWasmCodec(prefix, tokenInfo);
|
||||
return {
|
||||
establishConnection: async () => CosmWasmConnection.establish(url, prefix, tokenInfo),
|
||||
codec: codec,
|
||||
expectedChainId: expectedChainId,
|
||||
};
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export { cosmosCodec, CosmosCodec } from "./cosmoscodec";
|
||||
export { CosmosConnection } from "./cosmosconnection";
|
||||
export { createCosmosConnector } from "./cosmosconnector";
|
||||
export { CosmWasmCodec } from "./cosmwasmcodec";
|
||||
export { CosmWasmConnection } from "./cosmwasmconnection";
|
||||
export { createCosmWasmConnector } from "./cosmwasmconnector";
|
||||
export { TokenInfo } from "./types";
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
} from "@iov/bcp";
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { TokenInfos } from "./types";
|
||||
export declare class CosmosCodec implements TxCodec {
|
||||
export declare class CosmWasmCodec implements TxCodec {
|
||||
private readonly prefix;
|
||||
private readonly tokens;
|
||||
constructor(prefix: CosmosBech32Prefix, tokens: TokenInfos);
|
||||
@ -23,4 +23,5 @@ export declare class CosmosCodec implements TxCodec {
|
||||
identityToAddress(identity: Identity): Address;
|
||||
isValidAddress(address: string): boolean;
|
||||
}
|
||||
export declare const cosmosCodec: CosmosCodec;
|
||||
/** Unconfigured codec is useful for testing only */
|
||||
export declare const cosmWasmCodec: CosmWasmCodec;
|
@ -22,8 +22,12 @@ import {
|
||||
import { Stream } from "xstream";
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { TokenInfos } from "./types";
|
||||
export declare class CosmosConnection implements BlockchainConnection {
|
||||
static establish(url: string, prefix: CosmosBech32Prefix, tokenInfo: TokenInfos): Promise<CosmosConnection>;
|
||||
export declare class CosmWasmConnection implements BlockchainConnection {
|
||||
static establish(
|
||||
url: string,
|
||||
prefix: CosmosBech32Prefix,
|
||||
tokenInfo: TokenInfos,
|
||||
): Promise<CosmWasmConnection>;
|
||||
private static initialize;
|
||||
private readonly restClient;
|
||||
private readonly chainData;
|
@ -1,13 +1,13 @@
|
||||
import { ChainConnector, ChainId } from "@iov/bcp";
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { CosmosConnection } from "./cosmosconnection";
|
||||
import { TokenInfos } from "./types";
|
||||
import { CosmWasmConnection } from "./cosmwasmconnection";
|
||||
import { TokenInfo } from "./types";
|
||||
/**
|
||||
* A helper to connect to a cosmos-based chain at a given url
|
||||
*/
|
||||
export declare function createCosmosConnector(
|
||||
export declare function createCosmWasmConnector(
|
||||
url: string,
|
||||
prefix: CosmosBech32Prefix,
|
||||
tokenInfo: TokenInfos,
|
||||
tokenInfo: readonly TokenInfo[],
|
||||
expectedChainId?: ChainId,
|
||||
): ChainConnector<CosmosConnection>;
|
||||
): ChainConnector<CosmWasmConnection>;
|
7
packages/bcp/types/index.d.ts
vendored
7
packages/bcp/types/index.d.ts
vendored
@ -1,3 +1,4 @@
|
||||
export { cosmosCodec, CosmosCodec } from "./cosmoscodec";
|
||||
export { CosmosConnection } from "./cosmosconnection";
|
||||
export { createCosmosConnector } from "./cosmosconnector";
|
||||
export { CosmWasmCodec } from "./cosmwasmcodec";
|
||||
export { CosmWasmConnection } from "./cosmwasmconnection";
|
||||
export { createCosmWasmConnector } from "./cosmwasmconnector";
|
||||
export { TokenInfo } from "./types";
|
||||
|
1
packages/faucet/.eslintignore
Symbolic link
1
packages/faucet/.eslintignore
Symbolic link
@ -0,0 +1 @@
|
||||
../../.eslintignore
|
3
packages/faucet/.gitignore
vendored
Normal file
3
packages/faucet/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build/
|
||||
dist/
|
||||
docs/
|
126
packages/faucet/README.md
Normal file
126
packages/faucet/README.md
Normal file
@ -0,0 +1,126 @@
|
||||
# @cosmwasm/faucet
|
||||
|
||||
The faucet is built as part of the monorepo. In the repo root do:
|
||||
|
||||
```
|
||||
yarn install
|
||||
yarn build
|
||||
```
|
||||
|
||||
Then start it for a Wasmd development blockchain using:
|
||||
|
||||
```
|
||||
cd packages/faucet
|
||||
yarn dev-start
|
||||
```
|
||||
|
||||
Advanced users that want to provide their custom config can start as follows:
|
||||
|
||||
```
|
||||
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/cosm-faucet start cosmwasm "http://localhost:1317"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
sage: cosmwasm-faucet action [arguments...]
|
||||
|
||||
Positional arguments per action are listed below. Arguments in parentheses are optional.
|
||||
|
||||
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 Codec
|
||||
2 Chain ID
|
||||
|
||||
start Starts the faucet
|
||||
1 Codec
|
||||
2 Node base URL, e.g. wss://bov.friendnet-fast.iov.one
|
||||
|
||||
Environment variables
|
||||
|
||||
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_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.
|
||||
FAUCET_REFILL_THRESHOLD Refill when balance gets below factor times credit amount.
|
||||
Defaults to 20.
|
||||
```
|
||||
|
||||
### Faucet HD wallet
|
||||
|
||||
The faucet uses standard HD paths for each blockchain, e.g.
|
||||
|
||||
```
|
||||
IOV m/44'/234'/a'
|
||||
Lisk m/44'/134'/a'
|
||||
CosmWasm m/44'/118'/0'/0/a
|
||||
```
|
||||
|
||||
where `a` is a 0-based index of the account. Account 0 is the token holder and
|
||||
account 1...FAUCET_CONCURRENCY are the distributor accounts.
|
||||
|
||||
This means the token holder account can be accessed using the Neuma wallet when
|
||||
the same mnemonic is used. Accessing the distributor accounts will be possible
|
||||
as soon as there is
|
||||
[multi account support](https://github.com/iov-one/ponferrada/milestone/3).
|
||||
|
||||
### Working with docker
|
||||
|
||||
- Build an artifact ()
|
||||
|
||||
```sh
|
||||
docker build -t cosmwasm/faucet:manual --file faucet.Dockerfile .
|
||||
```
|
||||
|
||||
- Version and help
|
||||
|
||||
```sh
|
||||
docker run --read-only --rm cosmwasm/faucet:manual version
|
||||
docker run --read-only --rm cosmwasm/faucet:manual help
|
||||
```
|
||||
|
||||
- Run faucet locally
|
||||
|
||||
```sh
|
||||
DOCKER_HOST_IP=$(docker run --read-only --rm alpine ip route | awk 'NR==1 {print $3}') \
|
||||
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" \
|
||||
docker run --read-only --rm \
|
||||
-e FAUCET_MNEMONIC \
|
||||
-e FAUCET_CONCURRENCY \
|
||||
-p 8000:8000 \
|
||||
cosmwasm/faucet:manual \
|
||||
start cosmwasm "http://$DOCKER_HOST_IP:1317"
|
||||
```
|
||||
|
||||
### Using the faucet
|
||||
|
||||
Now that the faucet has been started up, you can send credit requests to it.
|
||||
This can be done with a simple http POST request. These commands assume the
|
||||
faucet is running locally, be sure to change it from `localhost` if your
|
||||
situation is different.
|
||||
|
||||
```
|
||||
curl --header "Content-Type: application/json" \
|
||||
--request POST \
|
||||
--data '{"ticker":"CASH","address":"tiov1k898u78hgs36uqw68dg7va5nfkgstu5z0fhz3f"}' \
|
||||
http://localhost:8000/credit
|
||||
```
|
||||
|
||||
### Checking the faucets status
|
||||
|
||||
The faucet provides a simple status check in the form of an http GET request. As
|
||||
above, make sure to adjust the URL as necessary.
|
||||
|
||||
```
|
||||
curl http://localhost:8000/status
|
||||
```
|
6
packages/faucet/bin/cosm-faucet
Executable file
6
packages/faucet/bin/cosm-faucet
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
const path = require("path");
|
||||
|
||||
// attempt to call in main file....
|
||||
const faucet = require(path.join(__dirname, "..", "build", "faucet.js"));
|
||||
faucet.main(process.argv.slice(2));
|
12
packages/faucet/jasmine-spec-reporter.config.json
Normal file
12
packages/faucet/jasmine-spec-reporter.config.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"suite": {
|
||||
"displayNumber": true
|
||||
},
|
||||
"spec": {
|
||||
"displayDuration": true
|
||||
},
|
||||
"summary": {
|
||||
"displayPending": false,
|
||||
"displayStacktrace": true
|
||||
}
|
||||
}
|
26
packages/faucet/jasmine-testrunner.js
Executable file
26
packages/faucet/jasmine-testrunner.js
Executable file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require("source-map-support").install();
|
||||
const defaultSpecReporterConfig = require("./jasmine-spec-reporter.config.json");
|
||||
|
||||
// setup Jasmine
|
||||
const Jasmine = require("jasmine");
|
||||
const jasmine = new Jasmine();
|
||||
jasmine.loadConfig({
|
||||
spec_dir: "build",
|
||||
spec_files: ["**/*.spec.js"],
|
||||
helpers: [],
|
||||
random: false,
|
||||
seed: null,
|
||||
stopSpecOnExpectationFailure: false,
|
||||
});
|
||||
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000;
|
||||
|
||||
// setup reporter
|
||||
const { SpecReporter } = require("jasmine-spec-reporter");
|
||||
const reporter = new SpecReporter({ ...defaultSpecReporterConfig });
|
||||
|
||||
// initialize and execute
|
||||
jasmine.env.clearReporters();
|
||||
jasmine.addReporter(reporter);
|
||||
jasmine.execute();
|
1
packages/faucet/nonces/README.txt
Normal file
1
packages/faucet/nonces/README.txt
Normal file
@ -0,0 +1 @@
|
||||
Directory used to trigger lerna package updates for all packages
|
56
packages/faucet/package.json
Normal file
56
packages/faucet/package.json
Normal file
@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@cosmwasm/faucet",
|
||||
"version": "0.0.1",
|
||||
"description": "The faucet",
|
||||
"author": "Ethan Frey <ethanfrey@users.noreply.github.com>",
|
||||
"license": "Apache-2.0",
|
||||
"main": "build/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"build/",
|
||||
"types/",
|
||||
"*.md",
|
||||
"!*.spec.*",
|
||||
"!**/testdata/"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/confio/cosm-js/tree/master/packages/faucet"
|
||||
},
|
||||
"publishConfig": {
|
||||
"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/cosm-faucet start cosmwasm \"http://localhost:1317\"",
|
||||
"docs": "shx rm -rf docs && typedoc --options typedoc.js",
|
||||
"format": "prettier --write --loglevel warn \"./src/**/*.ts\"",
|
||||
"lint": "eslint --max-warnings 0 \"**/*.{js,ts}\" && tslint -t verbose --project .",
|
||||
"lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix",
|
||||
"build": "shx rm -rf ./build && tsc",
|
||||
"build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build",
|
||||
"test-node": "node jasmine-testrunner.js",
|
||||
"test": "yarn build-or-skip && yarn test-node"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmwasm/bcp": "^0.0.1",
|
||||
"@iov/bcp": "^2.0.0-alpha.7",
|
||||
"@iov/crypto": "^2.0.0-alpha.7",
|
||||
"@iov/encoding": "^2.0.0-alpha.7",
|
||||
"@iov/lisk": "^2.0.0-alpha.7",
|
||||
"@iov/multichain": "^2.0.0-alpha.7",
|
||||
"@koa/cors": "^3.0.0",
|
||||
"axios": "^0.19.0",
|
||||
"bn.js": "^5.1.1",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"koa": "^2.11.0",
|
||||
"koa-bodyparser": "^4.2.1",
|
||||
"readonly-date": "^1.0.0",
|
||||
"xstream": "^11.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bn.js": "^4.11.6",
|
||||
"@types/koa": "^2.11.0",
|
||||
"@types/koa-bodyparser": "^4.3.0",
|
||||
"@types/koa__cors": "^3.0.1"
|
||||
}
|
||||
}
|
20
packages/faucet/src/actions/generate.ts
Normal file
20
packages/faucet/src/actions/generate.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { ChainId } from "@iov/bcp";
|
||||
import { Bip39, Random } from "@iov/crypto";
|
||||
import { UserProfile } from "@iov/keycontrol";
|
||||
|
||||
import { codecFromString } from "../codec";
|
||||
import { setSecretAndCreateIdentities } from "../profile";
|
||||
|
||||
export async function generate(args: ReadonlyArray<string>): Promise<void> {
|
||||
if (args.length < 2) {
|
||||
throw Error(`Not enough arguments for action 'generate'. See 'iov-faucet help' or README for arguments.`);
|
||||
}
|
||||
const codecName = codecFromString(args[0]);
|
||||
const chainId = args[1] as ChainId;
|
||||
|
||||
const mnemonic = Bip39.encode(await Random.getBytes(16)).toString();
|
||||
console.info(`FAUCET_MNEMONIC="${mnemonic}"`);
|
||||
|
||||
const profile = new UserProfile();
|
||||
await setSecretAndCreateIdentities(profile, mnemonic, chainId, codecName);
|
||||
}
|
35
packages/faucet/src/actions/help.ts
Normal file
35
packages/faucet/src/actions/help.ts
Normal file
@ -0,0 +1,35 @@
|
||||
const binaryName = "cosmwasm-faucet";
|
||||
|
||||
export function help(): void {
|
||||
const out = `
|
||||
Usage: ${binaryName} action [arguments...]
|
||||
|
||||
Positional arguments per action are listed below. Arguments in parentheses are optional.
|
||||
|
||||
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 Codec
|
||||
2 Chain ID
|
||||
|
||||
start Starts the faucet
|
||||
1 Codec
|
||||
2 Node base URL, e.g. wss://bov.friendnet-fast.iov.one
|
||||
|
||||
Environment variables
|
||||
|
||||
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_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.
|
||||
FAUCET_REFILL_THRESHOLD Refill when balance gets below factor times credit amount.
|
||||
Defaults to 20.
|
||||
`.trim();
|
||||
|
||||
process.stdout.write(`${out}\n`);
|
||||
}
|
4
packages/faucet/src/actions/index.ts
Normal file
4
packages/faucet/src/actions/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { generate } from "./generate";
|
||||
export { help } from "./help";
|
||||
export { start } from "./start";
|
||||
export { version } from "./version";
|
18
packages/faucet/src/actions/start/httperror.spec.ts
Normal file
18
packages/faucet/src/actions/start/httperror.spec.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { HttpError } from "./httperror";
|
||||
|
||||
describe("HttpError", () => {
|
||||
it("can be constructed", () => {
|
||||
{
|
||||
const error = new HttpError(400, "Invalid name field");
|
||||
expect(error.message).toEqual("Invalid name field");
|
||||
expect(error.status).toEqual(400);
|
||||
expect(error.expose).toEqual(true);
|
||||
}
|
||||
{
|
||||
const error = new HttpError(500, "Out of memory", false);
|
||||
expect(error.message).toEqual("Out of memory");
|
||||
expect(error.status).toEqual(500);
|
||||
expect(error.expose).toEqual(false);
|
||||
}
|
||||
});
|
||||
});
|
5
packages/faucet/src/actions/start/httperror.ts
Normal file
5
packages/faucet/src/actions/start/httperror.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class HttpError extends Error {
|
||||
constructor(public readonly status: number, text: string, public readonly expose: boolean = true) {
|
||||
super(text);
|
||||
}
|
||||
}
|
1
packages/faucet/src/actions/start/index.ts
Normal file
1
packages/faucet/src/actions/start/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { start } from "./start";
|
46
packages/faucet/src/actions/start/requestparser.spec.ts
Normal file
46
packages/faucet/src/actions/start/requestparser.spec.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { RequestParser } from "./requestparser";
|
||||
|
||||
describe("RequestParser", () => {
|
||||
it("can process valid credit request", () => {
|
||||
const body = { address: "abc", ticker: "CASH" };
|
||||
expect(RequestParser.parseCreditBody(body)).toEqual({ address: "abc", ticker: "CASH" });
|
||||
});
|
||||
|
||||
it("throws for invalid credit requests", () => {
|
||||
// address unset
|
||||
{
|
||||
const body = { ticker: "CASH" };
|
||||
expect(() => RequestParser.parseCreditBody(body)).toThrowError(/Property 'address' must be a string/i);
|
||||
}
|
||||
|
||||
// address wrong type
|
||||
{
|
||||
const body = { address: true, ticker: "CASH" };
|
||||
expect(() => RequestParser.parseCreditBody(body)).toThrowError(/Property 'address' must be a string/i);
|
||||
}
|
||||
|
||||
// address empty
|
||||
{
|
||||
const body = { address: "", ticker: "CASH" };
|
||||
expect(() => RequestParser.parseCreditBody(body)).toThrowError(/Property 'address' must not be empty/i);
|
||||
}
|
||||
|
||||
// ticker unset
|
||||
{
|
||||
const body = { address: "abc" };
|
||||
expect(() => RequestParser.parseCreditBody(body)).toThrowError(/Property 'ticker' must be a string/i);
|
||||
}
|
||||
|
||||
// ticker wrong type
|
||||
{
|
||||
const body = { address: "abc", ticker: true };
|
||||
expect(() => RequestParser.parseCreditBody(body)).toThrowError(/Property 'ticker' must be a string/i);
|
||||
}
|
||||
|
||||
// ticker empty
|
||||
{
|
||||
const body = { address: "abc", ticker: "" };
|
||||
expect(() => RequestParser.parseCreditBody(body)).toThrowError(/Property 'ticker' must not be empty/i);
|
||||
}
|
||||
});
|
||||
});
|
35
packages/faucet/src/actions/start/requestparser.ts
Normal file
35
packages/faucet/src/actions/start/requestparser.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Address, TokenTicker } from "@iov/bcp";
|
||||
|
||||
import { HttpError } from "./httperror";
|
||||
|
||||
export interface CreditRequestBodyData {
|
||||
readonly ticker: TokenTicker;
|
||||
readonly address: Address;
|
||||
}
|
||||
|
||||
export class RequestParser {
|
||||
public static parseCreditBody(body: any): CreditRequestBodyData {
|
||||
const { address, ticker } = body;
|
||||
|
||||
if (typeof address !== "string") {
|
||||
throw new HttpError(400, "Property 'address' must be a string.");
|
||||
}
|
||||
|
||||
if (address.length === 0) {
|
||||
throw new HttpError(400, "Property 'address' must not be empty.");
|
||||
}
|
||||
|
||||
if (typeof ticker !== "string") {
|
||||
throw new HttpError(400, "Property 'ticker' must be a string");
|
||||
}
|
||||
|
||||
if (ticker.length === 0) {
|
||||
throw new HttpError(400, "Property 'ticker' must not be empty.");
|
||||
}
|
||||
|
||||
return {
|
||||
address: address as Address,
|
||||
ticker: ticker as TokenTicker,
|
||||
};
|
||||
}
|
||||
}
|
155
packages/faucet/src/actions/start/start.ts
Normal file
155
packages/faucet/src/actions/start/start.ts
Normal file
@ -0,0 +1,155 @@
|
||||
// tslint:disable: no-object-mutation
|
||||
import { UserProfile } from "@iov/keycontrol";
|
||||
import { MultiChainSigner } from "@iov/multichain";
|
||||
import cors = require("@koa/cors");
|
||||
import Koa from "koa";
|
||||
import bodyParser from "koa-bodyparser";
|
||||
|
||||
import { creditAmount, setFractionalDigits } from "../../cashflow";
|
||||
import {
|
||||
codecDefaultFractionalDigits,
|
||||
codecFromString,
|
||||
codecImplementation,
|
||||
createChainConnector,
|
||||
} from "../../codec";
|
||||
import * as constants from "../../constants";
|
||||
import { logAccountsState, logSendJob } from "../../debugging";
|
||||
import {
|
||||
accountsOfFirstChain,
|
||||
availableTokensFromHolder,
|
||||
identitiesOfFirstWallet,
|
||||
refillFirstChain,
|
||||
sendOnFirstChain,
|
||||
tokenTickersOfFirstChain,
|
||||
} from "../../multichainhelpers";
|
||||
import { setSecretAndCreateIdentities } from "../../profile";
|
||||
import { SendJob } from "../../types";
|
||||
import { HttpError } from "./httperror";
|
||||
import { RequestParser } from "./requestparser";
|
||||
|
||||
let count = 0;
|
||||
|
||||
/** returns an integer >= 0 that increments and is unique in module scope */
|
||||
function getCount(): number {
|
||||
return count++;
|
||||
}
|
||||
|
||||
export async function start(args: ReadonlyArray<string>): Promise<void> {
|
||||
if (args.length < 2) {
|
||||
throw Error(`Not enough arguments for action 'start'. See 'iov-faucet help' or README for arguments.`);
|
||||
}
|
||||
const codec = codecFromString(args[0]);
|
||||
const blockchainBaseUrl: string = args[1];
|
||||
|
||||
const port = constants.port;
|
||||
|
||||
const profile = new UserProfile();
|
||||
if (!constants.mnemonic) {
|
||||
throw new Error("The FAUCET_MNEMONIC environment variable is not set");
|
||||
}
|
||||
const signer = new MultiChainSigner(profile);
|
||||
console.info(`Connecting to blockchain ${blockchainBaseUrl} ...`);
|
||||
const connection = (await signer.addChain(createChainConnector(codec, blockchainBaseUrl))).connection;
|
||||
|
||||
const connectedChainId = connection.chainId();
|
||||
console.info(`Connected to network: ${connectedChainId}`);
|
||||
|
||||
setFractionalDigits(codecDefaultFractionalDigits(codec));
|
||||
await setSecretAndCreateIdentities(profile, constants.mnemonic, connectedChainId, codec);
|
||||
|
||||
const chainTokens = await tokenTickersOfFirstChain(signer);
|
||||
console.info("Chain tokens:", chainTokens);
|
||||
|
||||
const accounts = await accountsOfFirstChain(profile, signer);
|
||||
logAccountsState(accounts);
|
||||
|
||||
let availableTokens = availableTokensFromHolder(accounts[0]);
|
||||
console.info("Available tokens:", availableTokens);
|
||||
setInterval(async () => {
|
||||
const updatedAccounts = await accountsOfFirstChain(profile, signer);
|
||||
availableTokens = availableTokensFromHolder(updatedAccounts[0]);
|
||||
console.info("Available tokens:", availableTokens);
|
||||
}, 60_000);
|
||||
|
||||
const distibutorIdentities = identitiesOfFirstWallet(profile).slice(1);
|
||||
|
||||
await refillFirstChain(profile, signer);
|
||||
setInterval(async () => refillFirstChain(profile, signer), 60_000); // ever 60 seconds
|
||||
|
||||
console.info("Creating webserver ...");
|
||||
const api = new Koa();
|
||||
api.use(cors());
|
||||
api.use(bodyParser());
|
||||
|
||||
api.use(async context => {
|
||||
switch (context.path) {
|
||||
case "/":
|
||||
case "/healthz":
|
||||
context.response.body =
|
||||
"Welcome to the faucet!\n" +
|
||||
"\n" +
|
||||
"Check the full status via the /status endpoint.\n" +
|
||||
"You can get tokens from here by POSTing to /credit.\n" +
|
||||
"See https://github.com/iov-one/iov-faucet for all further information.\n";
|
||||
break;
|
||||
case "/status": {
|
||||
const updatedAccounts = await accountsOfFirstChain(profile, signer);
|
||||
context.response.body = {
|
||||
status: "ok",
|
||||
nodeUrl: blockchainBaseUrl,
|
||||
chainId: connectedChainId,
|
||||
chainTokens: chainTokens,
|
||||
availableTokens: availableTokens,
|
||||
holder: updatedAccounts[0],
|
||||
distributors: updatedAccounts.slice(1),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "/credit": {
|
||||
if (context.request.method !== "POST") {
|
||||
throw new HttpError(405, "This endpoint requires a POST request");
|
||||
}
|
||||
|
||||
if (context.request.type !== "application/json") {
|
||||
throw new HttpError(415, "Content-type application/json expected");
|
||||
}
|
||||
|
||||
// context.request.body is set by the bodyParser() plugin
|
||||
const requestBody = (context.request as any).body;
|
||||
const { address, ticker } = RequestParser.parseCreditBody(requestBody);
|
||||
|
||||
if (!codecImplementation(codec).isValidAddress(address)) {
|
||||
throw new HttpError(400, "Address is not in the expected format for this chain.");
|
||||
}
|
||||
|
||||
if (availableTokens.indexOf(ticker) === -1) {
|
||||
const tokens = JSON.stringify(availableTokens);
|
||||
throw new HttpError(422, `Token is not available. Available tokens are: ${tokens}`);
|
||||
}
|
||||
|
||||
const sender = distibutorIdentities[getCount() % distibutorIdentities.length];
|
||||
|
||||
try {
|
||||
const job: SendJob = {
|
||||
sender: sender,
|
||||
recipient: address,
|
||||
amount: creditAmount(ticker),
|
||||
tokenTicker: ticker,
|
||||
};
|
||||
logSendJob(signer, job);
|
||||
await sendOnFirstChain(profile, signer, job);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw new HttpError(500, "Sending tokens failed");
|
||||
}
|
||||
|
||||
context.response.body = "ok";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// koa sends 404 by default
|
||||
}
|
||||
});
|
||||
console.info(`Starting webserver on port ${port} ...`);
|
||||
api.listen(port);
|
||||
}
|
15
packages/faucet/src/actions/version.ts
Normal file
15
packages/faucet/src/actions/version.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import fs from "fs";
|
||||
|
||||
export async function version(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.readFile(__dirname + "/../../package.json", { encoding: "utf8" }, (error, data) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
const packagejson = JSON.parse(data);
|
||||
process.stdout.write(`${packagejson.version}\n`);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
125
packages/faucet/src/cashflow.spec.ts
Normal file
125
packages/faucet/src/cashflow.spec.ts
Normal file
@ -0,0 +1,125 @@
|
||||
// tslint:disable: no-object-mutation
|
||||
import { TokenTicker } from "@iov/bcp";
|
||||
|
||||
import { creditAmount, refillAmount, refillThreshold, setFractionalDigits } from "./cashflow";
|
||||
|
||||
describe("Cashflow", () => {
|
||||
beforeAll(() => {
|
||||
setFractionalDigits(3);
|
||||
});
|
||||
|
||||
describe("creditAmount", () => {
|
||||
it("returns '10' + '000' by default", () => {
|
||||
expect(creditAmount("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "10000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
expect(creditAmount("TRASH" as TokenTicker)).toEqual({
|
||||
quantity: "10000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TRASH",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns value from env variable + '000' when set", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "22";
|
||||
expect(creditAmount("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "22000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns default from env variable + '000' when set to empty", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "";
|
||||
expect(creditAmount("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "10000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("refillAmount", () => {
|
||||
beforeEach(() => {
|
||||
process.env.FAUCET_REFILL_FACTOR = "";
|
||||
});
|
||||
it("returns 20*10 + '000' by default", () => {
|
||||
expect(refillAmount("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "200000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 20*22 + '000' when credit amount is 22", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "22";
|
||||
expect(refillAmount("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "440000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 30*10 + '000' when refill factor is 30", () => {
|
||||
process.env.FAUCET_REFILL_FACTOR = "30";
|
||||
expect(refillAmount("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "300000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 30*22 + '000' when refill factor is 30 and credit amount is 22", () => {
|
||||
process.env.FAUCET_REFILL_FACTOR = "30";
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "22";
|
||||
expect(refillAmount("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "660000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("refillThreshold", () => {
|
||||
beforeEach(() => {
|
||||
process.env.FAUCET_REFILL_THRESHOLD = "";
|
||||
});
|
||||
it("returns 8*10 + '000' by default", () => {
|
||||
expect(refillThreshold("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "80000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 8*22 + '000' when credit amount is 22", () => {
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "22";
|
||||
expect(refillThreshold("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "176000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 5*10 + '000' when refill threshold is 5", () => {
|
||||
process.env.FAUCET_REFILL_THRESHOLD = "5";
|
||||
expect(refillThreshold("TOKENZ" as TokenTicker)).toEqual({
|
||||
quantity: "50000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "TOKENZ",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns 5*22 + '000' when refill threshold is 5 and credit amount is 22", () => {
|
||||
process.env.FAUCET_REFILL_THRESHOLD = "5";
|
||||
process.env.FAUCET_CREDIT_AMOUNT_WTF = "22";
|
||||
expect(refillThreshold("WTF" as TokenTicker)).toEqual({
|
||||
quantity: "110000",
|
||||
fractionalDigits: 3,
|
||||
tokenTicker: "WTF",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
60
packages/faucet/src/cashflow.ts
Normal file
60
packages/faucet/src/cashflow.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import BN = require("bn.js");
|
||||
|
||||
import { Account, Amount, TokenTicker } from "@iov/bcp";
|
||||
import { Int53 } from "@iov/encoding";
|
||||
|
||||
/** Send `factor` times credit amount on refilling */
|
||||
const defaultRefillFactor = 20;
|
||||
|
||||
/** refill when balance gets below `factor` times credit amount */
|
||||
const defaultRefillThresholdFactor = 8;
|
||||
|
||||
// Load this from connection?
|
||||
let globalFractionalDigits: number | undefined;
|
||||
|
||||
export function setFractionalDigits(input: number): void {
|
||||
globalFractionalDigits = input;
|
||||
}
|
||||
|
||||
export function getFractionalDigits(): number {
|
||||
if (globalFractionalDigits === undefined) {
|
||||
throw new Error("Fractional digits not set");
|
||||
}
|
||||
return globalFractionalDigits;
|
||||
}
|
||||
|
||||
/** The amount of tokens that will be sent to the user */
|
||||
export function creditAmount(token: TokenTicker, factor = 1): Amount {
|
||||
const amountFromEnv = process.env[`FAUCET_CREDIT_AMOUNT_${token}`];
|
||||
const wholeNumber = amountFromEnv ? Int53.fromString(amountFromEnv).toNumber() : 10;
|
||||
const total = wholeNumber * factor;
|
||||
const fractionalDigits = getFractionalDigits();
|
||||
// replace BN with BigInt with TypeScript 3.2 and node 11
|
||||
const quantity = new BN(total).imul(new BN(10).pow(new BN(fractionalDigits))).toString();
|
||||
return {
|
||||
quantity: quantity,
|
||||
fractionalDigits: fractionalDigits,
|
||||
tokenTicker: token,
|
||||
};
|
||||
}
|
||||
|
||||
export function refillAmount(token: TokenTicker): Amount {
|
||||
const factorFromEnv = Number.parseInt(process.env.FAUCET_REFILL_FACTOR || "0", 10) || undefined;
|
||||
const factor = factorFromEnv || defaultRefillFactor;
|
||||
return creditAmount(token, factor);
|
||||
}
|
||||
|
||||
export function refillThreshold(token: TokenTicker): Amount {
|
||||
const factorFromEnv = Number.parseInt(process.env.FAUCET_REFILL_THRESHOLD || "0", 10) || undefined;
|
||||
const factor = factorFromEnv || defaultRefillThresholdFactor;
|
||||
return creditAmount(token, factor);
|
||||
}
|
||||
|
||||
/** true iff the distributor account needs a refill */
|
||||
export function needsRefill(account: Account, token: TokenTicker): boolean {
|
||||
const coin = account.balance.find(balance => balance.tokenTicker === token);
|
||||
|
||||
const tokenBalance = coin ? coin.quantity : "0";
|
||||
const refillQty = new BN(refillThreshold(token).quantity);
|
||||
return new BN(tokenBalance).lt(refillQty);
|
||||
}
|
14
packages/faucet/src/codec.spec.ts
Normal file
14
packages/faucet/src/codec.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Codec, codecFromString } from "./codec";
|
||||
|
||||
describe("Codec", () => {
|
||||
it("can convert string to codec", () => {
|
||||
expect(codecFromString("lisk")).toEqual(Codec.Lisk);
|
||||
expect(codecFromString("cosmwasm")).toEqual(Codec.CosmWasm);
|
||||
|
||||
expect(() => codecFromString("")).toThrowError(/not supported/i);
|
||||
expect(() => codecFromString("bns")).toThrowError(/not supported/i);
|
||||
expect(() => codecFromString("abc")).toThrowError(/not supported/i);
|
||||
expect(() => codecFromString("LISK")).toThrowError(/not supported/i);
|
||||
expect(() => codecFromString("CosmWasm")).toThrowError(/not supported/i);
|
||||
});
|
||||
});
|
76
packages/faucet/src/codec.ts
Normal file
76
packages/faucet/src/codec.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { createCosmWasmConnector, TokenInfo } from "@cosmwasm/bcp";
|
||||
import { ChainConnector, TokenTicker, TxCodec } from "@iov/bcp";
|
||||
import { Slip10RawIndex } from "@iov/crypto";
|
||||
import { HdPaths } from "@iov/keycontrol";
|
||||
import { createLiskConnector } from "@iov/lisk";
|
||||
|
||||
export const enum Codec {
|
||||
Lisk,
|
||||
CosmWasm,
|
||||
}
|
||||
|
||||
export function codecFromString(input: string): Codec {
|
||||
switch (input) {
|
||||
case "lisk":
|
||||
return Codec.Lisk;
|
||||
case "cosmwasm":
|
||||
return Codec.CosmWasm;
|
||||
default:
|
||||
throw new Error(`Codec '${input}' not supported`);
|
||||
}
|
||||
}
|
||||
|
||||
export function createPathBuilderForCodec(codec: Codec): (derivation: number) => readonly Slip10RawIndex[] {
|
||||
const pathBuilder = (accountIndex: number): readonly Slip10RawIndex[] => {
|
||||
switch (codec) {
|
||||
case Codec.Lisk:
|
||||
return HdPaths.bip44Like(134, accountIndex);
|
||||
case Codec.CosmWasm:
|
||||
return HdPaths.cosmos(accountIndex);
|
||||
default:
|
||||
throw new Error("No path builder for this codec found");
|
||||
}
|
||||
};
|
||||
return pathBuilder;
|
||||
}
|
||||
|
||||
export function createChainConnector(codec: Codec, url: string): ChainConnector {
|
||||
switch (codec) {
|
||||
case Codec.Lisk:
|
||||
return createLiskConnector(url);
|
||||
case Codec.CosmWasm: {
|
||||
const tokens: readonly TokenInfo[] = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Fee Token",
|
||||
tokenTicker: "COSM" as TokenTicker,
|
||||
denom: "cosm",
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Staking Token",
|
||||
tokenTicker: "STAKE" as TokenTicker,
|
||||
denom: "stake",
|
||||
},
|
||||
];
|
||||
return createCosmWasmConnector(url, "cosmos", tokens);
|
||||
}
|
||||
default:
|
||||
throw new Error("No connector for this codec found");
|
||||
}
|
||||
}
|
||||
|
||||
export function codecImplementation(codec: Codec): TxCodec {
|
||||
return createChainConnector(codec, "unused dummy url").codec;
|
||||
}
|
||||
|
||||
export function codecDefaultFractionalDigits(codec: Codec): number {
|
||||
switch (codec) {
|
||||
case Codec.Lisk:
|
||||
return 8;
|
||||
case Codec.CosmWasm:
|
||||
return 6;
|
||||
default:
|
||||
throw new Error("Unknown codec");
|
||||
}
|
||||
}
|
3
packages/faucet/src/constants.ts
Normal file
3
packages/faucet/src/constants.ts
Normal file
@ -0,0 +1,3 @@
|
||||
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;
|
14
packages/faucet/src/crypto.ts
Normal file
14
packages/faucet/src/crypto.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Ed25519HdWallet, Secp256k1HdWallet, Wallet } from "@iov/keycontrol";
|
||||
|
||||
import { Codec } from "./codec";
|
||||
|
||||
export function createWalletForCodec(input: Codec, mnemonic: string): Wallet {
|
||||
switch (input) {
|
||||
case Codec.Lisk:
|
||||
return Ed25519HdWallet.fromMnemonic(mnemonic);
|
||||
case Codec.CosmWasm:
|
||||
return Secp256k1HdWallet.fromMnemonic(mnemonic);
|
||||
default:
|
||||
throw new Error(`Codec '${input}' not supported`);
|
||||
}
|
||||
}
|
58
packages/faucet/src/debugging.ts
Normal file
58
packages/faucet/src/debugging.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { Account, Amount } from "@iov/bcp";
|
||||
import { MultiChainSigner } from "@iov/multichain";
|
||||
|
||||
import { SendJob } from "./types";
|
||||
|
||||
export function amountToNumber(amount: Amount): number {
|
||||
const { quantity, fractionalDigits } = amount;
|
||||
if (!quantity.match(/^[0-9]+$/)) {
|
||||
throw new Error(`quantity must be a number, got ${quantity}`);
|
||||
}
|
||||
if (fractionalDigits < 0) {
|
||||
throw new Error(`invalid fractional digits: ${fractionalDigits}`);
|
||||
}
|
||||
// let's remove those leading zeros...
|
||||
const temp = quantity.replace(/^0+/, "");
|
||||
// unless we need them to reach a decimal point
|
||||
const pad = fractionalDigits - temp.length;
|
||||
const trimmed = pad > 0 ? "0".repeat(pad) + temp : temp;
|
||||
|
||||
const cut = trimmed.length - fractionalDigits;
|
||||
const whole = cut === 0 ? "0" : trimmed.slice(0, cut);
|
||||
const decimal = fractionalDigits === 0 ? "" : `.${trimmed.slice(cut)}`;
|
||||
const value = `${whole}${decimal}`;
|
||||
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
/** A string representation of a coin in a human-readable format that can change at any time */
|
||||
export function debugCoin(coin: Amount): string {
|
||||
return `${amountToNumber(coin)} ${coin.tokenTicker}`;
|
||||
}
|
||||
|
||||
/** A string representation of a balance in a human-readable format that can change at any time */
|
||||
export function debugBalance(data: ReadonlyArray<Amount>): string {
|
||||
return `[${data.map(debugCoin).join(", ")}]`;
|
||||
}
|
||||
|
||||
/** A string representation of an account in a human-readable format that can change at any time */
|
||||
export function debugAccount(account: Account): string {
|
||||
return `${account.address}: ${debugBalance(account.balance)}`;
|
||||
}
|
||||
|
||||
export function logAccountsState(accounts: ReadonlyArray<Account>): void {
|
||||
if (accounts.length < 2) {
|
||||
throw new Error("List of accounts must contain at least one token holder and one distributor");
|
||||
}
|
||||
const holder = accounts[0];
|
||||
const distributors = accounts.slice(1);
|
||||
console.info("Holder:\n" + ` ${debugAccount(holder)}`);
|
||||
console.info("Distributors:\n" + distributors.map(r => ` ${debugAccount(r)}`).join("\n"));
|
||||
}
|
||||
|
||||
export function logSendJob(signer: MultiChainSigner, job: SendJob): void {
|
||||
const from = signer.identityToAddress(job.sender);
|
||||
const to = job.recipient;
|
||||
const amount = debugCoin(job.amount);
|
||||
console.info(`Sending ${amount} from ${from} to ${to} ...`);
|
||||
}
|
38
packages/faucet/src/faucet.ts
Normal file
38
packages/faucet/src/faucet.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { generate, help, start, version } from "./actions";
|
||||
|
||||
export function main(args: ReadonlyArray<string>): void {
|
||||
if (args.length < 1) {
|
||||
help();
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const action = args[0];
|
||||
const restArgs = args.slice(1);
|
||||
|
||||
switch (action) {
|
||||
case "generate":
|
||||
generate(restArgs).catch(error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
break;
|
||||
case "help":
|
||||
help();
|
||||
break;
|
||||
case "version":
|
||||
version().catch(error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
break;
|
||||
case "start":
|
||||
start(restArgs).catch(error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unexpected action argument");
|
||||
}
|
||||
}
|
44
packages/faucet/src/hdpaths.spec.ts
Normal file
44
packages/faucet/src/hdpaths.spec.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Slip10RawIndex } from "@iov/crypto";
|
||||
|
||||
import { debugPath } from "./hdpaths";
|
||||
|
||||
describe("hdpaths", () => {
|
||||
describe("debugPath", () => {
|
||||
it("works for no component", () => {
|
||||
// See https://github.com/bitcoin/bips/blob/master/bip-0032/derivation.png from BIP32
|
||||
expect(debugPath([])).toEqual("m");
|
||||
});
|
||||
|
||||
it("works for normal components", () => {
|
||||
const one = Slip10RawIndex.normal(1);
|
||||
expect(debugPath([one])).toEqual("m/1");
|
||||
expect(debugPath([one, one])).toEqual("m/1/1");
|
||||
expect(debugPath([one, one, one])).toEqual("m/1/1/1");
|
||||
|
||||
const min = Slip10RawIndex.normal(0);
|
||||
expect(debugPath([min])).toEqual("m/0");
|
||||
|
||||
const max = Slip10RawIndex.normal(2 ** 31 - 1);
|
||||
expect(debugPath([max])).toEqual("m/2147483647");
|
||||
});
|
||||
|
||||
it("works for hardened components", () => {
|
||||
const one = Slip10RawIndex.hardened(1);
|
||||
expect(debugPath([one])).toEqual("m/1'");
|
||||
expect(debugPath([one, one])).toEqual("m/1'/1'");
|
||||
expect(debugPath([one, one, one])).toEqual("m/1'/1'/1'");
|
||||
|
||||
const min = Slip10RawIndex.hardened(0);
|
||||
expect(debugPath([min])).toEqual("m/0'");
|
||||
|
||||
const max = Slip10RawIndex.hardened(2 ** 31 - 1);
|
||||
expect(debugPath([max])).toEqual("m/2147483647'");
|
||||
});
|
||||
|
||||
it("works for mixed components", () => {
|
||||
const one = Slip10RawIndex.normal(1);
|
||||
const two = Slip10RawIndex.hardened(2);
|
||||
expect(debugPath([one, two, two, one])).toEqual("m/1/2'/2'/1");
|
||||
});
|
||||
});
|
||||
});
|
10
packages/faucet/src/hdpaths.ts
Normal file
10
packages/faucet/src/hdpaths.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Slip10RawIndex } from "@iov/crypto";
|
||||
|
||||
export function debugPath(path: readonly Slip10RawIndex[]): string {
|
||||
return path.reduce((current, component): string => {
|
||||
const componentString = component.isHardened()
|
||||
? `${component.toNumber() - 2 ** 31}'`
|
||||
: component.toString();
|
||||
return current + "/" + componentString;
|
||||
}, "m");
|
||||
}
|
56
packages/faucet/src/multichainhelpers.spec.ts
Normal file
56
packages/faucet/src/multichainhelpers.spec.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { Address, Algorithm, PubkeyBundle, PubkeyBytes, TokenTicker } from "@iov/bcp";
|
||||
|
||||
import { availableTokensFromHolder } from "./multichainhelpers";
|
||||
|
||||
describe("multichainhelpers", () => {
|
||||
describe("availableTokensFromHolder", () => {
|
||||
const defaultPubkey: PubkeyBundle = {
|
||||
algo: Algorithm.Ed25519,
|
||||
data: new Uint8Array([0, 1, 2, 3]) as PubkeyBytes,
|
||||
};
|
||||
|
||||
it("works for an empty account", () => {
|
||||
const tickers = availableTokensFromHolder({
|
||||
address: "aabbccdd" as Address,
|
||||
pubkey: defaultPubkey,
|
||||
balance: [],
|
||||
});
|
||||
expect(tickers).toEqual([]);
|
||||
});
|
||||
|
||||
it("works for one token", () => {
|
||||
const tickers = availableTokensFromHolder({
|
||||
address: "aabbccdd" as Address,
|
||||
pubkey: defaultPubkey,
|
||||
balance: [
|
||||
{
|
||||
quantity: "1",
|
||||
fractionalDigits: 9,
|
||||
tokenTicker: "CASH" as TokenTicker,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(tickers).toEqual(["CASH"]);
|
||||
});
|
||||
|
||||
it("works for two tokens", () => {
|
||||
const tickers = availableTokensFromHolder({
|
||||
address: "aabbccdd" as Address,
|
||||
pubkey: defaultPubkey,
|
||||
balance: [
|
||||
{
|
||||
quantity: "1",
|
||||
fractionalDigits: 9,
|
||||
tokenTicker: "CASH" as TokenTicker,
|
||||
},
|
||||
{
|
||||
quantity: "1",
|
||||
fractionalDigits: 9,
|
||||
tokenTicker: "TRASH" as TokenTicker,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(tickers).toEqual(["CASH", "TRASH"]);
|
||||
});
|
||||
});
|
||||
});
|
137
packages/faucet/src/multichainhelpers.ts
Normal file
137
packages/faucet/src/multichainhelpers.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import {
|
||||
Account,
|
||||
Identity,
|
||||
isBlockInfoFailed,
|
||||
isBlockInfoPending,
|
||||
SendTransaction,
|
||||
TokenTicker,
|
||||
} from "@iov/bcp";
|
||||
import { UserProfile } from "@iov/keycontrol";
|
||||
import { MultiChainSigner } from "@iov/multichain";
|
||||
|
||||
import { needsRefill, refillAmount } from "./cashflow";
|
||||
import { debugAccount, logAccountsState, logSendJob } from "./debugging";
|
||||
import { SendJob } from "./types";
|
||||
|
||||
async function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function identitiesOfFirstWallet(profile: UserProfile): ReadonlyArray<Identity> {
|
||||
const wallet = profile.wallets.value[0];
|
||||
return profile.getIdentities(wallet.id);
|
||||
}
|
||||
|
||||
export async function accountsOfFirstChain(
|
||||
profile: UserProfile,
|
||||
signer: MultiChainSigner,
|
||||
): Promise<ReadonlyArray<Account>> {
|
||||
const addresses = identitiesOfFirstWallet(profile).map(identity => signer.identityToAddress(identity));
|
||||
const chainId = signer.chainIds()[0];
|
||||
|
||||
// tslint:disable-next-line: readonly-array
|
||||
const out: Account[] = [];
|
||||
for (const address of addresses) {
|
||||
const response = await signer.connection(chainId).getAccount({ address: address });
|
||||
if (response) {
|
||||
out.push({
|
||||
address: response.address,
|
||||
balance: response.balance,
|
||||
});
|
||||
} else {
|
||||
out.push({
|
||||
address: address,
|
||||
balance: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export async function tokenTickersOfFirstChain(
|
||||
signer: MultiChainSigner,
|
||||
): Promise<ReadonlyArray<TokenTicker>> {
|
||||
const chainId = signer.chainIds()[0];
|
||||
return (await signer.connection(chainId).getAllTokens()).map(token => token.tokenTicker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and posts a send transaction. Then waits until the transaction is in a block.
|
||||
*/
|
||||
export async function sendOnFirstChain(
|
||||
profile: UserProfile,
|
||||
signer: MultiChainSigner,
|
||||
job: SendJob,
|
||||
): Promise<void> {
|
||||
const chainId = signer.chainIds()[0];
|
||||
const connection = signer.connection(chainId);
|
||||
|
||||
const sendWithFee = await connection.withDefaultFee<SendTransaction>({
|
||||
kind: "bcp/send",
|
||||
chainId: chainId,
|
||||
sender: signer.identityToAddress(job.sender),
|
||||
senderPubkey: job.sender.pubkey,
|
||||
recipient: job.recipient,
|
||||
memo: "We ❤️ developers – iov.one",
|
||||
amount: job.amount,
|
||||
});
|
||||
|
||||
const post = await signer.signAndPost(job.sender, sendWithFee);
|
||||
const blockInfo = await post.blockInfo.waitFor(info => !isBlockInfoPending(info));
|
||||
if (isBlockInfoFailed(blockInfo)) {
|
||||
throw new Error(`Sending tokens failed. Code: ${blockInfo.code}, message: ${blockInfo.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function availableTokensFromHolder(holderAccount: Account): ReadonlyArray<TokenTicker> {
|
||||
return holderAccount.balance.map(coin => coin.tokenTicker);
|
||||
}
|
||||
|
||||
export async function refillFirstChain(profile: UserProfile, signer: MultiChainSigner): Promise<void> {
|
||||
const chainId = signer.chainIds()[0];
|
||||
|
||||
console.info(`Connected to network: ${chainId}`);
|
||||
console.info(`Tokens on network: ${(await tokenTickersOfFirstChain(signer)).join(", ")}`);
|
||||
|
||||
const holderIdentity = identitiesOfFirstWallet(profile)[0];
|
||||
|
||||
const accounts = await accountsOfFirstChain(profile, signer);
|
||||
logAccountsState(accounts);
|
||||
const holderAccount = accounts[0];
|
||||
const distributorAccounts = accounts.slice(1);
|
||||
|
||||
const availableTokens = availableTokensFromHolder(holderAccount);
|
||||
console.info("Available tokens:", availableTokens);
|
||||
|
||||
// tslint:disable-next-line: readonly-array
|
||||
const jobs: SendJob[] = [];
|
||||
|
||||
for (const token of availableTokens) {
|
||||
const refillDistibutors = distributorAccounts.filter(account => needsRefill(account, token));
|
||||
console.info(`Refilling ${token} of:`);
|
||||
console.info(
|
||||
refillDistibutors.length ? refillDistibutors.map(r => ` ${debugAccount(r)}`).join("\n") : " none",
|
||||
);
|
||||
for (const refillDistibutor of refillDistibutors) {
|
||||
jobs.push({
|
||||
sender: holderIdentity,
|
||||
recipient: refillDistibutor.address,
|
||||
tokenTicker: token,
|
||||
amount: refillAmount(token),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (jobs.length > 0) {
|
||||
for (const job of jobs) {
|
||||
logSendJob(signer, job);
|
||||
await sendOnFirstChain(profile, signer, job);
|
||||
await sleep(50);
|
||||
}
|
||||
|
||||
console.info("Done refilling accounts.");
|
||||
logAccountsState(await accountsOfFirstChain(profile, signer));
|
||||
} else {
|
||||
console.info("Nothing to be done. Anyways, thanks for checking.");
|
||||
}
|
||||
}
|
34
packages/faucet/src/profile.ts
Normal file
34
packages/faucet/src/profile.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { ChainId } from "@iov/bcp";
|
||||
import { UserProfile } from "@iov/keycontrol";
|
||||
|
||||
import { Codec, codecImplementation, createPathBuilderForCodec } from "./codec";
|
||||
import * as constants from "./constants";
|
||||
import { createWalletForCodec } from "./crypto";
|
||||
import { debugPath } from "./hdpaths";
|
||||
|
||||
export async function setSecretAndCreateIdentities(
|
||||
profile: UserProfile,
|
||||
mnemonic: string,
|
||||
chainId: ChainId,
|
||||
codecName: Codec,
|
||||
): Promise<void> {
|
||||
if (profile.wallets.value.length !== 0) {
|
||||
throw new Error("Profile already contains wallets");
|
||||
}
|
||||
const wallet = profile.addWallet(createWalletForCodec(codecName, mnemonic));
|
||||
|
||||
const pathBuilder = createPathBuilderForCodec(codecName);
|
||||
|
||||
// first account is the token holder
|
||||
const numberOfIdentities = 1 + constants.concurrency;
|
||||
for (let i = 0; i < numberOfIdentities; i++) {
|
||||
// create
|
||||
const path = pathBuilder(i);
|
||||
const identity = await profile.createIdentity(wallet.id, chainId, path);
|
||||
|
||||
// log
|
||||
const role = i === 0 ? "token holder " : `distributor ${i}`;
|
||||
const address = codecImplementation(codecName).identityToAddress(identity);
|
||||
console.info(`Created ${role} (${debugPath(path)}): ${address}`);
|
||||
}
|
||||
}
|
8
packages/faucet/src/types.ts
Normal file
8
packages/faucet/src/types.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Address, Amount, Identity, TokenTicker } from "@iov/bcp";
|
||||
|
||||
export interface SendJob {
|
||||
readonly sender: Identity;
|
||||
readonly recipient: Address;
|
||||
readonly tokenTicker: TokenTicker;
|
||||
readonly amount: Amount;
|
||||
}
|
12
packages/faucet/tsconfig.json
Normal file
12
packages/faucet/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "build",
|
||||
"declarationDir": "build/types",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
3
packages/faucet/tslint.json
Normal file
3
packages/faucet/tslint.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../tslint.json"
|
||||
}
|
14
packages/faucet/typedoc.js
Normal file
14
packages/faucet/typedoc.js
Normal file
@ -0,0 +1,14 @@
|
||||
const packageJson = require("./package.json");
|
||||
|
||||
module.exports = {
|
||||
src: ["./src"],
|
||||
out: "docs",
|
||||
exclude: "**/*.spec.ts",
|
||||
target: "es6",
|
||||
name: `${packageJson.name} Documentation`,
|
||||
readme: "README.md",
|
||||
mode: "file",
|
||||
excludeExternals: true,
|
||||
excludeNotExported: true,
|
||||
excludePrivate: true,
|
||||
};
|
@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -o errexit -o nounset -o pipefail
|
||||
command -v shellcheck > /dev/null && shellcheck "$0"
|
||||
|
||||
# Choose from https://hub.docker.com/r/iov1/iov-faucet/tags/
|
||||
FAUCET_VERSION="v0.8.1"
|
||||
|
||||
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/faucet_start.XXXXXXXXX")
|
||||
LOGFILE="$TMP_DIR/faucet.log"
|
||||
|
||||
DOCKER_HOST_IP=$(docker run --rm alpine ip route | awk 'NR==1 {print $3}')
|
||||
|
||||
BLOCKCHAIN_URL="ws://$DOCKER_HOST_IP:23456"
|
||||
echo "Connecting to $BLOCKCHAIN_URL"
|
||||
|
||||
docker run --rm \
|
||||
--read-only \
|
||||
--env "FAUCET_CONCURRENCY=3" \
|
||||
--env "FAUCET_MNEMONIC=degree tackle suggest window test behind mesh extra cover prepare oak script" \
|
||||
-p 8000:8000 \
|
||||
"iov1/iov-faucet:${FAUCET_VERSION}" \
|
||||
start bns "$BLOCKCHAIN_URL" \
|
||||
> "$LOGFILE" &
|
||||
|
||||
echo "Faucet running and logging into $LOGFILE"
|
@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -o errexit -o nounset -o pipefail
|
||||
command -v shellcheck > /dev/null && shellcheck "$0"
|
||||
|
||||
LABEL_PART="iov1/iov-faucet"
|
||||
|
||||
CONTAINER_ID=$(docker container ls | grep -F "$LABEL_PART:" | awk '{print $1}')
|
||||
echo "Killing $LABEL_PART container '$CONTAINER_ID' ..."
|
||||
docker container kill "$CONTAINER_ID"
|
403
yarn.lock
403
yarn.lock
@ -130,6 +130,15 @@
|
||||
bn.js "^4.11.8"
|
||||
readonly-date "^1.0.0"
|
||||
|
||||
"@iov/jsonrpc@^2.0.0-alpha.7":
|
||||
version "2.0.0-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/@iov/jsonrpc/-/jsonrpc-2.0.0-alpha.7.tgz#7bff8e1f21d52ff07482212ded8cc00e01dda964"
|
||||
integrity sha512-eVCfNi3Zg4ZUEXOxhzRb3kcoBupBGQWmU4pYu7OYBi3uvuUz8KP6kcIdsy+51+y/nqrnk3H2Ur7MWCzwsu215w==
|
||||
dependencies:
|
||||
"@iov/encoding" "^2.0.0-alpha.7"
|
||||
"@iov/stream" "^2.0.0-alpha.7"
|
||||
xstream "^11.10.0"
|
||||
|
||||
"@iov/keycontrol@^2.0.0-alpha.7":
|
||||
version "2.0.0-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/@iov/keycontrol/-/keycontrol-2.0.0-alpha.7.tgz#d115a1b536664afb64b40a6db87aaf19f8d07afd"
|
||||
@ -150,6 +159,35 @@
|
||||
type-tagger "^1.0.0"
|
||||
xstream "^11.10.0"
|
||||
|
||||
"@iov/lisk@^2.0.0-alpha.7":
|
||||
version "2.0.0-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/@iov/lisk/-/lisk-2.0.0-alpha.7.tgz#5ff6c617ac00e6be736ada34ad68dce0efd64637"
|
||||
integrity sha512-m5mr2NDU7pxuS8d6SBXCZ1WBtCSXtV+EWqMEIuPSCJwZxnwmWotowX+WhSYn+dGyuvgTd7DpfQ5C6pliAyhcyQ==
|
||||
dependencies:
|
||||
"@iov/bcp" "^2.0.0-alpha.7"
|
||||
"@iov/crypto" "^2.0.0-alpha.7"
|
||||
"@iov/encoding" "^2.0.0-alpha.7"
|
||||
"@iov/keycontrol" "^2.0.0-alpha.7"
|
||||
"@iov/stream" "^2.0.0-alpha.7"
|
||||
"@types/long" "^4.0.0"
|
||||
axios "^0.19.0"
|
||||
fast-deep-equal "^3.1.1"
|
||||
long "^4.0.0"
|
||||
readonly-date "^1.0.0"
|
||||
xstream "^11.10.0"
|
||||
|
||||
"@iov/multichain@^2.0.0-alpha.7":
|
||||
version "2.0.0-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/@iov/multichain/-/multichain-2.0.0-alpha.7.tgz#29ec9f61ec2fa60c17d462c9be48feb7196c28ba"
|
||||
integrity sha512-hhEyqalADrQa4JS99JqawM7jWLYGqb52Ipq/49Zz5eDG9/nj9QyLn5nayWLTbm60ZXzHvvGr5oJpZx8Xb2PzfA==
|
||||
dependencies:
|
||||
"@iov/bcp" "^2.0.0-alpha.7"
|
||||
"@iov/encoding" "^2.0.0-alpha.7"
|
||||
"@iov/jsonrpc" "^2.0.0-alpha.7"
|
||||
"@iov/keycontrol" "^2.0.0-alpha.7"
|
||||
"@types/long" "^4.0.0"
|
||||
long "^4.0.0"
|
||||
|
||||
"@iov/stream@^2.0.0-alpha.7":
|
||||
version "2.0.0-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/@iov/stream/-/stream-2.0.0-alpha.7.tgz#212c3f684f592ec04ac43e166183d946a49b895c"
|
||||
@ -157,6 +195,13 @@
|
||||
dependencies:
|
||||
xstream "^11.10.0"
|
||||
|
||||
"@koa/cors@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.0.0.tgz#df021b4df2dadf1e2b04d7c8ddf93ba2d42519cb"
|
||||
integrity sha512-hDp+cXj6vTYSwHRJfiSpnf5dTMv3FmqNKh1or9BPJk4SHOviHnK9GoL9dT0ypt/E+hlkRkZ9edHylcosW3Ghrw==
|
||||
dependencies:
|
||||
vary "^1.1.2"
|
||||
|
||||
"@lerna/add@3.20.0":
|
||||
version "3.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.20.0.tgz#bea7edf36fc93fb72ec34cb9ba854c48d4abf309"
|
||||
@ -950,6 +995,45 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/abstract-leveldown/-/abstract-leveldown-5.0.1.tgz#3c7750d0186b954c7f2d2f6acc8c3c7ba0c3412e"
|
||||
integrity sha512-wYxU3kp5zItbxKmeRYCEplS2MW7DzyBnxPGj+GJVHZEUZiK/nn5Ei1sUFgURDh+X051+zsGe28iud3oHjrYWQQ==
|
||||
|
||||
"@types/accepts@*":
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575"
|
||||
integrity sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/bn.js@^4.11.6":
|
||||
version "4.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
|
||||
integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.17.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.1.tgz#18fcf61768fb5c30ccc508c21d6fd2e8b3bf7897"
|
||||
integrity sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==
|
||||
dependencies:
|
||||
"@types/connect" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/connect@*":
|
||||
version "3.4.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546"
|
||||
integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/cookies@*":
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/cookies/-/cookies-0.7.4.tgz#26dedf791701abc0e36b5b79a5722f40e455f87b"
|
||||
integrity sha512-oTGtMzZZAVuEjTwCjIh8T8FrC8n/uwy+PG0yTvQcdZ7etoel7C7/3MSd7qrukENTgQtotG7gvBlBojuVs7X5rw==
|
||||
dependencies:
|
||||
"@types/connect" "*"
|
||||
"@types/express" "*"
|
||||
"@types/keygrip" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/eslint-visitor-keys@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||
@ -960,6 +1044,23 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
|
||||
|
||||
"@types/express-serve-static-core@*":
|
||||
version "4.17.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz#f6f41fa35d42e79dbf6610eccbb2637e6008a0cf"
|
||||
integrity sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/range-parser" "*"
|
||||
|
||||
"@types/express@*":
|
||||
version "4.17.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c"
|
||||
integrity sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/glob@^7.1.1":
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
|
||||
@ -969,6 +1070,11 @@
|
||||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/http-assert@*":
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b"
|
||||
integrity sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==
|
||||
|
||||
"@types/jasmine@^3.3.7":
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.5.1.tgz#e417aa74738aa9e9285016abbfcae68d5be0f827"
|
||||
@ -979,6 +1085,44 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
|
||||
integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
|
||||
|
||||
"@types/keygrip@*":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
|
||||
integrity sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==
|
||||
|
||||
"@types/koa-bodyparser@^4.3.0":
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.0.tgz#54ecd662c45f3a4fa9de849528de5fc8ab269ba5"
|
||||
integrity sha512-aB/vwwq4G9FAtKzqZ2p8UHTscXxZvICFKVjuckqxCtkX1Ro7F5KHkTCUqTRZFBgDoEkmeca+bFLI1bIsdPPZTA==
|
||||
dependencies:
|
||||
"@types/koa" "*"
|
||||
|
||||
"@types/koa-compose@*":
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/koa-compose/-/koa-compose-3.2.5.tgz#85eb2e80ac50be95f37ccf8c407c09bbe3468e9d"
|
||||
integrity sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==
|
||||
dependencies:
|
||||
"@types/koa" "*"
|
||||
|
||||
"@types/koa@*", "@types/koa@^2.11.0":
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.11.0.tgz#394a3e9ec94f796003a6c8374b4dbc2778746f20"
|
||||
integrity sha512-Hgx/1/rVlJvqYBrdeCsS7PDiR2qbxlMt1RnmNWD4Uxi5FF9nwkYqIldo7urjc+dfNpk+2NRGcnAYd4L5xEhCcQ==
|
||||
dependencies:
|
||||
"@types/accepts" "*"
|
||||
"@types/cookies" "*"
|
||||
"@types/http-assert" "*"
|
||||
"@types/keygrip" "*"
|
||||
"@types/koa-compose" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/koa__cors@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.0.1.tgz#a8cf8535f0fe682c9421f1b9379837c585f8b66b"
|
||||
integrity sha512-loqZNXliley8kncc4wrX9KMqLGN6YfiaO3a3VFX+yVkkXJwOrZU4lipdudNjw5mFyC+5hd7h9075hQWcVVpeOg==
|
||||
dependencies:
|
||||
"@types/koa" "*"
|
||||
|
||||
"@types/levelup@^3.1.0":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/levelup/-/levelup-3.1.1.tgz#f7cc08f248f14cb6c92914e91bceb8761020e8f0"
|
||||
@ -987,6 +1131,11 @@
|
||||
"@types/abstract-leveldown" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/long@^4.0.0":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
|
||||
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
|
||||
|
||||
"@types/memdown@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/memdown/-/memdown-3.0.0.tgz#2d909cb507afd341e3132d77dafa213347e47455"
|
||||
@ -994,6 +1143,11 @@
|
||||
dependencies:
|
||||
"@types/abstract-leveldown" "*"
|
||||
|
||||
"@types/mime@*":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
|
||||
integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
|
||||
|
||||
"@types/minimatch@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
@ -1019,6 +1173,19 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/random-js/-/random-js-1.0.31.tgz#18a8bcc075afa504421e638fcbe021f27e802941"
|
||||
integrity sha512-EAM56DrKw3VhcE4HV0/YlVKeJI07We4Mz1ra6TNtZpaMoiBVMA2bkLEcoFpYOyxoDXfVZWojxkR617LTqtRI0A==
|
||||
|
||||
"@types/range-parser@*":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
|
||||
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
|
||||
|
||||
"@types/serve-static@*":
|
||||
version "1.13.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
|
||||
integrity sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==
|
||||
dependencies:
|
||||
"@types/express-serve-static-core" "*"
|
||||
"@types/mime" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^2.10.0":
|
||||
version "2.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.17.0.tgz#880435a9f9bdd50b45fa286ba63fed723d73c837"
|
||||
@ -1257,7 +1424,7 @@ abstract-leveldown@~6.2.1:
|
||||
level-supports "~1.0.0"
|
||||
xtend "~4.0.0"
|
||||
|
||||
accepts@~1.3.4:
|
||||
accepts@^1.3.5, accepts@~1.3.4:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
|
||||
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
|
||||
@ -1365,7 +1532,7 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
any-promise@^1.0.0:
|
||||
any-promise@^1.0.0, any-promise@^1.1.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
|
||||
@ -1687,6 +1854,11 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.8, bn.js@^4.4.0:
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
|
||||
integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
|
||||
|
||||
bn.js@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.1.tgz#48efc4031a9c4041b9c99c6941d903463ab62eb5"
|
||||
integrity sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA==
|
||||
|
||||
body-parser@^1.16.1:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||
@ -1906,6 +2078,14 @@ cache-base@^1.0.1:
|
||||
union-value "^1.0.0"
|
||||
unset-value "^1.0.0"
|
||||
|
||||
cache-content-type@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c"
|
||||
integrity sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==
|
||||
dependencies:
|
||||
mime-types "^2.1.18"
|
||||
ylru "^1.2.0"
|
||||
|
||||
call-me-maybe@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
|
||||
@ -2102,6 +2282,21 @@ clone@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
|
||||
|
||||
co-body@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/co-body/-/co-body-6.0.0.tgz#965b9337d7f5655480787471f4237664820827e3"
|
||||
integrity sha512-9ZIcixguuuKIptnY8yemEOuhb71L/lLf+Rl5JfJEUiDNJk0e02MBt7BPxR2GEh5mw8dPthQYR4jPI/BnS1MQgw==
|
||||
dependencies:
|
||||
inflation "^2.0.0"
|
||||
qs "^6.5.2"
|
||||
raw-body "^2.3.3"
|
||||
type-is "^1.6.16"
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
|
||||
|
||||
code-point-at@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
@ -2253,7 +2448,14 @@ contains-path@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
|
||||
integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
|
||||
|
||||
content-type@~1.0.4:
|
||||
content-disposition@~0.5.2:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
|
||||
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
|
||||
dependencies:
|
||||
safe-buffer "5.1.2"
|
||||
|
||||
content-type@^1.0.4, content-type@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||
@ -2346,6 +2548,14 @@ cookie@0.3.1:
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
|
||||
|
||||
cookies@~0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
|
||||
integrity sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==
|
||||
dependencies:
|
||||
depd "~2.0.0"
|
||||
keygrip "~1.1.0"
|
||||
|
||||
copy-concurrently@^1.0.0:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
|
||||
@ -2363,6 +2573,11 @@ copy-descriptor@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
||||
|
||||
copy-to@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/copy-to/-/copy-to-2.0.1.tgz#2680fbb8068a48d08656b6098092bdafc906f4a5"
|
||||
integrity sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=
|
||||
|
||||
core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
@ -2534,6 +2749,11 @@ dedent@^0.7.0:
|
||||
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
|
||||
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
|
||||
|
||||
deep-equal@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||
integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
|
||||
|
||||
deep-is@~0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
||||
@ -2593,11 +2813,16 @@ delegates@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||
|
||||
depd@~1.1.2:
|
||||
depd@^1.1.2, depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
depd@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||
|
||||
deprecation@^2.0.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
|
||||
@ -2611,6 +2836,11 @@ des.js@^1.0.0:
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
|
||||
destroy@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
|
||||
|
||||
detect-file@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
|
||||
@ -2760,7 +2990,7 @@ emojis-list@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
|
||||
integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k=
|
||||
|
||||
encodeurl@~1.0.2:
|
||||
encodeurl@^1.0.2, encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||
@ -2871,6 +3101,11 @@ error-ex@^1.2.0, error-ex@^1.3.1:
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
error-inject@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37"
|
||||
integrity sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=
|
||||
|
||||
es-abstract@^1.17.0, es-abstract@^1.17.0-next.1:
|
||||
version "1.17.4"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
|
||||
@ -2914,7 +3149,7 @@ es6-promisify@^5.0.0:
|
||||
dependencies:
|
||||
es6-promise "^4.0.3"
|
||||
|
||||
escape-html@~1.0.3:
|
||||
escape-html@^1.0.3, escape-html@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||
@ -3393,6 +3628,11 @@ fragment-cache@^0.2.1:
|
||||
dependencies:
|
||||
map-cache "^0.2.2"
|
||||
|
||||
fresh@~0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||
|
||||
from2@^2.1.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
|
||||
@ -3809,6 +4049,14 @@ hosted-git-info@^2.1.4, hosted-git-info@^2.7.1:
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
|
||||
integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
|
||||
|
||||
http-assert@^1.3.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.4.1.tgz#c5f725d677aa7e873ef736199b89686cceb37878"
|
||||
integrity sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==
|
||||
dependencies:
|
||||
deep-equal "~1.0.1"
|
||||
http-errors "~1.7.2"
|
||||
|
||||
http-cache-semantics@^3.8.1:
|
||||
version "3.8.1"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
|
||||
@ -3825,6 +4073,17 @@ http-errors@1.7.2:
|
||||
statuses ">= 1.5.0 < 2"
|
||||
toidentifier "1.0.0"
|
||||
|
||||
http-errors@1.7.3, http-errors@^1.6.3, http-errors@~1.7.2:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
|
||||
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.4"
|
||||
setprototypeof "1.1.1"
|
||||
statuses ">= 1.5.0 < 2"
|
||||
toidentifier "1.0.0"
|
||||
|
||||
http-proxy-agent@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||
@ -3956,6 +4215,11 @@ infer-owner@^1.0.3, infer-owner@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
|
||||
integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
|
||||
|
||||
inflation@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/inflation/-/inflation-2.0.0.tgz#8b417e47c28f925a45133d914ca1fd389107f30f"
|
||||
integrity sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
@ -3964,7 +4228,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@ -4184,6 +4448,11 @@ is-fullwidth-code-point@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||
|
||||
is-generator-function@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522"
|
||||
integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==
|
||||
|
||||
is-glob@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
|
||||
@ -4514,6 +4783,13 @@ karma@^4.1.0:
|
||||
tmp "0.0.33"
|
||||
useragent "2.3.0"
|
||||
|
||||
keygrip@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
|
||||
integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==
|
||||
dependencies:
|
||||
tsscmp "1.0.6"
|
||||
|
||||
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||
@ -4538,6 +4814,64 @@ kind-of@^6.0.0, kind-of@^6.0.2:
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||
|
||||
koa-bodyparser@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/koa-bodyparser/-/koa-bodyparser-4.2.1.tgz#4d7dacb5e6db1106649b595d9e5ccb158b6f3b29"
|
||||
integrity sha512-UIjPAlMZfNYDDe+4zBaOAUKYqkwAGcIU6r2ARf1UOXPAlfennQys5IiShaVeNf7KkVBlf88f2LeLvBFvKylttw==
|
||||
dependencies:
|
||||
co-body "^6.0.0"
|
||||
copy-to "^2.0.1"
|
||||
|
||||
koa-compose@^3.0.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-3.2.1.tgz#a85ccb40b7d986d8e5a345b3a1ace8eabcf54de7"
|
||||
integrity sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=
|
||||
dependencies:
|
||||
any-promise "^1.1.0"
|
||||
|
||||
koa-compose@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877"
|
||||
integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==
|
||||
|
||||
koa-convert@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-1.2.0.tgz#da40875df49de0539098d1700b50820cebcd21d0"
|
||||
integrity sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=
|
||||
dependencies:
|
||||
co "^4.6.0"
|
||||
koa-compose "^3.0.0"
|
||||
|
||||
koa@^2.11.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/koa/-/koa-2.11.0.tgz#fe5a51c46f566d27632dd5dc8fd5d7dd44f935a4"
|
||||
integrity sha512-EpR9dElBTDlaDgyhDMiLkXrPwp6ZqgAIBvhhmxQ9XN4TFgW+gEz6tkcsNI6BnUbUftrKDjVFj4lW2/J2aNBMMA==
|
||||
dependencies:
|
||||
accepts "^1.3.5"
|
||||
cache-content-type "^1.0.0"
|
||||
content-disposition "~0.5.2"
|
||||
content-type "^1.0.4"
|
||||
cookies "~0.8.0"
|
||||
debug "~3.1.0"
|
||||
delegates "^1.0.0"
|
||||
depd "^1.1.2"
|
||||
destroy "^1.0.4"
|
||||
encodeurl "^1.0.2"
|
||||
error-inject "^1.0.0"
|
||||
escape-html "^1.0.3"
|
||||
fresh "~0.5.2"
|
||||
http-assert "^1.3.0"
|
||||
http-errors "^1.6.3"
|
||||
is-generator-function "^1.0.7"
|
||||
koa-compose "^4.1.0"
|
||||
koa-convert "^1.2.0"
|
||||
on-finished "^2.3.0"
|
||||
only "~0.0.2"
|
||||
parseurl "^1.3.2"
|
||||
statuses "^1.5.0"
|
||||
type-is "^1.6.16"
|
||||
vary "^1.1.2"
|
||||
|
||||
lcid@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
|
||||
@ -5004,7 +5338,7 @@ mime-db@1.43.0:
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
|
||||
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
|
||||
mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.19, mime-types@~2.1.24:
|
||||
version "2.1.26"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
|
||||
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
|
||||
@ -5479,7 +5813,7 @@ octokit-pagination-methods@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
|
||||
integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==
|
||||
|
||||
on-finished@~2.3.0:
|
||||
on-finished@^2.3.0, on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
|
||||
@ -5507,6 +5841,11 @@ onetime@^5.1.0:
|
||||
dependencies:
|
||||
mimic-fn "^2.1.0"
|
||||
|
||||
only@~0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
|
||||
integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=
|
||||
|
||||
optimist@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
|
||||
@ -5746,7 +6085,7 @@ parseuri@0.0.5:
|
||||
dependencies:
|
||||
better-assert "~1.0.0"
|
||||
|
||||
parseurl@~1.3.3:
|
||||
parseurl@^1.3.2, parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
@ -6034,6 +6373,11 @@ qs@6.7.0:
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
||||
qs@^6.5.2:
|
||||
version "6.9.1"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9"
|
||||
integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==
|
||||
|
||||
qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
@ -6089,6 +6433,16 @@ raw-body@2.4.0:
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
raw-body@^2.3.3:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
|
||||
integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==
|
||||
dependencies:
|
||||
bytes "3.1.0"
|
||||
http-errors "1.7.3"
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
read-cmd-shim@^1.0.1:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz#87e43eba50098ba5a32d0ceb583ab8e43b961c16"
|
||||
@ -6443,16 +6797,16 @@ rxjs@^6.4.0, rxjs@^6.5.3:
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
|
||||
|
||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-regex@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
|
||||
@ -6808,7 +7162,7 @@ static-extend@^0.1.1:
|
||||
define-property "^0.2.5"
|
||||
object-copy "^0.1.0"
|
||||
|
||||
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||
"statuses@>= 1.5.0 < 2", statuses@^1.5.0, statuses@~1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||
@ -7251,6 +7605,11 @@ tslint@^5.19.0:
|
||||
tslib "^1.8.0"
|
||||
tsutils "^2.29.0"
|
||||
|
||||
tsscmp@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
|
||||
integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==
|
||||
|
||||
"tsutils@^2.28.0 || ^3.0.0", tsutils@^3.17.1:
|
||||
version "3.17.1"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
|
||||
@ -7299,7 +7658,7 @@ type-fest@^0.8.1:
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||
|
||||
type-is@~1.6.17:
|
||||
type-is@^1.6.16, type-is@~1.6.17:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||
@ -7498,6 +7857,11 @@ validate-npm-package-name@^3.0.0:
|
||||
dependencies:
|
||||
builtins "^1.0.3"
|
||||
|
||||
vary@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
|
||||
|
||||
verror@1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||
@ -7808,3 +8172,8 @@ yeast@0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
|
||||
integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
|
||||
|
||||
ylru@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f"
|
||||
integrity sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==
|
||||
|
Loading…
x
Reference in New Issue
Block a user