Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add deploy to CLI, including start of CLI refactor #213

Merged
merged 13 commits into from
Sep 24, 2024
4 changes: 2 additions & 2 deletions src/account/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { EntropyAccount } from "./main";
import { selectAndPersistNewAccount } from "./utils";
import { ACCOUNTS_CONTENT } from './constants'
import * as config from '../config'
import { cliWrite, currentAccountAddressOption, endpointOption, loadEntropy, passwordOption } from "../common/utils-cli";
import { cliWrite, accountOption, endpointOption, loadEntropy, passwordOption } from "../common/utils-cli";
import { findAccountByAddressOrName } from "src/common/utils";

export function entropyAccountCommand () {
Expand Down Expand Up @@ -87,7 +87,7 @@ function entropyAccountRegister () {
.description('Register an entropy account with a program')
.addOption(passwordOption())
.addOption(endpointOption())
.addOption(currentAccountAddressOption())
.addOption(accountOption())
// Removing these options for now until we update the design to accept program configs
// .addOption(
// new Option(
Expand Down
4 changes: 2 additions & 2 deletions src/account/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const FLOW_CONTEXT = 'ENTROPY_ACCOUNTS'
export const FLOW_CONTEXT = 'ENTROPY_ACCOUNT'

export const ACCOUNTS_CONTENT = {
seed: {
Expand Down Expand Up @@ -33,4 +33,4 @@ export const ACCOUNTS_CONTENT = {
{ name: 'Exit to Main Menu', value: 'exit' }
]
}
}
}
12 changes: 6 additions & 6 deletions src/account/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ import { EntropyConfig } from "../config/types";
import * as config from "../config";

import {
manageAccountsQuestions,
newAccountQuestions,
selectAccountQuestions
accountManageQuestions,
accountNewQuestions,
accountSelectQuestions
} from "./utils"

/*
* @returns partialConfigUpdate | "exit" | undefined
*/
export async function entropyAccount (endpoint: string, storedConfig: EntropyConfig) {
const { accounts } = storedConfig
const { interactionChoice } = await inquirer.prompt(manageAccountsQuestions)
const { interactionChoice } = await inquirer.prompt(accountManageQuestions)

switch (interactionChoice) {

case 'create-import': {
const answers = await inquirer.prompt(newAccountQuestions)
const answers = await inquirer.prompt(accountNewQuestions)
const { name, path, importKey } = answers
let { seed } = answers
if (importKey && seed.includes('#debug')) {
Expand All @@ -44,7 +44,7 @@ export async function entropyAccount (endpoint: string, storedConfig: EntropyCon
console.error('There are currently no accounts available, please create or import a new account using the Manage Accounts feature')
return
}
const { selectedAccount } = await inquirer.prompt(selectAccountQuestions(accounts))
const { selectedAccount } = await inquirer.prompt(accountSelectQuestions(accounts))
await config.set({
...storedConfig,
selectedAccount: selectedAccount.address
Expand Down
10 changes: 5 additions & 5 deletions src/account/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function validateSeedInput (seed) {
return ACCOUNTS_CONTENT.seed.invalidSeed
}

export const importQuestions = [
export const accountImportQuestions = [
{
type: 'input',
name: ACCOUNTS_CONTENT.seed.name,
Expand All @@ -48,29 +48,29 @@ export const importQuestions = [
},
]

export const newAccountQuestions = [
export const accountNewQuestions = [
{
type: 'confirm',
name: ACCOUNTS_CONTENT.importKey.name,
message: ACCOUNTS_CONTENT.importKey.message,
default: ACCOUNTS_CONTENT.importKey.default,
},
...importQuestions,
...accountImportQuestions,
{
type: 'input',
name: ACCOUNTS_CONTENT.name.name,
default: ACCOUNTS_CONTENT.name.default,
},
]

export const selectAccountQuestions = (accounts: EntropyAccountConfig[]) => [{
export const accountSelectQuestions = (accounts: EntropyAccountConfig[]) => [{
type: 'list',
name: ACCOUNTS_CONTENT.selectAccount.name,
message: ACCOUNTS_CONTENT.selectAccount.message,
choices: generateAccountChoices(accounts)
}]

export const manageAccountsQuestions = [
export const accountManageQuestions = [
{
type: 'list',
name: ACCOUNTS_CONTENT.interactionChoice.name,
Expand Down
1 change: 0 additions & 1 deletion src/balance/command.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Entropy from "@entropyxyz/sdk";
import { Command } from "commander";
import { cliWrite, endpointOption, loadEntropy, passwordOption } from "src/common/utils-cli";
import { EntropyBalance } from "./main";
Expand Down
27 changes: 18 additions & 9 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@
import { Command, Option } from 'commander'

import { EntropyTuiOptions } from './types'
import { currentAccountAddressOption, endpointOption, loadEntropy } from './common/utils-cli'
import { accountOption, endpointOption, loadEntropy } from './common/utils-cli'
import * as config from './config'

import launchTui from './tui'
import { entropyAccountCommand } from './account/command'
import { entropyTransferCommand } from './transfer/command'
import { entropySignCommand } from './sign/command'
import { entropyBalanceCommand } from './balance/command'
import { entropyProgramCommand } from './program/command'

const program = new Command()

/* no 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(currentAccountAddressOption())
.description('CLI interface for interacting with entropy.xyz. Running without commands starts an interactive ui')
.addOption(accountOption())
.addOption(endpointOption())
// NOTE: I think this is currently unused
.addOption(
new Option(
'-d, --dev',
Expand All @@ -33,10 +34,18 @@ program
.addCommand(entropyAccountCommand())
.addCommand(entropyTransferCommand())
.addCommand(entropySignCommand())
.action(async (options: EntropyTuiOptions) => {
const { account, endpoint } = options
const entropy = await loadEntropy(account, endpoint)
launchTui(entropy, options)
.addCommand(entropyProgramCommand())
.action(async (opts: EntropyTuiOptions) => {
const { account, endpoint } = opts
const entropy = account
? await loadEntropy(account, endpoint)
: undefined
// NOTE: on initial startup you have no account
mixmix marked this conversation as resolved.
Show resolved Hide resolved
launchTui(entropy, opts)
})
.hook('preAction', async () => {
// set up config file, run migrations
return config.init()
mixmix marked this conversation as resolved.
Show resolved Hide resolved
})

program.parseAsync().then(() => {})
program.parseAsync()
mixmix marked this conversation as resolved.
Show resolved Hide resolved
49 changes: 32 additions & 17 deletions src/common/utils-cli.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import Entropy from '@entropyxyz/sdk'
import { Option } from 'commander'
import { findAccountByAddressOrName, stringify } from './utils'
import * as config from '../config'
import Entropy from '@entropyxyz/sdk'
import { initializeEntropy } from './initializeEntropy'

export function cliWrite (result) {
const prettyResult = stringify(result, 0)
process.stdout.write(prettyResult)
}

function getConfigOrNull () {
try {
return config.getSync()
} catch (err) {
if (config.isDangerousReadError(err)) throw err
return null
}
}

mixmix marked this conversation as resolved.
Show resolved Hide resolved
export function endpointOption () {
return new Option(
'-e, --endpoint <endpoint>',
Expand All @@ -17,14 +26,14 @@ export function endpointOption () {
'Can also be given a stored endpoint name from config eg: `entropy --endpoint test-net`.'
].join(' ')
)
.env('ENDPOINT')
.env('ENTROPY_ENDPOINT')
.argParser(aliasOrEndpoint => {
/* see if it's a raw endpoint */
if (aliasOrEndpoint.match(/^wss?:\/\//)) return aliasOrEndpoint

/* look up endpoint-alias */
const storedConfig = config.getSync()
const endpoint = storedConfig.endpoints[aliasOrEndpoint]
const storedConfig = getConfigOrNull()
const endpoint = storedConfig?.endpoints?.[aliasOrEndpoint]
if (!endpoint) throw Error('unknown endpoint alias: ' + aliasOrEndpoint)

return endpoint
Expand All @@ -40,31 +49,37 @@ export function passwordOption (description?: string) {
)
}

export function currentAccountAddressOption () {
const storedConfig = config.getSync()
export function accountOption () {
const storedConfig = getConfigOrNull()

return new Option(
'-a, --account <address>',
'Sets the current account for the session or defaults to the account stored in the config'
'-a, --account <accountAddressOrName>',
[
'Sets the account for the session.',
'Defaults to the last set account (or the first account if one has not been set before).'
].join(' ')
)
.env('ACCOUNT_ADDRESS')
.env('ENTROPY_ACCOUNT')
.argParser(async (account) => {
if (account === storedConfig.selectedAccount) return account
// Updated selected account in config with new address from this option
const newConfigUpdates = { selectedAccount: account }
await config.set({ ...storedConfig, ...newConfigUpdates })
if (storedConfig && storedConfig.selectedAccount !== account) {
// Updated selected account in config with new address from this option
await config.set({
...storedConfig,
selectedAccount: account
})
}

return account
})
.default(storedConfig.selectedAccount)
.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<Entropy> {
const storedConfig = config.getSync()
const selectedAccount = findAccountByAddressOrName(storedConfig.accounts, addressOrName)
if (!selectedAccount) throw new Error(`AddressError: No account with name or address "${addressOrName}"`)
const accounts = getConfigOrNull()?.accounts || []
const selectedAccount = findAccountByAddressOrName(accounts, addressOrName)
if (!selectedAccount) throw new Error(`No account with name or address: "${addressOrName}"`)

// check if data is encrypted + we have a password
if (typeof selectedAccount.data === 'string' && !password) {
Expand Down
2 changes: 1 addition & 1 deletion src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function accountChoicesWithOther (accounts: EntropyAccountConfig[]) {
}

export function findAccountByAddressOrName (accounts: EntropyAccountConfig[], aliasOrAddress: string) {
if (!aliasOrAddress || !aliasOrAddress.length) throw Error('aliasOrAddress required')
if (!aliasOrAddress || !aliasOrAddress.length) throw Error('account name or address required')

return (
accounts.find(account => account.address === aliasOrAddress) ||
Expand Down
38 changes: 18 additions & 20 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readFile, writeFile, rm } from 'node:fs/promises'
import { readFileSync, writeFileSync } from 'node:fs'
import { readFileSync } from 'node:fs'
import { mkdirp } from 'mkdirp'
import { join, dirname } from 'path'
import envPaths from 'env-paths'
Expand Down Expand Up @@ -35,9 +35,10 @@ function hasRunMigration (config: any, version: number) {

export async function init (configPath = CONFIG_PATH, oldConfigPath = OLD_CONFIG_PATH) {
const currentConfig = await get(configPath)
.catch(async (err) => {
if (err.code !== 'ENOENT') throw err
.catch(async (err ) => {
if (isDangerousReadError(err)) throw err

// If there is no current config, try loading the old one
const oldConfig = await get(oldConfigPath).catch(noop) // drop errors
if (oldConfig) {
// move the config
Expand All @@ -58,33 +59,30 @@ export async function init (configPath = CONFIG_PATH, oldConfigPath = OLD_CONFIG
export async function get (configPath = CONFIG_PATH) {
return readFile(configPath, 'utf-8')
.then(deserialize)
.catch(makeGetErrorHandler(configPath))
}

export function getSync (configPath = CONFIG_PATH): EntropyConfig {
try {
const configBuffer = readFileSync(configPath, 'utf8')
return deserialize(configBuffer)
} catch (err) {
return makeGetErrorHandler(configPath)(err)
}
export function getSync (configPath = CONFIG_PATH) {
const configStr = readFileSync(configPath, 'utf8')
return deserialize(configStr)
}
mixmix marked this conversation as resolved.
Show resolved Hide resolved

export async function set (config: EntropyConfig, configPath = CONFIG_PATH) {
assertConfigPath(configPath)

await mkdirp(dirname(configPath))
await writeFile(configPath, serialize(config))
}

/* util */
function noop () {}

function makeGetErrorHandler (configPath) {
return function getErrorHandler (err) {
if (err.code !== 'ENOENT') throw err

const newConfig = migrateData(allMigrations, {})
mkdirp.sync(dirname(configPath))
writeFileSync(configPath, serialize(newConfig))
return newConfig
function assertConfigPath (configPath) {
if (!configPath.endsWith('.json')) {
throw Error(`configPath must be of form *.json, got ${configPath}`)
}
}
export function isDangerousReadError (err) {
// file not found:
if (err.code === 'ENOENT') return false

return true
}
8 changes: 5 additions & 3 deletions src/flows/entropyFaucet/faucet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import Entropy from "@entropyxyz/sdk";
import { blake2AsHex, encodeAddress } from "@polkadot/util-crypto";
import { viewPrograms } from "../programs/view";
import { EntropyProgram } from '../../program/main'
import FaucetSigner from "./signer";
import { FAUCET_PROGRAM_MOD_KEY, TESTNET_PROGRAM_HASH } from "./constants";
import { EntropyBalance } from "src/balance/main";
Expand Down Expand Up @@ -67,7 +67,7 @@ export async function sendMoney (
faucetAddress,
chosenVerifyingKey,
faucetProgramPointer = TESTNET_PROGRAM_HASH
}: {
}: {
amount: string,
addressToSendTo: string,
faucetAddress: string,
Expand All @@ -76,11 +76,13 @@ export async function sendMoney (
}
): Promise<any> {
const balanceService = new EntropyBalance(entropy, endpoint)
const programService = new EntropyProgram(entropy, endpoint)

// check balance of faucet address
const balance = await balanceService.getBalance(faucetAddress)
if (balance <= 0) throw new Error('FundsError: Faucet Account does not have funds')
// check verifying key for only one program matching the program hash
const programs = await viewPrograms(entropy, { verifyingKey: chosenVerifyingKey })
const programs = await programService.list({ verifyingKey: chosenVerifyingKey })
if (programs.length) {
if (programs.length > 1) throw new Error('ProgramsError: Faucet Account has too many programs attached, expected less')
if (programs.length === 1 && programs[0].program_pointer !== faucetProgramPointer) {
Expand Down
1 change: 0 additions & 1 deletion src/flows/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { entropyFaucet } from './entropyFaucet'
export { userPrograms, devPrograms } from './programs'
12 changes: 0 additions & 12 deletions src/flows/programs/add.ts

This file was deleted.

Loading
Loading