Skip to content

Commit

Permalink
[NayNay] Transfer updates + unit testing (#140)
Browse files Browse the repository at this point in the history
* [NayNay] Transfer updates + unit testing

closes #124

* updated transfer pure function and added tests

* Update transfer.test.ts

Co-authored-by: mix irving <mix@protozoa.nz>

* updated based on PR suggestions

* updated progress bar use to be more universal for the cli; based on pr suggestion

* updating comment

---------

Co-authored-by: Nayyir Jutha <nayyir@entropy.xyz>
Co-authored-by: mixmix <mix@protozoa.nz>
  • Loading branch information
3 people authored Jul 3, 2024
1 parent fb1f6ee commit a9b4b0b
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 81 deletions.
7 changes: 4 additions & 3 deletions src/common/initializeEntropy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import inquirer from "inquirer"
import { decrypt, encrypt } from "../flows/password"
import { debug } from "../common/utils"
import * as config from "../config"
import { EntropyAccountData } from "../types"
import { EntropyAccountData } from "../config/types"

// TODO: unused
// let defaultAccount // have a main account to use
Expand All @@ -17,7 +17,8 @@ const keyrings = {
default: undefined // this is the "selected account" keyring
}

export function getKeyring (address) {
export function getKeyring (address?: string) {

if (!address && keyrings.default) return keyrings.default
if (address && keyrings[address]) return keyrings[address]
// explicitly return undefined so there is no confusion around what is selected
Expand Down Expand Up @@ -65,7 +66,7 @@ export const initializeEntropy = async ({ keyMaterial, password, endpoint, confi
}

let selectedAccount
const storedKeyring = getKeyring(accountData?.admin?.address)
const storedKeyring = getKeyring(accountData.admin.address)

if(!storedKeyring) {
const keyring = new Keyring({ ...accountData, debug: true })
Expand Down
32 changes: 32 additions & 0 deletions src/common/progress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import cliProgress from 'cli-progress'
import colors from 'ansi-colors'

export function setupProgress (label: string): { start: () => void; stop: () => void } {
let interval: NodeJS.Timeout
const b1 = new cliProgress.SingleBar({
format: `${label} |` + colors.cyan('{bar}') + '| {percentage}%',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true
})

const start = () => {
// 160 was found through trial and error, don't believe there is a formula to
// determine the exact time it takes for the transaction to be processed and finalized
// TO-DO: Change progress bar to loading animation?
b1.start(160, 0, {
speed: "N/A"
})
// update values
interval = setInterval(() => {
b1.increment()
}, 100)
}

const stop = () => {
b1.stop()
clearInterval(interval)
}

return { start, stop }
}
2 changes: 1 addition & 1 deletion src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { decodeAddress, encodeAddress } from "@polkadot/keyring"
import { hexToU8a, isHex } from "@polkadot/util"
import { Buffer } from 'buffer'
import Debug from 'debug'
import { EntropyAccountConfig } from "../types"
import { EntropyAccountConfig } from "../config/types"

const _debug = Debug('@entropyxyz/cli')

Expand Down
2 changes: 1 addition & 1 deletion src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import envPaths from 'env-paths'

import allMigrations from './migrations'

const paths = envPaths('entropyxyz', { suffix: '' })
const paths = envPaths('entropy-cryptography', { suffix: '' })
const CONFIG_PATH = join(paths.config, 'entropy-cli.json')
const OLD_CONFIG_PATH = join(process.env.HOME, '.entropy-cli.config')

Expand Down
36 changes: 36 additions & 0 deletions src/config/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export interface EntropyConfig {
accounts: EntropyAccountConfig[]
endpoints: { dev: string; 'test-net': string }
'migration-version': string
}

export interface EntropyAccountConfig {
name: string
address: string
data: EntropyAccountData
}

export interface EntropyAccountData {
debug?: boolean
seed: string
admin?: EntropyAccount
registration?: EntropyAccount
deviceKey?: EntropyAccount
programDev?: EntropyAccount
}

export interface EntropyAccount {
seed: string
path: string
address: string
verifyingKeys?: string[]
userContext?: EntropyAccountContextType
used?: boolean
}

export enum EntropyAccountContextType {
programDev = 'PROGRAM_DEV_KEY',
registration = 'ADMIN_KEY',
deviceKey = 'CONSUMER_KEY',
undefined = 'MIXED_KEY',
}
57 changes: 20 additions & 37 deletions src/flows/entropyTransfer/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import inquirer from "inquirer"
import cliProgress from 'cli-progress'
import colors from 'ansi-colors'

import { print, getSelectedAccount } from "../../common/utils"
import { getSelectedAccount, print } from "../../common/utils"
import { initializeEntropy } from "../../common/initializeEntropy"
import { transfer } from "./transfer"
import { setupProgress } from "src/common/progress"

const question = [
{
Expand All @@ -29,53 +28,37 @@ export async function entropyTransfer ({ accounts, selectedAccount: selectedAcco
const { endpoint } = options
const selectedAccount = getSelectedAccount(accounts, selectedAccountAddress)

const { start: startProgress, stop: stopProgress } = setupProgress('Transferring Funds')

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

const b1 = new cliProgress.SingleBar({
format: 'Transferring Funds |' + colors.cyan('{bar}') + '| {percentage}%',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true
})

const { amount, recipientAddress } = await inquirer.prompt(question)

if (!entropy?.keyring?.accounts?.registration?.pair) {
throw new Error("Signer keypair is undefined or not properly initialized.")
}
const formattedAmount = BigInt(parseInt(amount) * 1e10)
const tx = await entropy.substrate.tx.balances.transferAllowDeath(
recipientAddress,
BigInt(formattedAmount),
startProgress()
const transferStatus = await transfer(
entropy,
{
from: entropy.keyring.accounts.registration.pair,
to: recipientAddress,
amount: formattedAmount
}
)
if (transferStatus.isFinalized) stopProgress()

await tx.signAndSend (entropy.keyring.accounts.registration.pair, ({ status }) => {
// initialize the bar - defining payload token "speed" with the default value "N/A"
b1.start(300, 0, {
speed: "N/A"
});
// update values
const interval = setInterval(() => {
b1.increment()
}, 100)
if (status.isFinalized) {
b1.stop()
clearInterval(interval)
print(
`\nTransaction successful: Sent ${amount} to ${recipientAddress}`
)
print('\nPress enter to return to main menu');
}
})
return;

print(
`\nTransaction successful: Sent ${amount} to ${recipientAddress}`
)
print('\nPress enter to return to main menu')
} catch (error) {
stopProgress()
console.error('ERR:::', error);


}
}
}
34 changes: 34 additions & 0 deletions src/flows/entropyTransfer/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Entropy from "@entropyxyz/sdk";
import { TransferOptions } from "./types";

export async function transfer (entropy: Entropy, payload: TransferOptions): Promise<any> {
const { from, to, amount } = payload

return new Promise((resolve, reject) => {
// WARN: await signAndSend is dangerous as it does not resolve
// after transaction is complete :melt:
entropy.substrate.tx.balances
.transferAllowDeath(to, amount)
// @ts-ignore
.signAndSend(from, ({ status, dispatchError }) => {
if (dispatchError) {
let msg: string
if (dispatchError.isModule) {
// for module errors, we have the section indexed, lookup
const decoded = entropy.substrate.registry.findMetaError(
dispatchError.asModule
)
const { docs, name, section } = decoded

msg = `${section}.${name}: ${docs.join(' ')}`
} else {
// Other, CannotLookup, BadOrigin, no extra info
msg = dispatchError.toString()
}
return reject(Error(msg))
}

if (status.isFinalized) resolve(status)
})
})
}
7 changes: 7 additions & 0 deletions src/flows/entropyTransfer/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @ts-ignore
import { Pair } from '@entropyxyz/sdk/keys'
export interface TransferOptions {
from: Pair
to: string
amount: bigint
}
36 changes: 0 additions & 36 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,3 @@
export interface EntropyConfig {
accounts: EntropyAccountConfig[]
endpoints: { dev: string; 'test-net': string }
'migration-version': string
}
export interface EntropyAccountConfig {
name: string
address: string
data: EntropyAccountData
}

export interface EntropyAccountData {
debug?: boolean
seed: string
admin?: EntropyAccount
registration?: EntropyAccount
deviceKey?: EntropyAccount
programDev?: EntropyAccount
}

export interface EntropyAccount {
seed: string
path: string
address: string
verifyingKeys?: string[]
userContext?: EntropyAccountContextType
used?: boolean
}

export enum EntropyAccountContextType {
programDev = 'PROGRAM_DEV_KEY',
registration = 'ADMIN_KEY',
deviceKey = 'CONSUMER_KEY',
undefined = 'MIXED_KEY',
}

export interface EntropyTuiOptions {
dev: boolean
endpoint: string
Expand Down
2 changes: 1 addition & 1 deletion tests/manage-accounts.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EntropyAccountConfig, EntropyConfig } from 'src/types'
import { EntropyAccountConfig, EntropyConfig } from 'src/config/types'
import test from 'tape'
import { charlieStashAddress, charlieStashSeed } from './testing-utils/constants'
import { listAccounts } from 'src/flows/manage-accounts/list'
Expand Down
8 changes: 6 additions & 2 deletions tests/testing-utils/setup-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Test } from 'tape'
import { Entropy, wasmGlobalsReady } from '@entropyxyz/sdk'
// @ts-ignore
import { spinNetworkUp, spinNetworkDown, } from "@entropyxyz/sdk/testing"
// @ts-ignore
import Keyring from '@entropyxyz/sdk/keys'

import { initializeEntropy } from '../../src/common/initializeEntropy'
import * as config from '../../src/config'
Expand Down Expand Up @@ -38,9 +40,11 @@ export async function setupTest (t: Test, opts?: SetupTestOpts): Promise<{ entro

// TODO: remove this after new SDK is published
await sleep(process.env.GITHUB_WORKSPACE ? 30_000 : 5_000)

// To follow the same way we initiate entropy within the cli we must go through the same process of creating an initial keyring
// as done in src/flows/manage-accounts/new-key.ts
const keyring = new Keyring({ seed, debug: true })
const entropy = await initializeEntropy({
keyMaterial: { seed, debug: true },
keyMaterial: keyring.getAccount(),
endpoint: 'ws://127.0.0.1:9944',
configPath
})
Expand Down
Loading

0 comments on commit a9b4b0b

Please sign in to comment.