@zing-protocol/zing-sdk 0.0.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.
Files changed (87) hide show
  1. package/README.md +12 -0
  2. package/package.json +37 -0
  3. package/src/_generated/index.js +6 -0
  4. package/src/_generated/utils/index.ts +181 -0
  5. package/src/_generated/zing_framework/coin_utils.ts +41 -0
  6. package/src/_generated/zing_framework/deps/sui/object.ts +16 -0
  7. package/src/_generated/zing_framework/derived_object_bag.ts +400 -0
  8. package/src/_generated/zing_framework/derived_table.ts +366 -0
  9. package/src/_generated/zing_governance/admin.ts +47 -0
  10. package/src/_generated/zing_governance/deps/sui/object.ts +16 -0
  11. package/src/_generated/zing_governance/treasury.ts +134 -0
  12. package/src/_generated/zing_identity/config.ts +191 -0
  13. package/src/_generated/zing_identity/deps/std/type_name.ts +25 -0
  14. package/src/_generated/zing_identity/deps/sui/object.ts +16 -0
  15. package/src/_generated/zing_identity/deps/sui/object_table.ts +26 -0
  16. package/src/_generated/zing_identity/deps/sui/table.ts +38 -0
  17. package/src/_generated/zing_identity/deps/sui/vec_set.ts +28 -0
  18. package/src/_generated/zing_identity/ecdsa.ts +166 -0
  19. package/src/_generated/zing_identity/identity.ts +661 -0
  20. package/src/_generated/zing_identity/reclaim.ts +803 -0
  21. package/src/_generated/zing_studio/app.ts +855 -0
  22. package/src/_generated/zing_studio/article.ts +598 -0
  23. package/src/_generated/zing_studio/config.ts +475 -0
  24. package/src/_generated/zing_studio/deps/std/type_name.ts +25 -0
  25. package/src/_generated/zing_studio/deps/sui/bag.ts +43 -0
  26. package/src/_generated/zing_studio/deps/sui/balance.ts +20 -0
  27. package/src/_generated/zing_studio/deps/sui/coin.ts +21 -0
  28. package/src/_generated/zing_studio/deps/sui/dynamic_field.ts +272 -0
  29. package/src/_generated/zing_studio/deps/sui/object.ts +16 -0
  30. package/src/_generated/zing_studio/deps/sui/table.ts +38 -0
  31. package/src/_generated/zing_studio/deps/sui/vec_map.ts +39 -0
  32. package/src/_generated/zing_studio/deps/sui/vec_set.ts +28 -0
  33. package/src/_generated/zing_studio/deps/walrus/blob.ts +22 -0
  34. package/src/_generated/zing_studio/deps/walrus/events.ts +124 -0
  35. package/src/_generated/zing_studio/deps/walrus/metadata.ts +137 -0
  36. package/src/_generated/zing_studio/deps/walrus/storage_resource.ts +17 -0
  37. package/src/_generated/zing_studio/deps/zing_framework/derived_object_bag.ts +17 -0
  38. package/src/_generated/zing_studio/deps/zing_framework/derived_table.ts +17 -0
  39. package/src/_generated/zing_studio/donation.ts +18 -0
  40. package/src/_generated/zing_studio/enclave.ts +466 -0
  41. package/src/_generated/zing_studio/nitro_attestation.ts +308 -0
  42. package/src/_generated/zing_studio/referral.ts +447 -0
  43. package/src/_generated/zing_studio/storage.ts +664 -0
  44. package/src/_generated/zing_studio/studio.ts +682 -0
  45. package/src/bcs.ts +139 -0
  46. package/src/client/index.ts +557 -0
  47. package/src/client/types.ts +28 -0
  48. package/src/components/index.ts +3 -0
  49. package/src/components/wallet-provider.tsx +53 -0
  50. package/src/components/walrus-provider.tsx +212 -0
  51. package/src/components/zing-provider.tsx +54 -0
  52. package/src/config/common.ts +82 -0
  53. package/src/config/index.ts +45 -0
  54. package/src/config/mainnet.ts +109 -0
  55. package/src/config/testnet.ts +111 -0
  56. package/src/config/types.ts +69 -0
  57. package/src/const.ts +32 -0
  58. package/src/getters.ts +523 -0
  59. package/src/hooks/index.ts +14 -0
  60. package/src/hooks/useGetStudio.ts +22 -0
  61. package/src/hooks/useGetTierPlan.ts +115 -0
  62. package/src/hooks/useReclaim.ts +165 -0
  63. package/src/hooks/useSignAndExecuteTransaction.ts +57 -0
  64. package/src/hooks/useSignPersonalMessage.ts +28 -0
  65. package/src/hooks/useZingInfiniteQueries.ts +92 -0
  66. package/src/hooks/useZingMutation.ts +66 -0
  67. package/src/hooks/useZingQueries.ts +107 -0
  68. package/src/hooks/useZingQuery.ts +88 -0
  69. package/src/index.ts +15 -0
  70. package/src/lib/utils/colorful-logger.ts +27 -0
  71. package/src/lib/utils/const.ts +25 -0
  72. package/src/lib/utils/helpers.ts +78 -0
  73. package/src/lib/utils/index.ts +5 -0
  74. package/src/lib/utils/init-app-with-shadow.ts +42 -0
  75. package/src/lib/utils/quilt.ts +317 -0
  76. package/src/lib/utils/reclaim.ts +148 -0
  77. package/src/lib/utils/types.ts +14 -0
  78. package/src/mutations/index.ts +3 -0
  79. package/src/mutations/seal.ts +78 -0
  80. package/src/mutations/signer.ts +241 -0
  81. package/src/mutations/walrus.ts +862 -0
  82. package/src/stores/index.ts +3 -0
  83. package/src/stores/walletStore.ts +360 -0
  84. package/src/stores/walrusStore.ts +118 -0
  85. package/src/stores/zkloginStore.ts +53 -0
  86. package/src/types.ts +120 -0
  87. package/tsconfig.json +12 -0
