Skip to content

Commit 524bc20

Browse files
committed
extraced metadata upload into sdk
1 parent e13dcbd commit 524bc20

File tree

7 files changed

+232
-11
lines changed

7 files changed

+232
-11
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,10 @@
7272
},
7373
"dependencies": {
7474
"ajv": "^8.11.0",
75-
"axios": "^0.27.2",
75+
"axios": "^1.1.0",
7676
"ethers": "^5.6.9",
7777
"fast-memoize": "^2.5.2",
78+
"form-data": "^4.0.0",
7879
"graphql": "^16.5.0",
7980
"graphql-request": "^5.0.0",
8081
"urql": "^3.0.3"

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as graphql from './graphql';
22
import * as contracts from './contracts';
3+
import * as metadata from './metadata';
34

4-
export { graphql, contracts };
5+
export { graphql, contracts, metadata };

src/metadata/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { MetadataUploader } from './metadata';
2+
export { Metadata, validateSchema } from './validate';

src/metadata/metadata.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import axios, { AxiosError, AxiosProgressEvent } from 'axios';
2+
import FormData, { EventEmitter } from 'form-data';
3+
4+
import { Metadata as MetadataType, validateSchema } from './validate';
5+
6+
export type Metadata = MetadataType;
7+
8+
type HttpResponse = { response: unknown; error: string };
9+
10+
const API_URL = 'https://api.questchains.xyz';
11+
12+
interface File extends Blob {
13+
readonly lastModified: number;
14+
readonly name: string;
15+
readonly webkitRelativePath: string;
16+
}
17+
18+
interface UploadOptions {
19+
label?: string; // used for identifying the upload object in events
20+
apiUrl?: string;
21+
}
22+
23+
export class MetadataUploader extends EventEmitter {
24+
uploadMetadata = async (metadata: MetadataType, { label, apiUrl = API_URL }: UploadOptions): Promise<string> => {
25+
const valid = validateSchema(metadata);
26+
if (!valid) throw new Error('Invalid Metadata Schema');
27+
28+
const config = {
29+
headers: {
30+
'Content-Type': 'application/json',
31+
Accept: 'application/json',
32+
},
33+
onUploadProgress: (event: AxiosProgressEvent) => {
34+
this.emit('progress', { progress: event, label });
35+
},
36+
};
37+
38+
try {
39+
const res = await axios.post(`${apiUrl}/upload/json`, metadata, config);
40+
return res.data.response;
41+
} catch (error) {
42+
throw new Error(((error as AxiosError).response?.data as HttpResponse).error);
43+
}
44+
};
45+
46+
uploadFiles = async (files: File[], { label, apiUrl = API_URL }: UploadOptions): Promise<string> => {
47+
const formData = new FormData();
48+
for (let i = 0; i < files.length; ++i) {
49+
formData.append(files[i].name, files[i]);
50+
}
51+
52+
const config = {
53+
headers: {
54+
'Content-Type': 'multipart/form-data',
55+
Accept: 'application/json',
56+
},
57+
onUploadProgress: (event: AxiosProgressEvent) => {
58+
this.emit('progress', { progress: event, label });
59+
},
60+
};
61+
62+
try {
63+
const res = await axios.post(`${apiUrl}/upload/files`, formData, config);
64+
return res.data.response;
65+
} catch (error) {
66+
throw new Error(((error as AxiosError).response?.data as HttpResponse).error);
67+
}
68+
};
69+
}

src/metadata/schema.json

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"type": "object",
4+
"title": "Quest Chain Metadata",
5+
"description": "This defines the schema for the metadata supported by Quest Chain System",
6+
"default": {},
7+
"examples": [
8+
{
9+
"name": "DAO Quest",
10+
"description": "This describes the quest",
11+
"image_url": "ipfs://QmZTzK8YAsPcLoKENzWmXv519pDmX1XgNQ6h9RtnSjwksn",
12+
"external_url": "ipfs://QmZTzK8YAsPcLoKENzWmXv519pDmX1XgNQ6h9RtnSjwksn"
13+
}
14+
],
15+
"required": ["name", "description"],
16+
"properties": {
17+
"name": {
18+
"$id": "#/properties/name",
19+
"type": "string",
20+
"title": "The name schema",
21+
"description": "This property is the name of the item",
22+
"default": "",
23+
"examples": ["DAO Quest"]
24+
},
25+
"description": {
26+
"$id": "#/properties/description",
27+
"type": "string",
28+
"title": "The description schema",
29+
"description": "This property is the description of the item in plain text or optionally in markdown",
30+
"default": "",
31+
"examples": ["This describes the quest"]
32+
},
33+
"image_url": {
34+
"$id": "#/properties/image_url",
35+
"type": "string",
36+
"title": "The image_url schema",
37+
"description": "This property defines an optional image URL for the item",
38+
"default": "",
39+
"examples": ["ipfs://QmZTzK8YAsPcLoKENzWmXv519pDmX1XgNQ6h9RtnSjwksn"]
40+
},
41+
"animation_url": {
42+
"$id": "#/properties/animation_url",
43+
"type": "string",
44+
"title": "The animation_url schema",
45+
"description": "This property defines an optional animation URL that can reference a multi-media attachment of the item",
46+
"default": "",
47+
"examples": ["ipfs://QmZTzK8YAsPcLoKENzWmXv519pDmX1XgNQ6h9RtnSjwksn"]
48+
},
49+
"external_url": {
50+
"$id": "#/properties/external_url",
51+
"type": "string",
52+
"title": "The external_url schema",
53+
"description": "This property defines an optional external URL that can reference a webpage or external asset of the item",
54+
"default": "",
55+
"examples": ["ipfs://QmZTzK8YAsPcLoKENzWmXv519pDmX1XgNQ6h9RtnSjwksn"]
56+
},
57+
"attributes": {
58+
"$id": "#/properties/attributes",
59+
"type": "array",
60+
"title": "Attributes",
61+
"description": "OpenSea NFT attributes",
62+
"default": [],
63+
"examples": [
64+
[
65+
{
66+
"trait_type": "Base",
67+
"value": "narwhal"
68+
}
69+
]
70+
],
71+
"items": {
72+
"$id": "#/properties/attributes/items",
73+
"anyOf": [
74+
{
75+
"$id": "#/properties/attributes/items/anyOf/0",
76+
"type": "object",
77+
"title": "Attributes Item",
78+
"description": "OpenSea NFT attribute",
79+
"default": {},
80+
"examples": [
81+
{
82+
"trait_type": "Base",
83+
"value": "narwhal"
84+
}
85+
],
86+
"required": ["value"],
87+
"properties": {
88+
"display_type": {
89+
"$id": "#/properties/attributes/items/anyOf/0/properties/display_type",
90+
"type": "string",
91+
"enum": ["number", "boost_number", "boost_percentage"],
92+
"title": "Display Type",
93+
"description": "OpenSea NFT attribute display type",
94+
"default": "",
95+
"examples": ["number"]
96+
},
97+
"trait_type": {
98+
"$id": "#/properties/attributes/items/anyOf/0/properties/trait_type",
99+
"type": "string",
100+
"title": "Trait Type",
101+
"description": "OpenSea NFT attribute trait type",
102+
"default": "",
103+
"examples": ["Base"]
104+
},
105+
"value": {
106+
"$id": "#/properties/attributes/items/anyOf/0/properties/value",
107+
"type": ["string", "number"],
108+
"title": "Trait Value",
109+
"description": "OpenSea NFT attribute value type",
110+
"default": "",
111+
"examples": ["narwhal", 5]
112+
}
113+
}
114+
}
115+
]
116+
}
117+
}
118+
},
119+
"additionalProperties": false
120+
}

