@solana-mobile/dapp-store-cli 0.16.0 → 0.16.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 (113) hide show
  1. package/bin/dapp-store.js +3 -1
  2. package/lib/CliSetup.js +304 -505
  3. package/lib/CliUtils.js +6 -376
  4. package/lib/__tests__/CliSetupTest.js +484 -74
  5. package/lib/cli/__tests__/parseErrors.test.js +25 -0
  6. package/lib/cli/__tests__/signer.test.js +436 -0
  7. package/lib/cli/constants.js +23 -0
  8. package/lib/cli/messages.js +21 -0
  9. package/lib/cli/parseErrors.js +41 -0
  10. package/lib/{commands/publish/PublishCliSupport.js → cli/selfUpdate.js} +72 -38
  11. package/lib/{commands/publish/PublishCliRemove.js → cli/signer.js} +35 -56
  12. package/lib/index.js +96 -5
  13. package/lib/package.json +5 -24
  14. package/lib/portal/__tests__/releaseMetadata.test.js +647 -0
  15. package/lib/portal/__tests__/translators.test.js +76 -0
  16. package/lib/portal/__tests__/workflowClient.test.js +457 -0
  17. package/lib/portal/attestationClient.js +143 -0
  18. package/lib/portal/files.js +64 -0
  19. package/lib/portal/http.js +364 -0
  20. package/lib/portal/records.js +64 -0
  21. package/lib/portal/releaseMetadata.js +748 -0
  22. package/lib/portal/translators.js +460 -0
  23. package/lib/portal/types.js +1 -0
  24. package/lib/portal/workflowClient.js +704 -0
  25. package/lib/publication/PublicationProgressReporter.js +1051 -0
  26. package/lib/publication/__tests__/PublicationProgressReporter.test.js +174 -0
  27. package/lib/{commands/ValidateCommand.js → publication/__tests__/fundingPreflight.test.js} +90 -66
  28. package/lib/publication/__tests__/publicationSummary.test.js +26 -0
  29. package/lib/publication/cliValidation.js +482 -0
  30. package/lib/publication/fundingPreflight.js +246 -0
  31. package/lib/publication/publicationSummary.js +99 -0
  32. package/lib/{commands/utils.js → publication/runPublicationWorkflow.js} +16 -46
  33. package/package.json +5 -24
  34. package/src/CliSetup.ts +370 -505
  35. package/src/CliUtils.ts +9 -233
  36. package/src/__tests__/CliSetupTest.ts +272 -120
  37. package/src/cli/__tests__/parseErrors.test.ts +34 -0
  38. package/src/cli/__tests__/signer.test.ts +359 -0
  39. package/src/cli/constants.ts +3 -0
  40. package/src/cli/messages.ts +27 -0
  41. package/src/cli/parseErrors.ts +62 -0
  42. package/src/cli/selfUpdate.ts +59 -0
  43. package/src/cli/signer.ts +38 -0
  44. package/src/index.ts +31 -4
  45. package/src/portal/__tests__/releaseMetadata.test.ts +508 -0
  46. package/src/portal/__tests__/translators.test.ts +82 -0
  47. package/src/portal/__tests__/workflowClient.test.ts +278 -0
  48. package/src/portal/attestationClient.ts +19 -0
  49. package/src/portal/files.ts +73 -0
  50. package/src/portal/http.ts +170 -0
  51. package/src/portal/records.ts +38 -0
  52. package/src/portal/releaseMetadata.ts +489 -0
  53. package/src/portal/translators.ts +750 -0
  54. package/src/portal/types.ts +27 -0
  55. package/src/portal/workflowClient.ts +575 -0
  56. package/src/publication/PublicationProgressReporter.ts +1026 -0
  57. package/src/publication/__tests__/PublicationProgressReporter.test.ts +210 -0
  58. package/src/publication/__tests__/fundingPreflight.test.ts +78 -0
  59. package/src/publication/__tests__/publicationSummary.test.ts +30 -0
  60. package/src/publication/cliValidation.ts +264 -0
  61. package/src/publication/fundingPreflight.ts +123 -0
  62. package/src/publication/publicationSummary.ts +26 -0
  63. package/src/publication/runPublicationWorkflow.ts +46 -0
  64. package/lib/commands/create/CreateCliApp.js +0 -223
  65. package/lib/commands/create/CreateCliRelease.js +0 -290
  66. package/lib/commands/create/index.js +0 -40
  67. package/lib/commands/index.js +0 -3
  68. package/lib/commands/publish/PublishCliSubmit.js +0 -208
  69. package/lib/commands/publish/PublishCliUpdate.js +0 -211
  70. package/lib/commands/publish/index.js +0 -22
  71. package/lib/commands/scaffolding/ScaffoldInit.js +0 -15
  72. package/lib/commands/scaffolding/index.js +0 -1
  73. package/lib/config/EnvVariables.js +0 -59
  74. package/lib/config/PublishDetails.js +0 -915
  75. package/lib/config/S3StorageManager.js +0 -93
  76. package/lib/config/index.js +0 -2
  77. package/lib/generated/config_obj.json +0 -1
  78. package/lib/generated/config_schema.json +0 -1
  79. package/lib/prebuild_schema/publishing_source.yaml +0 -64
  80. package/lib/prebuild_schema/schemagen.js +0 -25
  81. package/lib/upload/CachedStorageDriver.js +0 -458
  82. package/lib/upload/TurboStorageDriver.js +0 -718
  83. package/lib/upload/__tests__/CachedStorageDriver.test.js +0 -437
  84. package/lib/upload/__tests__/TurboStorageDriver.test.js +0 -17
  85. package/lib/upload/__tests__/contentGateway.test.js +0 -17
  86. package/lib/upload/contentGateway.js +0 -23
  87. package/lib/upload/index.js +0 -2
  88. package/src/commands/ValidateCommand.ts +0 -82
  89. package/src/commands/create/CreateCliApp.ts +0 -93
  90. package/src/commands/create/CreateCliRelease.ts +0 -149
  91. package/src/commands/create/index.ts +0 -47
  92. package/src/commands/index.ts +0 -3
  93. package/src/commands/publish/PublishCliRemove.ts +0 -66
  94. package/src/commands/publish/PublishCliSubmit.ts +0 -93
  95. package/src/commands/publish/PublishCliSupport.ts +0 -66
  96. package/src/commands/publish/PublishCliUpdate.ts +0 -101
  97. package/src/commands/publish/index.ts +0 -29
  98. package/src/commands/scaffolding/ScaffoldInit.ts +0 -20
  99. package/src/commands/scaffolding/index.ts +0 -1
  100. package/src/commands/utils.ts +0 -33
  101. package/src/config/EnvVariables.ts +0 -39
  102. package/src/config/PublishDetails.ts +0 -456
  103. package/src/config/S3StorageManager.ts +0 -47
  104. package/src/config/index.ts +0 -2
  105. package/src/prebuild_schema/publishing_source.yaml +0 -64
  106. package/src/prebuild_schema/schemagen.js +0 -31
  107. package/src/upload/CachedStorageDriver.ts +0 -179
  108. package/src/upload/TurboStorageDriver.ts +0 -283
  109. package/src/upload/__tests__/CachedStorageDriver.test.ts +0 -246
  110. package/src/upload/__tests__/TurboStorageDriver.test.ts +0 -15
  111. package/src/upload/__tests__/contentGateway.test.ts +0 -31
  112. package/src/upload/contentGateway.ts +0 -37
  113. package/src/upload/index.ts +0 -2
