diff --git a/.circleci/config.yml b/.circleci/config.yml
index dfaa56267e..78acafff47 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -41,7 +41,6 @@ jobs:
- ./packages/core-container/node_modules
- ./packages/core-database/node_modules
- ./packages/core-database-postgres/node_modules
- - ./packages/core-debugger-cli/node_modules
- ./packages/core-elasticsearch/node_modules
- ./packages/core-error-tracker-bugsnag/node_modules
- ./packages/core-error-tracker-sentry/node_modules
@@ -55,6 +54,7 @@ jobs:
- ./packages/core-logger-pino/node_modules
- ./packages/core-p2p/node_modules
- ./packages/core-snapshots/node_modules
+ - ./packages/core-test-utils/node_modules
- ./packages/core-tester-cli/node_modules
- ./packages/core-transaction-pool/node_modules
- ./packages/core-utils/node_modules
@@ -96,11 +96,11 @@ jobs:
test:coverage /unit/core-database-postgres/ --coverageDirectory
.coverage/unit/core-database-postgres
- run:
- name: core-debugger-cli - unit
+ name: core-event-emitter - unit
command: >-
cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn
- test:coverage /unit/core-debugger-cli/ --coverageDirectory
- .coverage/unit/core-debugger-cli
+ test:coverage /unit/core-event-emitter/ --coverageDirectory
+ .coverage/unit/core-event-emitter
- run:
name: core-api - integration
command: >-
@@ -164,7 +164,6 @@ jobs:
- ./packages/core-container/node_modules
- ./packages/core-database/node_modules
- ./packages/core-database-postgres/node_modules
- - ./packages/core-debugger-cli/node_modules
- ./packages/core-elasticsearch/node_modules
- ./packages/core-error-tracker-bugsnag/node_modules
- ./packages/core-error-tracker-sentry/node_modules
@@ -178,6 +177,7 @@ jobs:
- ./packages/core-logger-pino/node_modules
- ./packages/core-p2p/node_modules
- ./packages/core-snapshots/node_modules
+ - ./packages/core-test-utils/node_modules
- ./packages/core-tester-cli/node_modules
- ./packages/core-transaction-pool/node_modules
- ./packages/core-utils/node_modules
@@ -219,11 +219,11 @@ jobs:
test:coverage /unit/core-database-postgres/ --coverageDirectory
.coverage/unit/core-database-postgres
- run:
- name: core-debugger-cli - unit
+ name: core-event-emitter - unit
command: >-
cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn
- test:coverage /unit/core-debugger-cli/ --coverageDirectory
- .coverage/unit/core-debugger-cli
+ test:coverage /unit/core-event-emitter/ --coverageDirectory
+ .coverage/unit/core-event-emitter
- run:
name: core-api - integration
command: >-
@@ -287,7 +287,6 @@ jobs:
- ./packages/core-container/node_modules
- ./packages/core-database/node_modules
- ./packages/core-database-postgres/node_modules
- - ./packages/core-debugger-cli/node_modules
- ./packages/core-elasticsearch/node_modules
- ./packages/core-error-tracker-bugsnag/node_modules
- ./packages/core-error-tracker-sentry/node_modules
@@ -301,6 +300,7 @@ jobs:
- ./packages/core-logger-pino/node_modules
- ./packages/core-p2p/node_modules
- ./packages/core-snapshots/node_modules
+ - ./packages/core-test-utils/node_modules
- ./packages/core-tester-cli/node_modules
- ./packages/core-transaction-pool/node_modules
- ./packages/core-utils/node_modules
@@ -311,12 +311,6 @@ jobs:
- run:
name: Create .core/database directory
command: mkdir -p $HOME/.core/database
- - run:
- name: core-event-emitter - unit
- command: >-
- cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn
- test:coverage /unit/core-event-emitter/ --coverageDirectory
- .coverage/unit/core-event-emitter
- run:
name: core-forger - unit
command: >-
@@ -422,7 +416,6 @@ jobs:
- ./packages/core-container/node_modules
- ./packages/core-database/node_modules
- ./packages/core-database-postgres/node_modules
- - ./packages/core-debugger-cli/node_modules
- ./packages/core-elasticsearch/node_modules
- ./packages/core-error-tracker-bugsnag/node_modules
- ./packages/core-error-tracker-sentry/node_modules
@@ -436,6 +429,7 @@ jobs:
- ./packages/core-logger-pino/node_modules
- ./packages/core-p2p/node_modules
- ./packages/core-snapshots/node_modules
+ - ./packages/core-test-utils/node_modules
- ./packages/core-tester-cli/node_modules
- ./packages/core-transaction-pool/node_modules
- ./packages/core-utils/node_modules
@@ -539,7 +533,6 @@ jobs:
- ./packages/core-container/node_modules
- ./packages/core-database/node_modules
- ./packages/core-database-postgres/node_modules
- - ./packages/core-debugger-cli/node_modules
- ./packages/core-elasticsearch/node_modules
- ./packages/core-error-tracker-bugsnag/node_modules
- ./packages/core-error-tracker-sentry/node_modules
@@ -553,6 +546,7 @@ jobs:
- ./packages/core-logger-pino/node_modules
- ./packages/core-p2p/node_modules
- ./packages/core-snapshots/node_modules
+ - ./packages/core-test-utils/node_modules
- ./packages/core-tester-cli/node_modules
- ./packages/core-transaction-pool/node_modules
- ./packages/core-utils/node_modules
@@ -563,12 +557,6 @@ jobs:
- run:
name: Create .core/database directory
command: mkdir -p $HOME/.core/database
- - run:
- name: core-event-emitter - unit
- command: >-
- cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn
- test:coverage /unit/core-event-emitter/ --coverageDirectory
- .coverage/unit/core-event-emitter
- run:
name: core-forger - unit
command: >-
@@ -674,7 +662,6 @@ jobs:
- ./packages/core-container/node_modules
- ./packages/core-database/node_modules
- ./packages/core-database-postgres/node_modules
- - ./packages/core-debugger-cli/node_modules
- ./packages/core-elasticsearch/node_modules
- ./packages/core-error-tracker-bugsnag/node_modules
- ./packages/core-error-tracker-sentry/node_modules
@@ -688,6 +675,7 @@ jobs:
- ./packages/core-logger-pino/node_modules
- ./packages/core-p2p/node_modules
- ./packages/core-snapshots/node_modules
+ - ./packages/core-test-utils/node_modules
- ./packages/core-tester-cli/node_modules
- ./packages/core-transaction-pool/node_modules
- ./packages/core-utils/node_modules
diff --git a/__tests__/integration/core-p2p/server/internal.test.ts b/__tests__/integration/core-p2p/server/internal.test.ts
index a40284344d..15f0529674 100644
--- a/__tests__/integration/core-p2p/server/internal.test.ts
+++ b/__tests__/integration/core-p2p/server/internal.test.ts
@@ -1,8 +1,8 @@
-import { generateTransfers } from "../../../utils/generators/transactions/transfer";
import { models, Transaction } from "@arkecosystem/crypto";
-import blockFixture from "../fixtures/block.json";
+import { generateTransfers } from "../../../utils/generators/transactions/transfer";
import { setUp, tearDown } from "../__support__/setup";
import { utils } from "../__support__/utils";
+import blockFixture from "../fixtures/block.json";
const { Block } = models;
diff --git a/__tests__/unit/core-debugger-cli/utils.test.ts b/__tests__/unit/core-debugger-cli/utils.test.ts
deleted file mode 100644
index c853cb3541..0000000000
--- a/__tests__/unit/core-debugger-cli/utils.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { readSync } from "clipboardy";
-import "jest-extended";
-
-import { copyToClipboard, handleOutput } from "../../../packages/core-debugger-cli/src/utils";
-
-const dummyData = { hello: "world" };
-
-describe("Utils", () => {
- describe("copyToClipboard", () => {
- it("should contain the copied data", () => {
- copyToClipboard(dummyData);
-
- expect(JSON.parse(readSync())).toEqual(dummyData);
- });
- });
-
- describe("handleOutput", () => {
- it("should copy the data", () => {
- handleOutput({ copy: true }, dummyData);
-
- expect(JSON.parse(readSync())).toEqual(dummyData);
- });
-
- it("should log the data", () => {
- const method = jest.spyOn(global.console, "log");
-
- handleOutput({ log: true }, dummyData);
-
- expect(method).toHaveBeenCalledWith(dummyData);
- });
-
- it("should return the data", () => {
- expect(handleOutput({}, dummyData)).toEqual(dummyData);
- });
- });
-});
diff --git a/__tests__/unit/core-debugger-cli/__fixtures__/block.json b/__tests__/unit/core-tester-cli/__fixtures__/block.json
similarity index 100%
rename from __tests__/unit/core-debugger-cli/__fixtures__/block.json
rename to __tests__/unit/core-tester-cli/__fixtures__/block.json
diff --git a/__tests__/unit/core-debugger-cli/__fixtures__/identities.json b/__tests__/unit/core-tester-cli/__fixtures__/identities.json
similarity index 100%
rename from __tests__/unit/core-debugger-cli/__fixtures__/identities.json
rename to __tests__/unit/core-tester-cli/__fixtures__/identities.json
diff --git a/__tests__/unit/core-debugger-cli/__fixtures__/transaction-second.json b/__tests__/unit/core-tester-cli/__fixtures__/transaction-second.json
similarity index 85%
rename from __tests__/unit/core-debugger-cli/__fixtures__/transaction-second.json
rename to __tests__/unit/core-tester-cli/__fixtures__/transaction-second.json
index 1f7dc144d0..8a0ebe0907 100644
--- a/__tests__/unit/core-debugger-cli/__fixtures__/transaction-second.json
+++ b/__tests__/unit/core-tester-cli/__fixtures__/transaction-second.json
@@ -8,7 +8,7 @@
"asset": {},
"senderPublicKey": "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192",
"signature": "304402206da703bfcc11ec2ccb3f363fa0e23fc64050fdf68e1f1852b7d4a5bb07824166022031ed1d86b586a79f9c1e5010dbc4f4cb36641c62a196536f90b1dfd6be1c9868",
- "secondSignature": "304402200759b6f9de5257aa3fcf54b9cd7a426a00af9368b7ea3d5ea2b13a91b97fb277022076e4d2d7deb9bdd8245b2533cab1eeeef72981e18576ef8455a61ee3e6f3fb57",
+ "signSignature": "304402200759b6f9de5257aa3fcf54b9cd7a426a00af9368b7ea3d5ea2b13a91b97fb277022076e4d2d7deb9bdd8245b2533cab1eeeef72981e18576ef8455a61ee3e6f3fb57",
"id": "bb8054b6298d659d4b5d655e82de17b3504ba27655ec3d6e35d311f3104b1c43"
},
"serialized": "ff011e00ceb47502034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b00000000000000001e0995750207ecaf0ccf251c1265b92ad84f553662304402206da703bfcc11ec2ccb3f363fa0e23fc64050fdf68e1f1852b7d4a5bb07824166022031ed1d86b586a79f9c1e5010dbc4f4cb36641c62a196536f90b1dfd6be1c9868304402200759b6f9de5257aa3fcf54b9cd7a426a00af9368b7ea3d5ea2b13a91b97fb277022076e4d2d7deb9bdd8245b2533cab1eeeef72981e18576ef8455a61ee3e6f3fb57"
diff --git a/__tests__/unit/core-debugger-cli/__fixtures__/transaction.json b/__tests__/unit/core-tester-cli/__fixtures__/transaction.json
similarity index 100%
rename from __tests__/unit/core-debugger-cli/__fixtures__/transaction.json
rename to __tests__/unit/core-tester-cli/__fixtures__/transaction.json
diff --git a/__tests__/unit/core-debugger-cli/commands/deserialize.test.ts b/__tests__/unit/core-tester-cli/commands/debug/deserialize.test.ts
similarity index 92%
rename from __tests__/unit/core-debugger-cli/commands/deserialize.test.ts
rename to __tests__/unit/core-tester-cli/commands/debug/deserialize.test.ts
index 7254b0d104..3775c3b78c 100644
--- a/__tests__/unit/core-debugger-cli/commands/deserialize.test.ts
+++ b/__tests__/unit/core-tester-cli/commands/debug/deserialize.test.ts
@@ -1,10 +1,10 @@
import "jest-extended";
-import { DeserializeCommand } from "../../../../packages/core-debugger-cli/src/commands/deserialize";
+import { DeserializeCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/deserialize";
describe("Commands - Deserialize", () => {
- const fixtureBlock = require("../__fixtures__/block.json");
- const fixtureTransaction = require("../__fixtures__/transaction.json");
+ const fixtureBlock = require("../../__fixtures__/block.json");
+ const fixtureTransaction = require("../../__fixtures__/transaction.json");
it("should deserialize a block (not-full)", async () => {
const actual = JSON.parse(await DeserializeCommand.run(["--data", fixtureBlock.serialized, "--type", "block"]));
diff --git a/__tests__/unit/core-debugger-cli/commands/identity.test.ts b/__tests__/unit/core-tester-cli/commands/debug/identity.test.ts
similarity index 89%
rename from __tests__/unit/core-debugger-cli/commands/identity.test.ts
rename to __tests__/unit/core-tester-cli/commands/debug/identity.test.ts
index 80082b1741..1362648740 100644
--- a/__tests__/unit/core-debugger-cli/commands/identity.test.ts
+++ b/__tests__/unit/core-tester-cli/commands/debug/identity.test.ts
@@ -1,9 +1,9 @@
import "jest-extended";
-import { IdentityCommand } from "../../../../packages/core-debugger-cli/src/commands/identity";
+import { IdentityCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/identity";
describe("Commands - Identity", async () => {
- const fixtureIdentities = require("../__fixtures__/identities.json");
+ const fixtureIdentities = require("../../__fixtures__/identities.json");
it("should return identities from passphrase", async () => {
const expected = {
diff --git a/__tests__/unit/core-debugger-cli/commands/serialize.test.ts b/__tests__/unit/core-tester-cli/commands/debug/serialize.test.ts
similarity index 76%
rename from __tests__/unit/core-debugger-cli/commands/serialize.test.ts
rename to __tests__/unit/core-tester-cli/commands/debug/serialize.test.ts
index e0da4b4b9f..6e26821bb8 100644
--- a/__tests__/unit/core-debugger-cli/commands/serialize.test.ts
+++ b/__tests__/unit/core-tester-cli/commands/debug/serialize.test.ts
@@ -1,10 +1,10 @@
import "jest-extended";
-import { SerializeCommand } from "../../../../packages/core-debugger-cli/src/commands/serialize";
+import { SerializeCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/serialize";
describe("Commands - Serialize", () => {
- const fixtureBlock = require("../__fixtures__/block.json");
- const fixtureTransaction = require("../__fixtures__/transaction.json");
+ const fixtureBlock = require("../../__fixtures__/block.json");
+ const fixtureTransaction = require("../../__fixtures__/transaction.json");
it("should serialize a block (not-full)", async () => {
expect(await SerializeCommand.run(["--data", JSON.stringify(fixtureBlock.data), "--type", "block"])).toEqual(
diff --git a/__tests__/unit/core-debugger-cli/commands/verify-second.test.ts b/__tests__/unit/core-tester-cli/commands/debug/verify-second.test.ts
similarity index 66%
rename from __tests__/unit/core-debugger-cli/commands/verify-second.test.ts
rename to __tests__/unit/core-tester-cli/commands/debug/verify-second.test.ts
index 2873836546..40b8236663 100644
--- a/__tests__/unit/core-debugger-cli/commands/verify-second.test.ts
+++ b/__tests__/unit/core-tester-cli/commands/debug/verify-second.test.ts
@@ -1,9 +1,9 @@
import "jest-extended";
-import { VerifySecondSignatureCommand } from "../../../../packages/core-debugger-cli/src/commands/verify-second";
+import { VerifySecondSignatureCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/verify-second-signature";
describe("Commands - Verify Second", () => {
- const fixtureTransaction = require("../__fixtures__/transaction-second.json");
+ const fixtureTransaction = require("../../__fixtures__/transaction-second.json");
it("should verify a second signature", async () => {
expect(
diff --git a/__tests__/unit/core-debugger-cli/commands/verify.test.ts b/__tests__/unit/core-tester-cli/commands/debug/verify.test.ts
similarity index 62%
rename from __tests__/unit/core-debugger-cli/commands/verify.test.ts
rename to __tests__/unit/core-tester-cli/commands/debug/verify.test.ts
index 97bf79d731..8425ac3ebb 100644
--- a/__tests__/unit/core-debugger-cli/commands/verify.test.ts
+++ b/__tests__/unit/core-tester-cli/commands/debug/verify.test.ts
@@ -1,10 +1,10 @@
import "jest-extended";
-import { VerifyCommand } from "../../../../packages/core-debugger-cli/src/commands/verify";
+import { VerifyCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/verify";
describe("Commands - Verify", () => {
- const fixtureBlock = require("../__fixtures__/block.json");
- const fixtureTransaction = require("../__fixtures__/transaction.json");
+ const fixtureBlock = require("../../__fixtures__/block.json");
+ const fixtureTransaction = require("../../__fixtures__/transaction.json");
it("should verify a block", async () => {
expect(await VerifyCommand.run(["--data", fixtureBlock.serializedFull, "--type", "block"])).toBeTrue();
diff --git a/__tests__/unit/core-tester-cli/commands/delegate-registration.test.ts b/__tests__/unit/core-tester-cli/commands/delegate-registration.test.ts
deleted file mode 100644
index 37e5292822..0000000000
--- a/__tests__/unit/core-tester-cli/commands/delegate-registration.test.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import "jest-extended";
-
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import superheroes from "superheroes";
-import { DelegateRegistrationCommand } from "../../../../packages/core-tester-cli/src/commands/delegate-registration";
-import { arkToSatoshi } from "../../../../packages/core-tester-cli/src/utils";
-import { toFlags } from "../shared";
-
-const mockAxios = new MockAdapter(axios);
-
-beforeEach(() => {
- // Just passthru. We'll test the Command class logic in its own test file more thoroughly
- mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } });
- mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } });
- jest.spyOn(axios, "get");
- jest.spyOn(axios, "post");
-});
-
-afterEach(() => {
- mockAxios.reset();
-});
-
-describe("Commands - Delegate Registration", () => {
- it("should register as delegate", async () => {
- const opts = {
- delegateFee: 1,
- number: 1,
- };
-
- const expectedDelegateName = "mr_bojangles";
- // call to delegates/{publicKey}/voters returns zero delegates
- mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates/).reply(200, {
- meta: { pageCount: 1 },
- data: [],
- });
- jest.spyOn(superheroes, "random").mockImplementation(() => expectedDelegateName);
-
- mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} });
-
- const flags = toFlags(opts);
- await DelegateRegistrationCommand.run(flags);
-
- expect(axios.post).toHaveBeenCalledWith(
- "http://localhost:4003/api/v2/transactions",
- {
- transactions: [
- expect.objectContaining({
- fee: arkToSatoshi(opts.delegateFee),
- asset: {
- delegate: {
- username: expectedDelegateName,
- },
- },
- }),
- ],
- },
- expect.any(Object),
- );
- });
-});
diff --git a/__tests__/unit/core-tester-cli/commands/second-signature.test.ts b/__tests__/unit/core-tester-cli/commands/second-signature.test.ts
deleted file mode 100644
index 996b68ea6f..0000000000
--- a/__tests__/unit/core-tester-cli/commands/second-signature.test.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import "jest-extended";
-
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { SecondSignatureCommand } from "../../../../packages/core-tester-cli/src/commands/second-signature";
-import { arkToSatoshi } from "../../../../packages/core-tester-cli/src/utils";
-import { toFlags } from "../shared";
-
-const mockAxios = new MockAdapter(axios);
-
-beforeEach(() => {
- // Just passthru. We'll test the Command class logic in its own test file more thoroughly
- mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } });
- mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } });
- jest.spyOn(axios, "get");
- jest.spyOn(axios, "post");
-});
-
-afterEach(() => {
- mockAxios.reset();
-});
-
-describe("Commands - Second signature", () => {
- it("should apply second signature", async () => {
- const opts = {
- signatureFee: 1,
- number: 1,
- };
-
- mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} });
-
- const flags = toFlags(opts);
- await SecondSignatureCommand.run(flags);
-
- expect(axios.post).toHaveBeenCalledWith(
- "http://localhost:4003/api/v2/transactions",
- {
- transactions: [
- expect.objectContaining({
- fee: arkToSatoshi(opts.signatureFee),
- asset: {
- signature: {
- publicKey: expect.any(String),
- },
- },
- }),
- ],
- },
- expect.any(Object),
- );
- });
-});
diff --git a/__tests__/unit/core-tester-cli/commands/send/delegate-registration.test.ts b/__tests__/unit/core-tester-cli/commands/send/delegate-registration.test.ts
new file mode 100644
index 0000000000..23145778c2
--- /dev/null
+++ b/__tests__/unit/core-tester-cli/commands/send/delegate-registration.test.ts
@@ -0,0 +1,54 @@
+import "jest-extended";
+
+import axios from "axios";
+import MockAdapter from "axios-mock-adapter";
+import pokemon from "pokemon";
+import { DelegateRegistrationCommand } from "../../../../../packages/core-tester-cli/src/commands/send/delegate-registration";
+import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared";
+
+const mockAxios = new MockAdapter(axios);
+
+beforeEach(() => {
+ // Just passthru. We'll test the Command class logic in its own test file more thoroughly
+ mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } });
+ mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } });
+ jest.spyOn(axios, "get");
+ jest.spyOn(axios, "post");
+});
+
+afterEach(() => {
+ mockAxios.reset();
+});
+
+describe("Commands - Delegate Registration", () => {
+ it("should register as delegate", async () => {
+ const opts = {
+ delegateFee: 1,
+ number: 1,
+ };
+
+ const expectedDelegateName = "mr_bojangles";
+ // call to delegates/{publicKey}/voters returns zero delegates
+ mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates/).reply(200, {
+ meta: { pageCount: 1 },
+ data: [],
+ });
+ jest.spyOn(pokemon, "random").mockImplementation(() => expectedDelegateName);
+
+ const expectedTransactions = [];
+ captureTransactions(mockAxios, expectedTransactions);
+
+ await DelegateRegistrationCommand.run(toFlags(opts));
+
+ expect(axios.post).toHaveBeenCalledTimes(2);
+
+ expectTransactions(expectedTransactions, {
+ fee: arkToSatoshi(opts.delegateFee),
+ asset: {
+ delegate: {
+ username: expectedDelegateName,
+ },
+ },
+ });
+ });
+});
diff --git a/__tests__/unit/core-tester-cli/commands/send/second-signature.test.ts b/__tests__/unit/core-tester-cli/commands/send/second-signature.test.ts
new file mode 100644
index 0000000000..f0979127b7
--- /dev/null
+++ b/__tests__/unit/core-tester-cli/commands/send/second-signature.test.ts
@@ -0,0 +1,45 @@
+import "jest-extended";
+
+import axios from "axios";
+import MockAdapter from "axios-mock-adapter";
+import { SecondSignatureRegistrationCommand } from "../../../../../packages/core-tester-cli/src/commands/send/second-signature-registration";
+import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared";
+
+const mockAxios = new MockAdapter(axios);
+
+beforeEach(() => {
+ // Just passthru. We'll test the Command class logic in its own test file more thoroughly
+ mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } });
+ mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } });
+ jest.spyOn(axios, "get");
+ jest.spyOn(axios, "post");
+});
+
+afterEach(() => {
+ mockAxios.reset();
+});
+
+describe("Commands - Second signature", () => {
+ it("should apply second signature", async () => {
+ const opts = {
+ signatureFee: 1,
+ number: 1,
+ };
+
+ const expectedTransactions = [];
+ captureTransactions(mockAxios, expectedTransactions);
+
+ await SecondSignatureRegistrationCommand.run(toFlags(opts));
+
+ expect(axios.post).toHaveBeenCalledTimes(2);
+
+ expectTransactions(expectedTransactions, {
+ fee: arkToSatoshi(opts.signatureFee),
+ asset: {
+ signature: {
+ publicKey: expect.any(String),
+ },
+ },
+ });
+ });
+});
diff --git a/__tests__/unit/core-tester-cli/commands/transfer.test.ts b/__tests__/unit/core-tester-cli/commands/send/transfer.test.ts
similarity index 55%
rename from __tests__/unit/core-tester-cli/commands/transfer.test.ts
rename to __tests__/unit/core-tester-cli/commands/send/transfer.test.ts
index 2f0f048f5e..aa9170fb3a 100644
--- a/__tests__/unit/core-tester-cli/commands/transfer.test.ts
+++ b/__tests__/unit/core-tester-cli/commands/send/transfer.test.ts
@@ -2,9 +2,8 @@ import "jest-extended";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
-import { TransferCommand } from "../../../../packages/core-tester-cli/src/commands/transfer";
-import { arkToSatoshi } from "../../../../packages/core-tester-cli/src/utils";
-import { toFlags } from "../shared";
+import { TransferCommand } from "../../../../../packages/core-tester-cli/src/commands/send/transfer";
+import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared";
const mockAxios = new MockAdapter(axios);
@@ -31,30 +30,21 @@ describe("Commands - Transfer", () => {
amount: expectedTransactionAmount,
transferFee: expectedFee,
number: 1,
- smartBridge: "foo bar",
+ vendorField: "foo bar",
recipient: expectedRecipientId,
};
- mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} });
- let expectedTransactions = [];
- // @ts-ignore
- jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => {
- expectedTransactions = transactions;
- });
+ const expectedTransactions = [];
+ captureTransactions(mockAxios, expectedTransactions);
+
+ await TransferCommand.run(toFlags(opts));
- const flags = toFlags(opts);
- await TransferCommand.run(flags);
-
- expect(expectedTransactions).toEqual(
- expect.arrayContaining([
- expect.objectContaining({
- vendorField: "foo bar",
- amount: arkToSatoshi(expectedTransactionAmount),
- fee: arkToSatoshi(expectedFee),
- recipientId: expectedRecipientId,
- }),
- ]),
- );
+ expectTransactions(expectedTransactions, {
+ vendorField: "foo bar",
+ amount: arkToSatoshi(expectedTransactionAmount),
+ fee: arkToSatoshi(expectedFee),
+ recipientId: expectedRecipientId,
+ });
});
it("should generate n transactions", async () => {
@@ -67,15 +57,10 @@ describe("Commands - Transfer", () => {
recipient: expectedRecipientId,
};
- mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} });
- let expectedTransactions = [];
- // @ts-ignore
- jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => {
- expectedTransactions = transactions;
- });
+ const expectedTransactions = [];
+ captureTransactions(mockAxios, expectedTransactions);
- const flags = toFlags(opts);
- await TransferCommand.run(flags);
+ await TransferCommand.run(toFlags(opts));
expect(expectedTransactions).toHaveLength(expectedTxCount);
for (const t of expectedTransactions) {
@@ -95,15 +80,10 @@ describe("Commands - Transfer", () => {
recipient: expectedRecipientId,
};
- mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} });
- let expectedTransactions = [];
- // @ts-ignore
- jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => {
- expectedTransactions = transactions;
- });
+ const expectedTransactions = [];
+ captureTransactions(mockAxios, expectedTransactions);
- const flags = toFlags(opts);
- await TransferCommand.run(flags);
+ await TransferCommand.run(toFlags(opts));
expect(expectedTransactions).toHaveLength(expectedTxCount);
for (const t of expectedTransactions) {
@@ -124,19 +104,14 @@ describe("Commands - Transfer", () => {
recipient: expectedRecipientId,
};
- mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} });
- let expectedTransactions = [];
- // @ts-ignore
- jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => {
- expectedTransactions = transactions;
- });
+ const expectedTransactions = [];
+ captureTransactions(mockAxios, expectedTransactions);
- const flags = toFlags(opts);
- await TransferCommand.run(flags);
+ await TransferCommand.run(toFlags(opts));
expect(expectedTransactions).toHaveLength(1);
- for (const t of expectedTransactions) {
- expect(t.signSignature).toBeDefined();
+ for (const transaction of expectedTransactions) {
+ expect(transaction.secondSignature).toBeDefined();
}
});
});
diff --git a/__tests__/unit/core-tester-cli/commands/send/vote.test.ts b/__tests__/unit/core-tester-cli/commands/send/vote.test.ts
new file mode 100644
index 0000000000..19b1b8d678
--- /dev/null
+++ b/__tests__/unit/core-tester-cli/commands/send/vote.test.ts
@@ -0,0 +1,75 @@
+import "jest-extended";
+
+import axios from "axios";
+import MockAdapter from "axios-mock-adapter";
+import { VoteCommand } from "../../../../../packages/core-tester-cli/src/commands/send/vote";
+import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared";
+
+const mockAxios = new MockAdapter(axios);
+
+beforeEach(() => {
+ // Just passthru. We'll test the Command class logic in its own test file more thoroughly
+ mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } });
+ mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } });
+ jest.spyOn(axios, "get");
+ jest.spyOn(axios, "post");
+});
+
+afterEach(() => {
+ mockAxios.reset();
+ jest.restoreAllMocks();
+});
+
+afterAll(() => mockAxios.restore());
+
+describe("Commands - Vote", () => {
+ it("should vote for specified delegate", async () => {
+ const expectedDelegate = "03f294777f7376e970b2bd4805b4a90c8449b5935d530bdb566d02800ac44a4c00";
+ const opts = {
+ number: 1,
+ voteFee: 1,
+ delegate: expectedDelegate,
+ };
+
+ const expectedTransactions = [];
+ captureTransactions(mockAxios, expectedTransactions);
+
+ await VoteCommand.run(toFlags(opts));
+
+ expect(axios.post).toHaveBeenCalledTimes(2);
+
+ expectTransactions(expectedTransactions, {
+ fee: arkToSatoshi(opts.voteFee),
+ asset: {
+ votes: [`+${expectedDelegate}`],
+ },
+ });
+ });
+
+ it("should vote random delegate if non specified", async () => {
+ const expectedDelegate = "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37";
+ const opts = {
+ number: 1,
+ voteFee: 1,
+ };
+
+ mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates/).reply(200, {
+ meta: { pageCount: 1 },
+ data: [{ publicKey: expectedDelegate }],
+ });
+
+ const expectedTransactions = [];
+ captureTransactions(mockAxios, expectedTransactions);
+
+ await VoteCommand.run(toFlags(opts));
+
+ expect(axios.post).toHaveBeenCalledTimes(2);
+
+ expectTransactions(expectedTransactions, {
+ fee: arkToSatoshi(opts.voteFee),
+ asset: {
+ votes: [`+${expectedDelegate}`],
+ },
+ });
+ });
+});
diff --git a/__tests__/unit/core-tester-cli/commands/vote.test.ts b/__tests__/unit/core-tester-cli/commands/vote.test.ts
deleted file mode 100644
index 1cf7c63a11..0000000000
--- a/__tests__/unit/core-tester-cli/commands/vote.test.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import "jest-extended";
-
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { VoteCommand } from "../../../../packages/core-tester-cli/src/commands/vote";
-import { arkToSatoshi } from "../../../../packages/core-tester-cli/src/utils";
-import { toFlags } from "../shared";
-
-const mockAxios = new MockAdapter(axios);
-
-beforeEach(() => {
- // Just passthru. We'll test the Command class logic in its own test file more thoroughly
- mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } });
- mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } });
- jest.spyOn(axios, "get");
- jest.spyOn(axios, "post");
-});
-
-afterEach(() => {
- mockAxios.reset();
-});
-
-afterAll(() => mockAxios.restore());
-
-describe("Commands - Vote", () => {
- it("should vote for specified delegate", async () => {
- const expectedDelegate = "03f294777f7376e970b2bd4805b4a90c8449b5935d530bdb566d02800ac44a4c00";
- const opts = {
- number: 1,
- voteFee: 1,
- delegate: expectedDelegate,
- };
-
- mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates.*/).reply(200);
- mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} });
-
- const flags = toFlags(opts);
- await VoteCommand.run(flags);
-
- expect(axios.post).toHaveBeenCalledWith(
- "http://localhost:4003/api/v2/transactions",
- {
- transactions: [
- expect.objectContaining({
- fee: arkToSatoshi(opts.voteFee),
- asset: {
- votes: [`+${expectedDelegate}`],
- },
- }),
- ],
- },
- expect.any(Object),
- );
- });
-
- it("should vote random delegate if non specified", async () => {
- const expectedDelegate = "03f294777f7376e970b2bd4805b4a90c8449b5935d530bdb566d02800ac44a4c00";
- const opts = {
- number: 1,
- voteFee: 1,
- };
-
- mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} });
- mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates\/.*/).reply(200); // call to delegates/{publicKey}/voters
- // call to /delegates
- mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates/).reply(200, {
- meta: { pageCount: 1 },
- data: [{ publicKey: expectedDelegate }],
- });
-
- const flags = toFlags(opts);
- await VoteCommand.run(flags);
-
- expect(axios.post).toHaveBeenCalledWith(
- "http://localhost:4003/api/v2/transactions",
- {
- transactions: [
- expect.objectContaining({
- fee: arkToSatoshi(opts.voteFee),
- asset: {
- votes: [`+${expectedDelegate}`],
- },
- }),
- ],
- },
- expect.any(Object),
- );
- });
-});
diff --git a/__tests__/unit/core-tester-cli/shared.ts b/__tests__/unit/core-tester-cli/shared.ts
index 1562862cb6..a9a6e26131 100644
--- a/__tests__/unit/core-tester-cli/shared.ts
+++ b/__tests__/unit/core-tester-cli/shared.ts
@@ -1,7 +1,29 @@
-const defaultOpts = ["--skipTesting", "--skipValidation"];
+import { bignumify } from "@arkecosystem/core-utils";
+import axios from "axios";
+
+const defaultOpts = ["--skipProbing"];
export const toFlags = (opts: object): string[] => {
return Object.keys(opts)
.map(k => [`--${k}`, String(opts[k])])
.reduce((a, b) => a.concat(b), defaultOpts);
};
+
+export const arkToSatoshi = value =>
+ bignumify(value)
+ .times(1e8)
+ .toFixed();
+
+export const expectTransactions = (transactions, obj) =>
+ expect(transactions).toEqual(expect.arrayContaining([expect.objectContaining(obj)]));
+
+export const captureTransactions = (mockAxios, expectedTransactions) => {
+ mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} });
+
+ // @ts-ignore
+ jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => {
+ for (const transaction of transactions) {
+ expectedTransactions.push(transaction);
+ }
+ });
+};
diff --git a/__tests__/unit/core-tester-cli/utils.test.ts b/__tests__/unit/core-tester-cli/utils.test.ts
deleted file mode 100644
index f9318aa310..0000000000
--- a/__tests__/unit/core-tester-cli/utils.test.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { arkToSatoshi, parseFee, satoshiToArk } from "../../../packages/core-tester-cli/src/utils";
-
-describe("Utils", () => {
- describe("parseFee", () => {
- it("should give satoshi", () => {
- expect(parseFee(0.1).toString()).toBe("10000000");
- expect(parseFee(1).toString()).toBe("100000000");
- expect(parseFee(10).toString()).toBe("1000000000");
- expect(parseFee("0.1").toString()).toBe("10000000");
- expect(parseFee("1").toString()).toBe("100000000");
- expect(parseFee("10").toString()).toBe("1000000000");
- expect(parseFee("0.001-0.005").toNumber()).toBeWithin(100000, 500000);
- });
- });
-
- describe("arkToSatoshi", () => {
- it("should give satoshi", () => {
- expect(arkToSatoshi(0.00000001).toString()).toBe("1");
- expect(arkToSatoshi(0.1).toString()).toBe("10000000");
- expect(arkToSatoshi(1).toString()).toBe("100000000");
- expect(arkToSatoshi(10).toString()).toBe("1000000000");
- });
- });
-
- describe("satoshiToArk", () => {
- it("should give ark", () => {
- expect(satoshiToArk(1)).toBe("0.00000001 DѦ");
- expect(satoshiToArk(10000000)).toBe("0.1 DѦ");
- expect(satoshiToArk(100000000)).toBe("1 DѦ");
- expect(satoshiToArk(1000000000)).toBe("10 DѦ");
- });
- });
-});
diff --git a/packages/core-api/src/versions/2/wallets/methods.ts b/packages/core-api/src/versions/2/wallets/methods.ts
index c354ff87f5..d9ccaf3904 100644
--- a/packages/core-api/src/versions/2/wallets/methods.ts
+++ b/packages/core-api/src/versions/2/wallets/methods.ts
@@ -122,7 +122,7 @@ export function registerMethods(server) {
...paginate(request),
}))
.method("v2.wallets.top", top, 30, request => paginate(request))
- .method("v2.wallets.show", show, 30, request => ({ id: request.params.id }))
+ .method("v2.wallets.show", show, 8, request => ({ id: request.params.id }))
.method("v2.wallets.transactions", transactions, 30, request => ({
...{ id: request.params.id },
...request.query,
diff --git a/packages/core-debugger-cli/.gitattributes b/packages/core-debugger-cli/.gitattributes
deleted file mode 100644
index 60cc52db63..0000000000
--- a/packages/core-debugger-cli/.gitattributes
+++ /dev/null
@@ -1,11 +0,0 @@
-# Path-based git attributes
-# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
-
-# Ignore all test and documentation with "export-ignore".
-/.editorconfig export-ignore
-/.gitattributes export-ignore
-/.gitignore export-ignore
-/.travis.yml export-ignore
-/__tests__ export-ignore
-/docs export-ignore
-/README.md export-ignore
diff --git a/packages/core-debugger-cli/.gitignore b/packages/core-debugger-cli/.gitignore
deleted file mode 100644
index 5dc9198e84..0000000000
--- a/packages/core-debugger-cli/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-test-wallets
\ No newline at end of file
diff --git a/packages/core-debugger-cli/README.md b/packages/core-debugger-cli/README.md
deleted file mode 100644
index bc6530e60b..0000000000
--- a/packages/core-debugger-cli/README.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Ark Core - Debugger CLI
-
-
-
-
-
-## Documentation
-
-You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-debugger-cli.html).
-
-## Security
-
-If you discover a security vulnerability within this package, please send an e-mail to security@ark.io. All security vulnerabilities will be promptly addressed.
-
-## Credits
-
-- [Brian Faust](https://github.com/faustbrian)
-- [Joshua Noack](https://github.com/supaiku0)
-- [All Contributors](../../../../contributors)
-
-## License
-
-[MIT](LICENSE) © [ArkEcosystem](https://ark.io)
diff --git a/packages/core-debugger-cli/bin/run b/packages/core-debugger-cli/bin/run
deleted file mode 100755
index 30b14e1773..0000000000
--- a/packages/core-debugger-cli/bin/run
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env node
-
-require('@oclif/command').run()
-.then(require('@oclif/command/flush'))
-.catch(require('@oclif/errors/handle'))
diff --git a/packages/core-debugger-cli/bin/run.cmd b/packages/core-debugger-cli/bin/run.cmd
deleted file mode 100644
index 968fc30758..0000000000
--- a/packages/core-debugger-cli/bin/run.cmd
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-
-node "%~dp0\run" %*
diff --git a/packages/core-debugger-cli/package.json b/packages/core-debugger-cli/package.json
deleted file mode 100644
index d88bf9e8ae..0000000000
--- a/packages/core-debugger-cli/package.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
- "name": "@arkecosystem/core-debugger-cli",
- "description": "Debugger CLI for Ark Core",
- "version": "2.2.0-beta.7",
- "contributors": [
- "Brian Faust "
- ],
- "license": "MIT",
- "main": "dist/index.js",
- "files": [
- "/bin",
- "/dist",
- "/oclif.manifest.json"
- ],
- "bin": {
- "debugger": "./bin/run"
- },
- "scripts": {
- "debugger": "./bin/run",
- "publish:alpha": "npm publish --tag alpha",
- "publish:beta": "npm publish --tag beta",
- "publish:rc": "npm publish --tag rc",
- "publish:stable": "npm publish --tag latest",
- "prepublishOnly": "yarn build",
- "pretest": "yarn lint && yarn build",
- "prepack": "oclif-dev manifest && npm shrinkwrap",
- "postpack": "rm -f oclif.manifest.json",
- "compile": "../../node_modules/typescript/bin/tsc",
- "build": "yarn clean && yarn compile",
- "build:watch": "yarn clean && yarn compile -w",
- "clean": "del dist",
- "docs": "../../node_modules/typedoc/bin/typedoc src --out docs",
- "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix",
- "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a"
- },
- "dependencies": {
- "@arkecosystem/crypto": "^2.2.0-beta.7",
- "@oclif/command": "^1.5.10",
- "@oclif/config": "^1.12.6",
- "@oclif/plugin-help": "^2.1.6",
- "@oclif/plugin-not-found": "^1.2.2",
- "@types/clipboardy": "^1.1.0",
- "clipboardy": "^1.2.3"
- },
- "publishConfig": {
- "access": "public"
- },
- "engines": {
- "node": ">=10.x"
- },
- "oclif": {
- "commands": "./dist/commands",
- "bin": "debugger",
- "plugins": [
- "@oclif/plugin-help",
- "@oclif/plugin-not-found"
- ]
- }
-}
diff --git a/packages/core-debugger-cli/src/commands/command.ts b/packages/core-debugger-cli/src/commands/command.ts
deleted file mode 100644
index 4ab327459a..0000000000
--- a/packages/core-debugger-cli/src/commands/command.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import Command, { flags } from "@oclif/command";
-
-export abstract class BaseCommand extends Command {
- public static flags = {
- log: flags.string({
- description: "log the data to the console",
- }),
- copy: flags.string({
- description: "copy the data to the clipboard",
- }),
- };
-}
diff --git a/packages/core-debugger-cli/src/index.ts b/packages/core-debugger-cli/src/index.ts
deleted file mode 100644
index 8bdb76f9a0..0000000000
--- a/packages/core-debugger-cli/src/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { run } from "@oclif/command";
diff --git a/packages/core-debugger-cli/src/utils.ts b/packages/core-debugger-cli/src/utils.ts
deleted file mode 100644
index 2fbd8ef2ac..0000000000
--- a/packages/core-debugger-cli/src/utils.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import clipboardy from "clipboardy";
-
-export function copyToClipboard(data) {
- clipboardy.writeSync(JSON.stringify(data));
-}
-
-export function handleOutput(opts, data) {
- if (opts.copy) {
- return copyToClipboard(data);
- }
-
- if (opts.log) {
- // tslint:disable-next-line:no-console
- return console.log(data);
- }
-
- return data;
-}
diff --git a/packages/core-debugger-cli/tsconfig.json b/packages/core-debugger-cli/tsconfig.json
deleted file mode 100644
index 0b089c5fa8..0000000000
--- a/packages/core-debugger-cli/tsconfig.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "dist"
- },
- "include": ["src/**/**.ts"]
-}
diff --git a/packages/core-tester-cli/package.json b/packages/core-tester-cli/package.json
index f7e306b34c..be1f0b7e3c 100644
--- a/packages/core-tester-cli/package.json
+++ b/packages/core-tester-cli/package.json
@@ -42,21 +42,19 @@
"@oclif/plugin-help": "^2.1.6",
"@oclif/plugin-not-found": "^1.2.2",
"@types/bip39": "^2.4.1",
- "@types/clipboardy": "^1.1.0",
- "@types/lodash.fill": "^3.4.4",
"@types/pino": "^5.8.4",
"@types/pluralize": "^0.0.29",
"axios": "^0.18.0",
"bip39": "^2.5.0",
"clipboardy": "^1.2.3",
"delay": "^4.1.0",
- "lodash.fill": "^3.4.0",
"pino": "^5.11.1",
"pino-pretty": "^2.5.0",
"pluralize": "^7.0.0",
- "superheroes": "^2.0.0"
+ "pokemon": "^1.2.3"
},
"devDependencies": {
+ "@types/clipboardy": "^1.1.0",
"axios-mock-adapter": "^1.16.0"
},
"publishConfig": {
@@ -67,7 +65,18 @@
},
"oclif": {
"commands": "./dist/commands",
- "bin": "snapshot",
+ "bin": "ark-tester",
+ "topics": {
+ "debug": {
+ "description": "debug blocks and transactions"
+ },
+ "send": {
+ "description": "send transactions of various types"
+ },
+ "make": {
+ "description": "make new identities"
+ }
+ },
"plugins": [
"@oclif/plugin-help",
"@oclif/plugin-not-found"
diff --git a/packages/core-tester-cli/src/commands/command.ts b/packages/core-tester-cli/src/commands/command.ts
index 84b04f525c..6208f857c3 100644
--- a/packages/core-tester-cli/src/commands/command.ts
+++ b/packages/core-tester-cli/src/commands/command.ts
@@ -1,337 +1,205 @@
import { bignumify } from "@arkecosystem/core-utils";
-import { Bignum, crypto, Transaction } from "@arkecosystem/crypto";
+import { Address, Bignum, formatSatoshi } from "@arkecosystem/crypto";
import Command, { flags } from "@oclif/command";
-import bip39 from "bip39";
-import clipboardy from "clipboardy";
import delay from "delay";
-import fs from "fs";
-import path from "path";
-import pluralize from "pluralize";
-import { config } from "../config";
-import { customFlags } from "../flags";
-import { logger, paginate, request } from "../utils";
+import { satoshiFlag } from "../flags";
+import { HttpClient } from "../http-client";
+import { logger } from "../logger";
+import { Signer } from "../signer";
export abstract class BaseCommand extends Command {
- public static flags = {
- number: flags.integer({
- description: "number of wallets",
- default: 10,
- }),
- amount: customFlags.number({
- description: "initial wallet token amount",
- default: 2,
- }),
- transferFee: customFlags.number({
- description: "transfer fee",
- default: 0.1,
- }),
- baseUrl: flags.string({
- description: "base api url",
+ public static flagsConfig = {
+ host: flags.string({
+ description: "API host",
+ default: "http://localhost",
}),
- apiPort: flags.integer({
- description: "base api port",
+ portAPI: flags.integer({
+ description: "API port",
default: 4003,
}),
- p2pPort: flags.integer({
- description: "base p2p port",
- default: 4002,
+ portP2P: flags.integer({
+ description: "P2P port",
+ default: 4000,
}),
+ };
+
+ public static flagsSend = {
+ ...BaseCommand.flagsConfig,
passphrase: flags.string({
description: "passphrase of initial wallet",
+ default: "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire",
}),
secondPassphrase: flags.string({
description: "second passphrase of initial wallet",
}),
- skipValidation: flags.boolean({
- description: "skip transaction validations",
+ number: flags.integer({
+ description: "number of wallets",
+ default: 1,
+ }),
+ amount: satoshiFlag({
+ description: "initial wallet token amount",
+ default: 2,
}),
- skipTesting: flags.boolean({
- description: "skip testing",
+ transferFee: satoshiFlag({
+ description: "transfer fee",
+ default: 0.1,
}),
- copy: flags.boolean({
- description: "copy the transactions to the clipboard",
+ skipProbing: flags.boolean({
+ description: "skip transaction probing",
}),
};
- public options: any;
- public config: any;
+ public static flagsDebug = {
+ log: flags.string({
+ description: "log the data to the console",
+ }),
+ copy: flags.string({
+ description: "copy the data to the clipboard",
+ }),
+ };
- /**
- * Init new instance of command.
- * @param {Object} options
- * @return {*}
- */
- public async initialize(command): Promise {
- // tslint:disable-next-line:no-shadowed-variable
- const { flags } = this.parse(command);
+ protected api: HttpClient;
+ protected p2p: HttpClient;
+ protected signer: Signer;
+ protected network: Record;
+ protected constants: Record;
- this.options = flags;
- this.applyConfig();
- await this.loadConstants();
- await this.loadNetworkConfig();
+ protected async make(command): Promise {
+ const { args, flags } = this.parse(command);
- return { flags };
- }
+ this.api = new HttpClient(`${flags.host}:${flags.portAPI}/api/v2/`);
+ this.p2p = new HttpClient(`${flags.host}:${flags.portP2P}/`);
- /**
- * Copy transactions to clipboard.
- * @param {Object[]} transactions
- * @return {void}
- */
- public copyToClipboard(transactions) {
- for (const transaction of transactions) {
- transaction.serialized = transaction.serialized.toString("hex");
- }
+ await this.setupConstants();
+ await this.setupNetwork();
- clipboardy.writeSync(JSON.stringify(transactions));
- logger.info(`Copied ${pluralize("transaction", transactions.length, true)}`);
+ this.signer = new Signer(this.network);
+
+ return { args, flags };
}
- /**
- * Generate wallets based on quantity.
- * @param {Number} [quantity]
- * @return {Object[]}
- */
- public generateWallets(quantity: any = null) {
- if (!quantity) {
- quantity = this.options.number;
+ protected async sendTransaction(transactions: any[]): Promise> {
+ if (!Array.isArray(transactions)) {
+ transactions = [transactions];
}
- const wallets = [];
- for (let i = 0; i < quantity; i++) {
- const passphrase = bip39.generateMnemonic();
- const keys = crypto.getKeys(passphrase);
- const address = crypto.getAddress(keys.publicKey, this.config.network.version);
+ for (const transaction of transactions) {
+ let recipientId = transaction.recipientId;
- wallets.push({ address, keys, passphrase });
- }
+ if (!recipientId) {
+ recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version);
+ }
- const testWalletsPath = path.resolve(__dirname, "../../test-wallets");
- fs.appendFileSync(testWalletsPath, `${new Date().toLocaleDateString()} ${"-".repeat(70)}\n`);
- for (const wallet of wallets) {
- fs.appendFileSync(testWalletsPath, `${wallet.address}: ${wallet.passphrase}\n`);
+ logger.info(
+ `[T] ${transaction.id} (${recipientId} / ${this.fromSatoshi(transaction.amount)} / ${this.fromSatoshi(
+ transaction.fee,
+ )})`,
+ );
}
- return wallets;
+ return this.api.post("transactions", { transactions });
}
- /**
- * Get delegate API response.
- * @return {Object[]}
- * @throws 'Could not get delegates'
- */
- public async getDelegates() {
+ protected async knockTransaction(id: string): Promise {
try {
- const delegates = await paginate(this.config, "/api/v2/delegates");
+ const { data } = await this.api.get(`transactions/${id}`);
+
+ logger.info(`[T] ${id} (${data.blockId})`);
- return delegates;
+ return true;
} catch (error) {
- const message = error.response ? error.response.data.message : error.message;
- throw new Error(`Could not get delegates: ${message}`);
+ logger.error(error.message);
+
+ logger.error(`[T] ${id} (not forged)`);
+
+ return false;
}
}
- /**
- * Get transaction from API by ID.
- * @param {String} id
- * @return {(Object|null)}
- */
- public async getTransaction(id) {
- try {
- const response = await request(this.config).get(`/api/v2/transactions/${id}`);
+ protected async knockBalance(address: string, expected: Bignum): Promise {
+ const actual = await this.getWalletBalance(address);
- if (response.data) {
- return response.data;
- }
- } catch (error) {
- //
+ if (bignumify(expected).isEqualTo(actual)) {
+ logger.info(`[W] ${address} (${this.fromSatoshi(actual)})`);
+ } else {
+ logger.error(`[W] ${address} (${this.fromSatoshi(expected)} / ${this.fromSatoshi(actual)})`);
}
-
- return null;
}
- /**
- * Get delegate voters by public key.
- * @param {String} publicKey
- * @return {Object[]}
- */
- public async getVoters(publicKey) {
+ protected async getWalletBalance(address: string): Promise {
try {
- return paginate(this.config, `/api/v2/delegates/${publicKey}/voters`);
+ const { data } = await this.api.get(`wallets/${address}`);
+
+ return bignumify(data.balance);
} catch (error) {
- const message = error.response ? error.response.data.message : error.message;
- throw new Error(`Could not get voters for '${publicKey}': ${message}`);
+ return Bignum.ZERO;
}
}
- /**
- * Get wallet balance by address.
- * @param {String} address
- * @return {Bignum}
- */
- public async getWalletBalance(address) {
- try {
- return bignumify((await this.getWallet(address)).balance);
- } catch (error) {
- //
- }
+ protected async broadcastTransactions(transactions) {
+ await this.sendTransaction(transactions);
- return Bignum.ZERO;
+ return this.awaitConfirmations(transactions);
}
- /**
- * Get wallet by address.
- * @param {String} address
- * @return {Object}
- */
- public async getWallet(address) {
- try {
- const response = await request(this.config).get(`/api/v2/wallets/${address}`);
+ protected castFlags(values: Record): string[] {
+ return Object.keys(BaseCommand.flagsConfig)
+ .map((key: string) => {
+ const value = values[key];
- if (response.data) {
- return response.data;
- }
+ if (value === undefined) {
+ return undefined;
+ }
- return null;
- } catch (error) {
- const message = error.response ? error.response.data.message : error.message;
- throw new Error(`Could not get wallet for '${address}': ${message}`);
- }
- }
+ if (value === true) {
+ return `--${key}`;
+ }
- /**
- * Send transactions to API and wait for response.
- * @param {Object[]} transactions
- * @param {String} [transactionType]
- * @param {Boolean} [wait=true]
- * @return {Object}
- */
- public async sendTransactions(transactions: Transaction[], transactionType: any = null, wait = true) {
- const response = await this.postTransactions(transactions);
-
- if (wait) {
- const delaySeconds = this.getTransactionDelaySeconds(transactions);
- transactionType = `${transactionType ? `${transactionType} ` : ""}transactions`;
- logger.info(`Waiting ${delaySeconds} seconds to apply ${transactionType}`);
- await delay(delaySeconds * 1000);
- }
+ return `--${key}=${value}`;
+ })
+ .filter(value => value !== undefined);
+ }
- return response;
+ protected toSatoshi(value) {
+ return bignumify(value)
+ .times(1e8)
+ .toFixed();
}
- /**
- * Send transactions to API.
- * @param {Object[]} transactions
- * @return {Object}
- */
- public async postTransactions(transactions: Transaction[]) {
- try {
- const response = await request(this.config).post("/api/v2/transactions", {
- transactions: transactions.map(tx => tx.data),
- });
- return response.data;
- } catch (error) {
- const message = error.response ? error.response.data.message : error.message;
- throw new Error(`Could not post transactions: ${message}`);
- }
+ protected fromSatoshi(satoshi) {
+ return formatSatoshi(satoshi);
}
- /**
- * Load constants from API and apply to config.
- * @return {void}
- */
- public async loadConstants() {
+ private async setupConstants() {
try {
- this.config.constants = (await request(this.config).get("/api/v2/node/configuration")).data.constants;
+ const { data } = await this.api.get("node/configuration");
+
+ this.constants = data.constants;
} catch (error) {
- logger.error("Failed to get constants: ", error.message);
+ logger.error(error.message);
process.exit(1);
}
}
- /**
- * Load network from API and apply to config.
- * @return {void}
- */
- public async loadNetworkConfig() {
+ private async setupNetwork() {
try {
- this.config.network = (await request(this.config).get("/config", true)).data.network;
+ const { data } = await this.p2p.get("config");
+
+ this.network = data.network;
} catch (error) {
- logger.error("Failed to get network config: ", error.message);
+ logger.error(error.message);
process.exit(1);
}
}
- /**
- * Apply options to config.
- * @return {void}
- */
- protected applyConfig() {
- this.config = { ...config };
-
- if (this.options.baseUrl) {
- this.config.baseUrl = this.options.baseUrl.replace(/\/+$/, "");
- }
-
- if (this.options.apiPort) {
- this.config.apiPort = this.options.apiPort;
- }
-
- if (this.options.p2pPort && process.env.NODE_ENV !== "test") {
- this.config.p2pPort = this.options.p2pPort;
- }
-
- if (this.options.passphrase) {
- this.config.passphrase = this.options.passphrase;
- }
-
- if (this.options.secondPassphrase) {
- this.config.secondPassphrase = this.options.secondPassphrase;
- }
- }
-
- /**
- * Quit command and output error when problem sending transactions.
- * @param {Error} error
- * @return {void}
- */
- protected problemSendingTransactions(error) {
- const message = error.response ? error.response.data.message : error.message;
- logger.error(`There was a problem sending transactions: ${message}`);
- process.exit(1);
- }
-
- /**
- * Determine how long to wait for transactions to process.
- * @param {Object[]} transactions
- * @return {Number}
- */
- protected getTransactionDelaySeconds(transactions) {
+ private async awaitConfirmations(transactions): Promise {
if (process.env.NODE_ENV === "test") {
- return 0;
+ return;
}
- const waitPerBlock = Math.round(this.config.constants.blocktime / 10) * 20;
-
- return waitPerBlock * Math.ceil(transactions.length / this.config.constants.block.maxTransactions);
- }
-
- protected castFlags(values: Record): string[] {
- return Object.keys(BaseCommand.flags)
- .filter(k => !["copy"].includes(k))
- .map((key: string) => {
- const value = values[key];
+ const waitPerBlock =
+ this.constants.blocktime * Math.ceil(transactions.length / this.constants.block.maxTransactions);
- if (value === undefined) {
- return undefined;
- }
-
- if (value === true) {
- return `--${key}`;
- }
-
- return `--${key}=${value}`;
- })
- .filter(value => value !== undefined);
+ await delay(waitPerBlock * 1000);
}
}
diff --git a/packages/core-debugger-cli/src/commands/deserialize.ts b/packages/core-tester-cli/src/commands/debug/deserialize.ts
similarity index 85%
rename from packages/core-debugger-cli/src/commands/deserialize.ts
rename to packages/core-tester-cli/src/commands/debug/deserialize.ts
index 7af2cb292d..ae92cf93fe 100644
--- a/packages/core-debugger-cli/src/commands/deserialize.ts
+++ b/packages/core-tester-cli/src/commands/debug/deserialize.ts
@@ -1,13 +1,13 @@
import { models } from "@arkecosystem/crypto";
import { flags } from "@oclif/command";
-import { handleOutput } from "../utils";
-import { BaseCommand } from "./command";
+import { handleOutput } from "../../utils";
+import { BaseCommand } from "../command";
export class DeserializeCommand extends BaseCommand {
public static description: string = "Deserialize the given HEX";
public static flags = {
- ...BaseCommand.flags,
+ ...BaseCommand.flagsDebug,
data: flags.string({
description: "the HEX blob to deserialize",
required: true,
@@ -20,7 +20,6 @@ export class DeserializeCommand extends BaseCommand {
};
public async run(): Promise {
- // tslint:disable-next-line:no-shadowed-variable
const { flags } = this.parse(DeserializeCommand);
let output;
diff --git a/packages/core-debugger-cli/src/commands/identity.ts b/packages/core-tester-cli/src/commands/debug/identity.ts
similarity index 91%
rename from packages/core-debugger-cli/src/commands/identity.ts
rename to packages/core-tester-cli/src/commands/debug/identity.ts
index 0ed91c6ba3..3d257dff6e 100644
--- a/packages/core-debugger-cli/src/commands/identity.ts
+++ b/packages/core-tester-cli/src/commands/debug/identity.ts
@@ -1,13 +1,13 @@
import { crypto } from "@arkecosystem/crypto";
import { flags } from "@oclif/command";
-import { handleOutput } from "../utils";
-import { BaseCommand } from "./command";
+import { handleOutput } from "../../utils";
+import { BaseCommand } from "../command";
export class IdentityCommand extends BaseCommand {
public static description: string = "Get identities from the given input";
public static flags = {
- ...BaseCommand.flags,
+ ...BaseCommand.flagsDebug,
data: flags.string({
description: "the data to get the identities from",
required: true,
@@ -24,7 +24,6 @@ export class IdentityCommand extends BaseCommand {
};
public async run(): Promise {
- // tslint:disable-next-line:no-shadowed-variable
const { flags } = this.parse(IdentityCommand);
let output;
diff --git a/packages/core-debugger-cli/src/commands/serialize.ts b/packages/core-tester-cli/src/commands/debug/serialize.ts
similarity index 86%
rename from packages/core-debugger-cli/src/commands/serialize.ts
rename to packages/core-tester-cli/src/commands/debug/serialize.ts
index d058bbbe67..0ae78aa331 100644
--- a/packages/core-debugger-cli/src/commands/serialize.ts
+++ b/packages/core-tester-cli/src/commands/debug/serialize.ts
@@ -1,13 +1,13 @@
import { models, Transaction } from "@arkecosystem/crypto";
import { flags } from "@oclif/command";
-import { handleOutput } from "../utils";
-import { BaseCommand } from "./command";
+import { handleOutput } from "../../utils";
+import { BaseCommand } from "../command";
export class SerializeCommand extends BaseCommand {
public static description: string = "Serialize the given JSON";
public static flags = {
- ...BaseCommand.flags,
+ ...BaseCommand.flagsDebug,
data: flags.string({
description: "the HEX blob to serialize",
required: true,
@@ -23,7 +23,6 @@ export class SerializeCommand extends BaseCommand {
};
public async run(): Promise {
- // tslint:disable-next-line:no-shadowed-variable
const { flags } = this.parse(SerializeCommand);
const serialized =
diff --git a/packages/core-debugger-cli/src/commands/verify-second.ts b/packages/core-tester-cli/src/commands/debug/verify-second-signature.ts
similarity index 83%
rename from packages/core-debugger-cli/src/commands/verify-second.ts
rename to packages/core-tester-cli/src/commands/debug/verify-second-signature.ts
index 891265c7d5..aa6176aeb8 100644
--- a/packages/core-debugger-cli/src/commands/verify-second.ts
+++ b/packages/core-tester-cli/src/commands/debug/verify-second-signature.ts
@@ -1,13 +1,13 @@
import { crypto, models } from "@arkecosystem/crypto";
import { flags } from "@oclif/command";
-import { handleOutput } from "../utils";
-import { BaseCommand } from "./command";
+import { handleOutput } from "../../utils";
+import { BaseCommand } from "../command";
export class VerifySecondSignatureCommand extends BaseCommand {
public static description: string = "Verify a second signature of a transaction";
public static flags = {
- ...BaseCommand.flags,
+ ...BaseCommand.flagsDebug,
data: flags.string({
description: "the HEX blob to deserialize and verify",
required: true,
@@ -19,7 +19,6 @@ export class VerifySecondSignatureCommand extends BaseCommand {
};
public async run(): Promise {
- // tslint:disable-next-line:no-shadowed-variable
const { flags } = this.parse(VerifySecondSignatureCommand);
const { data } = models.Transaction.fromHex(flags.data);
diff --git a/packages/core-debugger-cli/src/commands/verify.ts b/packages/core-tester-cli/src/commands/debug/verify.ts
similarity index 84%
rename from packages/core-debugger-cli/src/commands/verify.ts
rename to packages/core-tester-cli/src/commands/debug/verify.ts
index 0a246ddb9c..a7ba9736ce 100644
--- a/packages/core-debugger-cli/src/commands/verify.ts
+++ b/packages/core-tester-cli/src/commands/debug/verify.ts
@@ -1,13 +1,13 @@
import { models } from "@arkecosystem/crypto";
import { flags } from "@oclif/command";
-import { handleOutput } from "../utils";
-import { BaseCommand } from "./command";
+import { handleOutput } from "../../utils";
+import { BaseCommand } from "../command";
export class VerifyCommand extends BaseCommand {
public static description: string = "Verify the given HEX";
public static flags = {
- ...BaseCommand.flags,
+ ...BaseCommand.flagsDebug,
data: flags.string({
description: "the HEX blob to deserialize and verify",
required: true,
@@ -19,7 +19,6 @@ export class VerifyCommand extends BaseCommand {
};
public async run(): Promise {
- // tslint:disable-next-line:no-shadowed-variable
const { flags } = this.parse(VerifyCommand);
let output = false;
diff --git a/packages/core-tester-cli/src/commands/delegate-registration.ts b/packages/core-tester-cli/src/commands/delegate-registration.ts
deleted file mode 100644
index 2d6cf5412e..0000000000
--- a/packages/core-tester-cli/src/commands/delegate-registration.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import { client } from "@arkecosystem/crypto";
-import { flags } from "@oclif/command";
-import pluralize from "pluralize";
-import superheroes from "superheroes";
-import { customFlags } from "../flags";
-import { logger, parseFee, satoshiToArk } from "../utils";
-import { BaseCommand } from "./command";
-import { TransferCommand } from "./transfer";
-
-export class DelegateRegistrationCommand extends BaseCommand {
- public static description: string = "create multiple delegates";
-
- public static flags = {
- ...BaseCommand.flags,
- delegateFee: customFlags.number({
- description: "delegate registration fee",
- default: 25,
- }),
- };
-
- /**
- * Run delegate-registration command.
- * @return {void}
- */
- public async run(): Promise {
- // tslint:disable-next-line: no-shadowed-variable
- const { flags } = await this.initialize(DelegateRegistrationCommand);
-
- const wallets = this.generateWallets();
-
- for (const wallet of wallets) {
- await TransferCommand.run(
- ["--amount", String(this.options.amount || 25), "--recipient", wallet.address, "--skipTesting"].concat(
- this.castFlags(flags),
- ),
- );
- }
-
- const delegates = await this.getDelegates();
-
- logger.error(
- `Sending ${this.options.number} delegate registration ${pluralize("transaction", this.options.number)}`,
- );
-
- if (!this.options.skipValidation) {
- logger.error(`Starting delegate count: ${delegates.length}`);
- }
-
- const transactions = [];
- const usedDelegateNames = delegates.map(delegate => delegate.username);
-
- wallets.forEach((wallet, i) => {
- while (!wallet.username || usedDelegateNames.includes(wallet.username)) {
- wallet.username = superheroes.random();
- }
-
- wallet.username = wallet.username.toLowerCase().replace(/ /g, "_");
- usedDelegateNames.push(wallet.username);
-
- const transaction = client
- .getBuilder()
- .delegateRegistration()
- .fee(parseFee(this.options.delegateFee))
- .usernameAsset(wallet.username)
- .network(this.config.network.version)
- .sign(wallet.passphrase)
- .secondSign(this.config.secondPassphrase)
- .build();
-
- transactions.push(transaction);
-
- logger.info(
- `${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.data.fee)}, username: ${
- wallet.username
- })`,
- );
- });
-
- if (this.options.copy) {
- this.copyToClipboard(transactions);
- return;
- }
-
- const expectedDelegates = delegates.length + wallets.length;
- if (!this.options.skipValidation) {
- logger.info(`Expected end delegate count: ${expectedDelegates}`);
- }
-
- try {
- await this.sendTransactions(transactions, "delegate", !this.options.skipValidation);
-
- if (this.options.skipValidation) {
- return;
- }
-
- const targetDelegates = await this.getDelegates();
- logger.info(`All transactions have been sent! Total delegates: ${targetDelegates.length}`);
-
- if (targetDelegates.length !== expectedDelegates) {
- logger.error(
- `Delegate count incorrect. '${targetDelegates.length}' but should be '${expectedDelegates}'`,
- );
- }
- } catch (error) {
- logger.error(
- `There was a problem sending transactions: ${error.response ? error.response.data.message : error}`,
- );
- }
- }
-}
diff --git a/packages/core-tester-cli/src/commands/make/wallets.ts b/packages/core-tester-cli/src/commands/make/wallets.ts
new file mode 100644
index 0000000000..91dcd98872
--- /dev/null
+++ b/packages/core-tester-cli/src/commands/make/wallets.ts
@@ -0,0 +1,46 @@
+import { crypto } from "@arkecosystem/crypto";
+import { flags } from "@oclif/command";
+import { generateMnemonic } from "bip39";
+import { writeFileSync } from "fs";
+import { copyToClipboard } from "../../utils";
+import { BaseCommand } from "../command";
+
+export class WalletCommand extends BaseCommand {
+ public static description: string = "send multiple transactions";
+
+ public static flags = {
+ ...BaseCommand.flagsConfig,
+ quantity: flags.integer({
+ description: "number of wallets to generate",
+ }),
+ copy: flags.boolean({
+ description: "write the wallets to the clipboard",
+ }),
+ write: flags.boolean({
+ description: "write the wallets to the disk",
+ }),
+ };
+
+ public async run(): Promise> {
+ const { flags } = await this.make(WalletCommand);
+
+ const wallets = {};
+ for (let i = 0; i < flags.quantity; i++) {
+ const passphrase = generateMnemonic();
+ const keys = crypto.getKeys(passphrase);
+ const address = crypto.getAddress(keys.publicKey, this.network.version);
+
+ wallets[address] = { address, keys, passphrase };
+ }
+
+ if (flags.copy) {
+ copyToClipboard(JSON.stringify(wallets));
+ }
+
+ if (flags.write) {
+ writeFileSync("./wallets.json", JSON.stringify(wallets));
+ }
+
+ return wallets;
+ }
+}
diff --git a/packages/core-tester-cli/src/commands/multi-signature.ts b/packages/core-tester-cli/src/commands/multi-signature.ts
deleted file mode 100644
index f3f02a8b24..0000000000
--- a/packages/core-tester-cli/src/commands/multi-signature.ts
+++ /dev/null
@@ -1,354 +0,0 @@
-import { client } from "@arkecosystem/crypto";
-import { flags } from "@oclif/command";
-import take from "lodash/take";
-import pluralize from "pluralize";
-import { customFlags } from "../flags";
-import { arkToSatoshi, generateTransactions, logger, parseFee, satoshiToArk } from "../utils";
-import { BaseCommand } from "./command";
-import { TransferCommand } from "./transfer";
-
-export class MultiSignatureCommand extends BaseCommand {
- public static description: string = "create multiple multisig wallets";
-
- public static flags = {
- ...BaseCommand.flags,
- multisigFee: customFlags.number({
- description: "multisig fee",
- default: 5,
- }),
- min: flags.integer({
- description: "minimum number of signatures per transaction",
- default: 2,
- }),
- lifetime: flags.integer({
- description: "lifetime of transaction",
- default: 72,
- }),
- quantity: flags.integer({
- description: "number of signatures per wallet",
- default: 3,
- }),
- skipTests: flags.boolean({
- description: "skip transaction tests",
- }),
- };
-
- /**
- * Run multi-signature command.
- * @return {void}
- */
- public async run(): Promise {
- // tslint:disable-next-line: no-shadowed-variable
- const { flags } = await this.initialize(MultiSignatureCommand);
-
- const approvalWallets = this.generateWallets(this.options.quantity);
- const publicKeys = approvalWallets.map(wallet => `+${wallet.keys.publicKey}`);
- const min = this.options.min ? Math.min(this.options.min, publicKeys.length) : publicKeys.length;
-
- const testCosts = this.options.skipTests ? 1 : 2;
- const wallets = this.generateWallets();
-
- for (const wallet of wallets) {
- await TransferCommand.run(
- [
- "--recipient",
- wallet.address,
- "--amount",
- (publicKeys.length + 1) * 5 + testCosts,
- "--skipTesting",
- ].concat(this.castFlags(flags)),
- );
- }
-
- const transactions = this.generateTransactions(wallets, approvalWallets, publicKeys, min);
-
- if (this.options.copy) {
- this.copyToClipboard(transactions);
-
- return;
- }
-
- try {
- const response = await this.sendTransactions(transactions, "multi-signature", !this.options.skipValidation);
-
- if (!this.options.skipValidation) {
- let hasUnprocessed = false;
- for (const transaction of transactions) {
- if (!response.accept.includes(transaction.id)) {
- hasUnprocessed = true;
- logger.error(`Multi-signature transaction '${transaction.id}' was not processed`);
- }
- }
- if (hasUnprocessed) {
- process.exit(1);
- }
-
- for (const transaction of transactions) {
- const tx = await this.getTransaction(transaction.id);
- if (!tx) {
- logger.error(`Transaction '${transaction.id}' should be on the blockchain`);
- }
- }
- }
- } catch (error) {
- const message = error.response ? error.response.data.message : error.message;
- logger.error(`There was a problem sending multi-signature transactions: ${message}`);
- process.exit(1);
- }
-
- if (this.options.skipTests || this.options.skipValidation) {
- return;
- }
-
- await this.testSendWithSignatures(wallets, approvalWallets);
- await this.testSendWithMinSignatures(wallets, approvalWallets, min);
- await this.testSendWithBelowMinSignatures(wallets, approvalWallets, min);
- await this.testSendWithoutSignatures(wallets);
- await this.testSendWithEmptySignatures(wallets);
- await this.testNewMultiSignatureRegistration(wallets, approvalWallets, publicKeys, min);
- }
-
- /**
- * Generate batch of transactions based on wallets
- * @param {Object[]} wallets
- * @param {Object[]} [approvalWallets=[]]
- * @param {String[]} [publicKeys=[]]
- * @param {Number} [min=2]
- * @param {Boolean} [log=true]
- * @return {Object[]}
- */
- public generateTransactions(wallets, approvalWallets = [], publicKeys = [], min = 2, log = true) {
- const transactions = [];
- wallets.forEach((wallet, i) => {
- const builder = client.getBuilder().multiSignature();
-
- builder
- .fee(parseFee(this.options.multisigFee))
- .multiSignatureAsset({
- lifetime: this.options.lifetime,
- keysgroup: publicKeys,
- min,
- })
- .network(this.config.network.version)
- .sign(wallet.passphrase);
-
- if (wallet.secondPassphrase || this.config.secondPassphrase) {
- builder.secondSign(wallet.secondPassphrase || this.config.secondPassphrase);
- }
-
- if (approvalWallets) {
- for (let j = approvalWallets.length - 1; j >= 0; j--) {
- builder.multiSignatureSign(approvalWallets[j].passphrase);
- }
- }
-
- const transaction = builder.build();
- transactions.push(transaction);
-
- if (log) {
- logger.info(
- `${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.data.fee)})`,
- );
- }
- });
-
- return transactions;
- }
-
- /**
- * Send transactions with approver signatures.
- * @param {Object[]} wallets
- * @param {Object[]} [approvalWallets=[]]
- * @return {void}
- */
- public async testSendWithSignatures(wallets, approvalWallets = []) {
- logger.info("Sending transactions with signatures");
-
- const transactions = generateTransactions(arkToSatoshi(2), wallets, approvalWallets, {
- config: this.config,
- ...this.options,
- });
-
- try {
- await this.sendTransactions(transactions);
- for (const transaction of transactions) {
- const tx = await this.getTransaction(transaction.id);
- if (!tx) {
- logger.error(`Transaction '${transaction.id}' should be on the blockchain`);
- }
- }
- } catch (error) {
- this.problemSendingTransactions(error);
- }
- }
-
- /**
- * Send transactions with min approver signatures.
- * @param {Object[]} wallets
- * @param {Object[]} [approvalWallets=[]]
- * @param {Number} [min=2]
- * @return {void}
- */
- public async testSendWithMinSignatures(wallets, approvalWallets = [], min = 2) {
- logger.info(
- `Sending transactions with ${min} (min) of ${pluralize("signature", approvalWallets.length, true)}`,
- );
-
- const transactions = generateTransactions(arkToSatoshi(2), wallets, take(approvalWallets, min), {
- config: this.config,
- ...this.options,
- });
-
- try {
- await this.sendTransactions(transactions);
- for (const transaction of transactions) {
- const tx = await this.getTransaction(transaction.id);
- if (!tx) {
- logger.error(`Transaction '${transaction.id}' should be on the blockchain`);
- }
- }
- } catch (error) {
- this.problemSendingTransactions(error);
- }
- }
-
- /**
- * Send transactions with below min approver signatures.
- * @param {Object[]} wallets
- * @param {Object[]} [approvalWallets=[]]
- * @param {Number} [min=2]
- * @return {void}
- */
- public async testSendWithBelowMinSignatures(wallets, approvalWallets = [], min = 2) {
- const max = min - 1;
- logger.info(
- `Sending transactions with ${max} (below min) of ${pluralize("signature", approvalWallets.length, true)}`,
- );
-
- const transactions = generateTransactions(arkToSatoshi(2), wallets, take(approvalWallets, max), {
- config: this.config,
- ...this.options,
- });
-
- try {
- await this.sendTransactions(transactions);
- for (const transaction of transactions) {
- try {
- const tx = await this.getTransaction(transaction.id);
- if (tx) {
- logger.error(`Transaction '${transaction.id}' should not be on the blockchain`);
- }
- } catch (error) {
- const message = error.response ? error.response.data.message : error.message;
- if (message !== "Transaction not found") {
- logger.error(`Failed to check transaction '${transaction.id}': ${message}`);
- }
- }
- }
- } catch (error) {
- this.problemSendingTransactions(error);
- }
- }
-
- /**
- * Send transactions without approver signatures.
- * @param {Object[]} wallets
- * @return {void}
- */
- public async testSendWithoutSignatures(wallets) {
- logger.info("Sending transactions without signatures");
-
- const transactions = generateTransactions(arkToSatoshi(2), wallets, [], {
- config: this.config,
- ...this.options,
- });
-
- try {
- await this.sendTransactions(transactions);
- for (const transaction of transactions) {
- try {
- const tx = await this.getTransaction(transaction.id);
- if (tx) {
- logger.error(`Transaction '${transaction.id}' should not be on the blockchain`);
- }
- } catch (error) {
- const message = error.response ? error.response.data.message : error.message;
- if (message !== "Transaction not found") {
- logger.error(`Failed to check transaction '${transaction.id}': ${message}`);
- }
- }
- }
- } catch (error) {
- this.problemSendingTransactions(error);
- }
- }
-
- /**
- * Send transactions with empty approver signatures.
- * @param {Object[]} wallets
- * @return {void}
- */
- public async testSendWithEmptySignatures(wallets) {
- logger.info("Sending transactions with empty signatures");
-
- const transactions = generateTransactions(arkToSatoshi(2), wallets, [], {
- config: this.config,
- ...this.options,
- });
- for (const transaction of transactions) {
- transaction.data.signatures = [];
- }
-
- try {
- await this.sendTransactions(transactions);
- for (const transaction of transactions) {
- try {
- const tx = await this.getTransaction(transaction.id);
- if (tx) {
- logger.error(`Transaction '${transaction.id}' should not be on the blockchain`);
- }
- } catch (error) {
- const message = error.response ? error.response.data.message : error.message;
- if (message !== "Transaction not found") {
- logger.error(`Failed to check transaction '${transaction.id}': ${message}`);
- }
- }
- }
- } catch (error) {
- this.problemSendingTransactions(error);
- }
- }
-
- /**
- * Send transactions to re-register multi-signature wallets.
- * @param {Object[]} wallets
- * @param {Object[]} [approvalWallets=[]]
- * @param {Object[]} [publicKeys=[]]
- * @param {Number} [min=2]
- * @return {void}
- */
- public async testNewMultiSignatureRegistration(wallets, approvalWallets = [], publicKeys = [], min = 2) {
- logger.info("Sending transactions to re-register multi-signature");
-
- const transactions = this.generateTransactions(wallets, approvalWallets, publicKeys, min);
-
- try {
- await this.sendTransactions(transactions);
- for (const transaction of transactions) {
- try {
- const tx = await this.getTransaction(transaction.id);
- if (tx) {
- logger.error(`Transaction '${transaction.id}' should not be on the blockchain`);
- }
- } catch (error) {
- const message = error.response ? error.response.data.message : error.message;
- if (message !== "Transaction not found") {
- logger.error(`Failed to check transaction '${transaction.id}': ${message}`);
- }
- }
- }
- } catch (error) {
- this.problemSendingTransactions(error);
- }
- }
-}
diff --git a/packages/core-tester-cli/src/commands/second-signature.ts b/packages/core-tester-cli/src/commands/second-signature.ts
deleted file mode 100644
index c72e681762..0000000000
--- a/packages/core-tester-cli/src/commands/second-signature.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { client } from "@arkecosystem/crypto";
-import { flags } from "@oclif/command";
-import pluralize from "pluralize";
-import { customFlags } from "../flags";
-import { logger, parseFee, satoshiToArk } from "../utils";
-import { BaseCommand } from "./command";
-import { TransferCommand } from "./transfer";
-
-export class SecondSignatureCommand extends BaseCommand {
- public static description: string = "create wallets with second signature";
-
- public static flags = {
- ...BaseCommand.flags,
- signatureFee: customFlags.number({
- description: "second signature fee",
- default: 5,
- }),
- };
-
- /**
- * Run second-signature command.
- * @return {void}
- */
- public async run(): Promise {
- // tslint:disable-next-line: no-shadowed-variable
- const { flags } = await this.initialize(SecondSignatureCommand);
-
- const wallets = this.generateWallets();
-
- for (const wallet of wallets) {
- await TransferCommand.run(
- ["--recipient", wallet.address, "--amount", String(this.options.amount || 5), "--skipTesting"].concat(
- this.castFlags(flags),
- ),
- );
- }
-
- logger.info(`Sending ${this.options.number} second signature ${pluralize("transaction", this.options.number)}`);
-
- const transactions = [];
- wallets.forEach((wallet, i) => {
- wallet.secondPassphrase = this.config.secondPassphrase || wallet.passphrase;
- const transaction = client
- .getBuilder()
- .secondSignature()
- .fee(parseFee(this.options.signatureFee))
- .signatureAsset(wallet.secondPassphrase)
- .network(this.config.network.version)
- .sign(wallet.passphrase)
- .build();
-
- wallet.publicKey = transaction.data.senderPublicKey;
- wallet.secondPublicKey = transaction.data.asset.signature.publicKey;
- transactions.push(transaction);
-
- logger.info(`${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.data.fee)})`);
- });
-
- if (this.options.copy) {
- this.copyToClipboard(transactions);
- return;
- }
-
- try {
- await this.sendTransactions(transactions, "second-signature", !this.options.skipValidation);
-
- if (this.options.skipValidation) {
- return;
- }
-
- for (const walletObject of wallets) {
- const wallet = await this.getWallet(walletObject.address);
-
- if (
- wallet.secondPublicKey !== walletObject.secondPublicKey ||
- wallet.publicKey !== walletObject.publicKey
- ) {
- logger.error(`Invalid second signature for ${walletObject.address}.`);
- }
- }
- } catch (error) {
- logger.error(
- `There was a problem sending transactions: ${error.response ? error.response.data.message : error}`,
- );
- }
- }
-}
diff --git a/packages/core-tester-cli/src/commands/send/delegate-registration.ts b/packages/core-tester-cli/src/commands/send/delegate-registration.ts
new file mode 100644
index 0000000000..af3f8f0f5e
--- /dev/null
+++ b/packages/core-tester-cli/src/commands/send/delegate-registration.ts
@@ -0,0 +1,86 @@
+import { Address } from "@arkecosystem/crypto";
+import pokemon from "pokemon";
+import { satoshiFlag } from "../../flags";
+import { logger } from "../../logger";
+import { SendCommand } from "../../shared/send";
+import { TransferCommand } from "./transfer";
+
+export class DelegateRegistrationCommand extends SendCommand {
+ public static description: string = "create multiple delegates";
+
+ public static flags = {
+ ...SendCommand.flagsSend,
+ delegateFee: satoshiFlag({
+ description: "delegate registration fee",
+ default: 25,
+ }),
+ };
+
+ protected getCommand(): any {
+ return DelegateRegistrationCommand;
+ }
+
+ protected async createWalletsWithBalance(flags: Record): Promise {
+ return TransferCommand.run(
+ [`--amount=${flags.delegateFee}`, `--number=${flags.number}`, "--skipProbing"].concat(
+ this.castFlags(flags),
+ ),
+ );
+ }
+
+ protected async signTransactions(flags: Record, wallets: Record): Promise {
+ const transactions = [];
+
+ for (const [address, wallet] of Object.entries(wallets)) {
+ wallets[address].username = pokemon
+ .random()
+ .toLowerCase()
+ .replace(/ /g, "_");
+
+ transactions.push(
+ this.signer.makeDelegate({
+ ...flags,
+ ...{
+ username: wallets[address].username,
+ // @ts-ignore
+ passphrase: wallet.passphrase,
+ },
+ }),
+ );
+ }
+
+ return transactions;
+ }
+
+ protected async expectBalances(transactions, wallets): Promise {
+ for (const transaction of transactions) {
+ const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version);
+
+ const currentBalance = await this.getWalletBalance(recipientId);
+ wallets[recipientId].expectedBalance = currentBalance.minus(transaction.fee);
+ }
+ }
+
+ protected async verifyTransactions(transactions, wallets): Promise {
+ for (const transaction of transactions) {
+ const wasCreated = await this.knockTransaction(transaction.id);
+
+ if (wasCreated) {
+ const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version);
+
+ await this.knockBalance(recipientId, wallets[recipientId].expectedBalance);
+ await this.knockUsername(recipientId, wallets[recipientId].username);
+ }
+ }
+ }
+
+ private async knockUsername(address: string, expected: string): Promise {
+ const { username: actual } = (await this.api.get(`wallets/${address}`)).data;
+
+ if (actual === expected) {
+ logger.info(`[W] ${address} (${actual})`);
+ } else {
+ logger.error(`[W] ${address} (${expected} / ${actual})`);
+ }
+ }
+}
diff --git a/packages/core-tester-cli/src/commands/send/second-signature-registration.ts b/packages/core-tester-cli/src/commands/send/second-signature-registration.ts
new file mode 100644
index 0000000000..cd0adcd96d
--- /dev/null
+++ b/packages/core-tester-cli/src/commands/send/second-signature-registration.ts
@@ -0,0 +1,82 @@
+import { Address } from "@arkecosystem/crypto";
+import { satoshiFlag } from "../../flags";
+import { logger } from "../../logger";
+import { SendCommand } from "../../shared/send";
+import { TransferCommand } from "./transfer";
+
+export class SecondSignatureRegistrationCommand extends SendCommand {
+ public static description: string = "create wallets with second signature";
+
+ public static flags = {
+ ...SendCommand.flagsSend,
+ signatureFee: satoshiFlag({
+ description: "second signature fee",
+ default: 5,
+ }),
+ };
+
+ protected getCommand(): any {
+ return SecondSignatureRegistrationCommand;
+ }
+
+ protected async createWalletsWithBalance(flags: Record): Promise {
+ return TransferCommand.run(
+ [`--amount=${flags.signatureFee}`, `--number=${flags.number}`, "--skipProbing"].concat(
+ this.castFlags(flags),
+ ),
+ );
+ }
+
+ protected async signTransactions(flags: Record, wallets: Record): Promise {
+ const transactions = [];
+
+ for (const [address, wallet] of Object.entries(wallets)) {
+ const transaction = this.signer.makeSecondSignature({
+ ...flags,
+ ...{
+ passphrase: wallet.passphrase,
+ secondPassphrase: wallet.passphrase,
+ },
+ });
+
+ wallets[address].publicKey = transaction.senderPublicKey;
+ wallets[address].secondPublicKey = transaction.asset.signature.publicKey;
+
+ transactions.push(transaction);
+ }
+
+ return transactions;
+ }
+
+ protected async expectBalances(transactions, wallets): Promise {
+ for (const transaction of transactions) {
+ const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version);
+
+ const currentBalance = await this.getWalletBalance(recipientId);
+ wallets[recipientId].expectedBalance = currentBalance.minus(transaction.fee);
+ }
+ }
+
+ protected async verifyTransactions(transactions, wallets): Promise {
+ for (const transaction of transactions) {
+ const wasCreated = await this.knockTransaction(transaction.id);
+
+ if (wasCreated) {
+ const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version);
+
+ await this.knockBalance(recipientId, wallets[recipientId].expectedBalance);
+ await this.knockSignature(recipientId, wallets[recipientId].secondPublicKey);
+ }
+ }
+ }
+
+ private async knockSignature(address: string, expected: string): Promise {
+ const { secondPublicKey: actual } = (await this.api.get(`wallets/${address}`)).data;
+
+ if (actual === expected) {
+ logger.info(`[W] ${address} (${actual})`);
+ } else {
+ logger.error(`[W] ${address} (${expected} / ${actual})`);
+ }
+ }
+}
diff --git a/packages/core-tester-cli/src/commands/send/transfer.ts b/packages/core-tester-cli/src/commands/send/transfer.ts
new file mode 100644
index 0000000000..b893ce07b3
--- /dev/null
+++ b/packages/core-tester-cli/src/commands/send/transfer.ts
@@ -0,0 +1,63 @@
+import { flags } from "@oclif/command";
+import { delay } from "bluebird";
+import { SendCommand } from "../../shared/send";
+import { WalletCommand } from "../make/wallets";
+
+export class TransferCommand extends SendCommand {
+ public static description: string = "send multiple transactions";
+
+ public static flags = {
+ ...SendCommand.flagsSend,
+ recipient: flags.string({
+ description: "recipient address",
+ }),
+ vendorField: flags.string({
+ description: "vendor field to use",
+ }),
+ };
+
+ protected getCommand(): any {
+ return TransferCommand;
+ }
+
+ protected async createWalletsWithBalance(flags: Record): Promise {
+ return WalletCommand.run([`--quantity=${flags.number}`].concat(this.castFlags(flags)));
+ }
+
+ protected async signTransactions(flags: Record, wallets: Record): Promise {
+ const transactions = [];
+
+ for (let i = 0; i < flags.number; i++) {
+ const vendorField = flags.vendorField || `Transaction ${i}`;
+
+ if (flags.recipient) {
+ transactions.push(
+ this.signer.makeTransfer({ ...flags, ...{ recipient: flags.recipient, vendorField } }),
+ );
+ } else {
+ for (const wallet of Object.keys(wallets)) {
+ transactions.push(this.signer.makeTransfer({ ...flags, ...{ recipient: wallet, vendorField } }));
+ }
+ }
+ }
+
+ return transactions;
+ }
+
+ protected async expectBalances(transactions, wallets): Promise {
+ for (const transaction of transactions) {
+ const currentBalance = await this.getWalletBalance(transaction.recipientId);
+ wallets[transaction.recipientId].expectedBalance = currentBalance.plus(transaction.amount);
+ }
+ }
+
+ protected async verifyTransactions(transactions, wallets): Promise {
+ for (const transaction of transactions) {
+ const wasCreated = await this.knockTransaction(transaction.id);
+
+ if (wasCreated) {
+ await this.knockBalance(transaction.recipientId, wallets[transaction.recipientId].expectedBalance);
+ }
+ }
+ }
+}
diff --git a/packages/core-tester-cli/src/commands/send/vote.ts b/packages/core-tester-cli/src/commands/send/vote.ts
new file mode 100644
index 0000000000..1dcaad6463
--- /dev/null
+++ b/packages/core-tester-cli/src/commands/send/vote.ts
@@ -0,0 +1,91 @@
+import { Address } from "@arkecosystem/crypto";
+import { flags } from "@oclif/command";
+import { satoshiFlag } from "../../flags";
+import { logger } from "../../logger";
+import { SendCommand } from "../../shared/send";
+import { TransferCommand } from "./transfer";
+
+export class VoteCommand extends SendCommand {
+ public static description: string = "create multiple votes for a delegate";
+
+ public static flags = {
+ ...SendCommand.flagsSend,
+ delegate: flags.string({
+ description: "delegate public key",
+ }),
+ voteFee: satoshiFlag({
+ description: "vote fee",
+ default: 1,
+ }),
+ };
+
+ protected getCommand(): any {
+ return VoteCommand;
+ }
+
+ protected async createWalletsWithBalance(flags: Record): Promise {
+ return TransferCommand.run(
+ [`--amount=${flags.voteFee}`, `--number=${flags.number}`, "--skipProbing"].concat(this.castFlags(flags)),
+ );
+ }
+
+ protected async signTransactions(flags: Record, wallets: Record): Promise {
+ const transactions = [];
+
+ for (const [address, wallet] of Object.entries(wallets)) {
+ const delegate = flags.delegate || (await this.getRandomDelegate());
+
+ const transaction = this.signer.makeVote({
+ ...flags,
+ ...{
+ delegate,
+ passphrase: wallet.passphrase,
+ },
+ });
+
+ wallets[address].vote = delegate;
+
+ transactions.push(transaction);
+ }
+
+ return transactions;
+ }
+
+ protected async expectBalances(transactions, wallets): Promise {
+ for (const transaction of transactions) {
+ const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version);
+
+ const currentBalance = await this.getWalletBalance(recipientId);
+ wallets[recipientId].expectedBalance = currentBalance.minus(transaction.fee);
+ }
+ }
+
+ protected async verifyTransactions(transactions, wallets): Promise {
+ for (const transaction of transactions) {
+ const wasCreated = await this.knockTransaction(transaction.id);
+
+ if (wasCreated) {
+ const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version);
+
+ await this.knockBalance(recipientId, wallets[recipientId].expectedBalance);
+ await this.knockVote(recipientId, wallets[recipientId].vote);
+ }
+ }
+ }
+
+ private async knockVote(address: string, expected: string): Promise {
+ const { vote: actual } = (await this.api.get(`wallets/${address}`)).data;
+
+ if (actual === expected) {
+ logger.info(`[W] ${address} (${actual})`);
+ } else {
+ logger.error(`[W] ${address} (${expected} / ${actual})`);
+ }
+ }
+
+ private async getRandomDelegate() {
+ const { data } = await this.api.get("delegates");
+
+ return data[0].publicKey;
+ }
+}
diff --git a/packages/core-tester-cli/src/commands/transfer.ts b/packages/core-tester-cli/src/commands/transfer.ts
deleted file mode 100644
index cd1a07489e..0000000000
--- a/packages/core-tester-cli/src/commands/transfer.ts
+++ /dev/null
@@ -1,315 +0,0 @@
-import { Bignum, crypto } from "@arkecosystem/crypto";
-import { flags } from "@oclif/command";
-import delay from "delay";
-import unique from "lodash/uniq";
-import pluralize from "pluralize";
-import { arkToSatoshi, generateTransactions, logger, satoshiToArk } from "../utils";
-import { BaseCommand } from "./command";
-
-export class TransferCommand extends BaseCommand {
- public static description: string = "send multiple transactions";
-
- public static flags = {
- ...BaseCommand.flags,
- recipient: flags.string({
- description: "recipient address",
- }),
- floodAttempts: flags.integer({
- description: "flood node with same transactions",
- default: 0,
- }),
- skipSecondRun: flags.string({
- description: "skip second sending of transactions",
- }),
- smartBridge: flags.string({
- description: "smart-bridge value to use",
- }),
- };
-
- /**
- * Run transfer command.
- * @param {Object} options
- * @return {void}
- */
- public async run(): Promise {
- await this.initialize(TransferCommand);
-
- const primaryAddress = crypto.getAddress(
- crypto.getKeys(this.config.passphrase).publicKey,
- this.config.network.version,
- );
-
- let wallets = this.options.wallets;
- if (wallets === undefined) {
- wallets = this.generateWallets();
- }
-
- logger.info(`Sending ${wallets.length} transfer ${pluralize("transaction", wallets.length)}`);
-
- const walletBalance = await this.getWalletBalance(primaryAddress);
-
- if (!this.options.skipValidation) {
- logger.info(`Sender starting balance: ${satoshiToArk(walletBalance)}`);
- }
-
- let totalDeductions = Bignum.ZERO;
- const transactionAmount = arkToSatoshi(this.options.amount || 2);
-
- const transactions = this.generateTransactions(transactionAmount, wallets, null, true);
- for (const transaction of transactions) {
- totalDeductions = totalDeductions.plus(transactionAmount).plus(transaction.fee);
- }
-
- if (this.options.copy) {
- this.copyToClipboard(transactions);
- return;
- }
-
- const expectedSenderBalance = new Bignum(walletBalance).minus(totalDeductions);
- if (!this.options.skipValidation) {
- logger.info(`Sender expected ending balance: ${satoshiToArk(expectedSenderBalance)}`);
- }
-
- const runOptions = {
- primaryAddress,
- transactions,
- wallets,
- transactionAmount,
- expectedSenderBalance,
- skipValidation: this.options.skipValidation,
- };
-
- try {
- if (!this.options.floodAttempts) {
- const successfulTest = await this.performRun(runOptions, 1);
- if (
- successfulTest &&
- !this.options.skipSecondRun &&
- !this.options.skipValidation &&
- !this.options.skipTesting
- ) {
- await this.performRun(runOptions, 2, false, true);
- }
- } else {
- const attempts = this.options.floodAttempts;
- for (let i = attempts; i > 0; i--) {
- await this.performRun(runOptions, attempts - i + 1, i !== 1, i !== attempts);
- }
- }
- } catch (error) {
- const message = error.response ? error.response.data.message : error;
- logger.error(`There was a problem sending transactions: ${message}`);
- }
-
- if (this.options.skipValidation) {
- return;
- }
-
- await this.testVendorField(wallets);
- await this.testEmptyVendorField(wallets);
-
- return;
- }
-
- /**
- * Generate batch of transactions based on wallets.
- * @param {Bignum} transactionAmount
- * @param {Object[]} wallets
- * @param {Object[]} [approvalWallets=[]]
- * @param {Boolean} [overridePassphrase=false]
- * @param {String} [vendorField]
- * @param {Boolean} [log=true]
- * @return {Object[]}
- */
- public generateTransactions(
- transactionAmount,
- wallets,
- approvalWallets = [],
- overridePassphrase = false,
- vendorField = null,
- log = true,
- ) {
- return generateTransactions(transactionAmount, wallets, approvalWallets, {
- ...this.options,
- config: this.config,
- overridePassphrase,
- vendorField: vendorField || this.options.smartBridge,
- log,
- });
- }
-
- /**
- * Perform a run of transactions.
- * @param {Object} runOptions
- * @param {Number} [runNumber=1]
- * @param {Boolean} [skipWait=false]
- * @param {Boolean} [isSubsequentRun=false]
- * @return {Boolean}
- */
- public async performRun(runOptions, runNumber = 1, skipWait = false, isSubsequentRun = false) {
- if (skipWait) {
- runOptions.skipValidation = true;
- this.sendTransactionsWithResults(runOptions, isSubsequentRun);
-
- return true;
- }
-
- if (await this.sendTransactionsWithResults(runOptions, isSubsequentRun)) {
- logger.info(`All transactions have been received and forged for run ${runNumber}!`);
-
- return true;
- }
-
- logger.error(`Test failed on run ${runNumber}`);
-
- return false;
- }
-
- /**
- * Send transactions and validate results.
- * @param {Object} runOptions
- * @param {Boolean} isSubsequentRun
- * @return {Boolean}
- */
- public async sendTransactionsWithResults(runOptions, isSubsequentRun) {
- let successfulTest = true;
-
- let postResponse;
- try {
- postResponse = await this.postTransactions(runOptions.transactions);
- } catch (error) {
- if (runOptions.skipValidation) {
- return true;
- }
-
- const message = error.response ? error.response.data.error : error.message;
- logger.error(`Transaction request failed: ${message}`);
-
- return false;
- }
-
- if (runOptions.skipValidation) {
- return true;
- }
-
- if (!isSubsequentRun && (!postResponse.accept || !postResponse.accept.length)) {
- return false;
- }
-
- if (!isSubsequentRun) {
- for (const transaction of runOptions.transactions) {
- if (!postResponse.accept.includes(transaction.id)) {
- logger.error(`Transaction '${transaction.id}' didn't get approved on the network`);
-
- successfulTest = false;
- }
- }
- }
-
- for (const key of Object.keys(postResponse)) {
- if (key === "success") {
- continue;
- }
-
- const dataLength = postResponse[key].length;
- const uniqueLength = unique(postResponse[key]).length;
- if (dataLength !== uniqueLength) {
- logger.error(`Response data for '${key}' has ${dataLength - uniqueLength} duplicate transaction ids`);
- successfulTest = false;
- }
- }
-
- const delaySeconds = this.getTransactionDelaySeconds(runOptions.transactions);
- logger.info(`Waiting ${delaySeconds} seconds to apply transfer transactions`);
- await delay(delaySeconds * 1000);
-
- for (const transaction of runOptions.transactions) {
- const transactionResponse = await this.getTransaction(transaction.id);
- if (transactionResponse && transactionResponse.id !== transaction.id) {
- logger.error(`Transaction '${transaction.id}' didn't get applied on the network`);
-
- successfulTest = false;
- }
- }
-
- if (runOptions.primaryAddress && runOptions.expectedSenderBalance) {
- const walletBalance = await this.getWalletBalance(runOptions.primaryAddress);
- if (!walletBalance.isEqualTo(runOptions.expectedSenderBalance)) {
- successfulTest = false;
- logger.error(
- `Sender balance incorrect: '${satoshiToArk(walletBalance)}' but should be '${satoshiToArk(
- runOptions.expectedSenderBalance,
- )}'`,
- );
- }
- }
-
- for (const wallet of runOptions.wallets) {
- const balance = await this.getWalletBalance(wallet.address);
- if (!balance.isEqualTo(runOptions.transactionAmount)) {
- successfulTest = false;
- logger.error(
- `Incorrect destination balance for ${wallet.address}. Should be '${satoshiToArk(
- runOptions.transactionAmount,
- )}' but is '${satoshiToArk(balance)}'`,
- );
- }
- }
-
- return successfulTest;
- }
-
- /**
- * Test vendor field is set correctly on blockchain.
- * @param {Object[]} wallets
- * @return {void}
- */
- public async testVendorField(wallets) {
- logger.info("Testing VendorField value is set correctly");
-
- const transactions = this.generateTransactions(arkToSatoshi(2), wallets, null, null, "Testing VendorField");
-
- try {
- await this.sendTransactions(transactions);
-
- for (const transaction of transactions) {
- const tx = await this.getTransaction(transaction.id);
- if (!tx) {
- logger.error(`Transaction '${transaction.id}' should be on the blockchain`);
- } else if (tx.vendorField !== "Testing VendorField") {
- logger.error(`Transaction '${transaction.id}' does not have correct vendorField value`);
- }
- }
- } catch (error) {
- this.problemSendingTransactions(error);
- }
- }
-
- /**
- * Test empty vendor field is set correctly on blockchain.
- * @param {Object[]} wallets
- * @return {void}
- */
- public async testEmptyVendorField(wallets) {
- logger.info("Testing empty VendorField value");
-
- const transactions = this.generateTransactions(arkToSatoshi(2), wallets, null, null, null);
-
- try {
- await this.sendTransactions(transactions);
-
- for (const transaction of transactions) {
- const tx = await this.getTransaction(transaction.id);
- if (!tx) {
- logger.error(`Transaction '${transaction.id}' should be on the blockchain`);
- } else if (tx.vendorField) {
- logger.error(
- `Transaction '${transaction.id}' should not have vendorField value '${tx.vendorField}'`,
- );
- }
- }
- } catch (error) {
- this.problemSendingTransactions(error);
- }
- }
-}
diff --git a/packages/core-tester-cli/src/commands/vote.ts b/packages/core-tester-cli/src/commands/vote.ts
deleted file mode 100644
index 15fdead066..0000000000
--- a/packages/core-tester-cli/src/commands/vote.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import { client } from "@arkecosystem/crypto";
-import { flags } from "@oclif/command";
-import sample from "lodash/sample";
-import pluralize from "pluralize";
-import { customFlags } from "../flags";
-import { logger, parseFee, satoshiToArk } from "../utils";
-import { BaseCommand } from "./command";
-import { TransferCommand } from "./transfer";
-
-export class VoteCommand extends BaseCommand {
- public static description: string = "create multiple votes for a delegate";
-
- public static flags = {
- ...BaseCommand.flags,
- delegate: flags.string({
- description: "delegate public key",
- }),
- voteFee: customFlags.number({
- description: "vote fee",
- default: 1,
- }),
- };
-
- /**
- * Run vote command.
- * @return {void}
- */
- public async run(): Promise {
- // tslint:disable-next-line: no-shadowed-variable
- const { flags } = await this.initialize(VoteCommand);
-
- const wallets = this.generateWallets();
-
- for (const wallet of wallets) {
- await TransferCommand.run(
- ["--recipient", wallet.address, "--amount", String(2), "--skipTesting"].concat(this.castFlags(flags)),
- );
- }
-
- let delegate = this.options.delegate;
- if (!delegate) {
- try {
- delegate = sample(await this.getDelegates()).publicKey;
- } catch (error) {
- logger.error(error);
- return;
- }
- }
-
- const voters = await this.getVoters(delegate);
- logger.info(`Sending ${this.options.number} vote ${pluralize("transaction", this.options.number)}`);
-
- const transactions = [];
- wallets.forEach((wallet, i) => {
- const transaction = client
- .getBuilder()
- .vote()
- .fee(parseFee(this.options.voteFee))
- .votesAsset([`+${delegate}`])
- .network(this.config.network.version)
- .sign(wallet.passphrase)
- .secondSign(this.config.secondPassphrase)
- .build();
-
- transactions.push(transaction);
-
- logger.info(`${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.data.fee)})`);
- });
-
- if (this.options.copy) {
- this.copyToClipboard(transactions);
- return;
- }
-
- const expectedVoterCount = voters.length + wallets.length;
- if (!this.options.skipValidation) {
- logger.info(`Expected end voters: ${expectedVoterCount}`);
- }
-
- try {
- await this.sendTransactions(transactions, "vote", !this.options.skipValidation);
-
- if (this.options.skipValidation) {
- return;
- }
-
- const voterCount = (await this.getVoters(delegate)).length;
-
- logger.info(`All transactions have been sent! Total voters: ${voterCount}`);
-
- if (voterCount !== expectedVoterCount) {
- logger.error(`Delegate voter count incorrect. '${voterCount}' but should be '${expectedVoterCount}'`);
- }
- } catch (error) {
- logger.error(
- `There was a problem sending transactions: ${error.response ? error.response.data.message : error}`,
- );
- }
- }
-}
diff --git a/packages/core-tester-cli/src/config.ts b/packages/core-tester-cli/src/config.ts
deleted file mode 100644
index 49fdac960a..0000000000
--- a/packages/core-tester-cli/src/config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export const config = Object.freeze({
- apiPort: 4003,
- p2pPort: 4000,
- baseUrl: "http://localhost",
- passphrase: "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire",
- secondPassphrase: "",
-});
diff --git a/packages/core-tester-cli/src/flags.ts b/packages/core-tester-cli/src/flags.ts
index a75effe727..d9820bc27a 100644
--- a/packages/core-tester-cli/src/flags.ts
+++ b/packages/core-tester-cli/src/flags.ts
@@ -1,14 +1,13 @@
-import { flags as oclifFlags } from "@oclif/command";
+import { flags } from "@oclif/command";
-export const customFlags = {
- number: oclifFlags.build({
- parse: input => {
- const value = Number(input);
- if (value < 1 / 1e8) {
- throw new Error(`Expected number greater than 1 satoshi.`);
- }
+export const satoshiFlag = flags.build({
+ parse: input => {
+ const value = Number(input);
- return value;
- },
- }),
-};
+ if (value < 1 / 1e8) {
+ throw new Error(`Expected number greater than 1 satoshi.`);
+ }
+
+ return value;
+ },
+});
diff --git a/packages/core-tester-cli/src/http-client.ts b/packages/core-tester-cli/src/http-client.ts
new file mode 100644
index 0000000000..f0bc539481
--- /dev/null
+++ b/packages/core-tester-cli/src/http-client.ts
@@ -0,0 +1,29 @@
+import axios from "axios";
+
+export class HttpClient {
+ private baseURL: any;
+
+ public constructor(baseURL: string) {
+ this.baseURL = baseURL;
+ }
+
+ public async get(path: string, params?: Record, headers?: Record): Promise {
+ try {
+ const { data } = await axios.get(`${this.baseURL}${path}`, { params, headers });
+
+ return data;
+ } catch (error) {
+ // do nothing...
+ }
+ }
+
+ public async post(path: string, payload: Record): Promise {
+ try {
+ const { data } = await axios.post(`${this.baseURL}${path}`, payload);
+
+ return data;
+ } catch (error) {
+ // do nothing...
+ }
+ }
+}
diff --git a/packages/core-tester-cli/src/logger.ts b/packages/core-tester-cli/src/logger.ts
new file mode 100644
index 0000000000..1a857882bd
--- /dev/null
+++ b/packages/core-tester-cli/src/logger.ts
@@ -0,0 +1,7 @@
+import pino from "pino";
+
+export const logger = pino({
+ name: "core-tester-cli",
+ safe: true,
+ prettyPrint: true,
+});
diff --git a/packages/core-tester-cli/src/shared/send.ts b/packages/core-tester-cli/src/shared/send.ts
new file mode 100644
index 0000000000..03013e3d82
--- /dev/null
+++ b/packages/core-tester-cli/src/shared/send.ts
@@ -0,0 +1,40 @@
+import { BaseCommand } from "../commands/command";
+
+export abstract class SendCommand extends BaseCommand {
+ public async run(): Promise {
+ // Parse...
+ const { flags } = await this.make(this.getCommand());
+
+ // Prepare...
+ const wallets = await this.createWalletsWithBalance(flags);
+
+ // Sign...
+ const transactions = await this.signTransactions(flags, wallets);
+
+ // Expect...
+ if (!flags.skipProbing) {
+ await this.expectBalances(transactions, wallets);
+ }
+
+ // Send...
+ await this.broadcastTransactions(transactions);
+
+ // Verify...
+ if (!flags.skipProbing) {
+ await this.verifyTransactions(transactions, wallets);
+ }
+
+ // Return...
+ return wallets;
+ }
+
+ protected abstract getCommand(): Promise;
+
+ protected abstract async createWalletsWithBalance(flags: Record): Promise;
+
+ protected abstract async signTransactions(flags: Record, wallets: Record): Promise;
+
+ protected abstract async expectBalances(transactions, wallets): Promise;
+
+ protected abstract async verifyTransactions(transactions, wallets): Promise;
+}
diff --git a/packages/core-tester-cli/src/signer.ts b/packages/core-tester-cli/src/signer.ts
new file mode 100644
index 0000000000..e1e7048a81
--- /dev/null
+++ b/packages/core-tester-cli/src/signer.ts
@@ -0,0 +1,82 @@
+import { bignumify } from "@arkecosystem/core-utils";
+import { formatSatoshi } from "@arkecosystem/crypto";
+import { client } from "@arkecosystem/crypto";
+
+export class Signer {
+ protected network: Record;
+
+ public constructor(network) {
+ this.network = network;
+ }
+
+ public makeTransfer(opts: Record): any {
+ const transaction = client
+ .getBuilder()
+ .transfer()
+ .fee(this.toSatoshi(opts.transferFee))
+ .network(this.network.version)
+ .recipientId(opts.recipient)
+ .amount(this.toSatoshi(opts.amount));
+
+ if (opts.vendorField) {
+ transaction.vendorField(opts.vendorField);
+ }
+
+ transaction.sign(opts.passphrase);
+
+ if (opts.secondPassphrase) {
+ transaction.secondSign(opts.secondPassphrase);
+ }
+
+ return transaction.getStruct();
+ }
+
+ public makeDelegate(opts: Record): any {
+ const transaction = client
+ .getBuilder()
+ .delegateRegistration()
+ .fee(this.toSatoshi(opts.delegateFee))
+ .network(this.network.version)
+ .usernameAsset(opts.username)
+ .sign(opts.passphrase);
+
+ if (opts.secondPassphrase) {
+ transaction.secondSign(opts.secondPassphrase);
+ }
+
+ return transaction.getStruct();
+ }
+
+ public makeSecondSignature(opts: Record): any {
+ return client
+ .getBuilder()
+ .secondSignature()
+ .fee(this.toSatoshi(opts.signatureFee))
+ .network(this.network.version)
+ .signatureAsset(opts.secondPassphrase)
+ .sign(opts.passphrase)
+ .getStruct();
+ }
+
+ public makeVote(opts: Record): any {
+ const transaction = client
+ .getBuilder()
+ .vote()
+ .fee(this.toSatoshi(opts.voteFee))
+ .votesAsset([`+${opts.delegate}`])
+ .network(this.network.version)
+ .sign(opts.passphrase);
+
+ if (opts.secondPassphrase) {
+ transaction.secondSign(opts.secondPassphrase);
+ }
+
+ return transaction.getStruct();
+ }
+
+ private toSatoshi(value) {
+ return bignumify(value)
+ .times(1e8)
+ .toFixed();
+ }
+}
diff --git a/packages/core-tester-cli/src/utils.ts b/packages/core-tester-cli/src/utils.ts
index 66d5a5ff00..8ff6884a38 100644
--- a/packages/core-tester-cli/src/utils.ts
+++ b/packages/core-tester-cli/src/utils.ts
@@ -1,144 +1,17 @@
-import { bignumify } from "@arkecosystem/core-utils";
-import { Bignum, client, formatSatoshi } from "@arkecosystem/crypto";
-import axios from "axios";
-import pino from "pino";
+import clipboardy from "clipboardy";
-export const logger = pino({
- name: "core-tester-cli",
- safe: true,
- prettyPrint: true,
-});
-
-export function request(config) {
- const headers: any = {};
- if (config && config.network) {
- headers.nethash = config.network.nethash;
- headers.version = "2.1.0";
- headers.port = config.p2pPort;
- headers["Content-Type"] = "application/json";
- }
-
- return {
- get: async (endpoint, isP2P = false) => {
- const baseUrl = `${config.baseUrl}:${isP2P ? config.p2pPort : config.apiPort}`;
-
- return (await axios.get(baseUrl + endpoint, { headers })).data;
- },
- post: async (endpoint, data, isP2P = false) => {
- const baseUrl = `${config.baseUrl}:${isP2P ? config.p2pPort : config.apiPort}`;
-
- return (await axios.post(baseUrl + endpoint, data, { headers })).data;
- },
- };
+export function copyToClipboard(data) {
+ clipboardy.writeSync(JSON.stringify(data));
}
-export async function paginate(config, endpoint) {
- const data = [];
- let page = 1;
- let maxPages = null;
- while (maxPages === null || page <= maxPages) {
- const response = await request(config).get(`${endpoint}?page=${page}`);
- if (response) {
- page++;
- maxPages = response.meta.pageCount;
- data.push(...response.data);
- } else {
- break;
- }
+export function handleOutput(opts, data) {
+ if (opts.copy) {
+ return copyToClipboard(data);
}
- return data;
-}
-
-/**
- * Generate batch of transactions based on wallets.
- */
-export function generateTransactions(
- amountPerTransaction: any,
- wallets: any[],
- approvalWallets: any[],
- options: {
- config: any;
- overridePassphrase?: false;
- vendorField?: string;
- log?: boolean;
- [key: string]: any;
- },
-) {
- const transactions = [];
- wallets.forEach((wallet, i) => {
- const builder = client.getBuilder().transfer();
- // noinspection JSCheckFunctionSignatures
- builder
- .fee(this.parseFee(options.transferFee))
- .recipientId(options.recipient || wallet.address)
- .network(options.config.network.version)
- .amount(amountPerTransaction)
- .vendorField(options.vendorField === undefined ? `Transaction ${i + 1}` : options.vendorField)
- .sign(options.overridePassphrase ? options.config.passphrase : wallet.passphrase);
-
- if (wallet.secondPassphrase || options.config.secondPassphrase) {
- builder.secondSign(wallet.secondPassphrase || options.config.secondPassphrase);
- }
-
- if (approvalWallets) {
- for (let j = approvalWallets.length - 1; j >= 0; j--) {
- builder.multiSignatureSign(approvalWallets[j].passphrase);
- }
- }
-
- const transaction = builder.build();
- transactions.push(transaction);
-
- if (options.log) {
- logger.info(
- `${i} ==> ${transaction.id}, ${transaction.data.recipientId} (fee: ${satoshiToArk(
- transaction.data.fee,
- )})`,
- );
- }
- });
-
- return transactions;
-}
-
-/**
- * Parse fee based on input.
- * @param {(String|Number)} fee
- * @return {Bignum}
- */
-export function parseFee(fee): Bignum {
- if (typeof fee === "string" && fee.indexOf("-") !== -1) {
- const feeRange = fee.split("-").map(
- f =>
- +bignumify(f)
- .times(1e8)
- .toFixed(),
- );
- if (feeRange[1] < feeRange[0]) {
- return bignumify(feeRange[0]);
- }
-
- return bignumify(Math.floor(Math.random() * (feeRange[1] - feeRange[0] + 1) + feeRange[0]));
+ if (opts.log) {
+ return console.log(data);
}
- return bignumify(fee).times(1e8);
-}
-
-/**
- * Convert ARK to Satoshi.
- * @param {Number} ark
- * @return {Bignum}
- */
-export function arkToSatoshi(ark) {
- return bignumify(ark * 1e8);
-}
-
-/**
- * Convert Satoshi to ARK.
- * @param {Bignum} satoshi
- * @return {String}
- */
-export function satoshiToArk(satoshi) {
- return formatSatoshi(satoshi);
+ return data;
}
diff --git a/yarn.lock b/yarn.lock
index 30ae170e9e..0dff240e95 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2024,13 +2024,6 @@
dependencies:
"@types/lodash" "*"
-"@types/lodash.fill@^3.4.4":
- version "3.4.4"
- resolved "https://registry.yarnpkg.com/@types/lodash.fill/-/lodash.fill-3.4.4.tgz#c54608d7da691142bf281134149b6ecf0d1f701b"
- integrity sha512-D2c164uS5YG3OYmalDFW3yXlhq3DmFE8y1EdQ9MqQ9VPFBD9+73GMzZxRAdG4/G8O3ZNeERkRGXMJCgsWi7c6A==
- dependencies:
- "@types/lodash" "*"
-
"@types/lodash.flatten@^4.4.4":
version "4.4.4"
resolved "https://registry.yarnpkg.com/@types/lodash.flatten/-/lodash.flatten-4.4.4.tgz#7f28009ef57c8d2b1d8463c3e53fdccf780120a5"
@@ -8513,11 +8506,6 @@ lodash.compact@^3.0.1:
resolved "https://registry.yarnpkg.com/lodash.compact/-/lodash.compact-3.0.1.tgz#540ce3837745975807471e16b4a2ba21e7256ca5"
integrity sha1-VAzjg3dFl1gHRx4WtKK6IeclbKU=
-lodash.fill@^3.4.0:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/lodash.fill/-/lodash.fill-3.4.0.tgz#a3c74ae640d053adf0dc2079f8720788e8bfef85"
- integrity sha1-o8dK5kDQU63w3CB5+HIHiOi/74U=
-
lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
@@ -10594,6 +10582,13 @@ podium@3.x.x:
hoek "6.x.x"
joi "14.x.x"
+pokemon@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/pokemon/-/pokemon-1.2.3.tgz#2b494263606408df10f6c499c299e9c2446ac05c"
+ integrity sha512-3wwtG0QKvkje3umDTnaNbPNLmL3XoxdjQwRMz3WEcA/txEagzmWHB1H1TwPNCgZvjIsTrkX7j+DK5Y9+Eu9Xyg==
+ dependencies:
+ unique-random-array "^1.0.0"
+
port-numbers@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/port-numbers/-/port-numbers-4.0.4.tgz#fe1c1fa7cd551f4ceb835b3bbf88c07baa0783d7"