@alephium/web3 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/alephium-web3.min.js +1 -1
- package/dist/alephium-web3.min.js.map +1 -1
- package/dist/src/api/node-provider.d.ts +3 -1
- package/dist/src/api/node-provider.js +17 -2
- package/dist/src/api/types.d.ts +9 -0
- package/dist/src/api/types.js +7 -1
- package/dist/src/api/utils.d.ts +1 -0
- package/dist/src/api/utils.js +2 -1
- package/dist/src/token/nft.d.ts +15 -1
- package/dist/src/token/nft.js +100 -0
- package/package.json +1 -1
- package/src/api/node-provider.ts +22 -3
- package/src/api/types.ts +11 -0
- package/src/api/utils.ts +3 -2
- package/src/token/nft.ts +124 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiRequestArguments, ApiRequestHandler, FungibleTokenMetaData, NFTMetaData } from './types';
|
|
1
|
+
import { ApiRequestArguments, ApiRequestHandler, FungibleTokenMetaData, NFTMetaData, NFTCollectionMetaData } from './types';
|
|
2
2
|
import { Api as NodeApi } from './api-alephium';
|
|
3
3
|
import { HexString } from '../contract';
|
|
4
4
|
interface NodeProviderApis {
|
|
@@ -34,7 +34,9 @@ export declare class NodeProvider implements NodeProviderApis {
|
|
|
34
34
|
static Remote(handler: ApiRequestHandler): NodeProvider;
|
|
35
35
|
fetchFungibleTokenMetaData: (tokenId: HexString) => Promise<FungibleTokenMetaData>;
|
|
36
36
|
fetchNFTMetaData: (tokenId: HexString) => Promise<NFTMetaData>;
|
|
37
|
+
fetchNFTCollectionMetaData: (contractId: HexString) => Promise<NFTCollectionMetaData>;
|
|
37
38
|
guessStdInterfaceId: (tokenId: HexString) => Promise<HexString | undefined>;
|
|
39
|
+
guessFollowsNFTCollectionStd: (contractId: HexString) => Promise<boolean>;
|
|
38
40
|
guessStdTokenType: (tokenId: HexString) => Promise<'fungible' | 'non-fungible' | undefined>;
|
|
39
41
|
}
|
|
40
42
|
export {};
|
|
@@ -65,6 +65,17 @@ class NodeProvider {
|
|
|
65
65
|
collectionAddress: (0, utils_2.addressFromContractId)(result.results[1].returns[0].value)
|
|
66
66
|
};
|
|
67
67
|
};
|
|
68
|
+
// Only use this when the contract follows the NFT collection interface, check `guessFollowsNFTCollectionStd` first
|
|
69
|
+
this.fetchNFTCollectionMetaData = async (contractId) => {
|
|
70
|
+
const address = (0, utils_2.addressFromContractId)(contractId);
|
|
71
|
+
const group = (0, utils_2.groupOfAddress)(address);
|
|
72
|
+
const calls = Array.from([0, 1], (index) => ({ methodIndex: index, group: group, address: address }));
|
|
73
|
+
const result = await this.contracts.postContractsMulticallContract({ calls });
|
|
74
|
+
return {
|
|
75
|
+
collectionUri: (0, utils_2.hexToString)(result.results[0].returns[0].value),
|
|
76
|
+
totalSupply: BigInt(result.results[1].returns[0].value)
|
|
77
|
+
};
|
|
78
|
+
};
|
|
68
79
|
this.guessStdInterfaceId = async (tokenId) => {
|
|
69
80
|
const address = (0, utils_2.addressFromTokenId)(tokenId);
|
|
70
81
|
const group = (0, utils_2.groupOfAddress)(address);
|
|
@@ -78,12 +89,16 @@ class NodeProvider {
|
|
|
78
89
|
return undefined;
|
|
79
90
|
}
|
|
80
91
|
};
|
|
92
|
+
this.guessFollowsNFTCollectionStd = async (contractId) => {
|
|
93
|
+
const interfaceId = await this.guessStdInterfaceId(contractId);
|
|
94
|
+
return interfaceId === types_1.StdInterfaceIds.NFTCollection;
|
|
95
|
+
};
|
|
81
96
|
this.guessStdTokenType = async (tokenId) => {
|
|
82
97
|
const interfaceId = await this.guessStdInterfaceId(tokenId);
|
|
83
98
|
switch (interfaceId) {
|
|
84
|
-
case
|
|
99
|
+
case types_1.StdInterfaceIds.FungibleToken:
|
|
85
100
|
return 'fungible';
|
|
86
|
-
case
|
|
101
|
+
case types_1.StdInterfaceIds.NFT:
|
|
87
102
|
return 'non-fungible';
|
|
88
103
|
default:
|
|
89
104
|
return undefined;
|
package/dist/src/api/types.d.ts
CHANGED
|
@@ -30,6 +30,11 @@ export interface ApiRequestArguments {
|
|
|
30
30
|
export type ApiRequestHandler = (args: ApiRequestArguments) => Promise<any>;
|
|
31
31
|
export declare function forwardRequests(api: Record<string, any>, handler: ApiRequestHandler): void;
|
|
32
32
|
export declare function request(provider: Record<string, any>, args: ApiRequestArguments): Promise<any>;
|
|
33
|
+
export declare enum StdInterfaceIds {
|
|
34
|
+
FungibleToken = "0001",
|
|
35
|
+
NFTCollection = "0002",
|
|
36
|
+
NFT = "0003"
|
|
37
|
+
}
|
|
33
38
|
export interface FungibleTokenMetaData {
|
|
34
39
|
name: string;
|
|
35
40
|
symbol: string;
|
|
@@ -40,3 +45,7 @@ export interface NFTMetaData {
|
|
|
40
45
|
collectionAddress: string;
|
|
41
46
|
tokenUri: string;
|
|
42
47
|
}
|
|
48
|
+
export interface NFTCollectionMetaData {
|
|
49
|
+
collectionUri: string;
|
|
50
|
+
totalSupply: Number256;
|
|
51
|
+
}
|
package/dist/src/api/types.js
CHANGED
|
@@ -17,7 +17,7 @@ You should have received a copy of the GNU Lesser General Public License
|
|
|
17
17
|
along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.request = exports.forwardRequests = exports.typeLength = exports.fromApiVal = exports.fromApiArray = exports.fromApiVals = exports.toApiVal = exports.toApiArray = exports.toApiAddress = exports.toApiByteVec = exports.fromApiNumber256 = exports.toApiNumber256Optional = exports.toApiNumber256 = exports.toApiBoolean = exports.fromApiTokens = exports.fromApiToken = exports.toApiTokens = exports.toApiToken = void 0;
|
|
20
|
+
exports.StdInterfaceIds = exports.request = exports.forwardRequests = exports.typeLength = exports.fromApiVal = exports.fromApiArray = exports.fromApiVals = exports.toApiVal = exports.toApiArray = exports.toApiAddress = exports.toApiByteVec = exports.fromApiNumber256 = exports.toApiNumber256Optional = exports.toApiNumber256 = exports.toApiBoolean = exports.fromApiTokens = exports.fromApiToken = exports.toApiTokens = exports.toApiToken = void 0;
|
|
21
21
|
const utils_1 = require("../utils");
|
|
22
22
|
utils_1.assertType;
|
|
23
23
|
function toApiToken(token) {
|
|
@@ -257,3 +257,9 @@ async function request(provider, args) {
|
|
|
257
257
|
return call(...args.params);
|
|
258
258
|
}
|
|
259
259
|
exports.request = request;
|
|
260
|
+
var StdInterfaceIds;
|
|
261
|
+
(function (StdInterfaceIds) {
|
|
262
|
+
StdInterfaceIds["FungibleToken"] = "0001";
|
|
263
|
+
StdInterfaceIds["NFTCollection"] = "0002";
|
|
264
|
+
StdInterfaceIds["NFT"] = "0003";
|
|
265
|
+
})(StdInterfaceIds = exports.StdInterfaceIds || (exports.StdInterfaceIds = {}));
|
package/dist/src/api/utils.d.ts
CHANGED
package/dist/src/api/utils.js
CHANGED
|
@@ -23,7 +23,8 @@ const utils_1 = require("../utils");
|
|
|
23
23
|
const async_sema_1 = require("async-sema");
|
|
24
24
|
function convertHttpResponse(response) {
|
|
25
25
|
if (response.error) {
|
|
26
|
-
|
|
26
|
+
const errorMessage = response.error.detail ?? `status code: ${response.status}`;
|
|
27
|
+
throw new Error(`[API Error] - ${errorMessage}`);
|
|
27
28
|
}
|
|
28
29
|
else {
|
|
29
30
|
return response.data;
|
package/dist/src/token/nft.d.ts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
|
+
import 'cross-fetch/polyfill';
|
|
1
2
|
export interface NFTMetadata {
|
|
2
3
|
name: string;
|
|
3
|
-
description
|
|
4
|
+
description?: string;
|
|
4
5
|
image: string;
|
|
6
|
+
attributes?: [
|
|
7
|
+
{
|
|
8
|
+
trait_type: string;
|
|
9
|
+
value: string | number | boolean;
|
|
10
|
+
}
|
|
11
|
+
];
|
|
5
12
|
}
|
|
6
13
|
export interface NFTCollectionMetadata {
|
|
7
14
|
name: string;
|
|
8
15
|
description: string;
|
|
9
16
|
image: string;
|
|
10
17
|
}
|
|
18
|
+
export declare const validNFTMetadataFields: string[];
|
|
19
|
+
export declare const validNFTMetadataAttributesFields: string[];
|
|
20
|
+
export declare const validNFTMetadataAttributeTypes: string[];
|
|
21
|
+
export declare const validNFTCollectionMetadataFields: string[];
|
|
22
|
+
export declare function validateNFTMetadata(metadata: any): NFTMetadata;
|
|
23
|
+
export declare function validateNFTCollectionMetadata(metadata: any): NFTCollectionMetadata;
|
|
24
|
+
export declare function validateEnumerableNFTBaseUri(nftBaseUri: string, maxSupply: number): Promise<NFTMetadata[]>;
|
package/dist/src/token/nft.js
CHANGED
|
@@ -17,3 +17,103 @@ You should have received a copy of the GNU Lesser General Public License
|
|
|
17
17
|
along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.validateEnumerableNFTBaseUri = exports.validateNFTCollectionMetadata = exports.validateNFTMetadata = exports.validNFTCollectionMetadataFields = exports.validNFTMetadataAttributeTypes = exports.validNFTMetadataAttributesFields = exports.validNFTMetadataFields = void 0;
|
|
21
|
+
// JSON Schema for the NFT metadata, which is pointed to by the value
|
|
22
|
+
// returned from the `getTokenUri` method of the NFT contract
|
|
23
|
+
require("cross-fetch/polyfill");
|
|
24
|
+
exports.validNFTMetadataFields = ['name', 'description', 'image', 'attributes'];
|
|
25
|
+
exports.validNFTMetadataAttributesFields = ['trait_type', 'value'];
|
|
26
|
+
exports.validNFTMetadataAttributeTypes = ['string', 'number', 'boolean'];
|
|
27
|
+
exports.validNFTCollectionMetadataFields = ['name', 'description', 'image'];
|
|
28
|
+
function validateNFTMetadata(metadata) {
|
|
29
|
+
Object.keys(metadata).forEach((key) => {
|
|
30
|
+
if (!exports.validNFTMetadataFields.includes(key)) {
|
|
31
|
+
throw new Error(`Invalid field ${key}, only ${exports.validNFTMetadataFields} are allowed`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const name = validateNonEmptyString(metadata, 'name');
|
|
35
|
+
const description = validateNonEmptyStringIfExists(metadata, 'description');
|
|
36
|
+
const image = validateNonEmptyString(metadata, 'image');
|
|
37
|
+
const attributes = validateNFTMetadataAttributes(metadata['attributes']);
|
|
38
|
+
return { name, description, image, attributes };
|
|
39
|
+
}
|
|
40
|
+
exports.validateNFTMetadata = validateNFTMetadata;
|
|
41
|
+
function validateNFTCollectionMetadata(metadata) {
|
|
42
|
+
Object.keys(metadata).forEach((key) => {
|
|
43
|
+
if (!exports.validNFTCollectionMetadataFields.includes(key)) {
|
|
44
|
+
throw new Error(`Invalid field ${key}, only ${exports.validNFTCollectionMetadataFields} are allowed`);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const name = validateNonEmptyString(metadata, 'name');
|
|
48
|
+
const description = validateNonEmptyString(metadata, 'description');
|
|
49
|
+
const image = validateNonEmptyString(metadata, 'image');
|
|
50
|
+
return { name, description, image };
|
|
51
|
+
}
|
|
52
|
+
exports.validateNFTCollectionMetadata = validateNFTCollectionMetadata;
|
|
53
|
+
async function validateEnumerableNFTBaseUri(nftBaseUri, maxSupply) {
|
|
54
|
+
if (isInteger(maxSupply) && maxSupply > 0) {
|
|
55
|
+
const nftMetadataz = [];
|
|
56
|
+
for (let i = 0; i < maxSupply; i++) {
|
|
57
|
+
const nftMetadata = await fetchNFTMetadata(nftBaseUri, i);
|
|
58
|
+
const validatedNFTMetadata = validateNFTMetadata(nftMetadata);
|
|
59
|
+
nftMetadataz.push(validatedNFTMetadata);
|
|
60
|
+
}
|
|
61
|
+
return nftMetadataz;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
throw new Error('maxSupply should be a positive integer');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.validateEnumerableNFTBaseUri = validateEnumerableNFTBaseUri;
|
|
68
|
+
function validateNFTMetadataAttributes(attributes) {
|
|
69
|
+
if (!!attributes) {
|
|
70
|
+
if (!Array.isArray(attributes)) {
|
|
71
|
+
throw new Error(`Field 'attributes' should be an array`);
|
|
72
|
+
}
|
|
73
|
+
attributes.forEach((item) => {
|
|
74
|
+
if (typeof item !== 'object') {
|
|
75
|
+
throw new Error(`Field 'attributes' should be an array of objects`);
|
|
76
|
+
}
|
|
77
|
+
Object.keys(item).forEach((key) => {
|
|
78
|
+
if (!exports.validNFTMetadataAttributesFields.includes(key)) {
|
|
79
|
+
throw new Error(`Invalid field ${key} for attributes, only ${exports.validNFTMetadataAttributesFields} are allowed`);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
validateNonEmptyString(item, 'trait_type');
|
|
83
|
+
validateNonEmptyAttributeValue(item, 'value');
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return attributes;
|
|
87
|
+
}
|
|
88
|
+
function validateNonEmptyString(obj, field) {
|
|
89
|
+
const value = obj[`${field}`];
|
|
90
|
+
if (!(typeof value === 'string' && value !== '')) {
|
|
91
|
+
throw new Error(`JSON field '${field}' is not a non empty string`);
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
function validateNonEmptyStringIfExists(obj, field) {
|
|
96
|
+
const value = obj[`${field}`];
|
|
97
|
+
if (value !== undefined && !(typeof value === 'string' && value !== '')) {
|
|
98
|
+
throw new Error(`JSON field '${field}' is not a non empty string`);
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
function validateNonEmptyAttributeValue(obj, field) {
|
|
103
|
+
const value = obj[`${field}`];
|
|
104
|
+
if (!((typeof value === 'string' && value !== '') || typeof value === 'number' || typeof value === 'boolean')) {
|
|
105
|
+
throw new Error(`Attribute value should be a non empty string, number or boolean`);
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
async function fetchNFTMetadata(nftBaseUri, index) {
|
|
110
|
+
try {
|
|
111
|
+
return await (await fetch(`${nftBaseUri}${index}`)).json();
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
throw new Error(`Error fetching NFT metadata from ${nftBaseUri}${index}: ${e}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function isInteger(num) {
|
|
118
|
+
return num === parseInt(num.toString(), 10);
|
|
119
|
+
}
|
package/package.json
CHANGED
package/src/api/node-provider.ts
CHANGED
|
@@ -22,7 +22,9 @@ import {
|
|
|
22
22
|
forwardRequests,
|
|
23
23
|
request,
|
|
24
24
|
FungibleTokenMetaData,
|
|
25
|
-
NFTMetaData
|
|
25
|
+
NFTMetaData,
|
|
26
|
+
NFTCollectionMetaData,
|
|
27
|
+
StdInterfaceIds
|
|
26
28
|
} from './types'
|
|
27
29
|
import { Api as NodeApi } from './api-alephium'
|
|
28
30
|
import { DEFAULT_THROTTLE_FETCH } from './utils'
|
|
@@ -137,6 +139,18 @@ export class NodeProvider implements NodeProviderApis {
|
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
// Only use this when the contract follows the NFT collection interface, check `guessFollowsNFTCollectionStd` first
|
|
143
|
+
fetchNFTCollectionMetaData = async (contractId: HexString): Promise<NFTCollectionMetaData> => {
|
|
144
|
+
const address = addressFromContractId(contractId)
|
|
145
|
+
const group = groupOfAddress(address)
|
|
146
|
+
const calls = Array.from([0, 1], (index) => ({ methodIndex: index, group: group, address: address }))
|
|
147
|
+
const result = await this.contracts.postContractsMulticallContract({ calls })
|
|
148
|
+
return {
|
|
149
|
+
collectionUri: hexToString(result.results[0].returns[0].value as any as string),
|
|
150
|
+
totalSupply: BigInt(result.results[1].returns[0].value as any as string)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
140
154
|
guessStdInterfaceId = async (tokenId: HexString): Promise<HexString | undefined> => {
|
|
141
155
|
const address = addressFromTokenId(tokenId)
|
|
142
156
|
const group = groupOfAddress(address)
|
|
@@ -150,12 +164,17 @@ export class NodeProvider implements NodeProviderApis {
|
|
|
150
164
|
}
|
|
151
165
|
}
|
|
152
166
|
|
|
167
|
+
guessFollowsNFTCollectionStd = async (contractId: HexString): Promise<boolean> => {
|
|
168
|
+
const interfaceId = await this.guessStdInterfaceId(contractId)
|
|
169
|
+
return interfaceId === StdInterfaceIds.NFTCollection
|
|
170
|
+
}
|
|
171
|
+
|
|
153
172
|
guessStdTokenType = async (tokenId: HexString): Promise<'fungible' | 'non-fungible' | undefined> => {
|
|
154
173
|
const interfaceId = await this.guessStdInterfaceId(tokenId)
|
|
155
174
|
switch (interfaceId) {
|
|
156
|
-
case
|
|
175
|
+
case StdInterfaceIds.FungibleToken:
|
|
157
176
|
return 'fungible'
|
|
158
|
-
case
|
|
177
|
+
case StdInterfaceIds.NFT:
|
|
159
178
|
return 'non-fungible'
|
|
160
179
|
default:
|
|
161
180
|
return undefined
|
package/src/api/types.ts
CHANGED
|
@@ -264,6 +264,12 @@ export async function request(provider: Record<string, any>, args: ApiRequestArg
|
|
|
264
264
|
return call(...args.params)
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
export enum StdInterfaceIds {
|
|
268
|
+
FungibleToken = '0001',
|
|
269
|
+
NFTCollection = '0002',
|
|
270
|
+
NFT = '0003'
|
|
271
|
+
}
|
|
272
|
+
|
|
267
273
|
export interface FungibleTokenMetaData {
|
|
268
274
|
name: string
|
|
269
275
|
symbol: string
|
|
@@ -275,3 +281,8 @@ export interface NFTMetaData {
|
|
|
275
281
|
collectionAddress: string
|
|
276
282
|
tokenUri: string
|
|
277
283
|
}
|
|
284
|
+
|
|
285
|
+
export interface NFTCollectionMetaData {
|
|
286
|
+
collectionUri: string
|
|
287
|
+
totalSupply: Number256
|
|
288
|
+
}
|
package/src/api/utils.ts
CHANGED
|
@@ -20,9 +20,10 @@ import 'cross-fetch/polyfill'
|
|
|
20
20
|
import { sleep } from '../utils'
|
|
21
21
|
import { RateLimit } from 'async-sema'
|
|
22
22
|
|
|
23
|
-
export function convertHttpResponse<T>(response: { data: T; error?: { detail: string } }): T {
|
|
23
|
+
export function convertHttpResponse<T>(response: { status: number; data: T; error?: { detail: string } }): T {
|
|
24
24
|
if (response.error) {
|
|
25
|
-
|
|
25
|
+
const errorMessage = response.error.detail ?? `status code: ${response.status}`
|
|
26
|
+
throw new Error(`[API Error] - ${errorMessage}`)
|
|
26
27
|
} else {
|
|
27
28
|
return response.data
|
|
28
29
|
}
|
package/src/token/nft.ts
CHANGED
|
@@ -18,10 +18,19 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
18
18
|
|
|
19
19
|
// JSON Schema for the NFT metadata, which is pointed to by the value
|
|
20
20
|
// returned from the `getTokenUri` method of the NFT contract
|
|
21
|
+
|
|
22
|
+
import 'cross-fetch/polyfill'
|
|
23
|
+
|
|
21
24
|
export interface NFTMetadata {
|
|
22
25
|
name: string
|
|
23
|
-
description
|
|
26
|
+
description?: string
|
|
24
27
|
image: string
|
|
28
|
+
attributes?: [
|
|
29
|
+
{
|
|
30
|
+
trait_type: string
|
|
31
|
+
value: string | number | boolean
|
|
32
|
+
}
|
|
33
|
+
]
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
// JSON Schema for the NFT Collection metadata, which is pointed to by
|
|
@@ -32,3 +41,117 @@ export interface NFTCollectionMetadata {
|
|
|
32
41
|
description: string
|
|
33
42
|
image: string
|
|
34
43
|
}
|
|
44
|
+
|
|
45
|
+
export const validNFTMetadataFields = ['name', 'description', 'image', 'attributes']
|
|
46
|
+
export const validNFTMetadataAttributesFields = ['trait_type', 'value']
|
|
47
|
+
export const validNFTMetadataAttributeTypes = ['string', 'number', 'boolean']
|
|
48
|
+
export const validNFTCollectionMetadataFields = ['name', 'description', 'image']
|
|
49
|
+
|
|
50
|
+
export function validateNFTMetadata(metadata: any): NFTMetadata {
|
|
51
|
+
Object.keys(metadata).forEach((key) => {
|
|
52
|
+
if (!validNFTMetadataFields.includes(key)) {
|
|
53
|
+
throw new Error(`Invalid field ${key}, only ${validNFTMetadataFields} are allowed`)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const name = validateNonEmptyString(metadata, 'name')
|
|
58
|
+
const description = validateNonEmptyStringIfExists(metadata, 'description')
|
|
59
|
+
const image = validateNonEmptyString(metadata, 'image')
|
|
60
|
+
const attributes = validateNFTMetadataAttributes(metadata['attributes'])
|
|
61
|
+
|
|
62
|
+
return { name, description, image, attributes }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function validateNFTCollectionMetadata(metadata: any): NFTCollectionMetadata {
|
|
66
|
+
Object.keys(metadata).forEach((key) => {
|
|
67
|
+
if (!validNFTCollectionMetadataFields.includes(key)) {
|
|
68
|
+
throw new Error(`Invalid field ${key}, only ${validNFTCollectionMetadataFields} are allowed`)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const name = validateNonEmptyString(metadata, 'name')
|
|
73
|
+
const description = validateNonEmptyString(metadata, 'description')
|
|
74
|
+
const image = validateNonEmptyString(metadata, 'image')
|
|
75
|
+
|
|
76
|
+
return { name, description, image }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function validateEnumerableNFTBaseUri(nftBaseUri: string, maxSupply: number): Promise<NFTMetadata[]> {
|
|
80
|
+
if (isInteger(maxSupply) && maxSupply > 0) {
|
|
81
|
+
const nftMetadataz: NFTMetadata[] = []
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < maxSupply; i++) {
|
|
84
|
+
const nftMetadata = await fetchNFTMetadata(nftBaseUri, i)
|
|
85
|
+
const validatedNFTMetadata = validateNFTMetadata(nftMetadata)
|
|
86
|
+
nftMetadataz.push(validatedNFTMetadata)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return nftMetadataz
|
|
90
|
+
} else {
|
|
91
|
+
throw new Error('maxSupply should be a positive integer')
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function validateNFTMetadataAttributes(attributes: any): NFTMetadata['attributes'] {
|
|
96
|
+
if (!!attributes) {
|
|
97
|
+
if (!Array.isArray(attributes)) {
|
|
98
|
+
throw new Error(`Field 'attributes' should be an array`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
attributes.forEach((item) => {
|
|
102
|
+
if (typeof item !== 'object') {
|
|
103
|
+
throw new Error(`Field 'attributes' should be an array of objects`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Object.keys(item).forEach((key) => {
|
|
107
|
+
if (!validNFTMetadataAttributesFields.includes(key)) {
|
|
108
|
+
throw new Error(`Invalid field ${key} for attributes, only ${validNFTMetadataAttributesFields} are allowed`)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
validateNonEmptyString(item, 'trait_type')
|
|
113
|
+
validateNonEmptyAttributeValue(item, 'value')
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return attributes as NFTMetadata['attributes']
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function validateNonEmptyString(obj: object, field: string): string {
|
|
121
|
+
const value = obj[`${field}`]
|
|
122
|
+
if (!(typeof value === 'string' && value !== '')) {
|
|
123
|
+
throw new Error(`JSON field '${field}' is not a non empty string`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return value
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function validateNonEmptyStringIfExists(obj: object, field: string): string {
|
|
130
|
+
const value = obj[`${field}`]
|
|
131
|
+
if (value !== undefined && !(typeof value === 'string' && value !== '')) {
|
|
132
|
+
throw new Error(`JSON field '${field}' is not a non empty string`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return value
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function validateNonEmptyAttributeValue(obj: object, field: string): string | number | boolean {
|
|
139
|
+
const value = obj[`${field}`]
|
|
140
|
+
if (!((typeof value === 'string' && value !== '') || typeof value === 'number' || typeof value === 'boolean')) {
|
|
141
|
+
throw new Error(`Attribute value should be a non empty string, number or boolean`)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return value
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function fetchNFTMetadata(nftBaseUri: string, index: number) {
|
|
148
|
+
try {
|
|
149
|
+
return await (await fetch(`${nftBaseUri}${index}`)).json()
|
|
150
|
+
} catch (e) {
|
|
151
|
+
throw new Error(`Error fetching NFT metadata from ${nftBaseUri}${index}: ${e}`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isInteger(num: number) {
|
|
156
|
+
return num === parseInt(num.toString(), 10)
|
|
157
|
+
}
|