From 148b6f8e74f6bc2145d0b84c86c1dafbd71bc171 Mon Sep 17 00:00:00 2001 From: mixmix Date: Tue, 1 Oct 2024 17:04:46 +1300 Subject: [PATCH 01/15] WIP: QA file-restructure --- src/account/command.ts | 12 ++++++++-- src/balance/command.ts | 19 ++++++++++----- src/common/utils-cli.ts | 7 +++--- src/config/encoding.ts | 12 +++++++--- src/sign/command.ts | 4 ++-- src/transfer/command.ts | 21 ++++++++++++---- tests/cli.test.sh | 53 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 107 insertions(+), 21 deletions(-) create mode 100755 tests/cli.test.sh diff --git a/src/account/command.ts b/src/account/command.ts index a502a850..59fa8745 100644 --- a/src/account/command.ts +++ b/src/account/command.ts @@ -13,6 +13,9 @@ export function entropyAccountCommand () { .addCommand(entropyAccountImport()) .addCommand(entropyAccountList()) .addCommand(entropyAccountRegister()) + // .addCommand(entropyAccountAlias()) + // IDEA: support aliases for remote accounts (those we don't have seeds for) + // this would make transfers safer/ easier from CLI } function entropyAccountCreate () { @@ -84,9 +87,9 @@ function entropyAccountList () { function entropyAccountRegister () { return new Command('register') .description('Register an entropy account with a program') - .addOption(passwordOption()) - .addOption(endpointOption()) .addOption(accountOption()) + .addOption(endpointOption()) + .addOption(passwordOption()) // Removing these options for now until we update the design to accept program configs // .addOption( // new Option( @@ -101,11 +104,16 @@ function entropyAccountRegister () { // ) // ) .action(async (opts) => { + console.log('here 0') + console.log(opts) // NOTE: loadEntropy throws if it can't find opts.account const entropy: Entropy = await loadEntropy(opts.account, opts.endpoint) const accountService = new EntropyAccount(entropy, opts.endpoint) + console.log('here 1') + const verifyingKey = await accountService.register() + console.log('here 2') await addVerifyingKeyToAccountAndSelect(verifyingKey, opts.account) cliWrite(verifyingKey) diff --git a/src/balance/command.ts b/src/balance/command.ts index 4d0b023a..ad60000a 100644 --- a/src/balance/command.ts +++ b/src/balance/command.ts @@ -1,22 +1,29 @@ import { Command } from "commander"; import Entropy from "@entropyxyz/sdk"; -import { cliWrite, endpointOption, loadEntropy, passwordOption } from "src/common/utils-cli"; + import { EntropyBalance } from "./main"; +import { cliWrite, endpointOption, loadEntropy, passwordOption } from "../common/utils-cli"; +import { findAccountByAddressOrName } from "../common/utils"; +import * as config from "../config"; export function entropyBalanceCommand () { const balanceCommand = new Command('balance') balanceCommand .description('Command to retrieive the balance of an account on the Entropy Network') - .argument('address', 'Account address whose balance you want to query') - .addOption(passwordOption()) + .argument('account ', 'Account address whose balance you want to query') .addOption(endpointOption()) - .action(async (address, opts) => { - const entropy: Entropy = await loadEntropy(address, opts.endpoint) + .addOption(passwordOption()) + .action(async (account, opts) => { + const entropy: Entropy = await loadEntropy(account, opts.endpoint) const BalanceService = new EntropyBalance(entropy, opts.endpoint) + + const { accounts } = await config.get() + const address = findAccountByAddressOrName(accounts, account)?.address + const balance = await BalanceService.getBalance(address) cliWrite(`${balance.toLocaleString('en-US')} BITS`) process.exit(0) }) - + return balanceCommand } diff --git a/src/common/utils-cli.ts b/src/common/utils-cli.ts index 3aec350b..f185cd05 100644 --- a/src/common/utils-cli.ts +++ b/src/common/utils-cli.ts @@ -20,7 +20,7 @@ function getConfigOrNull () { export function endpointOption () { return new Option( - '-e, --endpoint ', + '-e, --endpoint ', [ 'Runs entropy with the given endpoint and ignores network endpoints in config.', 'Can also be given a stored endpoint name from config eg: `entropy --endpoint test-net`.' @@ -44,16 +44,17 @@ export function endpointOption () { export function passwordOption (description?: string) { return new Option( - '-p, --password ', + '-p, --password', description || 'Password for the account' ) + .hideHelp() // TEMP } export function accountOption () { const storedConfig = getConfigOrNull() return new Option( - '-a, --account ', + '-a, --account ', [ 'Sets the account for the session.', 'Defaults to the last set account (or the first account if one has not been set before).' diff --git a/src/config/encoding.ts b/src/config/encoding.ts index cb580655..e29422d6 100644 --- a/src/config/encoding.ts +++ b/src/config/encoding.ts @@ -1,12 +1,18 @@ const PREFIX = 'data:application/UI8A;base64,' // was a UInt8Array, but is stored as base64 -export function serialize (config) { +export function serialize (config: object) { return JSON.stringify(config, replacer, 2) } -export function deserialize (config) { - return JSON.parse(config, reviver) +export function deserialize (config: string) { + try { + return JSON.parse(config, reviver) + } catch (err) { + console.log('broken config:', config) + // WIP here: nothing being passed in?!! + throw err + } } function replacer (_key: string, value: any) { diff --git a/src/sign/command.ts b/src/sign/command.ts index fc574228..87c2ed70 100644 --- a/src/sign/command.ts +++ b/src/sign/command.ts @@ -6,9 +6,9 @@ export function entropySignCommand () { const signCommand = new Command('sign') .description('Sign a message using the Entropy network. Output is a JSON { verifyingKey, signature }') .argument('msg', 'Message you would like to sign (string)') - .addOption(passwordOption('Password for the source account (if required)')) - .addOption(endpointOption()) .addOption(accountOption()) + .addOption(endpointOption()) + .addOption(passwordOption('Password for the source account (if required)')) // .addOption( // new Option( // '-r, --raw', diff --git a/src/transfer/command.ts b/src/transfer/command.ts index b814e2f9..db3bf157 100644 --- a/src/transfer/command.ts +++ b/src/transfer/command.ts @@ -1,20 +1,31 @@ import { Command } from "commander" -import { accountOption, endpointOption, loadEntropy, passwordOption } from "src/common/utils-cli" +import { accountOption, endpointOption, loadEntropy, passwordOption } from "../common/utils-cli" import { EntropyTransfer } from "./main" export function entropyTransferCommand () { - const transferCommand = new Command('tranfer') + const transferCommand = new Command('transfer') transferCommand .description('Transfer funds between two Entropy accounts.') // TODO: name the output .argument('destination', 'Account address funds will be sent to') - .argument('amount', 'Amount of funds to be moved') - .addOption(passwordOption('Password for the source account (if required)')) - .addOption(endpointOption()) + .argument('amount', 'Amount of funds to be moved (in "tokens")') .addOption(accountOption()) + .addOption(endpointOption()) + .addOption(passwordOption('Password for the source account (if required)')) .action(async (destination, amount, opts) => { + console.log({ destination, amount, opts }) + + // TODO: destination as ? const entropy = await loadEntropy(opts.account, opts.endpoint) + .catch(err => { + // WIP here. SOMETHING is wrecking the config upstream + console.error("loadEntropy failed", err) + throw err + }) + const transferService = new EntropyTransfer(entropy, opts.endpoint) + await transferService.transfer(destination, amount) + // cliWrite(??) // TODO: write the output process.exit(0) }) diff --git a/tests/cli.test.sh b/tests/cli.test.sh new file mode 100755 index 00000000..0e2a0374 --- /dev/null +++ b/tests/cli.test.sh @@ -0,0 +1,53 @@ +#! /usr/bin/bash + +ENTROPY_ENDPOINT=ws://127.0.0.1:9944 + +rm ~/.config/entropy-cryptography/entropy-cli.json +# backup config +# mv ~/.config/entropy-cryptography/entropy-cli{.json,.backup.json} + +print () { + COLOR='\033[0;35m' + RESET='\033[0m' + echo "" + echo -e "${COLOR}> $1${RESET}" +} + +print "// ACCOUNT /////////////////////////////////////////////////" + +# Errors (correct, but messy?) +# print "account ls:" +# entropy account ls | jq + +print "account create" +entropy account create naynay | jq + +print "account import" +entropy account import faucet 0x358f394d157e31be23313a1500f5e2c8871e514e530a35aa5c05334be7a39ba6 | jq + +print "account list" +entropy account list | jq + + + +print "// BALANCE ///////////////////////////////////////////////// " + +print "balance naynay" +entropy balance naynay + +print "balance 5CqJyjALDFz4sKjQgK8NXBQGHCWAiV63xXn2Dye393Y6Vghz" +# entropy balance faucet +entropy balance 5CqJyjALDFz4sKjQgK8NXBQGHCWAiV63xXn2Dye393Y6Vghz + + + +print "// TRANSFER ////////////////////////////////////////////////" + +print "entropy transfer" +NAYNAY_ADDRESS=`entropy account ls | jq --raw-output ".[0].address"` +# NOTE: --raw-output is needed to drop the quotes +entropy transfer -a faucet ${NAYNAY_ADDRESS} 2.5 +entropy balance naynay + +# restore config +# mv ~/.config/entropy-cryptography/entropy-cli{.backup.json,.json} From 657e6fa85c6c6c3b10cd51602774d5411e7ed62d Mon Sep 17 00:00:00 2001 From: mixmix Date: Wed, 2 Oct 2024 11:49:17 +1300 Subject: [PATCH 02/15] fixes --- src/account/command.ts | 12 +++++------- src/account/interaction.ts | 5 +---- src/account/main.ts | 39 +++++++++++++++++++++++++++++++------- src/account/utils.ts | 11 ++++------- src/common/utils-cli.ts | 24 +++++++++++------------ src/config/index.ts | 17 ++++++++++++++++- src/config/types.ts | 1 + src/transfer/command.ts | 8 -------- src/tui.ts | 6 +----- tests/cli.test.sh | 7 +++---- 10 files changed, 75 insertions(+), 55 deletions(-) diff --git a/src/account/command.ts b/src/account/command.ts index 59fa8745..71645feb 100644 --- a/src/account/command.ts +++ b/src/account/command.ts @@ -76,8 +76,11 @@ function entropyAccountList () { .description('List all accounts. Output is JSON of form [{ name, address, verifyingKeys }]') .action(async () => { // TODO: test if it's an encrypted account, if no password provided, throw because later on there's no protection from a prompt coming up - const storedConfig = await config.get() - const accounts = EntropyAccount.list(storedConfig) + const accounts = await config.get() + .then(storedConfig => EntropyAccount.list(storedConfig)) + .catch(() => []) + // QUESTION: is dropping the error right? Maybe only if "There are currently no accounts" + cliWrite(accounts) process.exit(0) }) @@ -104,16 +107,11 @@ function entropyAccountRegister () { // ) // ) .action(async (opts) => { - console.log('here 0') - console.log(opts) // NOTE: loadEntropy throws if it can't find opts.account const entropy: Entropy = await loadEntropy(opts.account, opts.endpoint) const accountService = new EntropyAccount(entropy, opts.endpoint) - console.log('here 1') - const verifyingKey = await accountService.register() - console.log('here 2') await addVerifyingKeyToAccountAndSelect(verifyingKey, opts.account) cliWrite(verifyingKey) diff --git a/src/account/interaction.ts b/src/account/interaction.ts index bf1c599f..b26103dd 100644 --- a/src/account/interaction.ts +++ b/src/account/interaction.ts @@ -45,10 +45,7 @@ export async function entropyAccount (endpoint: string, storedConfig: EntropyCon return } const { selectedAccount } = await inquirer.prompt(accountSelectQuestions(accounts)) - await config.set({ - ...storedConfig, - selectedAccount: selectedAccount.address - }) + await config.setSelectedAccount(selectedAccount) print('Current selected account is ' + selectedAccount) return diff --git a/src/account/main.ts b/src/account/main.ts index c1395dcb..dac8ce83 100644 --- a/src/account/main.ts +++ b/src/account/main.ts @@ -19,19 +19,19 @@ export class EntropyAccount extends EntropyBase { return EntropyAccount.import({ name, seed, path }) } + // WARNING: #create depends on #import => be careful modifying this function static async import ({ name, seed, path }: AccountImportParams ): Promise { - // WARNING: #create currently depends on this => be careful modifying this function - await wasmGlobalsReady() const keyring = new Keyring({ seed, path, debug: true }) + const fullAccount = keyring.getAccount() // TODO: sdk should create account on constructor - const { admin } = keyring.getAccount() + const data = fixData(fullAccount) + // const encryptedData = password ? passwordFlow.encrypt(data, password) : data - const data = fullAccount + const { admin } = keyring.getAccount() delete admin.pair - // const encryptedData = password ? passwordFlow.encrypt(data, password) : data - + return { name, address: admin.address, @@ -90,7 +90,7 @@ export class EntropyAccount extends EntropyBase { dispatchError.asModule ) const { docs, name, section } = decoded - + msg = `${section}.${name}: ${docs.join(' ')}` } else { // Other, CannotLookup, BadOrigin, no extra info @@ -107,3 +107,28 @@ export class EntropyAccount extends EntropyBase { }) } } + +// TODO: there is a bug in SDK that is munting this data +function fixData (data) { + if (data.admin) { + data.admin.pair.addressRaw = objToUint8Array(data.admin.pair.addressRaw) + data.admin.pair.secretKey = objToUint8Array(data.admin.pair.secretKey) + data.admin.pair.publicKey = objToUint8Array(data.admin.pair.publicKey) + } + + if (data.registration) { + data.registration.pair.addressRaw = objToUint8Array(data.registration.pair.addressRaw) + data.registration.pair.secretKey = objToUint8Array(data.registration.pair.secretKey) + data.registration.pair.publicKey = objToUint8Array(data.registration.pair.publicKey) + } + + return data +} + +function objToUint8Array (obj) { + const values: any = Object.entries(obj) + .sort((a, b) => Number(a[0]) - Number(b[0])) // sort entries by keys + .map(entry => entry[1]) + + return new Uint8Array(values) +} diff --git a/src/account/utils.ts b/src/account/utils.ts index cf51603a..16e5e4cc 100644 --- a/src/account/utils.ts +++ b/src/account/utils.ts @@ -19,22 +19,19 @@ export async function selectAndPersistNewAccount (newAccount: EntropyAccountConf accounts.push(newAccount) await config.set({ ...storedConfig, - selectedAccount: newAccount.address + selectedAccount: newAccount.name }) } export async function addVerifyingKeyToAccountAndSelect (verifyingKey: string, accountNameOrAddress: string) { - const storedConfig = await config.get() - const account = findAccountByAddressOrName(storedConfig.accounts, accountNameOrAddress) + const { accounts } = await config.get() + const account = findAccountByAddressOrName(accounts, accountNameOrAddress) if (!account) throw Error(`Unable to persist verifyingKey "${verifyingKey}" to unknown account "${accountNameOrAddress}"`) account.data.registration.verifyingKeys.push(verifyingKey) // persist to config, set selectedAccount - await config.set({ - ...storedConfig, - selectedAccount: account.address - }) + await config.setSelectedAccount(account) } function validateSeedInput (seed) { diff --git a/src/common/utils-cli.ts b/src/common/utils-cli.ts index f185cd05..37e29ffc 100644 --- a/src/common/utils-cli.ts +++ b/src/common/utils-cli.ts @@ -39,7 +39,7 @@ export function endpointOption () { return endpoint }) .default('ws://testnet.entropy.xyz:9944/') - // NOTE: argParser is only run IF an option is provided, so this cannot be 'test-net' + // NOTE: argParser only runs IF the -e/--endpoint option called, so this default cannot be 'test-net' } export function passwordOption (description?: string) { @@ -61,20 +61,20 @@ export function accountOption () { ].join(' ') ) .env('ENTROPY_ACCOUNT') - .argParser(async (account) => { - if (storedConfig && storedConfig.selectedAccount !== account) { - // Updated selected account in config with new address from this option - await config.set({ - ...storedConfig, - selectedAccount: account - }) - } + .argParser(async (addressOrName) => { + // We try to map addressOrName to an account we have stored + if (!storedConfig) return addressOrName - return account + const account = findAccountByAddressOrName(storedConfig.accounts, addressOrName) + if (!account) return addressOrName + + // If we find one, we set this account as the future default + await config.setSelectedAccount(account) + + // We finally return the account name to be as consistent as possible (using name, not address) + return account.name }) .default(storedConfig?.selectedAccount) - // TODO: display the *name* not address - // TODO: standardise whether selectedAccount is name or address. } export async function loadEntropy (addressOrName: string, endpoint: string, password?: string): Promise { diff --git a/src/config/index.ts b/src/config/index.ts index 049b6775..93b6f351 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -6,7 +6,7 @@ import envPaths from 'env-paths' import allMigrations from './migrations' import { serialize, deserialize } from './encoding' -import { EntropyConfig } from './types' +import { EntropyConfig, EntropyAccountConfig } from './types' const paths = envPaths('entropy-cryptography', { suffix: '' }) const CONFIG_PATH = join(paths.config, 'entropy-cli.json') @@ -63,6 +63,7 @@ export async function get (configPath = CONFIG_PATH) { export function getSync (configPath = CONFIG_PATH) { const configStr = readFileSync(configPath, 'utf8') + // console.log('getSync', configPath, configStr) return deserialize(configStr) } @@ -73,6 +74,20 @@ export async function set (config: EntropyConfig, configPath = CONFIG_PATH) { await writeFile(configPath, serialize(config)) } +export async function setSelectedAccount (account: EntropyAccountConfig, configPath = CONFIG_PATH) { + const storedConfig = await get(configPath) + + if (storedConfig.selectedAccount === account.name) return storedConfig + // no need for update + + const newConfig = { + ...storedConfig, + selectedAccount: account.name + } + await set(newConfig, configPath) + return newConfig +} + /* util */ function noop () {} function assertConfigPath (configPath) { diff --git a/src/config/types.ts b/src/config/types.ts index 7d4deb78..66e1c6cc 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -4,6 +4,7 @@ export interface EntropyConfig { dev: string; 'test-net': string } + // selectedAccount is account.name (alias) for the account selectedAccount: string 'migration-version': string } diff --git a/src/transfer/command.ts b/src/transfer/command.ts index db3bf157..c3683553 100644 --- a/src/transfer/command.ts +++ b/src/transfer/command.ts @@ -12,16 +12,8 @@ export function entropyTransferCommand () { .addOption(endpointOption()) .addOption(passwordOption('Password for the source account (if required)')) .action(async (destination, amount, opts) => { - console.log({ destination, amount, opts }) - // TODO: destination as ? const entropy = await loadEntropy(opts.account, opts.endpoint) - .catch(err => { - // WIP here. SOMETHING is wrecking the config upstream - console.error("loadEntropy failed", err) - throw err - }) - const transferService = new EntropyTransfer(entropy, opts.endpoint) await transferService.transfer(destination, amount) diff --git a/src/tui.ts b/src/tui.ts index bd7780cf..f02d82da 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -19,11 +19,7 @@ async function setupConfig () { // set selectedAccount if we can if (!storedConfig.selectedAccount && storedConfig.accounts.length) { - await config.set({ - ...storedConfig, - selectedAccount: storedConfig.accounts[0].address - }) - storedConfig = await config.get() + storedConfig = await config.setSelectedAccount(storedConfig.accounts[0]) } return storedConfig diff --git a/tests/cli.test.sh b/tests/cli.test.sh index 0e2a0374..f22ee2c6 100755 --- a/tests/cli.test.sh +++ b/tests/cli.test.sh @@ -15,9 +15,9 @@ print () { print "// ACCOUNT /////////////////////////////////////////////////" -# Errors (correct, but messy?) -# print "account ls:" -# entropy account ls | jq +print "account ls" +entropy account ls | jq +# TODO: change this to return [] ? print "account create" entropy account create naynay | jq @@ -45,7 +45,6 @@ print "// TRANSFER ////////////////////////////////////////////////" print "entropy transfer" NAYNAY_ADDRESS=`entropy account ls | jq --raw-output ".[0].address"` -# NOTE: --raw-output is needed to drop the quotes entropy transfer -a faucet ${NAYNAY_ADDRESS} 2.5 entropy balance naynay From ca9e31fa914c872e996975e8e59df326ec1c50eb Mon Sep 17 00:00:00 2001 From: mixmix Date: Wed, 2 Oct 2024 16:15:58 +1300 Subject: [PATCH 03/15] more tweaks --- package.json | 1 - src/account/command.ts | 13 ++++-- src/account/main.ts | 12 +++--- src/account/utils.ts | 9 ++++- src/common/masking.ts | 51 ++++++++++++------------ src/common/utils-cli.ts | 10 ++++- src/config/index.ts | 1 - src/config/types.ts | 7 ++++ src/sign/main.ts | 5 ++- src/transfer/main.ts | 2 +- tests/cli.test.sh | 52 ------------------------ tests/common.test.ts | 53 +++++++++++++++++++++++++ tests/qa.sh | 88 +++++++++++++++++++++++++++++++++++++++++ yarn.lock | 5 --- 14 files changed, 209 insertions(+), 100 deletions(-) delete mode 100755 tests/cli.test.sh create mode 100644 tests/common.test.ts create mode 100755 tests/qa.sh diff --git a/package.json b/package.json index 1c06b247..21f06fcf 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,6 @@ "commander": "^12.0.0", "env-paths": "^3.0.0", "inquirer": "8.0.0", - "lodash.clonedeep": "^4.5.0", "mkdirp": "^3.0.1", "winston": "^3.13.0", "x25519": "^0.1.0" diff --git a/src/account/command.ts b/src/account/command.ts index 71645feb..866499ce 100644 --- a/src/account/command.ts +++ b/src/account/command.ts @@ -38,7 +38,8 @@ function entropyAccountCreate () { cliWrite({ name: newAccount.name, - address: newAccount.address + address: newAccount.address, + verifyingKeys: [] }) process.exit(0) }) @@ -64,7 +65,8 @@ function entropyAccountImport () { cliWrite({ name: newAccount.name, - address: newAccount.address + address: newAccount.address, + verifyingKeys: [] }) process.exit(0) }) @@ -78,8 +80,11 @@ function entropyAccountList () { // TODO: test if it's an encrypted account, if no password provided, throw because later on there's no protection from a prompt coming up const accounts = await config.get() .then(storedConfig => EntropyAccount.list(storedConfig)) - .catch(() => []) - // QUESTION: is dropping the error right? Maybe only if "There are currently no accounts" + .catch((err) => { + if (err.message.includes('currently no accounts')) return [] + + throw err + }) cliWrite(accounts) process.exit(0) diff --git a/src/account/main.ts b/src/account/main.ts index dac8ce83..dc35e0cc 100644 --- a/src/account/main.ts +++ b/src/account/main.ts @@ -7,7 +7,7 @@ import { FLOW_CONTEXT } from "./constants"; import { AccountCreateParams, AccountImportParams, AccountRegisterParams } from "./types"; import { EntropyBase } from "../common/entropy-base"; -import { EntropyAccountConfig } from "../config/types"; +import { EntropyAccountConfig, EntropyAccountConfigFormatted } from "../config/types"; export class EntropyAccount extends EntropyBase { constructor (entropy: Entropy, endpoint: string) { @@ -27,7 +27,8 @@ export class EntropyAccount extends EntropyBase { const fullAccount = keyring.getAccount() // TODO: sdk should create account on constructor const data = fixData(fullAccount) - // const encryptedData = password ? passwordFlow.encrypt(data, password) : data + const maybeEncryptedData = data + // const maybeEncryptedData = password ? passwordFlow.encrypt(data, password) : data const { admin } = keyring.getAccount() delete admin.pair @@ -35,12 +36,11 @@ export class EntropyAccount extends EntropyBase { return { name, address: admin.address, - data - // data: encryptedData // TODO: replace once password input is added back + data: maybeEncryptedData, } } - static list ({ accounts }: { accounts: EntropyAccountConfig[] }) { + static list ({ accounts }: { accounts: EntropyAccountConfig[] }): EntropyAccountConfigFormatted[] { if (!accounts.length) throw new Error( 'AccountsError: There are currently no accounts available, please create or import a new account using the Manage Accounts feature' @@ -49,7 +49,7 @@ export class EntropyAccount extends EntropyBase { return accounts.map((account: EntropyAccountConfig) => ({ name: account.name, address: account.address, - verifyingKeys: account?.data?.admin?.verifyingKeys + verifyingKeys: account?.data?.registration?.verifyingKeys || [] })) } diff --git a/src/account/utils.ts b/src/account/utils.ts index 16e5e4cc..25792a28 100644 --- a/src/account/utils.ts +++ b/src/account/utils.ts @@ -24,14 +24,19 @@ export async function selectAndPersistNewAccount (newAccount: EntropyAccountConf } export async function addVerifyingKeyToAccountAndSelect (verifyingKey: string, accountNameOrAddress: string) { - const { accounts } = await config.get() + const storedConfig = await config.get() + const { accounts } = storedConfig + const account = findAccountByAddressOrName(accounts, accountNameOrAddress) if (!account) throw Error(`Unable to persist verifyingKey "${verifyingKey}" to unknown account "${accountNameOrAddress}"`) account.data.registration.verifyingKeys.push(verifyingKey) // persist to config, set selectedAccount - await config.setSelectedAccount(account) + await config.set({ + ...storedConfig, + setSelectedAccount: account.name + }) } function validateSeedInput (seed) { diff --git a/src/common/masking.ts b/src/common/masking.ts index ae9aa07f..3ee66295 100644 --- a/src/common/masking.ts +++ b/src/common/masking.ts @@ -1,37 +1,38 @@ -import cloneDeep from 'lodash.clonedeep' - -const DEFAULT_MASKED_FIELDS = [ +const PREFIX = 'data:application/UI8A;base64,' +const DEFAULT_MASKED_FIELDS = new Set([ 'seed', 'secretKey', 'addressRaw', -]; +]); export function maskPayload (payload: any): any { - if (typeof payload === 'string') return payload - - const clonedPayload = cloneDeep(payload); - const maskedPayload = {} + if ( + typeof payload === 'string' || + typeof payload === 'boolean' || + payload === null + ) return payload - if (!clonedPayload) { - return clonedPayload; - } + const maskedPayload = Array.isArray(payload) + ? [] + : {} // maskJSONFields doesn't handle nested objects very well so we'll // need to recursively walk to object and mask them one by one - for (const [property, value] of Object.entries(clonedPayload)) { - if (value && typeof value === 'object') { - if (Object.keys(clonedPayload[property]).filter(key => isNaN(parseInt(key))).length === 0) { - const reconstructedUintArr: number[] = Object.values(clonedPayload[property]) - maskedPayload[property] = "base64:" + Buffer.from(reconstructedUintArr).toString("base64"); - } else { - maskedPayload[property] = maskPayload(value); - } - } else if (value && typeof value === 'string' && DEFAULT_MASKED_FIELDS.includes(property)) { - maskedPayload[property] = "*".repeat(clonedPayload[property].length) - } else { - maskedPayload[property] = value + return Object.entries(payload).reduce((acc, [property, value]) => { + if (DEFAULT_MASKED_FIELDS.has(property)) { + // @ts-expect-error .length does not exist on type "unknown" + acc[property] = "*".repeat(value?.length || 32) + } + else if (value instanceof Uint8Array) { + acc[property] = PREFIX + Buffer.from(value).toString('base64') + } + else if (typeof value === 'object') { + acc[property] = maskPayload(value); + } + else { + acc[property] = value } - } - return maskedPayload; + return acc + }, maskedPayload) } diff --git a/src/common/utils-cli.ts b/src/common/utils-cli.ts index 37e29ffc..96826f6d 100644 --- a/src/common/utils-cli.ts +++ b/src/common/utils-cli.ts @@ -61,7 +61,7 @@ export function accountOption () { ].join(' ') ) .env('ENTROPY_ACCOUNT') - .argParser(async (addressOrName) => { + .argParser(addressOrName => { // We try to map addressOrName to an account we have stored if (!storedConfig) return addressOrName @@ -69,7 +69,13 @@ export function accountOption () { if (!account) return addressOrName // If we find one, we set this account as the future default - await config.setSelectedAccount(account) + config.setSelectedAccount(account) + // NOTE: argParser cannot be an async function, so we cannot await this call + // WARNING: this will lead to a race-condition if functions are called in quick succession + // and assume the selectedAccount has been persisted + // + // RISK: doesn't seem likely as most of our functions will await at slow other steps.... + // SOLUTION: write a scynchronous version? // We finally return the account name to be as consistent as possible (using name, not address) return account.name diff --git a/src/config/index.ts b/src/config/index.ts index 93b6f351..ca2762f3 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -63,7 +63,6 @@ export async function get (configPath = CONFIG_PATH) { export function getSync (configPath = CONFIG_PATH) { const configStr = readFileSync(configPath, 'utf8') - // console.log('getSync', configPath, configStr) return deserialize(configStr) } diff --git a/src/config/types.ts b/src/config/types.ts index 66e1c6cc..e86611f3 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -15,6 +15,13 @@ export interface EntropyAccountConfig { data: EntropyAccountData } +// Safe output format +export interface EntropyAccountConfigFormatted { + name: string + address: string + verifyingKeys: string[] +} + export interface EntropyAccountData { debug?: boolean seed: string diff --git a/src/sign/main.ts b/src/sign/main.ts index 63ffa4df..e2210043 100644 --- a/src/sign/main.ts +++ b/src/sign/main.ts @@ -55,7 +55,10 @@ export class EntropySign extends EntropyBase { const signatureHexString = u8aToHex(signature) this.logger.log(`Signature: ${signatureHexString}`) - return { signature: signatureHexString, verifyingKey: this.entropy.signingManager.verifyingKey } + return { + signature: signatureHexString, + verifyingKey: this.entropy.signingManager.verifyingKey + } } catch (error) { this.logger.error('Error signing message', error) throw error diff --git a/src/transfer/main.ts b/src/transfer/main.ts index 650119e2..534792e8 100644 --- a/src/transfer/main.ts +++ b/src/transfer/main.ts @@ -50,7 +50,7 @@ export class EntropyTransfer extends EntropyBase { dispatchError.asModule ) const { docs, name, section } = decoded - + msg = `${section}.${name}: ${docs.join(' ')}` } else { // Other, CannotLookup, BadOrigin, no extra info diff --git a/tests/cli.test.sh b/tests/cli.test.sh deleted file mode 100755 index f22ee2c6..00000000 --- a/tests/cli.test.sh +++ /dev/null @@ -1,52 +0,0 @@ -#! /usr/bin/bash - -ENTROPY_ENDPOINT=ws://127.0.0.1:9944 - -rm ~/.config/entropy-cryptography/entropy-cli.json -# backup config -# mv ~/.config/entropy-cryptography/entropy-cli{.json,.backup.json} - -print () { - COLOR='\033[0;35m' - RESET='\033[0m' - echo "" - echo -e "${COLOR}> $1${RESET}" -} - -print "// ACCOUNT /////////////////////////////////////////////////" - -print "account ls" -entropy account ls | jq -# TODO: change this to return [] ? - -print "account create" -entropy account create naynay | jq - -print "account import" -entropy account import faucet 0x358f394d157e31be23313a1500f5e2c8871e514e530a35aa5c05334be7a39ba6 | jq - -print "account list" -entropy account list | jq - - - -print "// BALANCE ///////////////////////////////////////////////// " - -print "balance naynay" -entropy balance naynay - -print "balance 5CqJyjALDFz4sKjQgK8NXBQGHCWAiV63xXn2Dye393Y6Vghz" -# entropy balance faucet -entropy balance 5CqJyjALDFz4sKjQgK8NXBQGHCWAiV63xXn2Dye393Y6Vghz - - - -print "// TRANSFER ////////////////////////////////////////////////" - -print "entropy transfer" -NAYNAY_ADDRESS=`entropy account ls | jq --raw-output ".[0].address"` -entropy transfer -a faucet ${NAYNAY_ADDRESS} 2.5 -entropy balance naynay - -# restore config -# mv ~/.config/entropy-cryptography/entropy-cli{.backup.json,.json} diff --git a/tests/common.test.ts b/tests/common.test.ts new file mode 100644 index 00000000..8ec05314 --- /dev/null +++ b/tests/common.test.ts @@ -0,0 +1,53 @@ +import test from 'tape' + +import { maskPayload } from '../src/common/masking' + +test('common/masking', async (t) => { + + t.deepEqual( + maskPayload('dog'), + 'dog', + 'handles string' + ) + + t.deepEqual( + maskPayload(null), + null, + 'handles null' + ) + + t.deepEqual( + maskPayload(true), + true, + 'handles bool' + ) + + const buildPayload = () => { + return { + nested: { + seed: 'secrets', + secretKey: new Uint8Array([1,2,3]), + publicKey: new Uint8Array([4,5,6]), + arr: ['a', 'b', 'c'], + obj: { "0": 17, "1": 23 } + } + } + } + const payload = buildPayload() + const expected = { + nested: { + seed: '*'.repeat('secrets'.length), + secretKey: '***', + publicKey: 'data:application/UI8A;base64,BAUG', + arr: ['a', 'b', 'c'], + obj: { "0": 17, "1": 23 } + } + } + t.deepEqual(maskPayload(payload), expected, 'nested mess') + t.deepEqual(payload, buildPayload(), 'maskPayload does not mutate') + + + t.deepEqual(maskPayload([payload, payload]), [expected, expected], 'arrays') + + t.end() +}) diff --git a/tests/qa.sh b/tests/qa.sh new file mode 100755 index 00000000..7e37e1bb --- /dev/null +++ b/tests/qa.sh @@ -0,0 +1,88 @@ +#! /usr/bin/bash + +# WARNING - this script nukes your config! +# +# Dependencies +# - internet connection +# - jq - see https://jqlang.github.io/jq +# +# Run +# $ yarn build && ./tests/qa.sh + +rm ~/.config/entropy-cryptography/entropy-cli.json + +print () { + COLOR='\033[0;35m' + RESET='\033[0m' + echo "" + echo -e "${COLOR}> $1${RESET}" +} + +print "// ACCOUNT /////////////////////////////////////////////////" + +print "account ls" +entropy account ls | jq + +print "account create" +entropy account create naynay | jq + +print "account import" +entropy account import faucet 0x358f394d157e31be23313a1500f5e2c8871e514e530a35aa5c05334be7a39ba6 | jq + +print "account list" +entropy account list | jq + + + +print "// BALANCE ///////////////////////////////////////////////// " + +print "balance (name)" +entropy balance naynay + +print "balance (address)" +entropy balance 5CqJyjALDFz4sKjQgK8NXBQGHCWAiV63xXn2Dye393Y6Vghz + + + +print "// TRANSFER ////////////////////////////////////////////////" + +print "transfer" +NAYNAY_ADDRESS=`entropy account ls | jq --raw-output ".[0].address"` +entropy transfer -a faucet ${NAYNAY_ADDRESS} 2.5 + +print "balance" +entropy balance naynay + + + +print "// REGISTER ////////////////////////////////////////////////" + +print "register" +entropy account register -a naynay + +print "account ls" +entropy account ls | jq + +# print "entropy register (again)" +# entropy account register -a naynay + + + +print "// SIGN ////////////////////////////////////////////////////" + + +print "entropy sign" +entropy sign -a naynay "some content!\nNICE&SIMPLE" + + + +print "// PROGRAM /////////////////////////////////////////////////" + +DATE=`date` && echo "wasm junk - ${DATE}" > /tmp/entropy.fake.wasm +echo '{"type": "object"}' > /tmp/entropy.configSchema.fake.json +echo '{"type": "object"}' > /tmp/entropy.auxDataSchema.fake.json +print "program deploy" +entropy program deploy -a naynay \ + /tmp/entropy.fake.wasm \ + /tmp/entropy.configSchema.fake.json \ + /tmp/entropy.auxDataSchema.fake.json diff --git a/yarn.lock b/yarn.lock index 53eef8ff..96dd29ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2658,11 +2658,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" From c68d66d65a7ca9ff1198d0dd3346369bbe1f80e1 Mon Sep 17 00:00:00 2001 From: mix irving Date: Wed, 2 Oct 2024 16:30:13 +1300 Subject: [PATCH 04/15] Update src/config/encoding.ts --- src/config/encoding.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/config/encoding.ts b/src/config/encoding.ts index e29422d6..11fd1f45 100644 --- a/src/config/encoding.ts +++ b/src/config/encoding.ts @@ -10,7 +10,6 @@ export function deserialize (config: string) { return JSON.parse(config, reviver) } catch (err) { console.log('broken config:', config) - // WIP here: nothing being passed in?!! throw err } } From c0f24a5b9fbf05538f5fd895eead9ded8637d578 Mon Sep 17 00:00:00 2001 From: mix irving Date: Wed, 2 Oct 2024 16:37:53 +1300 Subject: [PATCH 05/15] Update tests/qa.sh --- tests/qa.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/qa.sh b/tests/qa.sh index 7e37e1bb..5523b3d6 100755 --- a/tests/qa.sh +++ b/tests/qa.sh @@ -78,10 +78,10 @@ entropy sign -a naynay "some content!\nNICE&SIMPLE" print "// PROGRAM /////////////////////////////////////////////////" +print "program deploy" DATE=`date` && echo "wasm junk - ${DATE}" > /tmp/entropy.fake.wasm echo '{"type": "object"}' > /tmp/entropy.configSchema.fake.json echo '{"type": "object"}' > /tmp/entropy.auxDataSchema.fake.json -print "program deploy" entropy program deploy -a naynay \ /tmp/entropy.fake.wasm \ /tmp/entropy.configSchema.fake.json \ From 8730871733c671311b020ccae3bec51e6ea42cbc Mon Sep 17 00:00:00 2001 From: mixmix Date: Thu, 3 Oct 2024 13:11:46 +1300 Subject: [PATCH 06/15] fixups --- package.json | 2 +- src/account/main.ts | 28 ++++++++++++++++++---------- src/cli.ts | 7 ++++--- src/common/utils-cli.ts | 7 ++++--- src/config/types.ts | 2 ++ tests/commander.test.ts | 36 ++++++++++++++++++++++++++++++++++++ tests/{qa.sh => e2e.cli.sh} | 6 ++++-- yarn.lock | 2 +- 8 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 tests/commander.test.ts rename tests/{qa.sh => e2e.cli.sh} (92%) diff --git a/package.json b/package.json index 21f06fcf..667ae0e9 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@entropyxyz/sdk": "^0.2.3", "ansi-colors": "^4.1.3", "cli-progress": "^3.12.0", - "commander": "^12.0.0", + "commander": "^12.1.0", "env-paths": "^3.0.0", "inquirer": "8.0.0", "mkdirp": "^3.0.1", diff --git a/src/account/main.ts b/src/account/main.ts index dc35e0cc..a8dc0874 100644 --- a/src/account/main.ts +++ b/src/account/main.ts @@ -110,23 +110,31 @@ export class EntropyAccount extends EntropyBase { // TODO: there is a bug in SDK that is munting this data function fixData (data) { - if (data.admin) { - data.admin.pair.addressRaw = objToUint8Array(data.admin.pair.addressRaw) - data.admin.pair.secretKey = objToUint8Array(data.admin.pair.secretKey) - data.admin.pair.publicKey = objToUint8Array(data.admin.pair.publicKey) + if (data.admin?.pair) { + const { addressRaw, secretKey, publicKey } = data.admin.pair + Object.assign(data.admin.pair, { + addressRaw: objToUint8Array(addressRaw), + secretKey: objToUint8Array(secretKey), + publicKey: objToUint8Array(publicKey) + }) } - if (data.registration) { - data.registration.pair.addressRaw = objToUint8Array(data.registration.pair.addressRaw) - data.registration.pair.secretKey = objToUint8Array(data.registration.pair.secretKey) - data.registration.pair.publicKey = objToUint8Array(data.registration.pair.publicKey) + if (data.registration?.pair) { + const { addressRaw, secretKey, publicKey } = data.registration.pair + Object.assign(data.registration.pair, { + addressRaw: objToUint8Array(addressRaw), + secretKey: objToUint8Array(secretKey), + publicKey: objToUint8Array(publicKey) + }) } return data } -function objToUint8Array (obj) { - const values: any = Object.entries(obj) +function objToUint8Array (input) { + if (input instanceof Uint8Array) return input + + const values: any = Object.entries(input) .sort((a, b) => Number(a[0]) - Number(b[0])) // sort entries by keys .map(entry => entry[1]) diff --git a/src/cli.ts b/src/cli.ts index b531efa8..1fc56a36 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,7 +4,7 @@ import { Command, Option } from 'commander' import { EntropyTuiOptions } from './types' -import { accountOption, endpointOption, loadEntropy } from './common/utils-cli' +import { loadEntropy } from './common/utils-cli' import * as config from './config' import launchTui from './tui' @@ -20,8 +20,7 @@ const program = new Command() program .name('entropy') .description('CLI interface for interacting with entropy.xyz. Running this binary without any commands or arguments starts a text-based interface.') - .addOption(accountOption()) - .addOption(endpointOption()) + .addOption( new Option( '-d, --dev', @@ -30,11 +29,13 @@ program .env('DEV_MODE') .hideHelp() ) + .addCommand(entropyBalanceCommand()) .addCommand(entropyAccountCommand()) .addCommand(entropyTransferCommand()) .addCommand(entropySignCommand()) .addCommand(entropyProgramCommand()) + .action(async (opts: EntropyTuiOptions) => { const { account, endpoint } = opts const entropy = account diff --git a/src/common/utils-cli.ts b/src/common/utils-cli.ts index 96826f6d..cfb5d54e 100644 --- a/src/common/utils-cli.ts +++ b/src/common/utils-cli.ts @@ -39,12 +39,13 @@ export function endpointOption () { return endpoint }) .default('ws://testnet.entropy.xyz:9944/') - // NOTE: argParser only runs IF the -e/--endpoint option called, so this default cannot be 'test-net' + // NOTE: default cannot be "test-net" as argParser only runs if the -e/--endpoint flag + // or ENTROPY_ENDPOINT env set } export function passwordOption (description?: string) { return new Option( - '-p, --password', + '-p, --password ', description || 'Password for the account' ) .hideHelp() // TEMP @@ -80,7 +81,7 @@ export function accountOption () { // We finally return the account name to be as consistent as possible (using name, not address) return account.name }) - .default(storedConfig?.selectedAccount) + // .default(storedConfig?.selectedAccount) } export async function loadEntropy (addressOrName: string, endpoint: string, password?: string): Promise { diff --git a/src/config/types.ts b/src/config/types.ts index e86611f3..872b24a5 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -22,6 +22,7 @@ export interface EntropyAccountConfigFormatted { verifyingKeys: string[] } +// TODO: document this whole thing export interface EntropyAccountData { debug?: boolean seed: string @@ -42,6 +43,7 @@ export interface EntropyAccount { export enum EntropyAccountContextType { programDev = 'PROGRAM_DEV_KEY', + // QUESTION: what is this and why is registration = ADMIN_KEY?! registration = 'ADMIN_KEY', deviceKey = 'CONSUMER_KEY', undefined = 'MIXED_KEY', diff --git a/tests/commander.test.ts b/tests/commander.test.ts new file mode 100644 index 00000000..ed82af33 --- /dev/null +++ b/tests/commander.test.ts @@ -0,0 +1,36 @@ +import test from 'tape' +import { Command, Option } from 'commander' + +test.only('too many opts on the dance floor', async (t) => { + const accountOption = () => { + return new Option('-a, --account ') + .argParser((str) => str.toUpperCase()) + .default('DERP') + } + + const program = new Command() + .addOption(accountOption()) + + const danceCommand = new Command('dance') + // .addOption(accountOption()) + program.addCommand(danceCommand) + + const input = 'dance --account naynay' + await program.parseAsync(input.split(' '), { from: 'user' }) + program.parseAsync + + + t.deepEqual( + program.opts(), + { account: 'NAYNAY' } + ) + // ✓ + + t.deepEqual( + danceCommand.opts(), + { account: 'NAYNAY' } + ) + // ✗ gets { account: 'DERP' } + + t.end() +}) diff --git a/tests/qa.sh b/tests/e2e.cli.sh similarity index 92% rename from tests/qa.sh rename to tests/e2e.cli.sh index 5523b3d6..119f1ad7 100755 --- a/tests/qa.sh +++ b/tests/e2e.cli.sh @@ -7,7 +7,7 @@ # - jq - see https://jqlang.github.io/jq # # Run -# $ yarn build && ./tests/qa.sh +# $ yarn build && ./tests/e2e.cli.sh rm ~/.config/entropy-cryptography/entropy-cli.json @@ -59,6 +59,8 @@ print "// REGISTER ////////////////////////////////////////////////" print "register" entropy account register -a naynay +# NOTE, this does not work: +# entropy account -a naynay register print "account ls" entropy account ls | jq @@ -79,7 +81,7 @@ entropy sign -a naynay "some content!\nNICE&SIMPLE" print "// PROGRAM /////////////////////////////////////////////////" print "program deploy" -DATE=`date` && echo "wasm junk - ${DATE}" > /tmp/entropy.fake.wasm +echo "wasm junk - $(date)" > /tmp/entropy.fake.wasm echo '{"type": "object"}' > /tmp/entropy.configSchema.fake.json echo '{"type": "object"}' > /tmp/entropy.auxDataSchema.fake.json entropy program deploy -a naynay \ diff --git a/yarn.lock b/yarn.lock index 96dd29ae..76000513 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1422,7 +1422,7 @@ colorspace@1.1.x: color "^3.1.3" text-hex "1.0.x" -commander@^12.0.0, commander@~12.1.0: +commander@^12.1.0, commander@~12.1.0: version "12.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== From a415db0d788b6a6555a126ecfe441e4d7edbbfba Mon Sep 17 00:00:00 2001 From: mixmix Date: Thu, 3 Oct 2024 13:25:45 +1300 Subject: [PATCH 07/15] rm commander.test.ts --- tests/commander.test.ts | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 tests/commander.test.ts diff --git a/tests/commander.test.ts b/tests/commander.test.ts deleted file mode 100644 index ed82af33..00000000 --- a/tests/commander.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import test from 'tape' -import { Command, Option } from 'commander' - -test.only('too many opts on the dance floor', async (t) => { - const accountOption = () => { - return new Option('-a, --account ') - .argParser((str) => str.toUpperCase()) - .default('DERP') - } - - const program = new Command() - .addOption(accountOption()) - - const danceCommand = new Command('dance') - // .addOption(accountOption()) - program.addCommand(danceCommand) - - const input = 'dance --account naynay' - await program.parseAsync(input.split(' '), { from: 'user' }) - program.parseAsync - - - t.deepEqual( - program.opts(), - { account: 'NAYNAY' } - ) - // ✓ - - t.deepEqual( - danceCommand.opts(), - { account: 'NAYNAY' } - ) - // ✗ gets { account: 'DERP' } - - t.end() -}) From fb4691b6582d130b894d21a40b3c9aecff2e27ff Mon Sep 17 00:00:00 2001 From: mixmix Date: Thu, 3 Oct 2024 13:33:08 +1300 Subject: [PATCH 08/15] fix cli opts bug with multiple accountOptions --- src/account/command.ts | 6 +++--- src/cli.ts | 4 +--- src/common/utils-cli.ts | 7 ++++--- src/sign/command.ts | 6 +++--- src/transfer/command.ts | 6 +++--- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/account/command.ts b/src/account/command.ts index a502a850..8447af16 100644 --- a/src/account/command.ts +++ b/src/account/command.ts @@ -4,7 +4,7 @@ import { EntropyAccount } from "./main"; import { selectAndPersistNewAccount, addVerifyingKeyToAccountAndSelect } from "./utils"; import { ACCOUNTS_CONTENT } from './constants' import * as config from '../config' -import { cliWrite, accountOption, endpointOption, loadEntropy, passwordOption } from "../common/utils-cli"; +import { accountOption, endpointOption, passwordOption, cliWrite, loadEntropy } from "../common/utils-cli"; export function entropyAccountCommand () { return new Command('account') @@ -84,9 +84,9 @@ function entropyAccountList () { function entropyAccountRegister () { return new Command('register') .description('Register an entropy account with a program') - .addOption(passwordOption()) - .addOption(endpointOption()) .addOption(accountOption()) + .addOption(endpointOption()) + .addOption(passwordOption()) // Removing these options for now until we update the design to accept program configs // .addOption( // new Option( diff --git a/src/cli.ts b/src/cli.ts index b531efa8..cc004d79 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,7 +4,7 @@ import { Command, Option } from 'commander' import { EntropyTuiOptions } from './types' -import { accountOption, endpointOption, loadEntropy } from './common/utils-cli' +import { loadEntropy } from './common/utils-cli' import * as config from './config' import launchTui from './tui' @@ -20,8 +20,6 @@ const program = new Command() program .name('entropy') .description('CLI interface for interacting with entropy.xyz. Running this binary without any commands or arguments starts a text-based interface.') - .addOption(accountOption()) - .addOption(endpointOption()) .addOption( new Option( '-d, --dev', diff --git a/src/common/utils-cli.ts b/src/common/utils-cli.ts index 3aec350b..00fc8c34 100644 --- a/src/common/utils-cli.ts +++ b/src/common/utils-cli.ts @@ -20,7 +20,7 @@ function getConfigOrNull () { export function endpointOption () { return new Option( - '-e, --endpoint ', + '-e, --endpoint ', [ 'Runs entropy with the given endpoint and ignores network endpoints in config.', 'Can also be given a stored endpoint name from config eg: `entropy --endpoint test-net`.' @@ -44,16 +44,17 @@ export function endpointOption () { export function passwordOption (description?: string) { return new Option( - '-p, --password ', + '-p, --password ', description || 'Password for the account' ) + .hideHelp(true) } export function accountOption () { const storedConfig = getConfigOrNull() return new Option( - '-a, --account ', + '-a, --account ', [ 'Sets the account for the session.', 'Defaults to the last set account (or the first account if one has not been set before).' diff --git a/src/sign/command.ts b/src/sign/command.ts index fc574228..1c67f4d6 100644 --- a/src/sign/command.ts +++ b/src/sign/command.ts @@ -1,14 +1,14 @@ import { Command, /* Option */ } from 'commander' -import { cliWrite, accountOption, endpointOption, loadEntropy, passwordOption } from '../common/utils-cli' +import { accountOption, endpointOption, passwordOption, cliWrite, loadEntropy } from '../common/utils-cli' import { EntropySign } from './main' export function entropySignCommand () { const signCommand = new Command('sign') .description('Sign a message using the Entropy network. Output is a JSON { verifyingKey, signature }') .argument('msg', 'Message you would like to sign (string)') - .addOption(passwordOption('Password for the source account (if required)')) - .addOption(endpointOption()) .addOption(accountOption()) + .addOption(endpointOption()) + .addOption(passwordOption('Password for the source account (if required)')) // .addOption( // new Option( // '-r, --raw', diff --git a/src/transfer/command.ts b/src/transfer/command.ts index b814e2f9..e8ef3ceb 100644 --- a/src/transfer/command.ts +++ b/src/transfer/command.ts @@ -1,5 +1,5 @@ import { Command } from "commander" -import { accountOption, endpointOption, loadEntropy, passwordOption } from "src/common/utils-cli" +import { accountOption, endpointOption, passwordOption, loadEntropy } from "../common/utils-cli" import { EntropyTransfer } from "./main" export function entropyTransferCommand () { @@ -8,9 +8,9 @@ export function entropyTransferCommand () { .description('Transfer funds between two Entropy accounts.') // TODO: name the output .argument('destination', 'Account address funds will be sent to') .argument('amount', 'Amount of funds to be moved') - .addOption(passwordOption('Password for the source account (if required)')) - .addOption(endpointOption()) .addOption(accountOption()) + .addOption(endpointOption()) + .addOption(passwordOption('Password for the source account (if required)')) .action(async (destination, amount, opts) => { const entropy = await loadEntropy(opts.account, opts.endpoint) const transferService = new EntropyTransfer(entropy, opts.endpoint) From 6eccbba805096e430b0c75645b8b692376ad7b4c Mon Sep 17 00:00:00 2001 From: mixmix Date: Thu, 3 Oct 2024 13:51:23 +1300 Subject: [PATCH 09/15] drop async argParser function! (unsupported) --- src/common/utils-cli.ts | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/common/utils-cli.ts b/src/common/utils-cli.ts index 00fc8c34..f8e7be9b 100644 --- a/src/common/utils-cli.ts +++ b/src/common/utils-cli.ts @@ -61,20 +61,29 @@ export function accountOption () { ].join(' ') ) .env('ENTROPY_ACCOUNT') - .argParser(async (account) => { - if (storedConfig && storedConfig.selectedAccount !== account) { - // Updated selected account in config with new address from this option - await config.set({ - ...storedConfig, - selectedAccount: account - }) - } + .argParser(addressOrName => { + // We try to map addressOrName to an account we have stored + if (!storedConfig) return addressOrName - return account + const account = findAccountByAddressOrName(storedConfig.accounts, addressOrName) + if (!account) return addressOrName + + // If we find one, we set this account as the future default + config.set({ + ...storedConfig, + selectedAccount: account.name + }) + // NOTE: argParser cannot be an async function, so we cannot await this call + // WARNING: this will lead to a race-condition if functions are called in quick succession + // and assume the selectedAccount has been persisted + // + // RISK: doesn't seem likely as most of our functions will await at slow other steps.... + // SOLUTION: write a scynchronous version? + + // We finally return the account name to be as consistent as possible (using name, not address) + return account.name }) .default(storedConfig?.selectedAccount) - // TODO: display the *name* not address - // TODO: standardise whether selectedAccount is name or address. } export async function loadEntropy (addressOrName: string, endpoint: string, password?: string): Promise { From 9e6373df0e608eda915a21345f0c0c590a016edb Mon Sep 17 00:00:00 2001 From: mixmix Date: Thu, 3 Oct 2024 13:54:41 +1300 Subject: [PATCH 10/15] tweeeks --- src/common/utils-cli.ts | 5 +---- tests/account.test.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/common/utils-cli.ts b/src/common/utils-cli.ts index f4096337..b7d7dd40 100644 --- a/src/common/utils-cli.ts +++ b/src/common/utils-cli.ts @@ -70,10 +70,7 @@ export function accountOption () { if (!account) return addressOrName // If we find one, we set this account as the future default - config.set({ - ...storedConfig, - selectedAccount: account.name - }) + config.setSelectedAccount(account) // NOTE: argParser cannot be an async function, so we cannot await this call // WARNING: this will lead to a race-condition if functions are called in quick succession // and assume the selectedAccount has been persisted diff --git a/tests/account.test.ts b/tests/account.test.ts index 9f0288f6..220f101a 100644 --- a/tests/account.test.ts +++ b/tests/account.test.ts @@ -32,7 +32,7 @@ test('Account - list', async t => { dev: 'ws://127.0.0.1:9944', 'test-net': 'wss://testnet.entropy.xyz', }, - selectedAccount: account.address, + selectedAccount: account.name, 'migration-version': '0' } From ea2388148d7658dcb01ed455c754c8b99036c391 Mon Sep 17 00:00:00 2001 From: mixmix Date: Thu, 3 Oct 2024 14:01:13 +1300 Subject: [PATCH 11/15] add setSelectedAccount --- src/account/interaction.ts | 5 +---- src/account/utils.ts | 6 +++--- src/common/utils-cli.ts | 5 +---- src/config/index.ts | 20 +++++++++++++++++--- src/config/types.ts | 9 +++++++++ src/tui.ts | 6 +----- tests/account.test.ts | 2 +- 7 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/account/interaction.ts b/src/account/interaction.ts index bf1c599f..b26103dd 100644 --- a/src/account/interaction.ts +++ b/src/account/interaction.ts @@ -45,10 +45,7 @@ export async function entropyAccount (endpoint: string, storedConfig: EntropyCon return } const { selectedAccount } = await inquirer.prompt(accountSelectQuestions(accounts)) - await config.set({ - ...storedConfig, - selectedAccount: selectedAccount.address - }) + await config.setSelectedAccount(selectedAccount) print('Current selected account is ' + selectedAccount) return diff --git a/src/account/utils.ts b/src/account/utils.ts index cf51603a..c8efef2e 100644 --- a/src/account/utils.ts +++ b/src/account/utils.ts @@ -16,7 +16,8 @@ export async function selectAndPersistNewAccount (newAccount: EntropyAccountConf throw Error(`An account with address "${newAccount.address}" already exists.`) } - accounts.push(newAccount) + // persist to config, set selectedAccount + accounts.push(newAccount) await config.set({ ...storedConfig, selectedAccount: newAccount.address @@ -28,9 +29,8 @@ export async function addVerifyingKeyToAccountAndSelect (verifyingKey: string, a const account = findAccountByAddressOrName(storedConfig.accounts, accountNameOrAddress) if (!account) throw Error(`Unable to persist verifyingKey "${verifyingKey}" to unknown account "${accountNameOrAddress}"`) - account.data.registration.verifyingKeys.push(verifyingKey) - // persist to config, set selectedAccount + account.data.registration.verifyingKeys.push(verifyingKey) await config.set({ ...storedConfig, selectedAccount: account.address diff --git a/src/common/utils-cli.ts b/src/common/utils-cli.ts index f8e7be9b..96f8bed7 100644 --- a/src/common/utils-cli.ts +++ b/src/common/utils-cli.ts @@ -69,10 +69,7 @@ export function accountOption () { if (!account) return addressOrName // If we find one, we set this account as the future default - config.set({ - ...storedConfig, - selectedAccount: account.name - }) + config.setSelectedAccount(account) // NOTE: argParser cannot be an async function, so we cannot await this call // WARNING: this will lead to a race-condition if functions are called in quick succession // and assume the selectedAccount has been persisted diff --git a/src/config/index.ts b/src/config/index.ts index 049b6775..a38859a4 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -6,7 +6,7 @@ import envPaths from 'env-paths' import allMigrations from './migrations' import { serialize, deserialize } from './encoding' -import { EntropyConfig } from './types' +import { EntropyConfig, EntropyAccountConfig } from './types' const paths = envPaths('entropy-cryptography', { suffix: '' }) const CONFIG_PATH = join(paths.config, 'entropy-cli.json') @@ -73,14 +73,28 @@ export async function set (config: EntropyConfig, configPath = CONFIG_PATH) { await writeFile(configPath, serialize(config)) } +export async function setSelectedAccount (account: EntropyAccountConfig, configPath = CONFIG_PATH) { + const storedConfig = await get(configPath) + + if (storedConfig.selectedAccount === account.name) return storedConfig + // no need for update + + const newConfig = { + ...storedConfig, + selectedAccount: account.name + } + await set(newConfig, configPath) + return newConfig +} + /* util */ function noop () {} -function assertConfigPath (configPath) { +function assertConfigPath (configPath: string) { if (!configPath.endsWith('.json')) { throw Error(`configPath must be of form *.json, got ${configPath}`) } } -export function isDangerousReadError (err) { +export function isDangerousReadError (err: any) { // file not found: if (err.code === 'ENOENT') return false diff --git a/src/config/types.ts b/src/config/types.ts index 7d4deb78..26aa6678 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -4,6 +4,7 @@ export interface EntropyConfig { dev: string; 'test-net': string } + // selectedAccount is account.name (alias) for the account selectedAccount: string 'migration-version': string } @@ -14,6 +15,14 @@ export interface EntropyAccountConfig { data: EntropyAccountData } +// Safe output format +export interface EntropyAccountConfigFormatted { + name: string + address: string + verifyingKeys: string[] +} + +// TODO: document this whole thing export interface EntropyAccountData { debug?: boolean seed: string diff --git a/src/tui.ts b/src/tui.ts index bd7780cf..f02d82da 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -19,11 +19,7 @@ async function setupConfig () { // set selectedAccount if we can if (!storedConfig.selectedAccount && storedConfig.accounts.length) { - await config.set({ - ...storedConfig, - selectedAccount: storedConfig.accounts[0].address - }) - storedConfig = await config.get() + storedConfig = await config.setSelectedAccount(storedConfig.accounts[0]) } return storedConfig diff --git a/tests/account.test.ts b/tests/account.test.ts index 9f0288f6..220f101a 100644 --- a/tests/account.test.ts +++ b/tests/account.test.ts @@ -32,7 +32,7 @@ test('Account - list', async t => { dev: 'ws://127.0.0.1:9944', 'test-net': 'wss://testnet.entropy.xyz', }, - selectedAccount: account.address, + selectedAccount: account.name, 'migration-version': '0' } From 215f26fdfeb0cf5a6d27f656847df568ffd0d367 Mon Sep 17 00:00:00 2001 From: mixmix Date: Thu, 3 Oct 2024 14:18:05 +1300 Subject: [PATCH 12/15] push verifyingKeys into admin account --- src/account/main.ts | 2 +- src/account/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/account/main.ts b/src/account/main.ts index a8dc0874..47b7cf08 100644 --- a/src/account/main.ts +++ b/src/account/main.ts @@ -49,7 +49,7 @@ export class EntropyAccount extends EntropyBase { return accounts.map((account: EntropyAccountConfig) => ({ name: account.name, address: account.address, - verifyingKeys: account?.data?.registration?.verifyingKeys || [] + verifyingKeys: account?.data?.admin?.verifyingKeys || [] })) } diff --git a/src/account/utils.ts b/src/account/utils.ts index e0e008db..cd66c10e 100644 --- a/src/account/utils.ts +++ b/src/account/utils.ts @@ -32,7 +32,7 @@ export async function addVerifyingKeyToAccountAndSelect (verifyingKey: string, a if (!account) throw Error(`Unable to persist verifyingKey "${verifyingKey}" to unknown account "${accountNameOrAddress}"`) // persist to config, set selectedAccount - account.data.registration.verifyingKeys.push(verifyingKey) + account.data.admin.verifyingKeys.push(verifyingKey) await config.set({ ...storedConfig, setSelectedAccount: account.name From 9d4ed23683f982a9acae0bca5edcc23a7004f05d Mon Sep 17 00:00:00 2001 From: mixmix Date: Thu, 3 Oct 2024 14:24:58 +1300 Subject: [PATCH 13/15] revert: push verifyingKeys into registration keyring account --- src/account/main.ts | 2 +- src/account/utils.ts | 2 +- tests/account.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/account/main.ts b/src/account/main.ts index 47b7cf08..a8dc0874 100644 --- a/src/account/main.ts +++ b/src/account/main.ts @@ -49,7 +49,7 @@ export class EntropyAccount extends EntropyBase { return accounts.map((account: EntropyAccountConfig) => ({ name: account.name, address: account.address, - verifyingKeys: account?.data?.admin?.verifyingKeys || [] + verifyingKeys: account?.data?.registration?.verifyingKeys || [] })) } diff --git a/src/account/utils.ts b/src/account/utils.ts index cd66c10e..e0e008db 100644 --- a/src/account/utils.ts +++ b/src/account/utils.ts @@ -32,7 +32,7 @@ export async function addVerifyingKeyToAccountAndSelect (verifyingKey: string, a if (!account) throw Error(`Unable to persist verifyingKey "${verifyingKey}" to unknown account "${accountNameOrAddress}"`) // persist to config, set selectedAccount - account.data.admin.verifyingKeys.push(verifyingKey) + account.data.registration.verifyingKeys.push(verifyingKey) await config.set({ ...storedConfig, setSelectedAccount: account.name diff --git a/tests/account.test.ts b/tests/account.test.ts index 220f101a..eb9756d6 100644 --- a/tests/account.test.ts +++ b/tests/account.test.ts @@ -18,7 +18,7 @@ test('Account - list', async t => { address: charlieStashAddress, data: { seed: charlieStashSeed, - admin: { + registration: { verifyingKeys: ['this-is-a-verifying-key'], seed: charlieStashSeed, address: charlieStashAddress, @@ -41,7 +41,7 @@ test('Account - list', async t => { t.deepEqual(accountsArray, [{ name: account.name, address: account.address, - verifyingKeys: account?.data?.admin?.verifyingKeys + verifyingKeys: account?.data?.registration?.verifyingKeys }]) // Resetting accounts on config to test for empty list From 4f1c0ca8a63d7f0b04b53e2975b281d7918994eb Mon Sep 17 00:00:00 2001 From: Nayyir Jutha Date: Mon, 21 Oct 2024 14:08:54 -0400 Subject: [PATCH 14/15] reverting changes from new sdk --- src/program/main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/program/main.ts b/src/program/main.ts index ac241a85..95e8a903 100644 --- a/src/program/main.ts +++ b/src/program/main.ts @@ -55,13 +55,13 @@ export class EntropyProgram extends EntropyBase { async get (programPointer: string): Promise { this.logger.debug(`program pointer: ${programPointer}`, `${FLOW_CONTEXT}::PROGRAM_PRESENCE_CHECK`); - return this.entropy.programs.dev.get(programPointer) + return this.entropy.programs.dev.getProgramInfo(programPointer) } async listDeployed () { const address = this.entropy.keyring.accounts.registration.address // QUESTION: will we always be wanting this address? - return this.entropy.programs.dev.getByDeployer(address) + return this.entropy.programs.dev.get(address) } } From 3a0f362fb46f4584bf71c4c1057bec192235fdbf Mon Sep 17 00:00:00 2001 From: mix irving Date: Tue, 22 Oct 2024 12:37:53 +1300 Subject: [PATCH 15/15] Update src/config/types.ts --- src/config/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/config/types.ts b/src/config/types.ts index 872b24a5..26aa6678 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -43,7 +43,6 @@ export interface EntropyAccount { export enum EntropyAccountContextType { programDev = 'PROGRAM_DEV_KEY', - // QUESTION: what is this and why is registration = ADMIN_KEY?! registration = 'ADMIN_KEY', deviceKey = 'CONSUMER_KEY', undefined = 'MIXED_KEY',