@solana-mobile/dapp-store-cli 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/CliSetup.js CHANGED
@@ -536,17 +536,12 @@ publishCommand.command("submit").description("Submit a new app to the Solana Mob
536
536
  ];
537
537
  case 1:
538
538
  _state.sent();
539
- return [
540
- 4,
541
- checkSubmissionNetwork(url)
542
- ];
543
- case 2:
544
- _state.sent();
539
+ checkSubmissionNetwork(url);
545
540
  return [
546
541
  4,
547
542
  loadPublishDetails(Constants.getConfigFilePath())
548
543
  ];
549
- case 3:
544
+ case 2:
550
545
  config = _state.sent();
551
546
  if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
552
547
  throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.");
@@ -554,8 +549,32 @@ publishCommand.command("submit").description("Submit a new app to the Solana Mob
554
549
  signer = parseKeypair(keypair);
555
550
  if (!signer) return [
556
551
  3,
557
- 5
552
+ 7
553
+ ];
554
+ if (!(config.lastUpdatedVersionOnStore != null && config.lastSubmittedVersionOnChain.address != null)) return [
555
+ 3,
556
+ 4
558
557
  ];
558
+ return [
559
+ 4,
560
+ publishUpdateCommand({
561
+ appMintAddress: appMintAddress,
562
+ releaseMintAddress: releaseMintAddress,
563
+ signer: signer,
564
+ url: url,
565
+ dryRun: dryRun,
566
+ compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies,
567
+ requestorIsAuthorized: requestorIsAuthorized,
568
+ critical: false
569
+ })
570
+ ];
571
+ case 3:
572
+ _state.sent();
573
+ return [
574
+ 3,
575
+ 6
576
+ ];
577
+ case 4:
559
578
  return [
560
579
  4,
561
580
  publishSubmitCommand({
@@ -568,15 +587,17 @@ publishCommand.command("submit").description("Submit a new app to the Solana Mob
568
587
  requestorIsAuthorized: requestorIsAuthorized
569
588
  })
570
589
  ];
571
- case 4:
590
+ case 5:
572
591
  _state.sent();
592
+ _state.label = 6;
593
+ case 6:
573
594
  if (dryRun) {
574
595
  dryRunSuccessMessage();
575
596
  } else {
576
597
  showMessage("Success", "Successfully submitted to the Solana Mobile dApp publisher portal");
577
598
  }
578
- _state.label = 5;
579
- case 5:
599
+ _state.label = 7;
600
+ case 7:
580
601
  return [
581
602
  2
582
603
  ];
@@ -616,17 +637,12 @@ publishCommand.command("update").description("Update an existing app on the Sola
616
637
  ];
617
638
  case 1:
618
639
  _state.sent();
619
- return [
620
- 4,
621
- checkSubmissionNetwork(url)
622
- ];
623
- case 2:
624
- _state.sent();
640
+ checkSubmissionNetwork(url);
625
641
  return [
626
642
  4,
627
643
  loadPublishDetails(Constants.getConfigFilePath())
628
644
  ];
629
- case 3:
645
+ case 2:
630
646
  config = _state.sent();
631
647
  if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
632
648
  throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.");
@@ -634,7 +650,7 @@ publishCommand.command("update").description("Update an existing app on the Sola
634
650
  signer = parseKeypair(keypair);
635
651
  if (!signer) return [
636
652
  3,
637
- 5
653
+ 4
638
654
  ];
639
655
  return [
640
656
  4,
@@ -649,15 +665,15 @@ publishCommand.command("update").description("Update an existing app on the Sola
649
665
  critical: critical
650
666
  })
651
667
  ];
652
- case 4:
668
+ case 3:
653
669
  _state.sent();
654
670
  if (dryRun) {
655
671
  dryRunSuccessMessage();
656
672
  } else {
657
673
  showMessage("Success", "dApp successfully updated on the publisher portal");
658
674
  }
659
- _state.label = 5;
660
- case 5:
675
+ _state.label = 4;
676
+ case 4:
661
677
  return [
662
678
  2
663
679
  ];
@@ -697,17 +713,12 @@ publishCommand.command("remove").description("Remove an existing app from the So
697
713
  ];
698
714
  case 1:
699
715
  _state.sent();
700
- return [
701
- 4,
702
- checkSubmissionNetwork(url)
703
- ];
704
- case 2:
705
- _state.sent();
716
+ checkSubmissionNetwork(url);
706
717
  return [
707
718
  4,
708
719
  loadPublishDetails(Constants.getConfigFilePath())
709
720
  ];
710
- case 3:
721
+ case 2:
711
722
  config = _state.sent();
712
723
  if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
713
724
  throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.");
@@ -715,7 +726,7 @@ publishCommand.command("remove").description("Remove an existing app from the So
715
726
  signer = parseKeypair(keypair);
716
727
  if (!signer) return [
717
728
  3,
718
- 5
729
+ 4
719
730
  ];
720
731
  return [
721
732
  4,
@@ -729,15 +740,15 @@ publishCommand.command("remove").description("Remove an existing app from the So
729
740
  critical: critical
730
741
  })
731
742
  ];
732
- case 4:
743
+ case 3:
733
744
  _state.sent();
734
745
  if (dryRun) {
735
746
  dryRunSuccessMessage();
736
747
  } else {
737
748
  showMessage("Success", "dApp successfully removed from the publisher portal");
738
749
  }
739
- _state.label = 5;
740
- case 5:
750
+ _state.label = 4;
751
+ case 4:
741
752
  return [
742
753
  2
743
754
  ];
@@ -777,17 +788,12 @@ publishCommand.command("support <request_details>").description("Submit a suppor
777
788
  ];
778
789
  case 1:
779
790
  _state.sent();
780
- return [
781
- 4,
782
- checkSubmissionNetwork(url)
783
- ];
784
- case 2:
785
- _state.sent();
791
+ checkSubmissionNetwork(url);
786
792
  return [
787
793
  4,
788
794
  loadPublishDetails(Constants.getConfigFilePath())
789
795
  ];
790
- case 3:
796
+ case 2:
791
797
  config = _state.sent();
792
798
  if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
793
799
  throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.");
@@ -795,7 +801,7 @@ publishCommand.command("support <request_details>").description("Submit a suppor
795
801
  signer = parseKeypair(keypair);
796
802
  if (!signer) return [
797
803
  3,
798
- 5
804
+ 4
799
805
  ];
800
806
  return [
801
807
  4,
@@ -809,15 +815,15 @@ publishCommand.command("support <request_details>").description("Submit a suppor
809
815
  requestDetails: requestDetails
810
816
  })
811
817
  ];
812
- case 4:
818
+ case 3:
813
819
  _state.sent();
814
820
  if (dryRun) {
815
821
  dryRunSuccessMessage();
816
822
  } else {
817
823
  showMessage("Success", "Support request sent successfully");
818
824
  }
819
- _state.label = 5;
820
- case 5:
825
+ _state.label = 4;
826
+ case 4:
821
827
  return [
822
828
  2
823
829
  ];
package/lib/CliUtils.js CHANGED
@@ -159,7 +159,7 @@ export var Constants = function Constants() {
159
159
  "use strict";
160
160
  _class_call_check(this, Constants);
161
161
  };
162
- _define_property(Constants, "CLI_VERSION", "0.8.1");
162
+ _define_property(Constants, "CLI_VERSION", "0.8.2");
163
163
  _define_property(Constants, "CONFIG_FILE_NAME", "config.yaml");
164
164
  _define_property(Constants, "DEFAULT_RPC_DEVNET", "https://api.devnet.solana.com");
165
165
  _define_property(Constants, "DEFAULT_PRIORITY_FEE", 500000);
@@ -185,7 +185,7 @@ export var checkForSelfUpdate = function() {
185
185
  latestVer = new ver.SemVer(updateInfo.latest);
186
186
  currentVer = new ver.SemVer(updateInfo.current);
187
187
  if (latestVer.major > currentVer.major || latestVer.minor > currentVer.minor) {
188
- throw new Error("Please update to the latest version of the dApp Store CLI before proceeding.");
188
+ throw new Error("Please update to the latest version of the dApp Store CLI before proceeding.\nCurrent version is ".concat(currentVer.raw, "\nLatest version is ").concat(latestVer.raw));
189
189
  }
190
190
  return [
191
191
  2
@@ -214,9 +214,7 @@ var createAppNft = function() {
214
214
  2
215
215
  ];
216
216
  case 8:
217
- return [
218
- 2
219
- ];
217
+ throw new Error("Unable to mint app NFT");
220
218
  }
221
219
  });
222
220
  });
