diff --git a/ironfish-cli/src/commands/wallet/balance.ts b/ironfish-cli/src/commands/wallet/balance.ts index 25e689d402..be89bd8f4a 100644 --- a/ironfish-cli/src/commands/wallet/balance.ts +++ b/ironfish-cli/src/commands/wallet/balance.ts @@ -1,7 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { CurrencyUtils, GetBalanceResponse, isNativeIdentifier } from '@ironfish/sdk' +import { CurrencyUtils, GetBalanceResponse, isNativeIdentifier, RpcAsset } from '@ironfish/sdk' import { Flags } from '@oclif/core' import { IronfishCommand } from '../../command' import { RemoteFlags } from '../../flags' @@ -54,60 +54,56 @@ export class BalanceCommand extends IronfishCommand { confirmations: flags.confirmations, }) + const assetId = response.content.assetId + const asset = ( await client.wallet.getAsset({ account, - id: response.content.assetId, + id: assetId, confirmations: flags.confirmations, }) ).content - const assetId = response.content.assetId - const assetName = renderAssetName(isNativeIdentifier(assetId) ? '$IRON' : assetId, { + let nameToRender + if (isNativeIdentifier(assetId)) { + nameToRender = '$IRON' + } else if (asset.symbol) { + nameToRender = asset.symbol + } else { + nameToRender = assetId + } + + const assetName = renderAssetName(nameToRender, { verification: asset.verification, verbose: !!flags.verbose, logWarn: this.warn.bind(this), }) if (flags.explain) { - this.explainBalance(response.content, assetName) + this.explainBalance(response.content, asset, assetName) return } + const renderedAvailable = renderValue(response.content.available, asset, assetName) + const renderedConfirmed = renderValue(response.content.confirmed, asset, assetName) + const renderedUnconfirmed = renderValue(response.content.unconfirmed, asset, assetName) + const renderedPending = renderValue(response.content.pending, asset, assetName) if (flags.all) { this.log(`Account: ${response.content.account}`) this.log(`Head Hash: ${response.content.blockHash || 'NULL'}`) this.log(`Head Sequence: ${response.content.sequence || 'NULL'}`) - this.log( - `Available: ${CurrencyUtils.renderIron(response.content.available, true, assetName)}`, - ) - this.log( - `Confirmed: ${CurrencyUtils.renderIron(response.content.confirmed, true, assetName)}`, - ) - this.log( - `Unconfirmed: ${CurrencyUtils.renderIron( - response.content.unconfirmed, - true, - assetName, - )}`, - ) - this.log( - `Pending: ${CurrencyUtils.renderIron(response.content.pending, true, assetName)}`, - ) + this.log(`Available: ${renderedAvailable}`) + this.log(`Confirmed: ${renderedConfirmed}`) + this.log(`Unconfirmed: ${renderedUnconfirmed}`) + this.log(`Pending: ${renderedPending}`) return } this.log(`Account: ${response.content.account}`) - this.log( - `Available Balance: ${CurrencyUtils.renderIron( - response.content.available, - true, - assetName, - )}`, - ) + this.log(`Available Balance: ${renderedAvailable}`) } - explainBalance(response: GetBalanceResponse, assetId: string): void { + explainBalance(response: GetBalanceResponse, asset: RpcAsset, assetName: string): void { const unconfirmed = CurrencyUtils.decode(response.unconfirmed) const confirmed = CurrencyUtils.decode(response.confirmed) const pending = CurrencyUtils.decode(response.pending) @@ -116,6 +112,13 @@ export class BalanceCommand extends IronfishCommand { const unconfirmedDelta = unconfirmed - confirmed const pendingDelta = pending - unconfirmed + const renderedUnconfirmed = renderValue(unconfirmed, asset, assetName) + const renderedUnconfirmedDelta = renderValue(unconfirmedDelta, asset, assetName) + const renderedConfirmed = renderValue(confirmed, asset, assetName) + const renderedPending = renderValue(pending, asset, assetName) + const renderedPendingDelta = renderValue(pendingDelta, asset, assetName) + const renderedAvailable = renderValue(available, asset, assetName) + this.log(`Account: ${response.account}`) this.log( @@ -126,26 +129,34 @@ export class BalanceCommand extends IronfishCommand { this.log('') this.log(`Your available balance is made of notes on the chain that are safe to spend`) - this.log(`Available: ${CurrencyUtils.renderIron(available, true, assetId)}`) + this.log(`Available: ${renderedAvailable}`) this.log('') this.log('Your confirmed balance includes all notes from transactions on the chain') - this.log(`Confirmed: ${CurrencyUtils.renderIron(confirmed, true, assetId)}`) + this.log(`Confirmed: ${renderedConfirmed}`) this.log('') this.log( - `${response.unconfirmedCount} transactions worth ${CurrencyUtils.renderIron( - unconfirmedDelta, - )} are on the chain within ${response.confirmations} blocks of the head`, + `${response.unconfirmedCount} transactions worth ${renderedUnconfirmedDelta} are on the chain within ${response.confirmations} blocks of the head`, ) - this.log(`Unconfirmed: ${CurrencyUtils.renderIron(unconfirmed, true, assetId)}`) + this.log(`Unconfirmed: ${renderedUnconfirmed}`) this.log('') this.log( - `${response.pendingCount} transactions worth ${CurrencyUtils.renderIron( - pendingDelta, - )} are pending and have not been added to the chain`, + `${response.pendingCount} transactions worth ${renderedPendingDelta} are pending and have not been added to the chain`, ) - this.log(`Pending: ${CurrencyUtils.renderIron(pending, true, assetId)}`) + this.log(`Pending: ${renderedPending}`) + } +} + +// TODO(mat): Eventually this logic should probably be rolled into +// CurrencyUtils.render() via additional options +function renderValue(amount: string | bigint, asset: RpcAsset, assetName: string): string { + const renderNameManually = asset.verification.status === 'verified' + + if (renderNameManually) { + return `${assetName} ${CurrencyUtils.render(amount, false, asset)}` + } else { + return CurrencyUtils.render(amount, true, asset) } } diff --git a/ironfish-cli/src/commands/wallet/balances.ts b/ironfish-cli/src/commands/wallet/balances.ts index bee764eef8..b815ce1c9d 100644 --- a/ironfish-cli/src/commands/wallet/balances.ts +++ b/ironfish-cli/src/commands/wallet/balances.ts @@ -76,7 +76,7 @@ export class BalancesCommand extends IronfishCommand { }, available: { header: 'Available Balance', - get: ({ balance }) => CurrencyUtils.renderIron(balance.available), + get: ({ asset, balance }) => CurrencyUtils.render(balance.available, false, asset), }, } @@ -85,15 +85,15 @@ export class BalancesCommand extends IronfishCommand { ...columns, confirmed: { header: 'Confirmed Balance', - get: ({ balance }) => CurrencyUtils.renderIron(balance.confirmed), + get: ({ asset, balance }) => CurrencyUtils.render(balance.confirmed, false, asset), }, unconfirmed: { header: 'Unconfirmed Balance', - get: ({ balance }) => CurrencyUtils.renderIron(balance.unconfirmed), + get: ({ asset, balance }) => CurrencyUtils.render(balance.unconfirmed, false, asset), }, pending: { header: 'Pending Balance', - get: ({ balance }) => CurrencyUtils.renderIron(balance.pending), + get: ({ asset, balance }) => CurrencyUtils.render(balance.pending, false, asset), }, blockHash: { header: 'Head Hash', diff --git a/ironfish-cli/src/commands/wallet/burn.ts b/ironfish-cli/src/commands/wallet/burn.ts index b703ac4111..47287b8c99 100644 --- a/ironfish-cli/src/commands/wallet/burn.ts +++ b/ironfish-cli/src/commands/wallet/burn.ts @@ -7,6 +7,7 @@ import { CurrencyUtils, RawTransaction, RawTransactionSerde, + RpcAsset, Transaction, } from '@ironfish/sdk' import { CliUx, Flags } from '@oclif/core' @@ -130,6 +131,14 @@ export class Burn extends IronfishCommand { this.error(`You must have a custom asset in order to burn.`) } + const assetData = ( + await client.wallet.getAsset({ + account, + id: assetId, + confirmations: flags.confirmations, + }) + ).content + let amount = flags.amount if (!amount) { amount = await promptCurrency({ @@ -141,7 +150,7 @@ export class Burn extends IronfishCommand { balance: { account, confirmations: flags.confirmations, - assetId, + asset: assetData, }, }) } @@ -183,7 +192,7 @@ export class Burn extends IronfishCommand { this.exit(0) } - if (!flags.confirm && !(await this.confirm(assetId, amount, raw.fee, account))) { + if (!flags.confirm && !(await this.confirm(assetData, amount, raw.fee, account))) { this.error('Transaction aborted.') } @@ -209,18 +218,14 @@ export class Burn extends IronfishCommand { this.warn(`Transaction '${transaction.hash().toString('hex')}' failed to broadcast`) } - const assetResponse = await client.wallet.getAsset({ - account, - id: assetId, - confirmations: flags.confirmations, - }) - const assetName = BufferUtils.toHuman(Buffer.from(assetResponse.content.name, 'hex')) + const assetName = BufferUtils.toHuman(Buffer.from(assetData.name, 'hex')) + const renderedAmount = CurrencyUtils.render(amount, false, assetData) this.log(`Burned asset ${assetName} from ${account}`) this.log(`Asset Identifier: ${assetId}`) - this.log(`Amount: ${CurrencyUtils.renderIron(amount)}`) + this.log(`Amount: ${renderedAmount}`) this.log(`Hash: ${transaction.hash().toString('hex')}`) - this.log(`Fee: ${CurrencyUtils.renderIron(transaction.fee(), true)}`) + this.log(`Fee: ${CurrencyUtils.render(transaction.fee(), true)}`) const networkId = (await client.chain.getNetworkInfo()).content.networkId const transactionUrl = getExplorer(networkId)?.getTransactionUrl( @@ -244,20 +249,15 @@ export class Burn extends IronfishCommand { } async confirm( - assetId: string, + asset: RpcAsset, amount: bigint, fee: bigint, account: string, ): Promise { + const renderedAmount = CurrencyUtils.render(amount, true, asset) + const renderedFee = CurrencyUtils.render(fee, true) this.log( - `You are about to burn: ${CurrencyUtils.renderIron( - amount, - true, - assetId, - )} plus a transaction fee of ${CurrencyUtils.renderIron( - fee, - true, - )} with the account ${account}`, + `You are about to burn: ${renderedAmount} plus a transaction fee of ${renderedFee} with the account ${account}`, ) return CliUx.ux.confirm('Do you confirm (Y/N)?') diff --git a/ironfish-cli/src/commands/wallet/mint.ts b/ironfish-cli/src/commands/wallet/mint.ts index ddd22329db..94e1df7190 100644 --- a/ironfish-cli/src/commands/wallet/mint.ts +++ b/ironfish-cli/src/commands/wallet/mint.ts @@ -9,7 +9,7 @@ import { isValidPublicAddress, RawTransaction, RawTransactionSerde, - RpcClient, + RpcAsset, Transaction, } from '@ironfish/sdk' import { CliUx, Flags } from '@oclif/core' @@ -180,8 +180,10 @@ export class Mint extends IronfishCommand { assetId = asset.id } + let assetData if (assetId) { - const isAssetOwner = await this.isAssetOwner(client, assetId, accountPublicKey) + assetData = (await client.chain.getAsset({ id: assetId })).content + const isAssetOwner = this.isAssetOwner(assetData, accountPublicKey) if (!isAssetOwner) { this.error(`The account '${account}' does not own this asset.`) } @@ -195,6 +197,9 @@ export class Mint extends IronfishCommand { text: 'Enter the amount', minimum: 0n, logger: this.logger, + balance: { + asset: assetData, + }, }) } @@ -254,6 +259,7 @@ export class Mint extends IronfishCommand { name, metadata, flags.transferOwnershipTo, + assetData, )) ) { this.error('Transaction aborted.') @@ -283,16 +289,15 @@ export class Mint extends IronfishCommand { this.warn(`Transaction '${transaction.hash().toString('hex')}' failed to broadcast`) } + const renderedValue = CurrencyUtils.render(minted.value, true, { + id: minted.asset.id().toString('hex'), + ...assetData, + }) + const renderedFee = CurrencyUtils.render(transaction.fee(), true) this.log(`Minted asset ${BufferUtils.toHuman(minted.asset.name())} from ${account}`) this.log(`Asset Identifier: ${minted.asset.id().toString('hex')}`) - this.log( - `Value: ${CurrencyUtils.renderIron( - minted.value, - true, - minted.asset.id().toString('hex'), - )}`, - ) - this.log(`Fee: ${CurrencyUtils.renderIron(transaction.fee(), true)}`) + this.log(`Value: ${renderedValue}`) + this.log(`Fee: ${renderedFee}`) this.log(`Hash: ${transaction.hash().toString('hex')}`) const networkId = (await client.chain.getNetworkInfo()).content.networkId @@ -324,14 +329,22 @@ export class Mint extends IronfishCommand { name?: string, metadata?: string, transferOwnershipTo?: string, + assetData?: RpcAsset, ): Promise { const nameString = name ? `\nName: ${name}` : '' const metadataString = metadata ? `\nMetadata: ${metadata}` : '' + + const renderedAmount = CurrencyUtils.render(amount, !!assetId, { + id: assetId, + ...assetData, + }) + const renderedFee = CurrencyUtils.render(fee, true) + this.log( `You are about to mint an asset with the account ${account}:${nameString}${metadataString}`, ) - this.log(`Amount: ${CurrencyUtils.renderIron(amount, !!assetId, assetId)}`) - this.log(`Fee: ${CurrencyUtils.renderIron(fee, true)}`) + this.log(`Amount: ${renderedAmount}`) + this.log(`Fee: ${renderedFee}`) if (transferOwnershipTo) { this.log( @@ -342,14 +355,9 @@ export class Mint extends IronfishCommand { return CliUx.ux.confirm('Do you confirm (Y/N)?') } - async isAssetOwner( - client: RpcClient, - assetId: string, - ownerPublicKey: string, - ): Promise { + isAssetOwner(asset: RpcAsset, ownerPublicKey: string): boolean { try { - const assetResponse = await client.chain.getAsset({ id: assetId }) - if (assetResponse.content.owner === ownerPublicKey) { + if (asset.owner === ownerPublicKey) { return true } } catch (e) { diff --git a/ironfish-cli/src/commands/wallet/multisig/signature/aggregate.ts b/ironfish-cli/src/commands/wallet/multisig/signature/aggregate.ts index 21e6dd88f2..807f52e282 100644 --- a/ironfish-cli/src/commands/wallet/multisig/signature/aggregate.ts +++ b/ironfish-cli/src/commands/wallet/multisig/signature/aggregate.ts @@ -90,7 +90,7 @@ export class MultisigSign extends IronfishCommand { this.log(`Transaction: ${response.content.transaction}`) this.log(`Hash: ${transaction.hash().toString('hex')}`) - this.log(`Fee: ${CurrencyUtils.renderIron(transaction.fee(), true)}`) + this.log(`Fee: ${CurrencyUtils.render(transaction.fee(), true)}`) if (flags.watch) { this.log('') diff --git a/ironfish-cli/src/commands/wallet/notes/combine.ts b/ironfish-cli/src/commands/wallet/notes/combine.ts index 8925ee88a1..d4e610b62f 100644 --- a/ironfish-cli/src/commands/wallet/notes/combine.ts +++ b/ironfish-cli/src/commands/wallet/notes/combine.ts @@ -7,6 +7,7 @@ import { CurrencyUtils, RawTransaction, RawTransactionSerde, + RpcAsset, RpcClient, TimeUtils, Transaction, @@ -299,7 +300,15 @@ export class CombineNotesCommand extends IronfishCommand { ) raw = RawTransactionSerde.deserialize(createTransactionBytes) - displayTransactionSummary(raw, Asset.nativeId().toString('hex'), amount, from, to, memo) + // TODO(mat): We need to add asset support here for bridges, etc. + const assetData = ( + await client.wallet.getAsset({ + id: Asset.nativeId().toString('hex'), + account: from, + }) + ).content + + displayTransactionSummary(raw, assetData, amount, from, to, memo) const transactionTimer = new TransactionTimer(spendPostTime, raw) @@ -347,7 +356,7 @@ export class CombineNotesCommand extends IronfishCommand { this.warn(`Transaction '${transaction.hash().toString('hex')}' failed to broadcast`) } - await this.displayCombinedNoteHashes(client, from, transaction) + await this.displayCombinedNoteHashes(client, from, transaction, assetData) this.log(`Transaction hash: ${transaction.hash().toString('hex')}`) @@ -385,6 +394,7 @@ export class CombineNotesCommand extends IronfishCommand { client: RpcClient, from: string, transaction: Transaction, + assetData: RpcAsset, ) { const resultingNotes = ( await client.wallet.getAccountTransaction({ @@ -402,7 +412,7 @@ export class CombineNotesCommand extends IronfishCommand { }, value: { header: 'Value', - get: (note) => CurrencyUtils.renderIron(note.value, true), + get: (note) => CurrencyUtils.render(note.value, true, assetData), }, owner: { header: 'Owner', diff --git a/ironfish-cli/src/commands/wallet/notes/index.ts b/ironfish-cli/src/commands/wallet/notes/index.ts index 1962dac0af..bd289b84e6 100644 --- a/ironfish-cli/src/commands/wallet/notes/index.ts +++ b/ironfish-cli/src/commands/wallet/notes/index.ts @@ -1,7 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { CurrencyUtils } from '@ironfish/sdk' +import { CurrencyUtils, RpcAsset } from '@ironfish/sdk' import { CliUx } from '@oclif/core' import { IronfishCommand } from '../../../command' import { RemoteFlags } from '../../../flags' @@ -28,6 +28,8 @@ export class NotesCommand extends IronfishCommand { const { flags, args } = await this.parse(NotesCommand) const account = args.account as string | undefined + const assetLookup: Map = new Map() + const client = await this.sdk.connectRpc() const response = client.wallet.getAccountNotesStream({ account }) @@ -35,6 +37,13 @@ export class NotesCommand extends IronfishCommand { let showHeader = !flags['no-header'] for await (const note of response.contentStream()) { + if (!assetLookup.has(note.assetId)) { + assetLookup.set( + note.assetId, + (await client.wallet.getAsset({ id: note.assetId, account })).content, + ) + } + CliUx.ux.table( [note], { @@ -62,7 +71,7 @@ export class NotesCommand extends IronfishCommand { ...TableCols.asset({ extended: flags.extended }), value: { header: 'Amount', - get: (row) => CurrencyUtils.renderIron(row.value), + get: (row) => CurrencyUtils.render(row.value, false, assetLookup.get(row.assetId)), minWidth: 16, }, noteHash: { diff --git a/ironfish-cli/src/commands/wallet/post.ts b/ironfish-cli/src/commands/wallet/post.ts index 7a0c1b2d5f..69b862c4b1 100644 --- a/ironfish-cli/src/commands/wallet/post.ts +++ b/ironfish-cli/src/commands/wallet/post.ts @@ -116,13 +116,10 @@ export class PostCommand extends IronfishCommand { spending += output.note.value() } + const renderedSpending = CurrencyUtils.render(spending, true) + const renderedFee = CurrencyUtils.render(raw.fee, true) this.log( - `You are about to post a transaction that sends ${CurrencyUtils.renderIron( - spending, - true, - )}, with ${raw.mints.length} mints and ${ - raw.burns.length - } burns with a fee ${CurrencyUtils.renderIron(raw.fee, true)} using account ${account}`, + `You are about to post a transaction that sends ${renderedSpending}, with ${raw.mints.length} mints and ${raw.burns.length} burns with a fee ${renderedFee} using account ${account}`, ) return CliUx.ux.confirm('Do you want to post this (Y/N)?') diff --git a/ironfish-cli/src/commands/wallet/send.ts b/ironfish-cli/src/commands/wallet/send.ts index d8835991d5..5457dd4989 100644 --- a/ironfish-cli/src/commands/wallet/send.ts +++ b/ironfish-cli/src/commands/wallet/send.ts @@ -140,6 +140,14 @@ export class Send extends IronfishCommand { } } + const assetData = ( + await client.wallet.getAsset({ + account: from, + id: assetId, + confirmations: flags.confirmations, + }) + ).content + if (amount == null) { amount = await promptCurrency({ client: client, @@ -150,7 +158,7 @@ export class Send extends IronfishCommand { balance: { account: from, confirmations: flags.confirmations, - assetId, + asset: assetData, }, }) } @@ -237,7 +245,7 @@ export class Send extends IronfishCommand { this.exit(0) } - displayTransactionSummary(raw, assetId, amount, from, to, memo) + displayTransactionSummary(raw, assetData, amount, from, to, memo) if (!flags.confirm) { const confirmed = await CliUx.ux.confirm('Do you confirm (Y/N)?') @@ -268,9 +276,11 @@ export class Send extends IronfishCommand { this.warn(`Transaction '${transaction.hash().toString('hex')}' failed to broadcast`) } - this.log(`Sent ${CurrencyUtils.renderIron(amount, true, assetId)} to ${to} from ${from}`) + const renderedAmount = CurrencyUtils.render(amount, true, assetData) + const renderedFee = CurrencyUtils.render(transaction.fee(), true) + this.log(`Sent ${renderedAmount} to ${to} from ${from}`) this.log(`Hash: ${transaction.hash().toString('hex')}`) - this.log(`Fee: ${CurrencyUtils.renderIron(transaction.fee(), true)}`) + this.log(`Fee: ${renderedFee}`) this.log(`Memo: ${memo}`) const networkId = (await client.chain.getNetworkInfo()).content.networkId diff --git a/ironfish-cli/src/commands/wallet/transaction/index.ts b/ironfish-cli/src/commands/wallet/transaction/index.ts index aa392506d6..64bc1b3b17 100644 --- a/ironfish-cli/src/commands/wallet/transaction/index.ts +++ b/ironfish-cli/src/commands/wallet/transaction/index.ts @@ -55,12 +55,13 @@ export class TransactionCommand extends IronfishCommand { Assert.isNotUndefined(response.content.transaction.notes) Assert.isNotUndefined(response.content.transaction.spends) + const renderedFee = CurrencyUtils.render(response.content.transaction.fee, true) this.log(`Transaction: ${hash}`) this.log(`Account: ${response.content.account}`) this.log(`Status: ${response.content.transaction.status}`) this.log(`Type: ${response.content.transaction.type}`) this.log(`Timestamp: ${TimeUtils.renderString(response.content.transaction.timestamp)}`) - this.log(`Fee: ${CurrencyUtils.renderIron(response.content.transaction.fee, true)}`) + this.log(`Fee: ${renderedFee}`) if (response.content.transaction.blockHash && response.content.transaction.blockSequence) { this.log(`Block Hash: ${response.content.transaction.blockHash}`) this.log(`Block Sequence: ${response.content.transaction.blockSequence}`) @@ -93,7 +94,7 @@ export class TransactionCommand extends IronfishCommand { CliUx.ux.table(noteAssetPairs, { amount: { header: 'Amount', - get: ({ note }) => CurrencyUtils.renderIron(note.value), + get: ({ asset, note }) => CurrencyUtils.render(note.value, false, asset), }, assetName: { header: 'Asset Name', diff --git a/ironfish-cli/src/commands/wallet/transactions.ts b/ironfish-cli/src/commands/wallet/transactions.ts index 5ea906949a..9607fdc24a 100644 --- a/ironfish-cli/src/commands/wallet/transactions.ts +++ b/ironfish-cli/src/commands/wallet/transactions.ts @@ -198,6 +198,8 @@ export class TransactionsCommand extends IronfishCommand { const amount = BigInt(note.value) const assetId = note.assetId const assetName = assetLookup[note.assetId].name + const assetDecimals = assetLookup[note.assetId].decimals + const assetSymbol = assetLookup[note.assetId].symbol const sender = note.sender const recipient = note.owner const memo = note.memo @@ -211,6 +213,8 @@ export class TransactionsCommand extends IronfishCommand { group, assetId, assetName, + assetDecimals, + assetSymbol, amount, feePaid, sender, @@ -222,6 +226,8 @@ export class TransactionsCommand extends IronfishCommand { group, assetId, assetName, + assetDecimals, + assetSymbol, amount, sender, recipient, @@ -283,14 +289,18 @@ export class TransactionsCommand extends IronfishCommand { feePaid: { header: 'Fee Paid ($IRON)', get: (row) => - row.feePaid && row.feePaid !== 0n ? CurrencyUtils.renderIron(row.feePaid) : '', + row.feePaid && row.feePaid !== 0n ? CurrencyUtils.render(row.feePaid) : '', }, ...TableCols.asset({ extended, format }), amount: { header: 'Amount', get: (row) => { Assert.isNotUndefined(row.amount) - return CurrencyUtils.renderIron(row.amount) + return CurrencyUtils.render(row.amount, false, { + id: row.assetId, + decimals: row.assetDecimals, + symbol: row.assetSymbol, + }) }, minWidth: 16, }, @@ -347,6 +357,8 @@ type TransactionRow = { hash: string assetId: string assetName: string + assetDecimals?: number + assetSymbol?: string amount: bigint feePaid?: bigint notesCount: number diff --git a/ironfish-cli/src/utils/asset.ts b/ironfish-cli/src/utils/asset.ts index c8b43f84c8..50485ceff5 100644 --- a/ironfish-cli/src/utils/asset.ts +++ b/ironfish-cli/src/utils/asset.ts @@ -134,9 +134,12 @@ export async function selectAsset( const choices = balances.map((balance) => { const assetName = BufferUtils.toHuman(Buffer.from(assetLookup[balance.assetId].name, 'hex')) - const name = `${balance.assetId} (${assetName}) (${CurrencyUtils.renderIron( + const renderedAvailable = CurrencyUtils.render( balance.available, - )})` + false, + assetLookup[balance.assetId], + ) + const name = `${balance.assetId} (${assetName}) (${renderedAvailable})` const value = { id: balance.assetId, diff --git a/ironfish-cli/src/utils/currency.ts b/ironfish-cli/src/utils/currency.ts index c81cc9518e..a3359e7802 100644 --- a/ironfish-cli/src/utils/currency.ts +++ b/ironfish-cli/src/utils/currency.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Asset } from '@ironfish/rust-nodejs' -import { Assert, CurrencyUtils, Logger, RpcClient } from '@ironfish/sdk' +import { Assert, CurrencyUtils, Logger, RpcAsset, RpcClient } from '@ironfish/sdk' import { CliUx } from '@oclif/core' /** @@ -18,7 +18,7 @@ export async function promptCurrency(options: { minimum?: bigint balance?: { account?: string - assetId?: string + asset?: RpcAsset confirmations?: number } }): Promise @@ -31,7 +31,7 @@ export async function promptCurrency(options: { minimum?: bigint balance?: { account?: string - assetId?: string + asset?: RpcAsset confirmations?: number } }): Promise { @@ -40,11 +40,16 @@ export async function promptCurrency(options: { if (options.balance) { const balance = await options.client.wallet.getAccountBalance({ account: options.balance.account, - assetId: options.balance.assetId ?? Asset.nativeId().toString('hex'), + assetId: options.balance.asset?.id ?? Asset.nativeId().toString('hex'), confirmations: options.balance.confirmations, }) - text += ` (balance ${CurrencyUtils.renderIron(balance.content.available)})` + const renderedAvailable = CurrencyUtils.render( + balance.content.available, + false, + options.balance.asset, + ) + text += ` (balance ${renderedAvailable})` } // eslint-disable-next-line no-constant-condition @@ -67,7 +72,12 @@ export async function promptCurrency(options: { Assert.isNotNull(amount) if (options.minimum != null && amount < options.minimum) { - options.logger.error(`Error: Minimum is ${CurrencyUtils.renderIron(options.minimum)}`) + const renderedMinimum = CurrencyUtils.render( + options.minimum, + false, + options.balance?.asset, + ) + options.logger.error(`Error: Minimum is ${renderedMinimum}`) continue } diff --git a/ironfish-cli/src/utils/fees.ts b/ironfish-cli/src/utils/fees.ts index f875196740..bfe13908d5 100644 --- a/ironfish-cli/src/utils/fees.ts +++ b/ironfish-cli/src/utils/fees.ts @@ -2,7 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { Asset } from '@ironfish/rust-nodejs' import { Assert, CreateTransactionRequest, @@ -81,7 +80,6 @@ export async function selectFee(options: { balance: { account: options.account, confirmations: options.confirmations, - assetId: Asset.nativeId().toString('hex'), }, }) @@ -137,7 +135,7 @@ function getChoiceFromTx( value: RawTransaction | null } { return { - name: `${name} ${transaction ? CurrencyUtils.renderIron(transaction.fee) : ''}`, + name: `${name} ${transaction ? CurrencyUtils.render(transaction.fee) : ''}`, disabled: transaction ? false : 'Not enough $IRON', value: transaction, } diff --git a/ironfish-cli/src/utils/note.ts b/ironfish-cli/src/utils/note.ts index 969e8f2139..60ba372799 100644 --- a/ironfish-cli/src/utils/note.ts +++ b/ironfish-cli/src/utils/note.ts @@ -7,6 +7,7 @@ import { Assert, RpcClient } from '@ironfish/sdk' export async function fetchNotes(client: RpcClient, account: string, notesToCombine: number) { const noteSize = await getNoteTreeSize(client) + // TODO(mat): We need to add asset support here for bridges, etc. const getNotesResponse = await client.wallet.getNotes({ account, pageSize: notesToCombine, diff --git a/ironfish-cli/src/utils/transaction.ts b/ironfish-cli/src/utils/transaction.ts index 2af15d4e26..af41fec15c 100644 --- a/ironfish-cli/src/utils/transaction.ts +++ b/ironfish-cli/src/utils/transaction.ts @@ -5,9 +5,11 @@ import { createRootLogger, CurrencyUtils, + GetUnsignedTransactionNotesResponse, Logger, PromiseUtils, RawTransaction, + RpcAsset, RpcClient, TimeUtils, TransactionStatus, @@ -15,6 +17,7 @@ import { } from '@ironfish/sdk' import { CliUx } from '@oclif/core' import { ProgressBar } from '../types' +import { getAssetsByIDs } from './asset' export class TransactionTimer { private progressBar: ProgressBar | undefined @@ -91,6 +94,17 @@ export async function renderUnsignedTransactionDetails( ): Promise { logger = logger ?? createRootLogger() + let response + if (unsignedTransaction.notes.length > 0) { + response = await client.wallet.getUnsignedTransactionNotes({ + account, + unsignedTransaction: unsignedTransaction.serialize().toString('hex'), + }) + } + + const assetIds = collectAssetIds(unsignedTransaction, response?.content) + const assetLookup = await getAssetsByIDs(client, assetIds, account, undefined) + if (unsignedTransaction.mints.length > 0) { logger.log('') logger.log('==================') @@ -103,9 +117,14 @@ export async function renderUnsignedTransactionDetails( } logger.log('') + const renderedAmount = CurrencyUtils.render( + mint.value, + false, + assetLookup[mint.asset.id().toString('hex')], + ) logger.log(`Asset ID: ${mint.asset.id().toString('hex')}`) logger.log(`Name: ${mint.asset.name().toString('utf8')}`) - logger.log(`Amount: ${CurrencyUtils.renderIron(mint.value, false)}`) + logger.log(`Amount: ${renderedAmount}`) if (mint.transferOwnershipTo) { logger.log( @@ -130,8 +149,13 @@ export async function renderUnsignedTransactionDetails( } logger.log('') + const renderedAmount = CurrencyUtils.render( + burn.value, + false, + assetLookup[burn.assetId.toString('hex')], + ) logger.log(`Asset ID: ${burn.assetId.toString('hex')}`) - logger.log(`Amount: ${CurrencyUtils.renderIron(burn.value, false)}`) + logger.log(`Amount: ${renderedAmount}`) logger.log('') } } @@ -158,7 +182,8 @@ export async function renderUnsignedTransactionDetails( } logger.log('') - logger.log(`Amount: ${CurrencyUtils.renderIron(note.value, true, note.assetId)}`) + const renderedAmount = CurrencyUtils.render(note.value, true, assetLookup[note.assetId]) + logger.log(`Amount: ${renderedAmount}`) logger.log(`Memo: ${note.memo}`) logger.log(`Recipient: ${note.owner}`) logger.log(`Sender: ${note.sender}`) @@ -176,7 +201,8 @@ export async function renderUnsignedTransactionDetails( } logger.log('') - logger.log(`Amount: ${CurrencyUtils.renderIron(note.value, true, note.assetId)}`) + const renderedAmount = CurrencyUtils.render(note.value, true, assetLookup[note.assetId]) + logger.log(`Amount: ${renderedAmount}`) logger.log(`Memo: ${note.memo}`) logger.log(`Recipient: ${note.owner}`) logger.log(`Sender: ${note.sender}`) @@ -196,7 +222,7 @@ export async function renderUnsignedTransactionDetails( export function displayTransactionSummary( transaction: RawTransaction, - assetId: string, + asset: RpcAsset, amount: bigint, from: string, to: string, @@ -205,8 +231,8 @@ export function displayTransactionSummary( ): void { logger = logger ?? createRootLogger() - const amountString = CurrencyUtils.renderIron(amount, true, assetId) - const feeString = CurrencyUtils.renderIron(transaction.fee, true) + const amountString = CurrencyUtils.render(amount, true, asset) + const feeString = CurrencyUtils.render(transaction.fee, true) const summary = `\ \nTRANSACTION SUMMARY: @@ -310,3 +336,30 @@ export async function watchTransaction(options: { } } } + +function collectAssetIds( + unsignedTransaction: UnsignedTransaction, + notes?: GetUnsignedTransactionNotesResponse, +): string[] { + const assetIds = new Set() + + for (const mint of unsignedTransaction.mints) { + assetIds.add(mint.asset.id().toString('hex')) + } + + for (const burn of unsignedTransaction.burns) { + assetIds.add(burn.assetId.toString('hex')) + } + + if (notes) { + for (const receivedNote of notes.receivedNotes) { + assetIds.add(receivedNote.assetId) + } + + for (const sentNotes of notes.sentNotes) { + assetIds.add(sentNotes.assetId) + } + } + + return Array.from(assetIds) +} diff --git a/ironfish/src/assets/assetsVerificationApi.ts b/ironfish/src/assets/assetsVerificationApi.ts index dfed5c2df6..321154eccd 100644 --- a/ironfish/src/assets/assetsVerificationApi.ts +++ b/ironfish/src/assets/assetsVerificationApi.ts @@ -5,7 +5,7 @@ import axios, { AxiosAdapter, AxiosError, AxiosRequestConfig, AxiosResponse } fr import url, { URL } from 'url' import { FileSystem } from '../fileSystems' -type AssetData = { +export type AssetData = { identifier: string symbol: string decimals?: number @@ -52,6 +52,13 @@ export class VerifiedAssets { } return this.assets.has(assetId) } + + getAssetData(assetId: Buffer | string): AssetData | undefined { + if (!(typeof assetId === 'string')) { + assetId = assetId.toString('hex') + } + return this.assets.get(assetId) + } } // `ExportedVerifiedAssets` may seem redundant, given that it duplicates the diff --git a/ironfish/src/assets/assetsVerifier.ts b/ironfish/src/assets/assetsVerifier.ts index 1f3906f2d7..8de0aa541b 100644 --- a/ironfish/src/assets/assetsVerifier.ts +++ b/ironfish/src/assets/assetsVerifier.ts @@ -7,7 +7,7 @@ import { createRootLogger, Logger } from '../logger' import { ErrorUtils } from '../utils' import { SetIntervalToken } from '../utils' import { Retry } from '../utils' -import { AssetsVerificationApi, VerifiedAssets } from './assetsVerificationApi' +import { AssetData, AssetsVerificationApi, VerifiedAssets } from './assetsVerificationApi' export type AssetVerification = { status: 'verified' | 'unverified' | 'unknown' @@ -114,4 +114,8 @@ export class AssetsVerifier { return { status: 'unverified' } } } + + getAssetData(assetId: Buffer | string): AssetData | undefined { + return this.verifiedAssets?.getAssetData(assetId) + } } diff --git a/ironfish/src/mining/webhooks/webhookNotifier.ts b/ironfish/src/mining/webhooks/webhookNotifier.ts index c6ab70e244..e636b1e54a 100644 --- a/ironfish/src/mining/webhooks/webhookNotifier.ts +++ b/ironfish/src/mining/webhooks/webhookNotifier.ts @@ -54,10 +54,11 @@ export abstract class WebhookNotifier { ): void { const total = outputs.reduce((m, c) => BigInt(c.amount) + m, BigInt(0)) + const renderedValue = CurrencyUtils.render(total, true) this.sendText( `Successfully created payout of ${shareCount} shares to ${ outputs.length - } users for ${CurrencyUtils.renderIron(total, true)} in transaction ${ + } users for ${renderedValue} in transaction ${ this.explorer?.getTransactionUrl(transactionHashHex) ?? `\`${transactionHashHex}\`` }. Transaction pending (${payoutPeriodId})`, ) @@ -76,10 +77,9 @@ export abstract class WebhookNotifier { ): void { const total = outputs.reduce((m, c) => BigInt(c.amount) + m, BigInt(0)) + const renderedValue = CurrencyUtils.render(total, true) this.sendText( - `Creating payout of ${shareCount} shares to ${ - outputs.length - } users for ${CurrencyUtils.renderIron(total, true)} (${payoutPeriodId})`, + `Creating payout of ${shareCount} shares to ${outputs.length} users for ${renderedValue} (${payoutPeriodId})`, ) } diff --git a/ironfish/src/primitives/rawTransaction.ts b/ironfish/src/primitives/rawTransaction.ts index 4343c3bd9c..9f24f5b8e7 100644 --- a/ironfish/src/primitives/rawTransaction.ts +++ b/ironfish/src/primitives/rawTransaction.ts @@ -132,10 +132,10 @@ export class RawTransaction { for (const mint of this.mints) { if (mint.value > MAX_MINT_OR_BURN_VALUE) { + const renderedValue = CurrencyUtils.renderOre(mint.value) + const renderedMax = CurrencyUtils.renderOre(MAX_MINT_OR_BURN_VALUE) throw new Error( - `Cannot post transaction. Mint value ${CurrencyUtils.renderIron( - mint.value, - )} exceededs maximum ${CurrencyUtils.renderIron(MAX_MINT_OR_BURN_VALUE)}. `, + `Cannot post transaction. Mint value ${renderedValue} exceededs maximum ${renderedMax}.`, ) } const asset = new Asset(mint.creator, mint.name, mint.metadata) @@ -145,10 +145,10 @@ export class RawTransaction { for (const burn of this.burns) { if (burn.value > MAX_MINT_OR_BURN_VALUE) { + const renderedValue = CurrencyUtils.renderOre(burn.value) + const renderedMax = CurrencyUtils.renderOre(MAX_MINT_OR_BURN_VALUE) throw new Error( - `Cannot post transaction. Burn value ${CurrencyUtils.renderIron( - burn.value, - )} exceededs maximum ${CurrencyUtils.renderIron(MAX_MINT_OR_BURN_VALUE)}`, + `Cannot post transaction. Burn value ${renderedValue} exceededs maximum ${renderedMax}`, ) } diff --git a/ironfish/src/rpc/routes/chain/getAsset.ts b/ironfish/src/rpc/routes/chain/getAsset.ts index 1a7135bf89..edb2823dee 100644 --- a/ironfish/src/rpc/routes/chain/getAsset.ts +++ b/ironfish/src/rpc/routes/chain/getAsset.ts @@ -73,7 +73,9 @@ routes.register( throw new RpcNotFoundError(`No asset found with identifier ${request.data.id}`) } - request.end({ + const verification = node.assetsVerifier.verify(asset.id) + + const payload: RpcAsset = { createdTransactionHash: asset.createdTransactionHash.toString('hex'), id: asset.id.toString('hex'), metadata: asset.metadata.toString('hex'), @@ -83,7 +85,21 @@ routes.register( owner: asset.owner.toString('hex'), supply: CurrencyUtils.encode(asset.supply), status: await getAssetStatus(node, asset), - verification: node.assetsVerifier.verify(asset.id), - }) + verification, + } + + // TODO(mat): Make sure this logic is added for all occurrences of RpcAsset, or + // at least the ones that make sense. Encapsulate in helper function? + if (verification.status === 'verified') { + const additionalFields = node.assetsVerifier.getAssetData(asset.id) + if (additionalFields !== undefined) { + payload.symbol = additionalFields.symbol + payload.decimals = additionalFields.decimals + payload.logoURI = additionalFields.logoURI + payload.website = additionalFields.website + } + } + + request.end(payload) }, ) diff --git a/ironfish/src/rpc/routes/types.ts b/ironfish/src/rpc/routes/types.ts index d2dc4ef347..1d46f2a26f 100644 --- a/ironfish/src/rpc/routes/types.ts +++ b/ironfish/src/rpc/routes/types.ts @@ -79,6 +79,10 @@ export type RpcAsset = { metadata: string createdTransactionHash: string verification: AssetVerification + symbol?: string + decimals?: number + logoURI?: string + website?: string supply?: string /** * @deprecated query for the transaction to find it's status diff --git a/ironfish/src/rpc/routes/wallet/createTransaction.ts b/ironfish/src/rpc/routes/wallet/createTransaction.ts index d0f2ee1114..cd4082a243 100644 --- a/ironfish/src/rpc/routes/wallet/createTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/createTransaction.ts @@ -112,7 +112,7 @@ routes.register => { - AssertHasRpcContext(request, node, 'wallet') + AssertHasRpcContext(request, node, 'wallet', 'assetsVerifier') const account = getAccount(node.wallet, request.data.account) @@ -227,7 +227,12 @@ routes.register( throw new RpcNotFoundError(`No asset found with identifier ${request.data.id}`) } - request.end({ + const verification = node.assetsVerifier.verify(asset.id) + + const payload: RpcAsset = { createdTransactionHash: asset.createdTransactionHash.toString('hex'), creator: asset.creator.toString('hex'), owner: asset.owner.toString('hex'), @@ -63,7 +65,19 @@ routes.register( confirmations: request.data.confirmations, }), supply: asset.supply ? CurrencyUtils.encode(asset.supply) : undefined, - verification: node.assetsVerifier.verify(asset.id), - }) + verification, + } + + if (verification.status === 'verified') { + const additionalFields = node.assetsVerifier.getAssetData(asset.id) + if (additionalFields !== undefined) { + payload.symbol = additionalFields.symbol + payload.decimals = additionalFields.decimals + payload.logoURI = additionalFields.logoURI + payload.website = additionalFields.website + } + } + + request.end(payload) }, ) diff --git a/ironfish/src/rpc/utils/asset.ts b/ironfish/src/rpc/utils/asset.ts new file mode 100644 index 0000000000..b664dbf0cb --- /dev/null +++ b/ironfish/src/rpc/utils/asset.ts @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { AssetsVerifier } from '../../assets/assetsVerifier' +import { RpcAsset } from '../routes/types' + +export function rpcAssetWithVerifiedMetadata( + asset: RpcAsset, + assetsVerifier: AssetsVerifier, +): RpcAsset { + asset.verification = assetsVerifier.verify(asset.id) + + if (asset.verification.status === 'verified') { + const additionalFields = assetsVerifier.getAssetData(asset.id) + if (additionalFields !== undefined) { + asset.symbol = additionalFields.symbol + asset.decimals = additionalFields.decimals + asset.logoURI = additionalFields.logoURI + asset.website = additionalFields.website + } + } + + return asset +} diff --git a/ironfish/src/rpc/utils/index.ts b/ironfish/src/rpc/utils/index.ts new file mode 100644 index 0000000000..952e7df1aa --- /dev/null +++ b/ironfish/src/rpc/utils/index.ts @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +export * from './asset' diff --git a/ironfish/src/utils/currency.test.ts b/ironfish/src/utils/currency.test.ts index 505097d130..3597c10c20 100644 --- a/ironfish/src/utils/currency.test.ts +++ b/ironfish/src/utils/currency.test.ts @@ -1,6 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { Asset } from '@ironfish/rust-nodejs' import { CurrencyUtils } from './currency' describe('CurrencyUtils', () => { @@ -42,6 +43,64 @@ describe('CurrencyUtils', () => { expect(CurrencyUtils.decodeIron('0.00000999')).toBe(999n) }) + describe('render', () => { + // Randomly generated custom asset ID + const assetId = '1a75bf033c1c1925cfcd1a77461364e77c6e861c2a3acabaf9e398e980146651' + + it('should render iron with no extra parameters with 8 decimal places', () => { + expect(CurrencyUtils.render(0n)).toEqual('0.00000000') + expect(CurrencyUtils.render(1n)).toEqual('0.00000001') + expect(CurrencyUtils.render(100n)).toEqual('0.00000100') + expect(CurrencyUtils.render(10000n)).toEqual('0.00010000') + expect(CurrencyUtils.render(100000000n)).toEqual('1.00000000') + }) + + it('should include IRON for the iron asset ticker', () => { + expect(CurrencyUtils.render(1n, true)).toEqual('$IRON 0.00000001') + }) + + it('should still render iron with 8 decimals and $IRON symbol even if parameters are given', () => { + expect( + CurrencyUtils.render(1n, false, { + id: Asset.nativeId().toString('hex'), + decimals: 2, + symbol: 'FOO', + }), + ).toEqual('0.00000001') + expect( + CurrencyUtils.render(1n, true, { + id: Asset.nativeId().toString('hex'), + decimals: 2, + symbol: 'FOO', + }), + ).toEqual('$IRON 0.00000001') + }) + + it('should render an asset value with 0 decimals by default', () => { + expect(CurrencyUtils.render(1n, false, { id: assetId })).toEqual('1') + expect(CurrencyUtils.render(1n, true, { id: assetId })).toEqual(`${assetId} 1`) + }) + + it('should render an asset value with the given decimals and symbol', () => { + expect(CurrencyUtils.render(1n, false, { id: assetId, decimals: 2 })).toEqual('0.01') + expect(CurrencyUtils.render(1n, true, { id: assetId, decimals: 2 })).toEqual( + `${assetId} 0.01`, + ) + + expect(CurrencyUtils.render(100n, false, { id: assetId, decimals: 2 })).toEqual('1.00') + expect(CurrencyUtils.render(100n, true, { id: assetId, decimals: 2 })).toEqual( + `${assetId} 1.00`, + ) + + expect( + CurrencyUtils.render(100n, false, { id: assetId, decimals: 2, symbol: 'FOO' }), + ).toEqual('1.00') + expect( + CurrencyUtils.render(100n, true, { id: assetId, decimals: 2, symbol: 'FOO' }), + ).toEqual(`FOO 1.00`) + }) + }) + it('renderIron', () => { expect(CurrencyUtils.renderIron(0n)).toEqual('0.00000000') expect(CurrencyUtils.renderIron(1n)).toEqual('0.00000001') diff --git a/ironfish/src/utils/currency.ts b/ironfish/src/utils/currency.ts index e12f187e82..1e710008c6 100644 --- a/ironfish/src/utils/currency.ts +++ b/ironfish/src/utils/currency.ts @@ -58,6 +58,54 @@ export class CurrencyUtils { return amount.toString() } + /** + * Renders values for human-readable purposes: + * - Renders $IRON in the major denomination, with 8 decimal places + * - If a custom asset, and `decimals` is provided, it will render the custom + * asset in the major denomination with the decimal places + * - If a custom asset, and `decimals` is not provided, it will render the + * custom asset in the minor denomination with no decimal places + */ + static render( + amount: bigint | string, + includeSymbol: boolean = false, + asset?: { + id?: string + decimals?: number + symbol?: string + }, + ): string { + if (typeof amount === 'string') { + amount = this.decode(amount) + } + + // If an asset ID was provided, check if it is the native asset. Otherwise, + // we can only assume that the amount is in the native asset + const isNativeAsset = asset?.id ? isNativeIdentifier(asset?.id) : true + + // Default to displaying 0 decimal places for custom assets + let decimals = 0 + if (isNativeAsset) { + // Hard-code the amount of decimals in the native asset + decimals = IRON_DECIMAL_PLACES + } else if (asset?.decimals) { + decimals = asset.decimals + } + + const majorDenominationAmount = FixedNumberUtils.render(amount, decimals) + + if (includeSymbol) { + let symbol = '$IRON' + + if (asset?.id && !isNativeAsset) { + symbol = asset?.symbol || asset?.id + } + return `${symbol} ${majorDenominationAmount}` + } + + return majorDenominationAmount + } + /* * Renders ore as iron for human-readable purposes */ @@ -115,6 +163,7 @@ export function isParseFixedError(error: unknown): error is ParseFixedError { ) } +const IRON_DECIMAL_PLACES = 8 export const ORE_TO_IRON = 100000000 export const MINIMUM_ORE_AMOUNT = 0n export const MAXIMUM_ORE_AMOUNT = 2n ** 64n diff --git a/ironfish/src/wallet/errors.ts b/ironfish/src/wallet/errors.ts index 3fe3243ebe..fd0e559874 100644 --- a/ironfish/src/wallet/errors.ts +++ b/ironfish/src/wallet/errors.ts @@ -7,16 +7,19 @@ import { CurrencyUtils } from '../utils' export class NotEnoughFundsError extends Error { name = this.constructor.name + assetId: string + amount: bigint + amountNeeded: bigint constructor(assetId: Buffer, amount: bigint, amountNeeded: bigint) { super() - this.message = `Insufficient funds: Needed ${CurrencyUtils.renderIron( - amountNeeded, - true, - assetId.toString('hex'), - )} but have ${CurrencyUtils.renderIron( - amount, - )} available to spend. Please fund your account and/or wait for any pending transactions to be confirmed.'` + this.assetId = assetId.toString('hex') + this.amount = amount + this.amountNeeded = amountNeeded + + const renderedAmountNeeded = CurrencyUtils.render(amountNeeded, true, { id: this.assetId }) + const renderedAmount = CurrencyUtils.render(amount) + this.message = `Insufficient funds: Needed ${renderedAmountNeeded} but have ${renderedAmount} available to spend. Please fund your account and/or wait for any pending transactions to be confirmed.'` } }