@ocap/asset 1.18.166 → 1.19.0

Sign up to get free protection for your applications and to get access to all the features.
package/esm/index.d.ts ADDED
@@ -0,0 +1,74 @@
1
+ import type { LiteralUnion, PartialDeep } from 'type-fest';
2
+ import type { WalletObject } from '@ocap/wallet';
3
+ import type { TAssetFactoryState, TCreateAssetTx } from '@ocap/types';
4
+ type $TSFixMe = any;
5
+ export type TPickedFactoryState = PartialDeep<TAssetFactoryState>;
6
+ export type TIssuer = {
7
+ name: string;
8
+ wallet: WalletObject;
9
+ };
10
+ export type TIssuerInput = {
11
+ id: string;
12
+ pk: string;
13
+ name: string;
14
+ };
15
+ export type THook = {
16
+ type: LiteralUnion<'contract' | 'url', string>;
17
+ name: LiteralUnion<'mint' | 'postMint' | 'preMint', string>;
18
+ hook: string;
19
+ };
20
+ export type TInputMap = {
21
+ [key: string]: string;
22
+ };
23
+ export type TMintResult = {
24
+ address: string;
25
+ asset: TCreateAssetTx;
26
+ };
27
+ export type TPreMintResult = TMintResult & {
28
+ variables: TInputMap;
29
+ issuer: TIssuerInput;
30
+ };
31
+ export declare const isValidNotation: (notation: string) => boolean;
32
+ export declare const isValidHook: (hook: THook, quota?: $TSFixMe, throwOnError?: boolean) => boolean;
33
+ export declare const isValidFactory: (props: any) => boolean;
34
+ /**
35
+ * Find credentialSubject path in the object
36
+ * Because they need prerender
37
+ *
38
+ * @param {object} obj
39
+ * @param {string} keyword
40
+ * @return {string} list of keys
41
+ */
42
+ export declare const findPrerenderKeys: (obj: $TSFixMe, keyword: string) => string[];
43
+ /**
44
+ * Mint from an asset factory, used on server side
45
+ *
46
+ * @param {object} params { factory, inputs, issuer }
47
+ * @param {object} params.factory factory object
48
+ * @param {object} params.inputs factory input variables
49
+ * @param {string} params.owner owner did for the new asset
50
+ * @param {object} params.issuer issuer object
51
+ */
52
+ export declare const mintFromFactory: ({ factory, inputs, owner, issuer, }: {
53
+ factory: TPickedFactoryState;
54
+ inputs: TInputMap;
55
+ owner: string;
56
+ issuer: TIssuerInput;
57
+ }) => TMintResult;
58
+ /**
59
+ * Simulate minting from an asset factory, used for client side
60
+ *
61
+ * @param {object} params { factory, inputs, issuer }
62
+ * @param {object} params.factory factory object
63
+ * @param {object} params.inputs factory input variables
64
+ * @param {string} params.owner owner did for the new asset
65
+ * @param {object} params.issuer factory issuer wallet and name
66
+ */
67
+ export declare const preMintFromFactory: ({ factory, inputs, owner, issuer, }: {
68
+ factory?: TPickedFactoryState;
69
+ inputs: TInputMap;
70
+ owner?: string;
71
+ issuer?: TIssuer;
72
+ }) => Promise<TPreMintResult>;
73
+ export declare const formatFactoryState: (state: TAssetFactoryState) => TPickedFactoryState;
74
+ export {};
package/esm/index.js ADDED
@@ -0,0 +1,247 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+ import get from 'lodash/get';
3
+ import set from 'lodash/set';
4
+ import uniq from 'lodash/uniq';
5
+ import uniqBy from 'lodash/uniqBy';
6
+ import isEmpty from 'lodash/isEmpty';
7
+ import flatten from 'flat';
8
+ import mustache from 'mustache';
9
+ import isAbsoluteUrl from 'is-absolute-url';
10
+ import cloneDeep from 'lodash/cloneDeep';
11
+ import { toTypeInfo, fromPublicKeyHash } from '@arcblock/did';
12
+ import { verify, stableStringify, proofTypes } from '@arcblock/vc';
13
+ import { schemas } from '@arcblock/validator';
14
+ import { toAssetAddress } from '@arcblock/did-util';
15
+ import { types } from '@ocap/mcrypto';
16
+ import { toBase58, toBase64 } from '@ocap/util';
17
+ import { compile, validate, getQuota } from '@ocap/contract';
18
+ import Debug from 'debug';
19
+ const debug = Debug('@ocap/asset');
20
+ const SUPPORTED_HOOK_NAMES = ['preMint', 'mint', 'postMint'];
21
+ const SUPPORTED_HOOK_TYPES = ['contract', 'url'];
22
+ export const isValidNotation = (notation) => ['ctx', 'data', 'input'].includes(notation.split('.').shift());
23
+ export const isValidHook = (hook, quota, throwOnError = false) => {
24
+ if (SUPPORTED_HOOK_TYPES.includes(hook.type) === false) {
25
+ return false;
26
+ }
27
+ if (SUPPORTED_HOOK_NAMES.includes(hook.name) === false) {
28
+ return false;
29
+ }
30
+ if (hook.type === 'url') {
31
+ return isAbsoluteUrl(hook.hook);
32
+ }
33
+ if (hook.type === 'contract') {
34
+ try {
35
+ const compiled = compile(hook.hook);
36
+ validate(compiled, quota);
37
+ return true;
38
+ }
39
+ catch (err) {
40
+ if (process.env.NODE_ENV !== 'test') {
41
+ console.error('invalid contract hook', err.message);
42
+ }
43
+ if (throwOnError) {
44
+ throw new Error(`Factory hook ${hook.name} is invalid: ${err.message}`);
45
+ }
46
+ return false;
47
+ }
48
+ }
49
+ return false;
50
+ };
51
+ // @link https://github.com/improbable-eng/ts-protoc-gen
52
+ export const isValidFactory = (props) => {
53
+ if (!props) {
54
+ throw new Error('Factory props should not be empty');
55
+ }
56
+ const { value, error } = schemas.factorySchema.validate(props);
57
+ if (error) {
58
+ throw new Error(`Invalid factory: ${error.details.map((x) => x.message).join(', ')}`);
59
+ }
60
+ // input.tokens and input.assets should not be empty
61
+ if (['tokens', 'assets'].every((x) => isEmpty(value.input[x])) && value.input.value <= 0) {
62
+ throw new Error('Factory input should contain at least one token or asset');
63
+ }
64
+ if (uniqBy(value.input.tokens, 'address').length !== value.input.tokens.length) {
65
+ throw new Error('Factory token input should not contains duplicate address');
66
+ }
67
+ if (uniq(value.input.assets).length !== value.input.assets.length) {
68
+ throw new Error('Factory asset input should not contains duplicate address');
69
+ }
70
+ try {
71
+ // should be a valid mustache template when serialized as json
72
+ const template = JSON.stringify(value.output, null, 2);
73
+ const result = mustache.parse(template);
74
+ if (result.filter(([type]) => type === 'name').some(([, notation]) => isValidNotation(notation) === false)) {
75
+ throw new Error('Invalid tags found in the output template');
76
+ }
77
+ }
78
+ catch (err) {
79
+ throw new Error('Factory output should be a valid mustache template when serialized as json');
80
+ }
81
+ // validate hooks
82
+ const quota = getQuota(value.input);
83
+ if (Array.isArray(value.hooks)) {
84
+ const invalidHook = value.hooks.find((x) => isValidHook(x, quota, true) === false);
85
+ if (invalidHook) {
86
+ throw new Error(`Factory hook ${invalidHook.name} is invalid`);
87
+ }
88
+ }
89
+ // ensure input and hook are zero-sum for factories that consumes token
90
+ if (value.settlement === 'instant') {
91
+ if (quota.value <= 0 && Object.keys(quota.tokens).every((x) => quota[x] <= 0)) {
92
+ return true;
93
+ }
94
+ if (isEmpty(value.hooks)) {
95
+ throw new Error('Factory hooks should not be empty for instant settlement');
96
+ }
97
+ const mintHook = value.hooks.find((x) => x.name === 'mint');
98
+ if (!mintHook) {
99
+ throw new Error('Factory hook mint should not be empty for instant settlement that consumes token');
100
+ }
101
+ try {
102
+ const compiled = compile(mintHook.hook);
103
+ validate(compiled, quota, true);
104
+ }
105
+ catch (err) {
106
+ throw new Error(`Factory hook mint is invalid: ${err.message}`);
107
+ }
108
+ }
109
+ return true;
110
+ };
111
+ /**
112
+ * Find credentialSubject path in the object
113
+ * Because they need prerender
114
+ *
115
+ * @param {object} obj
116
+ * @param {string} keyword
117
+ * @return {string} list of keys
118
+ */
119
+ export const findPrerenderKeys = (obj, keyword) => {
120
+ const flatObj = flatten(obj, { safe: true });
121
+ const keys = Object.keys(flatObj)
122
+ .map((x) => x.split('.'))
123
+ .filter((x) => x.includes(keyword))
124
+ .map((x) => x.slice(0, x.lastIndexOf(keyword) + 1))
125
+ .sort((a, b) => b.length - a.length)
126
+ .map((x) => x.join('.'));
127
+ return uniq(keys);
128
+ };
129
+ /**
130
+ * Mint from an asset factory, used on server side
131
+ *
132
+ * @param {object} params { factory, inputs, issuer }
133
+ * @param {object} params.factory factory object
134
+ * @param {object} params.inputs factory input variables
135
+ * @param {string} params.owner owner did for the new asset
136
+ * @param {object} params.issuer issuer object
137
+ */
138
+ export const mintFromFactory = ({ factory, inputs, owner, issuer, }) => {
139
+ const { output, address: factoryAddress, numMinted, data } = factory;
140
+ debug('mintFromFactory.args', JSON.stringify({ output, factoryAddress, numMinted, inputs, owner, issuer, data }, null, 2));
141
+ const asset = JSON.parse(mustache.render(JSON.stringify(output), {
142
+ input: inputs,
143
+ data: data.value || data,
144
+ ctx: { factory: factoryAddress, id: numMinted + 1, owner, issuer },
145
+ }));
146
+ const address = toAssetAddress(asset);
147
+ debug('mintFromFactory.result', JSON.stringify({ asset, address }, null, 2));
148
+ return { asset, address };
149
+ };
150
+ /**
151
+ * Simulate minting from an asset factory, used for client side
152
+ *
153
+ * @param {object} params { factory, inputs, issuer }
154
+ * @param {object} params.factory factory object
155
+ * @param {object} params.inputs factory input variables
156
+ * @param {string} params.owner owner did for the new asset
157
+ * @param {object} params.issuer factory issuer wallet and name
158
+ */
159
+ export const preMintFromFactory = async ({ factory, inputs, owner, issuer, }) => {
160
+ if (Object.keys(inputs).some((x) => typeof inputs[x] !== 'string')) {
161
+ throw new Error('Failed to mint asset from factory: input values must be strings');
162
+ }
163
+ let asset = null;
164
+ const { output, numMinted, address: factoryAddress, data } = factory;
165
+ const { wallet, name } = issuer;
166
+ debug('preMintFromFactory.args', JSON.stringify({ output, factoryAddress, numMinted, inputs, owner, issuer, data }, null, 2));
167
+ const extra = {};
168
+ const issuerObject = { id: wallet.address, pk: toBase58(wallet.publicKey), name };
169
+ const render = (templateObject) => JSON.parse(mustache.render(JSON.stringify(templateObject), {
170
+ input: { ...inputs, ...extra },
171
+ data: data.value || data,
172
+ ctx: { factory: factoryAddress, id: numMinted + 1, owner, issuer: issuerObject },
173
+ }));
174
+ const template = cloneDeep(output);
175
+ // prerender credentialSubjects if they exists
176
+ // then populate their ids
177
+ const prerenderKeys = findPrerenderKeys(template, 'credentialSubject');
178
+ if (prerenderKeys.length) {
179
+ extra.issuanceDate = new Date().toISOString();
180
+ for (const key of prerenderKeys) {
181
+ const subjectTemplate = get(template, key);
182
+ const subjectObject = Array.isArray(subjectTemplate)
183
+ ? subjectTemplate.map((x) => render(x))
184
+ : render(subjectTemplate);
185
+ set(template, key, subjectObject);
186
+ // calculate credential id from credentialSubject
187
+ const vcRootPath = key.split('.').slice(0, -1).join('.');
188
+ const vcIdPath = vcRootPath.split('.').concat(['id']).join('.');
189
+ const typeInfo = toTypeInfo(issuerObject.id);
190
+ const vcType = { ...typeInfo, role: types.RoleType.ROLE_VC };
191
+ const vcId = fromPublicKeyHash(wallet.hash(stableStringify(subjectObject)), vcType);
192
+ extra.id = vcId;
193
+ extra.proofType = proofTypes[typeInfo.pk];
194
+ // technically we do not support nested vc when minting
195
+ // But it is possible to support multiple credentialSubjects when minting
196
+ set(template, vcIdPath, vcId);
197
+ // Generate proof signatures
198
+ if (!proofTypes[typeInfo.pk]) {
199
+ throw new Error('Unsupported signer type when create verifiable credential');
200
+ }
201
+ let vcObj = render(get(template, vcRootPath));
202
+ delete vcObj.proof;
203
+ const vcStr = stableStringify(vcObj);
204
+ const signature = toBase64(wallet.sign(vcStr));
205
+ vcObj.proof = {
206
+ type: proofTypes[typeInfo.pk],
207
+ created: extra.issuanceDate,
208
+ proofPurpose: 'assertionMethod',
209
+ jws: signature,
210
+ };
211
+ extra.signature = signature;
212
+ try {
213
+ // Simulate minting from start, so that we can ensure the validity of minted asset
214
+ asset = render(cloneDeep(output));
215
+ vcObj = get(asset, vcRootPath);
216
+ debug('preMintFromFactory.result', JSON.stringify(asset, null, 2));
217
+ // eslint-disable-next-line no-await-in-loop
218
+ await verify({ vc: vcObj, trustedIssuers: [issuerObject.id], ownerDid: owner, ignoreExpired: true });
219
+ }
220
+ catch (err) {
221
+ console.error(err);
222
+ throw new Error('Failed to mint asset from factory: invalid verifiable credential minted');
223
+ }
224
+ }
225
+ }
226
+ else {
227
+ // populate other variables into the whole output
228
+ asset = render(template);
229
+ debug('preMintFromFactory.result', JSON.stringify(asset, null, 2));
230
+ }
231
+ // calculate address
232
+ const address = toAssetAddress(asset);
233
+ // return extra inputs
234
+ return { address, issuer: issuerObject, variables: { ...inputs, ...extra }, asset };
235
+ };
236
+ export const formatFactoryState = (state) => {
237
+ const { address, output, data, numMinted } = state;
238
+ const outputX = cloneDeep(output);
239
+ outputX.data.value = JSON.parse(outputX.data.value);
240
+ outputX.data.type = outputX.data.typeUrl;
241
+ return {
242
+ address,
243
+ output: outputX,
244
+ data: data.value ? JSON.parse(data.value) : {},
245
+ numMinted,
246
+ };
247
+ };
package/lib/index.d.ts CHANGED
@@ -1,30 +1,30 @@
1
1
  import type { LiteralUnion, PartialDeep } from 'type-fest';