@@ -0,0 +1,862 @@
1
+ import { signAndExecuteGasPoolTransaction } from "./signer.js";
2
+ import {
3
+ certifyArticleBlob,
4
+ finalizePublishArticle,
5
+ startPublishArticle,
6
+ } from "../_generated/zing_studio/app.js";
7
+ import {
8
+ articleBlobAddMetadata,
9
+ articleBlobInsertOrUpdateMetadataPair,
10
+ articleBlobRemoveMetadata,
11
+ } from "../_generated/zing_studio/article.js";
12
+ import { _new as newMetadata } from "../_generated/zing_studio/deps/walrus/metadata.js";
13
+ import {
14
+ encodedBlobLength,
15
+ encodeQuiltPatchId,
16
+ getDerivedStorageID,
17
+ getDerivedStudioID,
18
+ getPublishedArticleAndBlobId,
19
+ signersToBitmap,
20
+ } from "../getters.js";
21
+ import { encryptData } from "../mutations/seal.js";
22
+ import { bcs } from "@mysten/sui/bcs";
23
+ import { Transaction } from "@mysten/sui/transactions";
24
+ import { blobIdToInt } from "@mysten/walrus";
25
+ import type { PublishArticleEvent } from "../_generated/zing_studio/article.js";
26
+ import type { WalrusUploadFlowStage } from "../config/common.js";
27
+ import type { SharedObjectRef, WalrusSystemState } from "../config/types.js";
28
+ import type { ZKLoginSignerState } from "../types.js";
29
+ import type { SuiGrpcClient } from "@mysten/sui/grpc";
30
+ import type { SuiJsonRpcClient } from "@mysten/sui/jsonRpc";
31
+ import type {
32
+ CertifyBlobOptions,
33
+ EncodingType,
34
+ WalrusClient,
35
+ WalrusFile,
36
+ WriteFilesFlowOptions,
37
+ WriteFilesFlowRegisterOptions,
38
+ WriteFilesFlowUploadOptions,
39
+ } from "@mysten/walrus";
40
+
41
+ const REDSTUFF_CODING_TYPE = 1;
42
+ // ZING_PREFIX
43
+ const FIXED_FILE_IV = new Uint8Array([
44
+ 4, 122, 105, 110, 103, 0, 0, 0, 0, 0, 0, 0,
45
+ ]);
46
+ const BYTES_PER_UNIT_SIZE = 1024 * 1024;
47
+
48
+ type ZingWriteFlowConfig = {
49
+ zingStudioPackageId: string;
50
+ zingStudioV0PackageId: string;
51
+ walrusPackageId: string;
52
+ zingStudioConfigSharedObjectRef: SharedObjectRef;
53
+ walrusSystemSharedObjectRef: SharedObjectRef;
54
+ zingStorageTreasurySharedObjectRef: SharedObjectRef;
55
+ blobRegisteredEventType: string;
56
+ publishArticleEventType: string;
57
+ };
58
+
59
+ export function buildPublishArticleTransaction(
60
+ config: ZingWriteFlowConfig,
61
+ walrusSystem: WalrusSystemState,
62
+ walTreasury: string,
63
+ storageByEpoch: Record<number, number>,
64
+ fileSize: number,
65
+ owner: string,
66
+ subscriptionLevel: null | number,
67
+ blobId: string,
68
+ rootHash: Uint8Array,
69
+ articleMetadata: {
70
+ identifier: string;
71
+ tags: {
72
+ "content-type": string;
73
+ };
74
+ size: number;
75
+ }[],
76
+ attributes: Record<string, string | null>,
77
+ existingAttributes: Record<string, string> | null,
78
+ ) {
79
+ const {
80
+ zingStudioPackageId,
81
+ walrusPackageId,
82
+ zingStudioConfigSharedObjectRef,
83
+ walrusSystemSharedObjectRef,
84
+ zingStorageTreasurySharedObjectRef,
85
+ } = config;
86
+ return (tx: Transaction) => {
87
+ const { encodedSize, writeCost } = storageCost(walrusSystem, fileSize, 2);
88
+
89
+ console.log({ encodedSize });
90
+ const currentEpoch = walrusSystem.committee.epoch;
91
+
92
+ if (!storageByEpoch?.[currentEpoch]) {
93
+ throw new Error(`No Epochs Found for ${currentEpoch}`);
94
+ }
95
+
96
+ if (!storageByEpoch?.[currentEpoch + 1]) {
97
+ throw new Error(`No Epochs Found for ${currentEpoch + 1}`);
98
+ }
99
+
100
+ if (storageByEpoch[currentEpoch] < encodedSize) {
101
+ throw new Error(
102
+ `Insufficient Storages size ${storageByEpoch[currentEpoch]} for Epochs ${currentEpoch} with required size ${encodedSize}`,
103
+ );
104
+ }
105
+
106
+ if (storageByEpoch[currentEpoch + 1] < encodedSize) {
107
+ throw new Error(
108
+ `Insufficient Storages size ${storageByEpoch[currentEpoch + 1]} for Epochs ${currentEpoch + 1} with required size ${encodedSize}`,
109
+ );
110
+ }
111
+
112
+ if (BigInt(walTreasury) < writeCost) {
113
+ throw new Error(
114
+ `Insufficient walTreasury Balance ${walTreasury} for writeCost: ${writeCost}`,
115
+ );
116
+ }
117
+
118
+ console.log({ fileSize, encodedSize });
119
+ console.log({ articleMetadata });
120
+ const [article, publishReceipt] = tx.add(
121
+ startPublishArticle({
122
+ package: zingStudioPackageId,
123
+ arguments: {
124
+ config: tx.sharedObjectRef(zingStudioConfigSharedObjectRef),
125
+ studio: tx.object(
126
+ getDerivedStudioID(zingStudioConfigSharedObjectRef.objectId, owner),
127
+ ),
128
+ walrusSystem: tx.sharedObjectRef(walrusSystemSharedObjectRef),
129
+ storageTreasury: tx.sharedObjectRef(
130
+ zingStorageTreasurySharedObjectRef,
131
+ ),
132
+ storageSpace: tx.object(
133
+ getDerivedStorageID(
134
+ zingStudioConfigSharedObjectRef.objectId,
135
+ owner,
136
+ ),
137
+ ),
138
+ subscriptionLevel: tx.pure.option("u8", subscriptionLevel),
139
+ blobId: blobIdToInt(blobId),
140
+ rootHash: BigInt(bcs.u256().parse(rootHash)),
141
+ unencodedBlobSize: fileSize,
142
+ encodingType: REDSTUFF_CODING_TYPE,
143
+ identifiers: tx.pure.vector(
144
+ "string",
145
+ articleMetadata.map((m) => m.identifier),
146
+ ),
147
+ blobIndexes: tx.pure.vector(
148
+ "u64",
149
+ articleMetadata.map(() => 0),
150
+ ),
151
+ mimeTypes: tx.pure.vector(
152
+ "string",
153
+ articleMetadata.map((m) => m.tags["content-type"]),
154
+ ),
155
+ metadataSizes: tx.pure.vector(
156
+ "u64",
157
+ articleMetadata.map((m) => m.size),
158
+ ),
159
+ },
160
+ }),
161
+ );
162
+
163
+ if (!existingAttributes) {
164
+ tx.add(
165
+ articleBlobAddMetadata({
166
+ package: zingStudioPackageId,
167
+ arguments: {
168
+ config: tx.sharedObjectRef(zingStudioConfigSharedObjectRef),
169
+ self: article,
170
+ blobIndex: tx.pure.u64(0),
171
+ metadata: newMetadata({
172
+ package: walrusPackageId,
173
+ }),
174
+ },
175
+ }),
176
+ );
177
+ }
178
+
179
+ Object.keys(attributes).forEach((key) => {
180
+ const value = attributes[key];
181
+
182
+ if (value === null) {
183
+ if (existingAttributes && key in existingAttributes) {
184
+ tx.add(
185
+ articleBlobRemoveMetadata({
186
+ package: zingStudioPackageId,
187
+ arguments: {
188
+ config: tx.sharedObjectRef(zingStudioConfigSharedObjectRef),
189
+ self: article,
190
+ blobIndex: tx.pure.u64(0),
191
+ key,
192
+ },
193
+ }),
194
+ );
195
+ }
196
+ } else {
197
+ tx.add(
198
+ articleBlobInsertOrUpdateMetadataPair({
199
+ package: zingStudioPackageId,
200
+ arguments: {
201
+ config: tx.sharedObjectRef(zingStudioConfigSharedObjectRef),
202
+ self: article,
203
+ blobIndex: tx.pure.u64(0),
204
+ key,
205
+ value,
206
+ },
207
+ }),
208
+ );
209
+ }
210
+ });
211
+
212
+ // put article to studio
213
+ tx.add(
214
+ finalizePublishArticle({
215
+ package: zingStudioPackageId,
216
+ arguments: {
217
+ config: tx.sharedObjectRef(zingStudioConfigSharedObjectRef),
218
+ studio: tx.object(
219
+ getDerivedStudioID(zingStudioConfigSharedObjectRef.objectId, owner),
220
+ ),
221
+ receipt: publishReceipt,
222
+ article: article,
223
+ },
224
+ }),
225
+ );
226
+ };
227
+ }
228
+
229
+ export function buildCertifyArticleBlobTransaction(
230
+ config: {
231
+ zingStudioPackageId: string;
232
+ zingStudioConfigSharedObjectRef: SharedObjectRef;
233
+ walrusSystemSharedObjectRef: SharedObjectRef;
234
+ },
235
+ walrusClient: WalrusClient,
236
+ owner: string,
237
+ articleId: string,
238
+ {
239
+ blobId,
240
+ blobObjectId,
241
+ confirmations,
242
+ certificate,
243
+ deletable,
244
+ }: CertifyBlobOptions,
245
+ ) {
246
+ const {
247
+ zingStudioPackageId,
248
+ zingStudioConfigSharedObjectRef,
249
+ walrusSystemSharedObjectRef,
250
+ } = config;
251
+ return async (tx: Transaction) => {
252
+ const systemState = await walrusClient.systemState();
253
+ const combinedSignature =
254
+ certificate ??
255
+ (await walrusClient.certificateFromConfirmations({
256
+ confirmations,
257
+ blobId,
258
+ deletable,
259
+ blobObjectId,
260
+ }));
261
+ tx.add(
262
+ certifyArticleBlob({
263
+ package: zingStudioPackageId,
264
+ arguments: {
265
+ config: tx.sharedObjectRef(zingStudioConfigSharedObjectRef),
266
+ studio: tx.object(
267
+ getDerivedStudioID(zingStudioConfigSharedObjectRef.objectId, owner),
268
+ ),
269
+ walrusSystem: tx.sharedObjectRef(walrusSystemSharedObjectRef),
270
+ articleId: tx.object(articleId),
271
+ blobIndex: tx.pure.u64(0), // TODO
272
+ signature: tx.pure.vector("u8", combinedSignature.signature),
273
+ signersBitmap: tx.pure.vector(
274
+ "u8",
275
+ signersToBitmap(
276
+ combinedSignature.signers,
277
+ systemState.committee.members.length,
278
+ ),
279
+ ),
280
+ message: tx.pure.vector("u8", combinedSignature.serializedMessage),
281
+ },
282
+ }),
283
+ );
284
+ };
285
+ }
286
+
287
+ export const zingWriteFlow = (
288
+ config: ZingWriteFlowConfig,
289
+ suiGrpcClient: SuiGrpcClient,
290
+ suiJsonRpcClient: SuiJsonRpcClient,
291
+ walrusClient: WalrusClient,
292
+ walrusSystem: WalrusSystemState,
293
+ walTreasury: string,
294
+ storageByEpoch: Record<number, number>,
295
+ fileKey: CryptoKey,
296
+ studioObjectId: string,
297
+ owner: string,
298
+ subscriptionLevel: null | number,
299
+ articleMetadata: {
300
+ identifier: string;
301
+ tags: {
302
+ "content-type": string;
303
+ };
304
+ size: number;
305
+ }[],
306
+ options: WriteFilesFlowOptions,
307
+ withUploadRelayClient: boolean,
308
+ blobRegisteredEventType: string,
309
+ publishArticleEventType: string,
310
+ ) => {
311
+ const { files } = options;
312
+
313
+ const encode = async () => {
314
+ const { quilt, index } = await walrusClient.encodeQuilt({
315
+ blobs: await Promise.all(
316
+ files.map(async (file, i) => {
317
+ const contents = await file.bytes();
318
+ const bytes = await encryptData(fileKey, contents, FIXED_FILE_IV);
319
+ return {
320
+ contents: bytes,
321
+ identifier: (await file.getIdentifier()) ?? `file-${i}`,
322
+ tags: (await file.getTags()) ?? {},
323
+ };
324
+ }),
325
+ ),
326
+ });
327
+
328
+ console.log({ walrus_system: await walrusClient.systemState() });
329
+ console.log({ quiltlength: quilt.length });
330
+
331
+ // store encryptedBytes
332
+ const metadata = withUploadRelayClient
333
+ ? await walrusClient.computeBlobMetadata({
334
+ bytes: quilt,
335
+ })
336
+ : await walrusClient.encodeBlob(quilt);
337
+
338
+ return {
339
+ metadata,
340
+ size: quilt.length,
341
+ data: withUploadRelayClient ? quilt : undefined,
342
+ index,
343
+ };
344
+ };
345
+
346
+ const register = (
347
+ { data, metadata, index, size }: Awaited<ReturnType<typeof encode>>,
348
+ { deletable, owner, attributes }: WriteFilesFlowRegisterOptions,
349
+ ) => {
350
+ const transaction = new Transaction();
351
+ // transaction.setSenderIfNotSet(owner);
352
+
353
+ if (withUploadRelayClient) {
354
+ const meta = metadata as Awaited<
355
+ ReturnType<typeof walrusClient.computeBlobMetadata>
356
+ >;
357
+ transaction.add(
358
+ walrusClient.sendUploadRelayTip({
359
+ size,
360
+ blobDigest: meta.blobDigest,
361
+ nonce: meta.nonce,
362
+ }),
363
+ );
364
+ }
365
+
366
+ // 1. rent storage space and publish article
367
+ transaction.add(
368
+ buildPublishArticleTransaction(
369
+ config,
370
+ walrusSystem,
371
+ walTreasury,
372
+ storageByEpoch,
373
+ size,
374
+ owner,
375
+ subscriptionLevel,
376
+ metadata.blobId,
377
+ metadata.rootHash,
378
+ articleMetadata,
379
+ { _walrusBlobType: "quilt", ...attributes },
380
+ null,
381
+ ),
382
+ );
383
+
384
+ console.log({ registerTransaction: transaction });
385
+
386
+ return {
387
+ registerTransaction: transaction,
388
+ index,
389
+ data,
390
+ metadata,
391
+ deletable,
392
+ };
393
+ };
394
+
395
+ const upload = async (
396
+ { index, data, metadata, deletable }: Awaited<ReturnType<typeof register>>,
397
+ { digest }: WriteFilesFlowUploadOptions,
398
+ ) => {
399
+ const { parsedPublishArticleEvent, blobObjectId } =
400
+ await getPublishedArticleAndBlobId(
401
+ suiGrpcClient,
402
+ suiJsonRpcClient,
403
+ walrusClient,
404
+ digest,
405
+ blobRegisteredEventType,
406
+ publishArticleEventType,
407
+ );
408
+ const articleId = parsedPublishArticleEvent.article_id;
409
+
410
+ if (withUploadRelayClient) {
411
+ const meta = metadata as Awaited<
412
+ ReturnType<typeof walrusClient.computeBlobMetadata>
413
+ >;
414
+ return {
415
+ index,
416
+ parsedPublishArticleEvent,
417
+ articleId,
418
+ blobObjectId,
419
+ metadata,
420
+ deletable,
421
+ certificate: (
422
+ await walrusClient.writeBlobToUploadRelay({
423
+ blobId: metadata.blobId,
424
+ blob: data!,
425
+ nonce: meta.nonce,
426
+ txDigest: digest,
427
+ blobObjectId,
428
+ deletable,
429
+ encodingType: meta.metadata.encodingType as EncodingType,
430
+ })
431
+ ).certificate,
432
+ };
433
+ }
434
+
435
+ const meta = metadata as Awaited<
436
+ ReturnType<typeof walrusClient.encodeBlob>
437
+ >;
438
+
439
+ return {
440
+ index,
441
+ blobObjectId,
442
+ articleId,
443
+ parsedPublishArticleEvent,
444
+ metadata,
445
+ deletable,
446
+ confirmations: await walrusClient.writeEncodedBlobToNodes({
447
+ blobId: metadata.blobId,
448
+ objectId: blobObjectId,
449
+ metadata: meta.metadata,
450
+ sliversByNode: meta.sliversByNode,
451
+ deletable,
452
+ }),
453
+ };
454
+ };
455
+
456
+ const certify = ({
457
+ index,
458
+ metadata,
459
+ confirmations,
460
+ certificate,
461
+ articleId,
462
+ blobObjectId,
463
+ deletable,
464
+ }: Awaited<ReturnType<typeof upload>>) => {
465
+ console.log({ confirmations });
466
+ const tx = new Transaction();
467
+
468
+ if (confirmations) {
469
+ tx.add(
470
+ buildCertifyArticleBlobTransaction(
471
+ {
472
+ zingStudioPackageId: config.zingStudioPackageId,
473
+ zingStudioConfigSharedObjectRef:
474
+ config.zingStudioConfigSharedObjectRef,
475
+ walrusSystemSharedObjectRef: config.walrusSystemSharedObjectRef,
476
+ },
477
+ walrusClient,
478
+ owner,
479
+ articleId,
480
+ {
481
+ certificate,
482
+ blobId: metadata.blobId,
483
+ blobObjectId,
484
+ confirmations,
485
+ deletable,
486
+ },
487
+ ),
488
+ );
489
+ } else {
490
+ tx.add(
491
+ buildCertifyArticleBlobTransaction(
492
+ {
493
+ zingStudioPackageId: config.zingStudioPackageId,
494
+ zingStudioConfigSharedObjectRef:
495
+ config.zingStudioConfigSharedObjectRef,
496
+ walrusSystemSharedObjectRef: config.walrusSystemSharedObjectRef,
497
+ },
498
+ walrusClient,
499
+ owner,
500
+ articleId,
501
+ {
502
+ certificate,
503
+ blobId: metadata.blobId,
504
+ blobObjectId,
505
+ deletable,
506
+ },
507
+ ),
508
+ );
509
+ }
510
+ return {
511
+ index,
512
+ blobObjectId,
513
+ metadata,
514
+ transaction: tx,
515
+ };
516
+ };
517
+
518
+ async function listFiles({
519
+ index,
520
+ blobObjectId,
521
+ metadata,
522
+ }: Awaited<ReturnType<typeof certify>>) {
523
+ return index.patches.map((patch) => ({
524
+ id: encodeQuiltPatchId({
525
+ quiltId: metadata.blobId,
526
+ patchId: {
527
+ version: 1,
528
+ startIndex: patch.startIndex,
529
+ endIndex: patch.endIndex,
530
+ },
531
+ }),
532
+ blobId: metadata.blobId,
533
+ blobObjectId,
534
+ }));
535
+ }
536
+
537
+ const stepResults: {
538
+ encode?: Awaited<ReturnType<typeof encode>>;
539
+ register?: Awaited<ReturnType<typeof register>>;
540
+ upload?: Awaited<ReturnType<typeof upload>>;
541
+ certify?: Awaited<ReturnType<typeof certify>>;
542
+ listFiles?: never;
543
+ } = {};
544
+
545
+ function getResults<T extends keyof typeof stepResults>(
546
+ step: T,
547
+ current: keyof typeof stepResults,
548
+ ): NonNullable<(typeof stepResults)[T]> {
549
+ if (!stepResults[step]) {
550
+ throw new Error(`${step} must be executed before calling ${current}`);
551
+ }
552
+ return stepResults[step];
553
+ }
554
+
555
+ return {
556
+ encode: async () => {
557
+ if (!stepResults.encode) {
558
+ stepResults.encode = await encode();
559
+ }
560
+ },
561
+ register: (options: WriteFilesFlowRegisterOptions) => {
562
+ stepResults.register = register(
563
+ getResults("encode", "register"),
564
+ options,
565
+ );
566
+ return stepResults.register.registerTransaction;
567
+ },
568
+ upload: async (options: WriteFilesFlowUploadOptions) => {
569
+ stepResults.upload = await upload(
570
+ getResults("register", "upload"),
571
+ options,
572
+ );
573
+ return stepResults.upload.parsedPublishArticleEvent;
574
+ },
575
+ certify: () => {
576
+ stepResults.certify = certify(getResults("upload", "certify"));
577
+ return stepResults.certify.transaction;
578
+ },
579
+ listFiles: async () => listFiles(getResults("certify", "listFiles")),
580
+ };
581
+ };
582
+
583
+ export async function uploadFileToWalrus(
584
+ zingWriteFlowConfig: ZingWriteFlowConfig,
585
+ suiGrpcClient: SuiGrpcClient,
586
+ suiJsonRpcClient: SuiJsonRpcClient,
587
+ walrusClient: WalrusClient,
588
+ walrusSystem: WalrusSystemState,
589
+ walTreasury: string,
590
+ storageByEpoch: Record<number, number>,
591
+ fileKey: CryptoKey,
592
+ walrusFiles: WalrusFile[],
593
+ subscriptionLevel: null | number,
594
+ metadata: {
595
+ identifier: string;
596
+ tags: {
597
+ "content-type": string;
598
+ };
599
+ size: number;
600
+ }[],
601
+ signer: ZKLoginSignerState,
602
+ gasPoolBearerToken: string,
603
+ onProgress?: (stage: WalrusUploadFlowStage) => void,
604
+ ): Promise<typeof PublishArticleEvent.$inferType> {
605
+ const report = (stage: WalrusUploadFlowStage) => {
606
+ if (onProgress) onProgress(stage);
607
+ };
608
+ const owner = signer.suiAddress;
609
+
610
+ if (!owner) throw new Error("no signer");
611
+ // Step 1: Create and encode the flow (can be done immediately when file is selected)
612
+ const flow = zingWriteFlow(
613
+ zingWriteFlowConfig,
614
+ suiGrpcClient,
615
+ suiJsonRpcClient,
616
+ walrusClient,
617
+ walrusSystem,
618
+ walTreasury,
619
+ storageByEpoch,
620
+ fileKey,
621
+ getDerivedStudioID(
622
+ zingWriteFlowConfig.zingStudioConfigSharedObjectRef.objectId,
623
+ owner,
624
+ ),
625
+ owner,
626
+ subscriptionLevel,
627
+ metadata,
628
+ {
629
+ files: walrusFiles,
630
+ },
631
+ true,
632
+ zingWriteFlowConfig.blobRegisteredEventType,
633
+ zingWriteFlowConfig.publishArticleEventType,
634
+ );
635
+
636
+ report("encode");
637
+ await flow.encode();
638
+
639
+ // Step 2: Register the blob (triggered by user clicking a register button after the encode step)
640
+ async function handleRegister() {
641
+ if (!owner) throw new Error("suiaddress not found in zkloginsignerstate");
642
+
643
+ report("register_blob");
644
+ const registerTx = flow.register({
645
+ epochs: 1,
646
+ owner,
647
+ deletable: true,
648
+ });
649
+
650
+ const registerTxEffects = await signAndExecuteGasPoolTransaction(
651
+ signer,
652
+ suiJsonRpcClient,
653
+ registerTx,
654
+ );
655
+
656
+ if (!registerTxEffects?.transactionDigest)
657
+ throw new Error("fail to execute register Blob transaction");
658
+
659
+ const finalizedTransaction = await suiJsonRpcClient.waitForTransaction({
660
+ digest: registerTxEffects.transactionDigest,
661
+ });
662
+ // Step 3: Upload the data to storage nodes
663
+ // This can be done immediately after the register step, or as a separate step the user initiates
664
+ report("upload");
665
+ return await flow.upload({
666
+ digest: finalizedTransaction.digest,
667
+ });
668
+ }
669
+
670
+ const parsedPublishArticleEvent = await handleRegister();
671
+
672
+ // Step 4: Certify the blob (triggered by user clicking a certify button after the blob is uploaded)
673
+ async function handleCertify() {
674
+ report("certify_blob");
675
+ const certifyTx = flow.certify();
676
+
677
+ const certifyTxEffects = await signAndExecuteGasPoolTransaction(
678
+ signer,
679
+ suiJsonRpcClient,
680
+ certifyTx,
681
+ );
682
+
683
+ if (!certifyTxEffects?.transactionDigest)
684
+ throw new Error("fail to execute certify Blob transaction");
685
+
686
+ await suiJsonRpcClient.waitForTransaction({
687
+ digest: certifyTxEffects.transactionDigest,
688
+ });
689
+
690
+ report("publish");
691
+ }
692
+
693
+ await handleCertify();
694
+
695
+ return parsedPublishArticleEvent;
696
+ }
697
+
698
+ export async function downloadImagesFromMarkdown<
699
+ T extends "blob" | "uint8array",
700
+ >(
701
+ markdown: string,
702
+ as: T = "blob" as T,
703
+ ): Promise<{ url: string; data: T extends "blob" ? Blob : Uint8Array }[]> {
704
+ // Match ![alt](url) or [![alt](url)](...)
705
+ const imageRegex = /!\[[^\]]*\]\(([^)]+)\)/g;
706
+ const urls = new Set<string>();
707
+ let match;
708
+
709
+ while ((match = imageRegex.exec(markdown)) !== null) {
710
+ let url = match[1].trim();
711
+
712
+ // Remove optional Markdown title e.g. (url "title")
713
+ const spaceIndex = url.indexOf(" ");
714
+ if (spaceIndex !== -1 && url[0] !== '"') {
715
+ url = url.slice(0, spaceIndex);
716
+ }
717
+
718
+ // Include both HTTP URLs and data URLs (base64 encoded images)
719
+ if (
720
+ url.startsWith("http") ||
721
+ url.startsWith("https") ||
722
+ url.startsWith("data:")
723
+ ) {
724
+ urls.add(url);
725
+ }
726
+ }
727
+
728
+ const images: { url: string; data: T extends "blob" ? Blob : Uint8Array }[] =
729
+ [];
730
+
731
+ for (const url of urls) {
732
+ try {
733
+ if (url.startsWith("data:")) {
734
+ // Handle base64 encoded data URLs
735
+ const response = await fetch(url);
736
+ if (!response.ok) throw new Error(`Failed to process data URL`);
737
+
738
+ if (as === "blob") {
739
+ const blob = await response.blob();
740
+ images.push({
741
+ url,
742
+ data: blob as T extends "blob" ? Blob : Uint8Array,
743
+ });
744
+ } else {
745
+ const buffer = await response.arrayBuffer();
746
+ images.push({
747
+ url,
748
+ data: new Uint8Array(buffer) as T extends "blob"
749
+ ? Blob
750
+ : Uint8Array,
751
+ });
752
+ }
753
+ } else {
754
+ // Handle regular HTTP/HTTPS URLs
755
+ const response = await fetch(url);
756
+ if (!response.ok) throw new Error(`Failed to fetch ${url}`);
757
+
758
+ if (as === "blob") {
759
+ const blob = await response.blob();
760
+ images.push({
761
+ url,
762
+ data: blob as T extends "blob" ? Blob : Uint8Array,
763
+ });
764
+ } else {
765
+ const buffer = await response.arrayBuffer();
766
+ images.push({
767
+ url,
768
+ data: new Uint8Array(buffer) as T extends "blob"
769
+ ? Blob
770
+ : Uint8Array,
771
+ });
772
+ }
773
+ }
774
+ } catch (err) {
775
+ console.error(
776
+ `Error processing image from ${url.startsWith("data:") ? "data URL" : url}:`,
777
+ err,
778
+ );
779
+ }
780
+ }
781
+
782
+ return images;
783
+ }
784
+ /**
785
+ * Calculate the cost of storing a blob for a given a size and number of epochs.
786
+ */
787
+ export function storageCost(
788
+ systemState: WalrusSystemState,
789
+ size: number,
790
+ epochs: number,
791
+ ) {
792
+ const encodedSize = encodedBlobLength(size, systemState.committee.n_shards);
793
+ const storageUnits = storageUnitsFromSize(encodedSize);
794
+ const storageCost =
795
+ BigInt(storageUnits) *
796
+ BigInt(systemState.storage_price_per_unit_size) *
797
+ BigInt(epochs);
798
+ BigInt(epochs);
799
+
800
+ const writeCost =
801
+ BigInt(storageUnits) * BigInt(systemState.write_price_per_unit_size);
802
+
803
+ return {
804
+ encodedSize,
805
+ storageCost,
806
+ writeCost,
807
+ totalCost: storageCost + writeCost,
808
+ };
809
+ }
810
+
811
+ export function storageUnitsFromSize(size: number): number {
812
+ return Math.ceil(size / BYTES_PER_UNIT_SIZE);
813
+ }
814
+
815
+ /**
816
+ * Replace all base64 image URLs in markdown with asset identifiers (e.g. assets-0.png).
817
+ *
818
+ * @param markdown - the original markdown string containing data URLs
819
+ * @param assets - the array returned from downloadImagesFromMarkdown()
820
+ * @returns a new markdown string with data URLs replaced by identifiers
821
+ */
822
+ export function replaceBase64ImagesWithIdentifiers(
823
+ markdown: string,
824
+ assets: { url: string; data: Blob | Uint8Array }[],
825
+ ): string {
826
+ let replacedMarkdown = markdown;
827
+
828
+ // Regex to match ![alt](url) markdown image syntax
829
+ const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
830
+
831
+ replacedMarkdown = replacedMarkdown.replace(imageRegex, (match, alt, url) => {
832
+ url = url.trim();
833
+
834
+ // Find the matching asset by comparing the original URL
835
+ const assetIndex = assets.findIndex((asset) => asset.url === url);
836
+
837
+ if (assetIndex === -1) {
838
+ // No matching asset found, keep original
839
+ return match;
840
+ }
841
+
842
+ const asset = assets[assetIndex];
843
+
844
+ // Try to infer extension from mime type
845
+ let extension = ".bin";
846
+ if (asset.data instanceof Blob) {
847
+ const type = asset.data.type;
848
+ if (type.includes("png")) extension = ".png";
849
+ else if (type.includes("jpeg") || type.includes("jpg"))
850
+ extension = ".jpg";
851
+ else if (type.includes("gif")) extension = ".gif";
852
+ else if (type.includes("webp")) extension = ".webp";
853
+ else if (type.includes("svg")) extension = ".svg";
854
+ }
855
+
856
+ // Replace with new identifier reference
857
+ const newIdentifier = `assets-${assetIndex}${extension}`;
858
+ return `![${alt}](${newIdentifier})`;
859
+ });
860
+
861
+ return replacedMarkdown;
862
+ }