@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.
@@ -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 '0001':
99
+ case types_1.StdInterfaceIds.FungibleToken:
85
100
  return 'fungible';
86
- case '0003':
101
+ case types_1.StdInterfaceIds.NFT:
87
102
  return 'non-fungible';
88
103
  default:
89
104
  return undefined;
@@ -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
+ }
@@ -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 = {}));
@@ -1,5 +1,6 @@
1
1
  import 'cross-fetch/polyfill';
2
2
  export declare function convertHttpResponse<T>(response: {
3
+ status: number;
3
4
  data: T;
4
5
  error?: {
5
6
  detail: string;
@@ -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
- throw new Error(`[API Error] - ${response.error.detail}`);
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;
@@ -1,10 +1,24 @@
1
+ import 'cross-fetch/polyfill';
1
2
  export interface NFTMetadata {
2
3
  name: string;
3
- description: string;
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[]>;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alephium/web3",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "A JS/TS library to interact with the Alephium platform",
5
5
  "license": "GPL",
6
6
  "main": "dist/src/index.js",
@@ -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 '0001':
175
+ case StdInterfaceIds.FungibleToken:
157
176
  return 'fungible'
158
- case '0003':
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
- throw new Error(`[API Error] - ${response.error.detail}`)
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: string
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
+ }