2
2
  import type { WalletObject } from '@ocap/wallet';
3
3
  import type { TAssetFactoryState, TCreateAssetTx } from '@ocap/types';
4
- declare type $TSFixMe = any;
5
- export declare type TPickedFactoryState = PartialDeep<TAssetFactoryState>;
6
- export declare type TIssuer = {
4
+ type $TSFixMe = any;
5
+ export type TPickedFactoryState = PartialDeep<TAssetFactoryState>;
6
+ export type TIssuer = {
7
7
  name: string;
8
8
  wallet: WalletObject;
9
9
  };
10
- export declare type TIssuerInput = {
10
+ export type TIssuerInput = {
11
11
  id: string;
12
12
  pk: string;
13
13
  name: string;
14
14
  };
15
- export declare type THook = {
15
+ export type THook = {
16
16
  type: LiteralUnion<'contract' | 'url', string>;
17
17
  name: LiteralUnion<'mint' | 'postMint' | 'preMint', string>;
18
18
  hook: string;
19
19
  };
20
- export declare type TInputMap = {
20
+ export type TInputMap = {
21
21
  [key: string]: string;
22
22
  };
23
- export declare type TMintResult = {
23
+ export type TMintResult = {
24
24
  address: string;
25
25
  asset: TCreateAssetTx;
26
26
  };
27
- export declare type TPreMintResult = TMintResult & {
27
+ export type TPreMintResult = TMintResult & {
28
28
  variables: TInputMap;
29
29
  issuer: TIssuerInput;
30
30
  };
@@ -69,6 +69,6 @@ export declare const preMintFromFactory: ({ factory, inputs, owner, issuer, }: {
69
69
  inputs: TInputMap;
70
70
  owner?: string;
71
71
  issuer?: TIssuer;
72
- }) => TPreMintResult;
72
+ }) => Promise<TPreMintResult>;
73
73
  export declare const formatFactoryState: (state: TAssetFactoryState) => TPickedFactoryState;
74
74
  export {};
package/lib/index.js CHANGED
@@ -167,7 +167,7 @@ exports.mintFromFactory = mintFromFactory;
167
167
  * @param {string} params.owner owner did for the new asset
168
168
  * @param {object} params.issuer factory issuer wallet and name
169
169
  */
170
- const preMintFromFactory = ({ factory, inputs, owner, issuer, }) => {
170
+ const preMintFromFactory = async ({ factory, inputs, owner, issuer, }) => {
171
171
  if (Object.keys(inputs).some((x) => typeof inputs[x] !== 'string')) {
172
172
  throw new Error('Failed to mint asset from factory: input values must be strings');
173
173
  }
@@ -225,7 +225,8 @@ const preMintFromFactory = ({ factory, inputs, owner, issuer, }) => {
225
225
  asset = render((0, cloneDeep_1.default)(output));
226
226
  vcObj = (0, get_1.default)(asset, vcRootPath);
227
227
  debug('preMintFromFactory.result', JSON.stringify(asset, null, 2));
228
- (0, vc_1.verify)({ vc: vcObj, trustedIssuers: [issuerObject.id], ownerDid: owner, ignoreExpired: true });
228
+ // eslint-disable-next-line no-await-in-loop
229
+ await (0, vc_1.verify)({ vc: vcObj, trustedIssuers: [issuerObject.id], ownerDid: owner, ignoreExpired: true });
229
230
  }
230
231
  catch (err) {
231
232
  console.error(err);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ocap/asset",
3
3
  "description": "Utility to work with asset and factory on ArcBlock blockchain",
4
- "version": "1.18.166",
4
+ "version": "1.19.0",
5
5
  "author": {
6
6
  "name": "wangshijun",
7
7
  "email": "shijun@arcblock.io",
@@ -18,15 +18,15 @@
18
18
  "wangshijun <shijun@arcblock.io> (https://github.com/wangshijun)"
19
19
  ],
20
20
  "dependencies": {
21
- "@arcblock/did": "1.18.166",
22
- "@arcblock/did-util": "1.18.166",
23
- "@arcblock/validator": "1.18.166",
24
- "@arcblock/vc": "1.18.166",
25
- "@ocap/contract": "1.18.166",
26
- "@ocap/mcrypto": "1.18.166",
27
- "@ocap/types": "1.18.166",
28
- "@ocap/util": "1.18.166",
29
- "@ocap/wallet": "1.18.166",
21
+ "@arcblock/did": "1.19.0",
22
+ "@arcblock/did-util": "1.19.0",
23
+ "@arcblock/validator": "1.19.0",
24
+ "@arcblock/vc": "1.19.0",
25
+ "@ocap/contract": "1.19.0",
26
+ "@ocap/mcrypto": "1.19.0",
27
+ "@ocap/types": "1.19.0",
28
+ "@ocap/util": "1.19.0",
29
+ "@ocap/wallet": "1.19.0",
30
30
  "debug": "^4.3.6",
31
31
  "flat": "^5.0.2",
32
32
  "is-absolute-url": "^3.0.3",
@@ -35,17 +35,17 @@
35
35
  "mustache": "^4.2.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@arcblock/eslint-config-ts": "0.2.3",
39
- "@types/flat": "^5.0.2",
40
- "@types/jest": "^29.5.12",
41
- "@types/mustache": "^4.2.1",
42
- "@types/node": "^17.0.45",
43
- "eslint": "^8.25.0",
38
+ "@arcblock/eslint-config-ts": "0.3.3",
39
+ "@types/flat": "^5.0.5",
40
+ "@types/jest": "^29.5.13",
41
+ "@types/mustache": "^4.2.5",
42
+ "@types/node": "^22.7.5",
43
+ "eslint": "^8.57.0",
44
44
  "jest": "^29.7.0",
45
- "prettier": "^2.7.1",
45
+ "prettier": "^3.3.2",
46
46
  "ts-jest": "^29.2.5",
47
47
  "type-fest": "^3.1.0",
48
- "typescript": "^4.8.4"
48
+ "typescript": "^5.6.2"
49
49
  },
50
50
  "homepage": "https://github.com/ArcBlock/blockchain/tree/master/core/asset",
51
51
  "keywords": [
@@ -55,10 +55,19 @@
55
55
  "nodejs"
56
56
  ],
57
57
  "license": "Apache-2.0",
58
- "main": "lib/index.js",
59
- "typings": "lib/index.d.ts",
58
+ "main": "./lib/index.js",
59
+ "module": "./lib/index.js",
60
+ "types": "./esm/index.d.ts",
61
+ "exports": {
62
+ ".": {
63
+ "import": "./esm/index.js",
64
+ "require": "./lib/index.js",
65
+ "default": "./esm/index.js"
66
+ }
67
+ },
60
68
  "files": [
61
- "lib"
69
+ "lib",
70
+ "esm"
62
71
  ],
63
72
  "repository": {
64
73
  "type": "git",
@@ -69,10 +78,12 @@
69
78
  "lint:fix": "npm run lint -- --fix",
70
79
  "test": "jest --forceExit --detectOpenHandles",
71
80
  "coverage": "npm run test -- --coverage",
72
- "clean": "rm -fr lib",
81
+ "clean": "rm -fr lib esm",
73
82
  "prebuild": "npm run clean",
74
- "build": "tsc",
83
+ "build:cjs": "tsc -p tsconfig.cjs.json",
84
+ "build:esm": "tsc -p tsconfig.esm.json",
85
+ "build": "npm run build:cjs && npm run build:esm",
75
86
  "build:watch": "npm run build -- -w"
76
87
  },
77
- "gitHead": "58c8356b3b8c238728560e4c3fef6ed1704d3ac4"
88
+ "gitHead": "1b6fac03988fb18507c8ef4c21de282762005f87"
78
89
  }