@@ -213,9 +213,7 @@ var createPublisherNft = function() {
213
213
  2
214
214
  ];
215
215
  case 8:
216
- return [
217
- 2
218
- ];
216
+ throw new Error("Unable to mint publisher NFT");
219
217
  }
220
218
  });
221
219
  });
@@ -272,7 +270,11 @@ export var createPublisherCommand = function() {
272
270
  ];
273
271
  case 4:
274
272
  return [
275
- 2
273
+ 2,
274
+ {
275
+ publisherAddress: "",
276
+ transactionSignature: ""
277
+ }
276
278
  ];
277
279
  }
278
280
  });
@@ -152,6 +152,8 @@ function _ts_generator(thisArg, body) {
152
152
  }
153
153
  import { createRelease } from "@solana-mobile/dapp-store-publishing-tools";
154
154
  import { Connection, Keypair, PublicKey, sendAndConfirmTransaction } from "@solana/web3.js";
155
+ import fs from "fs";
156
+ import { createHash } from "crypto";
155
157
  import { Constants, getMetaplexInstance, showMessage } from "../../CliUtils.js";
156
158
  import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
157
159
  var createReleaseNft = function() {
@@ -244,9 +246,7 @@ var createReleaseNft = function() {
244
246
  2
245
247
  ];
246
248
  case 8:
247
- return [
248
- 2
249
- ];
249
+ throw new Error("Unable to mint release NFT");
250
250
  }
251
251
  });
252
252
  });
@@ -256,7 +256,7 @@ var createReleaseNft = function() {
256
256
  }();
