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

[NayNay] Updating Units within Entropy #306

Merged
merged 10 commits into from
Dec 3, 2024
18 changes: 15 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,31 @@ Version header format: `[version] Name - year-month-day (entropy-core compatibil

### Fixed

- Shared
- amount units are now correct! [#306](https://github.com/entropyxyz/cli/pull/306)
- 1 nanoBITS = 0.00000000001 BITS
- 1 BITS = 10000000000 nanoBITS
- Programmatic CLI
- `entropy program list` now prints output! [#298](https://github.com/entropyxyz/cli/pull/298)

### Added
### Added

- Shared
- new methods for conversion and other math operations [#306](https://github.com/entropyxyz/cli/pull/306)
- BITS => nanoBITS
- nanoBITS => BITS
- rounding to a specific number of decimal places
- nanoBITS per bits calculation
- new method to pull entropy token details from chain, and cache the results [#306](https://github.com/entropyxyz/cli/pull/306)

- TUI
- animation on tui load (while entropy loads) [#288](https://github.com/entropyxyz/cli/pull/288)

### Changes
### Changed

- Shared
- updated return data displayed to user on account creation (create or import) [#311](https://github.com/entropyxyz/cli/pull/311)

- Balance now displays the number of BITS to the nearest 4 decimal places [#306](https://github.com/entropyxyz/cli/pull/306)

## [0.1.1] Deadpool - 2024-11-06 (entropy-core compatibility: 0.3.0)

Expand Down
10 changes: 6 additions & 4 deletions src/balance/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Entropy from "@entropyxyz/sdk";

import { EntropyBalance } from "./main";
import { endpointOption, cliWrite, loadEntropy } from "../common/utils-cli";
import { findAccountByAddressOrName } from "../common/utils";
import { findAccountByAddressOrName, getTokenDetails, nanoBitsToBits, round } from "../common/utils";
import * as config from "../config";

export function entropyBalanceCommand () {
Expand All @@ -17,13 +17,15 @@ export function entropyBalanceCommand () {
.addOption(endpointOption())
.action(async (account, opts) => {
const entropy: Entropy = await loadEntropy(account, opts.endpoint)
const BalanceService = new EntropyBalance(entropy, opts.endpoint)
const balanceService = new EntropyBalance(entropy, opts.endpoint)
const { decimals, symbol } = await getTokenDetails(entropy)

const { accounts } = await config.get()
const address = findAccountByAddressOrName(accounts, account)?.address

const balance = await BalanceService.getBalance(address)
cliWrite(`${balance.toLocaleString('en-US')} BITS`)
const nanoBalance = await balanceService.getBalance(address)
const balance = round(nanoBitsToBits(nanoBalance, decimals))
cliWrite({ balance, symbol })
process.exit(0)
})

Expand Down
9 changes: 6 additions & 3 deletions src/balance/interaction.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { findAccountByAddressOrName, print } from "src/common/utils"
import { findAccountByAddressOrName, getTokenDetails, print, round, nanoBitsToBits } from "src/common/utils"
import { EntropyBalance } from "./main"

export async function entropyBalance (entropy, endpoint, storedConfig) {
try {
// grabbing decimals from chain spec as that is the source of truth for the value
const { decimals, symbol } = await getTokenDetails(entropy)
const balanceService = new EntropyBalance(entropy, endpoint)
const address = findAccountByAddressOrName(storedConfig.accounts, storedConfig.selectedAccount)?.address
const balance = await balanceService.getBalance(address)
print(`Entropy Account [${storedConfig.selectedAccount}] (${address}) has a balance of: ${balance.toLocaleString('en-US')} BITS`)
const nanoBalance = await balanceService.getBalance(address)
const balance = round(nanoBitsToBits(nanoBalance, decimals))
print(`Entropy Account [${storedConfig.selectedAccount}] (${address}) has a balance of: ${balance} ${symbol}`)
} catch (error) {
console.error('There was an error retrieving balance', error)
}
Expand Down
9 changes: 0 additions & 9 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
/*
A "bit" is the smallest indivisible unit of account value we track.
A "token" is the human readable unit of value value
This constant is then "the number of bits that make up 1 token", or said differently
"how many decimal places our token has".
*/
export const BITS_PER_TOKEN = 1e10


// ASCII Colors for Logging to Console
export const ERROR_RED = '\u001b[31m'
export const SUCCESS_GREEN = '\u001b[32m'
Expand Down
34 changes: 34 additions & 0 deletions src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Entropy } from '@entropyxyz/sdk'
import { Buffer } from 'buffer'
import { EntropyAccountConfig } from "../config/types"
import { EntropyLogger } from './logger'
import { TokenDetails } from 'src/types'

export function stripHexPrefix (str: string): string {
if (str.startsWith('0x')) return str.slice(2)
Expand Down Expand Up @@ -113,3 +114,36 @@ export async function jumpStartNetwork (entropy, endpoint): Promise<any> {
})
})
}

// caching details to reduce number of calls made to the rpc endpoint
let tokenDetails: TokenDetails
export async function getTokenDetails (entropy): Promise<TokenDetails> {
if (tokenDetails) return tokenDetails
const chainProperties = await entropy.substrate.rpc.system.properties()
const decimals = chainProperties.tokenDecimals.toHuman()[0]
const symbol = chainProperties.tokenSymbol.toHuman()[0]
tokenDetails = { decimals: parseInt(decimals), symbol }
return tokenDetails
}

/*
A "nanoBITS" is the smallest indivisible unit of account value we track.
A "BITS" is the human readable unit of value value
This constant is then "the number of nanoBITS that make up 1 BITS", or said differently
"how many decimal places our BITS has".
*/
export const nanoBitsPerBits = (decimals: number): number => {
return Math.pow(10, decimals)
}

export function nanoBitsToBits (numOfNanoBits: number, decimals: number) {
return numOfNanoBits / nanoBitsPerBits(decimals)
}

export function bitsToNanoBits (numOfBits: number, decimals: number): bigint {
return BigInt(numOfBits * nanoBitsPerBits(decimals))
}

export function round (num: number, decimals: number = 4): number {
return parseFloat(num.toFixed(decimals))
}
15 changes: 9 additions & 6 deletions src/faucet/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import yoctoSpinner from 'yocto-spinner';
import { EntropyLogger } from '../common/logger'
import { FAUCET_PROGRAM_POINTER } from "./utils"
import { EntropyFaucet } from "./main"
import { print } from "src/common/utils"
import { bitsToNanoBits, getTokenDetails, print } from "src/common/utils"

let chosenVerifyingKeys = []
// Sending only 1e10 BITS does not allow user's to register after receiving funds
// Sending only 1e10 nanoBITS does not allow user's to register after receiving funds
// there are limits in place to ensure user's are leftover with a certain balance in their accounts
// increasing amount send here, will allow user's to register right away
const amount = "20000000000"

// context for logging file
const FLOW_CONTEXT = 'ENTROPY_FAUCET_INTERACTION'
const SPINNER_TEXT = 'Funding account…'
Expand All @@ -23,15 +23,18 @@ export async function entropyFaucet (entropy: Entropy, options, logger: EntropyL
if (!entropy.registrationManager.signer.pair) {
throw new Error("Keys are undefined")
}

const { decimals } = await getTokenDetails(entropy)
const amount = bitsToNanoBits(2, decimals)
const faucetService = new EntropyFaucet(entropy, endpoint)
const verifyingKeys = await faucetService.getAllFaucetVerifyingKeys()
// @ts-expect-error
return sendMoneyFromRandomFaucet(entropy, options.endpoint, verifyingKeys, logger)
return sendMoneyFromRandomFaucet(entropy, options.endpoint, verifyingKeys, amount.toString(), logger)
}

// Method that takes in the initial list of verifying keys (to avoid multiple calls to the rpc) and recursively retries each faucet until
// a successful transfer is made
async function sendMoneyFromRandomFaucet (entropy: Entropy, endpoint: string, verifyingKeys: string[], logger: EntropyLogger) {
async function sendMoneyFromRandomFaucet (entropy: Entropy, endpoint: string, verifyingKeys: string[], amount: string, logger: EntropyLogger) {
if (!faucetSpinner.isSpinning) {
faucetSpinner.start()
}
Expand Down Expand Up @@ -59,7 +62,7 @@ async function sendMoneyFromRandomFaucet (entropy: Entropy, endpoint: string, ve
return
} else {
// Check for non faucet errors (FaucetError) and retry faucet
await sendMoneyFromRandomFaucet(entropy, endpoint, verifyingKeys, logger)
await sendMoneyFromRandomFaucet(entropy, endpoint, verifyingKeys, amount, logger)
}
}
}
13 changes: 10 additions & 3 deletions src/transfer/command.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import { Command } from "commander"
import { accountOption, endpointOption, loadEntropy } from "src/common/utils-cli"
import { accountOption, cliWrite, endpointOption, loadEntropy } from "src/common/utils-cli"
import { EntropyTransfer } from "./main"
import { getTokenDetails } from "src/common/utils"

export function entropyTransferCommand () {
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 (in "tokens")')
.argument('<amount>', 'Amount of funds (in "BITS") to be moved')
.addOption(accountOption())
.addOption(endpointOption())
.action(async (destination, amount, opts) => {
// TODO: destination as <name|address> ?
const entropy = await loadEntropy(opts.account, opts.endpoint)
const transferService = new EntropyTransfer(entropy, opts.endpoint)
const { symbol } = await getTokenDetails(entropy)

await transferService.transfer(destination, amount)

// cliWrite(??) // TODO: write the output
cliWrite({
source: opts.account,
destination,
amount,
symbol
})
process.exit(0)
})
return transferCommand
Expand Down
2 changes: 1 addition & 1 deletion src/transfer/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const TRANSFER_CONTENT = {
amount: {
name: 'amount',
message: 'Input amount to transfer:',
message: 'Input amount of BITS to transfer:',
default: '1',
invalidError: 'Please enter a value greater than 0',
},
Expand Down
5 changes: 3 additions & 2 deletions src/transfer/interaction.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import inquirer from "inquirer"
import { print } from "../common/utils"
import { getTokenDetails, print } from "../common/utils"
import { EntropyTransfer } from "./main"
import { transferInputQuestions } from "./utils"
import { setupProgress } from "src/common/progress"

export async function entropyTransfer (entropy, endpoint) {
const progressTracker = setupProgress('Transferring Funds')
const { symbol } = await getTokenDetails(entropy)
const transferService = new EntropyTransfer(entropy, endpoint)
const { amount, recipientAddress } = await inquirer.prompt(transferInputQuestions)
await transferService.transfer(recipientAddress, amount, progressTracker)
print('')
print(`Transaction successful: Sent ${amount} to ${recipientAddress}`)
print(`Transaction successful: Sent ${amount} ${symbol} to ${recipientAddress}`)
print('')
print('Press enter to return to main menu')
}
14 changes: 7 additions & 7 deletions src/transfer/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import Entropy from "@entropyxyz/sdk";

import { EntropyBase } from "../common/entropy-base";
import { formatDispatchError } from "../common/utils";
import { BITS_PER_TOKEN } from "../common/constants";
import { bitsToNanoBits, formatDispatchError, getTokenDetails } from "../common/utils";
import { TransferOptions } from "./types";

const FLOW_CONTEXT = 'ENTROPY_TRANSFER'
Expand All @@ -17,15 +16,16 @@ export class EntropyTransfer extends EntropyBase {
// - converting `amount` (string => BigInt)
// - progress callbacks (optional)

async transfer (toAddress: string, amount: string, progress?: { start: ()=>void, stop: ()=>void }) {
const formattedAmount = BigInt(Number(amount) * BITS_PER_TOKEN)
async transfer (toAddress: string, amountInBits: string, progress?: { start: ()=>void, stop: ()=>void }) {
const { decimals } = await getTokenDetails(this.entropy)
const nanoBits = bitsToNanoBits(Number(amountInBits), decimals)

if (progress) progress.start()
try {
await this.rawTransfer({
from: this.entropy.keyring.accounts.registration.pair,
to: toAddress,
amount: formattedAmount
nanoBits
})
if (progress) return progress.stop()
} catch (error) {
Expand All @@ -35,13 +35,13 @@ export class EntropyTransfer extends EntropyBase {
}

private async rawTransfer (payload: TransferOptions): Promise<any> {
const { from, to, amount } = payload
const { from, to, nanoBits } = payload

return new Promise((resolve, reject) => {
// WARN: await signAndSend is dangerous as it does not resolve
// after transaction is complete :melt:
this.entropy.substrate.tx.balances
.transferAllowDeath(to, amount)
.transferAllowDeath(to, nanoBits)
// @ts-ignore
.signAndSend(from, ({ status, dispatchError }) => {
if (dispatchError) {
Expand Down
2 changes: 1 addition & 1 deletion src/transfer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import { Pair } from '@entropyxyz/sdk/keys'
export interface TransferOptions {
from: Pair
to: string
amount: bigint
nanoBits: bigint
}
5 changes: 5 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ export interface EntropyLoggerOptions {
level?: EntropyLoggerLogLevel
isTesting?: boolean
}

export type TokenDetails = {
decimals: number
symbol: string
}
3 changes: 2 additions & 1 deletion tests/faucet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ test('Faucet Tests: Successfully send funds and register', async t => {
let naynayBalance = await balance.getBalance(naynayAddress)
t.equal(naynayBalance, 0, 'Naynay is broke af')

// 2 BITS
const amount = 20000000000
const transferStatus = await run(
'Sending faucet funds to account',
Expand Down Expand Up @@ -109,7 +110,7 @@ test('Faucet Tests: Successfully send funds and register', async t => {
t.end()
})

// TODO: @naynay fix below test for register failing when only sending 1e10 bits
// TODO: @naynay fix below test for register failing when only sending 1e10 tokens
// test('Faucet Tests: Successfully send funds but cannot register', async t => {
// const { run, endpoint, entropy: naynayEntropy } = await setupTest(t)

Expand Down
4 changes: 3 additions & 1 deletion tests/testing-utils/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export const charlieAddress = '5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y'

export const eveSeed =
'0x786ad0e2df456fe43dd1f91ebca22e235bc162e0bb8d53c633e8c85b2af68b7a'
export const eveAddress = '5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw'
export const eveAddress = '5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw'

export const DEFAULT_TOKEN_DECIMALS = 10
8 changes: 4 additions & 4 deletions tests/transfer.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import test from 'tape'

import { BITS_PER_TOKEN } from "../src/common/constants";
import { nanoBitsPerBits } from "../src/common/utils";
import { EntropyTransfer } from '../src/transfer/main'
import { EntropyBalance } from '../src/balance/main'
import { promiseRunner, setupTest } from './testing-utils'
import { charlieStashAddress, charlieStashSeed } from './testing-utils/constants.mjs'
import { charlieStashAddress, charlieStashSeed, DEFAULT_TOKEN_DECIMALS } from './testing-utils/constants.mjs'

const endpoint = 'ws://127.0.0.1:9944'

Expand Down Expand Up @@ -44,8 +44,8 @@ test('Transfer', async (t) => {
'getBalance (naynay)',
balanceService.getBalance(naynayAddress)
)
const expected = Number(inputAmount) * BITS_PER_TOKEN
t.equal(naynayBalance, expected,'naynay is rolling in it!')
const expected = Number(inputAmount) * nanoBitsPerBits(DEFAULT_TOKEN_DECIMALS)
t.equal(naynayBalance, expected, 'naynay is rolling in it!')

t.end()
})
Loading