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/command.ts b/src/account/command.ts index 4ff446cc..87ef635f 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 () { @@ -34,7 +37,8 @@ function entropyAccountCreate () { cliWrite({ name: newAccount.name, - address: newAccount.address + address: newAccount.address, + verifyingKeys: [] }) process.exit(0) }) @@ -59,7 +63,8 @@ function entropyAccountImport () { cliWrite({ name: newAccount.name, - address: newAccount.address + address: newAccount.address, + verifyingKeys: [] }) process.exit(0) }) @@ -70,8 +75,15 @@ function entropyAccountList () { .alias('ls') .description('List all accounts. Output is JSON of form [{ name, address, verifyingKeys }]') .action(async () => { - const storedConfig = await config.get() - const accounts = EntropyAccount.list(storedConfig) + // 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((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 638d1096..a8dc0874 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) { @@ -19,26 +19,28 @@ 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 maybeEncryptedData = data + // const maybeEncryptedData = password ? passwordFlow.encrypt(data, password) : data - const data = fullAccount + const { admin } = keyring.getAccount() delete admin.pair - + return { name, address: admin.address, - data + 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' @@ -47,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 || [] })) } @@ -88,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 @@ -105,3 +107,36 @@ export class EntropyAccount extends EntropyBase { }) } } + +// TODO: there is a bug in SDK that is munting this data +function fixData (data) { + 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?.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 (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]) + + return new Uint8Array(values) +} diff --git a/src/account/utils.ts b/src/account/utils.ts index c8efef2e..e0e008db 100644 --- a/src/account/utils.ts +++ b/src/account/utils.ts @@ -20,20 +20,22 @@ 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 } = storedConfig + + const account = findAccountByAddressOrName(accounts, accountNameOrAddress) if (!account) throw Error(`Unable to persist verifyingKey "${verifyingKey}" to unknown account "${accountNameOrAddress}"`) // persist to config, set selectedAccount account.data.registration.verifyingKeys.push(verifyingKey) await config.set({ ...storedConfig, - selectedAccount: account.address + setSelectedAccount: account.name }) } diff --git a/src/balance/command.ts b/src/balance/command.ts index bb9b47f3..f6829594 100644 --- a/src/balance/command.ts +++ b/src/balance/command.ts @@ -1,21 +1,28 @@ import { Command } from "commander"; import Entropy from "@entropyxyz/sdk"; -import { cliWrite, endpointOption, loadEntropy } from "src/common/utils-cli"; + import { EntropyBalance } from "./main"; +import { endpointOption, cliWrite, loadEntropy } 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') + .argument('account ', 'Account address whose balance you want to query') .addOption(endpointOption()) - .action(async (address, opts) => { - const entropy: Entropy = await loadEntropy(address, opts.endpoint) + .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/cli.ts b/src/cli.ts index cc004d79..8001f304 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -28,11 +28,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 4569d1de..7b53a4ff 100644 --- a/src/common/utils-cli.ts +++ b/src/common/utils-cli.ts @@ -39,7 +39,8 @@ 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: default cannot be "test-net" as argParser only runs if the -e/--endpoint flag + // or ENTROPY_ENDPOINT env set } export function accountOption () { diff --git a/src/config/encoding.ts b/src/config/encoding.ts index cb580655..11fd1f45 100644 --- a/src/config/encoding.ts +++ b/src/config/encoding.ts @@ -1,12 +1,17 @@ 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) + throw err + } } function replacer (_key: string, value: any) { 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/command.ts b/src/transfer/command.ts index 23d964fc..7791842f 100644 --- a/src/transfer/command.ts +++ b/src/transfer/command.ts @@ -3,17 +3,20 @@ import { accountOption, endpointOption, loadEntropy } from "src/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') + .argument('amount', 'Amount of funds to be moved (in "tokens")') .addOption(accountOption()) .addOption(endpointOption()) .action(async (destination, amount, opts) => { + // TODO: destination as ? const entropy = await loadEntropy(opts.account, opts.endpoint) const transferService = new EntropyTransfer(entropy, opts.endpoint) + await transferService.transfer(destination, amount) + // cliWrite(??) // TODO: write the output process.exit(0) }) 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/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 diff --git a/tests/e2e.cli.sh b/tests/e2e.cli.sh new file mode 100755 index 00000000..119f1ad7 --- /dev/null +++ b/tests/e2e.cli.sh @@ -0,0 +1,90 @@ +#! /usr/bin/bash + +# WARNING - this script nukes your config! +# +# Dependencies +# - internet connection +# - jq - see https://jqlang.github.io/jq +# +# Run +# $ yarn build && ./tests/e2e.cli.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 +# NOTE, this does not work: +# entropy account -a naynay register + +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 /////////////////////////////////////////////////" + +print "program deploy" +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 \ + /tmp/entropy.fake.wasm \ + /tmp/entropy.configSchema.fake.json \ + /tmp/entropy.auxDataSchema.fake.json 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==