257
257
  export var createReleaseCommand = function() {
258
258
  var _ref = _async_to_generator(function(param) {
259
- var appMintAddress, buildToolsPath, signer, url, _param_dryRun, dryRun, storageParams, _param_priorityFeeLamports, priorityFeeLamports, connection, _ref, release, app, publisher, _app_address, _ref1, releaseAddress, transactionSignature;
259
+ var appMintAddress, buildToolsPath, signer, url, _param_dryRun, dryRun, storageParams, _param_priorityFeeLamports, priorityFeeLamports, connection, config, apkEntry, mediaBuffer, hash, _config_app_address, _ref, releaseAddress, transactionSignature;
260
260
  return _ts_generator(this, function(_state) {
261
261
  switch(_state.label){
262
262
  case 0:
@@ -267,38 +267,59 @@ export var createReleaseCommand = function() {
267
267
  loadPublishDetailsWithChecks(buildToolsPath)
268
268
  ];
269
269
  case 1:
270
- _ref = _state.sent(), release = _ref.release, app = _ref.app, publisher = _ref.publisher;
271
- if (app.android_package != release.android_details.android_package) {
272
- 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);
270
+ config = _state.sent();
271
+ apkEntry = config.release.files.find(function(asset) {
272
+ return asset.purpose === "install";
273
+ });
274
+ return [
275
+ 4,
276
+ fs.promises.readFile(apkEntry.uri)
277
+ ];
278
+ case 2:
279
+ mediaBuffer = _state.sent();
280
+ hash = createHash("sha256").update(mediaBuffer).digest("base64");
281
+ if (config.lastSubmittedVersionOnChain != null && hash === config.lastSubmittedVersionOnChain.apk_hash) {
282
+ throw new Error("The last created release used the same apk file.");
283
+ }
284
+ if (config.lastSubmittedVersionOnChain != null && config.release.android_details.version_code <= config.lastSubmittedVersionOnChain.version_code) {
285
+ throw new Error("Each release NFT should have higher version code than previous minted release NFT.\nLast released version code is ".concat(config.lastSubmittedVersionOnChain.version_code, ".\nCurrent version code from apk file is ").concat(config.release.android_details.version_code));
286
+ }
287
+ if (config.app.android_package != config.release.android_details.android_package) {
288
+ throw new Error("App package name and release package name do not match.\nApp release specifies " + config.app.android_package + " while release specifies " + config.release.android_details.android_package);
273
289
  }
274
290
  if (!!dryRun) return [
275
291
  3,
276
- 4
292
+ 5
277
293
  ];
278
294
  return [
279
295
  4,
280
296
  createReleaseNft({
281
- appMintAddress: (_app_address = app.address) !== null && _app_address !== void 0 ? _app_address : appMintAddress,
297
+ appMintAddress: (_config_app_address = config.app.address) !== null && _config_app_address !== void 0 ? _config_app_address : appMintAddress,
282
298
  connection: connection,
283
299
  publisher: signer,
284
- releaseDetails: _object_spread({}, release),
285
- appDetails: app,
286
- publisherDetails: publisher,
300
+ releaseDetails: _object_spread({}, config.release),
301
+ appDetails: config.app,
302
+ publisherDetails: config.publisher,
287
303
  storageParams: storageParams,
288
304
  priorityFeeLamports: priorityFeeLamports
289
305
  })
290
306
  ];
291
- case 2:
292
- _ref1 = _state.sent(), releaseAddress = _ref1.releaseAddress, transactionSignature = _ref1.transactionSignature;
307
+ case 3:
308
+ _ref = _state.sent(), releaseAddress = _ref.releaseAddress, transactionSignature = _ref.transactionSignature;
293
309
  return [
294
310
  4,
295
311
  writeToPublishDetails({
296
312
  release: {
297
313
  address: releaseAddress
314
+ },
315
+ lastSubmittedVersionOnChain: {
316
+ address: releaseAddress,
317
+ version_code: config.release.android_details.version_code,
318
+ apk_hash: hash
298
319
  }
299
320
  })
300
321
  ];
301
- case 3:
322
+ case 4:
302
323
  _state.sent();
303
324
  return [
304
325
  2,
@@ -307,7 +328,7 @@ export var createReleaseCommand = function() {
307
328
  transactionSignature: transactionSignature
308
329
  }
309
330
  ];
310
- case 4:
331
+ case 5:
311
332
  return [
312
333
  2
313
334
  ];
@@ -126,10 +126,10 @@ import { Connection } from "@solana/web3.js";
126
126
  import { publishSubmit } from "@solana-mobile/dapp-store-publishing-tools";
127
127
  import nacl from "tweetnacl";
128
128
  import { checkMintedStatus, showMessage } from "../../CliUtils.js";
129
- import { loadPublishDetailsWithChecks } from "../../config/PublishDetails.js";
129
+ import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
130
130
  export var publishSubmitCommand = function() {
131
131
  var _ref = _async_to_generator(function(param) {
132
- var appMintAddress, releaseMintAddress, signer, url, _param_dryRun, dryRun, _param_compliesWithSolanaDappStorePolicies, compliesWithSolanaDappStorePolicies, _param_requestorIsAuthorized, requestorIsAuthorized, connection, _ref, publisherDetails, appDetails, releaseDetails, solanaMobileDappPublisherPortalDetails, sign, pubAddr, appAddr, releaseAddr;
132
+ var appMintAddress, releaseMintAddress, signer, url, _param_dryRun, dryRun, _param_compliesWithSolanaDappStorePolicies, compliesWithSolanaDappStorePolicies, _param_requestorIsAuthorized, requestorIsAuthorized, connection, _ref, publisherDetails, appDetails, releaseDetails, solanaMobileDappPublisherPortalDetails, lastUpdatedVersionOnStore, sign, pubAddr, appAddr, releaseAddr;
133
133
  return _ts_generator(this, function(_state) {
134
134
  switch(_state.label){
135
135
  case 0:
@@ -152,13 +152,16 @@ export var publishSubmitCommand = function() {
152
152
  loadPublishDetailsWithChecks()
153
153
  ];
154
154
  case 1:
155
- _ref = _state.sent(), publisherDetails = _ref.publisher, appDetails = _ref.app, releaseDetails = _ref.release, solanaMobileDappPublisherPortalDetails = _ref.solana_mobile_dapp_publisher_portal;
155
+ _ref = _state.sent(), publisherDetails = _ref.publisher, appDetails = _ref.app, releaseDetails = _ref.release, solanaMobileDappPublisherPortalDetails = _ref.solana_mobile_dapp_publisher_portal, lastUpdatedVersionOnStore = _ref.lastUpdatedVersionOnStore;
156
156
  sign = function(buf) {
157
157
  return nacl.sign(buf, signer.secretKey);
158
158
  };
159
159
  pubAddr = publisherDetails.address;
160
160
  appAddr = appMintAddress !== null && appMintAddress !== void 0 ? appMintAddress : appDetails.address;
161
161
  releaseAddr = releaseMintAddress !== null && releaseMintAddress !== void 0 ? releaseMintAddress : releaseDetails.address;
162
+ if (lastUpdatedVersionOnStore != null && releaseAddr === lastUpdatedVersionOnStore.address) {
163
+ throw new Error("You've already submitted this version for review.");
164
+ }
162
165
  return [
163
166
  4,
164
167
  checkMintedStatus(connection, pubAddr, appAddr, releaseAddr)
@@ -180,6 +183,16 @@ export var publishSubmitCommand = function() {
180
183
  }, dryRun)
181
184
  ];
182
185
  case 3:
186
+ _state.sent();
187
+ return [
188
+ 4,
189
+ writeToPublishDetails({
190
+ lastUpdatedVersionOnStore: {
191
+ address: releaseAddr
192
+ }
193
+ })
194
+ ];
195
+ case 4:
183
196
  _state.sent();
184
197
  return [
185
198
  2
@@ -126,10 +126,10 @@ import { Connection } from "@solana/web3.js";
126
126
  import { publishUpdate } from "@solana-mobile/dapp-store-publishing-tools";
127
127
  import { checkMintedStatus, showMessage } from "../../CliUtils.js";
128
128
  import nacl from "tweetnacl";
129
- import { loadPublishDetailsWithChecks } from "../../config/PublishDetails.js";
129
+ import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
130
130
  export var publishUpdateCommand = function() {
131
131
  var _ref = _async_to_generator(function(param) {
132
- var appMintAddress, releaseMintAddress, signer, url, _param_dryRun, dryRun, _param_compliesWithSolanaDappStorePolicies, compliesWithSolanaDappStorePolicies, _param_requestorIsAuthorized, requestorIsAuthorized, _param_critical, critical, connection, _ref, publisherDetails, appDetails, releaseDetails, solanaMobileDappPublisherPortalDetails, sign, pubAddr, appAddr, releaseAddr;
132
+ var appMintAddress, releaseMintAddress, signer, url, _param_dryRun, dryRun, _param_compliesWithSolanaDappStorePolicies, compliesWithSolanaDappStorePolicies, _param_requestorIsAuthorized, requestorIsAuthorized, _param_critical, critical, connection, _ref, publisherDetails, appDetails, releaseDetails, solanaMobileDappPublisherPortalDetails, lastUpdatedVersionOnStore, sign, pubAddr, appAddr, releaseAddr;
133
133
  return _ts_generator(this, function(_state) {
134
134
  switch(_state.label){
135
135
  case 0:
@@ -152,13 +152,16 @@ export var publishUpdateCommand = function() {
152
152
  loadPublishDetailsWithChecks()
153
153
  ];
154
154
  case 1:
155
- _ref = _state.sent(), publisherDetails = _ref.publisher, appDetails = _ref.app, releaseDetails = _ref.release, solanaMobileDappPublisherPortalDetails = _ref.solana_mobile_dapp_publisher_portal;
155
+ _ref = _state.sent(), publisherDetails = _ref.publisher, appDetails = _ref.app, releaseDetails = _ref.release, solanaMobileDappPublisherPortalDetails = _ref.solana_mobile_dapp_publisher_portal, lastUpdatedVersionOnStore = _ref.lastUpdatedVersionOnStore;
156
156
  sign = function(buf) {
157
157
  return nacl.sign(buf, signer.secretKey);
158
158
  };
159
159
  pubAddr = publisherDetails.address;
160
160
  appAddr = appMintAddress !== null && appMintAddress !== void 0 ? appMintAddress : appDetails.address;
161
161
  releaseAddr = releaseMintAddress !== null && releaseMintAddress !== void 0 ? releaseMintAddress : releaseDetails.address;
162
+ if (lastUpdatedVersionOnStore != null && releaseAddr === lastUpdatedVersionOnStore.address) {
163
+ throw new Error("You've already submitted this version for review.");
164
+ }
162
165
  return [
163
166
  4,
164
167
  checkMintedStatus(connection, pubAddr, appAddr, releaseAddr)
@@ -181,6 +184,16 @@ export var publishUpdateCommand = function() {
181
184
  }, dryRun)
182
185
  ];
183
186
  case 3:
187
+ _state.sent();
188
+ return [
189
+ 4,
190
+ writeToPublishDetails({
191
+ lastUpdatedVersionOnStore: {
192
+ address: releaseAddr
193
+ }
194
+ })
195
+ ];
196
+ case 4:
184
197
  _state.sent();
185
198
  return [
186
199
  2
@@ -265,7 +265,7 @@ export var loadPublishDetails = function() {
265
265
  }();
266
266
  export var loadPublishDetailsWithChecks = function() {
267
267
  var _ref = _async_to_generator(function() {
268
- var buildToolsDir, _config_publisher_media_find, _config_publisher_media, _config_app_media_find, _config_app_media, _config_release_media_find, _config_release_media, _config_release_media1, config, apkEntry, apkPath, _, publisherIcon, iconPath, iconBuffer, appIcon, iconPath1, iconBuffer1, releaseIcon, iconPath2, screenshots, googlePkg, pkgCompare;
268
+ var buildToolsDir, _config_publisher_media_find, _config_publisher_media, _config_app_media_find, _config_app_media, _config_release_media_find, _config_release_media, _config_release_media1, config, apkEntry, apkPath, _, publisherIcon, iconPath, iconBuffer, appIcon, iconPath1, iconBuffer1, releaseIcon, iconPath2, previewMediaFiles, googlePkg, pkgCompare;
269
269
  var _arguments = arguments;
270
270
  return _ts_generator(this, function(_state) {
271
271
  switch(_state.label){
@@ -368,11 +368,11 @@ export var loadPublishDetailsWithChecks = function() {
368
368
  throw new Error("Invalid media path or file type: ".concat(item.uri, ". Please ensure the file is a jpeg, png, or webp file."));
369
369
  }
370
370
  });
371
- screenshots = (_config_release_media1 = config.release.media) === null || _config_release_media1 === void 0 ? void 0 : _config_release_media1.filter(function(asset) {
372
- return asset.purpose === "screenshot";
371
+ previewMediaFiles = (_config_release_media1 = config.release.media) === null || _config_release_media1 === void 0 ? void 0 : _config_release_media1.filter(function(asset) {
372
+ return asset.purpose === "screenshot" || asset.purpose === "video";
373
373
  });
374
- if (screenshots.length < 4) {
375
- showMessage("Screenshots requirements changing in version 0.9.0", "At least 4 screenshots are required for publishing a new release. Found only ".concat(screenshots.length), "warning");
374
+ if (previewMediaFiles.length < 4) {
375
+ showMessage("Preview media requirements changing in version 0.9.0", "At least 4 screenshots or videos are required for publishing a new release. Found only ".concat(previewMediaFiles.length), "warning");
376
376
  }
377
377
  validateLocalizableResources(config);
378
378
  googlePkg = config.solana_mobile_dapp_publisher_portal.google_store_package;
@@ -490,7 +490,9 @@ var getAndroidDetails = function() {
490
490
  versionCode = new RegExp(AaptPrefixes.verCodePrefix + AaptPrefixes.quoteRegex).exec(stdout);
491
491
  versionName = new RegExp(AaptPrefixes.verNamePrefix + AaptPrefixes.quoteRegex).exec(stdout);
492
492
  minSdk = new RegExp(AaptPrefixes.sdkPrefix + AaptPrefixes.quoteRegex).exec(stdout);
493
- permissions = _to_consumable_array(stdout.matchAll(/uses-permission: name='(.*)'/g));
493
+ permissions = _to_consumable_array(stdout.matchAll(/uses-permission: name='(.*)'/g)).flatMap(function(permission) {
494
+ return permission[1];
495
+ });
494
496
  locales = new RegExp(AaptPrefixes.localePrefix + AaptPrefixes.quoteNonLazyRegex).exec(stdout);
495
497
  isDebuggable = new RegExp(AaptPrefixes.debuggableApkPrefix).exec(stdout);
496
498
  if (isDebuggable != null) {
@@ -503,6 +505,15 @@ var getAndroidDetails = function() {
503
505
  "en-US"
504
506
  ].concat(localesSrc.split("' '").slice(1));
505
507
  }
508
+ if (permissions.includes("android.permission.INSTALL_PACKAGES") || permissions.includes("android.permission.DELETE_PACKAGES")) {
509
+ showMessage("App requests system app install/delete permission", "Your app requests system install/delete permission which is managed by Solana dApp Store.\nThis app will be not approved for listing on Solana dApp Store.", "error");
510
+ }
511
+ if (permissions.includes("android.permission.REQUEST_INSTALL_PACKAGES") || permissions.includes("android.permission.REQUEST_DELETE_PACKAGES")) {
512
+ showMessage("App requests install or delete permission", "App will be subject to additional security reviews for listing on Solana dApp Store and processing time may be beyond regular review time", "warning");
513
+ }
514
+ if (permissions.includes("com.solanamobile.seedvault.ACCESS_SEED_VAULT")) {
515
+ showMessage("App requests Seed Vault permission", "If this is not a wallet application, your app maybe rejected from listing on Solana dApp Store.", "warning");
516
+ }
506
517
  if (localeArray.length >= 60) {
507
518
  showMessage("The bundle apk claims supports for following locales", "Claim for supported locales::\n" + localeArray + "\nIf this release does not support all these locales the release may be rejected" + "\nSee details at https://developer.android.com/guide/topics/resources/multilingual-support#design for configuring the supported locales", "warning");
508
519
  }
@@ -519,9 +530,7 @@ var getAndroidDetails = function() {
519
530
  case 2:
520
531
  return [
521
532
  2,
522
- (_tmp.cert_fingerprint = _state.sent(), _tmp.permissions = permissions.flatMap(function(permission) {
523
- return permission[1];
524
- }), _tmp.locales = localeArray, _tmp)
533
+ (_tmp.cert_fingerprint = _state.sent(), _tmp.permissions = permissions, _tmp.locales = localeArray, _tmp)
525
534
  ];
526
535
  case 3:
527
536
  e = _state.sent();
@@ -579,11 +588,11 @@ export var extractCertFingerprint = function() {
579
588
  }();
580
589
  export var writeToPublishDetails = function() {
581
590
  var _ref = _async_to_generator(function(param) {
582
- var publisher, app, release, currentConfig, _publisher_address, _app_address, _release_address, newConfig;
591
+ var publisher, app, release, lastSubmittedVersionOnChain, lastUpdatedVersionOnStore, currentConfig, _publisher_address, _app_address, _release_address, newConfig;
583
592
  return _ts_generator(this, function(_state) {
584
593
  switch(_state.label){
585
594
  case 0:
586
- publisher = param.publisher, app = param.app, release = param.release;
595
+ publisher = param.publisher, app = param.app, release = param.release, lastSubmittedVersionOnChain = param.lastSubmittedVersionOnChain, lastUpdatedVersionOnStore = param.lastUpdatedVersionOnStore;
587
596
  return [
588
597
  4,
589
598
  loadPublishDetailsWithChecks()
@@ -602,7 +611,9 @@ export var writeToPublishDetails = function() {
602
611
  release: _object_spread_props(_object_spread({}, currentConfig.release), {
603
612
  address: (_release_address = release === null || release === void 0 ? void 0 : release.address) !== null && _release_address !== void 0 ? _release_address : currentConfig.release.address
604
613
  }),
605
- solana_mobile_dapp_publisher_portal: currentConfig.solana_mobile_dapp_publisher_portal
614
+ solana_mobile_dapp_publisher_portal: currentConfig.solana_mobile_dapp_publisher_portal,
615
+ lastSubmittedVersionOnChain: lastSubmittedVersionOnChain !== null && lastSubmittedVersionOnChain !== void 0 ? lastSubmittedVersionOnChain : currentConfig.lastSubmittedVersionOnChain,
616
+ lastUpdatedVersionOnStore: lastUpdatedVersionOnStore !== null && lastUpdatedVersionOnStore !== void 0 ? lastUpdatedVersionOnStore : currentConfig.lastUpdatedVersionOnStore
606
617
  };
607
618
  fs.writeFileSync(Constants.getConfigFilePath(), dump(newConfig, {
608
619
  lineWidth: -1
@@ -1 +1 @@
1
- {"publisher":{"name":"<<YOUR_PUBLISHER_NAME>>","address":"","website":"<<URL_OF_PUBLISHER_WEBSITE>>","email":"<<EMAIL_ADDRESS_TO_CONTACT_PUBLISHER>>","media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_PUBLISHER_ICON>>"}]},"app":{"name":"<<APP_NAME>>","address":"","android_package":"<<ANDROID_PACKAGE_NAME>>","urls":{"license_url":"<<URL_OF_APP_LICENSE_OR_TERMS_OF_SERVICE>>","copyright_url":"<<URL_OF_COPYRIGHT_DETAILS_FOR_APP>>","privacy_policy_url":"<<URL_OF_APP_PRIVACY_POLICY>>","website":"<<URL_OF_APP_WEBSITE>>"},"media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_APP_ICON>>"}]},"release":{"address":"","media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_RELEASE_ICON>>"},{"purpose":"screenshot","uri":"<<RELATIVE_PATH_TO_SCREENSHOT>>"}],"files":[{"purpose":"install","uri":"<<RELATIVE_PATH_TO_APK>>"}],"catalog":{"en-US":{"name":"<<APP_NAME>>","short_description":"<<SHORT_APP_DESCRIPTION>>","long_description":"<<LONG_APP_DESCRIPTION>>","new_in_version":"<<WHATS_NEW_IN_THIS_VERSION>>","saga_features":"<<ANY_FEATURES_ONLY_AVAILBLE_WHEN_RUNNING_ON_SAGA>>"}}},"solana_mobile_dapp_publisher_portal":{"google_store_package":"<<ANDROID_PACKAGE_NAME_OF_GOOGLE_PLAY_STORE_VERSION_IF_DIFFERENT>>","testing_instructions":"<<TESTING_INSTRUCTIONS>>"}}
1
+ {"publisher":{"name":"<<YOUR_PUBLISHER_NAME>>","address":"","website":"<<URL_OF_PUBLISHER_WEBSITE>>","email":"<<EMAIL_ADDRESS_TO_CONTACT_PUBLISHER>>","media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_PUBLISHER_ICON>>"}]},"app":{"name":"<<APP_NAME>>","address":"","android_package":"<<ANDROID_PACKAGE_NAME>>","urls":{"license_url":"<<URL_OF_APP_LICENSE_OR_TERMS_OF_SERVICE>>","copyright_url":"<<URL_OF_COPYRIGHT_DETAILS_FOR_APP>>","privacy_policy_url":"<<URL_OF_APP_PRIVACY_POLICY>>","website":"<<URL_OF_APP_WEBSITE>>"},"media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_APP_ICON>>"}]},"release":{"address":"","media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_RELEASE_ICON>>"},{"purpose":"screenshot","uri":"<<RELATIVE_PATH_TO_SCREENSHOT1>>"},{"purpose":"screenshot","uri":"<<RELATIVE_PATH_TO_SCREENSHOT2>>"},{"purpose":"screenshot","uri":"<<RELATIVE_PATH_TO_SCREENSHOT3>>"},{"purpose":"screenshot","uri":"<<RELATIVE_PATH_TO_SCREENSHOT4>>"},{"purpose":"video","uri":"<<RELATIVE_PATH_TO_VIDEO1>>"}],"files":[{"purpose":"install","uri":"<<RELATIVE_PATH_TO_APK>>"}],"catalog":{"en-US":{"name":"<<APP_NAME>>","short_description":"<<SHORT_APP_DESCRIPTION>>","long_description":"<<LONG_APP_DESCRIPTION>>","new_in_version":"<<WHATS_NEW_IN_THIS_VERSION>>","saga_features":"<<ANY_FEATURES_ONLY_AVAILBLE_WHEN_RUNNING_ON_SAGA>>"}}},"solana_mobile_dapp_publisher_portal":{"google_store_package":"<<ANDROID_PACKAGE_NAME_OF_GOOGLE_PLAY_STORE_VERSION_IF_DIFFERENT>>","testing_instructions":"<<TESTING_INSTRUCTIONS>>"}}
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana-mobile/dapp-store-cli",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -53,7 +53,7 @@
53
53
  "dependencies": {
54
54
  "@aws-sdk/client-s3": "^3.321.1",
55
55
  "@metaplex-foundation/js-plugin-aws": "^0.18.3",
56
- "@solana-mobile/dapp-store-publishing-tools": "workspace:0.8.1",
56
+ "@solana-mobile/dapp-store-publishing-tools": "workspace:0.8.2",
57
57
  "@solana/web3.js": "1.68.0",
58
58
  "@types/semver": "^7.3.13",
59
59
  "ajv": "^8.11.0",
@@ -24,7 +24,15 @@ release:
24
24
  - purpose: icon
25
25
  uri: <<RELATIVE_PATH_TO_RELEASE_ICON>>
26
26
  - purpose: screenshot
27
- uri: <<RELATIVE_PATH_TO_SCREENSHOT>>
27
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT1>>
28
+ - purpose: screenshot
29
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT2>>
30
+ - purpose: screenshot
31
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT3>>
32
+ - purpose: screenshot
33
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT4>>
34
+ - purpose: video
35
+ uri: <<RELATIVE_PATH_TO_VIDEO1>>
28
36
  files:
29
37
  - purpose: install
30
38
  uri: <<RELATIVE_PATH_TO_APK>>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana-mobile/dapp-store-cli",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -46,7 +46,7 @@
46
46
  "dependencies": {
47
47
  "@aws-sdk/client-s3": "^3.321.1",
48
48
  "@metaplex-foundation/js-plugin-aws": "^0.18.3",
49
- "@solana-mobile/dapp-store-publishing-tools": "0.8.1",
49
+ "@solana-mobile/dapp-store-publishing-tools": "0.8.2",
50
50
  "@solana/web3.js": "1.68.0",
51
51
  "@types/semver": "^7.3.13",
52
52
  "ajv": "^8.11.0",
package/src/CliSetup.ts CHANGED
@@ -310,7 +310,7 @@ publishCommand
310
310
  }) => {
311
311
  await tryWithErrorMessage(async () => {
312
312
  await checkForSelfUpdate();
313
- await checkSubmissionNetwork(url);
313
+ checkSubmissionNetwork(url);
314
314
 
315
315
  const config = await loadPublishDetails(Constants.getConfigFilePath());
316
316
 
@@ -320,15 +320,28 @@ publishCommand
320
320
 
321
321
  const signer = parseKeypair(keypair);
322
322
  if (signer) {
323
- await publishSubmitCommand({
324
- appMintAddress,
325
- releaseMintAddress,
326
- signer,
327
- url,
328
- dryRun,
329
- compliesWithSolanaDappStorePolicies,
330
- requestorIsAuthorized,
331
- });
323
+ if (config.lastUpdatedVersionOnStore != null && config.lastSubmittedVersionOnChain.address != null) {
324
+ await publishUpdateCommand({
325
+ appMintAddress: appMintAddress,
326
+ releaseMintAddress: releaseMintAddress,
327
+ signer: signer,
328
+ url: url,
329
+ dryRun: dryRun,
330
+ compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies,
331
+ requestorIsAuthorized: requestorIsAuthorized,
332
+ critical: false,
333
+ });
334
+ } else {
335
+ await publishSubmitCommand({
336
+ appMintAddress: appMintAddress,
337
+ releaseMintAddress: releaseMintAddress,
338
+ signer: signer,
339
+ url: url,
340
+ dryRun: dryRun,
341
+ compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies,
342
+ requestorIsAuthorized: requestorIsAuthorized,
343
+ });
344
+ }
332
345
 
333
346
  if (dryRun) {
334
347
  dryRunSuccessMessage()
@@ -384,7 +397,7 @@ publishCommand
384
397
  }) => {
385
398
  await tryWithErrorMessage(async () => {
386
399
  await checkForSelfUpdate();
387
- await checkSubmissionNetwork(url);
400
+ checkSubmissionNetwork(url);
388
401
 
389
402
  const config = await loadPublishDetails(Constants.getConfigFilePath())
390
403
 
@@ -454,7 +467,7 @@ publishCommand
454
467
  }) => {
455
468
  await tryWithErrorMessage(async () => {
456
469
  await checkForSelfUpdate();
457
- await checkSubmissionNetwork(url);
470
+ checkSubmissionNetwork(url);
458
471
 
459
472
  const config = await loadPublishDetails(Constants.getConfigFilePath())
460
473
 
@@ -517,7 +530,7 @@ publishCommand
517
530
  ) => {
518
531
  await tryWithErrorMessage(async () => {
519
532
  await checkForSelfUpdate();
520
- await checkSubmissionNetwork(url);
533
+ checkSubmissionNetwork(url);
521
534
 
522
535
  const config = await loadPublishDetails(Constants.getConfigFilePath())
523
536
 
package/src/CliUtils.ts CHANGED
@@ -18,7 +18,7 @@ import { awsStorage } from "@metaplex-foundation/js-plugin-aws";
18
18
  import { S3StorageManager } from "./config/index.js";
19
19
 
20
20
  export class Constants {
21
- static CLI_VERSION = "0.8.1";
21
+ static CLI_VERSION = "0.8.2";
22
22
  static CONFIG_FILE_NAME = "config.yaml";
23
23
  static DEFAULT_RPC_DEVNET = "https://api.devnet.solana.com";
24
24
  static DEFAULT_PRIORITY_FEE = 500000;
@@ -42,7 +42,7 @@ export const checkForSelfUpdate = async () => {
42
42
  latestVer.minor > currentVer.minor
43
43
  ) {
44
44
  throw new Error(
45
- "Please update to the latest version of the dApp Store CLI before proceeding."
45
+ `Please update to the latest version of the dApp Store CLI before proceeding.\nCurrent version is ${currentVer.raw}\nLatest version is ${latestVer.raw}`
46
46
  );
47
47
  }
48
48
  };
@@ -11,7 +11,6 @@ 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";
14
- import { isMetaplexFile } from "@metaplex-foundation/js";
15
14
  import { loadPublishDetailsWithChecks } from "../config/PublishDetails.js";
16
15
 
17
16
  export const validateCommand = async ({
@@ -71,6 +71,7 @@ const createAppNft = async (
71
71
  }
72
72
  }
73
73
  }
74
+ throw new Error("Unable to mint app NFT");
74
75
  };
75
76
 
76
77
  type CreateAppCommandInput = {
@@ -62,6 +62,7 @@ const createPublisherNft = async (
62
62
  }
63
63
  }
64
64
  }
65
+ throw new Error("Unable to mint publisher NFT");
65
66
  };
66
67
 
67
68
  export const createPublisherCommand = async ({
@@ -96,4 +97,6 @@ export const createPublisherCommand = async ({
96
97
 
97
98
  return { publisherAddress, transactionSignature };
98
99
  }
100
+
101
+ return { publisherAddress: "", transactionSignature: "" };
99
102
  };
@@ -10,12 +10,14 @@ import {
10
10
  PublicKey,
11
11
  sendAndConfirmTransaction,
12
12
  } from "@solana/web3.js";
13
+ import fs from "fs";
14
+ import { createHash } from "crypto";
13
15
  import {
14
16
  Constants,
15
17
  getMetaplexInstance,
16
18
  showMessage
17
19
  } from "../../CliUtils.js";
18
- import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
20
+ import { PublishDetails, loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
19
21
 
20
22
  type CreateReleaseCommandInput = {
21
23
  appMintAddress: string;
@@ -89,6 +91,7 @@ const createReleaseNft = async ({
89
91
  }
90
92
  }
91
93
  }
94
+ throw new Error("Unable to mint release NFT");
92
95
  };
93
96
 
94
97
  export const createReleaseCommand = async ({
@@ -102,28 +105,49 @@ export const createReleaseCommand = async ({
102
105
  }: CreateReleaseCommandInput) => {
103
106
  const connection = new Connection(url);
104
107
 
105
- const { release, app, publisher } = await loadPublishDetailsWithChecks(buildToolsPath);
108
+ const config = await loadPublishDetailsWithChecks(buildToolsPath);
106
109
 
110
+ const apkEntry = config.release.files.find(
111
+ (asset: PublishDetails["release"]["files"][0]) => asset.purpose === "install"
112
+ )!;
113
+ const mediaBuffer = await fs.promises.readFile(apkEntry.uri);
114
+ const hash = createHash("sha256").update(mediaBuffer).digest("base64");
107
115
 
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)
116
+ if (config.lastSubmittedVersionOnChain != null && hash === config.lastSubmittedVersionOnChain.apk_hash) {
117
+ throw new Error(`The last created release used the same apk file.`);
118
+ }
119
+
120
+ if (config.lastSubmittedVersionOnChain != null && config.release.android_details.version_code <= config.lastSubmittedVersionOnChain.version_code) {
121
+ throw new Error(`Each release NFT should have higher version code than previous minted release NFT.\nLast released version code is ${config.lastSubmittedVersionOnChain.version_code}.\nCurrent version code from apk file is ${config.release.android_details.version_code}`);
122
+ }
123
+
124
+ if (config.app.android_package != config.release.android_details.android_package) {
125
+ throw new Error("App package name and release package name do not match.\nApp release specifies " + config.app.android_package + " while release specifies " + config.release.android_details.android_package)
110
126
  }
111
127
 
112
128
  if (!dryRun) {
113
129
  const { releaseAddress, transactionSignature } = await createReleaseNft({
114
- appMintAddress: app.address ?? appMintAddress,
130
+ appMintAddress: config.app.address ?? appMintAddress,
115
131
  connection,
116
132
  publisher: signer,
117
133
  releaseDetails: {
118
- ...release,
134
+ ...config.release,
119
135
  },
120
- appDetails: app,
121
- publisherDetails: publisher,
136
+ appDetails: config.app,
137
+ publisherDetails: config.publisher,
122
138
  storageParams: storageParams,
123
139
  priorityFeeLamports: priorityFeeLamports,
124
140
  });
125
141
 
126
- await writeToPublishDetails({ release: { address: releaseAddress }, });
142
+ await writeToPublishDetails(
143
+ {
144
+ release: { address: releaseAddress },
145
+ lastSubmittedVersionOnChain: {
146
+ address: releaseAddress,
147
+ version_code: config.release.android_details.version_code,
148
+ apk_hash: hash,
149
+ }
150
+ });
127
151
 
128
152
  return { releaseAddress, transactionSignature };
129
153
  }
@@ -4,7 +4,7 @@ import { publishSubmit } from "@solana-mobile/dapp-store-publishing-tools";
4
4
  import nacl from "tweetnacl";
5
5
  import { checkMintedStatus, showMessage } from "../../CliUtils.js";
6
6
  import { Buffer } from "buffer";
7
- import { loadPublishDetailsWithChecks } from "../../config/PublishDetails.js";
7
+ import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
8
8
 
9
9
  type PublishSubmitCommandInput = {
10
10
  appMintAddress: string;
@@ -49,6 +49,7 @@ export const publishSubmitCommand = async ({
49
49
  app: appDetails,
50
50
  release: releaseDetails,
51
51
  solana_mobile_dapp_publisher_portal: solanaMobileDappPublisherPortalDetails,
52
+ lastUpdatedVersionOnStore: lastUpdatedVersionOnStore,
52
53
  } = await loadPublishDetailsWithChecks();
53
54
 
54
55
  const sign = ((buf: Buffer) =>
@@ -58,6 +59,10 @@ export const publishSubmitCommand = async ({
58
59
  const appAddr = appMintAddress ?? appDetails.address;
59
60
  const releaseAddr = releaseMintAddress ?? releaseDetails.address;
60
61
 
62
+ if (lastUpdatedVersionOnStore != null && releaseAddr === lastUpdatedVersionOnStore.address) {
63
+ throw new Error(`You've already submitted this version for review.`);
64
+ }
65
+
61
66
  await checkMintedStatus(connection, pubAddr, appAddr, releaseAddr);
62
67
 
63
68
  await publishSubmit(
@@ -72,4 +77,9 @@ export const publishSubmitCommand = async ({
72
77
  },
73
78
  dryRun
74
79
  );
80
+
81
+ await writeToPublishDetails(
82
+ {
83
+ lastUpdatedVersionOnStore: { address: releaseAddr }
84
+ });
75
85
  };
@@ -3,7 +3,7 @@ import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publish
3
3
  import { publishUpdate } from "@solana-mobile/dapp-store-publishing-tools";
4
4
  import { checkMintedStatus, showMessage } from "../../CliUtils.js";
5
5
  import nacl from "tweetnacl";
6
- import { loadPublishDetailsWithChecks } from "../../config/PublishDetails.js";
6
+ import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
7
7
 
8
8
  type PublishUpdateCommandInput = {
9
9
  appMintAddress: string;
@@ -51,6 +51,7 @@ export const publishUpdateCommand = async ({
51
51
  app: appDetails,
52
52
  release: releaseDetails,
53
53
  solana_mobile_dapp_publisher_portal: solanaMobileDappPublisherPortalDetails,
54
+ lastUpdatedVersionOnStore: lastUpdatedVersionOnStore
54
55
  } = await loadPublishDetailsWithChecks();
55
56
 
56
57
  const sign = ((buf: Buffer) =>
@@ -60,6 +61,10 @@ export const publishUpdateCommand = async ({
60
61
  const appAddr = appMintAddress ?? appDetails.address;
61
62
  const releaseAddr = releaseMintAddress ?? releaseDetails.address;
62
63
 
64
+ if (lastUpdatedVersionOnStore != null && releaseAddr === lastUpdatedVersionOnStore.address) {
65
+ throw new Error(`You've already submitted this version for review.`);
66
+ }
67
+
63
68
  await checkMintedStatus(connection, pubAddr, appAddr, releaseAddr);
64
69
 
65
70
  await publishUpdate(
@@ -75,4 +80,8 @@ export const publishUpdateCommand = async ({
75
80
  },
76
81
  dryRun
77
82
  );
83
+ await writeToPublishDetails(
84
+ {
85
+ lastUpdatedVersionOnStore: { address: releaseAddr }
86
+ });
78
87
  };
@@ -1,6 +1,8 @@
1
1
  import type {
2
2
  AndroidDetails,
3
3
  App,
4
+ LastSubmittedVersionOnChain,
5
+ LastUpdatedVersionOnStore,
4
6
  Publisher,
5
7
  Release,
6
8
  SolanaMobileDappPublisherPortal
@@ -25,6 +27,8 @@ export interface PublishDetails {
25
27
  app: App;
26
28
  release: Release;
27
29
  solana_mobile_dapp_publisher_portal: SolanaMobileDappPublisherPortal;
30
+ lastSubmittedVersionOnChain: LastSubmittedVersionOnChain
31
+ lastUpdatedVersionOnStore: LastUpdatedVersionOnStore,
28
32
  }
29
33
 
30
34
  const AaptPrefixes = {
@@ -42,6 +46,8 @@ type SaveToConfigArgs = {
42
46
  publisher?: Pick<Publisher, "address">;
43
47
  app?: Pick<App, "address">;
44
48
  release?: Pick<Release, "address">;
49
+ lastSubmittedVersionOnChain?: LastSubmittedVersionOnChain;
50
+ lastUpdatedVersionOnStore?: LastUpdatedVersionOnStore;
45
51
  };
46
52
 
47
53
  const ajv = new Ajv({ strictTuples: false });
@@ -126,14 +132,14 @@ export const loadPublishDetailsWithChecks = async (
126
132
  }
127
133
  );
128
134
 
129
- const screenshots = config.release.media?.filter(
130
- (asset: any) => asset.purpose === "screenshot"
135
+ const previewMediaFiles = config.release.media?.filter(
136
+ (asset: any) => asset.purpose === "screenshot" || asset.purpose === "video"
131
137
  )
132
138
 
133
- if (screenshots.length < 4) {
139
+ if (previewMediaFiles.length < 4) {
134
140
  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}`,
141
+ "Preview media requirements changing in version 0.9.0",
142
+ `At least 4 screenshots or videos are required for publishing a new release. Found only ${previewMediaFiles.length}`,
137
143
  "warning"
138
144
  )
139
145
  }
@@ -223,7 +229,7 @@ const getAndroidDetails = async (
223
229
  const minSdk = new RegExp(
224
230
  AaptPrefixes.sdkPrefix + AaptPrefixes.quoteRegex
225
231
  ).exec(stdout);
226
- const permissions = [...stdout.matchAll(/uses-permission: name='(.*)'/g)];
232
+ const permissions = [...stdout.matchAll(/uses-permission: name='(.*)'/g)].flatMap(permission => permission[1]);
227
233
  const locales = new RegExp(
228
234
  AaptPrefixes.localePrefix + AaptPrefixes.quoteNonLazyRegex
229
235
  ).exec(stdout);
@@ -241,6 +247,30 @@ const getAndroidDetails = async (
241
247
  localeArray = ["en-US"].concat(localesSrc.split("' '").slice(1));
242
248
  }
243
249
 
250
+ if (permissions.includes("android.permission.INSTALL_PACKAGES") || permissions.includes("android.permission.DELETE_PACKAGES")) {
251
+ showMessage(
252
+ "App requests system app install/delete permission",
253
+ "Your app requests system install/delete permission which is managed by Solana dApp Store.\nThis app will be not approved for listing on Solana dApp Store.",
254
+ "error"
255
+ );
256
+ }
257
+
258
+ if (permissions.includes("android.permission.REQUEST_INSTALL_PACKAGES") || permissions.includes("android.permission.REQUEST_DELETE_PACKAGES")) {
259
+ showMessage(
260
+ "App requests install or delete permission",
261
+ "App will be subject to additional security reviews for listing on Solana dApp Store and processing time may be beyond regular review time",
262
+ "warning"
263
+ );
264
+ }
265
+
266
+ if (permissions.includes("com.solanamobile.seedvault.ACCESS_SEED_VAULT")) {
267
+ showMessage(
268
+ "App requests Seed Vault permission",
269
+ "If this is not a wallet application, your app maybe rejected from listing on Solana dApp Store.",
270
+ "warning"
271
+ );
272
+ }
273
+
244
274
  if (localeArray.length >= 60) {
245
275
  showMessage(
246
276
  "The bundle apk claims supports for following locales",
@@ -258,7 +288,7 @@ const getAndroidDetails = async (
258
288
  version_code: parseInt(versionCode?.[1] ?? "0", 10),
259
289
  version: versionName?.[1] ?? "0",
260
290
  cert_fingerprint: await extractCertFingerprint(aaptDir, apkPath),
261
- permissions: permissions.flatMap(permission => permission[1]),
291
+ permissions: permissions,
262
292
  locales: localeArray
263
293
  };
264
294
  } catch (e) {
@@ -283,7 +313,7 @@ export const extractCertFingerprint = async (aaptDir: string, apkPath: string):
283
313
  }
284
314
  }
285
315
 
286
- export const writeToPublishDetails = async ({ publisher, app, release }: SaveToConfigArgs) => {
316
+ export const writeToPublishDetails = async ({ publisher, app, release, lastSubmittedVersionOnChain, lastUpdatedVersionOnStore }: SaveToConfigArgs) => {
287
317
  const currentConfig = await loadPublishDetailsWithChecks();
288
318
 
289
319
  delete currentConfig.publisher.icon;
@@ -302,7 +332,9 @@ export const writeToPublishDetails = async ({ publisher, app, release }: SaveToC
302
332
  ...currentConfig.release,
303
333
  address: release?.address ?? currentConfig.release.address
304
334
  },
305
- solana_mobile_dapp_publisher_portal: currentConfig.solana_mobile_dapp_publisher_portal
335
+ solana_mobile_dapp_publisher_portal: currentConfig.solana_mobile_dapp_publisher_portal,
336
+ lastSubmittedVersionOnChain: lastSubmittedVersionOnChain ?? currentConfig.lastSubmittedVersionOnChain,
337
+ lastUpdatedVersionOnStore: lastUpdatedVersionOnStore ?? currentConfig.lastUpdatedVersionOnStore
306
338
  };
307
339
 
308
340
  fs.writeFileSync(Constants.getConfigFilePath(), dump(newConfig, {
@@ -24,7 +24,15 @@ release:
24
24
  - purpose: icon
25
25
  uri: <<RELATIVE_PATH_TO_RELEASE_ICON>>
26
26
  - purpose: screenshot
27
- uri: <<RELATIVE_PATH_TO_SCREENSHOT>>
27
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT1>>
28
+ - purpose: screenshot
29
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT2>>
30
+ - purpose: screenshot
31
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT3>>
32
+ - purpose: screenshot
33
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT4>>
34
+ - purpose: video
35
+ uri: <<RELATIVE_PATH_TO_VIDEO1>>
28
36
  files:
29
37
  - purpose: install
30
38
  uri: <<RELATIVE_PATH_TO_APK>>