@@ -0,0 +1,27 @@
1
+ export type PortalProcedureResult<T> =
2
+ | {
3
+ _tag: 'Left';
4
+ left: {
5
+ name?: string;
6
+ message: string;
7
+ };
8
+ }
9
+ | {
10
+ _tag: 'Right';
11
+ right: T;
12
+ };
13
+
14
+ export type PortalClientConfig = {
15
+ apiBaseUrl: string;
16
+ apiKey: string;
17
+ dappId?: string;
18
+ };
19
+
20
+ export type PortalUploadTarget = {
21
+ uploadUrl: string;
22
+ key: string;
23
+ providerId: string;
24
+ publicUrl: string;
25
+ };
26
+
27
+ export type PortalSourceKind = 'portal' | 'external';
@@ -0,0 +1,575 @@
1
+ import fs from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import path from "node:path";
4
+
5
+ import type {
6
+ PublicationBundle,
7
+ PublicationCleanupReleaseInput,
8
+ PublicationCleanupReleaseResult,
9
+ PublicationCreateIngestionSessionInput,
10
+ PublicationCreateUploadTargetInput,
11
+ PublicationCreateUploadTargetResult,
12
+ PublicationGetBundleInput,
13
+ PublicationGetIngestionSessionInput,
14
+ PublicationGetSessionInput,
15
+ PublicationIngestionSession,
16
+ PublicationPreparedReleaseTransaction,
17
+ PublicationPreparedVerifyCollectionTransaction,
18
+ PublicationPrepareReleaseNftTransactionInput,
19
+ PublicationPrepareVerifyCollectionTransactionInput,
20
+ PublicationSaveReleaseNftDataInput,
21
+ PublicationSaveReleaseNftDataResult,
22
+ PublicationSubmitSignedTransactionResult,
23
+ PublicationSubmitToStoreInput,
24
+ PublicationSubmitToStoreResult,
25
+ PublicationWorkflowClient,
26
+ } from "@solana-mobile/dapp-store-publishing-tools";
27
+
28
+ import {
29
+ ensureApkFileName,
30
+ fromBase64,
31
+ inferFileNameFromUrl,
32
+ toBase64,
33
+ } from "./files.js";
34
+ import {
35
+ callCreateIngestionSessionWithRetry,
36
+ callPortalProcedure,
37
+ uploadBytes,
38
+ } from "./http.js";
39
+ import { asRecord, isRecord } from "./records.js";
40
+ import {
41
+ inferPublicationSourceKind,
42
+ mapBackendBundleToPublicationBundle,
43
+ translateBackendIngestionSession,
44
+ translateBackendPublicationSession,
45
+ } from "./translators.js";
46
+ import {
47
+ buildReleaseMetadataDocument,
48
+ type ReleaseMetadataPortalClient,
49
+ } from "./releaseMetadata.js";
50
+ import type { PortalClientConfig, PortalUploadTarget } from "./types.js";
51
+
52
+ type PortalBackendResult = Record<string, unknown>;
53
+
54
+ type WorkflowClientState = {
55
+ currentPublicationSessionId?: string;
56
+ currentReleaseId?: string;
57
+ metadataUriByReleaseId: Map<string, string>;
58
+ publicationSessionIdByReleaseId: Map<string, string>;
59
+ };
60
+
61
+ function createWorkflowClientState(): WorkflowClientState {
62
+ return {
63
+ metadataUriByReleaseId: new Map<string, string>(),
64
+ publicationSessionIdByReleaseId: new Map<string, string>(),
65
+ };
66
+ }
67
+
68
+ function rememberLinkedPublicationSession(
69
+ state: WorkflowClientState,
70
+ releaseId?: string | null,
71
+ publicationSessionId?: string | null
72
+ ) {
73
+ if (releaseId && publicationSessionId) {
74
+ state.publicationSessionIdByReleaseId.set(releaseId, publicationSessionId);
75
+ }
76
+ }
77
+
78
+ function trackBackendIdentifiers(
79
+ state: WorkflowClientState,
80
+ backendResult: PortalBackendResult
81
+ ) {
82
+ if (typeof backendResult.releaseId === "string") {
83
+ state.currentReleaseId = backendResult.releaseId;
84
+ }
85
+ if (typeof backendResult.publicationSessionId === "string") {
86
+ state.currentPublicationSessionId = backendResult.publicationSessionId;
87
+ }
88
+
89
+ rememberLinkedPublicationSession(
90
+ state,
91
+ state.currentReleaseId,
92
+ state.currentPublicationSessionId
93
+ );
94
+ }
95
+
96
+ function trackTranslatedIngestionSession(
97
+ state: WorkflowClientState,
98
+ session: PublicationIngestionSession
99
+ ) {
100
+ rememberLinkedPublicationSession(
101
+ state,
102
+ session.releaseId,
103
+ session.publicationSessionId
104
+ );
105
+
106
+ if (session.publicationSessionId) {
107
+ state.currentPublicationSessionId = session.publicationSessionId;
108
+ } else if (session.publicationSession) {
109
+ state.currentPublicationSessionId = session.publicationSession.id;
110
+ }
111
+
112
+ if (session.releaseId) {
113
+ state.currentReleaseId = session.releaseId;
114
+ }
115
+ }
116
+
117
+ function readLocalSourceFileOrThrow(filePath: string): Buffer {
118
+ try {
119
+ return fs.readFileSync(filePath);
120
+ } catch (error) {
121
+ const code =
122
+ error && typeof error === "object" && "code" in error
123
+ ? String((error as { code?: unknown }).code || "")
124
+ : "";
125
+
126
+ if (code === "EPERM" || code === "EACCES") {
127
+ throw new Error(
128
+ `Cannot read local APK at ${filePath}. macOS denied access to this location (${code}). Move the APK out of Downloads into your workspace or another accessible folder, or grant this app Full Disk Access, then retry.`
129
+ );
130
+ }
131
+
132
+ throw error;
133
+ }
134
+ }
135
+
136
+ export function createPortalWorkflowClient(
137
+ config: PortalClientConfig
138
+ ): PublicationWorkflowClient {
139
+ const state = createWorkflowClientState();
140
+
141
+ const createUploadTarget = async (
142
+ input: PublicationCreateUploadTargetInput
143
+ ): Promise<PortalUploadTarget> => {
144
+ return await callPortalProcedure<PortalUploadTarget>(
145
+ config,
146
+ "publication.createUploadTarget",
147
+ input,
148
+ "mutation"
149
+ );
150
+ };
151
+
152
+ const translateIngestionBackendResult = (
153
+ backendResult: PortalBackendResult
154
+ ) => {
155
+ const translated = translateBackendIngestionSession(
156
+ backendResult,
157
+ asRecord(backendResult.bundle),
158
+ asRecord(backendResult.publicationSession)
159
+ );
160
+
161
+ trackTranslatedIngestionSession(state, translated);
162
+ return translated;
163
+ };
164
+
165
+ const uploadReleaseMetadata = async (bundle: PublicationBundle) => {
166
+ const releaseId = bundle.releaseId;
167
+ const cached = state.metadataUriByReleaseId.get(releaseId);
168
+ if (cached) {
169
+ return cached;
170
+ }
171
+
172
+ if (
173
+ typeof bundle.release.releaseMetadataUri === "string" &&
174
+ bundle.release.releaseMetadataUri.length > 0
175
+ ) {
176
+ state.metadataUriByReleaseId.set(
177
+ releaseId,
178
+ bundle.release.releaseMetadataUri
179
+ );
180
+ return bundle.release.releaseMetadataUri;
181
+ }
182
+
183
+ const releaseMetadataClient: ReleaseMetadataPortalClient = {
184
+ createUploadTarget,
185
+ async fetchRemoteFile(input) {
186
+ return await callPortalProcedure<{
187
+ data: string;
188
+ fileName: string;
189
+ mimeType: string;
190
+ }>(config, "fetchRemoteFile", input, "query");
191
+ },
192
+ };
193
+
194
+ const metadataDocument = await buildReleaseMetadataDocument(
195
+ releaseMetadataClient,
196
+ bundle,
197
+ inferPublicationSourceKind(
198
+ bundle.metadata.installFile.origin === "external"
199
+ ? "externalUrl"
200
+ : "portalUpload"
201
+ )
202
+ );
203
+ delete (metadataDocument as Record<string, unknown>).__origin;
204
+
205
+ const metadataBytes = Buffer.from(JSON.stringify(metadataDocument), "utf8");
206
+ const fileHash = createHash("sha256").update(metadataBytes).digest("hex");
207
+ const uploadTarget = await createUploadTarget({
208
+ fileHash,
209
+ fileExtension: "json",
210
+ contentType: "application/json",
211
+ });
212
+
213
+ await uploadBytes(
214
+ uploadTarget.uploadUrl,
215
+ metadataBytes,
216
+ "application/json"
217
+ );
218
+
219
+ state.metadataUriByReleaseId.set(releaseId, uploadTarget.publicUrl);
220
+ return uploadTarget.publicUrl;
221
+ };
222
+
223
+ return {
224
+ async createUploadTarget(
225
+ input: PublicationCreateUploadTargetInput
226
+ ): Promise<PublicationCreateUploadTargetResult> {
227
+ return await createUploadTarget(input);
228
+ },
229
+
230
+ async createIngestionSession(
231
+ input: PublicationCreateIngestionSessionInput
232
+ ): Promise<PublicationIngestionSession> {
233
+ const dappId = input.dappId || config.dappId;
234
+ const idempotencyKey = input.idempotencyKey || `${Date.now()}`;
235
+
236
+ if (input.source.kind === "apk-file") {
237
+ const source = (() => {
238
+ const filePath = path.resolve(input.source.filePath);
239
+ const fileName = ensureApkFileName(
240
+ input.source.fileName || path.basename(filePath)
241
+ );
242
+ const fileBytes = readLocalSourceFileOrThrow(filePath);
243
+ const fileHash =
244
+ input.source.sha256 ||
245
+ createHash("sha256").update(fileBytes).digest("hex");
246
+
247
+ return {
248
+ filePath,
249
+ fileName,
250
+ fileBytes,
251
+ fileHash,
252
+ fileExtension: "apk",
253
+ contentType:
254
+ input.source.mimeType ||
255
+ "application/vnd.android.package-archive",
256
+ releaseFileSize: input.source.size ?? fileBytes.byteLength,
257
+ };
258
+ })();
259
+
260
+ const uploadTarget = await createUploadTarget({
261
+ fileHash: source.fileHash,
262
+ fileExtension: source.fileExtension,
263
+ contentType: source.contentType,
264
+ });
265
+
266
+ await uploadBytes(
267
+ uploadTarget.uploadUrl,
268
+ fromBase64(toBase64(source.fileBytes)),
269
+ source.contentType
270
+ );
271
+
272
+ const backendResult = await callCreateIngestionSessionWithRetry(
273
+ config,
274
+ {
275
+ source: {
276
+ kind: "portalUpload",
277
+ releaseFileUrl: uploadTarget.publicUrl,
278
+ releaseFileName: source.fileName,
279
+ releaseFileSize: source.releaseFileSize,
280
+ },
281
+ whatsNew: input.whatsNew,
282
+ idempotencyKey,
283
+ ...(dappId ? { dappId } : {}),
284
+ }
285
+ );
286
+
287
+ trackBackendIdentifiers(state, backendResult);
288
+ return translateIngestionBackendResult(backendResult);
289
+ }
290
+
291
+ const backendSource =
292
+ input.source.kind === "portalUpload"
293
+ ? {
294
+ kind: "portalUpload",
295
+ releaseFileUrl: input.source.releaseFileUrl,
296
+ releaseFileName: input.source.releaseFileName,
297
+ releaseFileSize: input.source.releaseFileSize,
298
+ }
299
+ : input.source.kind === "existingRelease"
300
+ ? {
301
+ kind: "existingRelease",
302
+ sourceReleaseId: input.source.sourceReleaseId,
303
+ }
304
+ : {
305
+ kind: "externalUrl",
306
+ apkUrl:
307
+ input.source.kind === "externalUrl"
308
+ ? input.source.apkUrl
309
+ : input.source.url,
310
+ releaseFileName:
311
+ input.source.kind === "externalUrl"
312
+ ? input.source.releaseFileName ||
313
+ inferFileNameFromUrl(input.source.apkUrl)
314
+ : input.source.fileName ||
315
+ inferFileNameFromUrl(input.source.url),
316
+ };
317
+
318
+ const backendResult = await callCreateIngestionSessionWithRetry(config, {
319
+ source: backendSource,
320
+ whatsNew: input.whatsNew,
321
+ idempotencyKey,
322
+ ...(dappId ? { dappId } : {}),
323
+ });
324
+
325
+ trackBackendIdentifiers(state, backendResult);
326
+ return translateIngestionBackendResult(backendResult);
327
+ },
328
+
329
+ async getIngestionSession(
330
+ input: PublicationGetIngestionSessionInput
331
+ ): Promise<PublicationIngestionSession> {
332
+ const resolvedSessionId =
333
+ input.sessionId ||
334
+ ("ingestionSessionId" in input &&
335
+ typeof input.ingestionSessionId === "string" &&
336
+ input.ingestionSessionId.length > 0
337
+ ? input.ingestionSessionId
338
+ : undefined);
339
+
340
+ if (!resolvedSessionId) {
341
+ throw new Error(
342
+ "publication.getIngestionSession requires a session id"
343
+ );
344
+ }
345
+
346
+ const backendResult = await callPortalProcedure<PortalBackendResult>(
347
+ config,
348
+ "publication.getIngestionSession",
349
+ {
350
+ sessionId: resolvedSessionId,
351
+ },
352
+ "query"
353
+ );
354
+
355
+ trackBackendIdentifiers(state, backendResult);
356
+ return translateIngestionBackendResult(backendResult);
357
+ },
358
+
359
+ async getPublicationBundle(
360
+ input: PublicationGetBundleInput
361
+ ): Promise<PublicationBundle> {
362
+ const backendBundle = await callPortalProcedure<PortalBackendResult>(
363
+ config,
364
+ "publication.getPublicationBundle",
365
+ { releaseId: input.releaseId },
366
+ "query"
367
+ );
368
+
369
+ const linkedPublicationSessionId =
370
+ state.publicationSessionIdByReleaseId.get(input.releaseId) ||
371
+ state.currentPublicationSessionId;
372
+ const linkedPublicationSession = linkedPublicationSessionId
373
+ ? translateBackendPublicationSession(
374
+ await callPortalProcedure<PortalBackendResult>(
375
+ config,
376
+ "publication.getPublicationSession",
377
+ {
378
+ publicationSessionId: linkedPublicationSessionId,
379
+ releaseId: input.releaseId,
380
+ },
381
+ "query"
382
+ )
383
+ )
384
+ : undefined;
385
+
386
+ const releaseMetadataUri =
387
+ state.metadataUriByReleaseId.get(input.releaseId) ||
388
+ (isRecord(backendBundle.release) &&
389
+ typeof backendBundle.release.nftMetadataUri === "string" &&
390
+ backendBundle.release.nftMetadataUri.length > 0
391
+ ? backendBundle.release.nftMetadataUri
392
+ : await uploadReleaseMetadata(
393
+ mapBackendBundleToPublicationBundle(backendBundle, "", "portal")
394
+ ));
395
+
396
+ state.metadataUriByReleaseId.set(input.releaseId, releaseMetadataUri);
397
+
398
+ const translated = mapBackendBundleToPublicationBundle(
399
+ backendBundle,
400
+ releaseMetadataUri,
401
+ inferPublicationSourceKind(
402
+ state.currentReleaseId &&
403
+ state.publicationSessionIdByReleaseId.has(state.currentReleaseId)
404
+ ? "portalUpload"
405
+ : "externalUrl"
406
+ )
407
+ );
408
+
409
+ translated.releaseId = translated.releaseId || input.releaseId;
410
+ translated.publicationSessionId =
411
+ translated.publicationSessionId ||
412
+ linkedPublicationSession?.id ||
413
+ state.publicationSessionIdByReleaseId.get(input.releaseId) ||
414
+ state.currentPublicationSessionId ||
415
+ "";
416
+ translated.ingestionSessionId =
417
+ translated.ingestionSessionId ||
418
+ linkedPublicationSession?.ingestionSessionId ||
419
+ "";
420
+
421
+ state.currentReleaseId = translated.releaseId || state.currentReleaseId;
422
+ state.currentPublicationSessionId =
423
+ translated.publicationSessionId || state.currentPublicationSessionId;
424
+
425
+ rememberLinkedPublicationSession(
426
+ state,
427
+ translated.releaseId,
428
+ translated.publicationSessionId
429
+ );
430
+
431
+ return translated;
432
+ },
433
+
434
+ async getPublicationSession(input: PublicationGetSessionInput) {
435
+ const backendResult = await callPortalProcedure<PortalBackendResult>(
436
+ config,
437
+ "publication.getPublicationSession",
438
+ {
439
+ publicationSessionId:
440
+ input.publicationSessionId ||
441
+ (input.releaseId
442
+ ? state.publicationSessionIdByReleaseId.get(input.releaseId)
443
+ : undefined),
444
+ releaseId: input.releaseId,
445
+ },
446
+ "query"
447
+ );
448
+
449
+ const translated = translateBackendPublicationSession(backendResult);
450
+ state.currentPublicationSessionId = translated.id;
451
+ state.currentReleaseId = translated.releaseId || state.currentReleaseId;
452
+ rememberLinkedPublicationSession(
453
+ state,
454
+ translated.releaseId,
455
+ translated.id
456
+ );
457
+ return translated;
458
+ },
459
+
460
+ async cleanupRelease(
461
+ input: PublicationCleanupReleaseInput
462
+ ): Promise<PublicationCleanupReleaseResult> {
463
+ return await callPortalProcedure<PublicationCleanupReleaseResult>(
464
+ config,
465
+ "publication.cleanupRelease",
466
+ input,
467
+ "mutation"
468
+ );
469
+ },
470
+
471
+ async prepareReleaseNftTransaction(
472
+ input: PublicationPrepareReleaseNftTransactionInput
473
+ ): Promise<PublicationPreparedReleaseTransaction> {
474
+ return await callPortalProcedure<PublicationPreparedReleaseTransaction>(
475
+ config,
476
+ "publication.prepareReleaseNftTransaction",
477
+ input,
478
+ "mutation"
479
+ );
480
+ },
481
+
482
+ async submitSignedTransaction(input: {
483
+ signedTransaction: string;
484
+ publicationSessionId?: string;
485
+ }): Promise<PublicationSubmitSignedTransactionResult> {
486
+ return await callPortalProcedure<PublicationSubmitSignedTransactionResult>(
487
+ config,
488
+ "publication.submitSignedTransaction",
489
+ {
490
+ signedTransaction: input.signedTransaction,
491
+ publicationSessionId:
492
+ input.publicationSessionId || state.currentPublicationSessionId,
493
+ },
494
+ "mutation"
495
+ );
496
+ },
497
+
498
+ async saveReleaseNftData(
499
+ input: PublicationSaveReleaseNftDataInput
500
+ ): Promise<PublicationSaveReleaseNftDataResult> {
501
+ return await callPortalProcedure<PublicationSaveReleaseNftDataResult>(
502
+ config,
503
+ "publication.saveReleaseNftData",
504
+ input,
505
+ "mutation"
506
+ );
507
+ },
508
+
509
+ async prepareVerifyCollectionTransaction(
510
+ input: PublicationPrepareVerifyCollectionTransactionInput
511
+ ): Promise<PublicationPreparedVerifyCollectionTransaction> {
512
+ return await callPortalProcedure<PublicationPreparedVerifyCollectionTransaction>(
513
+ config,
514
+ "publication.prepareVerifyCollectionTransaction",
515
+ input,
516
+ "mutation"
517
+ );
518
+ },
519
+
520
+ async markReleaseCollectionAsVerified(input: {
521
+ releaseId: string;
522
+ }): Promise<{ success: boolean; releaseId: string }> {
523
+ return await callPortalProcedure<{ success: boolean; releaseId: string }>(
524
+ config,
525
+ "publication.markReleaseCollectionAsVerified",
526
+ input,
527
+ "mutation"
528
+ );
529
+ },
530
+
531
+ async submitToStore(
532
+ input: PublicationSubmitToStoreInput
533
+ ): Promise<PublicationSubmitToStoreResult> {
534
+ const attestation = isRecord(input.attestation)
535
+ ? input.attestation
536
+ : undefined;
537
+
538
+ const payload =
539
+ typeof attestation?.payload === "string" &&
540
+ attestation.payload.length > 0
541
+ ? attestation.payload
542
+ : typeof attestation?.attestationPayload === "string" &&
543
+ attestation.attestationPayload.length > 0
544
+ ? attestation.attestationPayload
545
+ : typeof (input as Record<string, unknown>).attestationPayload ===
546
+ "string"
547
+ ? String((input as Record<string, unknown>).attestationPayload)
548
+ : "";
549
+ const requestUniqueId =
550
+ typeof attestation?.requestUniqueId === "string"
551
+ ? attestation.requestUniqueId
552
+ : typeof (input as Record<string, unknown>).requestUniqueId ===
553
+ "string"
554
+ ? String((input as Record<string, unknown>).requestUniqueId)
555
+ : "";
556
+
557
+ return await callPortalProcedure<PublicationSubmitToStoreResult>(
558
+ config,
559
+ "publication.submitToStore",
560
+ {
561
+ releaseId: input.releaseId,
562
+ whatsNew: input.whatsNew,
563
+ criticalUpdate: input.criticalUpdate,
564
+ testingInstructions: input.testingInstructions,
565
+ isResubmission: input.isResubmission,
566
+ attestation: {
567
+ payload,
568
+ requestUniqueId,
569
+ },
570
+ },
571
+ "mutation"
572
+ );
573
+ },
574
+ };
575
+ }