Skip to content

Commit

Permalink
[NayNay] File Restructure: Signing Restructure (#218)
Browse files Browse the repository at this point in the history
* [NayNay] File Restructure: Signing Restructure

- created new file structure for signing flow
- updated tui/cli with new changes

* some cleanup; fixed sign tests

* removed inquirer input from commands file, added file inoput back to signing

* added raw sign back in

* wip: raw sign stuff

* wip: finished off last changes for raw sign but need to test

* wip: porting to newly defined structure

* updated signing to new structure, and added utils-cli from mix pr

* updated file structure for balance

* updated file structure for transfer

* pr review updates

* updated to sign not signing

* signing-restructure tweeeeks (#220)

* tweeeeks

* more tweeks

* fix tests

* rename test

* fix maskPayload

* test fix

* change sign command to just return sig

* stdout cleanup for balance and sign

* tweeks

* fix transfer to allow decimal transfers

---------

Co-authored-by: mix irving <mix@protozoa.nz>
  • Loading branch information
rh0delta and mixmix authored Aug 29, 2024
1 parent 038051d commit 67045d4
Show file tree
Hide file tree
Showing 31 changed files with 682 additions and 487 deletions.
33 changes: 17 additions & 16 deletions src/balance/command.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import Entropy from "@entropyxyz/sdk"
import { BaseCommand } from "../common/base-command"
import * as BalanceUtils from "./utils"
import Entropy from "@entropyxyz/sdk";
import { Command } from "commander";
import { cliWrite, endpointOption, passwordOption } from "src/common/utils-cli";
import { EntropyBalance } from "./main";

const FLOW_CONTEXT = 'ENTROPY-BALANCE'
export class BalanceCommand extends BaseCommand {
constructor (entropy: Entropy, endpoint: string) {
super(entropy, endpoint, FLOW_CONTEXT)
}
export async function entropyBalanceCommand (entropy: Entropy, rootCommand: Command) {
rootCommand.command('balance')
.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())
.addOption(endpointOption())
.action(async (address, opts) => {
const BalanceService = new EntropyBalance(entropy, opts.endpoint)
const balance = await BalanceService.getBalance(address)
cliWrite(`${balance.toLocaleString('en-US')} BITS`)
process.exit(0)
})

public async getBalance (address: string) {
const balance = await BalanceUtils.getBalance(this.entropy, address)

this.logger.log(`Current balance of ${address}: ${balance}`, `${BalanceCommand.name}`)

return `${balance.toLocaleString('en-US')} BITS`
}
}
}
12 changes: 12 additions & 0 deletions src/balance/interaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { print } from "src/common/utils"
import { EntropyBalance } from "./main"

export async function entropyBalance (entropy, endpoint, storedConfig) {
try {
const BalanceService = new EntropyBalance(entropy, endpoint)
const balance = await BalanceService.getBalance(storedConfig.selectedAccount)
print(`Address ${storedConfig.selectedAccount} has a balance of: ${balance.toLocaleString('en-US')} BITS`)
} catch (error) {
console.error('There was an error retrieving balance', error)
}
}
34 changes: 34 additions & 0 deletions src/balance/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Entropy from "@entropyxyz/sdk"
import { EntropyBase } from "../common/entropy-base"
import * as BalanceUtils from "./utils"
import { BalanceInfo } from "./types"

const FLOW_CONTEXT = 'ENTROPY-BALANCE'
export class EntropyBalance extends EntropyBase {
constructor (entropy: Entropy, endpoint: string) {
super(entropy, endpoint, FLOW_CONTEXT)
}

async getBalance (address: string): Promise<number> {
const accountInfo = (await this.entropy.substrate.query.system.account(address)) as any
const balance = parseInt(BalanceUtils.hexToBigInt(accountInfo.data.free).toString())

this.logger.log(`Current balance of ${address}: ${balance}`, EntropyBalance.name)
return balance
}

async getBalances (addresses: string[]): Promise<BalanceInfo> {
const balanceInfo: BalanceInfo = {}
await Promise.all(addresses.map(async address => {
try {
const balance = await this.getBalance(address)

balanceInfo[address] = { balance }
} catch (error) {
balanceInfo[address] = { error: error.message }
}
}))

return balanceInfo
}
}
25 changes: 1 addition & 24 deletions src/balance/utils.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1 @@
import Entropy from "@entropyxyz/sdk";
import { BalanceInfo } from "./types";

const hexToBigInt = (hexString: string) => BigInt(hexString)

export async function getBalance (entropy: Entropy, address: string): Promise<number> {
const accountInfo = (await entropy.substrate.query.system.account(address)) as any
return parseInt(hexToBigInt(accountInfo.data.free).toString())
}

export async function getBalances (entropy: Entropy, addresses: string[]): Promise<BalanceInfo> {
const balanceInfo: BalanceInfo = {}
await Promise.all(addresses.map(async address => {
try {
const balance = await getBalance(entropy, address)

balanceInfo[address] = { balance }
} catch (error) {
balanceInfo[address] = { error: error.message }
}
}))

return balanceInfo
}
export const hexToBigInt = (hexString: string) => BigInt(hexString)
160 changes: 29 additions & 131 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,92 +2,33 @@

/* NOTE: calling this file entropy.ts helps commander parse process.argv */
import { Command, Option } from 'commander'
import launchTui from './tui'
import * as config from './config'
import { EntropyTuiOptions } from './types'

import { cliListAccounts } from './flows/manage-accounts/cli'
import { cliSign } from './flows/sign/cli'
import { getSelectedAccount, stringify } from './common/utils'
import Entropy from '@entropyxyz/sdk'
import { initializeEntropy } from './common/initializeEntropy'
import { BalanceCommand } from './balance/command'
import { TransferCommand } from './transfer/command'
import { cliListAccounts } from './flows/manage-accounts/cli'
import { currentAccountAddressOption, endpointOption, loadEntropy, cliWrite } from './common/utils-cli'
import { entropyTransferCommand } from './transfer/command'
import { entropySignCommand } from './sign/command'
import { entropyBalanceCommand } from './balance/command'
import { EntropyTuiOptions } from './types'
import launchTui from './tui'

const program = new Command()
// Array of restructured commands to make it easier to migrate them to the new "flow"
const RESTRUCTURED_COMMANDS = ['balance']

function endpointOption (){
return new Option(
'-e, --endpoint <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`.'
].join(' ')
)
.env('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]
if (!endpoint) throw Error('unknown endpoint alias: ' + aliasOrEndpoint)

return endpoint
})
.default('ws://testnet.entropy.xyz:9944/')
// NOTE: argParser is only run IF an option is provided, so this cannot be 'test-net'
}

