@solana-mobile/dapp-store-publishing-tools 0.16.0 → 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.
Files changed (53) hide show
  1. package/lib/CoreUtils.js +0 -3
  2. package/lib/index.js +1 -0
  3. package/lib/portal/attestation.js +189 -0
  4. package/lib/portal/compat.js +3 -0
  5. package/lib/portal/index.js +5 -0
  6. package/lib/portal/signer.js +432 -0
  7. package/lib/portal/types.js +1 -0
  8. package/lib/portal/workflow/contracts.js +1 -0
  9. package/lib/portal/workflow/execution.js +493 -0
  10. package/lib/portal/workflow/ingestion.js +265 -0
  11. package/lib/portal/workflow/lifecycle.js +616 -0
  12. package/lib/portal/workflow/logging.js +8 -0
  13. package/lib/portal/workflow/source/files.js +304 -0
  14. package/lib/portal/workflow/source/preparation.js +318 -0
  15. package/lib/portal/workflow/state/bundle.js +260 -0
  16. package/lib/portal/workflow/state/checkpoints.js +53 -0
  17. package/lib/portal/workflow/state/session.js +100 -0
  18. package/lib/portal/workflow.js +1 -0
  19. package/lib/publish/PublishCoreAttestation.js +18 -17
  20. package/lib/publish/PublishCoreRemove.js +7 -89
  21. package/lib/publish/PublishCoreSubmit.js +7 -117
  22. package/lib/publish/PublishCoreSupport.js +7 -86
  23. package/lib/publish/PublishCoreUpdate.js +7 -117
  24. package/lib/publish/index.js +1 -0
  25. package/lib/schemas/releaseJsonMetadata.json +1 -2
  26. package/package.json +2 -4
  27. package/src/CoreUtils.ts +0 -6
  28. package/src/index.ts +1 -0
  29. package/src/portal/attestation.ts +76 -0
  30. package/src/portal/compat.ts +5 -0
  31. package/src/portal/index.ts +5 -0
  32. package/src/portal/signer.ts +327 -0
  33. package/src/portal/types.ts +447 -0
  34. package/src/portal/workflow/contracts.ts +108 -0
  35. package/src/portal/workflow/execution.ts +412 -0
  36. package/src/portal/workflow/ingestion.ts +187 -0
  37. package/src/portal/workflow/lifecycle.ts +435 -0
  38. package/src/portal/workflow/logging.ts +17 -0
  39. package/src/portal/workflow/source/files.ts +49 -0
  40. package/src/portal/workflow/source/preparation.ts +189 -0
  41. package/src/portal/workflow/state/bundle.ts +193 -0
  42. package/src/portal/workflow/state/checkpoints.ts +70 -0
  43. package/src/portal/workflow/state/session.ts +87 -0
  44. package/src/portal/workflow.ts +9 -0
  45. package/src/publish/PublishCoreAttestation.ts +21 -26
  46. package/src/publish/PublishCoreRemove.ts +13 -109
  47. package/src/publish/PublishCoreSubmit.ts +18 -150
  48. package/src/publish/PublishCoreSupport.ts +13 -102
  49. package/src/publish/PublishCoreUpdate.ts +17 -155
  50. package/src/publish/index.ts +2 -1
  51. package/src/schemas/releaseJsonMetadata.json +1 -2
  52. package/lib/publish/dapp_publisher_portal.js +0 -206
  53. package/src/publish/dapp_publisher_portal.ts +0 -81
