@solana-mobile/dapp-store-publishing-tools 0.1.1-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/lib/esm/create/app.js +47 -0
- package/lib/esm/create/app.js.map +1 -0
- package/lib/esm/create/index.js +4 -0
- package/lib/esm/create/index.js.map +1 -0
- package/lib/esm/create/publisher.js +43 -0
- package/lib/esm/create/publisher.js.map +1 -0
- package/lib/esm/create/release.js +127 -0
- package/lib/esm/create/release.js.map +1 -0
- package/lib/esm/index.js +5 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/publish/attestation.js +15 -0
- package/lib/esm/publish/attestation.js.map +1 -0
- package/lib/esm/publish/dapp_publisher_portal.js +56 -0
- package/lib/esm/publish/dapp_publisher_portal.js.map +1 -0
- package/lib/esm/publish/index.js +6 -0
- package/lib/esm/publish/index.js.map +1 -0
- package/lib/esm/publish/remove.js +57 -0
- package/lib/esm/publish/remove.js.map +1 -0
- package/lib/esm/publish/submit.js +69 -0
- package/lib/esm/publish/submit.js.map +1 -0
- package/lib/esm/publish/support.js +54 -0
- package/lib/esm/publish/support.js.map +1 -0
- package/lib/esm/publish/types.js +2 -0
- package/lib/esm/publish/types.js.map +1 -0
- package/lib/esm/publish/update.js +69 -0
- package/lib/esm/publish/update.js.map +1 -0
- package/lib/esm/types.js +2 -0
- package/lib/esm/types.js.map +1 -0
- package/lib/esm/utils.js +19 -0
- package/lib/esm/utils.js.map +1 -0
- package/lib/esm/validate/generated/appJsonMetadata.js +8 -0
- package/lib/esm/validate/generated/appJsonMetadata.js.map +1 -0
- package/lib/esm/validate/generated/index.js +4 -0
- package/lib/esm/validate/generated/index.js.map +1 -0
- package/lib/esm/validate/generated/publisherJsonMetadata.js +8 -0
- package/lib/esm/validate/generated/publisherJsonMetadata.js.map +1 -0
- package/lib/esm/validate/generated/releaseJsonMetadata.js +8 -0
- package/lib/esm/validate/generated/releaseJsonMetadata.js.map +1 -0
- package/lib/esm/validate/index.js +46 -0
- package/lib/esm/validate/index.js.map +1 -0
- package/lib/esm/validate/schemas/appJsonMetadata.json +57 -0
- package/lib/esm/validate/schemas/publisherJsonMetadata.json +82 -0
- package/lib/esm/validate/schemas/releaseJsonMetadata.json +214 -0
- package/lib/types/create/app.d.ts +11 -0
- package/lib/types/create/app.d.ts.map +1 -0
- package/lib/types/create/index.d.ts +4 -0
- package/lib/types/create/index.d.ts.map +1 -0
- package/lib/types/create/publisher.d.ts +11 -0
- package/lib/types/create/publisher.d.ts.map +1 -0
- package/lib/types/create/release.d.ts +33 -0
- package/lib/types/create/release.d.ts.map +1 -0
- package/lib/types/index.d.ts +5 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/publish/attestation.d.ts +7 -0
- package/lib/types/publish/attestation.d.ts.map +1 -0
- package/lib/types/publish/dapp_publisher_portal.d.ts +25 -0
- package/lib/types/publish/dapp_publisher_portal.d.ts.map +1 -0
- package/lib/types/publish/index.d.ts +6 -0
- package/lib/types/publish/index.d.ts.map +1 -0
- package/lib/types/publish/remove.d.ts +10 -0
- package/lib/types/publish/remove.d.ts.map +1 -0
- package/lib/types/publish/submit.d.ts +11 -0
- package/lib/types/publish/submit.d.ts.map +1 -0
- package/lib/types/publish/support.d.ts +10 -0
- package/lib/types/publish/support.d.ts.map +1 -0
- package/lib/types/publish/types.d.ts +8 -0
- package/lib/types/publish/types.d.ts.map +1 -0
- package/lib/types/publish/update.d.ts +12 -0
- package/lib/types/publish/update.d.ts.map +1 -0
- package/lib/types/types.d.ts +63 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/utils.d.ts +8 -0
- package/lib/types/utils.d.ts.map +1 -0
- package/lib/types/validate/generated/appJsonMetadata.d.ts +27 -0
- package/lib/types/validate/generated/appJsonMetadata.d.ts.map +1 -0
- package/lib/types/validate/generated/index.d.ts +4 -0
- package/lib/types/validate/generated/index.d.ts.map +1 -0
- package/lib/types/validate/generated/publisherJsonMetadata.d.ts +32 -0
- package/lib/types/validate/generated/publisherJsonMetadata.d.ts.map +1 -0
- package/lib/types/validate/generated/releaseJsonMetadata.d.ts +82 -0
- package/lib/types/validate/generated/releaseJsonMetadata.d.ts.map +1 -0
- package/lib/types/validate/index.d.ts +5 -0
- package/lib/types/validate/index.d.ts.map +1 -0
- package/package.json +44 -0
- package/src/create/app.ts +71 -0
- package/src/create/index.ts +3 -0
- package/src/create/publisher.ts +63 -0
- package/src/create/release.ts +195 -0
- package/src/index.ts +4 -0
- package/src/publish/attestation.ts +32 -0
- package/src/publish/dapp_publisher_portal.ts +64 -0
- package/src/publish/index.ts +5 -0
- package/src/publish/remove.ts +109 -0
- package/src/publish/submit.ts +128 -0
- package/src/publish/support.ts +102 -0
- package/src/publish/types.ts +8 -0
- package/src/publish/update.ts +134 -0
- package/src/types/index.d.ts +6 -0
- package/src/types.ts +77 -0
- package/src/utils.ts +40 -0
- package/src/validate/generated/appJsonMetadata.ts +28 -0
- package/src/validate/generated/index.ts +3 -0
- package/src/validate/generated/publisherJsonMetadata.ts +33 -0
- package/src/validate/generated/releaseJsonMetadata.ts +83 -0
- package/src/validate/index.ts +61 -0
- package/src/validate/schemas/appJsonMetadata.json +57 -0
- package/src/validate/schemas/publisherJsonMetadata.json +82 -0
- package/src/validate/schemas/releaseJsonMetadata.json +214 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file was automatically generated by json-schema-to-typescript.
|
|
3
|
+
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
|
4
|
+
* and run json-schema-to-typescript to regenerate this file.
|
|
5
|
+
*/
|
|
6
|
+
export interface ReleaseJsonMetadata {
|
|
7
|
+
schema_version: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
image: string;
|
|
11
|
+
properties: {
|
|
12
|
+
category?: string;
|
|
13
|
+
creators?: {
|
|
14
|
+
address: string;
|
|
15
|
+
[k: string]: unknown;
|
|
16
|
+
}[];
|
|
17
|
+
[k: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
extensions: {
|
|
20
|
+
solana_dapp_store: {
|
|
21
|
+
publisher_details: {
|
|
22
|
+
name: string;
|
|
23
|
+
website: string;
|
|
24
|
+
contact: string;
|
|
25
|
+
[k: string]: unknown;
|
|
26
|
+
};
|
|
27
|
+
release_details: {
|
|
28
|
+
version: string;
|
|
29
|
+
updated_on: string;
|
|
30
|
+
license_url: string;
|
|
31
|
+
copyright_url: string;
|
|
32
|
+
privacy_policy_url: string;
|
|
33
|
+
localized_resources: {
|
|
34
|
+
long_description: string;
|
|
35
|
+
new_in_version: string;
|
|
36
|
+
saga_features_localized: string;
|
|
37
|
+
name: string;
|
|
38
|
+
[k: string]: unknown;
|
|
39
|
+
};
|
|
40
|
+
[k: string]: unknown;
|
|
41
|
+
};
|
|
42
|
+
media: {
|
|
43
|
+
mime: string;
|
|
44
|
+
purpose: string;
|
|
45
|
+
uri: string;
|
|
46
|
+
width: number;
|
|
47
|
+
height: number;
|
|
48
|
+
sha256: string;
|
|
49
|
+
[k: string]: unknown;
|
|
50
|
+
}[];
|
|
51
|
+
files: {
|
|
52
|
+
mime: string;
|
|
53
|
+
purpose: string;
|
|
54
|
+
uri: string;
|
|
55
|
+
size: number;
|
|
56
|
+
sha256: string;
|
|
57
|
+
[k: string]: unknown;
|
|
58
|
+
}[];
|
|
59
|
+
android_details: {
|
|
60
|
+
android_package: string;
|
|
61
|
+
version_code: number;
|
|
62
|
+
min_sdk: number;
|
|
63
|
+
permissions: string[];
|
|
64
|
+
locales: string[];
|
|
65
|
+
[k: string]: unknown;
|
|
66
|
+
};
|
|
67
|
+
[k: string]: unknown;
|
|
68
|
+
};
|
|
69
|
+
i18n: {
|
|
70
|
+
/**
|
|
71
|
+
* This interface was referenced by `undefined`'s JSON-Schema definition
|
|
72
|
+
* via the `patternProperty` "^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{2,8})*$".
|
|
73
|
+
*/
|
|
74
|
+
[k: string]: {
|
|
75
|
+
[k: string]: string;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
[k: string]: unknown;
|
|
79
|
+
};
|
|
80
|
+
[k: string]: unknown;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=releaseJsonMetadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"releaseJsonMetadata.d.ts","sourceRoot":"","sources":["../../../../src/validate/generated/releaseJsonMetadata.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAEH,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE;QACV,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE;YACT,OAAO,EAAE,MAAM,CAAC;YAChB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;SACtB,EAAE,CAAC;QACJ,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KACtB,CAAC;IACF,UAAU,EAAE;QACV,iBAAiB,EAAE;YACjB,iBAAiB,EAAE;gBACjB,IAAI,EAAE,MAAM,CAAC;gBACb,OAAO,EAAE,MAAM,CAAC;gBAChB,OAAO,EAAE,MAAM,CAAC;gBAChB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;aACtB,CAAC;YACF,eAAe,EAAE;gBACf,OAAO,EAAE,MAAM,CAAC;gBAChB,UAAU,EAAE,MAAM,CAAC;gBACnB,WAAW,EAAE,MAAM,CAAC;gBACpB,aAAa,EAAE,MAAM,CAAC;gBACtB,kBAAkB,EAAE,MAAM,CAAC;gBAC3B,mBAAmB,EAAE;oBACnB,gBAAgB,EAAE,MAAM,CAAC;oBACzB,cAAc,EAAE,MAAM,CAAC;oBACvB,uBAAuB,EAAE,MAAM,CAAC;oBAChC,IAAI,EAAE,MAAM,CAAC;oBACb,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;iBACtB,CAAC;gBACF,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;aACtB,CAAC;YACF,KAAK,EAAE;gBACL,IAAI,EAAE,MAAM,CAAC;gBACb,OAAO,EAAE,MAAM,CAAC;gBAChB,GAAG,EAAE,MAAM,CAAC;gBACZ,KAAK,EAAE,MAAM,CAAC;gBACd,MAAM,EAAE,MAAM,CAAC;gBACf,MAAM,EAAE,MAAM,CAAC;gBACf,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;aACtB,EAAE,CAAC;YACJ,KAAK,EAAE;gBACL,IAAI,EAAE,MAAM,CAAC;gBACb,OAAO,EAAE,MAAM,CAAC;gBAChB,GAAG,EAAE,MAAM,CAAC;gBACZ,IAAI,EAAE,MAAM,CAAC;gBACb,MAAM,EAAE,MAAM,CAAC;gBACf,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;aACtB,EAAE,CAAC;YACJ,eAAe,EAAE;gBACf,eAAe,EAAE,MAAM,CAAC;gBACxB,YAAY,EAAE,MAAM,CAAC;gBACrB,OAAO,EAAE,MAAM,CAAC;gBAChB,WAAW,EAAE,MAAM,EAAE,CAAC;gBACtB,OAAO,EAAE,MAAM,EAAE,CAAC;gBAClB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;aACtB,CAAC;YACF,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;SACtB,CAAC;QACF,IAAI,EAAE;YACJ;;;eAGG;YACH,CAAC,CAAC,EAAE,MAAM,GAAG;gBACX,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;aACrB,CAAC;SACH,CAAC;QACF,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KACtB,CAAC;IACF,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AppMetadata, PublisherMetadata, ReleaseJsonMetadata } from "../types.js";
|
|
2
|
+
export declare const validatePublisher: (publisherJson: PublisherMetadata) => true;
|
|
3
|
+
export declare const validateApp: (appJson: AppMetadata) => true;
|
|
4
|
+
export declare const validateRelease: (releaseJson: ReleaseJsonMetadata) => true;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/validate/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,aAAa,CAAC;AASrB,eAAO,MAAM,iBAAiB,kBAAmB,iBAAiB,SAejE,CAAC;AAEF,eAAO,MAAM,WAAW,YAAa,WAAW,SAe/C,CAAC;AAEF,eAAO,MAAM,eAAe,gBAAiB,mBAAmB,SAU/D,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@solana-mobile/dapp-store-publishing-tools",
|
|
3
|
+
"version": "0.1.1-0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./lib/cjs/index.js",
|
|
8
|
+
"module": "./lib/esm/index.js",
|
|
9
|
+
"types": "./lib/types/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
"import": "./lib/esm/index.js",
|
|
12
|
+
"require": "./lib/cjs/index.js",
|
|
13
|
+
"types": "./lib/types/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"lib",
|
|
17
|
+
"src",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/debug": "^4.1.7",
|
|
25
|
+
"@types/mime": "^3.0.1",
|
|
26
|
+
"@types/node-fetch": "^2.6.2",
|
|
27
|
+
"json-schema-to-typescript": "^11.0.2",
|
|
28
|
+
"shx": "^0.3.4"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@metaplex-foundation/js": "^0.17.1",
|
|
32
|
+
"@solana/web3.js": "1.68.0",
|
|
33
|
+
"ajv": "^8.11.0",
|
|
34
|
+
"axios": "1.1.3",
|
|
35
|
+
"debug": "^4.3.4",
|
|
36
|
+
"image-size": "^1.0.2",
|
|
37
|
+
"mime": "^3.0.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"clean": "shx mkdir -p lib && shx rm -rf lib",
|
|
41
|
+
"prebuild": "pnpm run clean",
|
|
42
|
+
"generate-types": "json2ts -i src/validate/schemas -o src/validate/generated"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mintNft } from "../utils.js";
|
|
2
|
+
import type { App, AppMetadata, Context } from "../types.js";
|
|
3
|
+
import type { PublicKey, Signer } from "@solana/web3.js";
|
|
4
|
+
import debugModule from "debug";
|
|
5
|
+
import { validateApp } from "../validate/index.js";
|
|
6
|
+
|
|
7
|
+
const debug = debugModule("APP");
|
|
8
|
+
|
|
9
|
+
export const createAppJson = (
|
|
10
|
+
app: App,
|
|
11
|
+
publisherAddress: PublicKey
|
|
12
|
+
): AppMetadata => {
|
|
13
|
+
const appMetadata = {
|
|
14
|
+
schema_version: "0.2.3",
|
|
15
|
+
name: app.name,
|
|
16
|
+
image: app.icon!,
|
|
17
|
+
external_url: app.urls.website,
|
|
18
|
+
properties: {
|
|
19
|
+
category: "dApp",
|
|
20
|
+
creators: [
|
|
21
|
+
{
|
|
22
|
+
address: publisherAddress.toBase58(),
|
|
23
|
+
share: 100,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
extensions: {
|
|
28
|
+
solana_dapp_store: {
|
|
29
|
+
android_package: app.android_package,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return appMetadata;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type CreateAppInput = {
|
|
38
|
+
publisherMintAddress: PublicKey;
|
|
39
|
+
mintAddress: Signer;
|
|
40
|
+
appDetails: App;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const createApp = async (
|
|
44
|
+
{ publisherMintAddress, mintAddress, appDetails }: CreateAppInput,
|
|
45
|
+
{ metaplex, publisher }: Context
|
|
46
|
+
) => {
|
|
47
|
+
debug(`Minting app NFT for publisher: ${publisherMintAddress.toBase58()}`);
|
|
48
|
+
|
|
49
|
+
const appJson = createAppJson(appDetails, publisher.publicKey);
|
|
50
|
+
validateApp(appJson);
|
|
51
|
+
|
|
52
|
+
const txBuilder = await mintNft(metaplex, appJson, {
|
|
53
|
+
useNewMint: mintAddress,
|
|
54
|
+
collection: publisherMintAddress,
|
|
55
|
+
collectionAuthority: publisher,
|
|
56
|
+
isCollection: true,
|
|
57
|
+
isMutable: true,
|
|
58
|
+
creators: [{ address: publisher.publicKey, share: 100 }],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
txBuilder.append(
|
|
62
|
+
metaplex.nfts().builders().verifyCreator({
|
|
63
|
+
mintAddress: mintAddress.publicKey,
|
|
64
|
+
creator: publisher,
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
debug({ appNft: mintAddress.publicKey.toBase58() });
|
|
69
|
+
|
|
70
|
+
return txBuilder;
|
|
71
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { TransactionBuilder } from "@metaplex-foundation/js";
|
|
2
|
+
import debugModule from "debug";
|
|
3
|
+
import type { Signer } from "@solana/web3.js";
|
|
4
|
+
|
|
5
|
+
import { validatePublisher } from "../validate/index.js";
|
|
6
|
+
import { mintNft } from "../utils.js";
|
|
7
|
+
import type { Context, Publisher, PublisherMetadata } from "../types.js";
|
|
8
|
+
|
|
9
|
+
const debug = debugModule("PUBLISHER");
|
|
10
|
+
|
|
11
|
+
export const createPublisherJson = (
|
|
12
|
+
publisher: Publisher
|
|
13
|
+
): PublisherMetadata => {
|
|
14
|
+
const publisherMetadata = {
|
|
15
|
+
schema_version: "0.2.3",
|
|
16
|
+
name: publisher.name,
|
|
17
|
+
image: publisher.icon!,
|
|
18
|
+
external_url: publisher.website,
|
|
19
|
+
properties: {
|
|
20
|
+
category: "dApp",
|
|
21
|
+
creators: [
|
|
22
|
+
{
|
|
23
|
+
address: publisher.address,
|
|
24
|
+
share: 100,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
extensions: {
|
|
29
|
+
solana_dapp_store: {
|
|
30
|
+
publisher_details: {
|
|
31
|
+
name: publisher.name,
|
|
32
|
+
website: publisher.website,
|
|
33
|
+
contact: publisher.email,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return publisherMetadata;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type CreatePublisherInput = {
|
|
43
|
+
mintAddress: Signer;
|
|
44
|
+
publisherDetails: Publisher;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const createPublisher = async (
|
|
48
|
+
{ mintAddress, publisherDetails }: CreatePublisherInput,
|
|
49
|
+
{ metaplex }: Context
|
|
50
|
+
): Promise<TransactionBuilder> => {
|
|
51
|
+
debug(`Minting publisher NFT`);
|
|
52
|
+
|
|
53
|
+
const publisherJson = createPublisherJson(publisherDetails);
|
|
54
|
+
validatePublisher(publisherJson);
|
|
55
|
+
|
|
56
|
+
const txBuilder = await mintNft(metaplex, publisherJson, {
|
|
57
|
+
isCollection: true,
|
|
58
|
+
isMutable: true,
|
|
59
|
+
useNewMint: mintAddress,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return txBuilder;
|
|
63
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
import mime from "mime";
|
|
5
|
+
import debugModule from "debug";
|
|
6
|
+
import type { MetaplexFile } from "@metaplex-foundation/js";
|
|
7
|
+
import { toMetaplexFile } from "@metaplex-foundation/js";
|
|
8
|
+
import { mintNft, truncateAddress } from "../utils.js";
|
|
9
|
+
import * as util from "util";
|
|
10
|
+
import { validateRelease } from "../validate/index.js";
|
|
11
|
+
import { imageSize } from "image-size";
|
|
12
|
+
|
|
13
|
+
import type { Keypair, PublicKey } from "@solana/web3.js";
|
|
14
|
+
import type {
|
|
15
|
+
App,
|
|
16
|
+
Context,
|
|
17
|
+
Publisher,
|
|
18
|
+
Release,
|
|
19
|
+
ReleaseJsonMetadata,
|
|
20
|
+
} from "../types.js";
|
|
21
|
+
|
|
22
|
+
const runImgSize = util.promisify(imageSize);
|
|
23
|
+
const debug = debugModule("RELEASE");
|
|
24
|
+
|
|
25
|
+
type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;
|
|
26
|
+
type File = ArrayElement<Release["files"]>;
|
|
27
|
+
type Media = ArrayElement<Release["media"]>;
|
|
28
|
+
|
|
29
|
+
const getFileMetadata = async (item: Media | File) => {
|
|
30
|
+
const file = path.join(process.cwd(), item.uri);
|
|
31
|
+
debug({ file });
|
|
32
|
+
// TODO(jon): This stuff should be probably be in `packages/cli`
|
|
33
|
+
const mediaBuffer = await fs.promises.readFile(file);
|
|
34
|
+
const size = (await fs.promises.stat(file)).size;
|
|
35
|
+
const hash = createHash("sha256").update(mediaBuffer).digest("base64");
|
|
36
|
+
const metadata = {
|
|
37
|
+
purpose: item.purpose,
|
|
38
|
+
uri: toMetaplexFile(mediaBuffer, item.uri),
|
|
39
|
+
mime: mime.getType(item.uri) || "",
|
|
40
|
+
size,
|
|
41
|
+
sha256: hash,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return metadata;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const getMediaMetadata = async (item: Media) => {
|
|
48
|
+
const size = await runImgSize(item.uri);
|
|
49
|
+
const metadata = await getFileMetadata(item);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
...metadata,
|
|
53
|
+
width: size?.width ?? 0,
|
|
54
|
+
height: size?.height ?? 0,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type MetaplexFileReleaseJsonMetadata = ReleaseJsonMetadata & {
|
|
59
|
+
extensions: {
|
|
60
|
+
solana_dapp_store: {
|
|
61
|
+
media: { uri: MetaplexFile }[];
|
|
62
|
+
files: { uri: MetaplexFile }[];
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const createReleaseJson = async (
|
|
68
|
+
{
|
|
69
|
+
releaseDetails,
|
|
70
|
+
appDetails,
|
|
71
|
+
publisherDetails,
|
|
72
|
+
}: { releaseDetails: Release; appDetails: App; publisherDetails: Publisher },
|
|
73
|
+
publisherAddress: PublicKey
|
|
74
|
+
): Promise<MetaplexFileReleaseJsonMetadata> => {
|
|
75
|
+
const truncatedAppMintAddress = truncateAddress(appDetails.address);
|
|
76
|
+
|
|
77
|
+
const releaseName = `${truncatedAppMintAddress} ${releaseDetails.version}`;
|
|
78
|
+
|
|
79
|
+
const media = [];
|
|
80
|
+
debug({ media: releaseDetails.media });
|
|
81
|
+
for await (const item of releaseDetails.media || []) {
|
|
82
|
+
media.push(await getMediaMetadata(item));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const files = [];
|
|
86
|
+
debug({ files: releaseDetails.files });
|
|
87
|
+
for await (const item of releaseDetails.files) {
|
|
88
|
+
files.push(await getFileMetadata(item));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const releaseMetadata: MetaplexFileReleaseJsonMetadata = {
|
|
92
|
+
schema_version: "0.2.3",
|
|
93
|
+
name: releaseName,
|
|
94
|
+
description: Object.values(releaseDetails.catalog)[0].short_description,
|
|
95
|
+
// TODO(jon): Figure out where to get this image
|
|
96
|
+
image: "",
|
|
97
|
+
external_url: appDetails.urls.website,
|
|
98
|
+
properties: {
|
|
99
|
+
category: "dApp",
|
|
100
|
+
creators: [
|
|
101
|
+
{
|
|
102
|
+
address: publisherAddress.toBase58(),
|
|
103
|
+
share: 100,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
extensions: {
|
|
108
|
+
solana_dapp_store: {
|
|
109
|
+
publisher_details: {
|
|
110
|
+
name: publisherDetails.name,
|
|
111
|
+
website: publisherDetails.website,
|
|
112
|
+
contact: publisherDetails.email,
|
|
113
|
+
},
|
|
114
|
+
release_details: {
|
|
115
|
+
version: releaseDetails.version,
|
|
116
|
+
updated_on: new Date().toISOString(),
|
|
117
|
+
license_url: appDetails.urls.license_url,
|
|
118
|
+
copyright_url: appDetails.urls.copyright_url,
|
|
119
|
+
privacy_policy_url: appDetails.urls.privacy_policy_url,
|
|
120
|
+
localized_resources: {
|
|
121
|
+
short_description: "1",
|
|
122
|
+
long_description: "2",
|
|
123
|
+
new_in_version: "3",
|
|
124
|
+
saga_features_localized: "4",
|
|
125
|
+
name: "5",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
// @ts-expect-error It's a bit of a headache to modify the deeply-nested extension.solana_dapp_store.media.uri type
|
|
129
|
+
media,
|
|
130
|
+
// @ts-expect-error It's a bit of a headache to modify the deeply-nested extension.solana_dapp_store.files.uri type
|
|
131
|
+
files,
|
|
132
|
+
android_details: releaseDetails.android_details,
|
|
133
|
+
},
|
|
134
|
+
i18n: {},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
for (const [locale, strings] of Object.entries(releaseDetails.catalog)) {
|
|
139
|
+
releaseMetadata.extensions.i18n[locale] = {
|
|
140
|
+
"1": strings.short_description,
|
|
141
|
+
"2": strings.long_description,
|
|
142
|
+
"3": strings.new_in_version,
|
|
143
|
+
"4": strings.saga_features_localized,
|
|
144
|
+
"5": strings.name,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return releaseMetadata;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
type CreateReleaseInput = {
|
|
152
|
+
releaseMintAddress: Keypair;
|
|
153
|
+
appMintAddress: PublicKey;
|
|
154
|
+
releaseDetails: Release;
|
|
155
|
+
publisherDetails: Publisher;
|
|
156
|
+
appDetails: App;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const createRelease = async (
|
|
160
|
+
{
|
|
161
|
+
appMintAddress,
|
|
162
|
+
releaseMintAddress,
|
|
163
|
+
releaseDetails,
|
|
164
|
+
appDetails,
|
|
165
|
+
publisherDetails,
|
|
166
|
+
}: CreateReleaseInput,
|
|
167
|
+
{ publisher, metaplex }: Context
|
|
168
|
+
) => {
|
|
169
|
+
debug(`Minting release NFT for: ${appMintAddress.toBase58()}`);
|
|
170
|
+
|
|
171
|
+
const releaseJson = await createReleaseJson(
|
|
172
|
+
{ releaseDetails, appDetails, publisherDetails },
|
|
173
|
+
publisher.publicKey
|
|
174
|
+
);
|
|
175
|
+
validateRelease(releaseJson);
|
|
176
|
+
|
|
177
|
+
const txBuilder = await mintNft(metaplex, releaseJson, {
|
|
178
|
+
useNewMint: releaseMintAddress,
|
|
179
|
+
collection: appMintAddress,
|
|
180
|
+
collectionAuthority: publisher,
|
|
181
|
+
creators: [{ address: publisher.publicKey, share: 100 }],
|
|
182
|
+
isMutable: false,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// TODO(jon): Enable a case where the signer is not the publisher
|
|
186
|
+
// TODO(jon): Allow this to be unverified and to verify later
|
|
187
|
+
txBuilder.append(
|
|
188
|
+
metaplex.nfts().builders().verifyCreator({
|
|
189
|
+
mintAddress: releaseMintAddress.publicKey,
|
|
190
|
+
creator: publisher,
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
return { releaseJson, txBuilder };
|
|
195
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Connection, GetVersionedBlockConfig } from "@solana/web3.js";
|
|
2
|
+
import { SignWithPublisherKeypair } from "./types";
|
|
3
|
+
|
|
4
|
+
//
|
|
5
|
+
// Construct and sign attestation payloads
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
type Attestation = {
|
|
9
|
+
slot_number: number;
|
|
10
|
+
blockhash: string;
|
|
11
|
+
request_unique_id: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const createAttestationPayload = async (connection: Connection, sign: SignWithPublisherKeypair) => {
|
|
15
|
+
const REQUEST_UNIQUE_ID_LEN = 32;
|
|
16
|
+
const REQUEST_UNIQUE_ID_CHAR_SET = "0123456789";
|
|
17
|
+
const requestUniqueId = Array(REQUEST_UNIQUE_ID_LEN).fill(undefined).map((_) =>
|
|
18
|
+
REQUEST_UNIQUE_ID_CHAR_SET.charAt(Math.floor(Math.random() * REQUEST_UNIQUE_ID_CHAR_SET.length))
|
|
19
|
+
).join("")
|
|
20
|
+
|
|
21
|
+
const slot = await connection.getSlot("finalized");
|
|
22
|
+
const block = await connection.getBlock(slot, { commitment: "finalized", maxSupportedTransactionVersion: 100, rewards: false, transactionDetails: "none" } as GetVersionedBlockConfig);
|
|
23
|
+
|
|
24
|
+
const attestation: Attestation = {
|
|
25
|
+
slot_number: slot,
|
|
26
|
+
blockhash: block?.blockhash!!,
|
|
27
|
+
request_unique_id: requestUniqueId
|
|
28
|
+
};
|
|
29
|
+
const signedAttestation = sign(Buffer.from(JSON.stringify(attestation)));
|
|
30
|
+
|
|
31
|
+
return { attestationPayload: Buffer.from(signedAttestation.buffer).toString("base64"), requestUniqueId };
|
|
32
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import axios, { AxiosRequestConfig } from "axios";
|
|
2
|
+
|
|
3
|
+
export const PORTAL_ID = "22812690";
|
|
4
|
+
|
|
5
|
+
export const CONTACT_OBJECT_ID = "0-1";
|
|
6
|
+
export const CONTACT_PROPERTY_COMPANY = "company"; // string
|
|
7
|
+
export const CONTACT_PROPERTY_EMAIL = "email"; // string
|
|
8
|
+
export const CONTACT_PROPERTY_WEBSITE = "website"; // string
|
|
9
|
+
|
|
10
|
+
export const TICKET_OBJECT_ID = "0-5";
|
|
11
|
+
export const TICKET_PROPERTY_CONTENT = "content"; // string
|
|
12
|
+
export const TICKET_PROPERTY_ATTESTATION_PAYLOAD = "attestation_payload"; // base64-encoded string
|
|
13
|
+
export const TICKET_PROPERTY_AUTHORIZED_REQUEST = "requestor_is_authorized_to_submit_this_request"; // boolean
|
|
14
|
+
export const TICKET_PROPERTY_CRITICAL_UPDATE = "critical_update"; // boolean
|
|
15
|
+
export const TICKET_PROPERTY_DAPP_RELEASE_ACCOUNT_ADDRESS = "dapp_release_account_address"; // base58-encoded string
|
|
16
|
+
export const TICKET_PROPERTY_GOOGLE_PLAY_STORE_PACKAGE_NAME = "google_play_store_package_name"; // string
|
|
17
|
+
export const TICKET_PROPERTY_POLICY_COMPLIANT = "complies_with_solana_dapp_store_policies"; // boolean
|
|
18
|
+
export const TICKET_PROPERTY_REQUEST_UNIQUE_ID = "request_unique_id"; // string (32 base-10 digits)
|
|
19
|
+
export const TICKET_PROPERTY_TESTING_INSTRUCTIONS = "testing_instructions"; // string
|
|
20
|
+
|
|
21
|
+
export const FORM_SUBMIT = "1464247f-6804-46e1-8114-952f372daa81";
|
|
22
|
+
export const FORM_UPDATE = "87b4cbe7-957f-495c-a132-8b789678883d";
|
|
23
|
+
export const FORM_REMOVE = "913a4e44-ec90-4db6-8aa9-c49f29b569b9";
|
|
24
|
+
export const FORM_SUPPORT = "2961f018-6a4d-4e9d-8332-e219428c8cf2";
|
|
25
|
+
|
|
26
|
+
export const URL_FORM_SUBMIT = `https://api.hsforms.com/submissions/v3/integration/submit/${PORTAL_ID}/${FORM_SUBMIT}`
|
|
27
|
+
export const URL_FORM_UPDATE = `https://api.hsforms.com/submissions/v3/integration/submit/${PORTAL_ID}/${FORM_UPDATE}`
|
|
28
|
+
export const URL_FORM_REMOVE = `https://api.hsforms.com/submissions/v3/integration/submit/${PORTAL_ID}/${FORM_REMOVE}`
|
|
29
|
+
export const URL_FORM_SUPPORT = `https://api.hsforms.com/submissions/v3/integration/submit/${PORTAL_ID}/${FORM_SUPPORT}`
|
|
30
|
+
|
|
31
|
+
export const submitRequestToSolanaDappPublisherPortal = async (
|
|
32
|
+
request: any,
|
|
33
|
+
url: string,
|
|
34
|
+
dryRun: boolean
|
|
35
|
+
) => {
|
|
36
|
+
const config = {
|
|
37
|
+
method: "POST",
|
|
38
|
+
url: url,
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
},
|
|
42
|
+
data: JSON.stringify(request),
|
|
43
|
+
} as AxiosRequestConfig;
|
|
44
|
+
|
|
45
|
+
console.info("Sending dApp publisher portal request:", config);
|
|
46
|
+
|
|
47
|
+
if (!dryRun) {
|
|
48
|
+
await axios(config)
|
|
49
|
+
.then((response) => {
|
|
50
|
+
console.info(`dApp publisher portal response:`, response.data);
|
|
51
|
+
})
|
|
52
|
+
.catch((error) => {
|
|
53
|
+
if (error.response) {
|
|
54
|
+
console.error(`ERROR: failed to submit request to dApp publisher portal: ${error.response.status} /`, error.response.data);
|
|
55
|
+
} else if (error.request) {
|
|
56
|
+
console.error(`ERROR: failed sending request to dApp publisher portal:`, error.request);
|
|
57
|
+
} else {
|
|
58
|
+
console.error(`ERROR: failed sending request:`, error);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
console.warn("Dry run, not actually sending request to dApp publisher portal");
|
|
63
|
+
}
|
|
64
|
+
};
|