@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.
- package/bin/dapp-store.js +3 -1
- package/lib/CliSetup.js +304 -505
- package/lib/CliUtils.js +6 -376
- package/lib/__tests__/CliSetupTest.js +484 -74
- package/lib/cli/__tests__/parseErrors.test.js +25 -0
- package/lib/cli/__tests__/signer.test.js +436 -0
- package/lib/cli/constants.js +23 -0
- package/lib/cli/messages.js +21 -0
- package/lib/cli/parseErrors.js +41 -0
- package/lib/{commands/publish/PublishCliSupport.js → cli/selfUpdate.js} +72 -38
- package/lib/{commands/publish/PublishCliRemove.js → cli/signer.js} +35 -56
- package/lib/index.js +96 -5
- package/lib/package.json +5 -24
- package/lib/portal/__tests__/releaseMetadata.test.js +647 -0
- package/lib/portal/__tests__/translators.test.js +76 -0
- package/lib/portal/__tests__/workflowClient.test.js +457 -0
- package/lib/portal/attestationClient.js +143 -0
- package/lib/portal/files.js +64 -0
- package/lib/portal/http.js +364 -0
- package/lib/portal/records.js +64 -0
- package/lib/portal/releaseMetadata.js +748 -0
- package/lib/portal/translators.js +460 -0
- package/lib/portal/types.js +1 -0
- package/lib/portal/workflowClient.js +704 -0
- package/lib/publication/PublicationProgressReporter.js +1051 -0
- package/lib/publication/__tests__/PublicationProgressReporter.test.js +174 -0
- package/lib/{commands/ValidateCommand.js → publication/__tests__/fundingPreflight.test.js} +90 -66
- package/lib/publication/__tests__/publicationSummary.test.js +26 -0
- package/lib/publication/cliValidation.js +482 -0
- package/lib/publication/fundingPreflight.js +246 -0
- package/lib/publication/publicationSummary.js +99 -0
- package/lib/{commands/utils.js → publication/runPublicationWorkflow.js} +16 -46
- package/package.json +5 -24
- package/src/CliSetup.ts +370 -505
- package/src/CliUtils.ts +9 -233
- package/src/__tests__/CliSetupTest.ts +272 -120
- package/src/cli/__tests__/parseErrors.test.ts +34 -0
- package/src/cli/__tests__/signer.test.ts +359 -0
- package/src/cli/constants.ts +3 -0
- package/src/cli/messages.ts +27 -0
- package/src/cli/parseErrors.ts +62 -0
- package/src/cli/selfUpdate.ts +59 -0
- package/src/cli/signer.ts +38 -0
- package/src/index.ts +31 -4
- package/src/portal/__tests__/releaseMetadata.test.ts +508 -0
- package/src/portal/__tests__/translators.test.ts +82 -0
- package/src/portal/__tests__/workflowClient.test.ts +278 -0
- package/src/portal/attestationClient.ts +19 -0
- package/src/portal/files.ts +73 -0
- package/src/portal/http.ts +170 -0
- package/src/portal/records.ts +38 -0
- package/src/portal/releaseMetadata.ts +489 -0
- package/src/portal/translators.ts +750 -0
- package/src/portal/types.ts +27 -0
- package/src/portal/workflowClient.ts +575 -0
- package/src/publication/PublicationProgressReporter.ts +1026 -0
- package/src/publication/__tests__/PublicationProgressReporter.test.ts +210 -0
- package/src/publication/__tests__/fundingPreflight.test.ts +78 -0
- package/src/publication/__tests__/publicationSummary.test.ts +30 -0
- package/src/publication/cliValidation.ts +264 -0
- package/src/publication/fundingPreflight.ts +123 -0
- package/src/publication/publicationSummary.ts +26 -0
- package/src/publication/runPublicationWorkflow.ts +46 -0
- package/lib/commands/create/CreateCliApp.js +0 -223
- package/lib/commands/create/CreateCliRelease.js +0 -290
- package/lib/commands/create/index.js +0 -40
- package/lib/commands/index.js +0 -3
- package/lib/commands/publish/PublishCliSubmit.js +0 -208
- package/lib/commands/publish/PublishCliUpdate.js +0 -211
- package/lib/commands/publish/index.js +0 -22
- package/lib/commands/scaffolding/ScaffoldInit.js +0 -15
- package/lib/commands/scaffolding/index.js +0 -1
- package/lib/config/EnvVariables.js +0 -59
- package/lib/config/PublishDetails.js +0 -915
- package/lib/config/S3StorageManager.js +0 -93
- package/lib/config/index.js +0 -2
- package/lib/generated/config_obj.json +0 -1
- package/lib/generated/config_schema.json +0 -1
- package/lib/prebuild_schema/publishing_source.yaml +0 -64
- package/lib/prebuild_schema/schemagen.js +0 -25
- package/lib/upload/CachedStorageDriver.js +0 -458
- package/lib/upload/TurboStorageDriver.js +0 -718
- package/lib/upload/__tests__/CachedStorageDriver.test.js +0 -437
- package/lib/upload/__tests__/TurboStorageDriver.test.js +0 -17
- package/lib/upload/__tests__/contentGateway.test.js +0 -17
- package/lib/upload/contentGateway.js +0 -23
- package/lib/upload/index.js +0 -2
- package/src/commands/ValidateCommand.ts +0 -82
- package/src/commands/create/CreateCliApp.ts +0 -93
- package/src/commands/create/CreateCliRelease.ts +0 -149
- package/src/commands/create/index.ts +0 -47
- package/src/commands/index.ts +0 -3
- package/src/commands/publish/PublishCliRemove.ts +0 -66
- package/src/commands/publish/PublishCliSubmit.ts +0 -93
- package/src/commands/publish/PublishCliSupport.ts +0 -66
- package/src/commands/publish/PublishCliUpdate.ts +0 -101
- package/src/commands/publish/index.ts +0 -29
- package/src/commands/scaffolding/ScaffoldInit.ts +0 -20
- package/src/commands/scaffolding/index.ts +0 -1
- package/src/commands/utils.ts +0 -33
- package/src/config/EnvVariables.ts +0 -39
- package/src/config/PublishDetails.ts +0 -456
- package/src/config/S3StorageManager.ts +0 -47
- package/src/config/index.ts +0 -2
- package/src/prebuild_schema/publishing_source.yaml +0 -64
- package/src/prebuild_schema/schemagen.js +0 -31
- package/src/upload/CachedStorageDriver.ts +0 -179
- package/src/upload/TurboStorageDriver.ts +0 -283
- package/src/upload/__tests__/CachedStorageDriver.test.ts +0 -246
- package/src/upload/__tests__/TurboStorageDriver.test.ts +0 -15
- package/src/upload/__tests__/contentGateway.test.ts +0 -31
- package/src/upload/contentGateway.ts +0 -37
- package/src/upload/index.ts +0 -2
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
App,
|
|
3
|
-
Publisher,
|
|
4
|
-
Release,
|
|
5
|
-
} from "@solana-mobile/dapp-store-publishing-tools";
|
|
6
|
-
import { createRelease } from "@solana-mobile/dapp-store-publishing-tools";
|
|
7
|
-
import {
|
|
8
|
-
Connection,
|
|
9
|
-
Keypair,
|
|
10
|
-
PublicKey,
|
|
11
|
-
} from "@solana/web3.js";
|
|
12
|
-
import fs from "fs";
|
|
13
|
-
import { createHash } from "crypto";
|
|
14
|
-
import {
|
|
15
|
-
Constants,
|
|
16
|
-
getMetaplexInstance,
|
|
17
|
-
} from "../../CliUtils.js";
|
|
18
|
-
import { PublishDetails, loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
|
|
19
|
-
import { sendAndConfirmTransaction } from "../utils.js";
|
|
20
|
-
|
|
21
|
-
type CreateReleaseCommandInput = {
|
|
22
|
-
appMintAddress: string;
|
|
23
|
-
buildToolsPath: string;
|
|
24
|
-
signer: Keypair;
|
|
25
|
-
url: string;
|
|
26
|
-
dryRun?: boolean;
|
|
27
|
-
storageParams: string;
|
|
28
|
-
priorityFeeLamports: number;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const createReleaseNft = async ({
|
|
32
|
-
appMintAddress,
|
|
33
|
-
releaseDetails,
|
|
34
|
-
appDetails,
|
|
35
|
-
publisherDetails,
|
|
36
|
-
connection,
|
|
37
|
-
publisher,
|
|
38
|
-
storageParams,
|
|
39
|
-
priorityFeeLamports,
|
|
40
|
-
}: {
|
|
41
|
-
appMintAddress: string;
|
|
42
|
-
releaseDetails: Release;
|
|
43
|
-
appDetails: App;
|
|
44
|
-
publisherDetails: Publisher;
|
|
45
|
-
connection: Connection;
|
|
46
|
-
publisher: Keypair;
|
|
47
|
-
storageParams: string;
|
|
48
|
-
priorityFeeLamports: number;
|
|
49
|
-
}) => {
|
|
50
|
-
console.info(`Creating Release NFT`);
|
|
51
|
-
|
|
52
|
-
const releaseMintAddress = Keypair.generate();
|
|
53
|
-
|
|
54
|
-
const metaplex = getMetaplexInstance(connection, publisher, storageParams);
|
|
55
|
-
|
|
56
|
-
const { txBuilder } = await createRelease(
|
|
57
|
-
{
|
|
58
|
-
appMintAddress: new PublicKey(appMintAddress),
|
|
59
|
-
releaseMintAddress,
|
|
60
|
-
releaseDetails,
|
|
61
|
-
appDetails,
|
|
62
|
-
publisherDetails,
|
|
63
|
-
priorityFeeLamports
|
|
64
|
-
},
|
|
65
|
-
{ metaplex, publisher }
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
console.info(`Release NFT data upload complete\nSigning transaction now`);
|
|
69
|
-
|
|
70
|
-
const { response } = await sendAndConfirmTransaction(metaplex, txBuilder);
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
releaseAddress: releaseMintAddress.publicKey.toBase58(),
|
|
74
|
-
transactionSignature: response.signature,
|
|
75
|
-
};
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export const createReleaseCommand = async ({
|
|
79
|
-
appMintAddress,
|
|
80
|
-
buildToolsPath,
|
|
81
|
-
signer,
|
|
82
|
-
url,
|
|
83
|
-
dryRun = false,
|
|
84
|
-
storageParams,
|
|
85
|
-
priorityFeeLamports = Constants.DEFAULT_PRIORITY_FEE,
|
|
86
|
-
}: CreateReleaseCommandInput) => {
|
|
87
|
-
const connection = new Connection(
|
|
88
|
-
url,
|
|
89
|
-
{
|
|
90
|
-
commitment: "confirmed",
|
|
91
|
-
}
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const config = await loadPublishDetailsWithChecks(buildToolsPath);
|
|
95
|
-
|
|
96
|
-
const apkEntry = config.release.files.find(
|
|
97
|
-
(asset: PublishDetails["release"]["files"][0]) => asset.purpose === "install"
|
|
98
|
-
)!;
|
|
99
|
-
const mediaBuffer = await fs.promises.readFile(apkEntry.uri);
|
|
100
|
-
const hash = createHash("sha256").update(mediaBuffer).digest("base64");
|
|
101
|
-
|
|
102
|
-
if (config.lastSubmittedVersionOnChain != null && hash === config.lastSubmittedVersionOnChain.apk_hash) {
|
|
103
|
-
throw new Error(`The last created release used the same apk file.`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (config.lastSubmittedVersionOnChain != null && config.release.android_details.version_code <= config.lastSubmittedVersionOnChain.version_code) {
|
|
107
|
-
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}`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (config.app.android_package != config.release.android_details.android_package) {
|
|
111
|
-
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)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!dryRun) {
|
|
115
|
-
const { releaseAddress, transactionSignature } = await createReleaseNft({
|
|
116
|
-
appMintAddress: config.app.address ?? appMintAddress,
|
|
117
|
-
connection,
|
|
118
|
-
publisher: signer,
|
|
119
|
-
releaseDetails: {
|
|
120
|
-
...config.release,
|
|
121
|
-
},
|
|
122
|
-
appDetails: config.app,
|
|
123
|
-
publisherDetails: config.publisher,
|
|
124
|
-
storageParams: storageParams,
|
|
125
|
-
priorityFeeLamports: priorityFeeLamports,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
await writeToPublishDetails(
|
|
129
|
-
{
|
|
130
|
-
release: {
|
|
131
|
-
address: releaseAddress,
|
|
132
|
-
android_details: {
|
|
133
|
-
cert_fingerprint: config.release.android_details.cert_fingerprint,
|
|
134
|
-
min_sdk: config.release.android_details.min_sdk,
|
|
135
|
-
version: config.release.android_details.version,
|
|
136
|
-
version_code: config.release.android_details.version_code,
|
|
137
|
-
locales: config.release.android_details.locales
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
lastSubmittedVersionOnChain: {
|
|
141
|
-
address: releaseAddress,
|
|
142
|
-
version_code: config.release.android_details.version_code,
|
|
143
|
-
apk_hash: hash,
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
return { releaseAddress, transactionSignature };
|
|
148
|
-
}
|
|
149
|
-
};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
export * from "./CreateCliApp.js";
|
|
2
|
-
export * from "./CreateCliRelease.js";
|
|
3
|
-
|
|
4
|
-
/*
|
|
5
|
-
* Module responsible for creating publishers, apps, and releases (in that order)
|
|
6
|
-
* Anything that is out-of-order will be prompted back into order
|
|
7
|
-
* And steps that happen more than once will do their best to remember as much information as possible.
|
|
8
|
-
* We will ask questions and do our best to answer anything that's already been configured, and prompt for anything that's not
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// We'll never ask for private keys or seed phrases
|
|
12
|
-
// You can use a burner signer to publish; all NFTs require verification from the publisher signer
|
|
13
|
-
// You can use a multisig or Ledger for that purpose
|
|
14
|
-
|
|
15
|
-
// Publisher
|
|
16
|
-
// Public key attached to a publisher must also verify applications and releases
|
|
17
|
-
// Most information here can be be edited after the fact
|
|
18
|
-
// Only required fields are name, address, publisher website, and contact email
|
|
19
|
-
// Optional fields are: description, image_url (need dimensions!)
|
|
20
|
-
|
|
21
|
-
// App
|
|
22
|
-
// Publisher creator key required
|
|
23
|
-
// Most information can be edited after the fact
|
|
24
|
-
// Required: name, description, `android_package`
|
|
25
|
-
// Optional: Any additional creator keys
|
|
26
|
-
// TODO(jon): Probably okay to capture more information here like:
|
|
27
|
-
// - `license_url`
|
|
28
|
-
// - `copyright_url`
|
|
29
|
-
// - `privacy_policy_url`
|
|
30
|
-
// Release
|
|
31
|
-
// Publisher creator key required
|
|
32
|
-
// Immutable; information cannot be edited after publishing.
|
|
33
|
-
// Change based on the review process must result in a new release
|
|
34
|
-
// Required:
|
|
35
|
-
// - version (automatically prompt with semver + 1)
|
|
36
|
-
// - release notes (description)
|
|
37
|
-
// - publisher creator key
|
|
38
|
-
// - path to the APK
|
|
39
|
-
// Optional:
|
|
40
|
-
// - Media related to the release
|
|
41
|
-
// - New permissions (prompted)
|
|
42
|
-
// - New languages (prompted)
|
|
43
|
-
// Handles uploads of all files, sha'ing them
|
|
44
|
-
// Handles i18n (later)
|
|
45
|
-
|
|
46
|
-
// We'll attempt to read as much as possible from a provided `.yml` file
|
|
47
|
-
// If there are provided folders that are well-structured, we'll opt to use that too.
|
package/src/commands/index.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { Connection, Keypair } from "@solana/web3.js";
|
|
2
|
-
import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publishing-tools";
|
|
3
|
-
import { publishRemove } from "@solana-mobile/dapp-store-publishing-tools";
|
|
4
|
-
import { checkMintedStatus } from "../../CliUtils.js";
|
|
5
|
-
import nacl from "tweetnacl";
|
|
6
|
-
import { loadPublishDetailsWithChecks } from "../../config/PublishDetails.js";
|
|
7
|
-
|
|
8
|
-
type PublishRemoveCommandInput = {
|
|
9
|
-
appMintAddress: string;
|
|
10
|
-
releaseMintAddress: string;
|
|
11
|
-
signer: Keypair;
|
|
12
|
-
url: string;
|
|
13
|
-
dryRun: boolean;
|
|
14
|
-
requestorIsAuthorized: boolean;
|
|
15
|
-
critical: boolean;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export const publishRemoveCommand = async ({
|
|
19
|
-
appMintAddress,
|
|
20
|
-
releaseMintAddress,
|
|
21
|
-
signer,
|
|
22
|
-
url,
|
|
23
|
-
dryRun = false,
|
|
24
|
-
requestorIsAuthorized = false,
|
|
25
|
-
critical = false,
|
|
26
|
-
}: PublishRemoveCommandInput) => {
|
|
27
|
-
if (!requestorIsAuthorized) {
|
|
28
|
-
console.error(
|
|
29
|
-
"ERROR: Cannot submit a request for which the requestor does not attest they are authorized to do so"
|
|
30
|
-
);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const connection = new Connection(
|
|
35
|
-
url,
|
|
36
|
-
{
|
|
37
|
-
commitment: "confirmed",
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const {
|
|
42
|
-
publisher: publisherDetails,
|
|
43
|
-
app: appDetails,
|
|
44
|
-
release: releaseDetails,
|
|
45
|
-
} = await loadPublishDetailsWithChecks();
|
|
46
|
-
|
|
47
|
-
const sign = ((buf: Buffer) =>
|
|
48
|
-
nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
|
|
49
|
-
|
|
50
|
-
const appAddr = appMintAddress ?? appDetails.address;
|
|
51
|
-
const releaseAddr = releaseMintAddress ?? releaseDetails.address;
|
|
52
|
-
|
|
53
|
-
await checkMintedStatus(connection, appAddr, releaseAddr);
|
|
54
|
-
|
|
55
|
-
await publishRemove(
|
|
56
|
-
{ connection, sign },
|
|
57
|
-
{
|
|
58
|
-
appMintAddress: appMintAddress ?? appDetails.address,
|
|
59
|
-
releaseMintAddress: releaseMintAddress ?? releaseDetails.address,
|
|
60
|
-
publisherDetails,
|
|
61
|
-
requestorIsAuthorized,
|
|
62
|
-
criticalUpdate: critical,
|
|
63
|
-
},
|
|
64
|
-
dryRun
|
|
65
|
-
);
|
|
66
|
-
};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { AccountInfo, Connection, Keypair, PublicKey } from "@solana/web3.js";
|
|
2
|
-
import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publishing-tools";
|
|
3
|
-
import { publishSubmit } from "@solana-mobile/dapp-store-publishing-tools";
|
|
4
|
-
import nacl from "tweetnacl";
|
|
5
|
-
import { checkMintedStatus, showMessage } from "../../CliUtils.js";
|
|
6
|
-
import { Buffer } from "buffer";
|
|
7
|
-
import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
|
|
8
|
-
|
|
9
|
-
type PublishSubmitCommandInput = {
|
|
10
|
-
appMintAddress: string;
|
|
11
|
-
releaseMintAddress: string;
|
|
12
|
-
signer: Keypair;
|
|
13
|
-
url: string;
|
|
14
|
-
dryRun: boolean;
|
|
15
|
-
compliesWithSolanaDappStorePolicies: boolean;
|
|
16
|
-
requestorIsAuthorized: boolean;
|
|
17
|
-
alphaTest?: boolean;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const publishSubmitCommand = async ({
|
|
21
|
-
appMintAddress,
|
|
22
|
-
releaseMintAddress,
|
|
23
|
-
signer,
|
|
24
|
-
url,
|
|
25
|
-
dryRun = false,
|
|
26
|
-
compliesWithSolanaDappStorePolicies = false,
|
|
27
|
-
requestorIsAuthorized = false,
|
|
28
|
-
alphaTest
|
|
29
|
-
}: PublishSubmitCommandInput) => {
|
|
30
|
-
showMessage(
|
|
31
|
-
`Publishing Estimates`,
|
|
32
|
-
"New app submissions take around 3-4 business days for review.",
|
|
33
|
-
"warning"
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
if (!compliesWithSolanaDappStorePolicies) {
|
|
37
|
-
console.error(
|
|
38
|
-
"ERROR: Cannot submit a request for which the requestor does not attest that it complies with Solana dApp Store policies"
|
|
39
|
-
);
|
|
40
|
-
return;
|
|
41
|
-
} else if (!requestorIsAuthorized) {
|
|
42
|
-
console.error(
|
|
43
|
-
"ERROR: Cannot submit a request for which the requestor does not attest they are authorized to do so"
|
|
44
|
-
);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const connection = new Connection(url);
|
|
49
|
-
const {
|
|
50
|
-
publisher: publisherDetails,
|
|
51
|
-
app: appDetails,
|
|
52
|
-
release: releaseDetails,
|
|
53
|
-
solana_mobile_dapp_publisher_portal: solanaMobileDappPublisherPortalDetails,
|
|
54
|
-
lastUpdatedVersionOnStore: lastUpdatedVersionOnStore,
|
|
55
|
-
} = await loadPublishDetailsWithChecks();
|
|
56
|
-
|
|
57
|
-
if (alphaTest && solanaMobileDappPublisherPortalDetails.alpha_testers == undefined) {
|
|
58
|
-
throw new Error(`Alpha test submission without specifying any testers.\nAdd field alpha_testers in your 'config.yaml' file.`)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const sign = ((buf: Buffer) =>
|
|
62
|
-
nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
|
|
63
|
-
|
|
64
|
-
const appAddr = appMintAddress ?? appDetails.address;
|
|
65
|
-
const releaseAddr = releaseMintAddress ?? releaseDetails.address;
|
|
66
|
-
|
|
67
|
-
if (lastUpdatedVersionOnStore != null && releaseAddr === lastUpdatedVersionOnStore.address) {
|
|
68
|
-
throw new Error(`You've already submitted this version for review.`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
await checkMintedStatus(connection, appAddr, releaseAddr);
|
|
72
|
-
|
|
73
|
-
await publishSubmit(
|
|
74
|
-
{ connection, sign },
|
|
75
|
-
{
|
|
76
|
-
appMintAddress: appAddr,
|
|
77
|
-
releaseMintAddress: releaseAddr,
|
|
78
|
-
publisherDetails,
|
|
79
|
-
solanaMobileDappPublisherPortalDetails,
|
|
80
|
-
compliesWithSolanaDappStorePolicies,
|
|
81
|
-
requestorIsAuthorized,
|
|
82
|
-
alphaTest,
|
|
83
|
-
},
|
|
84
|
-
dryRun
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
if (!alphaTest) {
|
|
88
|
-
await writeToPublishDetails(
|
|
89
|
-
{
|
|
90
|
-
lastUpdatedVersionOnStore: { address: releaseAddr }
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { Connection, Keypair } from "@solana/web3.js";
|
|
2
|
-
import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publishing-tools";
|
|
3
|
-
import { publishSupport } from "@solana-mobile/dapp-store-publishing-tools";
|
|
4
|
-
import { checkMintedStatus } from "../../CliUtils.js";
|
|
5
|
-
import nacl from "tweetnacl";
|
|
6
|
-
import { loadPublishDetailsWithChecks } from "../../config/PublishDetails.js";
|
|
7
|
-
|
|
8
|
-
type PublishSupportCommandInput = {
|
|
9
|
-
appMintAddress: string;
|
|
10
|
-
releaseMintAddress: string;
|
|
11
|
-
signer: Keypair;
|
|
12
|
-
url: string;
|
|
13
|
-
dryRun: boolean;
|
|
14
|
-
requestorIsAuthorized: boolean;
|
|
15
|
-
requestDetails: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export const publishSupportCommand = async ({
|
|
19
|
-
appMintAddress,
|
|
20
|
-
releaseMintAddress,
|
|
21
|
-
signer,
|
|
22
|
-
url,
|
|
23
|
-
dryRun = false,
|
|
24
|
-
requestorIsAuthorized = false,
|
|
25
|
-
requestDetails,
|
|
26
|
-
}: PublishSupportCommandInput) => {
|
|
27
|
-
if (!requestorIsAuthorized) {
|
|
28
|
-
console.error(
|
|
29
|
-
"ERROR: Cannot submit a request for which the requestor does not attest they are authorized to do so"
|
|
30
|
-
);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const connection = new Connection(
|
|
35
|
-
url,
|
|
36
|
-
{
|
|
37
|
-
commitment: "confirmed",
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const {
|
|
42
|
-
publisher: publisherDetails,
|
|
43
|
-
app: appDetails,
|
|
44
|
-
release: releaseDetails,
|
|
45
|
-
} = await loadPublishDetailsWithChecks();
|
|
46
|
-
|
|
47
|
-
const sign = ((buf: Buffer) =>
|
|
48
|
-
nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
|
|
49
|
-
|
|
50
|
-
const appAddr = appMintAddress ?? appDetails.address;
|
|
51
|
-
const releaseAddr = releaseMintAddress ?? releaseDetails.address;
|
|
52
|
-
|
|
53
|
-
await checkMintedStatus(connection, appAddr, releaseAddr);
|
|
54
|
-
|
|
55
|
-
await publishSupport(
|
|
56
|
-
{ connection, sign },
|
|
57
|
-
{
|
|
58
|
-
appMintAddress: appMintAddress ?? appDetails.address,
|
|
59
|
-
releaseMintAddress: releaseMintAddress ?? releaseDetails.address,
|
|
60
|
-
publisherDetails,
|
|
61
|
-
requestorIsAuthorized,
|
|
62
|
-
requestDetails,
|
|
63
|
-
},
|
|
64
|
-
dryRun
|
|
65
|
-
);
|
|
66
|
-
};
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { Connection, Keypair } from "@solana/web3.js";
|
|
2
|
-
import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publishing-tools";
|
|
3
|
-
import { publishUpdate } from "@solana-mobile/dapp-store-publishing-tools";
|
|
4
|
-
import { checkMintedStatus, showMessage } from "../../CliUtils.js";
|
|
5
|
-
import nacl from "tweetnacl";
|
|
6
|
-
import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
|
|
7
|
-
|
|
8
|
-
type PublishUpdateCommandInput = {
|
|
9
|
-
appMintAddress: string;
|
|
10
|
-
releaseMintAddress: string;
|
|
11
|
-
signer: Keypair;
|
|
12
|
-
url: string;
|
|
13
|
-
dryRun: boolean;
|
|
14
|
-
compliesWithSolanaDappStorePolicies: boolean;
|
|
15
|
-
requestorIsAuthorized: boolean;
|
|
16
|
-
critical: boolean;
|
|
17
|
-
alphaTest?: boolean;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const publishUpdateCommand = async ({
|
|
21
|
-
appMintAddress,
|
|
22
|
-
releaseMintAddress,
|
|
23
|
-
signer,
|
|
24
|
-
url,
|
|
25
|
-
dryRun = false,
|
|
26
|
-
compliesWithSolanaDappStorePolicies = false,
|
|
27
|
-
requestorIsAuthorized = false,
|
|
28
|
-
critical = false,
|
|
29
|
-
alphaTest,
|
|
30
|
-
}: PublishUpdateCommandInput) => {
|
|
31
|
-
|
|
32
|
-
showMessage(
|
|
33
|
-
`Publishing Estimates`,
|
|
34
|
-
"App update approvals take around 1-2 business days for review.",
|
|
35
|
-
"warning"
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
if (!compliesWithSolanaDappStorePolicies) {
|
|
39
|
-
console.error(
|
|
40
|
-
"ERROR: Cannot submit a request for which the requestor does not attest that it complies with Solana dApp Store policies"
|
|
41
|
-
);
|
|
42
|
-
return;
|
|
43
|
-
} else if (!requestorIsAuthorized) {
|
|
44
|
-
console.error(
|
|
45
|
-
"ERROR: Cannot submit a request for which the requestor does not attest they are authorized to do so"
|
|
46
|
-
);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const connection = new Connection(
|
|
51
|
-
url,
|
|
52
|
-
{
|
|
53
|
-
commitment: "confirmed",
|
|
54
|
-
}
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const {
|
|
58
|
-
publisher: publisherDetails,
|
|
59
|
-
app: appDetails,
|
|
60
|
-
release: releaseDetails,
|
|
61
|
-
solana_mobile_dapp_publisher_portal: solanaMobileDappPublisherPortalDetails,
|
|
62
|
-
lastUpdatedVersionOnStore: lastUpdatedVersionOnStore
|
|
63
|
-
} = await loadPublishDetailsWithChecks();
|
|
64
|
-
|
|
65
|
-
if (alphaTest && solanaMobileDappPublisherPortalDetails.alpha_testers == undefined) {
|
|
66
|
-
throw new Error(`Alpha test submission without specifying any testers.\nAdd field alpha_testers in your 'config.yaml' file.`)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const sign = ((buf: Buffer) =>
|
|
70
|
-
nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
|
|
71
|
-
|
|
72
|
-
const appAddr = appMintAddress ?? appDetails.address;
|
|
73
|
-
const releaseAddr = releaseMintAddress ?? releaseDetails.address;
|
|
74
|
-
|
|
75
|
-
if (lastUpdatedVersionOnStore != null && releaseAddr === lastUpdatedVersionOnStore.address) {
|
|
76
|
-
throw new Error(`You've already submitted this version for review.`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
await checkMintedStatus(connection, appAddr, releaseAddr);
|
|
80
|
-
|
|
81
|
-
await publishUpdate(
|
|
82
|
-
{ connection, sign },
|
|
83
|
-
{
|
|
84
|
-
appMintAddress: appMintAddress ?? appDetails.address,
|
|
85
|
-
releaseMintAddress: releaseMintAddress ?? releaseDetails.address,
|
|
86
|
-
publisherDetails,
|
|
87
|
-
solanaMobileDappPublisherPortalDetails,
|
|
88
|
-
compliesWithSolanaDappStorePolicies,
|
|
89
|
-
requestorIsAuthorized,
|
|
90
|
-
criticalUpdate: critical,
|
|
91
|
-
alphaTest,
|
|
92
|
-
},
|
|
93
|
-
dryRun
|
|
94
|
-
);
|
|
95
|
-
if (!alphaTest) {
|
|
96
|
-
await writeToPublishDetails(
|
|
97
|
-
{
|
|
98
|
-
lastUpdatedVersionOnStore: { address: releaseAddr }
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export * from "./PublishCliRemove.js";
|
|
2
|
-
export * from "./PublishCliSubmit.js";
|
|
3
|
-
export * from "./PublishCliSupport.js";
|
|
4
|
-
export * from "./PublishCliUpdate.js";
|
|
5
|
-
|
|
6
|
-
/*
|
|
7
|
-
* Module responsible for submitting requests to the Solana dApp Store publisher portal
|
|
8
|
-
* Anything that is out-of-order will be prompted back into order
|
|
9
|
-
* And steps that happen more than once will do their best to remember as much information as possible.
|
|
10
|
-
* We will ask questions and do our best to answer anything that's already been configured, and prompt for anything that's not
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// We'll never ask for private keys or seed phrases
|
|
14
|
-
// You must use the same signer(s) when submitting requests to the publisher portal as was used to publish
|
|
15
|
-
// your app on-chain.
|
|
16
|
-
|
|
17
|
-
// The Solana Mobile dApp publisher portal supports 4 different requests: `submit`, `update`, `remove`, and `support`.
|
|
18
|
-
// Each request includes:
|
|
19
|
-
// - a 32-digit randomly generated unique identifier
|
|
20
|
-
// - an attestation payload, signed with the private key of the dApp collection update authority
|
|
21
|
-
// - the dApp release NFT address
|
|
22
|
-
// - contact and company information for the requestor
|
|
23
|
-
// - a self-attestation that the requestor is authorized to make this request
|
|
24
|
-
// - additional fields, specific to the request type in question
|
|
25
|
-
|
|
26
|
-
// We'll attempt to read as much as possible from a provided `.yml` file
|
|
27
|
-
// If there are provided folders that are well-structured, we'll opt to use that too.
|
|
28
|
-
|
|
29
|
-
// Requests and responses are logged to the console, to facilitate use in an automated CI/CD environment.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import yaml, { dump } from "js-yaml";
|
|
2
|
-
|
|
3
|
-
import { readFile } from 'fs/promises';
|
|
4
|
-
const releaseSchema = JSON.parse((await readFile(new URL("../../generated/config_obj.json", import.meta.url))).toString());
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
import path from "path";
|
|
7
|
-
import { Constants } from "../../CliUtils.js";
|
|
8
|
-
|
|
9
|
-
export const initScaffold = (): string => {
|
|
10
|
-
const outputYaml = Constants.CONFIG_FILE_NAME;
|
|
11
|
-
const outFile = path.join(process.cwd(), outputYaml);
|
|
12
|
-
|
|
13
|
-
if (fs.existsSync(outFile)) {
|
|
14
|
-
throw Error("Configuration file already present; please use to intialize a new config file.");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
fs.writeFileSync(outFile, dump(releaseSchema));
|
|
18
|
-
|
|
19
|
-
return `Your configuration file was created: ${outputYaml}`;
|
|
20
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./ScaffoldInit.js";
|
package/src/commands/utils.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Metaplex,
|
|
3
|
-
type TransactionBuilder,
|
|
4
|
-
FailedToConfirmTransactionError,
|
|
5
|
-
} from "@metaplex-foundation/js";
|
|
6
|
-
import { TransactionExpiredBlockheightExceededError } from "@solana/web3.js";
|
|
7
|
-
|
|
8
|
-
export async function sendAndConfirmTransaction(
|
|
9
|
-
metaplex: Metaplex,
|
|
10
|
-
builder: TransactionBuilder
|
|
11
|
-
): ReturnType<TransactionBuilder["sendAndConfirm"]> {
|
|
12
|
-
for (let i = 0; i < 10; i++) {
|
|
13
|
-
try {
|
|
14
|
-
return await builder.sendAndConfirm(metaplex);
|
|
15
|
-
} catch (e: unknown) {
|
|
16
|
-
if (isTransientError(e)) {
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
throw e;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
throw new Error("Unable to send transaction. Please try later.");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function isTransientError(e: unknown): boolean {
|
|
28
|
-
return (
|
|
29
|
-
e instanceof FailedToConfirmTransactionError &&
|
|
30
|
-
(e.cause instanceof TransactionExpiredBlockheightExceededError ||
|
|
31
|
-
/blockhash not found/i.test(e.cause?.message ?? ""))
|
|
32
|
-
);
|
|
33
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import * as dotenv from "dotenv";
|
|
2
|
-
|
|
3
|
-
export type S3Config = {
|
|
4
|
-
accessKey: string;
|
|
5
|
-
secretKey: string;
|
|
6
|
-
bucketName: string;
|
|
7
|
-
regionName: string;
|
|
8
|
-
}
|
|
9
|
-
export class EnvVariables {
|
|
10
|
-
|
|
11
|
-
public get hasAndroidTools(): boolean {
|
|
12
|
-
return process.env.ANDROID_TOOLS_DIR !== undefined;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
public get androidToolsDir(): string {
|
|
16
|
-
return process.env.ANDROID_TOOLS_DIR as string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public get hasS3EnvArgs(): boolean {
|
|
20
|
-
return process.env.STORAGE_TYPE == "s3" &&
|
|
21
|
-
process.env.S3_ACCESS_KEY != undefined &&
|
|
22
|
-
process.env.S3_SECRET_KEY != undefined &&
|
|
23
|
-
process.env.S3_BUCKET != undefined &&
|
|
24
|
-
process.env.S3_REGION != undefined;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public get s3Config(): S3Config {
|
|
28
|
-
return {
|
|
29
|
-
accessKey: process.env.S3_ACCESS_KEY as string,
|
|
30
|
-
secretKey: process.env.S3_SECRET_KEY as string,
|
|
31
|
-
bucketName: process.env.S3_BUCKET as string,
|
|
32
|
-
regionName: process.env.S3_REGION as string
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
constructor() {
|
|
37
|
-
dotenv.config();
|
|
38
|
-
}
|
|
39
|
-
}
|