/
getNFTokenID.ts
99 lines (92 loc) · 3.71 KB
/
getNFTokenID.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { decode } from 'ripple-binary-codec'
import { NFToken } from '../models/ledger/NFTokenPage'
import {
CreatedNode,
isCreatedNode,
isModifiedNode,
ModifiedNode,
TransactionMetadata,
} from '../models/transactions/metadata'
/**
* 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 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.
*/
// eslint-disable-next-line max-lines-per-function -- This function has a lot of documentation
export default function getNFTokenID(
meta: TransactionMetadata | string | undefined,
): string | undefined {
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,
* the created node needs to be linked to a third page, resulting
* in modifying that third page's PreviousPageMin or NextPageMin
* field changing, but no NFTs within that page changing. In this
* case, there will be no previous NFTs and we need to skip.
* However, there will always be NFTs listed in the final fields,
* as rippled outputs all fields in final fields even if they were
* not changed. Thus why we add the additional condition to check
* if the PreviousFields contains NFTokens
*/
const affectedNodes = decodedMeta.AffectedNodes.filter((node) => {
if (isCreatedNode(node)) {
return node.CreatedNode.LedgerEntryType === 'NFTokenPage'
}
if (isModifiedNode(node)) {
return (
node.ModifiedNode.LedgerEntryType === 'NFTokenPage' &&
Boolean(node.ModifiedNode.PreviousFields?.NFTokens)
)
}
return false
})
/* eslint-disable @typescript-eslint/consistent-type-assertions -- Necessary for parsing metadata */
const previousTokenIDSet = new Set(
affectedNodes
.flatMap((node) => {
const nftokens = isModifiedNode(node)
? (node.ModifiedNode.PreviousFields?.NFTokens as NFToken[])
: []
return nftokens.map((token) => token.NFToken.NFTokenID)
})
.filter((id) => Boolean(id)),
)
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- Cleaner to read */
const finalTokenIDs = affectedNodes
.flatMap((node) =>
(
(((node as ModifiedNode).ModifiedNode?.FinalFields?.NFTokens ??
(node as CreatedNode).CreatedNode?.NewFields
?.NFTokens) as NFToken[]) ?? []
).map((token) => token.NFToken.NFTokenID),
)
.filter((nftokenID) => Boolean(nftokenID))
/* eslint-enable @typescript-eslint/consistent-type-assertions -- Necessary for parsing metadata */
/* eslint-enable @typescript-eslint/no-unnecessary-condition -- Cleaner to read */
const nftokenID = finalTokenIDs.find((id) => !previousTokenIDSet.has(id))
return nftokenID
}