Add parseNFTokenID and tests (#1961)

* Add parseNFTokenID and tests

* Lint

* Move parseNFTokenID to utils

* Lint

* Move the NFTokenID into models

* Move NFTokenID type out of models

* Lint

* Remove extra type and lint
This commit is contained in:
Jackson Mills
2022-04-12 15:48:50 -07:00
committed by GitHub
parent 3b980d8be9
commit 587a3bbfa2
4 changed files with 111 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
### Added
* `federator_info` RPC support
* Helper method for creating a cross-chain payment to/from a sidechain
* Helper method for parsing an NFTokenID
### Fixed
* Type of TrustSet transaction edited, specifically LimitAmount property type (fixed typescript issue)

View File

@@ -38,6 +38,7 @@ import {
hashEscrow,
hashPaymentChannel,
} from './hashes'
import parseNFTokenID from './parseNFTokenID'
import {
percentToTransferRate,
decimalToTransferRate,
@@ -215,4 +216,5 @@ export {
encodeForSigning,
encodeForSigningClaim,
createCrossChainPayment,
parseNFTokenID,
}

View File

@@ -0,0 +1,81 @@
/* eslint-disable @typescript-eslint/no-magic-numbers -- Doing hex string parsing. */
import BigNumber from 'bignumber.js'
import { encodeAccountID } from 'ripple-address-codec'
import { XrplError } from '../errors'
/**
* An issuer may issue several NFTs with the same taxon; to ensure that NFTs are
* spread across multiple pages we lightly mix the taxon up by using the sequence
* (which is not under the issuer's direct control) as the seed for a simple linear
* congruential generator.
*
* From the Hull-Dobell theorem we know that f(x)=(m*x+c) mod n will yield a
* permutation of [0, n) when n is a power of 2 if m is congruent to 1 mod 4 and
* c is odd. By doing a bitwise XOR with this permutation we can scramble/unscramble
* the taxon.
*
* The XLS-20d proposal fixes m = 384160001 and c = 2459.
* We then take the modulus of 2^32 which is 4294967296.
*
* @param taxon - The scrambled or unscrambled taxon (The XOR is both the encoding and decoding)
* @param tokenSeq - The account sequence when the token was minted. Used as a psuedorandom seed.
* @returns the opposite taxon. If the taxon was scrambled it becomes unscrambled, and vice versa.
*/
function unscrambleTaxon(taxon: number, tokenSeq: number): number {
/* eslint-disable no-bitwise -- XOR is part of the encode/decode scheme. */
return (taxon ^ (384160001 * tokenSeq + 2459)) % 4294967296
/* eslint-enable no-bitwise */
}
/**
* Parses an NFTokenID into the information it is encoding.
*
* Example decoding:
*
* 000B 0539 C35B55AA096BA6D87A6E6C965A6534150DC56E5E 12C5D09E 0000000C
* +--- +--- +--------------------------------------- +------- +-------
* | | | | |
* | | | | `---> Sequence: 12
* | | | |
* | | | `---> Scrambled Taxon: 314,953,886
* | | | Unscrambled Taxon: 1337
* | | |
* | | `---> Issuer: rJoxBSzpXhPtAuqFmqxQtGKjA13jUJWthE
* | |
* | `---> TransferFee: 1337.0 bps or 13.37%
* |
* `---> Flags: 11 -> lsfBurnable, lsfOnlyXRP and lsfTransferable
*
* @param tokenID - A hex string which identifies an NFToken on the ledger.
* @throws XrplError when given an invalid tokenID.
* @returns a decoded tokenID with all fields encoded within.
*/
export default function parseNFTokenID(tokenID: string): {
TokenID: string
Flags: number
TransferFee: number
Issuer: string
Taxon: number
Sequence: number
} {
const expectedLength = 64
if (tokenID.length !== expectedLength) {
throw new XrplError(`Attempting to parse a tokenID with length ${tokenID.length}
, but expected a token with length ${expectedLength}`)
}
const scrambledTaxon = new BigNumber(tokenID.substring(48, 56), 16).toNumber()
const sequence = new BigNumber(tokenID.substring(56, 64), 16).toNumber()
const NFTokenIDData = {
TokenID: tokenID,
Flags: new BigNumber(tokenID.substring(0, 4), 16).toNumber(),
TransferFee: new BigNumber(tokenID.substring(4, 8), 16).toNumber(),
Issuer: encodeAccountID(Buffer.from(tokenID.substring(8, 48), 'hex')),
Taxon: unscrambleTaxon(scrambledTaxon, sequence),
Sequence: sequence,
}
return NFTokenIDData
}

View File

@@ -0,0 +1,27 @@
import { assert } from 'chai'
import { parseNFTokenID } from 'xrpl-local'
import { assertResultMatch } from '../testUtils'
describe('parseNFTokenID', function () {
it('decode a valid NFTokenID', function () {
const tokenID =
'000B0539C35B55AA096BA6D87A6E6C965A6534150DC56E5E12C5D09E0000000C'
const result = parseNFTokenID(tokenID)
const expected = {
TokenID: tokenID,
Flags: 11,
TransferFee: 1337,
Issuer: 'rJoxBSzpXhPtAuqFmqxQtGKjA13jUJWthE',
Taxon: 1337,
Sequence: 12,
}
assertResultMatch(result, expected)
})
it('fail when given invalid NFTokenID', function () {
assert.throws(() => {
parseNFTokenID('ABCD')
})
})
})