@@ -0,0 +1,193 @@
1
+ import type {
2
+ PublicationBundle,
3
+ PublicationSession,
4
+ PublicationSigner,
5
+ } from "../../types.js";
6
+ import { inferFileNameFromUrl } from "../source/files.js";
7
+
8
+ function normalizePublicationMetadata(
9
+ bundle: PublicationBundle
10
+ ): PublicationBundle["metadata"] {
11
+ if (bundle.metadata) {
12
+ return bundle.metadata;
13
+ }
14
+
15
+ return {
16
+ localizedName:
17
+ bundle.release.localizedName ??
18
+ bundle.release.releaseName ??
19
+ bundle.dapp.dappName,
20
+ shortDescription: bundle.dapp.subtitle ?? "",
21
+ longDescription: bundle.dapp.description ?? "",
22
+ newInVersion: bundle.release.newInVersion,
23
+ publisherWebsite: bundle.publisher.website,
24
+ supportEmail:
25
+ bundle.publisher.supportEmail ?? bundle.dapp.supportEmail ?? null,
26
+ website: bundle.dapp.website ?? bundle.dapp.appWebsite ?? null,
27
+ locales: bundle.dapp.languages ?? [],
28
+ legal: {
29
+ licenseUrl: bundle.dapp.licenseUrl ?? null,
30
+ copyrightUrl: bundle.dapp.copyrightUrl ?? null,
31
+ privacyPolicyUrl: bundle.dapp.privacyPolicyUrl ?? null,
32
+ },
33
+ media: [],
34
+ installFile: {
35
+ uri: bundle.installFile.uri,
36
+ mimeType: bundle.installFile.mimeType,
37
+ size: bundle.installFile.size,
38
+ sha256: bundle.installFile.sha256 ?? null,
39
+ fileName:
40
+ bundle.installFile.fileName ??
41
+ inferFileNameFromUrl(bundle.installFile.uri),
42
+ canonicalUrl: bundle.installFile.canonicalUrl ?? bundle.installFile.uri,
43
+ url: bundle.installFile.uri,
44
+ origin: "portal",
45
+ },
46
+ localizedStrings: [],
47
+ releaseMetadataUri:
48
+ bundle.release.releaseMetadataUri ??
49
+ bundle.release.nftMetadataUri ??
50
+ null,
51
+ };
52
+ }
53
+
54
+ function getReleaseMetadataUri(
55
+ bundle: PublicationBundle,
56
+ session?: PublicationSession
57
+ ): string | null {
58
+ return (
59
+ session?.metadataUri ??
60
+ bundle.release.releaseMetadataUri ??
61
+ bundle.release.nftMetadataUri ??
62
+ bundle.metadata?.releaseMetadataUri ??
63
+ null
64
+ );
65
+ }
66
+
67
+ export function normalizePublicationBundle(
68
+ bundle: PublicationBundle
69
+ ): PublicationBundle {
70
+ return {
71
+ ...bundle,
72
+ releaseId: bundle.releaseId || bundle.release.id || "",
73
+ metadata: normalizePublicationMetadata(bundle),
74
+ };
75
+ }
76
+
77
+ export function withPublicationBundleIdentifiers(
78
+ bundle: PublicationBundle,
79
+ identifiers: {
80
+ releaseId?: string | null;
81
+ publicationSessionId?: string | null;
82
+ ingestionSessionId?: string | null;
83
+ }
84
+ ): PublicationBundle {
85
+ return {
86
+ ...bundle,
87
+ releaseId:
88
+ bundle.releaseId || bundle.release.id || identifiers.releaseId || "",
89
+ publicationSessionId:
90
+ bundle.publicationSessionId || identifiers.publicationSessionId || "",
91
+ ingestionSessionId:
92
+ bundle.ingestionSessionId || identifiers.ingestionSessionId || "",
93
+ };
94
+ }
95
+
96
+ export function resolveReleaseMetadataUri(
97
+ bundle: PublicationBundle,
98
+ session?: PublicationSession
99
+ ): string {
100
+ const releaseMetadataUri = getReleaseMetadataUri(bundle, session);
101
+
102
+ if (!releaseMetadataUri) {
103
+ throw new Error(
104
+ "Publication bundle did not include a release metadata URI"
105
+ );
106
+ }
107
+
108
+ return releaseMetadataUri;
109
+ }
110
+
111
+ export function hasResolvableReleaseMetadataUri(
112
+ bundle: PublicationBundle,
113
+ session?: PublicationSession
114
+ ): boolean {
115
+ return Boolean(getReleaseMetadataUri(bundle, session));
116
+ }
117
+
118
+ export function resolvePublicationSignerAddress(
119
+ bundle: PublicationBundle
120
+ ): string {
121
+ return (
122
+ bundle.signerAuthority.dappWalletAddress ??
123
+ bundle.signerAuthority.requiredSigner ??
124
+ bundle.signerAuthority.collectionAuthority
125
+ );
126
+ }
127
+
128
+ export function resolveReleaseDisplayName(bundle: PublicationBundle): string {
129
+ return (
130
+ bundle.release.localizedName ??
131
+ bundle.release.releaseName ??
132
+ bundle.dapp.dappName
133
+ );
134
+ }
135
+
136
+ export function resolvePublicationFeePayer(
137
+ bundle: PublicationBundle,
138
+ signer: PublicationSigner
139
+ ): string {
140
+ return bundle.signerAuthority.feePayer ?? signer.publicKey;
141
+ }
142
+
143
+ export function validatePublicationBundle(bundle: PublicationBundle): void {
144
+ const normalizedBundle = normalizePublicationBundle(bundle);
145
+ const requiredFields: Array<[string, unknown]> = [
146
+ ["releaseId", normalizedBundle.releaseId],
147
+ ["publicationSessionId", normalizedBundle.publicationSessionId],
148
+ ["ingestionSessionId", normalizedBundle.ingestionSessionId],
149
+ ["androidPackage", normalizedBundle.release.androidPackage],
150
+ [
151
+ "release.localizedName",
152
+ normalizedBundle.release.localizedName ??
153
+ normalizedBundle.release.releaseName,
154
+ ],
155
+ ["versionName", normalizedBundle.release.versionName],
156
+ ["appMintAddress", normalizedBundle.signerAuthority.appMintAddress],
157
+ ["dappWalletAddress", normalizedBundle.signerAuthority.dappWalletAddress],
158
+ [
159
+ "collectionAuthority",
160
+ normalizedBundle.signerAuthority.collectionAuthority,
161
+ ],
162
+ [
163
+ "acceptedSignerRoles",
164
+ normalizedBundle.signerAuthority.acceptedSignerRoles,
165
+ ],
166
+ ["metadata.localizedName", normalizedBundle.metadata?.localizedName],
167
+ ["shortDescription", normalizedBundle.metadata?.shortDescription],
168
+ ["longDescription", normalizedBundle.metadata?.longDescription],
169
+ ["newInVersion", normalizedBundle.metadata?.newInVersion],
170
+ ["installFile.uri", normalizedBundle.installFile.uri],
171
+ ["installFile.mimeType", normalizedBundle.installFile.mimeType],
172
+ ];
173
+
174
+ const missing = requiredFields.filter(([, value]) => {
175
+ if (typeof value === "string") {
176
+ return value.trim().length === 0;
177
+ }
178
+
179
+ if (Array.isArray(value)) {
180
+ return value.length === 0;
181
+ }
182
+
183
+ return value === undefined || value === null;
184
+ });
185
+
186
+ if (missing.length > 0) {
187
+ throw new Error(
188
+ `Publication bundle is missing required fields: ${missing
189
+ .map(([field]) => field)
190
+ .join(", ")}`
191
+ );
192
+ }
193
+ }
@@ -0,0 +1,70 @@
1
+ import type { PublicationCheckpoint, PublicationSession } from "../../types.js";
2
+
3
+ const publicationCheckpointOrder: PublicationCheckpoint[] = [
4
+ "created",
5
+ "bundle-ready",
6
+ "mint-submitted",
7
+ "mint-saved",
8
+ "verification-submitted",
9
+ "verified",
10
+ "attested",
11
+ "submitted",
12
+ "completed",
13
+ ];
14
+
15
+ export function checkpointAtLeast(
16
+ checkpoint: PublicationCheckpoint,
17
+ expected: PublicationCheckpoint
18
+ ): boolean {
19
+ return (
20
+ publicationCheckpointOrder.indexOf(checkpoint) >=
21
+ publicationCheckpointOrder.indexOf(expected)
22
+ );
23
+ }
24
+
25
+ export function publicationStageToCheckpoint(
26
+ stage: PublicationSession["stage"]
27
+ ): PublicationCheckpoint {
28
+ if (!stage) {
29
+ return "created";
30
+ }
31
+
32
+ switch (stage) {
33
+ case "PreparedForMint":
34
+ return "bundle-ready";
35
+ case "MintSubmitted":
36
+ return "mint-submitted";
37
+ case "MintSaved":
38
+ return "mint-saved";
39
+ case "VerificationSubmitted":
40
+ return "verification-submitted";
41
+ case "Verified":
42
+ return "verified";
43
+ case "Attested":
44
+ return "attested";
45
+ case "Submitted":
46
+ return "submitted";
47
+ case "Failed":
48
+ default:
49
+ return "created";
50
+ }
51
+ }
52
+
53
+ export function publicationStageToStatus(
54
+ stage: PublicationSession["stage"]
55
+ ): PublicationSession["status"] {
56
+ if (!stage) {
57
+ return "pending";
58
+ }
59
+
60
+ switch (stage) {
61
+ case "Submitted":
62
+ return "completed";
63
+ case "Failed":
64
+ return "failed";
65
+ case "PreparedForMint":
66
+ return "pending";
67
+ default:
68
+ return "running";
69
+ }
70
+ }
@@ -0,0 +1,87 @@
1
+ import type {
2
+ PublicationBundle,
3
+ PublicationSession,
4
+ PublicationSessionStage,
5
+ } from "../../types.js";
6
+ import {
7
+ publicationStageToCheckpoint,
8
+ publicationStageToStatus,
9
+ } from "./checkpoints.js";
10
+
11
+ export function normalizePublicationSession(
12
+ session: PublicationSession
13
+ ): PublicationSession {
14
+ const stage = resolvePublicationSessionStage(session);
15
+ const checkpoint = session.checkpoint ?? publicationStageToCheckpoint(stage);
16
+
17
+ return {
18
+ ...session,
19
+ stage,
20
+ checkpoint,
21
+ status: session.status ?? publicationStageToStatus(stage),
22
+ releaseMintAddress:
23
+ session.releaseMintAddress ?? session.expectedMintAddress ?? null,
24
+ verifyTransactionSignature:
25
+ session.verifyTransactionSignature ??
26
+ session.verificationTransactionSignature ??
27
+ null,
28
+ };
29
+ }
30
+
31
+ export function resolvePublicationSessionStage(
32
+ session: PublicationSession
33
+ ): PublicationSessionStage {
34
+ if (session.stage) {
35
+ return session.stage;
36
+ }
37
+
38
+ if (session.status === "failed") {
39
+ return "Failed";
40
+ }
41
+
42
+ if (session.status === "completed") {
43
+ return "Submitted";
44
+ }
45
+
46
+ if (
47
+ session.checkpoint === "submitted" ||
48
+ session.checkpoint === "completed"
49
+ ) {
50
+ return "Submitted";
51
+ }
52
+
53
+ if (session.checkpoint === "verified") {
54
+ return "Verified";
55
+ }
56
+
57
+ if (session.checkpoint === "attested") {
58
+ return "Attested";
59
+ }
60
+
61
+ if (session.checkpoint === "verification-submitted") {
62
+ return "VerificationSubmitted";
63
+ }
64
+
65
+ if (session.checkpoint === "mint-saved") {
66
+ return "MintSaved";
67
+ }
68
+
69
+ if (session.checkpoint === "mint-submitted") {
70
+ return "MintSubmitted";
71
+ }
72
+
73
+ return "PreparedForMint";
74
+ }
75
+
76
+ export function resolveReleaseMintAddress(
77
+ bundle: PublicationBundle,
78
+ publicationSession: PublicationSession
79
+ ): string | undefined {
80
+ return (
81
+ publicationSession.releaseMintAddress ??
82
+ publicationSession.expectedMintAddress ??
83
+ bundle.release.nftMintAddress ??
84
+ bundle.release.releaseMintAddress ??
85
+ undefined
86
+ );
87
+ }
@@ -0,0 +1,9 @@
1
+ export type {
2
+ PublicationResumeInput,
3
+ PublicationWorkflowClient,
4
+ PublicationWorkflowInput,
5
+ PublicationWorkflowOptions,
6
+ PublicationWorkflowPrepareReleaseTransactionInput,
7
+ PublicationWorkflowPrepareVerifyTransactionInput,
8
+ } from "./workflow/contracts.js";
9
+ export { createPublicationWorkflow } from "./workflow/lifecycle.js";
@@ -1,31 +1,26 @@
1
- import { Connection } from "@solana/web3.js";
2
- import { SignWithPublisherKeypair } from "./types";
1
+ import { Connection } from '@solana/web3.js';
3
2
 
