Skip to content

Commit

Permalink
Merge pull request #38 from armada-network/keydesc
Browse files Browse the repository at this point in the history
Support optional key description
  • Loading branch information
the-masthead authored Nov 5, 2022
2 parents 91fcc61 + 353c73f commit 165f2eb
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 27 deletions.
9 changes: 6 additions & 3 deletions src/commands/key/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ export default class KeyDelete extends Command {
public async run(): Promise<void> {
const { args } = await this.parse(KeyDelete);
if (!args.ADDR) {
const addresses = await listWallets();
if (!addresses.length) {
const wallets = await listWallets();
if (!wallets.length) {
throw Error("Error: No private keys found.");
}

const res = await inquirer.prompt({
name: "address",
message: "Pick the account to delete:",
type: "list",
choices: addresses,
choices: wallets.map((w) => ({
value: w.address,
name: w.description ? `${w.address} - ${w.description}` : w.address,
})),
});

args.ADDR = res.address;
Expand Down
52 changes: 52 additions & 0 deletions src/commands/key/edit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Command } from "@oclif/core";
import { Arg } from "@oclif/core/lib/interfaces";
import inquirer from "inquirer";
import { listWallets, loadWallet, readWallet, updateWallet } from "../../keystore";

export default class KeyEdit extends Command {
static description = "Changes the optional key description.";
static examples = ["<%= config.bin %> <%= command.id %>"];
static usage = "<%= command.id %>";
static args: Arg[] = [
{ name: "ADDR", description: "The address of the key to describe." },
{ name: "DESC", description: "The new description for the key." },
];

public async run(): Promise<void> {
const { args } = await this.parse(KeyEdit);
if (!!args.ADDR !== !!args.DESC) {
this.error("ADDR and DESC must be specified together");
}

if (!args.ADDR) {
const wallets = await listWallets();
if (!wallets.length) {
throw Error("Error: No private keys found.");
}

const res1 = await inquirer.prompt({
name: "address",
message: "Pick the account to describe:",
type: "list",
choices: wallets.map((w) => ({
value: w.address,
name: w.description ? `${w.address} - ${w.description}` : w.address,
})),
});

const json = await readWallet(res1.address);
const res2 = await inquirer.prompt({
name: "description",
message: "Enter the key description:",
default: json.description,
});

args.ADDR = res1.address;
args.DESC = res2.description;
}

const address = args.ADDR;
await updateWallet(address, args.DESC);
console.log(`Account ${address} updated`);
}
}
5 changes: 3 additions & 2 deletions src/commands/key/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ export default class KeyImport extends Command {
await this.parse(KeyImport);
const responses = await inquirer.prompt([
{ name: "privateKey", message: "Paste the private key to import:", type: "password", mask: "*" },
{ name: "password", message: "Enter a key encryption password:", type: "password", mask: "*" },
{ name: "password", message: "Create a key encryption password:", type: "password", mask: "*" },
{ name: "description", message: "Enter an optional key description:" },
]);

const address = await saveWallet(responses.privateKey, responses.password);
const address = await saveWallet(responses.privateKey, responses.password, responses.description);
await loadWallet(address, responses.password);
console.log(`Account ${address} imported`);
}
Expand Down
7 changes: 4 additions & 3 deletions src/commands/key/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ export default class KeyList extends Command {

public async run(): Promise<void> {
await this.parse(KeyList);
const keys = await listWallets();
for (let i = 0; i < keys.length; ++i) {
console.log(keys[i]);
const wallets = await listWallets();
for (let i = 0; i < wallets.length; ++i) {
const w = wallets[i];
console.log(w.description ? `${w.address} - ${w.description}` : w.address);
}
}
}
13 changes: 8 additions & 5 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,25 @@ export async function getSigner(
wallet = wallet.connect(provider);
} else {
if (!address) {
const addresses = await listWallets();
if (!addresses.length) {
const wallets = await listWallets();
if (!wallets.length) {
throw Error("Error: No private keys found. Use key import command.");
}

const res = await inquirer.prompt({
name: "address",
message: "Pick the wallet to sign the transaction:",
type: "list",
choices: addresses,
choices: wallets.map((w) => ({
value: w.address,
name: w.description ? `${w.address} - ${w.description}` : w.address,
})),
});

address = res.address as string;
} else {
const addresses = await listWallets();
if (!addresses.includes(address)) {
const wallets = await listWallets();
if (!wallets.find((w) => w.address === address)) {
throw Error(`No saved key for address ${address}`);
}
}
Expand Down
44 changes: 30 additions & 14 deletions src/keystore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,65 @@ import keytar from "keytar";
import { normalizeHash } from "./helpers";

const homedir = os.homedir();
const keyStoreFolderPath = path.join(homedir, ".armada");
const keyStoreFolderPath = path.join(homedir, ".armada/keystore");

export async function saveWallet(privateKey: string, password: string): Promise<string> {
export async function saveWallet(privateKey: string, password: string, description: string): Promise<string> {
privateKey = normalizeHash(privateKey);
const wallet = new Wallet(privateKey);
const address = wallet.address;
const json = await encryptKeystore({ address, privateKey }, password);
const filename = `keystore_${address}.json`;

const text = await encryptKeystore({ address, privateKey }, password);
const json = { ...JSON.parse(text), description };
const filename = `${address}.json`;
if (!fs.existsSync(keyStoreFolderPath)) {
fs.mkdirSync(keyStoreFolderPath);
}

fs.writeFileSync(path.join(keyStoreFolderPath, filename), json);
fs.writeFileSync(path.join(keyStoreFolderPath, filename), JSON.stringify(json, null, " "));
keytar.setPassword("armada-cli", address, password);
return address;
}

export async function loadWallet(address: string, password: string): Promise<Wallet> {
const filename = `keystore_${address}.json`;
const json = fs.readFileSync(path.join(keyStoreFolderPath, filename), "utf8");
const wallet = await Wallet.fromEncryptedJson(json, password);
const filename = `${address}.json`;
const text = fs.readFileSync(path.join(keyStoreFolderPath, filename), "utf8");
const wallet = await Wallet.fromEncryptedJson(text, password);
return wallet;
}

export async function readWallet(address: string): Promise<{ description: string }> {
const filename = `${address}.json`;
const text = fs.readFileSync(path.join(keyStoreFolderPath, filename), "utf8");
return JSON.parse(text);
}

export async function deleteWallet(address: string): Promise<void> {
const filename = `keystore_${address}.json`;
const filename = `${address}.json`;
fs.unlinkSync(path.join(keyStoreFolderPath, filename));
}

export async function listWallets(): Promise<string[]> {
export async function updateWallet(address: string, description: string): Promise<void> {
const filename = `${address}.json`;
const pathname = path.join(keyStoreFolderPath, filename);
const text = fs.readFileSync(pathname, "utf8");
const json = { ...JSON.parse(text), description };
fs.writeFileSync(pathname, JSON.stringify(json, null, " "));
}

export async function listWallets(): Promise<{ address: string; description: string }[]> {
if (!fs.existsSync(keyStoreFolderPath)) {
return [];
}

const wallets: string[] = [];
const wallets: { address: string; description: string }[] = [];
const files = fs.readdirSync(keyStoreFolderPath);
for (const filename of files) {
if (!filename.match(/keystore_.*\.json/)) {
if (!filename.match(/.*\.json/)) {
continue;
}

wallets.push(filename.split("_")[1].split(".")[0]);
const text = fs.readFileSync(path.join(keyStoreFolderPath, filename), "utf8");
const json = JSON.parse(text);
wallets.push({ address: filename.split(".")[0], description: json.description });
}

return wallets;
Expand Down

0 comments on commit 165f2eb

Please sign in to comment.