@blocklet/meta 1.15.17 → 1.16.0-beta-b16cb035
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/channel.d.ts +32 -0
- package/lib/channel.js +54 -0
- package/lib/constants.d.ts +2 -0
- package/lib/constants.js +5 -152
- package/lib/did.d.ts +3 -0
- package/lib/did.js +9 -9
- package/lib/engine.d.ts +7 -0
- package/lib/engine.js +21 -25
- package/lib/entry.d.ts +3 -0
- package/lib/entry.js +51 -64
- package/lib/extension.d.ts +14 -0
- package/lib/extension.js +82 -77
- package/lib/file.d.ts +23 -0
- package/lib/file.js +51 -36
- package/lib/fix.d.ts +36 -0
- package/lib/fix.js +231 -228
- package/lib/get-component-process-id.d.ts +5 -0
- package/lib/get-component-process-id.js +16 -0
- package/lib/has-reserved-key.d.ts +3 -0
- package/lib/has-reserved-key.js +15 -0
- package/lib/index.d.ts +86 -0
- package/lib/index.js +55 -34
- package/lib/info.d.ts +15 -0
- package/lib/info.js +70 -38
- package/lib/name.d.ts +15 -0
- package/lib/name.js +41 -8
- package/lib/nft-templates.d.ts +86 -0
- package/lib/nft-templates.js +52 -0
- package/lib/parse-navigation-from-blocklet.d.ts +92 -0
- package/lib/parse-navigation-from-blocklet.js +539 -0
- package/lib/parse-navigation.d.ts +3 -0
- package/lib/parse-navigation.js +197 -0
- package/lib/parse.d.ts +22 -0
- package/lib/parse.js +100 -89
- package/lib/payment/index.d.ts +254 -0
- package/lib/payment/index.js +14 -0
- package/lib/payment/v1.d.ts +185 -0
- package/lib/payment/v1.js +84 -0
- package/lib/payment/v2.d.ts +242 -0
- package/lib/payment/v2.js +576 -0
- package/lib/schema.d.ts +63 -0
- package/lib/schema.js +669 -283
- package/lib/service.d.ts +27 -0
- package/lib/service.js +71 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +18 -0
- package/lib/types/schema.d.ts +284 -0
- package/lib/types/schema.js +3 -0
- package/lib/url-friendly.d.ts +6 -0
- package/lib/url-friendly.js +20 -0
- package/lib/util-meta.d.ts +42 -0
- package/lib/util-meta.js +146 -0
- package/lib/util.d.ts +201 -0
- package/lib/util.js +501 -82
- package/lib/validate.d.ts +13 -0
- package/lib/validate.js +37 -61
- package/lib/verify-multi-sig.d.ts +3 -0
- package/lib/verify-multi-sig.js +86 -59
- package/lib/wallet.d.ts +9 -0
- package/lib/wallet.js +19 -30
- package/package.json +59 -20
- package/lib/payment.js +0 -114
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.version = exports.checkFreeBlocklet = exports.verifyNftFactory = exports.verifyPaymentIntegrity = exports.createNftFactoryItx = exports._test = void 0;
|
|
30
|
+
/* eslint-disable no-await-in-loop */
|
|
31
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
32
|
+
const debug_1 = __importDefault(require("debug"));
|
|
33
|
+
const url_join_1 = __importDefault(require("url-join"));
|
|
34
|
+
const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
|
|
35
|
+
const get_1 = __importDefault(require("lodash/get"));
|
|
36
|
+
const pick_1 = __importDefault(require("lodash/pick"));
|
|
37
|
+
const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
|
|
38
|
+
const axios_1 = __importDefault(require("@abtnode/util/lib/axios"));
|
|
39
|
+
const util_1 = require("@ocap/util");
|
|
40
|
+
const asset_1 = require("@ocap/asset");
|
|
41
|
+
const wallet_1 = require("@ocap/wallet");
|
|
42
|
+
const did_util_1 = require("@arcblock/did-util");
|
|
43
|
+
const did = __importStar(require("@arcblock/did"));
|
|
44
|
+
const constant_1 = __importDefault(require("@abtnode/constant"));
|
|
45
|
+
const nft_templates_1 = require("../nft-templates");
|
|
46
|
+
const validate_1 = require("../validate");
|
|
47
|
+
const util_2 = require("../util");
|
|
48
|
+
const util_meta_1 = require("../util-meta");
|
|
49
|
+
const debug = (0, debug_1.default)('@blocklet/meta:payment');
|
|
50
|
+
const { toTypeInfo } = did;
|
|
51
|
+
const { BLOCKLET_STORE_META_PATH } = constant_1.default;
|
|
52
|
+
const VERSION = '2.0.0';
|
|
53
|
+
exports.version = VERSION;
|
|
54
|
+
const ZeroBN = new util_1.BN(0);
|
|
55
|
+
const defaultDecimals = 1e6; // we only support 6 decimals on share ratio
|
|
56
|
+
const defaultDecimalsBN = new util_1.BN(defaultDecimals);
|
|
57
|
+
const getComponentConfig = (meta) => []
|
|
58
|
+
.concat(meta.components || meta.children)
|
|
59
|
+
.concat(meta.staticComponents)
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
const safeMul = (a, b) => Number((0, util_1.fromUnitToToken)((0, util_1.fromTokenToUnit)(a)
|
|
62
|
+
.mul(new util_1.BN(b * defaultDecimals))
|
|
63
|
+
.div(defaultDecimalsBN)));
|
|
64
|
+
const md5 = (str) => crypto_1.default.createHash('md5').update(str).digest('hex');
|
|
65
|
+
const getStoreInfo = async (url) => {
|
|
66
|
+
const storeMetaUrl = (0, url_join_1.default)(new URL(url).origin, BLOCKLET_STORE_META_PATH);
|
|
67
|
+
const { data: info } = await axios_1.default.get(storeMetaUrl, { timeout: 8000 });
|
|
68
|
+
return info;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {{
|
|
72
|
+
* meta: Object
|
|
73
|
+
* storeInfo: Object
|
|
74
|
+
* storeUrl: string
|
|
75
|
+
* children: Array<Component>
|
|
76
|
+
* }} Component
|
|
77
|
+
*
|
|
78
|
+
* @param {TBlockletMeta} inputMeta
|
|
79
|
+
* @param {{
|
|
80
|
+
* ancestors: Array<{TBlockletMeta}>
|
|
81
|
+
* bundles: {
|
|
82
|
+
* <bundleName>: <storeId>
|
|
83
|
+
* }
|
|
84
|
+
* }} context
|
|
85
|
+
*
|
|
86
|
+
* @returns {Array<Component>}
|
|
87
|
+
*/
|
|
88
|
+
const innerGetComponents = async (inputMeta, context = {}) => {
|
|
89
|
+
// FIXME 是否需要验证: 在同一个链上; 重复的 component
|
|
90
|
+
const { ancestors = [], bundles = {} } = context;
|
|
91
|
+
// check ancestor length
|
|
92
|
+
if (ancestors.length > 40) {
|
|
93
|
+
throw new Error('The depth of component should not exceed 40');
|
|
94
|
+
}
|
|
95
|
+
const configs = getComponentConfig(inputMeta);
|
|
96
|
+
if (!configs || !configs.length) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
const children = [];
|
|
100
|
+
for (const config of configs) {
|
|
101
|
+
// get component meta
|
|
102
|
+
const urls = (0, util_meta_1.getSourceUrlsFromConfig)(config);
|
|
103
|
+
let meta;
|
|
104
|
+
let url;
|
|
105
|
+
try {
|
|
106
|
+
const res = await (0, util_meta_1.getBlockletMetaFromUrls)(urls, {
|
|
107
|
+
returnUrl: true,
|
|
108
|
+
validateFn: (m) => (0, validate_1.validateMeta)(m),
|
|
109
|
+
ensureTarball: false,
|
|
110
|
+
});
|
|
111
|
+
meta = res.meta;
|
|
112
|
+
url = res.url;
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
throw new Error(`Failed get component meta: ${config.title || config.name}: ${err.message}`);
|
|
116
|
+
}
|
|
117
|
+
// check is component
|
|
118
|
+
if (!(0, util_2.isComponentBlocklet)(meta)) {
|
|
119
|
+
throw new Error(`The blocklet cannot be a component: ${meta.title}`);
|
|
120
|
+
}
|
|
121
|
+
// check circular dependencies
|
|
122
|
+
if (ancestors.map((x) => x.meta?.did).indexOf(meta.did) > -1) {
|
|
123
|
+
throw new Error('Blocklet components have circular dependencies');
|
|
124
|
+
}
|
|
125
|
+
// generate child
|
|
126
|
+
const child = {
|
|
127
|
+
meta,
|
|
128
|
+
};
|
|
129
|
+
// child store info
|
|
130
|
+
if (config.source.store) {
|
|
131
|
+
const storeInfo = await getStoreInfo(url);
|
|
132
|
+
// check uniq bundle did in different stores
|
|
133
|
+
if (!bundles[child.meta.did]) {
|
|
134
|
+
bundles[child.meta.did] = storeInfo.id;
|
|
135
|
+
}
|
|
136
|
+
else if (bundles[child.meta.did] !== storeInfo.id) {
|
|
137
|
+
throw new Error('Bundles with the same did cannot in different stores');
|
|
138
|
+
}
|
|
139
|
+
child.storeInfo = storeInfo;
|
|
140
|
+
child.storeUrl = new URL(url).origin;
|
|
141
|
+
}
|
|
142
|
+
// child children
|
|
143
|
+
child.children = await innerGetComponents(meta, {
|
|
144
|
+
ancestors: [...ancestors, { meta }],
|
|
145
|
+
bundles,
|
|
146
|
+
});
|
|
147
|
+
children.push(child);
|
|
148
|
+
}
|
|
149
|
+
return children;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* @param {Array<Component>} components
|
|
153
|
+
* @param {Array<Store>} _stores
|
|
154
|
+
* @returns {Array<Store>}
|
|
155
|
+
*/
|
|
156
|
+
const getStores = (components, _stores = []) => {
|
|
157
|
+
for (const { meta, storeInfo, storeUrl, children } of components) {
|
|
158
|
+
if (storeInfo && (!(0, util_2.isFreeBlocklet)(meta) || !(0, util_2.isFreeComponent)(meta))) {
|
|
159
|
+
const store = _stores.find((x) => x.id === storeInfo.id);
|
|
160
|
+
if (!store) {
|
|
161
|
+
_stores.push({
|
|
162
|
+
id: storeInfo.id,
|
|
163
|
+
pk: storeInfo.pk,
|
|
164
|
+
url: storeUrl,
|
|
165
|
+
components: [{ did: meta.did, version: meta.version }],
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
else if (!store.components.some((x) => x.did === meta.did && x.version === meta.version)) {
|
|
169
|
+
store.components.push({ did: meta.did, version: meta.version });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (children && children.length > 0) {
|
|
173
|
+
getStores(children, _stores);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return _stores;
|
|
177
|
+
};
|
|
178
|
+
const getComponents = async (inputMeta) => {
|
|
179
|
+
const components = await innerGetComponents(inputMeta);
|
|
180
|
+
const stores = await getStores(components);
|
|
181
|
+
return { components, stores };
|
|
182
|
+
};
|
|
183
|
+
const getPriceTokens = async (meta, ocapClient) => {
|
|
184
|
+
const priceTokens = (0, cloneDeep_1.default)((0, get_1.default)(meta, 'payment.price', []));
|
|
185
|
+
for (const token of priceTokens) {
|
|
186
|
+
// eslint-disable-next-line no-await-in-loop
|
|
187
|
+
const { state } = await ocapClient.getTokenState({ address: token.address });
|
|
188
|
+
if (!state) {
|
|
189
|
+
throw new Error(`Token specified in blocklet meta was not found on chain: ${token.address}`);
|
|
190
|
+
}
|
|
191
|
+
token.decimal = state.decimal;
|
|
192
|
+
}
|
|
193
|
+
return priceTokens;
|
|
194
|
+
};
|
|
195
|
+
const getChildShare = (childMeta, parentPrice) => {
|
|
196
|
+
if (!childMeta?.payment?.componentPrice) {
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
const priceList = childMeta.payment.componentPrice;
|
|
200
|
+
let price = 0;
|
|
201
|
+
for (const { type, value, parentPriceRange } of priceList) {
|
|
202
|
+
const isDefault = !parentPriceRange || !parentPriceRange.length;
|
|
203
|
+
const skip = isDefault && price !== 0;
|
|
204
|
+
const inRange = isDefault || (parentPriceRange && parentPrice >= parentPriceRange[0] && parentPrice <= parentPriceRange[1]);
|
|
205
|
+
if (!skip && inRange) {
|
|
206
|
+
if (type === 'fixed') {
|
|
207
|
+
price = value;
|
|
208
|
+
}
|
|
209
|
+
else if (type === 'percentage') {
|
|
210
|
+
price = safeMul(parentPrice, value);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return price;
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* @returns {Array<{
|
|
218
|
+
* tokenAddress: string
|
|
219
|
+
* accountAddress: string
|
|
220
|
+
* amount: BN
|
|
221
|
+
* }>}
|
|
222
|
+
*/
|
|
223
|
+
const getTokenTransfers = ({ priceToken, shares = [], components = [], }) => {
|
|
224
|
+
// check share
|
|
225
|
+
const shareSum = shares.reduce((sum, x) => sum + x.value, 0);
|
|
226
|
+
if (shareSum > 1) {
|
|
227
|
+
throw new Error('payment.share invalid: share sum should not be greater than 1');
|
|
228
|
+
}
|
|
229
|
+
const { value: price } = priceToken;
|
|
230
|
+
let parentShareBN = (0, util_1.fromTokenToUnit)(price, priceToken.decimal);
|
|
231
|
+
const contracts = [];
|
|
232
|
+
for (const child of components) {
|
|
233
|
+
if (!(0, util_2.isFreeComponent)(child.meta)) {
|
|
234
|
+
// // check same token
|
|
235
|
+
const [token] = child.meta.payment.price || [];
|
|
236
|
+
if (token && token.address !== priceToken.address) {
|
|
237
|
+
throw new Error(`The token address of the component "${child.meta.title || child.meta.name}" is inconsistent with the blocklet. Component: ${priceToken.address}, Composite Blocklet: ${token.address}`);
|
|
238
|
+
}
|
|
239
|
+
const childShare = getChildShare(child.meta, price);
|
|
240
|
+
parentShareBN = parentShareBN.sub((0, util_1.fromTokenToUnit)(childShare, priceToken.decimal));
|
|
241
|
+
const componentContracts = getTokenTransfers({
|
|
242
|
+
priceToken: { ...priceToken, value: childShare },
|
|
243
|
+
shares: child.meta.payment.share,
|
|
244
|
+
components: child.children || [],
|
|
245
|
+
});
|
|
246
|
+
contracts.push(...componentContracts);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (parentShareBN.lt(ZeroBN)) {
|
|
250
|
+
const needPrice = (0, util_1.fromUnitToToken)((0, util_1.fromTokenToUnit)(price, priceToken.decimal).sub(parentShareBN));
|
|
251
|
+
throw new Error(`Price for composite blocklet must be greater than ${needPrice} because paid components are included.`);
|
|
252
|
+
}
|
|
253
|
+
shares.forEach(({ name, address: accountAddress, value: ratio }) => {
|
|
254
|
+
contracts.push({
|
|
255
|
+
tokenAddress: priceToken.address,
|
|
256
|
+
accountName: name,
|
|
257
|
+
accountAddress,
|
|
258
|
+
amount: parentShareBN.mul(new util_1.BN(ratio * defaultDecimals)).div(defaultDecimalsBN),
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
const mergedContracts = [];
|
|
262
|
+
contracts.forEach((x) => {
|
|
263
|
+
const index = mergedContracts.findIndex((y) => y.tokenAddress === x.tokenAddress && y.accountAddress === x.accountAddress);
|
|
264
|
+
if (index > -1) {
|
|
265
|
+
mergedContracts[index].amount = mergedContracts[index].amount.add(x.amount);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
mergedContracts.push(x);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
return mergedContracts;
|
|
272
|
+
};
|
|
273
|
+
const getContract = async ({ meta, priceTokens, components, }) => {
|
|
274
|
+
const shares = meta.payment.share || [];
|
|
275
|
+
const [priceToken] = priceTokens;
|
|
276
|
+
const contracts = getTokenTransfers({ priceToken, shares, components });
|
|
277
|
+
const code = contracts
|
|
278
|
+
.map((x) => `transferToken('${x.tokenAddress}','${x.accountAddress}','${x.amount.toString()}')`)
|
|
279
|
+
.join(';\n');
|
|
280
|
+
const shareList = contracts.map((x) => ({
|
|
281
|
+
...x,
|
|
282
|
+
amount: (0, util_1.fromUnitToToken)(x.amount, priceToken.decimal),
|
|
283
|
+
}));
|
|
284
|
+
return {
|
|
285
|
+
code,
|
|
286
|
+
shares: shareList,
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* we need to ensure that blocklet purchase factory does not change across changes
|
|
291
|
+
*
|
|
292
|
+
* @typedef {{
|
|
293
|
+
* data: {
|
|
294
|
+
* type: 'json'
|
|
295
|
+
* value: {
|
|
296
|
+
* did: string
|
|
297
|
+
* url: string
|
|
298
|
+
* name: string
|
|
299
|
+
* version: string
|
|
300
|
+
* payment: {
|
|
301
|
+
* version: string
|
|
302
|
+
* }
|
|
303
|
+
* stores: Array<{
|
|
304
|
+
* signer: string
|
|
305
|
+
* pk: string
|
|
306
|
+
* signature: string
|
|
307
|
+
* components: Array<{did: string, version: string}>
|
|
308
|
+
* paymentIntegrity: string
|
|
309
|
+
* }>
|
|
310
|
+
* }
|
|
311
|
+
* }
|
|
312
|
+
* }} Itx
|
|
313
|
+
* @returns {Itx}
|
|
314
|
+
*/
|
|
315
|
+
const innerCreateNftFactoryItx = ({ meta, issuers, serviceUrl, storeSignatures, factoryInput, contract, }) => {
|
|
316
|
+
const factoryOutput = (0, nft_templates_1.getBlockletPurchaseTemplate)(serviceUrl);
|
|
317
|
+
const itx = {
|
|
318
|
+
name: meta.title || meta.name,
|
|
319
|
+
description: `Purchase NFT factory for blocklet ${meta.name}`,
|
|
320
|
+
settlement: 'instant',
|
|
321
|
+
limit: 0,
|
|
322
|
+
trustedIssuers: issuers,
|
|
323
|
+
input: factoryInput,
|
|
324
|
+
output: {
|
|
325
|
+
issuer: '{{ctx.issuer.id}}',
|
|
326
|
+
parent: '{{ctx.factory}}',
|
|
327
|
+
moniker: 'BlockletPurchaseNFT',
|
|
328
|
+
readonly: true,
|
|
329
|
+
transferrable: false,
|
|
330
|
+
data: factoryOutput,
|
|
331
|
+
},
|
|
332
|
+
data: {
|
|
333
|
+
type: 'json',
|
|
334
|
+
value: {
|
|
335
|
+
did: meta.did,
|
|
336
|
+
url: (0, url_join_1.default)(serviceUrl, `/blocklet/${meta.did}`),
|
|
337
|
+
name: meta.name,
|
|
338
|
+
version: meta.version,
|
|
339
|
+
payment: {
|
|
340
|
+
version: VERSION,
|
|
341
|
+
},
|
|
342
|
+
stores: storeSignatures.map((x) => (0, pick_1.default)(x, ['signer', 'pk', 'signature', 'components', 'paymentIntegrity'])),
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
hooks: [
|
|
346
|
+
{
|
|
347
|
+
name: 'mint',
|
|
348
|
+
type: 'contract',
|
|
349
|
+
hook: contract,
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
};
|
|
353
|
+
itx.address = (0, did_util_1.toFactoryAddress)(itx);
|
|
354
|
+
// @ts-expect-error FIXME: help wanted
|
|
355
|
+
(0, asset_1.isValidFactory)(itx, true);
|
|
356
|
+
return itx;
|
|
357
|
+
};
|
|
358
|
+
const getFactoryInput = (inputTokens, { formatToken = true } = {}) => {
|
|
359
|
+
const tokens = (0, cloneDeep_1.default)(inputTokens);
|
|
360
|
+
tokens.forEach((token) => {
|
|
361
|
+
if (formatToken) {
|
|
362
|
+
token.value = (0, util_1.fromTokenToUnit)(token.value, token.decimal).toString();
|
|
363
|
+
}
|
|
364
|
+
delete token.decimal;
|
|
365
|
+
});
|
|
366
|
+
return {
|
|
367
|
+
tokens,
|
|
368
|
+
assets: [],
|
|
369
|
+
variables: [],
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
const getPaymentIntegrity = async ({ contract, factoryInput, storeComponents, meta, client, storeId, }) => {
|
|
373
|
+
if (!contract && !factoryInput && !storeComponents) {
|
|
374
|
+
const priceTokens = await getPriceTokens(meta, client);
|
|
375
|
+
const { components, stores } = await getComponents(meta);
|
|
376
|
+
const store = stores.find((x) => x.id === storeId);
|
|
377
|
+
// eslint-disable-next-line no-param-reassign
|
|
378
|
+
contract = (await getContract({ meta, components, priceTokens })).code;
|
|
379
|
+
// eslint-disable-next-line no-param-reassign
|
|
380
|
+
factoryInput = await getFactoryInput(priceTokens);
|
|
381
|
+
// eslint-disable-next-line no-param-reassign
|
|
382
|
+
storeComponents = store?.components || [];
|
|
383
|
+
}
|
|
384
|
+
const paymentData = {
|
|
385
|
+
factoryInput,
|
|
386
|
+
contract,
|
|
387
|
+
components: storeComponents || [],
|
|
388
|
+
};
|
|
389
|
+
const integrity = md5((0, json_stable_stringify_1.default)(paymentData));
|
|
390
|
+
return integrity;
|
|
391
|
+
};
|
|
392
|
+
const getStoreSignatures = async ({ meta, stores, factoryInput, contract, }) => {
|
|
393
|
+
const storeSignatures = [];
|
|
394
|
+
for (const store of stores) {
|
|
395
|
+
const { id, url, pk, components: storeComponents } = store;
|
|
396
|
+
const paymentIntegrity = await getPaymentIntegrity({ factoryInput, contract, storeComponents });
|
|
397
|
+
/**
|
|
398
|
+
* protocol: /api/payment/signature
|
|
399
|
+
* method: POST
|
|
400
|
+
* body: { blockletMeta, paymentIntegrity, paymentVersion }
|
|
401
|
+
* return: { signer, pk, signature}
|
|
402
|
+
*/
|
|
403
|
+
const { data: res } = await axios_1.default.post(`${url}/api/payment/signature`, {
|
|
404
|
+
blockletMeta: meta,
|
|
405
|
+
paymentIntegrity,
|
|
406
|
+
paymentVersion: VERSION,
|
|
407
|
+
}, { timeout: 20000 });
|
|
408
|
+
if (res.signer !== id) {
|
|
409
|
+
throw new Error('store signature: store id does not match');
|
|
410
|
+
}
|
|
411
|
+
if (res.pk !== pk) {
|
|
412
|
+
throw new Error('store signature: store pk does not match');
|
|
413
|
+
}
|
|
414
|
+
// verify sig
|
|
415
|
+
const type = toTypeInfo(id);
|
|
416
|
+
const wallet = (0, wallet_1.fromPublicKey)(pk, type);
|
|
417
|
+
const verifyRes = wallet.verify(paymentIntegrity, res.signature);
|
|
418
|
+
if (verifyRes !== true) {
|
|
419
|
+
throw new Error('verify store signature failed');
|
|
420
|
+
}
|
|
421
|
+
storeSignatures.push({
|
|
422
|
+
signer: res.signer,
|
|
423
|
+
pk: res.pk,
|
|
424
|
+
signature: res.signature,
|
|
425
|
+
components: storeComponents,
|
|
426
|
+
paymentIntegrity,
|
|
427
|
+
storeUrl: url,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
storeSignatures,
|
|
432
|
+
};
|
|
433
|
+
};
|
|
434
|
+
/**
|
|
435
|
+
* Used by CLI and Store to independent compute factory itx
|
|
436
|
+
*
|
|
437
|
+
* @param {{
|
|
438
|
+
* blockletMeta: TBlockletMeta,
|
|
439
|
+
* ocapClient: OcapClient,
|
|
440
|
+
* issuers: Array<string>,
|
|
441
|
+
* storeUrl: string,
|
|
442
|
+
* }}
|
|
443
|
+
* @returns {{
|
|
444
|
+
* itx: Itx
|
|
445
|
+
* store: Array<{id, url}>
|
|
446
|
+
* shares: Array<{
|
|
447
|
+
* accountName: string
|
|
448
|
+
* accountAddress: DID
|
|
449
|
+
* tokenAddress: DID
|
|
450
|
+
* amount: string|number,
|
|
451
|
+
* }>
|
|
452
|
+
* }}
|
|
453
|
+
*/
|
|
454
|
+
const createNftFactoryItx = async ({ blockletMeta, ocapClient, issuers, storeUrl, }) => {
|
|
455
|
+
const priceTokens = await getPriceTokens(blockletMeta, ocapClient);
|
|
456
|
+
const { components, stores } = await getComponents(blockletMeta);
|
|
457
|
+
const factoryInput = getFactoryInput(priceTokens);
|
|
458
|
+
const { code: contract, shares } = await getContract({
|
|
459
|
+
meta: blockletMeta,
|
|
460
|
+
priceTokens,
|
|
461
|
+
components,
|
|
462
|
+
});
|
|
463
|
+
const { storeSignatures } = await getStoreSignatures({
|
|
464
|
+
meta: blockletMeta,
|
|
465
|
+
stores,
|
|
466
|
+
factoryInput,
|
|
467
|
+
contract,
|
|
468
|
+
});
|
|
469
|
+
return {
|
|
470
|
+
itx: innerCreateNftFactoryItx({
|
|
471
|
+
meta: blockletMeta,
|
|
472
|
+
issuers,
|
|
473
|
+
serviceUrl: storeUrl,
|
|
474
|
+
storeSignatures,
|
|
475
|
+
factoryInput,
|
|
476
|
+
contract,
|
|
477
|
+
}),
|
|
478
|
+
stores: storeSignatures.map((x) => ({ id: x.signer, url: x.storeUrl })),
|
|
479
|
+
shares,
|
|
480
|
+
};
|
|
481
|
+
};
|
|
482
|
+
exports.createNftFactoryItx = createNftFactoryItx;
|
|
483
|
+
/**
|
|
484
|
+
* Used by Store before generating payment signature
|
|
485
|
+
*
|
|
486
|
+
* @param {{
|
|
487
|
+
* integrity: string,
|
|
488
|
+
* blockletMeta: TBlockletMeta,
|
|
489
|
+
* ocapClient: OcapClient,
|
|
490
|
+
* storeId: string
|
|
491
|
+
* }}
|
|
492
|
+
* @returns {string} integrity
|
|
493
|
+
*/
|
|
494
|
+
const verifyPaymentIntegrity = async ({ integrity: expected, blockletMeta, ocapClient, storeId, }) => {
|
|
495
|
+
const actual = await getPaymentIntegrity({ meta: blockletMeta, client: ocapClient, storeId });
|
|
496
|
+
if (actual !== expected) {
|
|
497
|
+
throw new Error('verify payment integrity failed');
|
|
498
|
+
}
|
|
499
|
+
return expected;
|
|
500
|
+
};
|
|
501
|
+
exports.verifyPaymentIntegrity = verifyPaymentIntegrity;
|
|
502
|
+
/**
|
|
503
|
+
* Used by Store before generating downloadToken
|
|
504
|
+
*
|
|
505
|
+
* @param {{
|
|
506
|
+
* {FactoryState} factoryState
|
|
507
|
+
* {Wallet} signerWallet
|
|
508
|
+
* }}
|
|
509
|
+
*
|
|
510
|
+
* @returns {{
|
|
511
|
+
* components: Array<{did: string, version: string}>
|
|
512
|
+
* }}
|
|
513
|
+
*/
|
|
514
|
+
const verifyNftFactory = async ({ factoryState, signerWallet, }) => {
|
|
515
|
+
const data = JSON.parse(factoryState?.data?.value);
|
|
516
|
+
const stores = data?.stores || [];
|
|
517
|
+
const store = stores.find((x) => x.signer === signerWallet.address);
|
|
518
|
+
if (!store) {
|
|
519
|
+
throw new Error(`Signer does not found in factory. factory: ${factoryState.address}, signer: ${signerWallet.address}`);
|
|
520
|
+
}
|
|
521
|
+
const c = factoryState.hooks.find((x) => x.type === 'contract');
|
|
522
|
+
const { components } = store;
|
|
523
|
+
// Token 的字段和 factory 中的字段不一致
|
|
524
|
+
const factoryInput = getFactoryInput(factoryState.input.tokens.map((x) => (0, pick_1.default)(x, ['address', 'value'])), { formatToken: false });
|
|
525
|
+
const integrity = await getPaymentIntegrity({
|
|
526
|
+
contract: c.hook,
|
|
527
|
+
factoryInput,
|
|
528
|
+
storeComponents: components,
|
|
529
|
+
});
|
|
530
|
+
if (signerWallet.sign(integrity) !== store.signature) {
|
|
531
|
+
debug(store, factoryInput, integrity, components, c.hook);
|
|
532
|
+
throw new Error(`verify nft factory failed: ${factoryState.address}`);
|
|
533
|
+
}
|
|
534
|
+
return { components };
|
|
535
|
+
};
|
|
536
|
+
exports.verifyNftFactory = verifyNftFactory;
|
|
537
|
+
/**
|
|
538
|
+
* Check blocklet and all of components are free
|
|
539
|
+
* Throw Error if not free
|
|
540
|
+
*
|
|
541
|
+
* @param {TBlockletMeta} meta
|
|
542
|
+
*/
|
|
543
|
+
const checkFreeBlocklet = async (blockletMeta) => {
|
|
544
|
+
if (!(0, util_2.isFreeBlocklet)(blockletMeta)) {
|
|
545
|
+
return Promise.reject(new Error('blocklet is not free'));
|
|
546
|
+
}
|
|
547
|
+
const { components } = await getComponents(blockletMeta);
|
|
548
|
+
const shouldAllComponentFree = (arr) => {
|
|
549
|
+
arr.forEach(({ meta, children }) => {
|
|
550
|
+
if (!(0, util_2.isFreeBlocklet)(meta) || !(0, util_2.isFreeComponent)(meta)) {
|
|
551
|
+
// throw new Error(`Found paid component "${meta.title || meta.name}" in free blocklet`);
|
|
552
|
+
throw new Error(`Paid component "${meta.title || meta.name}" found in free blocklet "${blockletMeta.title || blockletMeta.name}", which is forbidden`);
|
|
553
|
+
}
|
|
554
|
+
shouldAllComponentFree(children || []);
|
|
555
|
+
});
|
|
556
|
+
};
|
|
557
|
+
shouldAllComponentFree(components);
|
|
558
|
+
return true;
|
|
559
|
+
};
|
|
560
|
+
exports.checkFreeBlocklet = checkFreeBlocklet;
|
|
561
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
562
|
+
exports._test = {
|
|
563
|
+
getPriceTokens,
|
|
564
|
+
getFactoryInput,
|
|
565
|
+
getPaymentIntegrity,
|
|
566
|
+
getComponents,
|
|
567
|
+
getContract,
|
|
568
|
+
};
|
|
569
|
+
exports.default = {
|
|
570
|
+
createNftFactoryItx,
|
|
571
|
+
verifyPaymentIntegrity,
|
|
572
|
+
verifyNftFactory,
|
|
573
|
+
checkFreeBlocklet,
|
|
574
|
+
version: VERSION,
|
|
575
|
+
_test: exports._test,
|
|
576
|
+
};
|
package/lib/schema.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import JOI from 'joi';
|
|
2
|
+
declare const titleSchema: JOI.StringSchema<string>;
|
|
3
|
+
declare const descriptionSchema: JOI.StringSchema<string>;
|
|
4
|
+
declare const logoSchema: JOI.StringSchema<string>;
|
|
5
|
+
declare const mountPointSchema: JOI.StringSchema<string>;
|
|
6
|
+
declare const updateMountPointSchema: JOI.StringSchema<string>;
|
|
7
|
+
declare const blockletNameSchema: JOI.StringSchema<string>;
|
|
8
|
+
declare const environmentNameSchema: JOI.StringSchema<string>;
|
|
9
|
+
declare const environmentSchema: JOI.ObjectSchema<any>;
|
|
10
|
+
declare const scriptsSchema: JOI.ObjectSchema<any>;
|
|
11
|
+
declare const serviceSchema: JOI.ObjectSchema<any>;
|
|
12
|
+
declare const endpointSchema: JOI.ObjectSchema<any>;
|
|
13
|
+
declare const cacheableSchema: JOI.StringSchema<string>;
|
|
14
|
+
declare const interfaceSchema: JOI.ObjectSchema<any>;
|
|
15
|
+
declare const engineSchema: JOI.ObjectSchema<any>;
|
|
16
|
+
declare const personSchema: JOI.ObjectSchema<any>;
|
|
17
|
+
declare const distSchema: JOI.ObjectSchema<any>;
|
|
18
|
+
declare const statsSchema: JOI.ObjectSchema<any>;
|
|
19
|
+
declare const componentSchema: JOI.ObjectSchema<any>;
|
|
20
|
+
declare const signatureSchema: JOI.ObjectSchema<any>;
|
|
21
|
+
declare const navigationItemSchema: JOI.ObjectSchema<any>;
|
|
22
|
+
declare const navigationSchema: JOI.ArraySchema<any[]>;
|
|
23
|
+
declare const themeSchema: JOI.ObjectSchema<any>;
|
|
24
|
+
declare const authConfigSchema: JOI.ObjectSchema<any>;
|
|
25
|
+
declare const blockletMetaSchema: JOI.ObjectSchema<any>;
|
|
26
|
+
declare const createBlockletSchema: (baseDir: string, { ensureMain, ensureFiles, ensureDist, ensureComponentStore, ensureName, skipValidateDidName, ...schemaOptions }?: {
|
|
27
|
+
ensureMain?: boolean;
|
|
28
|
+
ensureFiles?: boolean;
|
|
29
|
+
ensureDist?: boolean;
|
|
30
|
+
ensureComponentStore?: boolean;
|
|
31
|
+
ensureName?: boolean;
|
|
32
|
+
skipValidateDidName?: boolean;
|
|
33
|
+
}) => JOI.ObjectSchema;
|
|
34
|
+
export { blockletMetaSchema, blockletNameSchema, componentSchema, createBlockletSchema, descriptionSchema, distSchema, endpointSchema, engineSchema, environmentSchema, environmentNameSchema, interfaceSchema, logoSchema, mountPointSchema, updateMountPointSchema, navigationItemSchema, navigationSchema, personSchema, scriptsSchema, serviceSchema, signatureSchema, themeSchema, titleSchema, statsSchema, cacheableSchema, authConfigSchema, };
|
|
35
|
+
declare const _default: {
|
|
36
|
+
blockletNameSchema: JOI.StringSchema<string>;
|
|
37
|
+
componentSchema: JOI.ObjectSchema<any>;
|
|
38
|
+
endpointSchema: JOI.ObjectSchema<any>;
|
|
39
|
+
serviceSchema: JOI.ObjectSchema<any>;
|
|
40
|
+
createBlockletSchema: (baseDir: string, { ensureMain, ensureFiles, ensureDist, ensureComponentStore, ensureName, skipValidateDidName, ...schemaOptions }?: {
|
|
41
|
+
ensureMain?: boolean;
|
|
42
|
+
ensureFiles?: boolean;
|
|
43
|
+
ensureDist?: boolean;
|
|
44
|
+
ensureComponentStore?: boolean;
|
|
45
|
+
ensureName?: boolean;
|
|
46
|
+
skipValidateDidName?: boolean;
|
|
47
|
+
}) => JOI.ObjectSchema<any>;
|
|
48
|
+
interfaceSchema: JOI.ObjectSchema<any>;
|
|
49
|
+
environmentSchema: JOI.ObjectSchema<any>;
|
|
50
|
+
environmentNameSchema: JOI.StringSchema<string>;
|
|
51
|
+
scriptsSchema: JOI.ObjectSchema<any>;
|
|
52
|
+
personSchema: JOI.ObjectSchema<any>;
|
|
53
|
+
distSchema: JOI.ObjectSchema<any>;
|
|
54
|
+
titleSchema: JOI.StringSchema<string>;
|
|
55
|
+
descriptionSchema: JOI.StringSchema<string>;
|
|
56
|
+
logoSchema: JOI.StringSchema<string>;
|
|
57
|
+
navigationSchema: JOI.ArraySchema<any[]>;
|
|
58
|
+
themeSchema: JOI.ObjectSchema<any>;
|
|
59
|
+
mountPointSchema: JOI.StringSchema<string>;
|
|
60
|
+
updateMountPointSchema: JOI.StringSchema<string>;
|
|
61
|
+
authConfigSchema: JOI.ObjectSchema<any>;
|
|
62
|
+
};
|
|
63
|
+
export default _default;
|