Skip to content

Commit

Permalink
Add Hedera Consensus Service and small improvements - Closes #183 #214
Browse files Browse the repository at this point in the history
…#215 (#217)

* Add topic create and private topics

Signed-off-by: Michiel Mulders <[email protected]>

* Add demo script for topic create

Signed-off-by: Michiel Mulders <[email protected]>

* Add topic submit feature

Signed-off-by: Michiel Mulders <[email protected]>

* Add message retrieve feature

Signed-off-by: Michiel Mulders <[email protected]>

* Add docs for topic features

Signed-off-by: Michiel Mulders <[email protected]>

* Update clear command to clear topics

Signed-off-by: Michiel Mulders <[email protected]>

* Update download state and clear state commands to handle topics

Signed-off-by: Michiel Mulders <[email protected]>

* Add dummy test for topic

Signed-off-by: Michiel Mulders <[email protected]>

* Update state clear for topics and add single-test command in package.json

Signed-off-by: Michiel Mulders <[email protected]>

* Add e2e tests for topics

Signed-off-by: Michiel Mulders <[email protected]>

* Update husky hook for pre-push

Signed-off-by: Michiel Mulders <[email protected]>

* Update husky hook for pre-push

Signed-off-by: Michiel Mulders <[email protected]>

* Linting changes

Signed-off-by: Michiel Mulders <[email protected]>

* Add filter options and tests

Signed-off-by: Michiel Mulders <[email protected]>

* Add e2e test for filters

Signed-off-by: Michiel Mulders <[email protected]>

* Update mock for account create test

Signed-off-by: Michiel Mulders <[email protected]>

* Update mock for token create test

Signed-off-by: Michiel Mulders <[email protected]>

* Add token associate test

Signed-off-by: Michiel Mulders <[email protected]>

* Add token transfer test

Signed-off-by: Michiel Mulders <[email protected]>

* Update state.json

Signed-off-by: Michiel Mulders <[email protected]>

* Fix balance test with mock state

Signed-off-by: Michiel Mulders <[email protected]>

* update base state

Signed-off-by: Michiel Mulders <[email protected]>

---------

Signed-off-by: Michiel Mulders <[email protected]>
  • Loading branch information