4
- //
5
- // Construct and sign attestation payloads
6
- //
3
+ import {
4
+ createAttestationPayload as createPortalAttestationPayload,
5
+ type PublicationAttestationResult,
6
+ } from '../portal/attestation.js';
7
7
 
8
- type Attestation = {
9
- slot_number: number;
10
- blockhash: string;
11
- request_unique_id: string;
12
- };
13
-
14
- export const createAttestationPayload = async (connection: Connection, sign: SignWithPublisherKeypair) => {
15
- const REQUEST_UNIQUE_ID_LEN = 32;
16
- const REQUEST_UNIQUE_ID_CHAR_SET = "0123456789";
17
- const requestUniqueId = Array(REQUEST_UNIQUE_ID_LEN).fill(undefined).map((_) =>
18
- REQUEST_UNIQUE_ID_CHAR_SET.charAt(Math.floor(Math.random() * REQUEST_UNIQUE_ID_CHAR_SET.length))
19
- ).join("")
20
-
21
- const blockhash = await connection.getLatestBlockhashAndContext("finalized");
8
+ export type SignWithPublisherKeypair = (buf: Buffer) => Buffer;
22
9
 
23
- const attestation: Attestation = {
24
- slot_number: blockhash.context.slot,
25
- blockhash: blockhash.value.blockhash,
26
- request_unique_id: requestUniqueId
27
- };
28
- const signedAttestation = sign(Buffer.from(JSON.stringify(attestation)));
10
+ type Attestation = PublicationAttestationResult;
29
11
 
30
- return { attestationPayload: Buffer.from(signedAttestation.buffer).toString("base64"), requestUniqueId };
12
+ export const createAttestationPayload = async (
13
+ connection: Connection,
14
+ sign: SignWithPublisherKeypair,
15
+ ): Promise<Attestation> => {
16
+ const blockhash = await connection.getLatestBlockhashAndContext('finalized');
17
+ return createPortalAttestationPayload(
18
+ {
19
+ slot: blockhash.context.slot,
20
+ blockhash: blockhash.value.blockhash,
21
+ },
22
+ {
23
+ signMessage: async (message: Uint8Array) => sign(Buffer.from(message)),
24
+ },
25
+ );
31
26
  };
@@ -1,119 +1,23 @@
1
- import { Connection } from "@solana/web3.js";
2
- import type { Publisher } from "../types.js";
3
- import { createAttestationPayload } from "./PublishCoreAttestation.js";
4
- import {
5
- CONTACT_OBJECT_ID,
6
- CONTACT_PROPERTY_COMPANY,
7
- CONTACT_PROPERTY_EMAIL,
8
- CONTACT_PROPERTY_WEBSITE,
9
- submitRequestToSolanaDappPublisherPortal,
10
- TICKET_OBJECT_ID,
11
- TICKET_PROPERTY_ATTESTATION_PAYLOAD,
12
- TICKET_PROPERTY_AUTHORIZED_REQUEST,
13
- TICKET_PROPERTY_CRITICAL_UPDATE,
14
- TICKET_PROPERTY_DAPP_COLLECTION_ACCOUNT_ADDRESS,
15
- TICKET_PROPERTY_DAPP_RELEASE_ACCOUNT_ADDRESS,
16
- TICKET_PROPERTY_REQUEST_UNIQUE_ID,
17
- URL_FORM_REMOVE
18
- } from "./dapp_publisher_portal.js";
19
- import { PublishSolanaNetworkInput, SignWithPublisherKeypair } from "./types.js";
20
-
21
- const createRemoveRequest = async (
22
- connection: Connection,
23
- sign: SignWithPublisherKeypair,
24
- appMintAddress: string,
25
- releaseMintAddress: string,
26
- publisherDetails: Publisher,
27
- requestorIsAuthorized: boolean,
28
- criticalUpdate: boolean
29
- ) => {
30
- const { attestationPayload, requestUniqueId } = await createAttestationPayload(connection, sign);
31
-
32
- const request = {
33
- fields: [
34
- {
35
- objectTypeId: CONTACT_OBJECT_ID,
36
- name: CONTACT_PROPERTY_COMPANY,
37
- value: publisherDetails.name
38
- },
39
- {
40
- objectTypeId: CONTACT_OBJECT_ID,
41
- name: CONTACT_PROPERTY_EMAIL,
42
- value: publisherDetails.email
43
- },
44
- {
45
- objectTypeId: CONTACT_OBJECT_ID,
46
- name: CONTACT_PROPERTY_WEBSITE,
47
- value: publisherDetails.website
48
- },
49
- {
50
- objectTypeId: TICKET_OBJECT_ID,
51
- name: TICKET_PROPERTY_ATTESTATION_PAYLOAD,
52
- value: attestationPayload
53
- },
54
- {
55
- objectTypeId: TICKET_OBJECT_ID,
56
- name: TICKET_PROPERTY_DAPP_COLLECTION_ACCOUNT_ADDRESS,
57
- value: appMintAddress
58
- },
59
- {
60
- objectTypeId: TICKET_OBJECT_ID,
61
- name: TICKET_PROPERTY_DAPP_RELEASE_ACCOUNT_ADDRESS,
62
- value: releaseMintAddress
63
- },
64
- {
65
- objectTypeId: TICKET_OBJECT_ID,
66
- name: TICKET_PROPERTY_REQUEST_UNIQUE_ID,
67
- value: requestUniqueId
68
- },
69
- {
70
- objectTypeId: TICKET_OBJECT_ID,
71
- name: TICKET_PROPERTY_AUTHORIZED_REQUEST,
72
- value: requestorIsAuthorized
73
- },
74
- ]
75
- };
76
-
77
- if (criticalUpdate) {
78
- request.fields.push(
79
- {
80
- objectTypeId: TICKET_OBJECT_ID,
81
- name: TICKET_PROPERTY_CRITICAL_UPDATE,
82
- value: criticalUpdate
83
- }
84
- );
85
- }
86
-
87
- return request;
88
- };
1
+ import { deprecateLegacyPublishSurface } from '../portal/compat.js';
2
+ import type { PublishSolanaNetworkInput } from './types.js';
89
3
 
90
4
  export type PublishRemoveInput = {
91
5
  appMintAddress: string;
92
6
  releaseMintAddress: string;
93
- publisherDetails: Publisher;
7
+ publisherDetails: {
8
+ name: string;
9
+ website: string;
10
+ email: string;
11
+ };
94
12
  requestorIsAuthorized: boolean;
95
13
  criticalUpdate: boolean;
96
14
  };
97
15
 
98
16
  export const publishRemove = async (
99
- publishSolanaNetworkInput: PublishSolanaNetworkInput,
100
- {
101
- appMintAddress,
102
- releaseMintAddress,
103
- publisherDetails,
104
- requestorIsAuthorized,
105
- criticalUpdate,
106
- } : PublishRemoveInput,
107
- dryRun: boolean,
108
- ) => {
109
- const removeRequest = await createRemoveRequest(
110
- publishSolanaNetworkInput.connection,
111
- publishSolanaNetworkInput.sign,
112
- appMintAddress,
113
- releaseMintAddress,
114
- publisherDetails,
115
- requestorIsAuthorized,
116
- criticalUpdate);
117
-
118
- return submitRequestToSolanaDappPublisherPortal(removeRequest, URL_FORM_REMOVE, dryRun);
17
+ _publishSolanaNetworkInput: PublishSolanaNetworkInput,
18
+ _input: PublishRemoveInput,
19
+ _dryRun: boolean,
20
+ ): Promise<never> => {
21
+ deprecateLegacyPublishSurface('publishRemove');
22
+ return undefined as never;
119
23
  };