diff --git a/ironfish-cli/src/commands/chain/asset.ts b/ironfish-cli/src/commands/chain/asset.ts index ae23adf160..3230a6079b 100644 --- a/ironfish-cli/src/commands/chain/asset.ts +++ b/ironfish-cli/src/commands/chain/asset.ts @@ -33,7 +33,7 @@ export default class Asset extends IronfishCommand { this.log(`Metadata: ${BufferUtils.toHuman(Buffer.from(data.content.metadata, 'hex'))}`) this.log(`Creator: ${data.content.creator}`) this.log(`Owner: ${data.content.owner}`) - this.log(`Supply: ${data.content.supply}`) + this.log(`Supply: ${data.content.supply ?? 'N/A'}`) this.log(`Identifier: ${data.content.id}`) this.log(`Transaction Created: ${data.content.createdTransactionHash}`) } diff --git a/ironfish/src/rpc/routes/chain/getAsset.ts b/ironfish/src/rpc/routes/chain/getAsset.ts index cbb66aa505..02ae72ab23 100644 --- a/ironfish/src/rpc/routes/chain/getAsset.ts +++ b/ironfish/src/rpc/routes/chain/getAsset.ts @@ -4,25 +4,19 @@ import { ASSET_ID_LENGTH } from '@ironfish/rust-nodejs' import * as yup from 'yup' import { Assert } from '../../../assert' +import { AssetValue } from '../../../blockchain/database/assetValue' import { FullNode } from '../../../node' import { CurrencyUtils } from '../../../utils' +import { AssetStatus } from '../../../wallet' import { NotFoundError, ValidationError } from '../../adapters' +import { RpcAsset, RpcAssetSchema } from '../../types' import { ApiNamespace, routes } from '../router' export type GetAssetRequest = { id: string } -export type GetAssetResponse = { - createdTransactionHash: string - id: string - metadata: string - name: string - nonce: number - creator: string - owner: string - supply: string -} +export type GetAssetResponse = RpcAsset export const GetAssetRequestSchema: yup.ObjectSchema = yup .object() @@ -31,18 +25,33 @@ export const GetAssetRequestSchema: yup.ObjectSchema = yup }) .defined() -export const GetAssetResponse: yup.ObjectSchema = yup - .object({ - createdTransactionHash: yup.string().defined(), - id: yup.string().defined(), - metadata: yup.string().defined(), - name: yup.string().defined(), - nonce: yup.number().defined(), - creator: yup.string().defined(), - owner: yup.string().defined(), - supply: yup.string().defined(), - }) - .defined() +export const GetAssetResponse: yup.ObjectSchema = RpcAssetSchema.defined() + +/** + * Note: This logic will be deprecated when we move the field `status` from the Asset response object. The status field has + * more to do with the transaction than the asset itself. + * + * @param node: FullNode + * @param asset: AssetValue + * @returns Promise + */ +async function getAssetStatus(node: FullNode, asset: AssetValue): Promise { + const blockHash = await node.chain.getBlockHashByTransactionHash(asset.createdTransactionHash) + if (!blockHash) { + return AssetStatus.UNKNOWN + } + + const blockHeader = await node.chain.getHeader(blockHash) + + if (!blockHeader) { + return AssetStatus.UNKNOWN + } + + return blockHeader.sequence + node.chain.config.get('confirmations') < + node.chain.head.sequence + ? AssetStatus.CONFIRMED + : AssetStatus.UNCONFIRMED +} routes.register( `${ApiNamespace.chain}/getAsset`, @@ -72,6 +81,8 @@ routes.register( creator: asset.creator.toString('hex'), owner: asset.owner.toString('hex'), supply: CurrencyUtils.encode(asset.supply), + status: await getAssetStatus(node, asset), + verification: node.assetsVerifier.verify(asset.id), }) }, ) diff --git a/ironfish/src/rpc/routes/chain/getTransactionStream.ts b/ironfish/src/rpc/routes/chain/getTransactionStream.ts index 47aa41085c..7810addaa5 100644 --- a/ironfish/src/rpc/routes/chain/getTransactionStream.ts +++ b/ironfish/src/rpc/routes/chain/getTransactionStream.ts @@ -15,6 +15,9 @@ import { ApiNamespace, routes } from '../router' interface Note { assetId: string + /** + * @deprecated Please use getAsset endpoint to get this information + */ assetName: string hash: string value: string @@ -22,11 +25,17 @@ interface Note { } interface Mint { assetId: string + /** + * @deprecated Please use getAsset endpoint to get this information + */ assetName: string value: string } interface Burn { assetId: string + /** + * @deprecated Please use getAsset endpoint to get this information + */ assetName: string value: string } diff --git a/ironfish/src/rpc/routes/wallet/burnAsset.test.ts b/ironfish/src/rpc/routes/wallet/burnAsset.test.ts index 725725166b..691dd8695d 100644 --- a/ironfish/src/rpc/routes/wallet/burnAsset.test.ts +++ b/ironfish/src/rpc/routes/wallet/burnAsset.test.ts @@ -2,6 +2,7 @@ * 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 } from '../../../assert' import { useAccountFixture, useMinerBlockFixture, @@ -74,6 +75,10 @@ describe('Route wallet/burnAsset', () => { }) jest.spyOn(wallet, 'burn').mockResolvedValueOnce(burnTransaction) + const accountAsset = await account.getAsset(assetId) + + Assert.isNotUndefined(accountAsset) + const response = await routeTest.client.wallet.burnAsset({ account: account.name, assetId: assetId.toString('hex'), @@ -82,6 +87,19 @@ describe('Route wallet/burnAsset', () => { }) expect(response.content).toEqual({ + asset: { + id: asset.id().toString('hex'), + metadata: asset.metadata().toString('hex'), + name: asset.name().toString('hex'), + creator: asset.creator().toString('hex'), + nonce: accountAsset.nonce ?? null, + owner: accountAsset.owner.toString('hex') ?? '', + status: await node.wallet.getAssetStatus(account, accountAsset, { + confirmations: 0, + }), + verification: node.assetsVerifier.verify(asset.id()), + createdTransactionHash: accountAsset.createdTransactionHash.toString('hex') ?? null, + }, assetId: asset.id().toString('hex'), name: asset.name().toString('hex'), hash: burnTransaction.hash().toString('hex'), diff --git a/ironfish/src/rpc/routes/wallet/burnAsset.ts b/ironfish/src/rpc/routes/wallet/burnAsset.ts index e798b4e998..012dafc911 100644 --- a/ironfish/src/rpc/routes/wallet/burnAsset.ts +++ b/ironfish/src/rpc/routes/wallet/burnAsset.ts @@ -4,6 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { CurrencyUtils, YupUtils } from '../../../utils' +import { RpcAsset, RpcAssetSchema } from '../../types' import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' @@ -18,8 +19,15 @@ export interface BurnAssetRequest { } export interface BurnAssetResponse { + asset: RpcAsset + /** + * @deprecated Please use `asset.id` instead + */ assetId: string hash: string + /** + * @deprecated Please use `asset.name` instead + */ name: string value: string } @@ -38,6 +46,7 @@ export const BurnAssetRequestSchema: yup.ObjectSchema = yup export const BurnAssetResponseSchema: yup.ObjectSchema = yup .object({ + asset: RpcAssetSchema.required(), assetId: yup.string().required(), hash: yup.string().required(), name: yup.string().required(), @@ -71,6 +80,19 @@ routes.register( const burn = transaction.burns[0] request.end({ + asset: { + id: asset.id.toString('hex'), + metadata: asset.metadata.toString('hex'), + name: asset.name.toString('hex'), + nonce: asset.nonce, + creator: asset.creator.toString('hex'), + owner: asset.owner.toString('hex'), + verification: node.assetsVerifier.verify(asset.id), + status: await node.wallet.getAssetStatus(account, asset, { + confirmations: request.data.confirmations, + }), + createdTransactionHash: asset.createdTransactionHash.toString('hex'), + }, assetId: burn.assetId.toString('hex'), hash: transaction.hash().toString('hex'), name: asset.name.toString('hex'), diff --git a/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts b/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts index 3f36339c15..b43270b880 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts @@ -5,7 +5,12 @@ import * as yup from 'yup' import { TransactionStatus, TransactionType } from '../../../wallet' import { RpcSpend, RpcSpendSchema } from '../chain' import { ApiNamespace, routes } from '../router' -import { RpcWalletNote, RpcWalletNoteSchema } from './types' +import { + RcpAccountAssetBalanceDelta, + RcpAccountAssetBalanceDeltaSchema, + RpcWalletNote, + RpcWalletNoteSchema, +} from './types' import { getAccount, getAccountDecryptedNotes, @@ -35,7 +40,7 @@ export type GetAccountTransactionResponse = { burnsCount: number timestamp: number submittedSequence: number - assetBalanceDeltas: Array<{ assetId: string; assetName: string; delta: string }> + assetBalanceDeltas: RcpAccountAssetBalanceDelta[] notes: RpcWalletNote[] spends: RpcSpend[] } | null @@ -69,17 +74,7 @@ export const GetAccountTransactionResponseSchema: yup.ObjectSchema + assetBalanceDeltas: RcpAccountAssetBalanceDelta[] notes?: RpcWalletNote[] spends?: RpcSpend[] } @@ -79,17 +84,7 @@ export const GetAccountTransactionsResponseSchema: yup.ObjectSchema { id: asset.id().toString('hex'), account: account.name, }) + + const accountAsset = await account.getAsset(asset.id()) + + Assert.isNotUndefined(accountAsset) + expect(response.content).toEqual({ createdTransactionHash: pendingMint.hash().toString('hex'), creator: account.publicAddress, @@ -119,9 +125,10 @@ describe('Route chain.getAsset', () => { metadata: asset.metadata().toString('hex'), name: asset.name().toString('hex'), nonce: asset.nonce(), - status: AssetStatus.PENDING, - supply: null, - verification: { status: 'unknown' }, + status: await node.wallet.getAssetStatus(account, accountAsset, { + confirmations: 0, + }), + verification: node.assetsVerifier.verify(asset.id()), }) }) diff --git a/ironfish/src/rpc/routes/wallet/getAsset.ts b/ironfish/src/rpc/routes/wallet/getAsset.ts index e09cbb1004..cdbc87866c 100644 --- a/ironfish/src/rpc/routes/wallet/getAsset.ts +++ b/ironfish/src/rpc/routes/wallet/getAsset.ts @@ -3,9 +3,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { ASSET_ID_LENGTH } from '@ironfish/rust-nodejs' import * as yup from 'yup' -import { AssetVerification } from '../../../assets' import { CurrencyUtils } from '../../../utils' import { NotFoundError, ValidationError } from '../../adapters' +import { RpcAsset, RpcAssetSchema } from '../../types' import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' @@ -15,19 +15,7 @@ export type GetWalletAssetRequest = { id: string } -export type GetWalletAssetResponse = { - createdTransactionHash: string - creator: string - owner: string - id: string - metadata: string - name: string - nonce: number - status: string - verification: AssetVerification - // Populated for assets the account owns - supply: string | null -} +export type GetWalletAssetResponse = RpcAsset export const GetWalletAssetRequestSchema: yup.ObjectSchema = yup .object() @@ -38,22 +26,8 @@ export const GetWalletAssetRequestSchema: yup.ObjectSchema = yup - .object({ - createdTransactionHash: yup.string().defined(), - creator: yup.string().defined(), - owner: yup.string().defined(), - id: yup.string().defined(), - metadata: yup.string().defined(), - name: yup.string().defined(), - nonce: yup.number().defined(), - status: yup.string().defined(), - verification: yup - .object({ status: yup.string().oneOf(['verified', 'unverified', 'unknown']).defined() }) - .defined(), - supply: yup.string().nullable().defined(), - }) - .defined() +export const GetWalletAssetResponse: yup.ObjectSchema = + RpcAssetSchema.defined() routes.register( `${ApiNamespace.wallet}/getAsset`, @@ -84,7 +58,7 @@ routes.register( status: await node.wallet.getAssetStatus(account, asset, { confirmations: request.data.confirmations, }), - supply: asset.supply ? CurrencyUtils.encode(asset.supply) : null, + supply: asset.supply ? CurrencyUtils.encode(asset.supply) : undefined, verification: node.assetsVerifier.verify(asset.id), }) }, diff --git a/ironfish/src/rpc/routes/wallet/getAssets.ts b/ironfish/src/rpc/routes/wallet/getAssets.ts index 382a031d7d..4f32d0d380 100644 --- a/ironfish/src/rpc/routes/wallet/getAssets.ts +++ b/ironfish/src/rpc/routes/wallet/getAssets.ts @@ -2,8 +2,8 @@ * 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 * as yup from 'yup' -import { AssetVerification } from '../../../assets' import { CurrencyUtils } from '../../../utils' +import { RpcAsset, RpcAssetSchema } from '../../types' import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' @@ -12,18 +12,7 @@ export type GetAssetsRequest = { confirmations?: number } -export type GetAssetsResponse = { - createdTransactionHash: string - id: string - metadata: string - name: string - creator: string - owner: string - nonce: number - status: string - supply?: string - verification: AssetVerification -} +export type GetAssetsResponse = RpcAsset export const GetAssetsRequestSchema: yup.ObjectSchema = yup .object() @@ -33,22 +22,8 @@ export const GetAssetsRequestSchema: yup.ObjectSchema = yup }) .defined() -export const GetAssetsResponseSchema: yup.ObjectSchema = yup - .object({ - createdTransactionHash: yup.string().defined(), - id: yup.string().defined(), - metadata: yup.string().defined(), - name: yup.string().defined(), - creator: yup.string().defined(), - owner: yup.string().defined(), - status: yup.string().defined(), - nonce: yup.number().defined(), - supply: yup.string().optional(), - verification: yup - .object({ status: yup.string().oneOf(['verified', 'unverified', 'unknown']).defined() }) - .defined(), - }) - .defined() +export const GetAssetsResponseSchema: yup.ObjectSchema = + RpcAssetSchema.defined() routes.register( `${ApiNamespace.wallet}/getAssets`, @@ -62,7 +37,6 @@ routes.register( } request.stream({ - createdTransactionHash: asset.createdTransactionHash.toString('hex'), id: asset.id.toString('hex'), metadata: asset.metadata.toString('hex'), name: asset.name.toString('hex'), @@ -74,6 +48,7 @@ routes.register( }), supply: asset.supply !== null ? CurrencyUtils.encode(asset.supply) : undefined, verification: node.assetsVerifier.verify(asset.id), + createdTransactionHash: asset.createdTransactionHash.toString('hex'), }) } diff --git a/ironfish/src/rpc/routes/wallet/getBalance.ts b/ironfish/src/rpc/routes/wallet/getBalance.ts index e3da2687e7..113ca499e2 100644 --- a/ironfish/src/rpc/routes/wallet/getBalance.ts +++ b/ironfish/src/rpc/routes/wallet/getBalance.ts @@ -18,6 +18,9 @@ export type GetBalanceRequest = export type GetBalanceResponse = { account: string assetId: string + /** + * @deprecated Please use getAsset endpoint to get this information + * */ assetVerification: AssetVerification confirmed: string unconfirmed: string diff --git a/ironfish/src/rpc/routes/wallet/getBalances.ts b/ironfish/src/rpc/routes/wallet/getBalances.ts index 562b955ce1..0ded87d1a0 100644 --- a/ironfish/src/rpc/routes/wallet/getBalances.ts +++ b/ironfish/src/rpc/routes/wallet/getBalances.ts @@ -16,9 +16,21 @@ export interface GetBalancesResponse { account: string balances: { assetId: string + /** + * @deprecated Please use getAsset endpoint to get this information + */ assetName: string + /** + * @deprecated Please use getAsset endpoint to get this information + */ assetCreator: string + /** + * @deprecated Please use getAsset endpoint to get this information + * */ assetOwner: string + /** + * @deprecated Please use getAsset endpoint to get this information + * */ assetVerification: AssetVerification confirmed: string unconfirmed: string diff --git a/ironfish/src/rpc/routes/wallet/mintAsset.test.ts b/ironfish/src/rpc/routes/wallet/mintAsset.test.ts index cf394c8816..022ba98107 100644 --- a/ironfish/src/rpc/routes/wallet/mintAsset.test.ts +++ b/ironfish/src/rpc/routes/wallet/mintAsset.test.ts @@ -2,6 +2,7 @@ * 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 } from '../../../assert' import { useAccountFixture, useMinerBlockFixture, useTxFixture } from '../../../testUtilities' import { createRouteTest } from '../../../testUtilities/routeTest' import { CurrencyUtils } from '../../../utils' @@ -80,6 +81,10 @@ describe('Route wallet/mintAsset', () => { jest.spyOn(wallet, 'mint').mockResolvedValueOnce(mintTransaction) + const accountAsset = await account.getAsset(asset.id()) + + Assert.isNotUndefined(accountAsset) + const response = await routeTest.client.wallet.mintAsset({ account: account.name, fee: '1', @@ -89,6 +94,20 @@ describe('Route wallet/mintAsset', () => { }) expect(response.content).toEqual({ + asset: { + id: asset.id().toString('hex'), + metadata: asset.metadata().toString('hex'), + name: asset.name().toString('hex'), + creator: asset.creator().toString('hex'), + nonce: asset.nonce(), + supply: undefined, + owner: accountAsset.owner.toString('hex'), + createdTransactionHash: accountAsset.createdTransactionHash.toString('hex'), + status: await node.wallet.getAssetStatus(account, accountAsset, { + confirmations: 0, + }), + verification: node.assetsVerifier.verify(asset.id()), + }, assetId: asset.id().toString('hex'), hash: mintTransaction.hash().toString('hex'), name: asset.name().toString('hex'), diff --git a/ironfish/src/rpc/routes/wallet/mintAsset.ts b/ironfish/src/rpc/routes/wallet/mintAsset.ts index cd8b476bd7..5d192ddcbf 100644 --- a/ironfish/src/rpc/routes/wallet/mintAsset.ts +++ b/ironfish/src/rpc/routes/wallet/mintAsset.ts @@ -6,6 +6,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { CurrencyUtils, YupUtils } from '../../../utils' import { MintAssetOptions } from '../../../wallet/interfaces/mintAssetOptions' +import { RpcAsset, RpcAssetSchema } from '../../types' import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' @@ -22,8 +23,15 @@ export interface MintAssetRequest { } export interface MintAssetResponse { + asset: RpcAsset + /** + * @deprecated Please use `asset.id` instead + */ assetId: string hash: string + /** + * @deprecated Please use `asset.name` instead + */ name: string value: string } @@ -44,6 +52,7 @@ export const MintAssetRequestSchema: yup.ObjectSchema = yup export const MintAssetResponseSchema: yup.ObjectSchema = yup .object({ + asset: RpcAssetSchema.defined(), assetId: yup.string().required(), hash: yup.string().required(), name: yup.string().required(), @@ -93,10 +102,26 @@ routes.register( Assert.isEqual(transaction.mints.length, 1) const mint = transaction.mints[0] + const asset = await account.getAsset(mint.asset.id()) + Assert.isNotUndefined(asset) + request.end({ - assetId: mint.asset.id().toString('hex'), + asset: { + id: asset.id.toString('hex'), + metadata: asset.metadata.toString('hex'), + name: asset.name.toString('hex'), + nonce: asset.nonce, + creator: asset.creator.toString('hex'), + owner: asset.owner.toString('hex'), + verification: node.assetsVerifier.verify(mint.asset.id()), + status: await node.wallet.getAssetStatus(account, asset, { + confirmations: request.data.confirmations, + }), + createdTransactionHash: asset.createdTransactionHash.toString('hex'), + }, + assetId: asset.id.toString('hex'), hash: transaction.hash().toString('hex'), - name: mint.asset.name().toString('hex'), + name: asset.name.toString('hex'), value: mint.value.toString(), }) }, diff --git a/ironfish/src/rpc/routes/wallet/types.ts b/ironfish/src/rpc/routes/wallet/types.ts index a6d1a9d960..2961471eba 100644 --- a/ironfish/src/rpc/routes/wallet/types.ts +++ b/ironfish/src/rpc/routes/wallet/types.ts @@ -21,13 +21,28 @@ export type RpcAccountTransaction = { export type RcpAccountAssetBalanceDelta = { assetId: string + /** + * @deprecated Please use the getAsset RPC to fetch additional asset details + */ assetName: string delta: string } +export const RcpAccountAssetBalanceDeltaSchema: yup.ObjectSchema = + yup + .object({ + assetId: yup.string().defined(), + assetName: yup.string().defined(), + delta: yup.string().defined(), + }) + .defined() + export type RpcWalletNote = { value: string assetId: string + /** + * @deprecated Please use `asset.name` instead + */ assetName: string memo: string sender: string diff --git a/ironfish/src/rpc/types.ts b/ironfish/src/rpc/types.ts new file mode 100644 index 0000000000..1d598acd9a --- /dev/null +++ b/ironfish/src/rpc/types.ts @@ -0,0 +1,39 @@ +/* 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 * as yup from 'yup' +import { AssetVerification } from '../assets' + +export type RpcAsset = { + id: string + metadata: string + name: string + nonce: number + creator: string + verification: AssetVerification + createdTransactionHash: string + owner: string + /** + * @deprecated query for the transaction to find it's status + */ + status: string + supply?: string +} + +export const RpcAssetSchema: yup.ObjectSchema = yup + .object({ + id: yup.string().required(), + metadata: yup.string().required(), + name: yup.string().required(), + nonce: yup.number().required(), + creator: yup.string().required(), + verification: yup + .object({ status: yup.string().oneOf(['verified', 'unverified', 'unknown']).defined() }) + .defined(), + status: yup.string().defined(), + supply: yup.string().optional(), + owner: yup.string().defined(), + createdTransactionHash: yup.string().defined(), + }) + .defined()