function passwordOption (description?: string) {
return new Option(
'-p, --password <password>',
description || 'Password for the account'
)
}

function currentAccountAddressOption () {
const storedConfig = config.getSync()
return new Option(
'-a, --account <accountAddress>',
'Sets the current account for the session or defaults to the account stored in the config'
)
.env('ACCOUNT_ADDRESS')
.argParser(async (address) => {
if (address === storedConfig.selectedAccount) return address
// Updated selected account in config with new address from this option
const newConfigUpdates = { selectedAccount: address }
await config.set({ ...storedConfig, ...newConfigUpdates })

return address
})
.hideHelp()
.default(storedConfig.selectedAccount)
}

let entropy: Entropy

export async function loadEntropy (address: string, endpoint: string, password?: string): Promise<Entropy> {
const storedConfig = config.getSync()
const selectedAccount = getSelectedAccount(storedConfig.accounts, address)

if (!selectedAccount) throw Error(`No account with address ${address}`)

// check if data is encrypted + we have a password
if (typeof selectedAccount.data === 'string' && !password) {
throw Error('This account requires a password, add --password <password>')
async function setEntropyGlobal (address: string, endpoint: string, password?: string) {
if (entropy) {
const currentAddress = entropy?.keyring?.accounts?.registration?.address
if (address !== currentAddress) {
// Is it possible to hit this?
// - programmatic usage kills process after function call
// - tui usage manages mutation of entropy instance itself
await entropy.close()
entropy = await loadEntropy(address, endpoint, password)
}
}

entropy = await initializeEntropy({ keyMaterial: selectedAccount.data, endpoint, password })

if (!entropy?.keyring?.accounts?.registration?.pair) {
throw new Error("Signer keypair is undefined or not properly initialized.")
else {
entropy = await loadEntropy(address, endpoint, password)
}

return entropy
}

/* no command */
Expand All @@ -105,16 +46,12 @@ program
.hideHelp()
)
.hook('preAction', async (_thisCommand, actionCommand) => {
if (!entropy || (entropy.keyring.accounts.registration.address !== actionCommand.args[0] || entropy.keyring.accounts.registration.address !== actionCommand.opts().account)) {
// balance includes an address argument, use that address to instantiate entropy
// can keep the conditional to check for length of args, and use the first index since it is our pattern to have the address as the first argument
if (RESTRUCTURED_COMMANDS.includes(actionCommand.name()) && actionCommand.args.length) {
await loadEntropy(actionCommand.args[0], actionCommand.opts().endpoint, actionCommand.opts().password)
} else {
// if address is not an argument, use the address from the option
await loadEntropy(actionCommand.opts().account, actionCommand.opts().endpoint, actionCommand.opts().password)
}
}
const { account, endpoint, password } = actionCommand.opts()
const address = actionCommand.name() === 'balance'
? actionCommand.args[0]
: account

await setEntropyGlobal(address, endpoint, password)
})
.action((options: EntropyTuiOptions) => {
launchTui(entropy, options)
Expand All @@ -127,56 +64,17 @@ program.command('list')
.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 accounts = await cliListAccounts()
writeOut(accounts)
cliWrite(accounts)
process.exit(0)
})

/* balance */
program.command('balance')
.description('Get the balance of an Entropy account. Output is a number')
.argument('address', 'Account address whose balance you want to query')
.addOption(passwordOption())
.addOption(endpointOption())
.action(async (address, opts) => {
const balanceCommand = new BalanceCommand(entropy, opts.endpoint)
const balance = await balanceCommand.getBalance(address)
writeOut(balance)
process.exit(0)
})
entropyBalanceCommand(entropy, program)

/* Transfer */
program.command('transfer')
.description('Transfer funds between two Entropy accounts.') // TODO: name the output
.argument('source', 'Account address funds will be drawn from')
.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(currentAccountAddressOption())
.action(async (_source, destination, amount, opts) => {
const transferCommand = new TransferCommand(entropy, opts.endpoint)
await transferCommand.sendTransfer(destination, amount)
// writeOut(??) // TODO: write the output
process.exit(0)
})
entropyTransferCommand(entropy, program)

/* Sign */
program.command('sign')
.description('Sign a message using the Entropy network. Output is a signature (string)')
.argument('address', 'Account address to use to sign')
.argument('message', 'Message you would like to sign')
.addOption(passwordOption('Password for the source account (if required)'))
.addOption(endpointOption())
.addOption(currentAccountAddressOption())
.action(async (address, message, opts) => {
const signature = await cliSign({ address, message, ...opts })
writeOut(signature)
process.exit(0)
})

function writeOut (result) {
const prettyResult = stringify(result)
process.stdout.write(prettyResult)
}
entropySignCommand(entropy, program)

program.parseAsync().then(() => {})
2 changes: 1 addition & 1 deletion src/common/base-command.ts → src/common/entropy-base.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Entropy from "@entropyxyz/sdk";
import { EntropyLogger } from "./logger";

export abstract class BaseCommand {
export abstract class EntropyBase {
protected logger: EntropyLogger
protected entropy: Entropy

Expand Down
2 changes: 2 additions & 0 deletions src/common/masking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const DEFAULT_MASKED_FIELDS = [
];

export function maskPayload (payload: any): any {
if (typeof payload === 'string') return payload

const clonedPayload = cloneDeep(payload);
const maskedPayload = {}

Expand Down
Loading

0 comments on commit 67045d4

Please sign in to comment.