diff --git a/packages/xrpl/HISTORY.md b/packages/xrpl/HISTORY.md index 7c4b81d0..a73c816b 100644 --- a/packages/xrpl/HISTORY.md +++ b/packages/xrpl/HISTORY.md @@ -13,6 +13,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr * Adds support for npm v9 ### Fixed +* `getNFTokenID` now also accepts metadata from `tx` in binary format * Fixed `ServerState.transitions` typing, it is now a string instead of a number. (Only used in return from `server_state` request) * Added `destination_amount` to `PathOption` which is returned as part of a `path_find` request * Removed the `decode(encode(tx)) == tx` check from the wallet signing process diff --git a/packages/xrpl/snippets/src/getTransaction.ts b/packages/xrpl/snippets/src/getTransaction.ts index eb768094..11bfe1b8 100644 --- a/packages/xrpl/snippets/src/getTransaction.ts +++ b/packages/xrpl/snippets/src/getTransaction.ts @@ -19,10 +19,11 @@ async function getTransaction(): Promise { }) console.log(tx) - // The meta field would be a string(hex) when the `binary` parameter is `true` for the `tx` request. + // The meta field can be undefined if the transaction has not been validated yet (and so has not changed the ledger). if (tx.result.meta == null) { throw new Error('meta not included in the response') } + /* * delivered_amount is the amount actually received by the destination account. * Use this field to determine how much was delivered, regardless of whether the transaction is a partial payment. diff --git a/packages/xrpl/src/models/methods/tx.ts b/packages/xrpl/src/models/methods/tx.ts index 6109255e..48516eca 100644 --- a/packages/xrpl/src/models/methods/tx.ts +++ b/packages/xrpl/src/models/methods/tx.ts @@ -46,7 +46,8 @@ export interface TxResponse hash: string /** The ledger index of the ledger that includes this transaction. */ ledger_index?: number - /** Transaction metadata, which describes the results of the transaction. */ + /** Transaction metadata, which describes the results of the transaction. + * Can be undefined if a transaction has not been validated yet. */ meta?: TransactionMetadata | string /** * If true, this data comes from a validated ledger version; if omitted or. diff --git a/packages/xrpl/src/utils/getNFTokenID.ts b/packages/xrpl/src/utils/getNFTokenID.ts index eb6533aa..cd9aa6b1 100644 --- a/packages/xrpl/src/utils/getNFTokenID.ts +++ b/packages/xrpl/src/utils/getNFTokenID.ts @@ -1,4 +1,5 @@ import flatMap from 'lodash/flatMap' +import { decode } from 'ripple-binary-codec' import { CreatedNode, @@ -15,24 +16,41 @@ interface NFToken { } } +/** + * Ensures that the metadata is in a deserialized format to parse. + * + * @param meta - the metadata from a `tx` method call. Can be in json format or binary format. + * @returns the metadata in a deserialized format. + */ +function ensureDecodedMeta( + meta: TransactionMetadata | string, +): TransactionMetadata { + if (typeof meta === 'string') { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Meta is either metadata or serialized metadata. + return decode(meta) as unknown as TransactionMetadata + } + return meta +} + /** * Gets the NFTokenID for an NFT recently minted with NFTokenMint. * - * @param meta - Metadata from the response to submitting an NFTokenMint transaction. + * @param meta - Metadata from the response to submitting and waiting for an NFTokenMint transaction or from a `tx` method call. * @returns The NFTokenID for the minted NFT. * @throws if meta is not TransactionMetadata. */ export default function getNFTokenID( - meta: TransactionMetadata, + meta: TransactionMetadata | string | undefined, ): string | undefined { - /* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Provides a nicer error for js users */ - if (meta.AffectedNodes === undefined) { - throw new TypeError(`Unable to parse the parameter given to getNFTokenID. - 'meta' must be the metadata from an NFTokenMint transaction. Received ${JSON.stringify( - meta, - )} instead.`) + if (typeof meta !== 'string' && meta?.AffectedNodes === undefined) { + throw new TypeError(`Unable to parse the parameter given to getNFTokenID. + 'meta' must be the metadata from an NFTokenMint transaction. Received ${JSON.stringify( + meta, + )} instead.`) } + const decodedMeta = ensureDecodedMeta(meta) + /* * When a mint results in splitting an existing page, * it results in a created page and a modified node. Sometimes, @@ -46,7 +64,7 @@ export default function getNFTokenID( * if the PreviousFields contains NFTokens */ - const affectedNodes = meta.AffectedNodes.filter((node) => { + const affectedNodes = decodedMeta.AffectedNodes.filter((node) => { if (isCreatedNode(node)) { return node.CreatedNode.LedgerEntryType === 'NFTokenPage' } diff --git a/packages/xrpl/test/integration/transactions/nftokenMint.test.ts b/packages/xrpl/test/integration/transactions/nftokenMint.test.ts index 4ef65502..d141f41b 100644 --- a/packages/xrpl/test/integration/transactions/nftokenMint.test.ts +++ b/packages/xrpl/test/integration/transactions/nftokenMint.test.ts @@ -1,69 +1,87 @@ import { assert } from 'chai' -import _ from 'lodash' -import { Client } from 'xrpl' +import { TransactionMetadata, TxRequest } from 'xrpl' +import { convertStringToHex, getNFTokenID, NFTokenMint } from '../../../src' +import { hashSignedTx } from '../../../src/utils/hashes' +import serverUrl from '../serverUrl' import { - convertStringToHex, - getNFTokenID, - NFTokenMint, - TransactionMetadata, -} from '../../../src' + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { testTransaction } from '../utils' // how long before each test case times out const TIMEOUT = 20000 describe('NFTokenMint', function () { - // TODO: Once we update our integration tests to handle NFTs, replace this client with XrplIntegrationTestContext + let testContext: XrplIntegrationTestContext + + beforeEach(async () => { + testContext = await setupClient(serverUrl) + }) + afterEach(async () => teardownClient(testContext)) + it( 'get NFTokenID', async function () { - const client = new Client('wss://s.altnet.rippletest.net:51233/') - await client.connect() - - const { wallet, balance: _balance } = await client.fundWallet(null, { - usageContext: 'integration-test', - }) - const tx: NFTokenMint = { TransactionType: 'NFTokenMint', - Account: wallet.address, + Account: testContext.wallet.address, URI: convertStringToHex('https://www.google.com'), NFTokenTaxon: 0, } - try { - const response = await client.submitAndWait(tx, { - wallet, - }) - assert.equal(response.type, 'response') - assert.equal( - (response.result.meta as TransactionMetadata).TransactionResult, - 'tesSUCCESS', - ) + const response = await testTransaction( + testContext.client, + tx, + testContext.wallet, + ) + assert.equal(response.type, 'response') - const accountNFTs = await client.request({ - command: 'account_nfts', - account: wallet.address, - }) - - const nftokenID = - getNFTokenID(response.result.meta as TransactionMetadata) ?? - 'undefined' - const accountHasNFT = accountNFTs.result.account_nfts.some( - (value) => value.NFTokenID === nftokenID, - ) - - assert.isTrue( - accountHasNFT, - `Expected to find an NFT with NFTokenID ${nftokenID} in account ${ - wallet.address - } but did not find it. - \n\nHere's what was returned from 'account_nfts' for ${ - wallet.address - }: ${JSON.stringify(accountNFTs)}`, - ) - } finally { - await client.disconnect() + const txRequest: TxRequest = { + command: 'tx', + transaction: hashSignedTx(response.result.tx_blob), } + const txResponse = await testContext.client.request(txRequest) + + assert.equal( + (txResponse.result.meta as TransactionMetadata).TransactionResult, + 'tesSUCCESS', + ) + + const accountNFTs = await testContext.client.request({ + command: 'account_nfts', + account: testContext.wallet.address, + }) + + const nftokenID = + getNFTokenID(txResponse.result.meta as TransactionMetadata) ?? + 'undefined' + + const accountHasNFT = accountNFTs.result.account_nfts.some( + (value) => value.NFTokenID === nftokenID, + ) + + assert.isTrue( + accountHasNFT, + `Expected to find an NFT with NFTokenID ${nftokenID} in account ${ + testContext.wallet.address + } but did not find it. + \n\nHere's what was returned from 'account_nfts' for ${ + testContext.wallet.address + }: ${JSON.stringify(accountNFTs)}`, + ) + + const binaryTxResponse = await testContext.client.request({ + ...txRequest, + binary: true, + }) + + assert.equal( + nftokenID, + getNFTokenID(binaryTxResponse.result.meta) ?? 'undefined', + `getNFTokenID produced a different outcome when decoding the metadata in binary format.`, + ) }, TIMEOUT, )