Skip to content

Commit

Permalink
[NayNay] Updating Units within Entropy (#306)
Browse files Browse the repository at this point in the history
* [NayNay] Updating Units within Entropy

- it had come to our attn that we were using the wrong units for the tokens display
- we were under the impression that bits were the smallest unit of the entropy token, that was a mistake
- updated all mentions of tokens and bits to match their respective definition
- updated return of balance to the human readable format, using BITS
- added new method to grab token details from chain

* updated methods and flows based on pr suggestions, added changelog)

* git weirdness

* git weirdness part ii

* updated nomenclature for the base unit of BITS to nanoBITS

* weird leftover conflict resolution

* updated cli output to be more programmatic and not human sentences, updated amount for faucet to send two whole BITS, rather than a hardcoded string
  • Loading branch information
rh0delta authored Dec 3, 2024
1 parent f124d6b commit 6d36cb3
Show file tree
Hide file tree
Showing 15 changed files with 106 additions and 45 deletions.
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 { EntropyConfigAccount } 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()
})

0 comments on commit 6d36cb3

Please sign in to comment.