src/metadata/validate.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Ajv from 'ajv';
2+
3+
import schema from './schema.json';
4+
5+
const ajv = new Ajv({ allowUnionTypes: true });
6+
const validate = ajv.compile(schema);
7+
8+
export type Metadata = {
9+
name: string;
10+
description: string;
11+
image_url?: string;
12+
animation_url?: string;
13+
external_url?: string;
14+
attributes?: {
15+
trait_type?: string;
16+
value: string | number;
17+
display_type?: 'number' | 'boost_number' | 'boost_percentage';
18+
}[];
19+
};
20+
21+
export const validateSchema = (metadata: Metadata): boolean =>
22+
validate(metadata);

yarn.lock

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2690,13 +2690,14 @@ axe-core@^4.4.3:
26902690
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
26912691
integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==
26922692

2693-
axios@^0.27.2:
2694-
version "0.27.2"
2695-
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
2696-
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
2693+
axios@^1.1.0:
2694+
version "1.1.0"
2695+
resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.0.tgz#94d25e6524743c7fc33954dd536687bbb957793a"
2696+
integrity sha512-hsJgcqz4JY7f+HZ4cWTrPZ6tZNCNFPTRx1MjRqu/hbpgpHdSCUpLVuplc+jE/h7dOvyANtw/ERA3HC2Rz/QoMg==
26972697
dependencies:
2698-
follow-redirects "^1.14.9"
2698+
follow-redirects "^1.15.0"
26992699
form-data "^4.0.0"
2700+
proxy-from-env "^1.1.0"
27002701

27012702
axobject-query@^2.2.0:
27022703
version "2.2.0"
@@ -4355,10 +4356,10 @@ flatted@^2.0.0:
43554356
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
43564357
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
43574358

4358-
follow-redirects@^1.14.9:
4359-
version "1.15.1"
4360-
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
4361-
integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
4359+
follow-redirects@^1.15.0:
4360+
version "1.15.2"
4361+
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
4362+
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
43624363

43634364
for-in@^1.0.2:
43644365
version "1.0.2"
@@ -6847,6 +6848,11 @@ prop-types@^15.8.1:
68476848
object-assign "^4.1.1"
68486849
react-is "^16.13.1"
68496850

6851+
proxy-from-env@^1.1.0:
6852+
version "1.1.0"
6853+
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
6854+
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
6855+
68506856
psl@^1.1.28:
68516857
version "1.9.0"
68526858
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"

0 commit comments

Comments
 (0)