@solana-mobile/dapp-store-cli 0.7.3 → 0.8.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.
@@ -7,7 +7,7 @@ import {
7
7
  validateRelease,
8
8
  metaplexFileReplacer,
9
9
  } from "@solana-mobile/dapp-store-publishing-tools";
10
- import { debug } from "../CliUtils.js";
10
+ import { debug, showMessage } from "../CliUtils.js";
11
11
 
12
12
  import type { Keypair } from "@solana/web3.js";
13
13
  import type { MetaplexFile } from "@metaplex-foundation/js";
@@ -39,7 +39,13 @@ export const validateCommand = async ({
39
39
  validatePublisher(publisherJson);
40
40
  console.info(`Publisher JSON valid!`);
41
41
  } catch (e) {
42
- console.error(e);
42
+ const errorMsg = (e as Error | null)?.message ?? "";
43
+ showMessage(
44
+ "Publisher JSON invalid",
45
+ errorMsg,
46
+ "error"
47
+ )
48
+ return
43
49
  }
44
50
 
45
51
  const appJson = createAppJson(appDetails, signer.publicKey);
@@ -52,7 +58,13 @@ export const validateCommand = async ({
52
58
  validateApp(appJson);
53
59
  console.info(`App JSON valid!`);
54
60
  } catch (e) {
55
- console.error(e);
61
+ const errorMsg = (e as Error | null)?.message ?? "";
62
+ showMessage(
63
+ "App JSON invalid",
64
+ errorMsg,
65
+ "error"
66
+ )
67
+ return
56
68
  }
57
69
 
58
70
  const releaseJson = await createReleaseJson(
@@ -60,6 +72,16 @@ export const validateCommand = async ({
60
72
  signer.publicKey
61
73
  );
62
74
 
75
+
76
+ if (appDetails.android_package != releaseDetails.android_details.android_package) {
77
+ showMessage(
78
+ "App package name and release package name do not match",
79
+ "App release specifies " + appDetails.android_package + " while release specifies " + releaseDetails.android_details.android_package,
80
+ "error"
81
+ )
82
+ return
83
+ }
84
+
63
85
  const objStringified = JSON.stringify(releaseJson, metaplexFileReplacer, 2);
64
86
  debug("releaseJson=", objStringified);
65
87
 
@@ -67,6 +89,18 @@ export const validateCommand = async ({
67
89
  validateRelease(JSON.parse(objStringified));
68
90
  console.info(`Release JSON valid!`);
69
91
  } catch (e) {
70
- console.error(e);
92
+ const errorMsg = (e as Error | null)?.message ?? "";
93
+ showMessage(
94
+ "Release JSON invalid",
95
+ errorMsg,
96
+ "error"
97
+ )
98
+ return
71
99
  }
100
+
101
+ showMessage(
102
+ "Json is Valid",
103
+ "Input data is valid",
104
+ "standard"
105
+ )
72
106
  };
@@ -8,7 +8,9 @@ import {
8
8
  } from "@solana/web3.js";
9
9
 
10
10
  import {
11
+ Constants,
11
12
  getMetaplexInstance,
13
+ showMessage,
12
14
  } from "../../CliUtils.js";
13
15
  import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
14
16
 
@@ -19,15 +21,18 @@ const createAppNft = async (
19
21
  publisherMintAddress,
20
22
  publisher,
21
23
  storageParams,
24
+ priorityFeeLamports,
22
25
  }: {
23
26
  appDetails: App;
24
27
  connection: Connection;
25
28
  publisherMintAddress: string;
26
29
  publisher: Keypair;
27
30
  storageParams: string;
31
+ priorityFeeLamports: number;
28
32
  },
29
- { dryRun }: { dryRun?: boolean }
30
33
  ) => {
34
+ console.info(`Creating App NFT`);
35
+
31
36
  const mintAddress = Keypair.generate();
32
37
  const metaplex = getMetaplexInstance(connection, publisher, storageParams);
33
38
  const txBuilder = await createApp(
@@ -35,25 +40,37 @@ const createAppNft = async (
35
40
  publisherMintAddress: new PublicKey(publisherMintAddress),
36
41
  mintAddress,
37
42
  appDetails,
43
+ priorityFeeLamports
38
44
  },
39
45
  { metaplex, publisher }
40
46
  );
41
47
 
42
- const blockhash = await connection.getLatestBlockhashAndContext();
43
- const tx = txBuilder.toTransaction(blockhash.value);
44
- tx.sign(mintAddress, publisher);
48
+ console.info(`App NFT data upload complete\nSigning transaction now`);
49
+ const maxTries = 8;
50
+ for (let i = 1; i <= maxTries; i++) {
51
+ try {
52
+ const blockhash = await connection.getLatestBlockhashAndContext();
53
+ const tx = txBuilder.toTransaction(blockhash.value);
54
+ tx.sign(mintAddress, publisher);
45
55
 
46
- if (!dryRun) {
47
- const txSig = await sendAndConfirmTransaction(connection, tx, [
48
- publisher,
49
- mintAddress,
50
- ], {
51
- minContextSlot: blockhash.context.slot
52
- });
53
- console.info({ txSig, mintAddress: mintAddress.publicKey.toBase58() });
56
+ const txSig = await sendAndConfirmTransaction(connection, tx, [
57
+ publisher,
58
+ mintAddress,
59
+ ], {
60
+ minContextSlot: blockhash.context.slot
61
+ });
62
+ return { appAddress: mintAddress.publicKey.toBase58(), transactionSignature: txSig };
63
+ } catch (e) {
64
+ const errorMsg = (e as Error | null)?.message ?? "";
65
+ if (i == maxTries) {
66
+ showMessage("Transaction Failure", errorMsg, "error");
67
+ process.exit(-1)
68
+ } else {
69
+ const retryMsg = errorMsg + "\nWill Retry minting app NFT."
70
+ showMessage("Transaction Failure", retryMsg, "standard");
71
+ }
72
+ }
54
73
  }
55
-
56
- return { appAddress: mintAddress.publicKey.toBase58() };
57
74
  };
58
75
 
59
76
  type CreateAppCommandInput = {
@@ -62,6 +79,7 @@ type CreateAppCommandInput = {
62
79
  url: string;
63
80
  dryRun?: boolean;
64
81
  storageParams: string;
82
+ priorityFeeLamports: number;
65
83
  };
66
84
 
67
85
  export const createAppCommand = async ({
@@ -70,26 +88,27 @@ export const createAppCommand = async ({
70
88
  dryRun,
71
89
  publisherMintAddress,
72
90
  storageParams,
91
+ priorityFeeLamports = Constants.DEFAULT_PRIORITY_FEE,
73
92
  }: CreateAppCommandInput) => {
74
93
  const connection = new Connection(url);
75
94
 
76
95
  const { app: appDetails, publisher: publisherDetails } =
77
96
  await loadPublishDetailsWithChecks();
78
97
 
79
- const { appAddress } = await createAppNft(
80
- {
81
- connection,
82
- publisher: signer,
83
- publisherMintAddress: publisherDetails.address ?? publisherMintAddress,
84
- appDetails,
85
- storageParams,
86
- },
87
- { dryRun }
88
- );
89
-
90
98
  if (!dryRun) {
99
+ const { appAddress, transactionSignature } = await createAppNft(
100
+ {
101
+ connection,
102
+ publisher: signer,
103
+ publisherMintAddress: publisherDetails.address ?? publisherMintAddress,
104
+ appDetails,
105
+ storageParams,
106
+ priorityFeeLamports
107
+ },
108
+ );
109
+
91
110
  await writeToPublishDetails({ app: { address: appAddress } });
92
- }
93
111
 
94
- return { appAddress };
112
+ return { appAddress, transactionSignature };
113
+ }
95
114
  };
@@ -7,7 +7,9 @@ import {
7
7
  } from "@solana/web3.js";
8
8
 
9
9
  import {
10
+ Constants,
10
11
  getMetaplexInstance,
12
+ showMessage,
11
13
  } from "../../CliUtils.js";
12
14
  import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
13
15
 
@@ -17,39 +19,49 @@ const createPublisherNft = async (
17
19
  publisher,
18
20
  publisherDetails,
19
21
  storageParams,
22
+ priorityFeeLamports,
20
23
  }: {
21
24
  connection: Connection;
22
25
  publisher: Keypair;
23
26
  publisherDetails: Publisher;
24
27
  storageParams: string;
28
+ priorityFeeLamports: number;
25
29
  },
26
- { dryRun }: { dryRun: boolean }
27
30
  ) => {
31
+ console.info(`Creating Publisher NFT`);
28
32
  const mintAddress = Keypair.generate();
29
33
  const metaplex = getMetaplexInstance(connection, publisher, storageParams);
30
- console.info(
31
- `Creating publisher at address: ${mintAddress.publicKey.toBase58()}`
32
- );
33
34
  const txBuilder = await createPublisher(
34
- { mintAddress, publisherDetails },
35
+ { mintAddress, publisherDetails, priorityFeeLamports },
35
36
  { metaplex, publisher }
36
37
  );
37
38
 
38
- const blockhash = await connection.getLatestBlockhashAndContext();
39
- const tx = txBuilder.toTransaction(blockhash.value);
40
- tx.sign(mintAddress, publisher);
39
+ console.info(`Publisher NFT data upload complete\nSigning transaction now`);
40
+ const maxTries = 8;
41
+ for (let i = 1; i <= maxTries; i++) {
42
+ try {
43
+ const blockhash = await connection.getLatestBlockhashAndContext();
44
+ const tx = txBuilder.toTransaction(blockhash.value);
45
+ tx.sign(mintAddress, publisher);
41
46
 
42
- if (!dryRun) {
43
- const txSig = await sendAndConfirmTransaction(connection, tx, [
44
- publisher,
45
- mintAddress,
46
- ], {
47
- minContextSlot: blockhash.context.slot
48
- });
49
- console.info({ txSig, mintAddress: mintAddress.publicKey.toBase58() });
47
+ const txSig = await sendAndConfirmTransaction(connection, tx, [
48
+ publisher,
49
+ mintAddress,
50
+ ], {
51
+ minContextSlot: blockhash.context.slot
52
+ });
53
+ return { publisherAddress: mintAddress.publicKey.toBase58(), transactionSignature: txSig};
54
+ } catch (e) {
55
+ const errorMsg = (e as Error | null)?.message ?? "";
56
+ if (i == maxTries) {
57
+ showMessage("Transaction Failure", errorMsg, "error");
58
+ process.exit(-1)
59
+ } else {
60
+ const retryMsg = errorMsg + "\nWill Retry minting publisher."
61
+ showMessage("Transaction Failure", retryMsg, "standard");
62
+ }
63
+ }
50
64
  }
51
-
52
- return { publisherAddress: mintAddress.publicKey.toBase58() };
53
65
  };
54
66
 
55
67
  export const createPublisherCommand = async ({
@@ -57,28 +69,31 @@ export const createPublisherCommand = async ({
57
69
  url,
58
70
  dryRun,
59
71
  storageParams,
72
+ priorityFeeLamports = Constants.DEFAULT_PRIORITY_FEE,
60
73
  }: {
61
74
  signer: Keypair;
62
75
  url: string;
63
76
  dryRun: boolean;
64
77
  storageParams: string;
78
+ priorityFeeLamports: number;
65
79
  }) => {
66
80
  const connection = new Connection(url);
67
81
 
68
82
  const { publisher: publisherDetails } = await loadPublishDetailsWithChecks();
69
83
 
70
- const { publisherAddress } = await createPublisherNft(
71
- {
72
- connection,
73
- publisher: signer,
74
- publisherDetails,
75
- storageParams: storageParams,
76
- },
77
- { dryRun }
78
- );
84
+ if (!dryRun) {
85
+ const { publisherAddress, transactionSignature } = await createPublisherNft(
86
+ {
87
+ connection,
88
+ publisher: signer,
89
+ publisherDetails,
90
+ storageParams: storageParams,
91
+ priorityFeeLamports: priorityFeeLamports,
92
+ },
93
+ );
79
94
 
80
- // TODO(sdlaver): dry-run should not modify config
81
- await writeToPublishDetails({ publisher: { address: publisherAddress } });
95
+ await writeToPublishDetails({ publisher: { address: publisherAddress } });
82
96
 
83
- return { publisherAddress };
97
+ return { publisherAddress, transactionSignature };
98
+ }
84
99
  };
@@ -11,6 +11,7 @@ import {
11
11
  sendAndConfirmTransaction,
12
12
  } from "@solana/web3.js";
13
13
  import {
14
+ Constants,
14
15
  getMetaplexInstance,
15
16
  showMessage
16
17
  } from "../../CliUtils.js";
@@ -23,6 +24,7 @@ type CreateReleaseCommandInput = {
23
24
  url: string;
24
25
  dryRun?: boolean;
25
26
  storageParams: string;
27
+ priorityFeeLamports: number;
26
28
  };
27
29
 
28
30
  const createReleaseNft = async ({
@@ -33,6 +35,7 @@ const createReleaseNft = async ({
33
35
  connection,
34
36
  publisher,
35
37
  storageParams,
38
+ priorityFeeLamports,
36
39
  }: {
37
40
  appMintAddress: string;
38
41
  releaseDetails: Release;
@@ -41,7 +44,10 @@ const createReleaseNft = async ({
41
44
  connection: Connection;
42
45
  publisher: Keypair;
43
46
  storageParams: string;
47
+ priorityFeeLamports: number;
44
48
  }) => {
49
+ console.info(`Creating Release NFT`);
50
+
45
51
  const releaseMintAddress = Keypair.generate();
46
52
 
47
53
  const metaplex = getMetaplexInstance(connection, publisher, storageParams);
@@ -53,10 +59,12 @@ const createReleaseNft = async ({
53
59
  releaseDetails,
54
60
  appDetails,
55
61
  publisherDetails,
62
+ priorityFeeLamports
56
63
  },
57
64
  { metaplex, publisher }
58
65
  );
59
66
 
67
+ console.info(`Release NFT data upload complete\nSigning transaction now`);
60
68
  const maxTries = 8;
61
69
  for (let i = 1; i <= maxTries; i++) {
62
70
  try {
@@ -69,18 +77,14 @@ const createReleaseNft = async ({
69
77
  ], {
70
78
  minContextSlot: blockhash.context.slot,
71
79
  });
72
- console.info({
73
- txSig,
74
- releaseMintAddress: releaseMintAddress.publicKey.toBase58(),
75
- });
76
- return { releaseAddress: releaseMintAddress.publicKey.toBase58() };
80
+ return { releaseAddress: releaseMintAddress.publicKey.toBase58(), transactionSignature: txSig };
77
81
  } catch (e) {
78
82
  const errorMsg = (e as Error | null)?.message ?? "";
79
83
  if (i == maxTries) {
80
84
  showMessage("Transaction Failure", errorMsg, "error");
81
85
  process.exit(-1)
82
86
  } else {
83
- const retryMsg = errorMsg + "\nWill Retry minting release"
87
+ const retryMsg = errorMsg + "\nWill Retry minting release NFT"
84
88
  showMessage("Transaction Failure", retryMsg, "standard");
85
89
  }
86
90
  }
@@ -94,13 +98,19 @@ export const createReleaseCommand = async ({
94
98
  url,
95
99
  dryRun = false,
96
100
  storageParams,
101
+ priorityFeeLamports = Constants.DEFAULT_PRIORITY_FEE,
97
102
  }: CreateReleaseCommandInput) => {
98
103
  const connection = new Connection(url);
99
104
 
100
105
  const { release, app, publisher } = await loadPublishDetailsWithChecks(buildToolsPath);
101
106
 
107
+
108
+ if (app.android_package != release.android_details.android_package) {
109
+ throw new Error("App package name and release package name do not match.\nApp release specifies " + app.android_package + " while release specifies " + release.android_details.android_package)
110
+ }
111
+
102
112
  if (!dryRun) {
103
- const { releaseAddress } = await createReleaseNft({
113
+ const { releaseAddress, transactionSignature } = await createReleaseNft({
104
114
  appMintAddress: app.address ?? appMintAddress,
105
115
  connection,
106
116
  publisher: signer,
@@ -110,10 +120,11 @@ export const createReleaseCommand = async ({
110
120
  appDetails: app,
111
121
  publisherDetails: publisher,
112
122
  storageParams: storageParams,
123
+ priorityFeeLamports: priorityFeeLamports,
113
124
  });
114
125
 
115
126
  await writeToPublishDetails({ release: { address: releaseAddress }, });
116
127
 
117
- return { releaseAddress };
128
+ return { releaseAddress, transactionSignature };
118
129
  }
119
130
  };
@@ -119,13 +119,25 @@ export const loadPublishDetailsWithChecks = async (
119
119
  }
120
120
 
121
121
  config.release.media.forEach((item: PublishDetails["release"]["media"][0]) => {
122
- const imagePath = path.join(process.cwd(), item.uri);
123
- if (!fs.existsSync(imagePath) || !checkImageExtension(imagePath)) {
124
- throw new Error(`Invalid media path or file type: ${item.uri}. Please ensure the file is a jpeg, png, or webp file.`);
125
- }
122
+ const imagePath = path.join(process.cwd(), item.uri);
123
+ if (!fs.existsSync(imagePath) || !checkImageExtension(imagePath)) {
124
+ throw new Error(`Invalid media path or file type: ${item.uri}. Please ensure the file is a jpeg, png, or webp file.`);
126
125
  }
126
+ }
127
127
  );
128
128
 
129
+ const screenshots = config.release.media?.filter(
130
+ (asset: any) => asset.purpose === "screenshot"
131
+ )
132
+
133
+ if (screenshots.length < 4) {
134
+ showMessage(
135
+ "Screenshots requirements changing in version 0.9.0",
136
+ `At least 4 screenshots are required for publishing a new release. Found only ${screenshots.length}`,
137
+ "warning"
138
+ )
139
+ }
140
+
129
141
  validateLocalizableResources(config);
130
142
 
131
143
  const googlePkg = config.solana_mobile_dapp_publisher_portal.google_store_package;
@@ -146,7 +158,7 @@ const checkIconCompatibility = async (path: string, typeString: string) => {
146
158
  }
147
159
 
148
160
  if (await checkIconDimensions(path)) {
149
- throw new Error("Icons must have square dimensions and be no greater than 512px by 512px.");
161
+ throw new Error("Icons must be 512px by 512px.");
150
162
  }
151
163
  };
152
164
 
@@ -189,7 +201,7 @@ const validateLocalizableResources = (config: PublishDetails) => {
189
201
  const checkIconDimensions = async (iconPath: string): Promise<boolean> => {
190
202
  const size = await runImgSize(iconPath);
191
203
 
192
- return size?.width != size?.height || (size?.width ?? 0) > 512;
204
+ return size?.width != size?.height || (size?.width ?? 0) != 512;
193
205
  };
194
206
 
195
207
  const getAndroidDetails = async (
@@ -32,7 +32,6 @@ export class CachedStorageDriver implements StorageDriver {
32
32
  { assetManifestPath }: { assetManifestPath: string }
33
33
  ) {
34
34
  this.assetManifestPath = assetManifestPath;
35
- console.info({ loading: true });
36
35
  this.assetManifest = this.loadAssetManifest(assetManifestPath) ?? {
37
36
  schema_version: CachedStorageDriver.SCHEMA_VERSION,
38
37
  assets: {},
@@ -69,15 +68,6 @@ export class CachedStorageDriver implements StorageDriver {
69
68
  }
70
69
  const hash = createHash("sha256").update(file.buffer).digest("base64");
71
70
 
72
- console.info(
73
- JSON.stringify({
74
- file: {
75
- name: file.fileName,
76
- disn: file.displayName,
77
- un: file.uniqueName,
78
- },
79
- })
80
- );
81
71
  const uploadedAsset = this.uploadedAsset(file.fileName, { sha256: hash });
82
72
  if (uploadedAsset) {
83
73
  console.log(
@@ -101,6 +91,7 @@ export class CachedStorageDriver implements StorageDriver {
101
91
  JSON.stringify({ assets: { ...this.assetManifest.assets } }, null, 2),
102
92
  "utf-8"
103
93
  );
94
+ console.log(`${file.fileName} uploaded at ${uri}`)
104
95
 
105
96
  return uri;
106
97
  }