tensorflow-sui2 1.0.0
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/dataset.d.ts +58 -0
- package/dist/dataset.js +323 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +7 -0
- package/package.json +20 -0
- package/src/dataset.ts +388 -0
- package/src/index.ts +2 -0
- package/src/types.ts +37 -0
- package/tsconfig.json +14 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
import { SuiClient } from "@mysten/sui/client";
|
2
|
+
import { Transaction } from "@mysten/sui/transactions";
|
3
|
+
import { OpenGraphClientConfig, DatasetMetadata, WalrusStorageInfo } from "./types.js";
|
4
|
+
export declare class OpenGraphClient {
|
5
|
+
private networkUrl;
|
6
|
+
private packageId;
|
7
|
+
private gasBudget;
|
8
|
+
suiClient: SuiClient;
|
9
|
+
private walrusNetwork;
|
10
|
+
private walrusPublisherUrl;
|
11
|
+
private walrusAggregatorUrl;
|
12
|
+
constructor(config: OpenGraphClientConfig);
|
13
|
+
getNetworkUrl(): string;
|
14
|
+
getPackageId(): string;
|
15
|
+
getGasBudget(): number;
|
16
|
+
getWalrusNetwork(): string;
|
17
|
+
getWalrusPublisherUrl(): string;
|
18
|
+
getWalrusAggregatorUrl(): string;
|
19
|
+
getConfig(): OpenGraphClientConfig;
|
20
|
+
setNetworkUrl(url: string): void;
|
21
|
+
setPackageId(packageId: string): void;
|
22
|
+
setGasBudget(gasBudget: number): void;
|
23
|
+
setWalrusNetwork(network: string): void;
|
24
|
+
setWalrusPublisherUrl(url: string): void;
|
25
|
+
setWalrusAggregatorUrl(url: string): void;
|
26
|
+
private getSuiScanUrl;
|
27
|
+
uploadDataset(files: File[], address: string, metadata: DatasetMetadata, annotations: string[], epochs?: number): Promise<{
|
28
|
+
storageInfos: WalrusStorageInfo[];
|
29
|
+
transaction: Transaction;
|
30
|
+
}>;
|
31
|
+
private createDataset;
|
32
|
+
getDatasets(ownerAddress: string): Promise<{
|
33
|
+
id: string | undefined;
|
34
|
+
name: any;
|
35
|
+
description: any;
|
36
|
+
dataType: any;
|
37
|
+
dataSize: any;
|
38
|
+
creator: any;
|
39
|
+
license: any;
|
40
|
+
tags: any;
|
41
|
+
}[]>;
|
42
|
+
getDatasetById(datasetId: string): Promise<{
|
43
|
+
id: string;
|
44
|
+
name: any;
|
45
|
+
description: any;
|
46
|
+
dataType: any;
|
47
|
+
dataSize: any;
|
48
|
+
creator: any;
|
49
|
+
license: any;
|
50
|
+
tags: any;
|
51
|
+
}>;
|
52
|
+
private uploadDatasetFiles;
|
53
|
+
private uploadMedia;
|
54
|
+
uploadTrainingData(file: File, address: string): Promise<WalrusStorageInfo>;
|
55
|
+
private transformResponse;
|
56
|
+
getTrainingData(blobIds: string[]): Promise<Blob[]>;
|
57
|
+
getMedia(blobId: string): Promise<Blob>;
|
58
|
+
}
|
package/dist/dataset.js
ADDED
@@ -0,0 +1,323 @@
|
|
1
|
+
// src/sdk.ts
|
2
|
+
import { SuiClient } from "@mysten/sui/client";
|
3
|
+
import { Transaction } from "@mysten/sui/transactions";
|
4
|
+
import { WalrusStorageStatus } from "./types.js";
|
5
|
+
export class OpenGraphClient {
|
6
|
+
constructor(config) {
|
7
|
+
this.walrusNetwork = "testnet";
|
8
|
+
this.walrusPublisherUrl = "https://publisher.testnet.walrus.atalma.io";
|
9
|
+
this.walrusAggregatorUrl = "https://aggregator.testnet.walrus.atalma.io";
|
10
|
+
this.networkUrl = config.networkUrl;
|
11
|
+
this.packageId = config.packageId;
|
12
|
+
this.gasBudget = config.gasBudget;
|
13
|
+
this.suiClient = new SuiClient({ url: config.networkUrl });
|
14
|
+
this.walrusNetwork = config.walrusNetwork ?? "testnet";
|
15
|
+
this.walrusPublisherUrl = config.walrusPublisherUrl ?? "https://publisher.testnet.walrus.atalma.io";
|
16
|
+
this.walrusAggregatorUrl = config.walrusAggregatorUrl ?? "https://aggregator.testnet.walrus.atalma.io";
|
17
|
+
}
|
18
|
+
// =============================
|
19
|
+
// Getter Methods
|
20
|
+
// =============================
|
21
|
+
getNetworkUrl() {
|
22
|
+
return this.networkUrl;
|
23
|
+
}
|
24
|
+
getPackageId() {
|
25
|
+
return this.packageId;
|
26
|
+
}
|
27
|
+
getGasBudget() {
|
28
|
+
return this.gasBudget;
|
29
|
+
}
|
30
|
+
getWalrusNetwork() {
|
31
|
+
return this.walrusNetwork;
|
32
|
+
}
|
33
|
+
getWalrusPublisherUrl() {
|
34
|
+
return this.walrusPublisherUrl;
|
35
|
+
}
|
36
|
+
getWalrusAggregatorUrl() {
|
37
|
+
return this.walrusAggregatorUrl;
|
38
|
+
}
|
39
|
+
getConfig() {
|
40
|
+
return {
|
41
|
+
networkUrl: this.networkUrl,
|
42
|
+
packageId: this.packageId,
|
43
|
+
gasBudget: this.gasBudget,
|
44
|
+
walrusNetwork: this.walrusNetwork,
|
45
|
+
walrusPublisherUrl: this.walrusPublisherUrl,
|
46
|
+
walrusAggregatorUrl: this.walrusAggregatorUrl,
|
47
|
+
};
|
48
|
+
}
|
49
|
+
// =============================
|
50
|
+
// Setter Methods
|
51
|
+
// =============================
|
52
|
+
setNetworkUrl(url) {
|
53
|
+
this.networkUrl = url;
|
54
|
+
this.suiClient = new SuiClient({ url }); // 변경 시 client 재생성
|
55
|
+
}
|
56
|
+
setPackageId(packageId) {
|
57
|
+
this.packageId = packageId;
|
58
|
+
}
|
59
|
+
setGasBudget(gasBudget) {
|
60
|
+
this.gasBudget = gasBudget;
|
61
|
+
}
|
62
|
+
setWalrusNetwork(network) {
|
63
|
+
this.walrusNetwork = network;
|
64
|
+
}
|
65
|
+
setWalrusPublisherUrl(url) {
|
66
|
+
this.walrusPublisherUrl = url;
|
67
|
+
}
|
68
|
+
setWalrusAggregatorUrl(url) {
|
69
|
+
this.walrusAggregatorUrl = url;
|
70
|
+
}
|
71
|
+
// Utility
|
72
|
+
getSuiScanUrl(type, id) {
|
73
|
+
const baseUrl = `https://suiscan.xyz/${this.walrusNetwork}`;
|
74
|
+
if (type === "transaction") {
|
75
|
+
return `${baseUrl}/tx/${id}`;
|
76
|
+
}
|
77
|
+
else if (type === "account") {
|
78
|
+
return `${baseUrl}/account/${id}`;
|
79
|
+
}
|
80
|
+
else {
|
81
|
+
return `${baseUrl}/object/${id}`;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
// walrus and create ptb tx
|
85
|
+
async uploadDataset(files, address, metadata, annotations, epochs) {
|
86
|
+
const storageInfos = await this.uploadDatasetFiles(files, address, epochs);
|
87
|
+
const dataFiles = await Promise.all(storageInfos.map(async (info, i) => {
|
88
|
+
const file = files[i];
|
89
|
+
const arrayBuffer = await file.arrayBuffer();
|
90
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
|
91
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
92
|
+
const fileHash = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
|
93
|
+
return {
|
94
|
+
blobId: info.blobId,
|
95
|
+
fileHash,
|
96
|
+
};
|
97
|
+
}));
|
98
|
+
const tx = await this.createDataset(address, metadata, annotations, dataFiles);
|
99
|
+
return {
|
100
|
+
storageInfos,
|
101
|
+
transaction: tx,
|
102
|
+
};
|
103
|
+
}
|
104
|
+
// create ptb
|
105
|
+
async createDataset(accountAddress, metadata, annotations, files) {
|
106
|
+
const tx = new Transaction();
|
107
|
+
tx.setGasBudget(this.gasBudget);
|
108
|
+
const metadataObject = tx.moveCall({
|
109
|
+
target: `${this.packageId}::metadata::new_metadata`,
|
110
|
+
arguments: [
|
111
|
+
tx.pure.option("string", metadata.description),
|
112
|
+
tx.pure.string(metadata.dataType),
|
113
|
+
tx.pure.u64(BigInt(metadata.dataSize)),
|
114
|
+
tx.pure.option("string", metadata.creator),
|
115
|
+
tx.pure.option("string", metadata.license),
|
116
|
+
tx.pure.option("vector<string>", metadata.tags),
|
117
|
+
],
|
118
|
+
});
|
119
|
+
const dataset = tx.moveCall({
|
120
|
+
target: `${this.packageId}::dataset::new_dataset`,
|
121
|
+
arguments: [tx.pure.string(metadata.name), metadataObject],
|
122
|
+
});
|
123
|
+
for (let i = 0; i < files.length; i++) {
|
124
|
+
const rangeOptionObject = tx.moveCall({
|
125
|
+
target: `${this.packageId}::dataset::new_range_option`,
|
126
|
+
arguments: [tx.pure.option("u64", null), tx.pure.option("u64", null)],
|
127
|
+
});
|
128
|
+
const dataObject = tx.moveCall({
|
129
|
+
target: `${this.packageId}::dataset::new_data`,
|
130
|
+
arguments: [
|
131
|
+
tx.pure.string(`data_${i}`),
|
132
|
+
tx.pure.string(files[i].blobId),
|
133
|
+
tx.pure.string(files[i].fileHash),
|
134
|
+
rangeOptionObject,
|
135
|
+
],
|
136
|
+
});
|
137
|
+
tx.moveCall({
|
138
|
+
target: `${this.packageId}::dataset::add_annotation_label`,
|
139
|
+
arguments: [dataObject, tx.pure.string(annotations[i])],
|
140
|
+
});
|
141
|
+
tx.moveCall({
|
142
|
+
target: `${this.packageId}::dataset::add_data`,
|
143
|
+
arguments: [dataset, dataObject],
|
144
|
+
});
|
145
|
+
}
|
146
|
+
tx.transferObjects([dataset], accountAddress);
|
147
|
+
return tx;
|
148
|
+
}
|
149
|
+
async getDatasets(ownerAddress) {
|
150
|
+
if (!ownerAddress) {
|
151
|
+
throw new Error("OwnerAddress ID is required");
|
152
|
+
}
|
153
|
+
const { data } = await this.suiClient.getOwnedObjects({ owner: ownerAddress });
|
154
|
+
const objectIds = data
|
155
|
+
.map(d => d.data?.objectId)
|
156
|
+
.filter((id) => id !== undefined);
|
157
|
+
const objects = await this.suiClient.multiGetObjects({
|
158
|
+
ids: objectIds,
|
159
|
+
options: { showContent: true, showType: true },
|
160
|
+
});
|
161
|
+
return objects
|
162
|
+
.filter(obj => obj.data?.content?.dataType === "moveObject" &&
|
163
|
+
obj.data?.content?.type?.includes("dataset::Dataset"))
|
164
|
+
.map(obj => {
|
165
|
+
const content = obj.data?.content;
|
166
|
+
return {
|
167
|
+
id: obj.data?.objectId,
|
168
|
+
name: content.fields.name,
|
169
|
+
description: content.fields.description,
|
170
|
+
dataType: content.fields.data_type,
|
171
|
+
dataSize: content.fields.data_size,
|
172
|
+
creator: content.fields.creator,
|
173
|
+
license: content.fields.license,
|
174
|
+
tags: content.fields.tags,
|
175
|
+
};
|
176
|
+
});
|
177
|
+
}
|
178
|
+
async getDatasetById(datasetId) {
|
179
|
+
if (!datasetId) {
|
180
|
+
throw new Error("Dataset ID is required");
|
181
|
+
}
|
182
|
+
const object = await this.suiClient.getObject({
|
183
|
+
id: datasetId,
|
184
|
+
options: { showContent: true, showType: true },
|
185
|
+
});
|
186
|
+
if (object.data?.content?.dataType !== "moveObject") {
|
187
|
+
throw new Error("Invalid dataset object");
|
188
|
+
}
|
189
|
+
const content = object.data?.content;
|
190
|
+
return {
|
191
|
+
id: object.data?.objectId,
|
192
|
+
name: content.fields.name,
|
193
|
+
description: content.fields.description,
|
194
|
+
dataType: content.fields.data_type,
|
195
|
+
dataSize: content.fields.data_size,
|
196
|
+
creator: content.fields.creator,
|
197
|
+
license: content.fields.license,
|
198
|
+
tags: content.fields.tags,
|
199
|
+
};
|
200
|
+
}
|
201
|
+
// walrus
|
202
|
+
async uploadDatasetFiles(files, address, epochs) {
|
203
|
+
const uploadPromises = files.map(file => this.uploadMedia(file, address, epochs));
|
204
|
+
return await Promise.all(uploadPromises);
|
205
|
+
}
|
206
|
+
// 미디어 업로드
|
207
|
+
async uploadMedia(file, sendTo, epochs) {
|
208
|
+
try {
|
209
|
+
let epochsParam = epochs ? `&epochs=${epochs}` : "";
|
210
|
+
const url = `${this.walrusPublisherUrl}/v1/blobs?send_object_to=${sendTo}${epochsParam}`;
|
211
|
+
const response = await fetch(url, {
|
212
|
+
method: "PUT",
|
213
|
+
headers: {
|
214
|
+
"Content-Type": file.type,
|
215
|
+
"Content-Length": file.size.toString(),
|
216
|
+
},
|
217
|
+
body: file,
|
218
|
+
});
|
219
|
+
if (!response.ok) {
|
220
|
+
const errorText = await response.text();
|
221
|
+
throw new Error(`업로드 실패: ${response.status} ${response.statusText} - ${errorText}`);
|
222
|
+
}
|
223
|
+
const data = await response.json();
|
224
|
+
let storageInfo;
|
225
|
+
if ("alreadyCertified" in data) {
|
226
|
+
storageInfo = {
|
227
|
+
status: WalrusStorageStatus.ALREADY_CERTIFIED,
|
228
|
+
blobId: data.alreadyCertified.blobId,
|
229
|
+
endEpoch: data.alreadyCertified.endEpoch,
|
230
|
+
suiRefType: "Previous Sui Certified Event",
|
231
|
+
suiRef: data.alreadyCertified.event.txDigest,
|
232
|
+
mediaUrl: `${this.walrusAggregatorUrl}/v1/blobs/${data.alreadyCertified.blobId}`,
|
233
|
+
suiScanUrl: this.getSuiScanUrl("transaction", data.alreadyCertified.event.txDigest),
|
234
|
+
suiRefId: data.alreadyCertified.event.txDigest,
|
235
|
+
};
|
236
|
+
}
|
237
|
+
else if ("newlyCreated" in data) {
|
238
|
+
storageInfo = {
|
239
|
+
status: WalrusStorageStatus.NEWLY_CREATED,
|
240
|
+
blobId: data.newlyCreated.blobObject.blobId,
|
241
|
+
endEpoch: data.newlyCreated.blobObject.storage.endEpoch,
|
242
|
+
suiRefType: "Associated Sui Object",
|
243
|
+
suiRef: data.newlyCreated.blobObject.id,
|
244
|
+
mediaUrl: `${this.walrusAggregatorUrl}/v1/blobs/${data.newlyCreated.blobObject.blobId}`,
|
245
|
+
suiScanUrl: this.getSuiScanUrl("object", data.newlyCreated.blobObject.id),
|
246
|
+
suiRefId: data.newlyCreated.blobObject.id,
|
247
|
+
};
|
248
|
+
}
|
249
|
+
else {
|
250
|
+
throw new Error("알 수 없는 응답 형식");
|
251
|
+
}
|
252
|
+
return storageInfo;
|
253
|
+
}
|
254
|
+
catch (error) {
|
255
|
+
console.error("미디어 업로드 오류:", error);
|
256
|
+
throw new Error(`미디어 업로드 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}`);
|
257
|
+
}
|
258
|
+
}
|
259
|
+
async uploadTrainingData(file, address) {
|
260
|
+
const response = await fetch(`${this.walrusPublisherUrl}/v1/blobs?send_object_to=${address}`, {
|
261
|
+
method: "PUT",
|
262
|
+
body: file,
|
263
|
+
});
|
264
|
+
if (!response.ok) {
|
265
|
+
throw new Error(`Failed to upload file: ${response.statusText}`);
|
266
|
+
}
|
267
|
+
const data = await response.json();
|
268
|
+
return this.transformResponse(data);
|
269
|
+
}
|
270
|
+
transformResponse(data) {
|
271
|
+
if ("alreadyCertified" in data) {
|
272
|
+
return {
|
273
|
+
status: WalrusStorageStatus.ALREADY_CERTIFIED,
|
274
|
+
blobId: data.alreadyCertified.blobId,
|
275
|
+
endEpoch: data.alreadyCertified.endEpoch,
|
276
|
+
suiRefType: "Previous Sui Certified Event",
|
277
|
+
suiRef: data.alreadyCertified.event.txDigest,
|
278
|
+
mediaUrl: `${this.walrusAggregatorUrl}/v1/blobs/${data.alreadyCertified.blobId}`,
|
279
|
+
suiScanUrl: this.getSuiScanUrl("transaction", data.alreadyCertified.event.txDigest),
|
280
|
+
suiRefId: data.alreadyCertified.event.txDigest,
|
281
|
+
};
|
282
|
+
}
|
283
|
+
else if ("newlyCreated" in data) {
|
284
|
+
return {
|
285
|
+
status: WalrusStorageStatus.NEWLY_CREATED,
|
286
|
+
blobId: data.newlyCreated.blobObject.blobId,
|
287
|
+
endEpoch: data.newlyCreated.blobObject.storage.endEpoch,
|
288
|
+
suiRefType: "Associated Sui Object",
|
289
|
+
suiRef: data.newlyCreated.blobObject.id,
|
290
|
+
mediaUrl: `${this.walrusAggregatorUrl}/v1/blobs/${data.newlyCreated.blobObject.blobId}`,
|
291
|
+
suiScanUrl: this.getSuiScanUrl("object", data.newlyCreated.blobObject.id),
|
292
|
+
suiRefId: data.newlyCreated.blobObject.id,
|
293
|
+
};
|
294
|
+
}
|
295
|
+
else {
|
296
|
+
throw new Error("Unknown response format");
|
297
|
+
}
|
298
|
+
}
|
299
|
+
async getTrainingData(blobIds) {
|
300
|
+
try {
|
301
|
+
const getPromises = blobIds.map(blobId => this.getMedia(blobId));
|
302
|
+
return await Promise.all(getPromises);
|
303
|
+
}
|
304
|
+
catch (error) {
|
305
|
+
console.error("학습 데이터 가져오기 오류:", error);
|
306
|
+
throw error;
|
307
|
+
}
|
308
|
+
}
|
309
|
+
async getMedia(blobId) {
|
310
|
+
try {
|
311
|
+
const url = `${this.walrusAggregatorUrl}/v1/blobs/${blobId}`;
|
312
|
+
const response = await fetch(url);
|
313
|
+
if (!response.ok) {
|
314
|
+
throw new Error(`미디어 가져오기 실패: ${response.status} ${response.statusText}`);
|
315
|
+
}
|
316
|
+
return await response.blob();
|
317
|
+
}
|
318
|
+
catch (error) {
|
319
|
+
console.error("미디어 가져오기 오류:", error);
|
320
|
+
throw error;
|
321
|
+
}
|
322
|
+
}
|
323
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
export type OpenGraphClientConfig = {
|
2
|
+
networkUrl: string;
|
3
|
+
packageId: string;
|
4
|
+
gasBudget: number;
|
5
|
+
walrusNetwork?: string;
|
6
|
+
walrusPublisherUrl?: string;
|
7
|
+
walrusAggregatorUrl?: string;
|
8
|
+
};
|
9
|
+
export interface DatasetMetadata {
|
10
|
+
name: string;
|
11
|
+
description?: string;
|
12
|
+
tags?: string[];
|
13
|
+
dataType: string;
|
14
|
+
dataSize: number;
|
15
|
+
creator?: string;
|
16
|
+
license?: string;
|
17
|
+
}
|
18
|
+
export declare enum WalrusStorageStatus {
|
19
|
+
ALREADY_CERTIFIED = "Already certified",
|
20
|
+
NEWLY_CREATED = "Newly created",
|
21
|
+
UNKNOWN = "Unknown"
|
22
|
+
}
|
23
|
+
export interface WalrusStorageInfo {
|
24
|
+
blobId: string;
|
25
|
+
endEpoch: number;
|
26
|
+
status: WalrusStorageStatus;
|
27
|
+
suiRef: string;
|
28
|
+
suiRefType: string;
|
29
|
+
mediaUrl: string;
|
30
|
+
suiScanUrl: string;
|
31
|
+
suiRefId: string;
|
32
|
+
}
|
package/dist/types.js
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
// src/types.ts
|
2
|
+
export var WalrusStorageStatus;
|
3
|
+
(function (WalrusStorageStatus) {
|
4
|
+
WalrusStorageStatus["ALREADY_CERTIFIED"] = "Already certified";
|
5
|
+
WalrusStorageStatus["NEWLY_CREATED"] = "Newly created";
|
6
|
+
WalrusStorageStatus["UNKNOWN"] = "Unknown";
|
7
|
+
})(WalrusStorageStatus || (WalrusStorageStatus = {}));
|
package/package.json
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"name": "tensorflow-sui2",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"main": "dist/index.js",
|
5
|
+
"types": "dist/index.d.ts",
|
6
|
+
"scripts": {
|
7
|
+
"build": "tsc"
|
8
|
+
},
|
9
|
+
"dependencies": {
|
10
|
+
"@mysten/sui": "^1.27.0",
|
11
|
+
"@mysten/sui.js": "^0.44.0"
|
12
|
+
},
|
13
|
+
"devDependencies": {
|
14
|
+
"typescript": "^5.3.0"
|
15
|
+
},
|
16
|
+
"keywords": [],
|
17
|
+
"author": "",
|
18
|
+
"license": "ISC",
|
19
|
+
"description": ""
|
20
|
+
}
|
package/src/dataset.ts
ADDED
@@ -0,0 +1,388 @@
|
|
1
|
+
// src/sdk.ts
|
2
|
+
import { SuiClient } from "@mysten/sui/client";
|
3
|
+
import { Transaction } from "@mysten/sui/transactions";
|
4
|
+
import { OpenGraphClientConfig, DatasetMetadata, WalrusStorageInfo, WalrusStorageStatus } from "./types.js";
|
5
|
+
|
6
|
+
export class OpenGraphClient {
|
7
|
+
private networkUrl: string;
|
8
|
+
private packageId: string;
|
9
|
+
private gasBudget: number;
|
10
|
+
public suiClient: SuiClient;
|
11
|
+
|
12
|
+
private walrusNetwork = "testnet";
|
13
|
+
private walrusPublisherUrl = "https://publisher.testnet.walrus.atalma.io";
|
14
|
+
private walrusAggregatorUrl = "https://aggregator.testnet.walrus.atalma.io";
|
15
|
+
|
16
|
+
constructor(config: OpenGraphClientConfig) {
|
17
|
+
this.networkUrl = config.networkUrl;
|
18
|
+
this.packageId = config.packageId;
|
19
|
+
this.gasBudget = config.gasBudget;
|
20
|
+
this.suiClient = new SuiClient({ url: config.networkUrl });
|
21
|
+
|
22
|
+
this.walrusNetwork = config.walrusNetwork ?? "testnet";
|
23
|
+
this.walrusPublisherUrl = config.walrusPublisherUrl ?? "https://publisher.testnet.walrus.atalma.io";
|
24
|
+
this.walrusAggregatorUrl = config.walrusAggregatorUrl ?? "https://aggregator.testnet.walrus.atalma.io";
|
25
|
+
}
|
26
|
+
|
27
|
+
// =============================
|
28
|
+
// Getter Methods
|
29
|
+
// =============================
|
30
|
+
public getNetworkUrl(): string {
|
31
|
+
return this.networkUrl;
|
32
|
+
}
|
33
|
+
|
34
|
+
public getPackageId(): string {
|
35
|
+
return this.packageId;
|
36
|
+
}
|
37
|
+
|
38
|
+
public getGasBudget(): number {
|
39
|
+
return this.gasBudget;
|
40
|
+
}
|
41
|
+
|
42
|
+
public getWalrusNetwork(): string {
|
43
|
+
return this.walrusNetwork;
|
44
|
+
}
|
45
|
+
|
46
|
+
public getWalrusPublisherUrl(): string {
|
47
|
+
return this.walrusPublisherUrl;
|
48
|
+
}
|
49
|
+
|
50
|
+
public getWalrusAggregatorUrl(): string {
|
51
|
+
return this.walrusAggregatorUrl;
|
52
|
+
}
|
53
|
+
|
54
|
+
public getConfig(): OpenGraphClientConfig {
|
55
|
+
return {
|
56
|
+
networkUrl: this.networkUrl,
|
57
|
+
packageId: this.packageId,
|
58
|
+
gasBudget: this.gasBudget,
|
59
|
+
walrusNetwork: this.walrusNetwork,
|
60
|
+
walrusPublisherUrl: this.walrusPublisherUrl,
|
61
|
+
walrusAggregatorUrl: this.walrusAggregatorUrl,
|
62
|
+
};
|
63
|
+
}
|
64
|
+
|
65
|
+
// =============================
|
66
|
+
// Setter Methods
|
67
|
+
// =============================
|
68
|
+
public setNetworkUrl(url: string): void {
|
69
|
+
this.networkUrl = url;
|
70
|
+
this.suiClient = new SuiClient({ url }); // 변경 시 client 재생성
|
71
|
+
}
|
72
|
+
|
73
|
+
public setPackageId(packageId: string): void {
|
74
|
+
this.packageId = packageId;
|
75
|
+
}
|
76
|
+
|
77
|
+
public setGasBudget(gasBudget: number): void {
|
78
|
+
this.gasBudget = gasBudget;
|
79
|
+
}
|
80
|
+
|
81
|
+
public setWalrusNetwork(network: string) {
|
82
|
+
this.walrusNetwork = network;
|
83
|
+
}
|
84
|
+
|
85
|
+
public setWalrusPublisherUrl(url: string) {
|
86
|
+
this.walrusPublisherUrl = url;
|
87
|
+
}
|
88
|
+
|
89
|
+
public setWalrusAggregatorUrl(url: string) {
|
90
|
+
this.walrusAggregatorUrl = url;
|
91
|
+
}
|
92
|
+
|
93
|
+
// Utility
|
94
|
+
private getSuiScanUrl(type: "transaction" | "object" | "account", id: string) {
|
95
|
+
const baseUrl = `https://suiscan.xyz/${this.walrusNetwork}`;
|
96
|
+
if (type === "transaction") {
|
97
|
+
return `${baseUrl}/tx/${id}`;
|
98
|
+
} else if (type === "account") {
|
99
|
+
return `${baseUrl}/account/${id}`;
|
100
|
+
} else {
|
101
|
+
return `${baseUrl}/object/${id}`;
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
// walrus and create ptb tx
|
106
|
+
public async uploadDataset(
|
107
|
+
files: File[],
|
108
|
+
address: string,
|
109
|
+
metadata: DatasetMetadata,
|
110
|
+
annotations: string[],
|
111
|
+
epochs?: number
|
112
|
+
) {
|
113
|
+
const storageInfos = await this.uploadDatasetFiles(files, address, epochs);
|
114
|
+
|
115
|
+
const dataFiles = await Promise.all(
|
116
|
+
storageInfos.map(async (info, i) => {
|
117
|
+
const file = files[i];
|
118
|
+
const arrayBuffer = await file.arrayBuffer();
|
119
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
|
120
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
121
|
+
const fileHash = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
|
122
|
+
|
123
|
+
return {
|
124
|
+
blobId: info.blobId,
|
125
|
+
fileHash,
|
126
|
+
};
|
127
|
+
})
|
128
|
+
);
|
129
|
+
|
130
|
+
const tx = await this.createDataset(address, metadata, annotations, dataFiles);
|
131
|
+
|
132
|
+
return {
|
133
|
+
storageInfos,
|
134
|
+
transaction: tx,
|
135
|
+
};
|
136
|
+
}
|
137
|
+
|
138
|
+
// create ptb
|
139
|
+
private async createDataset(
|
140
|
+
accountAddress: string,
|
141
|
+
metadata: DatasetMetadata,
|
142
|
+
annotations: string[],
|
143
|
+
files: { blobId: string; fileHash: string }[]
|
144
|
+
) {
|
145
|
+
const tx = new Transaction();
|
146
|
+
tx.setGasBudget(this.gasBudget);
|
147
|
+
|
148
|
+
const metadataObject = tx.moveCall({
|
149
|
+
target: `${this.packageId}::metadata::new_metadata`,
|
150
|
+
arguments: [
|
151
|
+
tx.pure.option("string", metadata.description),
|
152
|
+
tx.pure.string(metadata.dataType),
|
153
|
+
tx.pure.u64(BigInt(metadata.dataSize)),
|
154
|
+
tx.pure.option("string", metadata.creator),
|
155
|
+
tx.pure.option("string", metadata.license),
|
156
|
+
tx.pure.option("vector<string>", metadata.tags),
|
157
|
+
],
|
158
|
+
});
|
159
|
+
|
160
|
+
const dataset = tx.moveCall({
|
161
|
+
target: `${this.packageId}::dataset::new_dataset`,
|
162
|
+
arguments: [tx.pure.string(metadata.name), metadataObject],
|
163
|
+
});
|
164
|
+
|
165
|
+
for (let i = 0; i < files.length; i++) {
|
166
|
+
const rangeOptionObject = tx.moveCall({
|
167
|
+
target: `${this.packageId}::dataset::new_range_option`,
|
168
|
+
arguments: [tx.pure.option("u64", null), tx.pure.option("u64", null)],
|
169
|
+
});
|
170
|
+
|
171
|
+
const dataObject = tx.moveCall({
|
172
|
+
target: `${this.packageId}::dataset::new_data`,
|
173
|
+
arguments: [
|
174
|
+
tx.pure.string(`data_${i}`),
|
175
|
+
tx.pure.string(files[i].blobId),
|
176
|
+
tx.pure.string(files[i].fileHash),
|
177
|
+
rangeOptionObject,
|
178
|
+
],
|
179
|
+
});
|
180
|
+
|
181
|
+
tx.moveCall({
|
182
|
+
target: `${this.packageId}::dataset::add_annotation_label`,
|
183
|
+
arguments: [dataObject, tx.pure.string(annotations[i])],
|
184
|
+
});
|
185
|
+
|
186
|
+
tx.moveCall({
|
187
|
+
target: `${this.packageId}::dataset::add_data`,
|
188
|
+
arguments: [dataset, dataObject],
|
189
|
+
});
|
190
|
+
}
|
191
|
+
|
192
|
+
tx.transferObjects([dataset], accountAddress);
|
193
|
+
return tx;
|
194
|
+
}
|
195
|
+
|
196
|
+
public async getDatasets(ownerAddress: string) {
|
197
|
+
if (!ownerAddress) {
|
198
|
+
throw new Error("OwnerAddress ID is required");
|
199
|
+
}
|
200
|
+
|
201
|
+
const { data } = await this.suiClient.getOwnedObjects({ owner: ownerAddress });
|
202
|
+
const objectIds = data
|
203
|
+
.map(d => d.data?.objectId)
|
204
|
+
.filter((id): id is string => id !== undefined);
|
205
|
+
|
206
|
+
const objects = await this.suiClient.multiGetObjects({
|
207
|
+
ids: objectIds,
|
208
|
+
options: { showContent: true, showType: true },
|
209
|
+
});
|
210
|
+
|
211
|
+
return objects
|
212
|
+
.filter(obj =>
|
213
|
+
obj.data?.content?.dataType === "moveObject" &&
|
214
|
+
obj.data?.content?.type?.includes("dataset::Dataset"))
|
215
|
+
.map(obj => {
|
216
|
+
const content = obj.data?.content as any;
|
217
|
+
return {
|
218
|
+
id: obj.data?.objectId,
|
219
|
+
name: content.fields.name,
|
220
|
+
description: content.fields.description,
|
221
|
+
dataType: content.fields.data_type,
|
222
|
+
dataSize: content.fields.data_size,
|
223
|
+
creator: content.fields.creator,
|
224
|
+
license: content.fields.license,
|
225
|
+
tags: content.fields.tags,
|
226
|
+
};
|
227
|
+
});
|
228
|
+
}
|
229
|
+
|
230
|
+
public async getDatasetById(datasetId: string) {
|
231
|
+
if (!datasetId) {
|
232
|
+
throw new Error("Dataset ID is required");
|
233
|
+
}
|
234
|
+
|
235
|
+
const object = await this.suiClient.getObject({
|
236
|
+
id: datasetId,
|
237
|
+
options: { showContent: true, showType: true },
|
238
|
+
});
|
239
|
+
|
240
|
+
if (object.data?.content?.dataType !== "moveObject") {
|
241
|
+
throw new Error("Invalid dataset object");
|
242
|
+
}
|
243
|
+
|
244
|
+
const content = object.data?.content as any;
|
245
|
+
return {
|
246
|
+
id: object.data?.objectId,
|
247
|
+
name: content.fields.name,
|
248
|
+
description: content.fields.description,
|
249
|
+
dataType: content.fields.data_type,
|
250
|
+
dataSize: content.fields.data_size,
|
251
|
+
creator: content.fields.creator,
|
252
|
+
license: content.fields.license,
|
253
|
+
tags: content.fields.tags,
|
254
|
+
};
|
255
|
+
}
|
256
|
+
|
257
|
+
// walrus
|
258
|
+
private async uploadDatasetFiles(files: File[], address: string, epochs?: number): Promise<WalrusStorageInfo[]> {
|
259
|
+
const uploadPromises = files.map(file => this.uploadMedia(file, address, epochs));
|
260
|
+
return await Promise.all(uploadPromises);
|
261
|
+
}
|
262
|
+
|
263
|
+
// 미디어 업로드
|
264
|
+
private async uploadMedia(file: File, sendTo: string, epochs?: number): Promise<WalrusStorageInfo> {
|
265
|
+
try {
|
266
|
+
let epochsParam = epochs ? `&epochs=${epochs}` : "";
|
267
|
+
const url = `${this.walrusPublisherUrl}/v1/blobs?send_object_to=${sendTo}${epochsParam}`;
|
268
|
+
|
269
|
+
const response = await fetch(url, {
|
270
|
+
method: "PUT",
|
271
|
+
headers: {
|
272
|
+
"Content-Type": file.type,
|
273
|
+
"Content-Length": file.size.toString(),
|
274
|
+
},
|
275
|
+
body: file,
|
276
|
+
});
|
277
|
+
|
278
|
+
if (!response.ok) {
|
279
|
+
const errorText = await response.text();
|
280
|
+
throw new Error(`업로드 실패: ${response.status} ${response.statusText} - ${errorText}`);
|
281
|
+
}
|
282
|
+
|
283
|
+
const data = await response.json();
|
284
|
+
let storageInfo: WalrusStorageInfo;
|
285
|
+
|
286
|
+
if ("alreadyCertified" in data) {
|
287
|
+
storageInfo = {
|
288
|
+
status: WalrusStorageStatus.ALREADY_CERTIFIED,
|
289
|
+
blobId: data.alreadyCertified.blobId,
|
290
|
+
endEpoch: data.alreadyCertified.endEpoch,
|
291
|
+
suiRefType: "Previous Sui Certified Event",
|
292
|
+
suiRef: data.alreadyCertified.event.txDigest,
|
293
|
+
mediaUrl: `${this.walrusAggregatorUrl}/v1/blobs/${data.alreadyCertified.blobId}`,
|
294
|
+
suiScanUrl: this.getSuiScanUrl("transaction", data.alreadyCertified.event.txDigest),
|
295
|
+
suiRefId: data.alreadyCertified.event.txDigest,
|
296
|
+
};
|
297
|
+
} else if ("newlyCreated" in data) {
|
298
|
+
storageInfo = {
|
299
|
+
status: WalrusStorageStatus.NEWLY_CREATED,
|
300
|
+
blobId: data.newlyCreated.blobObject.blobId,
|
301
|
+
endEpoch: data.newlyCreated.blobObject.storage.endEpoch,
|
302
|
+
suiRefType: "Associated Sui Object",
|
303
|
+
suiRef: data.newlyCreated.blobObject.id,
|
304
|
+
mediaUrl: `${this.walrusAggregatorUrl}/v1/blobs/${data.newlyCreated.blobObject.blobId}`,
|
305
|
+
suiScanUrl: this.getSuiScanUrl("object", data.newlyCreated.blobObject.id),
|
306
|
+
suiRefId: data.newlyCreated.blobObject.id,
|
307
|
+
};
|
308
|
+
} else {
|
309
|
+
throw new Error("알 수 없는 응답 형식");
|
310
|
+
}
|
311
|
+
|
312
|
+
return storageInfo;
|
313
|
+
} catch (error) {
|
314
|
+
console.error("미디어 업로드 오류:", error);
|
315
|
+
throw new Error(
|
316
|
+
`미디어 업로드 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}`
|
317
|
+
);
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
public async uploadTrainingData(file: File, address: string): Promise<WalrusStorageInfo> {
|
322
|
+
const response = await fetch(`${this.walrusPublisherUrl}/v1/blobs?send_object_to=${address}`, {
|
323
|
+
method: "PUT",
|
324
|
+
body: file,
|
325
|
+
});
|
326
|
+
|
327
|
+
if (!response.ok) {
|
328
|
+
throw new Error(`Failed to upload file: ${response.statusText}`);
|
329
|
+
}
|
330
|
+
|
331
|
+
const data = await response.json();
|
332
|
+
return this.transformResponse(data);
|
333
|
+
}
|
334
|
+
|
335
|
+
private transformResponse(data: any): WalrusStorageInfo {
|
336
|
+
if ("alreadyCertified" in data) {
|
337
|
+
return {
|
338
|
+
status: WalrusStorageStatus.ALREADY_CERTIFIED,
|
339
|
+
blobId: data.alreadyCertified.blobId,
|
340
|
+
endEpoch: data.alreadyCertified.endEpoch,
|
341
|
+
suiRefType: "Previous Sui Certified Event",
|
342
|
+
suiRef: data.alreadyCertified.event.txDigest,
|
343
|
+
mediaUrl: `${this.walrusAggregatorUrl}/v1/blobs/${data.alreadyCertified.blobId}`,
|
344
|
+
suiScanUrl: this.getSuiScanUrl("transaction", data.alreadyCertified.event.txDigest),
|
345
|
+
suiRefId: data.alreadyCertified.event.txDigest,
|
346
|
+
};
|
347
|
+
} else if ("newlyCreated" in data) {
|
348
|
+
return {
|
349
|
+
status: WalrusStorageStatus.NEWLY_CREATED,
|
350
|
+
blobId: data.newlyCreated.blobObject.blobId,
|
351
|
+
endEpoch: data.newlyCreated.blobObject.storage.endEpoch,
|
352
|
+
suiRefType: "Associated Sui Object",
|
353
|
+
suiRef: data.newlyCreated.blobObject.id,
|
354
|
+
mediaUrl: `${this.walrusAggregatorUrl}/v1/blobs/${data.newlyCreated.blobObject.blobId}`,
|
355
|
+
suiScanUrl: this.getSuiScanUrl("object", data.newlyCreated.blobObject.id),
|
356
|
+
suiRefId: data.newlyCreated.blobObject.id,
|
357
|
+
};
|
358
|
+
} else {
|
359
|
+
throw new Error("Unknown response format");
|
360
|
+
}
|
361
|
+
}
|
362
|
+
|
363
|
+
public async getTrainingData(blobIds: string[]): Promise<Blob[]> {
|
364
|
+
try {
|
365
|
+
const getPromises = blobIds.map(blobId => this.getMedia(blobId));
|
366
|
+
return await Promise.all(getPromises);
|
367
|
+
} catch (error) {
|
368
|
+
console.error("학습 데이터 가져오기 오류:", error);
|
369
|
+
throw error;
|
370
|
+
}
|
371
|
+
}
|
372
|
+
|
373
|
+
public async getMedia(blobId: string): Promise<Blob> {
|
374
|
+
try {
|
375
|
+
const url = `${this.walrusAggregatorUrl}/v1/blobs/${blobId}`;
|
376
|
+
const response = await fetch(url);
|
377
|
+
|
378
|
+
if (!response.ok) {
|
379
|
+
throw new Error(`미디어 가져오기 실패: ${response.status} ${response.statusText}`);
|
380
|
+
}
|
381
|
+
|
382
|
+
return await response.blob();
|
383
|
+
} catch (error) {
|
384
|
+
console.error("미디어 가져오기 오류:", error);
|
385
|
+
throw error;
|
386
|
+
}
|
387
|
+
}
|
388
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
// src/types.ts
|
2
|
+
|
3
|
+
export type OpenGraphClientConfig = {
|
4
|
+
networkUrl: string;
|
5
|
+
packageId: string;
|
6
|
+
gasBudget: number;
|
7
|
+
walrusNetwork?: string;
|
8
|
+
walrusPublisherUrl?: string;
|
9
|
+
walrusAggregatorUrl?: string;
|
10
|
+
};
|
11
|
+
|
12
|
+
export interface DatasetMetadata {
|
13
|
+
name: string;
|
14
|
+
description?: string;
|
15
|
+
tags?: string[];
|
16
|
+
dataType: string;
|
17
|
+
dataSize: number;
|
18
|
+
creator?: string;
|
19
|
+
license?: string;
|
20
|
+
}
|
21
|
+
|
22
|
+
export enum WalrusStorageStatus {
|
23
|
+
ALREADY_CERTIFIED = "Already certified",
|
24
|
+
NEWLY_CREATED = "Newly created",
|
25
|
+
UNKNOWN = "Unknown",
|
26
|
+
}
|
27
|
+
|
28
|
+
export interface WalrusStorageInfo {
|
29
|
+
blobId: string;
|
30
|
+
endEpoch: number;
|
31
|
+
status: WalrusStorageStatus;
|
32
|
+
suiRef: string;
|
33
|
+
suiRefType: string;
|
34
|
+
mediaUrl: string;
|
35
|
+
suiScanUrl: string;
|
36
|
+
suiRefId: string;
|
37
|
+
}
|
package/tsconfig.json
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
"target": "ES2020",
|
4
|
+
"module": "ESNext",
|
5
|
+
"declaration": true,
|
6
|
+
"outDir": "./dist",
|
7
|
+
"moduleResolution": "Node",
|
8
|
+
"rootDir": "./src",
|
9
|
+
"strict": true,
|
10
|
+
"esModuleInterop": true,
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
12
|
+
"skipLibCheck": true
|
13
|
+
},
|
14
|
+
}
|