@@ -23,32 +23,15 @@ const assert = require('assert');
2323// Generate BN of 1.
2424const oneBn = new BN ( '1' , 16 ) ;
2525
26- // This number is used to shift the packed encoded asset information by 256 bits.
27- const shiftBN = new BN ( '10000000000000000000000000000000000000000000000000000000000000000' , 16 ) ;
28-
2926// Used to mask the 251 least signifcant bits given by Keccack256 to produce the final asset ID.
3027const mask = new BN ( '3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' , 16 ) ;
3128
29+ // Used to mask the 240 least signifcant bits given by Keccack256 to produce the final asset ID
30+ // (for mintable assets).
31+ const mask240 = new BN ( 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' , 16 ) ;
32+ const maskMintabilityBit =
33+ new BN ( '400000000000000000000000000000000000000000000000000000000000000' , 16 ) ;
3234
33- /*
34- Computes the hash representing the asset ID for a given asset.
35- asset is a dictionary containing the type and data of the asset to parse. the asset type is
36- represented by a string describing the associated asset while the data is a dictionary
37- containing further infomartion to distinguish between assets of a given type (such as the
38- address of the smart contract of an ERC20 asset).
39- The function returns the computed asset ID as a hex-string.
40-
41- For example:
42-
43- assetDict = {
44- type: 'ERC20',
45- data: { quantum: '10000', tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }
46- }
47-
48- Will produce an the following asset ID:
49-
50- '0x352386d5b7c781d47ecd404765307d74edc4d43b0490b8e03c71ac7a7429653'.
51- */
5235function getAssetType ( assetDict ) {
5336 const assetSelector = getAssetSelector ( assetDict . type ) ;
5437
@@ -64,17 +47,15 @@ function getAssetType(assetDict) {
6447 // In the case there is a valid tokenAddress in the data, we append that to the asset info
6548 // (before the quantum).
6649 const tokenAddress = new BN ( encUtils . removeHexPrefix ( assetDict . data . tokenAddress ) , 16 ) ;
67- assetInfo = assetInfo . mul ( shiftBN ) ;
50+ assetInfo = assetInfo . ushln ( 256 ) . add ( tokenAddress ) ;
6851 expectedLen += 64 ;
69- assetInfo = assetInfo . add ( tokenAddress ) ;
7052 }
7153
7254 // Default quantum is 1 (for assets which don't specify quantum explicitly).
7355 const quantInfo = assetDict . data . quantum ;
7456 const quantum = ( quantInfo === undefined ) ? oneBn : new BN ( quantInfo , 10 ) ;
75- assetInfo = assetInfo . mul ( shiftBN ) ;
57+ assetInfo = assetInfo . ushln ( 256 ) . add ( quantum ) ;
7658 expectedLen += 64 ;
77- assetInfo = assetInfo . add ( quantum ) ;
7859
7960 let assetType = sha3 . keccak_256 (
8061 encUtils . hexToBuffer ( addLeadingZeroes ( assetInfo . toJSON ( ) , expectedLen ) )
@@ -85,23 +66,64 @@ function getAssetType(assetDict) {
8566 return '0x' + assetType . toJSON ( ) ;
8667}
8768
69+ /*
70+ Computes the hash representing the encoded asset ID for a given asset.
71+ asset is a dictionary containing the type and data of the asset to parse. the asset type is
72+ represented by a string describing the associated asset while the data is a dictionary
73+ containing further information to distinguish between assets of a given type (such as the
74+ address of the smart contract of an ERC20 asset).
75+ The function returns the computed asset ID as a hex-string.
76+
77+ For example:
78+
79+ assetDict = {
80+ type: 'ERC20',
81+ data: { quantum: '10000', tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }
82+ }
83+
84+ Will produce an the following asset ID:
85+
86+ '0x352386d5b7c781d47ecd404765307d74edc4d43b0490b8e03c71ac7a7429653'.
87+ */
8888function getAssetId ( assetDict ) {
8989 const assetType = new BN ( encUtils . removeHexPrefix ( getAssetType ( assetDict ) ) , 16 ) ;
9090 // For ETH and ERC20, the asset ID is simply the asset type.
9191 let assetId = assetType ;
9292 if ( assetDict . type === 'ERC721' ) {
9393 // ERC721 assets require a slightly different construction for asset info.
9494 let assetInfo = new BN ( encUtils . utf8ToBuffer ( 'NFT:' ) , 16 ) ;
95- assetInfo = assetInfo . mul ( shiftBN ) ;
96- assetInfo = assetInfo . add ( assetType ) ;
97- assetInfo = assetInfo . mul ( shiftBN ) ;
98- assetInfo = assetInfo . add ( new BN ( parseInt ( assetDict . data . tokenId ) , 16 ) ) ;
99- const expectedLen = 136 ;
95+ // ExpectedLen is equal to the length (in hex characters) of the appended strings:
96+ // 'NFT:' (8 characters), 'assetType' (64 characters), 'tokenId' (64 characters).
97+ // Where assetType and tokenId are each padded with 0's to account for 64 hex characters
98+ // each.
99+ // We use this in order to pad the final assetInfo string with leading zeros in case the
100+ // calculation discarded them in the process.
101+ const expectedLen = 8 + 64 + 64 ;
102+ assetInfo = assetInfo . ushln ( 256 ) . add ( assetType ) ;
103+ assetInfo = assetInfo . ushln ( 256 ) . add ( new BN ( parseInt ( assetDict . data . tokenId ) , 16 ) ) ;
100104 assetId = sha3 . keccak_256 (
101105 encUtils . hexToBuffer ( addLeadingZeroes ( assetInfo . toJSON ( ) , expectedLen ) )
102106 ) ;
103107 assetId = new BN ( assetId , 16 ) ;
104108 assetId = assetId . and ( mask ) ;
109+ } else if ( assetDict . type === 'MINTABLE_ERC721' || assetDict . type === 'MINTABLE_ERC20' ) {
110+ let assetInfo = new BN ( encUtils . utf8ToBuffer ( 'MINTABLE:' ) , 16 ) ;
111+ // ExpectedLen is equal to the length (in hex characters) of the appended strings:
112+ // 'MINTABLE:' (18 characters), 'assetType' (64 characters), 'blobHash' (64 characters).
113+ // Where assetType and blobHash are each padded with 0's to account for 64 hex characters
114+ // each.
115+ // We use this in order to pad the final assetInfo string with leading zeros in case the
116+ // calculation discarded them in the process.
117+ const expectedLen = 18 + 64 + 64 ;
118+ assetInfo = assetInfo . ushln ( 256 ) . add ( assetType ) ;
119+ const blobHash = blobToBlobHash ( assetDict . data . blob ) ;
120+ assetInfo = assetInfo . ushln ( 256 ) . add ( new BN ( encUtils . removeHexPrefix ( blobHash ) , 16 ) ) ;
121+ assetId = sha3 . keccak_256 (
122+ encUtils . hexToBuffer ( addLeadingZeroes ( assetInfo . toJSON ( ) , expectedLen ) )
123+ ) ;
124+ assetId = new BN ( assetId , 16 ) ;
125+ assetId = assetId . and ( mask240 ) ;
126+ assetId = assetId . or ( maskMintabilityBit ) ;
105127 }
106128
107129 return '0x' + assetId . toJSON ( ) ;
@@ -122,6 +144,12 @@ function getAssetSelector(assetDictType) {
122144 case 'ERC721' :
123145 seed = 'ERC721Token(address,uint256)' ;
124146 break ;
147+ case 'MINTABLE_ERC20' :
148+ seed = 'MintableERC20Token(address)' ;
149+ break ;
150+ case 'MINTABLE_ERC721' :
151+ seed = 'MintableERC721Token(address,uint256)' ;
152+ break ;
125153 default :
126154 throw new Error ( `Unknown token type: ${ assetDictType } ` ) ;
127155 }
@@ -140,6 +168,11 @@ function addLeadingZeroes(hexStr, expectedLen) {
140168 return res ;
141169}
142170
171+ function blobToBlobHash ( blob ) {
172+ return '0x' + sha3 . keccak_256 ( blob ) ;
173+ }
174+
175+
143176module . exports = {
144177 getAssetType,
145178 getAssetId // Function.
0 commit comments