michielmulders authored Feb 2, 2024
1 parent d32a24d commit 1ba014c
Show file tree
Hide file tree
Showing 43 changed files with 1,521 additions and 78 deletions.
3 changes: 2 additions & 1 deletion .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
npm run test
node clear-state.js
node clear-state.js
git add .
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ Let's explore the different commands, their options, and outputs.
- [Wait Commmand](#wait-command): Wait for a specified amount of seconds
- [Account Commands](#account-commands): Create and manage accounts
- [Token Commands](#token-commands): Create and manage tokens
- [Topic Commands](#topic-commands): Create and manage topics
- [Hbar Command](#hbar-command): Transfer Hbars between accounts
- [Backup Commands](#backup-commands): Create a backup of your state
- [Record Commands](#record-commands): Record CLI interactions and store it in scripts
Expand Down Expand Up @@ -442,6 +443,66 @@ Flags:
- **From:** (required) Account ID to transfer the token from.
- **Balance:** (required) Amount of token to transfer.

## Topic Commands

### Overview

The `topic` command in the Hedera CLI tool provides functionality for creating topics and retrieving information about topics on the Hedera network.

```
topic create
topic list
topic message submit
topic message find
```

#### Usage

**1. Create Topic:**

Creates a new topic with a specified memo, submit key, and admin key. If you don't provide any options, a public topic will be generated. Setting the submit key creates a private topic. If you don't set an admin key, the topic is immutable.

```sh
hcli topic create [-s, --submit-key <submitKey>] [-a, --admin-key <adminKey>] [--memo <memo>]
```

Flags:
- **Submit Key:** (optional) Submit key for the topic.
- **Admin Key:** (optional) Admin key for the topic.
- **Memo:** (optional) Memo for the topic (100 bytes).

**2. List Topics:**

Lists all topics on the Hedera network known by the CLI tool.

```sh
hcli topic list
```

**3. Submit Message to Topic:**

Submits a message to a specified topic.

```sh
hcli topic message submit -t,--topic-id <topicId> -m,--message <message>
```

Flags:
- **Topic ID:** (required) Topic ID to submit the message to.
- **Message:** (required) Message to submit to the topic.

**4. Find Messages for Topic:**

Finds messages for a specified topic by its sequence number.

```sh
hcli topic message find -t,--topic-id <topicId> -s,--sequence-number <sequenceNumber>
```

Flags:
- **Topic ID:** (required) Topic ID to find the message for.
- **Sequence Number:** (required) Sequence number of the message you want to find.

## Hbar Command

### Overview
Expand Down
12 changes: 10 additions & 2 deletions __tests__/commands/account/balance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ import accountUtils from "../../../src/utils/account";
import api from "../../../src/api";

import { accountResponse, getAccountInfoResponseMock } from "../../helpers/api/apiAccountHelper";
import { baseState } from "../../helpers/state";
import stateController from "../../../src/state/stateController";

jest.mock('../../../src/state/state'); // Mock the original module -> looks for __mocks__/state.ts in same directory

describe("account balance command", () => {
const logSpy = jest.spyOn(console, 'log');
const getAccountBalanceSpy = jest.spyOn(accountUtils, "getAccountBalance");

describe("account balance - success path", () => {
beforeEach(() => {
stateController.saveState(baseState);
});

afterEach(() => {
// Spy cleanup
logSpy.mockClear();
Expand All @@ -24,7 +32,7 @@ describe("account balance command", () => {
commands.accountCommands(program);

// Act
await program.parse(["node", "hedera-cli.ts", "account", "balance", "-a", accountResponse.account, "--only-hbar"]);
await program.parseAsync(["node", "hedera-cli.ts", "account", "balance", "-a", accountResponse.account, "--only-hbar"]);

// Assert
expect(getAccountBalanceSpy).toHaveBeenCalledWith(accountResponse.account, true, undefined);
Expand All @@ -40,7 +48,7 @@ describe("account balance command", () => {
commands.accountCommands(program);

// Act
await program.parse(["node", "hedera-cli.ts", "account", "balance", "-a", accountResponse.account, "--token-id", accountResponse.balance.tokens[0].token_id]);
await program.parseAsync(["node", "hedera-cli.ts", "account", "balance", "-a", accountResponse.account, "--token-id", accountResponse.balance.tokens[0].token_id]);

// Assert
expect(getAccountBalanceSpy).toHaveBeenCalledWith(accountResponse.account, undefined, accountResponse.balance.tokens[0].token_id);
Expand Down
18 changes: 18 additions & 0 deletions __tests__/commands/account/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,26 @@ import { Command } from "commander";
import commands from "../../../src/commands";
import accountUtils from "../../../src/utils/account";
import stateController from "../../../src/state/stateController";
import { AccountId } from "@hashgraph/sdk";

jest.mock("../../../src/state/state"); // Mock the original module -> looks for __mocks__/state.ts in same directory
jest.mock('@hashgraph/sdk', () => {
const originalModule = jest.requireActual('@hashgraph/sdk');

return {
...originalModule,
AccountCreateTransaction: jest.fn().mockImplementation(() => ({
setKey: jest.fn().mockReturnThis(),
setInitialBalance: jest.fn().mockReturnThis(),
setMaxAutomaticTokenAssociations: jest.fn().mockReturnThis(),
execute: jest.fn().mockResolvedValue({
getReceipt: jest.fn().mockResolvedValue({
accountId: AccountId.fromString('0.0.1234'),
})
}),
})),
};
});

describe("account create command", () => {
beforeEach(() => {
Expand Down
1 change: 1 addition & 0 deletions __tests__/commands/state/clear.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ describe("state clear command", () => {
// Assert
expect(saveKeyStateControllerSpy).toHaveBeenCalledWith('accounts', {});
expect(saveKeyStateControllerSpy).toHaveBeenCalledWith('tokens', {});
expect(saveKeyStateControllerSpy).toHaveBeenCalledWith('topics', {});
expect(stateController.getAll()).toEqual(scriptState);
});

Expand Down
1 change: 0 additions & 1 deletion __tests__/commands/state/download.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
downloadState,
script_basic,
accountState,
token,
} from '../../helpers/state';
import { Command } from 'commander';
import commands from '../../../src/commands';
Expand Down
64 changes: 64 additions & 0 deletions __tests__/commands/token/associate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { alice, tokenState } from '../../helpers/state';
import { Command } from 'commander';
import commands from '../../../src/commands';
import stateController from '../../../src/state/stateController';

let tokenId = Object.keys(tokenState.tokens)[0];
jest.mock('../../../src/state/state'); // Mock the original module -> looks for __mocks__/state.ts in same directory
jest.mock('@hashgraph/sdk', () => {
const originalModule = jest.requireActual('@hashgraph/sdk');

return {
...originalModule,
TokenAssociateTransaction: jest.fn().mockImplementation(() => ({
setAccountId: jest.fn().mockReturnThis(),
setTokenIds: jest.fn().mockReturnThis(),
sign: jest.fn().mockReturnThis(),
freezeWith: jest.fn().mockReturnThis(),
execute: jest.fn().mockResolvedValue({
getReceipt: jest.fn().mockResolvedValue({}),
}),
})),
};
});

describe('token associate command', () => {
beforeEach(() => {
const tokenStateWithAlice = {
...tokenState,
accounts: {
[alice.alias]: alice,
},
};
stateController.saveState(tokenStateWithAlice);
});

describe('token associate - success path', () => {
test('✅ ', async () => {
// Arrange
const program = new Command();
commands.tokenCommands(program);

// Act
await program.parseAsync([
'node',
'hedera-cli.ts',
'token',
'associate',
'-a',
alice.accountId,
'-t',
tokenId,
]);

// Assert
const tokens = stateController.get('tokens');
expect(tokens[tokenId].associations).toEqual([
{
alias: alice.alias,
accountId: alice.accountId,
},
]);
});
});
});
135 changes: 96 additions & 39 deletions __tests__/commands/token/create.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import { alice, bob, baseState } from "../../helpers/state";
import { Command } from "commander";
import commands from "../../../src/commands";
import stateController from "../../../src/state/stateController";
import { alice, bob, baseState } from '../../helpers/state';
import { Command } from 'commander';
import commands from '../../../src/commands';
import stateController from '../../../src/state/stateController';

jest.mock("../../../src/state/state"); // Mock the original module -> looks for __mocks__/state.ts in same directory
import { TokenId } from '@hashgraph/sdk';
import { Token } from '../../../types';

describe("token create command", () => {
const mockProcessExit = jest.spyOn(process, 'exit').mockImplementation(((code) => {
throw new Error(`Process.exit(${code})`); // Forces the code to throw instead of exit
}));
let tokenId = '0.0.1234';
jest.mock('../../../src/state/state'); // Mock the original module -> looks for __mocks__/state.ts in same directory
jest.mock('@hashgraph/sdk', () => {
const originalModule = jest.requireActual('@hashgraph/sdk');

return {
...originalModule,
TokenCreateTransaction: jest.fn().mockImplementation(() => ({
setTokenName: jest.fn().mockReturnThis(),
setTokenSymbol: jest.fn().mockReturnThis(),
setDecimals: jest.fn().mockReturnThis(),
setInitialSupply: jest.fn().mockReturnThis(),
setTokenType: jest.fn().mockReturnThis(),
setSupplyType: jest.fn().mockReturnThis(),
setTreasuryAccountId: jest.fn().mockReturnThis(),
setAdminKey: jest.fn().mockReturnThis(),
sign: jest.fn().mockReturnThis(),
freezeWith: jest.fn().mockReturnThis(),
execute: jest.fn().mockResolvedValue({
getReceipt: jest.fn().mockResolvedValue({
tokenId: TokenId.fromString(tokenId),
}),
}),
})),
};
});

describe('token create command', () => {
const mockProcessExit = jest
.spyOn(process, 'exit')
.mockImplementation((code) => {
throw new Error(`Process.exit(${code})`); // Forces the code to throw instead of exit
});

const saveKeyStateControllerSpy = jest.spyOn(stateController, 'saveKey');

Expand All @@ -22,43 +52,70 @@ describe("token create command", () => {
saveKeyStateControllerSpy.mockClear();
});

describe("token create - success path", () => {
test("✅ ", async () => {
describe('token create - success path', () => {
test('✅ ', async () => {
// Arrange
const program = new Command();
commands.tokenCommands(program);
const tokenName = 'test-token';
const tokenSymbol = 'TST';
const tokenSupplyType = 'infinite';
const totalSupply = 1000;
const decimals = 2;

// Act
try {
await program.parseAsync([
"node",
"hedera-cli.ts",
"token",
"create",
"-t",
alice.accountId,
"-k",
alice.privateKey,
"-n",
"test-token",
"-s",
"TST",
"-d",
"2",
"-i",
"1000",
"--supply-type",
"infinite",
"-a",
bob.privateKey
]);
} catch (error) {
expect(error).toEqual(Error(`Process.exit(1)`));
}
await program.parseAsync([
'node',
'hedera-cli.ts',
'token',
'create',
'-t',
alice.accountId,
'-k',
alice.privateKey,
'-n',
tokenName,
'-s',
tokenSymbol,
'-d',
decimals.toString(),
'-i',
totalSupply.toString(),
'--supply-type',
tokenSupplyType,
'-a',
bob.privateKey,
]);

// Assert
expect(Object.keys(stateController.get('tokens')).length).toEqual(1);
expect(saveKeyStateControllerSpy).toHaveBeenCalledWith('tokens', expect.any(Object));
const tokens = stateController.get('tokens');
expect(Object.keys(tokens).length).toEqual(1);
expect(tokens[tokenId]).toEqual({
tokenId: tokenId,
name: tokenName,
symbol: tokenSymbol,
decimals: decimals,
initialSupply: totalSupply,
supplyType: tokenSupplyType.toUpperCase(),
treasuryId: alice.accountId,
associations: [],
maxSupply: tokenSupplyType.toUpperCase() === 'FINITE' ? totalSupply : 0,
keys: {
treasuryKey: alice.privateKey,
adminKey: bob.privateKey,
supplyKey: '',
wipeKey: '',
kycKey: '',
freezeKey: '',
pauseKey: '',
feeScheduleKey: '',
},
network: 'testnet',
} as Token);
expect(saveKeyStateControllerSpy).toHaveBeenCalledWith(
'tokens',
expect.any(Object),
);
});
});
});
Loading

0 comments on commit 1ba014c

Please sign in to comment.