├── .fatherrc.ts ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── examples ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .umirc.ts ├── README.md ├── package.json ├── src │ ├── myconfig_template.ts │ └── pages │ │ ├── quickStart.tsx │ │ ├── useUtils.ts │ │ ├── use_case_1.tsx │ │ ├── use_case_2.tsx │ │ └── use_case_3.tsx ├── tsconfig.json └── typings.d.ts ├── examples_static ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .umirc.ts ├── Dockerfile_use_case1 ├── Dockerfile_use_case2 ├── Dockerfile_use_case3 ├── README.md ├── build.js ├── build_arweave.js ├── ngnix │ ├── nginx_case_1.conf │ ├── nginx_case_2.conf │ └── nginx_case_3.conf ├── package-lock.json ├── package.json ├── src │ ├── myconfig_template.ts │ └── pages │ │ ├── useUtils.ts │ │ ├── use_case_1.tsx │ │ ├── use_case_2.tsx │ │ └── use_case_3.tsx ├── tsconfig.json └── typings.d.ts ├── jest.config.ts ├── package-lock.json ├── package.json ├── src ├── Crypto.ts ├── DataLake.ts ├── MindLake.ts ├── Permission.ts ├── index.ts ├── request │ ├── index.ts │ └── request.ts ├── types │ └── index.ts └── util │ ├── abi.ts │ ├── bcl.ts │ ├── cipher.ts │ ├── clerk.ts │ ├── constant.ts │ ├── result.ts │ ├── util.ts │ └── web3.ts ├── tsconfig.json ├── tutorial ├── Configure_AppKey.md ├── Configure_Docker_Case.md ├── Configure_Node.md ├── Configure_Wallet.md ├── README.md └── imgs │ ├── alice_connect.png │ ├── bob_connect.png │ ├── brew.png │ ├── case_2.png │ ├── case_2_logout.png │ ├── case_3.png │ ├── case_3_result.png │ ├── change_chain.png │ ├── change_wallet_alice.png │ ├── change_wallet_bob.png │ ├── change_wallet_charlie.png │ ├── charlie_connect.png │ ├── charlie_out_put.png │ ├── create_dapp.png │ ├── create_dapp_confirm.png │ ├── create_wallet.png │ ├── create_wallet_confirm.png │ ├── dapp_list.png │ ├── decrypt_request.png │ ├── docker-app-search.png │ ├── docker_app.png │ ├── docker_app_top.webp │ ├── docker_bac.jpeg │ ├── docker_bak.png │ ├── docker_case1_img.png │ ├── docker_info.png │ ├── docker_run.png │ ├── docker_run1.png │ ├── docker_run2.png │ ├── docker_win_download.png │ ├── docker_windows_win.jpeg │ ├── img.png │ ├── insert_data_done.png │ ├── install_brew.png │ ├── metamask_testnet_enable.png │ ├── myDapp_menu.png │ ├── nounce_sign.png │ ├── nvm_1.png │ ├── nvm_2.png │ ├── nvm_3.png │ ├── open_mac_terminal.png │ ├── quickStart.png │ ├── quickStart_logout.png │ ├── request_publickey.png │ ├── sign_scan.png │ ├── tutorial-architecture.jpeg │ ├── tutorial-architecture.jpg │ ├── upload_chain.png │ ├── white_list_popup.png │ ├── window_download_nvm.png │ ├── windows_open_terminal.png │ └── windows_terminal.png └── yarn.lock /.fatherrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'father'; 2 | 3 | export default defineConfig({ 4 | esm: {output: 'dist'}, 5 | cjs: {output: 'dist'}, 6 | prebundle: {}, 7 | }); 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | .DS_Store 4 | .idea 5 | .editorconfig 6 | examples/src/myconfig.ts 7 | .npmrc 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | package.json 6 | .umi 7 | .umi-production 8 | .umi-test 9 | .dist 10 | .node_modules 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 The Typescript Packaging Authority 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mind Lake Typescript SDK 2 | 3 | An Typescript implementation for Mind Lake 4 | 5 | ## Description 6 | 7 | The Mind Lake SDK utilizes Mind Lake's encryption storage and privacy computing capabilities to provide secure data management. 8 | * Mind Lake is the backbone of Mind Network. 9 | * All data is end-to-end encrypted at the client-side SDK side, ensuring that plaintext data never leaves the user's client. 10 | * Cryptographic principles ensure that only the data owner can access their own plaintext data. 11 | * Additionally, Mind Lake's powerful privacy computing capabilities enable the performance of calculations and querying of encrypted data. 12 | 13 | ## Getting Started 14 | 15 | ### Dependencies 16 | 17 | * node [16, 18) 18 | * web3js 19 | 20 | ### Installing 21 | 22 | ``` 23 | $ npm install --save mind-lake-sdk 24 | # or 25 | $ yarn add mind-lake-sdk 26 | ``` 27 | 28 | ### Import 29 | ``` 30 | import { MindLake } from "mind-lake-sdk"; 31 | const mindLake = await MindLake.getInstance("YOUR OWN APP KEY") 32 | ... 33 | ``` 34 | 35 | ### Executing program 36 | * [step-by-step tutorial](/tutorial/README.md) 37 | * [quick starts](https://mind-network.gitbook.io/mind-lake-sdk/get-started) 38 | * [more examples](https://mind-network.gitbook.io/mind-lake-sdk/use-cases) 39 | 40 | 41 | ## code 42 | ``` 43 | mind-lake-sdk-typescript 44 | |-- src # source code 45 | | |-- MindLake.ts 46 | | |-- DataLake.ts 47 | | |-- Permission.ts 48 | | |-- Cryptor.ts 49 | |-- examples # use case examples 50 | |-- tutorial # step-by-step tutorial 51 | |-- README.md 52 | └--- LICENSE 53 | 54 | ``` 55 | 56 | ## Help 57 | 58 | Full doc: [https://mind-network.gitbook.io/mind-lake-sdk](https://mind-network.gitbook.io/mind-lake-sdk) 59 | 60 | ## Authors 61 | * Joshua [@JoshuaW55818202](https://twitter.com/JoshuaW55818202) 62 | * Lee [@LeeTan853917](https://twitter.com/LeeTan853917) 63 | 64 | ## Version History 65 | 66 | * v1.0.0 67 | * Initial Release 68 | * v1.0.1 69 | * Fix bug 70 | * v1.0.2 71 | * Fix bug 72 | * v1.0.4 73 | * Add listOwner method to Permission Class 74 | * Add listOwnerColumn method to Permission Class 75 | * v1.0.5 76 | * Fix bug 77 | * v1.0.6 78 | * Update the connect function 79 | * v1.0.7 80 | * Add support for Mind DataPack 81 | * v1.0.13 82 | * Support multiple chains 83 | * Support clerk 84 | ## License 85 | 86 | This project is licensed under the [MIT] License - see the LICENSE.md file for details 87 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /yarn.lock 8 | /package-lock.json 9 | 10 | # production 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | 16 | # umi 17 | /src/.umi 18 | /src/.umi-production 19 | /src/.umi-test 20 | /.env.local 21 | -------------------------------------------------------------------------------- /examples/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | package.json 6 | .umi 7 | .umi-production 8 | .umi-test 9 | -------------------------------------------------------------------------------- /examples/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | 3 | export default defineConfig({ 4 | nodeModulesTransform: { 5 | type: 'none', 6 | }, 7 | routes: [ 8 | { path: '/', component: '@/pages/quickStart' }, 9 | { path: '/use_case_1', component: '@/pages/use_case_1' }, 10 | { path: '/use_case_2', component: '@/pages/use_case_2' }, 11 | { path: '/use_case_3', component: '@/pages/use_case_3' }, 12 | ], 13 | fastRefresh: {}, 14 | }); 15 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | A quick example on how to use mind-lake-sdk TypeScript library 3 | 4 | ## Getting Started 5 | 6 | ### Install dependencies 7 | 8 | install mind-lake-sdk manual 9 | ```bash 10 | $ npm install --save mind-lake-sdk 11 | ``` 12 | 13 | install other all packages required for this quick demo. mind-lake-sdk (TypeScript) is defined in package.json 14 | 15 | use npm 16 | ```bash 17 | $ npm install 18 | ``` 19 | 20 | or use yarn 21 | ```bash 22 | $ yarn 23 | ``` 24 | 25 | ### request and change to your own app_key 26 | ``` 27 | cp myconfig_template.ts myconfig.ts 28 | ``` 29 | 30 | ### Start the dev server 31 | 32 | use npm 33 | ```bash 34 | $ npm start 35 | ``` 36 | 37 | or use npm 38 | ```bash 39 | $ yarn start 40 | ``` 41 | 42 | ### checkout in browser: http://localhost:8000 43 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "umi dev" 5 | }, 6 | "dependencies": { 7 | "js-md5": "^0.7.3", 8 | "mind-lake-sdk": "^1.0.5", 9 | "react": "17.x", 10 | "react-dom": "17.x", 11 | "umi": "^3.5.23" 12 | }, 13 | "devDependencies": { 14 | "@types/react": "^17.0.0", 15 | "@types/react-dom": "^17.0.0", 16 | "typescript": "^4.1.2", 17 | "yorkie": "^2.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/src/myconfig_template.ts: -------------------------------------------------------------------------------- 1 | export const appKey = "YOUR_APP_KEY="; 2 | export const nodeUrl = "https://sdk.mindnetwork.xyz"; // or change to other node url 3 | export const aliceWalletAddress = "Alice_Wallet_Address"; 4 | export const bobWalletAddress = "Bob_Wallet_Address"; 5 | export const charlieWalletAddress = "Charlie_Wallet_Address"; 6 | -------------------------------------------------------------------------------- /examples/src/pages/quickStart.tsx: -------------------------------------------------------------------------------- 1 | import { MindLake } from 'mind-lake-sdk'; 2 | import React from 'react'; 3 | import useUtils, { resultFormat } from '@/pages/useUtils'; 4 | 5 | const tableName = "table_encrypt"; 6 | const columns = [{columnName: 'id', type: MindLake.DataType.int4, encrypt: true}, {columnName: 'name', type: MindLake.DataType.text, encrypt: true}]; 7 | 8 | 9 | const Index = () => { 10 | 11 | const { result, login, logger} = useUtils(); 12 | 13 | const quickStart = async () => { 14 | const mindLake = await login(); 15 | if(!mindLake) { 16 | return ; 17 | } 18 | 19 | // create a table 20 | const dataLake = mindLake.dataLake; 21 | await dataLake.dropTable(tableName); 22 | 23 | let result = await dataLake.createTable(tableName, columns); 24 | logger(`create Table "${tableName}" columns "${JSON.stringify(columns)}" >>> ${resultFormat(result)}`); 25 | if(result.code !== 0) { 26 | return 27 | } 28 | 29 | // encrypt data 30 | const crypto = mindLake.crypto; 31 | result = await crypto.encrypt(1, `${tableName}.id`); 32 | logger(`encrypt(${tableName}.id, 1) >>> ${resultFormat(result)}`); 33 | if(result.code !== 0) { 34 | return 35 | } 36 | const encryptId = result.result; 37 | result = await crypto.encrypt("tom", `${tableName}.name`); 38 | logger(`encrypt(${tableName}.name, "tom") >>> ${resultFormat(result)}`); 39 | if(result.code !== 0) { 40 | return 41 | } 42 | const encryptName = result.result; 43 | // insert encrypted data 44 | const sql = `insert into ${tableName} (id, name) values ('${encryptId}', '${encryptName}')`; 45 | result = await dataLake.query(sql); 46 | logger(`${sql} >>> ${resultFormat(result)}`); 47 | if(result.code !== 0){ 48 | return 49 | } 50 | 51 | //query encrypted data; 52 | const selectSql = `select * from ${tableName}`; 53 | result = await dataLake.query(selectSql); 54 | logger(`${selectSql} >>> ${resultFormat(result)}`); 55 | if(result.code === 0) { 56 | const columnList = result.result.columnList; 57 | for (const row of result.result.data) { 58 | for (const index in row) { 59 | const encryptData = row[index]; 60 | const column = columnList[index]; 61 | const decryptRes = await crypto.decrypt(encryptData); 62 | logger(`decrypt(${tableName}.${column}) >>> ${resultFormat(decryptRes)}`) 63 | } 64 | } 65 | } 66 | }; 67 | 68 | 69 | return ( 70 |
71 |
72 |
73 | Logs output:
74 | { 75 | result.map((log, k) =>
{ log }
) 76 | } 77 |
78 |
79 | ) 80 | 81 | 82 | }; 83 | 84 | export default Index; 85 | -------------------------------------------------------------------------------- /examples/src/pages/useUtils.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { MindLake } from 'mind-lake-sdk'; 3 | import { appKey, nodeUrl } from '@/myconfig'; 4 | 5 | export const resultFormat = (result: any) => { 6 | if(typeof result === 'string') { 7 | return result 8 | } 9 | if(result.code === 0) { 10 | return result.result ? JSON.stringify(result.result) : true 11 | }else { 12 | return result.message 13 | } 14 | }; 15 | 16 | 17 | const useUtils = () => { 18 | 19 | const [result, setResult] = useState>([]); 20 | 21 | const logger = (info: string | object, init?: boolean) => { 22 | setResult(pre => !init ? [...pre, info] : [info]); 23 | window.scrollTo(0, document.documentElement.scrollHeight) 24 | }; 25 | 26 | const login = async (init = true) => { 27 | const mindLake = await MindLake.getInstance(appKey, nodeUrl); 28 | if(!mindLake) { 29 | return logger("mind lake init error", init) 30 | } 31 | logger("logging...please wait...", init); 32 | let result = await mindLake.connect(); 33 | if(result.code === 403) { 34 | logger(`login >>> ${JSON.stringify({message: `Thanks for your interest. The product is under active development and limit to invited users only until full release. Your wallets are currently not in the early trial and you can apply via: https://bit.ly/mindalphatest`})}`); 35 | return ; 36 | }else if(result.code !==0 ){ 37 | logger(`login >>> ${JSON.stringify(result.result)}`); 38 | return 39 | } 40 | 41 | logger(`login >>> ${JSON.stringify(result.result)}`); 42 | return mindLake; 43 | }; 44 | 45 | return { 46 | result, 47 | logger, 48 | login 49 | } 50 | }; 51 | 52 | export default useUtils; 53 | -------------------------------------------------------------------------------- /examples/src/pages/use_case_1.tsx: -------------------------------------------------------------------------------- 1 | import { MindLake } from 'mind-lake-sdk'; 2 | import React from 'react'; 3 | import useUtils, { resultFormat } from '@/pages/useUtils'; 4 | 5 | const tableName1 = "wallet_balance"; 6 | const columns1 = [{columnName: 'WalletAddress', type: MindLake.DataType.text, encrypt: false}, {columnName: 'Name', type: MindLake.DataType.text, encrypt: true}, {columnName: 'Balance', type: MindLake.DataType.float4, encrypt: true}]; 7 | 8 | 9 | const Index = () => { 10 | 11 | const { result, login, logger} = useUtils(); 12 | 13 | const useCase1 = async () => { 14 | const mindLake = await login(); 15 | if(!mindLake) { 16 | return ; 17 | } 18 | 19 | // create a table 20 | const dataLake = mindLake.dataLake; 21 | await dataLake.dropTable(tableName1); 22 | let result = await dataLake.createTable("wallet_balance", columns1, ["WalletAddress"]); 23 | logger(`create Table "${tableName1}" columns "${JSON.stringify(columns1)}" >>> ${resultFormat(result)}`); 24 | if(result.code !== 0) { 25 | return 26 | } 27 | 28 | // encrypt data 29 | const crypto = mindLake.crypto; 30 | result = await crypto.encrypt("Alice", `${tableName1}.Name`); 31 | logger(`encrypt(${tableName1}.Name, "Alice") >>> ${resultFormat(result)}`); 32 | if(result.code !== 0) { 33 | return 34 | } 35 | let encryptedName = result.result; 36 | result = await crypto.encrypt(10.5, `${tableName1}.Balance`); 37 | logger(`encrypt(${tableName1}.Balance, 10.5") >>> ${resultFormat(result)}`); 38 | if(result.code !==0 ) { 39 | return 40 | } 41 | let encryptedBalance = result.result; 42 | 43 | // insert encrypted data 44 | const sql_alice = `insert into wallet_balance ("WalletAddress", "Name", "Balance") values ('0xB2F588A50E43f58FEb0c05ff86a30D0d0b1BF065', '${encryptedName}', '${encryptedBalance}')`; 45 | result = await dataLake.query(sql_alice); 46 | logger(`${sql_alice} >>> ${resultFormat(result)}`); 47 | if(result.code !== 0){ 48 | return 49 | } 50 | 51 | result = await crypto.encrypt("Bob", `${tableName1}.Name`); 52 | logger(`encrypt(${tableName1}.Name, "Bob") >>> ${resultFormat(result)}`); 53 | if(result.code !== 0) { 54 | return 55 | } 56 | encryptedName = result.result; 57 | 58 | result = await crypto.encrypt(20.8, `${tableName1}.Balance`); 59 | logger(`encrypt(${tableName1}.Balance, 20.8") >>> ${resultFormat(result)}`); 60 | if(result.code !==0 ) { 61 | return 62 | } 63 | encryptedBalance = result.result; 64 | 65 | // insert encrypted data 66 | const sql_bob = `insert into wallet_balance ("WalletAddress", "Name", "Balance") values ('0x420c08373E2ba9C7566Ba0D210fB42A20a1eD2f8', '${encryptedName}', '${encryptedBalance}')`; 67 | result = await dataLake.query(sql_bob); 68 | logger(`${sql_bob} >>> ${resultFormat(result)}`); 69 | if(result.code !== 0){ 70 | return 71 | } 72 | //query encrypted data; 73 | const selectSql = `select * from ${tableName1}`; 74 | result = await dataLake.query(selectSql); 75 | logger(`${selectSql} >>> ${resultFormat(result)}`); 76 | if(result.code === 0) { 77 | const columnList = result.result.columnList; 78 | for (const row of result.result.data) { 79 | for (const index in row) { 80 | // @ts-ignore 81 | if(index > 0) { 82 | const encryptData = row[index]; 83 | const column = columnList[index]; 84 | const decryptRes = await crypto.decrypt(encryptData); 85 | logger(`decrypt(${tableName1}.${column}) >>> ${resultFormat(decryptRes)}`) 86 | } 87 | } 88 | } 89 | } 90 | 91 | }; 92 | 93 | 94 | return ( 95 |
96 |
97 |
98 | Logs output:
99 | { 100 | result.map((log, k) =>
{ log }
) 101 | } 102 |
103 |
104 | ) 105 | 106 | 107 | }; 108 | 109 | export default Index; 110 | -------------------------------------------------------------------------------- /examples/src/pages/use_case_2.tsx: -------------------------------------------------------------------------------- 1 | import useUtils, { resultFormat } from '@/pages/useUtils'; 2 | import React from 'react'; 3 | import { MindLake } from 'mind-lake-sdk'; 4 | // @ts-ignore 5 | import md5 from 'js-md5'; 6 | 7 | const tableName2 = "album_1"; 8 | const columns2 = [{columnName: 'name', type: MindLake.DataType.text, encrypt: false}, {columnName: 'picture', type: MindLake.DataType.text, encrypt: true}]; 9 | 10 | 11 | const Index = () => { 12 | 13 | const { result, login, logger} = useUtils(); 14 | 15 | const useCase2 = async () => { 16 | const mindLake = await login(); 17 | if(!mindLake) { 18 | return ; 19 | } 20 | 21 | const response = await fetch("https://avatars.githubusercontent.com/u/97393721"); 22 | if(!response || response.status !== 200) { 23 | return logger("Failed to get picture from github") 24 | } 25 | const image = await response.arrayBuffer(); 26 | logger(`MD5 of the original picture pic_origin.png: >>> ${md5(image)}`); 27 | const base64 = Buffer.from(image).toString('base64'); 28 | logger('get picture from github >>>'); 29 | logger() 30 | // create a table 31 | const dataLake = mindLake.dataLake; 32 | await dataLake.dropTable(tableName2); 33 | let result = await dataLake.createTable(tableName2, columns2); 34 | logger(`create Table "${tableName2}" columns "${JSON.stringify(columns2)}" >>> ${resultFormat(result)}`); 35 | if(result.code !==0 ) { 36 | return 37 | } 38 | const crypto = mindLake.crypto; 39 | result = await crypto.encrypt(base64, `${tableName2}.picture`); 40 | logger(`encrypt(${tableName2}.picture) >>>${resultFormat(result)}`); 41 | if(result.code !== 0) { 42 | return 43 | } 44 | const sql = `insert into ${tableName2} (name, picture) values ('mind.png', '${result.result}')`; 45 | result = await dataLake.query(sql); 46 | logger(`${sql} >>> ${resultFormat(result)}`); 47 | if(result.code !== 0) { 48 | return 49 | } 50 | 51 | const selectSql = `select * from ${tableName2}`; 52 | result = await dataLake.query(selectSql); 53 | logger(`${selectSql} >>> ${resultFormat(result)}`); 54 | if(result.code !== 0) { 55 | return 56 | } 57 | for (const row of result.result.data) { 58 | const decrypt = await crypto.decrypt(row[1]); 59 | logger(`decrypt(${tableName2}.picture) >>> ${JSON.stringify(md5(Buffer.from(decrypt.result, 'base64')))}`); 60 | logger('decrypt picture from mind lake >>>'); 61 | logger() 62 | } 63 | }; 64 | 65 | return ( 66 |
67 |
68 |
69 | Logs output:
70 | { 71 | result.map((log, k) =>
{ log }
) 72 | } 73 |
74 |
75 | ) 76 | }; 77 | 78 | export default Index; 79 | -------------------------------------------------------------------------------- /examples/src/pages/use_case_3.tsx: -------------------------------------------------------------------------------- 1 | import useUtils, { resultFormat } from '@/pages/useUtils'; 2 | import React, { useRef } from 'react'; 3 | import { MindLake } from 'mind-lake-sdk'; 4 | import { aliceWalletAddress as alice, bobWalletAddress as bob, charlieWalletAddress as charlie} from '@/myconfig'; 5 | 6 | const tableName3 = "transaction"; 7 | const columns3 = [{columnName: 'WalletAddress', type: MindLake.DataType.text, encrypt: false}, {columnName: 'Token', type: MindLake.DataType.text, encrypt: true}, {columnName: 'Volume', type: MindLake.DataType.float4, encrypt: true}]; 8 | 9 | const dataAlice = [ 10 | { WalletAddress: "0x8CFB38b2cba74757431B205612E349B8b9a9E661", Token: 'USDT', Volume: 5.6 }, 11 | { WalletAddress: "0xD862D48f36ce6298eFD00474eC852b8838a54F66", Token: 'BUSD', Volume: 6.3 }, 12 | { WalletAddress: "0x8CFB38b2cba74757431B205612E349B8b9a9E661", Token: 'BUSD', Volume: 10.3}, 13 | ]; 14 | 15 | const dataBob = [ 16 | { WalletAddress: '0xD862D48f36ce6298eFD00474eC852b8838a54F66', Token: 'USDT', Volume: 3.3}, 17 | { WalletAddress: '0x70dBcC09edF6D9AdD4A235e2D8346E78A79ac770', Token: 'BUSD', Volume: 9.8}, 18 | { WalletAddress: '0x70dBcC09edF6D9AdD4A235e2D8346E78A79ac770', Token: 'USDT', Volume: 7.7} 19 | ]; 20 | 21 | const Index = () => { 22 | 23 | const { result: resultAlice, login: loginAlice, logger: loggerAlice } = useUtils(); 24 | 25 | const { result: resultBob, login: loginBob, logger: loggerBob } = useUtils(); 26 | 27 | const { result: resultCharlie, login: loginCharlie, logger: loggerCharlie } = useUtils(); 28 | 29 | const policyList = useRef>([]); 30 | 31 | const insertData = async (data: Array, role: string) => { 32 | let login,logger; 33 | if(role === 'alice') { 34 | login = loginAlice; 35 | logger = loggerAlice; 36 | }else { 37 | login = loginBob; 38 | logger = loggerBob; 39 | } 40 | 41 | const mindLake = await login(false); 42 | if(!mindLake) { 43 | return 44 | } 45 | // create a table 46 | const dataLake = mindLake.dataLake; 47 | await dataLake.dropTable(tableName3); 48 | let result = await dataLake.createTable(tableName3, columns3); 49 | logger(`create Table ${tableName3} columns ${JSON.stringify(columns3)} >>> ${resultFormat(result)}`); 50 | if(result.code !==0 ) { 51 | return 52 | } 53 | 54 | // encrypt data 55 | const crypto = mindLake.crypto; 56 | for (const row of data) { 57 | const walletAddress = row.WalletAddress; 58 | const encryptToken = await crypto.encrypt(row.Token, `${tableName3}.Token`); 59 | logger(`encrypt(${walletAddress}.${tableName3}.Token, ${row.Token}) >>> ${encryptToken.result}`); 60 | const encryptVolume = await crypto.encrypt(row.Volume, `${tableName3}.Volume`); 61 | logger(`encrypt(${walletAddress}.${tableName3}.Volume, ${row.Volume}) >>> ${encryptVolume.result}`); 62 | const sql = `insert into transaction ("WalletAddress", "Token", "Volume") values ('${walletAddress}', '${encryptToken.result}', '${encryptVolume.result}')`; 63 | result = await dataLake.query(sql); 64 | logger(`${sql} >>> ${resultFormat(result)}`); 65 | if(result.code !== 0) { 66 | return 67 | } 68 | } 69 | const permission = mindLake.permission; 70 | result = await permission.grant(charlie, [`${tableName3}.Token`, `${tableName3}.Volume`]); 71 | logger(`grant columns ${JSON.stringify([`${tableName3}.Token`, `${tableName3}.Volume`])} to charlie >>> ${resultFormat(result)}`); 72 | if(result.code !==0 ) { 73 | return 74 | } 75 | logger(`Insert data done`); 76 | await mindLake.disConnect(); 77 | if(policyList.current) { 78 | if(policyList.current.length > 2) { 79 | policyList.current = []; 80 | } 81 | policyList.current.push(result.result) 82 | } 83 | }; 84 | 85 | const charlieQuery = async () => { 86 | const logger = loggerCharlie; 87 | const login = loginCharlie; 88 | if(!policyList.current || policyList.current.length != 2) { 89 | return logger(`Please wait Alice or Bob grant data`) 90 | } 91 | console.log(policyList.current); 92 | // @ts-ignore 93 | const [policyAliceID, policyBobID] = policyList.current; 94 | const mindLake = await login(false); 95 | if(!mindLake) { 96 | return ; 97 | } 98 | const permission = mindLake.permission; 99 | const dataLake = mindLake.dataLake; 100 | const crypto = mindLake.crypto; 101 | let result = await permission.confirm(policyAliceID); 102 | logger(`charlie confirm grant policyAliceId=${policyAliceID} >>> ${resultFormat(result)}`); 103 | if(result.code !== 0) { 104 | return 105 | } 106 | result = await permission.confirm(policyBobID); 107 | logger(`charlie confirm grant policyAliceId=${policyBobID} >>> ${resultFormat(result)}`); 108 | if(result.code !== 0) { 109 | return 110 | } 111 | 112 | const sql = `SELECT combine."WalletAddress", SUM(combine."Volume") FROM 113 | (SELECT "WalletAddress","Volume" FROM "${alice.slice(2).toLocaleLowerCase()}"."transaction" 114 | UNION ALL 115 | SELECT "WalletAddress","Volume" FROM "${bob.slice(2).toLocaleLowerCase()}"."transaction") as combine 116 | GROUP BY "WalletAddress"`; 117 | result = await dataLake.query(sql); 118 | logger(`${sql} >>> ${resultFormat(result)}`); 119 | if(result.code !== 0) { 120 | return 121 | } 122 | const columnList = result.result.columnList; 123 | for (const row of result.result.data) { 124 | const walletAddress = row[0]; 125 | result = await crypto.decrypt(row[1]); 126 | logger(`${walletAddress}.${columnList[1]} >>> ${resultFormat(result)}`); 127 | if(result.code !== 0) { 128 | return 129 | } 130 | } 131 | }; 132 | 133 | 134 | return ( 135 |
136 |
137 | 138 |
139 | Logs output:
140 | { 141 | resultAlice.map((log, k) =>
{ log }
) 142 | } 143 |
144 |
145 |
146 | 147 |
148 | Logs output:
149 | { 150 | resultBob.map((log, k) =>
{ log }
) 151 | } 152 |
153 |
154 |
155 | 156 |
157 | Logs output:
158 | { 159 | resultCharlie.map((log, k) =>
{ log }
) 160 | } 161 |
162 |
163 | 164 |
165 | ) 166 | 167 | }; 168 | 169 | export default Index; 170 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "importHelpers": true, 8 | "jsx": "react-jsx", 9 | "esModuleInterop": true, 10 | "sourceMap": true, 11 | "baseUrl": "./", 12 | "strict": true, 13 | "paths": { 14 | "@/*": ["src/*"], 15 | "@@/*": ["src/.umi/*"] 16 | }, 17 | "allowSyntheticDefaultImports": true 18 | }, 19 | "include": [ 20 | "mock/**/*", 21 | "src/**/*", 22 | "config/**/*", 23 | ".umirc.ts", 24 | "typings.d.ts" 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "lib", 29 | "es", 30 | "dist", 31 | "typings", 32 | "**/__test__", 33 | "test", 34 | "docs", 35 | "tests" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /examples/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | declare module '*.png'; 4 | declare module '*.svg' { 5 | export function ReactComponent( 6 | props: React.SVGProps, 7 | ): React.ReactElement; 8 | const url: string; 9 | export default url; 10 | } 11 | -------------------------------------------------------------------------------- /examples_static/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /yarn.lock 8 | 9 | # production 10 | /dist 11 | 12 | # misc 13 | .DS_Store 14 | 15 | # umi 16 | /src/.umi 17 | /src/.umi-production 18 | /src/.umi-test 19 | /.env.local 20 | -------------------------------------------------------------------------------- /examples_static/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | package.json 6 | .umi 7 | .umi-production 8 | .umi-test 9 | -------------------------------------------------------------------------------- /examples_static/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples_static/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | 3 | export default defineConfig({ 4 | routes: [ 5 | { path: '/use_case_1', component: '@/pages/use_case_1' }, 6 | { path: '/use_case_2', component: '@/pages/use_case_2' }, 7 | { path: '/use_case_3', component: '@/pages/use_case_3' }, 8 | ], 9 | // exportStatic: { 10 | // dynamicRoot: true, 11 | // htmlSuffix: true 12 | // }, 13 | mpa: {}, 14 | publicPath: './' 15 | }); 16 | -------------------------------------------------------------------------------- /examples_static/Dockerfile_use_case1: -------------------------------------------------------------------------------- 1 | # Dockerfile_use_case1 2 | # build front-end 3 | FROM node:16 AS frontend 4 | 5 | WORKDIR /app 6 | 7 | COPY ./ /app 8 | 9 | #COPY ./pnpm-lock.yaml /app 10 | RUN node -v 11 | 12 | #RUN npm install --registry=https://registry.npmmirror.com/ 13 | RUN npm install --registry=https://registry.npmmirror.com/ 14 | 15 | #ENV NODE_OPTIONS=--openssl-legacy-provider 16 | 17 | RUN npm run build 18 | 19 | COPY ./ /app 20 | 21 | FROM nginx:stable-alpine 22 | # 23 | COPY --from=frontend /app/dist/use_case_1 /usr/share/nginx/html 24 | ## 25 | COPY --from=frontend /app/ngnix/nginx_case_1.conf /etc/nginx/conf.d/default.conf 26 | 27 | EXPOSE 80 28 | -------------------------------------------------------------------------------- /examples_static/Dockerfile_use_case2: -------------------------------------------------------------------------------- 1 | # Dockerfile_use_case2 2 | # build front-end 3 | FROM node:16 AS frontend 4 | 5 | WORKDIR /app 6 | 7 | COPY ./ /app 8 | 9 | #COPY ./pnpm-lock.yaml /app 10 | RUN node -v 11 | 12 | #RUN npm install --registry=https://registry.npmmirror.com/ 13 | RUN npm install --registry=https://registry.npmmirror.com/ 14 | 15 | #ENV NODE_OPTIONS=--openssl-legacy-provider 16 | 17 | RUN npm run build 18 | 19 | COPY ./ /app 20 | 21 | FROM nginx:stable-alpine 22 | # 23 | COPY --from=frontend /app/dist/use_case_2 /usr/share/nginx/html 24 | ## 25 | COPY --from=frontend /app/ngnix/nginx_case_2.conf /etc/nginx/conf.d/default.conf 26 | 27 | EXPOSE 80 28 | -------------------------------------------------------------------------------- /examples_static/Dockerfile_use_case3: -------------------------------------------------------------------------------- 1 | # Dockerfile_use_case2=3 2 | # build front-end 3 | FROM node:16 AS frontend 4 | 5 | WORKDIR /app 6 | 7 | COPY ./ /app 8 | 9 | #COPY ./pnpm-lock.yaml /app 10 | RUN node -v 11 | 12 | #RUN npm install --registry=https://registry.npmmirror.com/ 13 | RUN npm install --registry=https://registry.npmmirror.com/ 14 | 15 | #ENV NODE_OPTIONS=--openssl-legacy-provider 16 | 17 | RUN npm run build 18 | 19 | COPY ./ /app 20 | 21 | FROM nginx:stable-alpine 22 | # 23 | COPY --from=frontend /app/dist/use_case_3 /usr/share/nginx/html 24 | ## 25 | COPY --from=frontend /app/ngnix/nginx_case_3.conf /etc/nginx/conf.d/default.conf 26 | 27 | EXPOSE 80 28 | -------------------------------------------------------------------------------- /examples_static/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | A quick example on how to use mind-lake-sdk TypeScript library 3 | 4 | ## Getting Started 5 | 6 | ### Install dependencies 7 | 8 | install mind-lake-sdk manual 9 | ```bash 10 | $ npm install --save mind-lake-sdk 11 | ``` 12 | 13 | install other all packages required for this quick demo. mind-lake-sdk (TypeScript) is defined in package.json 14 | 15 | use npm 16 | ```bash 17 | $ npm install 18 | ``` 19 | 20 | or use yarn 21 | ```bash 22 | $ yarn 23 | ``` 24 | 25 | ### request and change to your own app_key 26 | ``` 27 | cp myconfig_template.ts myconfig.ts 28 | ``` 29 | 30 | ### Start the dev server 31 | 32 | use npm 33 | ```bash 34 | $ npm start 35 | ``` 36 | 37 | or use npm 38 | ```bash 39 | $ yarn start 40 | ``` 41 | ### checkout in browser: http://localhost:8000 42 | 43 | ### build to IPFS 44 | 45 | use npm 46 | ``` 47 | $ npm run build-to-ipfs 48 | ``` 49 | 50 | ### build to arweave 51 | use npm 52 | ``` 53 | $ npm run build-to-arweave 54 | ``` 55 | -------------------------------------------------------------------------------- /examples_static/build.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | const basePath = __dirname+ '/dist/'; 4 | 5 | //创建3个文件夹 6 | for (let i = 1; i < 4; i ++) { 7 | const oldPath = basePath + `use_case_${i}.html`; 8 | const newPath = basePath + 'use_case_'+ i; 9 | fs.mkdirSync(newPath); 10 | fs.renameSync(oldPath, newPath+`/use_case_${i}.html`); 11 | fs.copyFileSync(basePath + 'umi.js', newPath+ '/umi.js') 12 | } 13 | 14 | fs.unlinkSync(basePath + 'umi.js'); 15 | 16 | -------------------------------------------------------------------------------- /examples_static/build_arweave.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | const basePath = __dirname+ '/dist/'; 4 | 5 | const jsFile = fs.readFileSync(basePath + 'umi.js').toString(); 6 | 7 | for (let i = 1; i < 2; i ++) { 8 | const filePath = basePath +`use_case_${i}.html`; 9 | const htmlFile = fs.readFileSync(filePath).toString(); 10 | const result = htmlFile.replace('', 11 | `` 14 | ); 15 | fs.writeFileSync('test.txt', result, {encoding: 'utf-8', flag: 'w'}) 16 | } 17 | 18 | fs.unlinkSync(basePath + 'umi.js'); 19 | -------------------------------------------------------------------------------- /examples_static/ngnix/nginx_case_1.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | charset utf-8; 5 | error_page 500 502 503 504 /50x.html; 6 | location / { 7 | root /usr/share/nginx/html; 8 | index index.html; 9 | } 10 | rewrite ^/index\.html /use_case_1.html permanent; 11 | 12 | proxy_set_header Host $host; 13 | proxy_set_header X-Real-IP $remote_addr; 14 | proxy_set_header REMOTE-HOST $remote_addr; 15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 16 | } 17 | -------------------------------------------------------------------------------- /examples_static/ngnix/nginx_case_2.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | charset utf-8; 5 | error_page 500 502 503 504 /50x.html; 6 | location / { 7 | root /usr/share/nginx/html; 8 | index index.html; 9 | } 10 | rewrite ^/index\.html /use_case_2.html permanent; 11 | 12 | proxy_set_header Host $host; 13 | proxy_set_header X-Real-IP $remote_addr; 14 | proxy_set_header REMOTE-HOST $remote_addr; 15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 16 | } 17 | -------------------------------------------------------------------------------- /examples_static/ngnix/nginx_case_3.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | charset utf-8; 5 | error_page 500 502 503 504 /50x.html; 6 | location / { 7 | root /usr/share/nginx/html; 8 | index index.html; 9 | } 10 | rewrite ^/index\.html /use_case_3.html permanent; 11 | 12 | proxy_set_header Host $host; 13 | proxy_set_header X-Real-IP $remote_addr; 14 | proxy_set_header REMOTE-HOST $remote_addr; 15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 16 | } 17 | -------------------------------------------------------------------------------- /examples_static/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "umi dev", 5 | "build": "umi build", 6 | "build-to-ipfs": "umi build", 7 | "build-to-arweave": "umi build && node build_arweave.js", 8 | "analyze": "cross-env ANALYZE=1 umi build" 9 | }, 10 | "dependencies": { 11 | "js-md5": "^0.7.3", 12 | "mind-lake-sdk": "latest", 13 | "react": "17.x", 14 | "react-dom": "17.x", 15 | "umi": "^3.5.23" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^17.0.0", 19 | "@types/react-dom": "^17.0.0", 20 | "typescript": "^4.1.2", 21 | "yorkie": "^2.0.0", 22 | "cross-env": "^7.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples_static/src/myconfig_template.ts: -------------------------------------------------------------------------------- 1 | export const appKey = "YOUR_APP_KEY="; 2 | export const nodeUrl = "https://sdk.mindnetwork.xyz"; // or change to other node url 3 | export const aliceWalletAddress = "Alice_Wallet_Address"; 4 | export const bobWalletAddress = "Bob_Wallet_Address"; 5 | export const charlieWalletAddress = "Charlie_Wallet_Address"; 6 | -------------------------------------------------------------------------------- /examples_static/src/pages/useUtils.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { MindLake } from 'mind-lake-sdk'; 3 | import { appKey, nodeUrl } from '@/myconfig'; 4 | 5 | export const resultFormat = (result: any) => { 6 | if(typeof result === 'string') { 7 | return result 8 | } 9 | if(result.code === 0) { 10 | return result.result ? JSON.stringify(result.result) : true 11 | }else { 12 | return result.message 13 | } 14 | }; 15 | 16 | 17 | const useUtils = () => { 18 | const [loading, setLoading] = useState(false); 19 | const [result, setResult] = useState>([]); 20 | 21 | const logger = (info: string | object, init?: boolean) => { 22 | setResult(pre => !init ? [...pre, info] : [info]); 23 | window.scrollTo(0, document.documentElement.scrollHeight) 24 | }; 25 | 26 | const login = async (init = true) => { 27 | const mindLake = await MindLake.getInstance(appKey, nodeUrl).catch(e => logger(`mind lake init error >>> ${JSON.stringify(e)}`, init)); 28 | if(!mindLake) { 29 | return 30 | } 31 | setLoading(true); 32 | logger("logging...please wait...", init); 33 | let result = await mindLake.connect(); 34 | setLoading(false); 35 | if(result.code === 403) { 36 | logger(`login >>> ${JSON.stringify({message: `Thanks for your interest. The product is under active development and limit to invited users only until full release. Your wallets are currently not in the early trial and you can apply via: https://bit.ly/mindalphatest`})}`); 37 | return ; 38 | }else if(result.code !==0 ){ 39 | logger(`login >>> ${JSON.stringify(result)}`); 40 | return 41 | } 42 | 43 | logger(`login >>> ${JSON.stringify(result.result)}`); 44 | return mindLake; 45 | }; 46 | 47 | return { 48 | result, 49 | logger, 50 | login, 51 | loading 52 | } 53 | }; 54 | 55 | export default useUtils; 56 | -------------------------------------------------------------------------------- /examples_static/src/pages/use_case_1.tsx: -------------------------------------------------------------------------------- 1 | import { MindLake } from 'mind-lake-sdk'; 2 | import React from 'react'; 3 | import useUtils, { resultFormat } from '@/pages/useUtils'; 4 | 5 | const tableName1 = "wallet_balance"; 6 | const columns1 = [{columnName: 'WalletAddress', type: MindLake.DataType.text, encrypt: false}, {columnName: 'Name', type: MindLake.DataType.text, encrypt: true}, {columnName: 'Balance', type: MindLake.DataType.float4, encrypt: true}]; 7 | 8 | 9 | const Index = () => { 10 | 11 | const { result, login, logger, loading} = useUtils(); 12 | 13 | const useCase1 = async () => { 14 | const mindLake = await login(); 15 | if(!mindLake) { 16 | return ; 17 | } 18 | 19 | // create a table 20 | const dataLake = mindLake.dataLake; 21 | await dataLake.dropTable(tableName1); 22 | let result = await dataLake.createTable("wallet_balance", columns1, ["WalletAddress"]); 23 | logger(`create Table "${tableName1}" columns "${JSON.stringify(columns1)}" >>> ${resultFormat(result)}`); 24 | if(result.code !== 0) { 25 | return 26 | } 27 | 28 | // encrypt data 29 | const crypto = mindLake.crypto; 30 | result = await crypto.encrypt("Alice", `${tableName1}.Name`); 31 | logger(`encrypt(${tableName1}.Name, "Alice") >>> ${resultFormat(result)}`); 32 | if(result.code !== 0) { 33 | return 34 | } 35 | let encryptedName = result.result; 36 | result = await crypto.encrypt(10.5, `${tableName1}.Balance`); 37 | logger(`encrypt(${tableName1}.Balance, 10.5") >>> ${resultFormat(result)}`); 38 | if(result.code !==0 ) { 39 | return 40 | } 41 | let encryptedBalance = result.result; 42 | 43 | // insert encrypted data 44 | const sql_alice = `insert into wallet_balance ("WalletAddress", "Name", "Balance") values ('0xB2F588A50E43f58FEb0c05ff86a30D0d0b1BF065', '${encryptedName}', '${encryptedBalance}')`; 45 | result = await dataLake.query(sql_alice); 46 | logger(`${sql_alice} >>> ${resultFormat(result)}`); 47 | if(result.code !== 0){ 48 | return 49 | } 50 | 51 | result = await crypto.encrypt("Bob", `${tableName1}.Name`); 52 | logger(`encrypt(${tableName1}.Name, "Bob") >>> ${resultFormat(result)}`); 53 | if(result.code !== 0) { 54 | return 55 | } 56 | encryptedName = result.result; 57 | 58 | result = await crypto.encrypt(20.8, `${tableName1}.Balance`); 59 | logger(`encrypt(${tableName1}.Balance, 20.8") >>> ${resultFormat(result)}`); 60 | if(result.code !==0 ) { 61 | return 62 | } 63 | encryptedBalance = result.result; 64 | 65 | // insert encrypted data 66 | const sql_bob = `insert into wallet_balance ("WalletAddress", "Name", "Balance") values ('0x420c08373E2ba9C7566Ba0D210fB42A20a1eD2f8', '${encryptedName}', '${encryptedBalance}')`; 67 | result = await dataLake.query(sql_bob); 68 | logger(`${sql_bob} >>> ${resultFormat(result)}`); 69 | if(result.code !== 0){ 70 | return 71 | } 72 | //query encrypted data; 73 | const selectSql = `select * from ${tableName1}`; 74 | result = await dataLake.query(selectSql); 75 | logger(`${selectSql} >>> ${resultFormat(result)}`); 76 | if(result.code === 0) { 77 | const columnList = result.result.columnList; 78 | for (const row of result.result.data) { 79 | for (const index in row) { 80 | // @ts-ignore 81 | if(index > 0) { 82 | const encryptData = row[index]; 83 | const column = columnList[index]; 84 | const decryptRes = await crypto.decrypt(encryptData); 85 | logger(`decrypt(${tableName1}.${column}) >>> ${resultFormat(decryptRes)}`) 86 | } 87 | } 88 | } 89 | } 90 | 91 | }; 92 | 93 | 94 | return ( 95 |
96 |
97 |
98 | Logs output:
99 | { 100 | result.map((log, k) =>
{ log }
) 101 | } 102 |
103 |
104 | ) 105 | 106 | 107 | }; 108 | 109 | export default Index; 110 | -------------------------------------------------------------------------------- /examples_static/src/pages/use_case_2.tsx: -------------------------------------------------------------------------------- 1 | import useUtils, { resultFormat } from '@/pages/useUtils'; 2 | import React from 'react'; 3 | import { MindLake } from 'mind-lake-sdk'; 4 | // @ts-ignore 5 | import md5 from 'js-md5'; 6 | 7 | const tableName2 = "album_1"; 8 | const columns2 = [{columnName: 'name', type: MindLake.DataType.text, encrypt: false}, {columnName: 'picture', type: MindLake.DataType.text, encrypt: true}]; 9 | 10 | 11 | const Index = () => { 12 | 13 | const { result, login, logger, loading} = useUtils(); 14 | 15 | const useCase2 = async () => { 16 | const mindLake = await login(); 17 | if(!mindLake) { 18 | return ; 19 | } 20 | 21 | const response = await fetch("https://avatars.githubusercontent.com/u/97393721"); 22 | if(!response || response.status !== 200) { 23 | return logger("Failed to get picture from github") 24 | } 25 | const image = await response.arrayBuffer(); 26 | logger(`MD5 of the original picture pic_origin.png: >>> ${md5(image)}`); 27 | const base64 = Buffer.from(image).toString('base64'); 28 | logger('get picture from github >>>'); 29 | logger() 30 | // create a table 31 | const dataLake = mindLake.dataLake; 32 | await dataLake.dropTable(tableName2); 33 | let result = await dataLake.createTable(tableName2, columns2); 34 | logger(`create Table "${tableName2}" columns "${JSON.stringify(columns2)}" >>> ${resultFormat(result)}`); 35 | if(result.code !==0 ) { 36 | return 37 | } 38 | const crypto = mindLake.crypto; 39 | result = await crypto.encrypt(base64, `${tableName2}.picture`); 40 | logger(`encrypt(${tableName2}.picture) >>>${resultFormat(result)}`); 41 | if(result.code !== 0) { 42 | return 43 | } 44 | const sql = `insert into ${tableName2} (name, picture) values ('mind.png', '${result.result}')`; 45 | result = await dataLake.query(sql); 46 | logger(`${sql} >>> ${resultFormat(result)}`); 47 | if(result.code !== 0) { 48 | return 49 | } 50 | 51 | const selectSql = `select * from ${tableName2}`; 52 | result = await dataLake.query(selectSql); 53 | logger(`${selectSql} >>> ${resultFormat(result)}`); 54 | if(result.code !== 0) { 55 | return 56 | } 57 | for (const row of result.result.data) { 58 | const decrypt = await crypto.decrypt(row[1]); 59 | logger(`decrypt(${tableName2}.picture) >>> ${JSON.stringify(md5(Buffer.from(decrypt.result, 'base64')))}`); 60 | logger('decrypt picture from mind lake >>>'); 61 | logger() 62 | } 63 | }; 64 | 65 | return ( 66 |
67 |
68 |
69 | Logs output:
70 | { 71 | result.map((log, k) =>
{ log }
) 72 | } 73 |
74 |
75 | ) 76 | }; 77 | 78 | export default Index; 79 | -------------------------------------------------------------------------------- /examples_static/src/pages/use_case_3.tsx: -------------------------------------------------------------------------------- 1 | import useUtils, { resultFormat } from '@/pages/useUtils'; 2 | import React, { useRef, useState } from 'react'; 3 | import { MindLake } from 'mind-lake-sdk'; 4 | // import { aliceWalletAddress as alice, bobWalletAddress as bob, charlieWalletAddress as charlie} from '@/myconfig'; 5 | 6 | const tableName3 = "transaction"; 7 | const columns3 = [{columnName: 'WalletAddress', type: MindLake.DataType.text, encrypt: false}, {columnName: 'Token', type: MindLake.DataType.text, encrypt: true}, {columnName: 'Volume', type: MindLake.DataType.float4, encrypt: true}]; 8 | 9 | const dataAlice = [ 10 | { WalletAddress: "0x8CFB38b2cba74757431B205612E349B8b9a9E661", Token: 'USDT', Volume: 5.6 }, 11 | { WalletAddress: "0xD862D48f36ce6298eFD00474eC852b8838a54F66", Token: 'BUSD', Volume: 6.3 }, 12 | { WalletAddress: "0x8CFB38b2cba74757431B205612E349B8b9a9E661", Token: 'BUSD', Volume: 10.3}, 13 | ]; 14 | 15 | const dataBob = [ 16 | { WalletAddress: '0xD862D48f36ce6298eFD00474eC852b8838a54F66', Token: 'USDT', Volume: 3.3}, 17 | { WalletAddress: '0x70dBcC09edF6D9AdD4A235e2D8346E78A79ac770', Token: 'BUSD', Volume: 9.8}, 18 | { WalletAddress: '0x70dBcC09edF6D9AdD4A235e2D8346E78A79ac770', Token: 'USDT', Volume: 7.7} 19 | ]; 20 | 21 | const Index = () => { 22 | 23 | const { result: resultAlice, login: loginAlice, logger: loggerAlice, loading: loadingAlice } = useUtils(); 24 | 25 | const { result: resultBob, login: loginBob, logger: loggerBob, loading: loadingBob } = useUtils(); 26 | 27 | const { result: resultCharlie, login: loginCharlie, logger: loggerCharlie, loading: loadingCharlie } = useUtils(); 28 | 29 | const policyList = useRef>([]); 30 | 31 | const [alice, setAlice] = useState(""); 32 | const [bob, setBob] = useState(""); 33 | const [charlie, setCharlie] = useState(""); 34 | 35 | const insertData = async (data: Array, role: string) => { 36 | let login,logger; 37 | if(role === 'alice') { 38 | login = loginAlice; 39 | logger = loggerAlice; 40 | }else{ 41 | login = loginBob; 42 | logger = loggerBob; 43 | } 44 | if(!alice || !bob || !charlie) { 45 | logger(`Please input alice , bob and charlie wallet address`); 46 | return ; 47 | } 48 | 49 | const mindLake = await login(false); 50 | if(!mindLake) { 51 | return 52 | } 53 | // create a table 54 | const dataLake = mindLake.dataLake; 55 | await dataLake.dropTable(tableName3); 56 | let result = await dataLake.createTable(tableName3, columns3); 57 | logger(`create Table ${tableName3} columns ${JSON.stringify(columns3)} >>> ${resultFormat(result)}`); 58 | if(result.code !==0 ) { 59 | return 60 | } 61 | 62 | // encrypt data 63 | const crypto = mindLake.crypto; 64 | for (const row of data) { 65 | const walletAddress = row.WalletAddress; 66 | const encryptToken = await crypto.encrypt(row.Token, `${tableName3}.Token`); 67 | logger(`encrypt(${walletAddress}.${tableName3}.Token, ${row.Token}) >>> ${encryptToken.result}`); 68 | const encryptVolume = await crypto.encrypt(row.Volume, `${tableName3}.Volume`); 69 | logger(`encrypt(${walletAddress}.${tableName3}.Volume, ${row.Volume}) >>> ${encryptVolume.result}`); 70 | const sql = `insert into transaction ("WalletAddress", "Token", "Volume") values ('${walletAddress}', '${encryptToken.result}', '${encryptVolume.result}')`; 71 | result = await dataLake.query(sql); 72 | logger(`${sql} >>> ${resultFormat(result)}`); 73 | if(result.code !== 0) { 74 | return 75 | } 76 | } 77 | const permission = mindLake.permission; 78 | result = await permission.grant(charlie, [`${tableName3}.Token`, `${tableName3}.Volume`]); 79 | logger(`grant columns ${JSON.stringify([`${tableName3}.Token`, `${tableName3}.Volume`])} to charlie >>> ${resultFormat(result)}`); 80 | if(result.code !==0 ) { 81 | return 82 | } 83 | logger(`Insert data done`); 84 | await mindLake.disConnect(); 85 | if(policyList.current) { 86 | if(policyList.current.length > 2) { 87 | policyList.current = []; 88 | } 89 | policyList.current.push(result.result) 90 | } 91 | }; 92 | 93 | const charlieQuery = async () => { 94 | const logger = loggerCharlie; 95 | const login = loginCharlie; 96 | if(!alice || !bob || !charlie) { 97 | logger(`Please input alice , bob and charlie wallet address`); 98 | return ; 99 | } 100 | 101 | if(!policyList.current || policyList.current.length != 2) { 102 | return logger(`Please wait Alice or Bob grant data`) 103 | } 104 | console.log(policyList.current); 105 | // @ts-ignore 106 | const [policyAliceID, policyBobID] = policyList.current; 107 | const mindLake = await login(false); 108 | if(!mindLake) { 109 | return ; 110 | } 111 | const permission = mindLake.permission; 112 | const dataLake = mindLake.dataLake; 113 | const crypto = mindLake.crypto; 114 | let result = await permission.confirm(policyAliceID); 115 | logger(`charlie confirm grant policyAliceId=${policyAliceID} >>> ${resultFormat(result)}`); 116 | if(result.code !== 0) { 117 | return 118 | } 119 | result = await permission.confirm(policyBobID); 120 | logger(`charlie confirm grant policyAliceId=${policyBobID} >>> ${resultFormat(result)}`); 121 | if(result.code !== 0) { 122 | return 123 | } 124 | 125 | const sql = `SELECT combine."WalletAddress", SUM(combine."Volume") FROM 126 | (SELECT "WalletAddress","Volume" FROM "${alice.slice(2).toLocaleLowerCase()}"."transaction" 127 | UNION ALL 128 | SELECT "WalletAddress","Volume" FROM "${bob.slice(2).toLocaleLowerCase()}"."transaction") as combine 129 | GROUP BY "WalletAddress"`; 130 | result = await dataLake.query(sql); 131 | logger(`${sql} >>> ${resultFormat(result)}`); 132 | if(result.code !== 0) { 133 | return 134 | } 135 | const columnList = result.result.columnList; 136 | for (const row of result.result.data) { 137 | const walletAddress = row[0]; 138 | result = await crypto.decrypt(row[1]); 139 | logger(`${walletAddress}.${columnList[1]} >>> ${resultFormat(result)}`); 140 | if(result.code !== 0) { 141 | return 142 | } 143 | } 144 | }; 145 | 146 | 147 | return ( 148 | <> 149 |
Before starting this use case, please confirm that the 3 wallets have been registered on the https://scan.mindnetwork.xyz
150 |
151 |
Alice address:
setAlice(e.target.value)}/>
152 |
Bob address:
setBob(e.target.value)}/>
153 |
Charlie address:
setCharlie(e.target.value)}/>
154 |
155 |
156 |
157 | 162 |
163 | Logs output:
164 | { 165 | resultAlice.map((log, k) =>
{ log }
) 166 | } 167 |
168 |
169 |
170 | 174 |
175 | Logs output:
176 | { 177 | resultBob.map((log, k) =>
{ log }
) 178 | } 179 |
180 |
181 |
182 | 186 |
187 | Logs output:
188 | { 189 | resultCharlie.map((log, k) =>
{ log }
) 190 | } 191 |
192 |
193 |
194 | 195 | ) 196 | 197 | }; 198 | 199 | export default Index; 200 | -------------------------------------------------------------------------------- /examples_static/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "importHelpers": true, 8 | "jsx": "react-jsx", 9 | "esModuleInterop": true, 10 | "sourceMap": true, 11 | "baseUrl": "./", 12 | "strict": true, 13 | "paths": { 14 | "@/*": ["src/*"], 15 | "@@/*": ["src/.umi/*"] 16 | }, 17 | "allowSyntheticDefaultImports": true 18 | }, 19 | "include": [ 20 | "mock/**/*", 21 | "src/**/*", 22 | "config/**/*", 23 | ".umirc.ts", 24 | "typings.d.ts" 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "lib", 29 | "es", 30 | "dist", 31 | "typings", 32 | "**/__test__", 33 | "test", 34 | "docs", 35 | "tests" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /examples_static/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | declare module '*.png'; 4 | declare module '*.svg' { 5 | export function ReactComponent( 6 | props: React.SVGProps, 7 | ): React.ReactElement; 8 | const url: string; 9 | export default url; 10 | } 11 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | export default { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | moduleFileExtensions: [ 6 | "ts", 7 | "tsx", 8 | "js", 9 | "jsx" 10 | ], 11 | "modulePaths": [ 12 | "" 13 | ], 14 | moduleNameMapper: { 15 | '^src/(.*)': '/src/$1', 16 | }, 17 | setupFiles: [ 18 | ], 19 | testMatch: [ 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mind-lake-sdk", 3 | "version": "1.0.13", 4 | "description": "", 5 | "module": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "dev": "father dev", 9 | "prettier": "prettier --write src/**/*.{js,jsx,tsx,ts,json}", 10 | "test-all": "npm run test-create && npm run test-delete && npm run test-drop && npm run test-encrypt && npm run test-insert && npm run test-link && npm run test-update && npm run test-sharing", 11 | "test-create": "jest create", 12 | "test-delete": "jest delete", 13 | "test-drop": "jest drop", 14 | "test-encrypt": "jest encrypt", 15 | "test-insert": "jest insert", 16 | "test-link": "jest link --testNamePattern=alice_init_data && jest link --testNamePattern=test_link_table_to_otherwalletcocoon_bob", 17 | "test-sharing": "jest sharing --testNamePattern=Alice_init_encrypt_data && jest sharing --testNamePattern=no_grant_bob_try_to_decrypt && jest sharing --testNamePattern=grant_column_1 && jest sharing --testNamePattern=confirm_grant && jest sharing --testNamePattern=grant_column_3 && jest sharing --testNamePattern=confirm_grant_3", 18 | "test-update": "jest update", 19 | "build": "cross-env NODE_ENV=production && father build", 20 | "build:deps": "father prebundle", 21 | "prepublishOnly": "father doctor && npm run build" 22 | }, 23 | "keywords": [ 24 | "mind", 25 | "lake", 26 | "mind-lake" 27 | ], 28 | "authors": [], 29 | "license": "MIT", 30 | "files": [ 31 | "dist", 32 | "compiled" 33 | ], 34 | "publishConfig": { 35 | "access": "public" 36 | }, 37 | "repository": "https://github.com/mind-network/mind-lake-sdk-typescript", 38 | "homepage": "https://github.com/mind-network/mind-lake-sdk-typescript", 39 | "dependencies": { 40 | "@babel/runtime": "^7.21.5", 41 | "@clerk/clerk-js": "^4.56.1", 42 | "@ethersproject/providers": "^5.7.2", 43 | "@metamask/eth-sig-util": "^4.0.0", 44 | "@types/jest": "^29.5.1", 45 | "@types/node-rsa": "^1.1.1", 46 | "axios": "^1.3.6", 47 | "dayjs": "^1.11.7", 48 | "decimal.js": "^10.4.3", 49 | "js-base64": "^3.7.5", 50 | "node-forge": "^1.3.1", 51 | "node-rsa": "^1.1.1", 52 | "uuid": "^9.0.0", 53 | "web3": "^1.9.0" 54 | }, 55 | "devDependencies": { 56 | "cross-env": "^7.0.3", 57 | "father": "^4.1.8", 58 | "jest": "^29.5.0", 59 | "log4js": "^6.9.1", 60 | "prettier": "2.8.8", 61 | "ts-jest": "^29.1.0", 62 | "ts-node": "^10.9.1" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Crypto.ts: -------------------------------------------------------------------------------- 1 | import { Service } from './request'; 2 | import { CipherHelper } from './util/cipher'; 3 | import { MindLake } from './MindLake'; 4 | // @ts-ignore 5 | import { v4 as uuidv4 } from 'uuid'; 6 | import { CCEntry, DataType, ResultType, MkManager } from './types'; 7 | import Result from './util/result'; 8 | 9 | export default class Crypto { 10 | private service!: Service; 11 | 12 | private mkManager!: MkManager; 13 | 14 | private sdk!: MindLake; 15 | 16 | constructor(sdk: MindLake) { 17 | this.service = sdk.service; 18 | this.mkManager = sdk.mkManager; 19 | this.sdk = sdk; 20 | } 21 | 22 | /** 23 | * encrypt method 24 | * @param tableName 25 | * @param columnName 26 | * @param data 27 | * @param schema 28 | */ 29 | public async encrypt( 30 | data: any, 31 | tableNameColumnName: string | DataType, 32 | ): Promise { 33 | try { 34 | MindLake.checkLogin(); 35 | this.sdk.checkRegistered(); 36 | const walletAddress = await this.mkManager.getWalletAccount(); 37 | let encType!: number; 38 | //const { ctxId, decryptedDek, algorithm } 39 | let ccEntry!: { ctxId: number; algorithm: number; decryptedDek: Buffer }; 40 | if (typeof tableNameColumnName === 'string') { 41 | const [tableName, columnName] = tableNameColumnName.split('.'); 42 | encType = await this.service.execute({ 43 | bizType: 107, 44 | tableName, 45 | column: columnName, 46 | }); 47 | // @ts-ignore 48 | ccEntry = await this._getOrGenDek(tableName, columnName, walletAddress); 49 | } else { 50 | encType = tableNameColumnName; 51 | const { ctxId, algorithm, encryptedDek } = await this.service.execute< 52 | any, 53 | { ctxId: number; algorithm: number; encryptedDek: string } 54 | >({ bizType: 108 }); 55 | const mekBuffer = await this.mkManager.getMekBytes(); 56 | const [_, decryptedDek] = CipherHelper.decryptDekToBase64( 57 | mekBuffer, 58 | encryptedDek, 59 | ); 60 | ccEntry = { ctxId, algorithm, decryptedDek }; 61 | } 62 | 63 | if (encType > 4) { 64 | encType += 1; 65 | } 66 | const encodeDataBuffer = CipherHelper.encodeDataByType(data, encType); 67 | 68 | const header = this._genCryptoHeader(ccEntry.ctxId, encType); 69 | const checkCode = this._genCheckCode(encodeDataBuffer, 1); 70 | const data_to_enc = Buffer.concat([ 71 | encodeDataBuffer, 72 | Buffer.from(checkCode), 73 | ]); 74 | let encrypted_data; 75 | const iv = CipherHelper.randomBytes(); 76 | if (ccEntry.algorithm === 3) { 77 | encrypted_data = CipherHelper.aesEncrypt( 78 | ccEntry.decryptedDek, 79 | iv, 80 | data_to_enc, 81 | ); 82 | } 83 | if (!encrypted_data) { 84 | throw new Error('aesEncrypt error'); 85 | } 86 | const buf = Buffer.concat([header, iv, encrypted_data]); 87 | const temp = buf.slice(1); 88 | const checkCode2 = this._genCheckCode(temp, 1); 89 | const result = Buffer.concat([checkCode2, temp]); 90 | return Result.success(`\\x${Buffer.from(result).toString('hex')}`); 91 | } catch (e) { 92 | console.error(e); 93 | return Result.fail(e); 94 | } 95 | } 96 | 97 | /** 98 | * 99 | * @param hex 100 | */ 101 | public async decrypt(hex: string): Promise { 102 | try { 103 | MindLake.checkLogin(); 104 | this.sdk.checkRegistered(); 105 | const mek = await this.mkManager.getMekBytes(); 106 | const encryptData = hex.replace('\\x', ''); 107 | const encryptDataBuffer = Buffer.from(encryptData, 'hex'); 108 | const header = this._extractCryptoHeader(encryptDataBuffer); 109 | const cxtId = this._extractCtxId(header); 110 | const { encryptedDek, algorithm } = await this.service.execute< 111 | any, 112 | { encryptedDek: string; algorithm: number } 113 | >({ 114 | bizType: 111, 115 | ctxId: String(cxtId), 116 | }); 117 | const [_, dek] = CipherHelper.decryptDekToBase64(mek, encryptedDek); 118 | const afterEncType = this._extractEncType(header); 119 | const idx = (header[1] & 0x7) + 2; 120 | const iv = encryptDataBuffer.slice(idx, idx + 16); 121 | const cipherBlob = encryptDataBuffer.slice(idx + 16); 122 | // @ts-ignore 123 | const plainBlob = CipherHelper.aesDecrypt(dek, iv, cipherBlob); 124 | const encodeResult = plainBlob.subarray(0, -1); 125 | const checkCode = plainBlob.slice(-1); 126 | const checkCode2 = this._genCheckCode(encodeResult, 1); 127 | if (checkCode[0] != checkCode2[0]) { 128 | throw new Error('Check code is not correct'); 129 | } 130 | const decryptData = CipherHelper.decodeDataByType( 131 | Uint8Array.from(encodeResult), 132 | afterEncType, 133 | ); 134 | return Result.success(decryptData); 135 | } catch (e) { 136 | console.error(e); 137 | return Result.fail(e); 138 | } 139 | } 140 | 141 | /** 142 | * get dek 143 | * @param schema 144 | * @param table 145 | * @param column 146 | * @param walletAddress 147 | * @private 148 | */ 149 | private async _getOrGenDek( 150 | table: string, 151 | column: string, 152 | walletAddress: string, 153 | ) { 154 | const mekBuffer = await this.mkManager.getMekBytes(); 155 | let cc = await this._queryCCEntryByName(table, column, walletAddress).catch( 156 | (e) => {}, 157 | ); 158 | if (!cc) { 159 | cc = await this._genCCEntryFromLocal(table, column); 160 | } 161 | const { ctxId, dekId, encryptedDek, algorithm } = cc; 162 | const [, decryptedDek] = CipherHelper.decryptDekToBase64( 163 | mekBuffer, 164 | encryptedDek, 165 | ); 166 | return { ctxId: ctxId, decryptedDek, algorithm }; 167 | } 168 | 169 | private async _queryCCEntryByName( 170 | table: string, 171 | column: string, 172 | walletAddress: string, 173 | ) { 174 | return await this.service.execute({ 175 | bizType: 108, 176 | table: table, 177 | column: column, 178 | walletAddress, 179 | }); 180 | } 181 | 182 | private async _genCCEntryFromLocal(table: string, column: string) { 183 | const dekId = await this.service.execute({ 184 | bizType: 109, 185 | mekId: this.sdk.mekId, 186 | }); 187 | const dek: Buffer = CipherHelper.randomBytes(); 188 | const mek = await this.mkManager.getMekBytes(); 189 | return this._genSQLInsertDek( 190 | this.sdk.mekId, 191 | dekId, 192 | mek, 193 | dek, 194 | 3, 195 | table, 196 | column, 197 | ); 198 | } 199 | 200 | private async _genSQLInsertDek( 201 | mekId: string, 202 | dekId: number, 203 | mek: Buffer, 204 | dek: Buffer, 205 | alg = 3, 206 | table: string, 207 | column: string, 208 | ) { 209 | const dekCipherStr = CipherHelper.encryptDekToBase64(mek, dekId, dek); 210 | const grpIdStr = uuidv4(); 211 | const uuidStringWithoutHyphen = grpIdStr.replace(/-/g, ''); 212 | const _gripId = Buffer.from( 213 | uuidStringWithoutHyphen 214 | .match(/.{1,2}/g) 215 | ?.map((byte: string) => parseInt(byte, 16)) || [], 216 | ); 217 | const gAuthStr = CipherHelper.digest_gAuth(mek, _gripId, dekId); 218 | return this.service.execute({ 219 | bizType: 110, 220 | table: table, 221 | column: column, 222 | schema: 'public', 223 | mekId, 224 | dekId, 225 | dekCipherStr, 226 | grpIdStr, 227 | groupAuthStr: gAuthStr, 228 | }); 229 | } 230 | 231 | private _genCryptoHeader(ctxId: number, encType: number): Buffer { 232 | let head = Buffer.from('0000', 'hex'); 233 | let tmp_value = head[1]; 234 | tmp_value = tmp_value & 0xffffff07; 235 | tmp_value = tmp_value | (encType << 3); 236 | head[1] = tmp_value; 237 | let tmp = ctxId; 238 | while (tmp != 0) { 239 | head = Buffer.concat([head, Buffer.alloc(1).fill(tmp & 0xff)]); 240 | tmp >>= 8; 241 | } 242 | const ctxLen = head.length - 2; 243 | let tmp_val = head[1]; 244 | tmp_val = (tmp_val & 0xfffffff8) | (ctxLen & 0x7); 245 | head[1] = tmp_val; 246 | return head; 247 | } 248 | 249 | private _extractCryptoHeader(data: Uint8Array): Buffer { 250 | let header = []; 251 | let index = 0; 252 | for (let i = 0; i < 1; i++) { 253 | header.push(data[index]); 254 | // header[index] = data[index]; 255 | index++; 256 | } 257 | if (index !== 1) { 258 | throw new Error('Invalid header index'); 259 | } 260 | header.push(data[index]); 261 | //header[index] = data[index]; 262 | index++; 263 | const rng = header[1] & 0x7; 264 | for (let i = 0; i < rng; i++) { 265 | header.push(data[index]); 266 | // header[index] = data[index]; 267 | index++; 268 | } 269 | return Buffer.from(header); 270 | } 271 | 272 | private _extractEncType(header: Buffer): number { 273 | const tmp_value = header[1]; 274 | const type_value = (tmp_value & 0xf8) >> 3; 275 | return type_value; 276 | } 277 | 278 | private _extractCtxId(header: Buffer): number { 279 | const ctxIdLen = header[1] & 0x7; 280 | if (header.length !== ctxIdLen + 2) { 281 | throw new Error('Invalid header length'); 282 | } 283 | let ctxId = 0; 284 | for (let i = 0; i < ctxIdLen; i++) { 285 | const index = header.length - 1 - i; 286 | ctxId = (ctxId << 8) | (header[index] & 0xff); 287 | } 288 | return ctxId; 289 | } 290 | 291 | private _genCheckCode(encodeData: Uint8Array, resultSize: number) { 292 | let tmpCode = new Uint8Array(resultSize); 293 | for (let i = 0; i < encodeData.length; i++) { 294 | let n = i % resultSize; 295 | tmpCode[n] ^= encodeData[i]; 296 | } 297 | return Buffer.from(tmpCode); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/DataLake.ts: -------------------------------------------------------------------------------- 1 | import { Service } from './request'; 2 | import { MindLake } from './MindLake'; 3 | import Result from './util/result'; 4 | import { ColumnType, ResultType } from './types'; 5 | 6 | export default class DataLake { 7 | private service!: Service; 8 | 9 | constructor(sdk: MindLake) { 10 | this.service = sdk.service; 11 | } 12 | 13 | public async query(executeSql: string) { 14 | try { 15 | const res = await this.service.execute({ bizType: 114, executeSql }); 16 | return Result.success(res); 17 | } catch (e) { 18 | console.error(e); 19 | return Result.fail(e); 20 | } 21 | } 22 | 23 | public async createCocoon(cocoonName: string): Promise { 24 | try { 25 | await this.service.execute({ bizType: 121, cocoonName }); 26 | return Result.success(true); 27 | } catch (e) { 28 | console.error(e); 29 | return Result.fail(e); 30 | } 31 | } 32 | 33 | public async dropCocoon(cocoonName: string): Promise { 34 | try { 35 | await this.service.execute({ bizType: 129, cocoonName }); 36 | return Result.success(true); 37 | } catch (e) { 38 | console.error(e); 39 | return Result.fail(e); 40 | } 41 | } 42 | 43 | public async createTable( 44 | tableName: string, 45 | columns: Array, 46 | pkColumns?: Array, 47 | ): Promise { 48 | try { 49 | await this.service.execute({ 50 | bizType: 123, 51 | tableName, 52 | columns, 53 | pkColumns, 54 | }); 55 | return Result.success(true); 56 | } catch (e) { 57 | console.error(e); 58 | return Result.fail(e); 59 | } 60 | } 61 | 62 | public async dropTable(tableName: string): Promise { 63 | try { 64 | await this.service.execute({ bizType: 128, tableName }); 65 | return Result.success(true); 66 | } catch (e) { 67 | console.error(e); 68 | return Result.fail(e); 69 | } 70 | } 71 | 72 | public async listCocoon(): Promise { 73 | try { 74 | const data = await this.service.execute({ bizType: 122 }); 75 | return Result.success(data); 76 | } catch (e) { 77 | console.error(e); 78 | return Result.fail(e); 79 | } 80 | } 81 | 82 | public async listTablesByCocoon(cocoonName: string): Promise { 83 | try { 84 | const data = await this.service.execute({ 85 | bizType: 125, 86 | cocoonName, 87 | }); 88 | return Result.success(data); 89 | } catch (e) { 90 | console.error(e); 91 | return Result.fail(e); 92 | } 93 | } 94 | 95 | public async linkTableToCocoon( 96 | tableName: string, 97 | cocoonName: string, 98 | ): Promise { 99 | try { 100 | const data = await this.service.execute({ 101 | bizType: 124, 102 | tableName, 103 | cocoonName, 104 | }); 105 | return Result.success(data); 106 | } catch (e) { 107 | console.error(e); 108 | return Result.fail(e); 109 | } 110 | } 111 | 112 | public async listTableByWalletAddress(): Promise { 113 | try { 114 | const data = await this.service.execute({ 115 | bizType: 301, 116 | }); 117 | return Result.success(data); 118 | } catch (e) { 119 | console.error(e); 120 | return Result.fail(e); 121 | } 122 | } 123 | 124 | public async queryForDataAndMeta(executeSql: string): Promise { 125 | try { 126 | const data = await this.service.execute({ 127 | bizType: 113, 128 | executeSql, 129 | }); 130 | return Result.success(data); 131 | } catch (e) { 132 | console.error(e); 133 | return Result.fail(e); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/MindLake.ts: -------------------------------------------------------------------------------- 1 | import { Service } from './request'; 2 | import { APP_KEY, CHAIN_KEY, TOKEN_KEY } from './util/constant'; 3 | import { CipherHelper } from './util/cipher'; 4 | import { Web3Interact } from './util/web3'; 5 | import { Util } from './util/util'; 6 | import Crypto from './Crypto'; 7 | import DataLake from './DataLake'; 8 | import { Bcl } from './util/bcl'; 9 | import { 10 | ServerInfo, 11 | RequestMekProvisionBody, 12 | RequestRegisterCertificateBody, 13 | ResultType, 14 | DataType, 15 | MkManager, 16 | ChainInfo, 17 | ClerkConfig, 18 | } from './types'; 19 | import Permission from './Permission'; 20 | import Result from './util/result'; 21 | import pkConfig from '../package.json'; 22 | import Clerk from './util/clerk'; 23 | import { SignInProps, UserButtonProps } from '@clerk/types'; 24 | /** 25 | * 26 | */ 27 | export class MindLake { 28 | public service!: Service; 29 | 30 | /** 31 | * mindLake version 32 | */ 33 | public static readonly version = pkConfig.version; 34 | 35 | /** 36 | * isConnected mindDB 37 | */ 38 | public static isConnected = false; 39 | 40 | /** 41 | * mind db is init 42 | */ 43 | public isRegistered: boolean = false; 44 | 45 | /** 46 | * server publicKey 47 | * */ 48 | public publicKey!: string; 49 | 50 | /** 51 | *registerPukId 52 | */ 53 | public registerPukId!: string; 54 | 55 | /** 56 | * mek id 57 | */ 58 | public mekId!: string; 59 | 60 | /** 61 | * crypto instance 62 | */ 63 | public crypto!: Crypto; 64 | 65 | public mkManager!: MkManager; 66 | 67 | public dataLake!: DataLake; 68 | 69 | public permission!: Permission; 70 | 71 | private chainList: Array | undefined; 72 | 73 | private static instance: MindLake; 74 | 75 | public static log = false; 76 | 77 | public static readonly DataType = DataType; 78 | 79 | public static async getInstance( 80 | appKey: string, 81 | nodeUrl?: string, 82 | ): Promise { 83 | if (this.instance === undefined) { 84 | this.instance = new MindLake(appKey, nodeUrl); 85 | await this.instance._getServerInfo(); 86 | await this.instance.supportChaninList(); 87 | const chainStr = localStorage.getItem(CHAIN_KEY); 88 | let chain; 89 | try { 90 | chain = chainStr && JSON.parse(chainStr); 91 | } catch (error) { 92 | console.error(error); 93 | } 94 | if (chain?.clerk) { 95 | this.instance.mkManager = new Clerk(chain.clerk); 96 | let sessionToken; 97 | if (this.instance.mkManager instanceof Clerk) { 98 | sessionToken = await this.instance.mkManager.init(); 99 | } 100 | if (sessionToken) { 101 | const res = await this.instance.service.execute< 102 | any, 103 | { token: string } 104 | >({ 105 | bizType: 206, 106 | clerkToken: sessionToken, 107 | }); 108 | if (res && res.token) { 109 | localStorage.setItem(TOKEN_KEY, res.token); 110 | await this.instance._init(); 111 | MindLake.isConnected = true; 112 | } 113 | } else { 114 | MindLake.isConnected = false; 115 | } 116 | } else { 117 | this.instance.mkManager = new Web3Interact(); 118 | await this.instance.mkManager.checkConnection(); 119 | if (chain && this.instance.mkManager instanceof Web3Interact) { 120 | chain && this.instance.mkManager.setChain(chain); 121 | } 122 | } 123 | this.instance.crypto = new Crypto(this.instance); 124 | this.instance.dataLake = new DataLake(this.instance); 125 | this.instance.permission = new Permission(this.instance); 126 | } 127 | return this.instance; 128 | } 129 | 130 | private constructor(appKey: string, nodeUrl?: string) { 131 | localStorage.setItem(APP_KEY, appKey); 132 | this.service = new Service(nodeUrl); 133 | } 134 | 135 | /** 136 | *connect db 137 | */ 138 | public async connect(chainId: string | number): Promise { 139 | try { 140 | if (!(this.mkManager instanceof Web3Interact)) { 141 | this.mkManager = new Web3Interact(); 142 | this.crypto = new Crypto(this); 143 | this.permission = new Permission(this); 144 | } 145 | if (typeof chainId !== 'number' && typeof chainId !== 'string') { 146 | return Result.fail('chainId must be a number or string'); 147 | } 148 | const chain = this.chainList?.find((c) => c.chainId == chainId); 149 | if (!chain) { 150 | return Result.fail('chain not supported'); 151 | } 152 | if (this.mkManager instanceof Web3Interact) { 153 | this.mkManager.setChain(chain); 154 | await this.mkManager._changeChainToSupportChain(); 155 | } 156 | const walletAddress = await this.mkManager.getWalletAccount(); 157 | localStorage.setItem(CHAIN_KEY, JSON.stringify(chain)); 158 | const nonce = await this.service.execute({ 159 | bizType: 203, 160 | walletAddress, 161 | }); 162 | const signature = await this.mkManager.personalSignature(nonce); 163 | const res = await this.service.execute({ 164 | bizType: 201, 165 | walletAddress, 166 | signature, 167 | }); 168 | if (res && res.token) { 169 | localStorage.setItem(TOKEN_KEY, res.token); 170 | await this._init(); 171 | MindLake.isConnected = true; 172 | return Result.success(true); 173 | } 174 | return Result.fail(false); 175 | } catch (e) { 176 | console.error(e); 177 | localStorage.removeItem(TOKEN_KEY); 178 | return Result.fail(e); 179 | } 180 | } 181 | 182 | public async clerkConnect( 183 | config?: ClerkConfig, 184 | signProps?: SignInProps, 185 | ): Promise { 186 | try { 187 | this.mkManager = new Clerk(config); 188 | localStorage.setItem(CHAIN_KEY, JSON.stringify({ clerk: { ...config } })); 189 | if (this.mkManager instanceof Clerk) { 190 | await this.mkManager.init(); 191 | await this.mkManager.signIn(signProps); 192 | } 193 | } catch (e) { 194 | return Result.fail(e); 195 | } 196 | } 197 | 198 | public renderClerkUserBotton(dom: HTMLDivElement, props?: UserButtonProps) { 199 | if (this.mkManager instanceof Clerk) { 200 | this.mkManager.renderUserButton(dom, props); 201 | } 202 | } 203 | 204 | public async supportChaninList(): Promise> { 205 | if (!this.chainList) { 206 | const result = await this.service.execute>({ 207 | bizType: 205, 208 | }); 209 | this.chainList = result; 210 | } 211 | return this.chainList; 212 | } 213 | 214 | /** 215 | * disconnect db 216 | */ 217 | public async disConnect(): Promise { 218 | try { 219 | await this.service.execute({ bizType: 202 }); 220 | return Result.success(true); 221 | } catch (e) { 222 | console.error(e); 223 | return Result.fail(e); 224 | } 225 | } 226 | 227 | /** 228 | * getEnclaveInfo 229 | */ 230 | private async _getServerInfo() { 231 | const info = await this.service 232 | .execute({ 233 | bizType: 120, 234 | }) 235 | .catch((e) => console.error(e)); 236 | if (info) { 237 | MindLake.isConnected = true; 238 | this.publicKey = info.publicKey; 239 | this.isRegistered = 240 | info.isRegistered && info.isMekProvision && info.isSelfBcl; 241 | this.mekId = info.mekId; 242 | if (this.isRegistered && this.mekId) { 243 | await this._getAccount(); 244 | } 245 | } 246 | } 247 | 248 | private async _init() { 249 | await this._getServerInfo(); 250 | const provisionRes = await this._mekProvision(); 251 | if (!provisionRes) { 252 | throw new Error('Provision failed'); 253 | } 254 | if (!this.isRegistered) { 255 | const { privateKeyPem, publicKeyPem } = await this.mkManager.getPkPem(); 256 | const registerPukId = await this._registerCertificate( 257 | publicKeyPem, 258 | privateKeyPem, 259 | ); 260 | if (!registerPukId) { 261 | throw new Error('Register key pair failed'); 262 | } 263 | this.registerPukId = registerPukId; 264 | const sn = await this._issueBclForSelf(privateKeyPem); 265 | if (!sn) { 266 | throw new Error('Grant for self failed'); 267 | } 268 | this.isRegistered = true; 269 | } 270 | } 271 | 272 | /** 273 | * mek provision 274 | * @private 275 | */ 276 | private async _mekProvision() { 277 | if (!this.publicKey) { 278 | throw new Error('Provision failed: The public key is not empty'); 279 | } 280 | const pubKey = this.publicKey.replace('\\n', '\n'); 281 | const ephemeralKey = CipherHelper.randomBytes(); 282 | const sealedEphemeralKey: Uint8Array = CipherHelper.rsaEncrypt( 283 | pubKey, 284 | ephemeralKey, 285 | ); 286 | const sealedEphemeralKeyLenBytes = new Buffer([ 287 | sealedEphemeralKey.length & 0xff, 288 | (sealedEphemeralKey.length >> 8) & 0xff, 289 | ]); 290 | const iv = CipherHelper.randomBytes(); 291 | const envelope_json = await this._genEnvelope(); 292 | const envelope_enc = CipherHelper.aesEncrypt( 293 | ephemeralKey, 294 | iv, 295 | envelope_json, 296 | ); 297 | const big_envelope = Buffer.concat([ 298 | sealedEphemeralKeyLenBytes, 299 | sealedEphemeralKey, 300 | iv, 301 | envelope_enc, 302 | ]); 303 | const big_envelopeBase64 = Buffer.from(big_envelope).toString('base64'); 304 | return await this.service.execute({ 305 | bizType: 102, 306 | databasePublicKey: this.publicKey, 307 | envelope: big_envelopeBase64, 308 | }); 309 | } 310 | 311 | /** 312 | * registerCertificate pukId 313 | * @private 314 | */ 315 | private async _registerCertificate( 316 | publicKeyPem: string, 317 | privateKeyPem: string, 318 | ): Promise { 319 | const mekId = this.mekId; 320 | const mek = await this.mkManager.getMekBytes(); 321 | const pukId = CipherHelper.sha256Hash(publicKeyPem); 322 | const toBeSignedBytes = Buffer.concat([ 323 | Buffer.from(Util.structPackq(mekId)), 324 | Buffer.from(pukId, 'latin1'), //important 325 | ]); 326 | const rsaSign = Buffer.concat([ 327 | Buffer.from('01', 'hex'), 328 | CipherHelper.rsaSign(privateKeyPem, toBeSignedBytes), 329 | ]); 330 | const private_sig = rsaSign.toString('base64'); 331 | const mekSign = Buffer.concat([ 332 | Buffer.from('00', 'hex'), 333 | CipherHelper.hmacHash(mek, toBeSignedBytes), 334 | ]); 335 | const mek_sign = mekSign.toString('base64'); 336 | const res = await this.service.execute< 337 | RequestRegisterCertificateBody, 338 | boolean 339 | >({ 340 | bizType: 104, 341 | mekId, 342 | pukId, 343 | publicKey: publicKeyPem, 344 | privateSig: private_sig, 345 | mekSig: mek_sign, 346 | }); 347 | if (res) { 348 | return pukId; 349 | } 350 | } 351 | 352 | /** 353 | * issueBclForSelf 354 | * @param privateKeyPem 355 | * @private 356 | */ 357 | private async _issueBclForSelf(privateKeyPem: string) { 358 | // 359 | const bcl = new Bcl(this.service); 360 | await bcl.createBclBody(this.registerPukId, this.registerPukId, ''); 361 | const defaultGroup = await this.service.execute({ 362 | bizType: 108, 363 | }); 364 | if (!defaultGroup || !defaultGroup.groupId) { 365 | throw new Error('Get default group error'); 366 | } 367 | const selfDekGroup = { 368 | groupid: defaultGroup.groupId, 369 | min: 1, 370 | max: 1000, 371 | }; 372 | bcl.addDekGroup(selfDekGroup, selfDekGroup); 373 | return await bcl.issueBcl(privateKeyPem, 106); 374 | } 375 | 376 | private async _genEnvelope() { 377 | const mek = await this.mkManager.getMekBytes(); 378 | let envelope: any = {}; 379 | const base64Mek = Buffer.from(mek).toString('base64'); 380 | envelope['mek'] = base64Mek; 381 | envelope['expire'] = 0; 382 | let envelope_json = JSON.stringify(envelope).replace(' ', ''); 383 | return envelope_json; 384 | } 385 | 386 | private async _getAccount() { 387 | const registerPukId = await this.service.execute({ 388 | bizType: 103, 389 | mekId: this.mekId, 390 | }); 391 | this.registerPukId = registerPukId; 392 | } 393 | 394 | public static checkLogin() { 395 | if (!MindLake.isConnected) { 396 | throw new Error('Please connect first'); 397 | } 398 | } 399 | 400 | public checkRegistered() { 401 | if (!this.isRegistered) { 402 | throw new Error('Register error, please reLogin to register'); 403 | } 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/Permission.ts: -------------------------------------------------------------------------------- 1 | import { MindLake } from './MindLake'; 2 | import { Service } from './request'; 3 | import { Bcl } from './util/bcl'; 4 | import { ResultType } from './types'; 5 | import Result from './util/result'; 6 | import { MkManager } from './types'; 7 | 8 | export default class Permission { 9 | private readonly service!: Service; 10 | 11 | private readonly mkManager!: MkManager; 12 | 13 | private readonly sdk!: MindLake; 14 | 15 | constructor(sdk: MindLake) { 16 | this.service = sdk.service; 17 | this.mkManager = sdk.mkManager; 18 | this.sdk = sdk; 19 | } 20 | 21 | /** 22 | * 23 | * @param targetWalletAddress 24 | */ 25 | public async grant( 26 | targetChain: string, 27 | targetWalletAddress: string, 28 | columns: Array, 29 | ): Promise { 30 | try { 31 | MindLake.checkLogin(); 32 | this.sdk.checkRegistered(); 33 | if (!columns.length) { 34 | throw new Error('no columns to need grant'); 35 | } 36 | 37 | const bcl = new Bcl(this.service); 38 | //get subjectPublicId 39 | const res = await this.service.execute({ 40 | bizType: 119, 41 | targetWalletAddress: targetWalletAddress, 42 | targetChain, 43 | }); 44 | if (!res || !res.publicKeyId) { 45 | throw new Error( 46 | "Peer user (Subject)'s certificate hasn't been registered.", 47 | ); 48 | } 49 | await bcl.createBclBody(this.sdk.registerPukId, res.publicKeyId, ''); 50 | 51 | const eachFunc = async (data: Array) => { 52 | for (const tableColumn of data) { 53 | const [table, column] = tableColumn.split('.'); 54 | await this._addColumnIntoBcl(bcl, table, column); 55 | } 56 | }; 57 | 58 | await eachFunc(columns); 59 | const { privateKeyPem } = await this.sdk.mkManager.getPkPem(); 60 | const sn = await bcl.issueBcl(privateKeyPem, 115); 61 | return Result.success(sn); 62 | } catch (e) { 63 | console.error(e); 64 | return Result.fail(e); 65 | } 66 | } 67 | 68 | /** 69 | * 70 | * @param policyId 71 | */ 72 | public async confirm(policyId: string): Promise { 73 | try { 74 | MindLake.checkLogin(); 75 | this.sdk.checkRegistered(); 76 | if (!policyId) { 77 | throw new Error('The policy id is empty'); 78 | } 79 | const bcl = new Bcl(this.service); 80 | await bcl.loadBclBodyBySN(policyId); 81 | if (!bcl.bclBody || !bcl.bclBody.serial_num) { 82 | throw new Error('The policyID is not correct'); 83 | } 84 | const { privateKeyPem } = await this.mkManager.getPkPem(); 85 | const sn = await bcl.issueBcl(privateKeyPem, 117); 86 | return Result.success(sn); 87 | } catch (e) { 88 | console.error(e); 89 | return Result.fail(e); 90 | } 91 | } 92 | 93 | /** 94 | * revoke grant 95 | * @param targetWalletAddress 96 | * @param columns, if the columns is empty ,will revoke all 97 | */ 98 | public async revoke( 99 | targetWalletAddress: string, 100 | targetChain: string, 101 | columns?: Array<{ table: string; column: string }>, 102 | ): Promise { 103 | try { 104 | MindLake.checkLogin(); 105 | this.sdk.checkRegistered(); 106 | const bcl = new Bcl(this.service); 107 | //get subjectPublicId 108 | const res = await this.service.execute({ 109 | bizType: 119, 110 | targetWalletAddress: targetWalletAddress, 111 | targetChain, 112 | }); 113 | if (!res || !res.publicKeyId) { 114 | throw new Error( 115 | "Peer user (Subject)'s certificate hasn't been registered.", 116 | ); 117 | } 118 | await bcl.loadBclBodyByPukId(this.sdk.registerPukId, res.publicKeyId); 119 | if (!bcl.bclBody || !bcl.bclBody.serial_num) { 120 | throw new Error('No grant required to revoke!'); 121 | } 122 | if (!columns || !columns.length) { 123 | bcl.removeDekGroupAll(); 124 | } else { 125 | const groupIdArray = new Array(); 126 | const eachFunc = async ( 127 | data: Array<{ table: string; column: string }>, 128 | ) => { 129 | for (const column of data) { 130 | const ccSelf = await this.service.execute( 131 | { 132 | bizType: 108, 133 | schema: 'public', 134 | table: column.table, 135 | column: column.column, 136 | }, 137 | ); 138 | if (!ccSelf.groupId) { 139 | throw new Error('groupId is not exist'); 140 | } 141 | groupIdArray.push(ccSelf.groupId); 142 | } 143 | }; 144 | await eachFunc(columns); 145 | bcl.removeDekGroup(groupIdArray); 146 | } 147 | const { privateKeyPem } = await this.mkManager.getPkPem(); 148 | const sn = await bcl.issueBcl(privateKeyPem, 115); 149 | return Result.success(sn); 150 | } catch (e) { 151 | console.error(e); 152 | return Result.fail(e); 153 | } 154 | } 155 | 156 | public async listGrantee(): Promise { 157 | try { 158 | const data = await this.service.execute({ bizType: 126 }); 159 | return Result.success(data); 160 | } catch (e) { 161 | console.error(e); 162 | return Result.fail(e); 163 | } 164 | } 165 | 166 | public async listOwner(): Promise { 167 | try { 168 | const data = await this.service.execute({ bizType: 130 }); 169 | return Result.success(data); 170 | } catch (e) { 171 | console.error(e); 172 | return Result.fail(e); 173 | } 174 | } 175 | 176 | public async listOwnerColumn( 177 | targetWalletAddress: string, 178 | targetChain: string, 179 | ): Promise { 180 | try { 181 | const data = await this.service.execute({ 182 | bizType: 131, 183 | targetWalletAddress, 184 | targetChain, 185 | }); 186 | return Result.success(data); 187 | } catch (e) { 188 | console.error(e); 189 | return Result.fail(e); 190 | } 191 | } 192 | 193 | public async listGrantedColumn( 194 | targetWalletAddress: string, 195 | targetChain: string, 196 | ): Promise { 197 | try { 198 | const data = await this.service.execute({ 199 | bizType: 127, 200 | targetWalletAddress, 201 | targetChain, 202 | }); 203 | return Result.success(data); 204 | } catch (e) { 205 | console.error(e); 206 | return Result.fail(e); 207 | } 208 | } 209 | 210 | public async _addColumnIntoBcl( 211 | bcl: Bcl, 212 | table: string, 213 | column: string, 214 | minId = 1, 215 | maxId = 1000, 216 | ) { 217 | const ccSelf = await this.service.execute({ 218 | bizType: 108, 219 | table: table, 220 | column: column, 221 | }); 222 | if (!ccSelf.groupId) { 223 | throw new Error('groupId is not exist'); 224 | } 225 | const issuerDekGroup = { groupid: ccSelf.groupId, min: minId, max: maxId }; 226 | bcl.addDekGroup(issuerDekGroup); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { MindLake } from './MindLake'; 2 | import Crypto from './Crypto'; 3 | import DataLake from './DataLake'; 4 | import Permission from './Permission'; 5 | import { DataType, ColumnType } from './types'; 6 | 7 | export { MindLake, Crypto, DataLake, Permission, DataType, ColumnType }; 8 | -------------------------------------------------------------------------------- /src/request/index.ts: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | 3 | export class Service { 4 | 5 | nodeUrl = "https://sdk.mindnetwork.xyz"; 6 | 7 | constructor(nodeUrl?: string) { 8 | if(nodeUrl) { 9 | this.nodeUrl = nodeUrl 10 | } 11 | } 12 | 13 | async execute(data: R): Promise { 14 | return await request.post(this.nodeUrl + '/node', data); 15 | } 16 | 17 | } 18 | 19 | // Service.execute({databasePublicKey: '', envelope: ''}); 20 | -------------------------------------------------------------------------------- /src/request/request.ts: -------------------------------------------------------------------------------- 1 | import axios, { 2 | AxiosInstance, 3 | AxiosError, 4 | AxiosRequestConfig, 5 | AxiosResponse, 6 | } from 'axios'; 7 | import { APP_KEY, CHAIN_KEY, TOKEN_KEY, WALLET_key } from '../util/constant'; 8 | import { MindLake } from '../MindLake'; 9 | 10 | const URL: string = `https://sdk.mindnetwork.xyz/node`; 11 | enum RequestEnums { 12 | TIMEOUT = 20000, 13 | OVERDUE = 401, 14 | SUCCESS = 0, 15 | } 16 | 17 | const NOT_LOGIN_CODE = [401, 402, 403, 40003]; 18 | 19 | const config = { 20 | // baseURL: URL, 21 | timeout: RequestEnums.TIMEOUT, 22 | }; 23 | 24 | class RequestHttp { 25 | service: AxiosInstance; 26 | public constructor(config: AxiosRequestConfig) { 27 | this.service = axios.create(config); 28 | 29 | this.service.interceptors.request.use( 30 | // @ts-ignore 31 | (config) => { 32 | // session token 33 | const token = localStorage.getItem(TOKEN_KEY) || ''; 34 | // your wallet address 35 | const wa = localStorage.getItem(WALLET_key); 36 | //your dapp key 37 | const app = localStorage.getItem(APP_KEY); 38 | const chainStr = localStorage.getItem(CHAIN_KEY); 39 | let chain; 40 | try { 41 | chain = chainStr && JSON.parse(chainStr); 42 | } catch (error) { 43 | console.log(error); 44 | } 45 | const _config = { 46 | ...config, 47 | headers: { 48 | token: token, 49 | ver: `v${MindLake.version}`, 50 | wa, 51 | app, 52 | chain: chain?.clerk ? 0 : chain?.chainId, 53 | }, 54 | }; 55 | if (MindLake.log) { 56 | console.log('request data >>>', config.data); 57 | } 58 | return _config; 59 | }, 60 | (error: AxiosError) => { 61 | Promise.reject(error); 62 | }, 63 | ); 64 | 65 | this.service.interceptors.response.use( 66 | (response: AxiosResponse) => { 67 | const { data, config } = response; 68 | if (MindLake.log) { 69 | console.log('response >>> ', JSON.stringify(data)); 70 | } 71 | if (NOT_LOGIN_CODE.includes(data.code)) { 72 | localStorage.removeItem(TOKEN_KEY); 73 | MindLake.isConnected = false; 74 | return Promise.reject({ code: data.code, message: data.message }); 75 | } 76 | if (data.code && data.code !== RequestEnums.SUCCESS) { 77 | return Promise.reject({ code: data.code, message: data.message }); 78 | } 79 | return data.data; 80 | }, 81 | (error: AxiosError) => { 82 | console.error(error); 83 | }, 84 | ); 85 | } 86 | 87 | get(url: string, params?: object): Promise { 88 | return this.service.get(url, { params }); 89 | } 90 | post(url: string, data?: any): Promise { 91 | return this.service.post(url, data); 92 | } 93 | } 94 | 95 | export default new RequestHttp(config); 96 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { ClerkOptions, UserButtonProps } from '@clerk/types'; 2 | export type ResultType = { 3 | code: number; 4 | message?: string; 5 | result?: any; 6 | }; 7 | 8 | /** 9 | * This enum can be used in conjunction with the Column class, 10 | * which represents a single column in a database table, to specify the data type of the column. 11 | * By utilizing the DataType enum, 12 | * you can ensure that your data is stored in the appropriate format and can be processed correctly by the system. 13 | */ 14 | export enum DataType { 15 | int4 = 1, 16 | int8 = 2, 17 | float4 = 3, 18 | float8 = 4, 19 | decimal = 5, 20 | text = 6, 21 | timestamp = 7, 22 | } 23 | 24 | export type ColumnType = { 25 | columnName: string; 26 | type: DataType; 27 | encrypt: boolean; 28 | }; 29 | 30 | export type ServerInfo = { 31 | publicKey: string; 32 | mekId: string; 33 | isRegistered: boolean; 34 | isMekProvision: boolean; 35 | isSelfBcl: boolean; 36 | }; 37 | 38 | export type ChainInfo = { 39 | id: number; 40 | chainId: string; 41 | chainName: string; 42 | hexadecimalChainId: string; 43 | currency: string; 44 | rpcNodeUrl: string; 45 | rpcOtherNodeUrl: string; 46 | abi: string; 47 | smartAddress: string; 48 | createTime: string; 49 | updateTime: string; 50 | status: number; 51 | feature: string; 52 | }; 53 | 54 | export type RequestMekProvisionBody = { 55 | bizType: number; 56 | databasePublicKey: string; 57 | envelope: string; 58 | }; 59 | 60 | export type RequestRegisterCertificateBody = { 61 | bizType: number; 62 | mekId: string; 63 | pukId: string; 64 | publicKey: string; 65 | privateSig: string; 66 | mekSig: string; 67 | }; 68 | 69 | export type CCEntry = { 70 | ctxId: number; 71 | dekId: string; 72 | encryptedDek: string; 73 | algorithm: number; 74 | groupId: string; 75 | }; 76 | 77 | export interface MkManager { 78 | getWalletAccount(): Promise; 79 | checkConnection(): Promise; 80 | getMekBytes(): Promise; 81 | getPkPem(): Promise<{ 82 | privateKeyPem: string; 83 | publicKeyPem: string; 84 | }>; 85 | personalSignature(signData: string): Promise; 86 | encrypt(str: string): Promise; 87 | decrypt(cipher: Buffer): Promise; 88 | } 89 | 90 | /** 91 | * target userButton dom node render to 92 | */ 93 | export type ClerkConfig = { 94 | target?: string | HTMLDivElement; 95 | options?: ClerkOptions; 96 | userButtonProps?: UserButtonProps; 97 | }; 98 | 99 | export type MindLakeConfig = { 100 | clerk?: ClerkConfig; 101 | }; 102 | 103 | export type ConnectChain = ChainInfo | { clerk: ClerkConfig }; 104 | -------------------------------------------------------------------------------- /src/util/abi.ts: -------------------------------------------------------------------------------- 1 | const Abi = [ 2 | { 3 | anonymous: false, 4 | inputs: [ 5 | { 6 | indexed: true, 7 | internalType: 'address', 8 | name: 'wallet', 9 | type: 'address', 10 | }, 11 | { 12 | indexed: false, 13 | internalType: 'bytes', 14 | name: 'MK', 15 | type: 'bytes', 16 | }, 17 | { 18 | indexed: false, 19 | internalType: 'bytes', 20 | name: 'SK', 21 | type: 'bytes', 22 | }, 23 | ], 24 | name: 'KeysUpdated', 25 | type: 'event', 26 | }, 27 | { 28 | inputs: [ 29 | { 30 | internalType: 'bytes', 31 | name: '_mk', 32 | type: 'bytes', 33 | }, 34 | { 35 | internalType: 'bytes', 36 | name: '_sk', 37 | type: 'bytes', 38 | }, 39 | ], 40 | name: 'setKeys', 41 | outputs: [], 42 | stateMutability: 'nonpayable', 43 | type: 'function', 44 | }, 45 | { 46 | inputs: [ 47 | { 48 | internalType: 'address', 49 | name: '_wallet', 50 | type: 'address', 51 | }, 52 | ], 53 | name: 'getKeys', 54 | outputs: [ 55 | { 56 | internalType: 'bytes', 57 | name: 'MK', 58 | type: 'bytes', 59 | }, 60 | { 61 | internalType: 'bytes', 62 | name: 'SK', 63 | type: 'bytes', 64 | }, 65 | ], 66 | stateMutability: 'view', 67 | type: 'function', 68 | }, 69 | ]; 70 | 71 | export default Abi; 72 | -------------------------------------------------------------------------------- /src/util/bcl.ts: -------------------------------------------------------------------------------- 1 | import { Service } from '../request'; 2 | // @ts-ignore 3 | import dayjs from 'dayjs'; 4 | // @ts-ignore 5 | import { v4 as uuidv4 } from 'uuid'; 6 | import { CipherHelper } from './cipher'; 7 | 8 | export interface IDekGroup { 9 | groupid: string; 10 | min: number; 11 | max: number; 12 | } 13 | 14 | export interface IPolicies { 15 | issuer_dek_group: Array; 16 | subject_dek_group: Array; 17 | result_dek: string; 18 | operation: Array; 19 | post_proc: string; 20 | pre_proc: string; 21 | } 22 | 23 | /** 24 | * 25 | */ 26 | export class Bcl { 27 | public bclBody = { 28 | version: 1, 29 | serial_num: '', 30 | issuer_pukid: '', 31 | subject_pukid: '', 32 | validity: { 33 | not_after: '', 34 | not_before: '', 35 | }, 36 | policies: { 37 | issuer_dek_group: new Array(), 38 | subject_dek_group: new Array(), 39 | result_dek: '', 40 | operation: '', 41 | postproc: '', 42 | preproc: '', 43 | }, 44 | }; 45 | 46 | private service!: Service; 47 | 48 | constructor(service: Service) { 49 | this.service = service; 50 | } 51 | 52 | /** 53 | * 54 | * @param issuerPukid 55 | * @param subjectPukid 56 | * @param serialNum 57 | * @param resultDek 58 | * @param operation 59 | * @param postProc 60 | * @param preProc 61 | * @param version 62 | * @param notBefore 63 | * @param notAfter 64 | */ 65 | public async createBclBody( 66 | issuerPukid: string, 67 | subjectPukid: string, 68 | serialNum: string, 69 | version = 1, 70 | resultDek = 'SUBJECT', 71 | operation = ['*'], 72 | postProc = 'NULL', 73 | preProc = 'NULL', 74 | notBefore?: number, 75 | notAfter?: number, 76 | ): Promise { 77 | if (issuerPukid && subjectPukid) { 78 | await this.loadBclBodyByPukId(issuerPukid, subjectPukid); 79 | } else if (serialNum) { 80 | await this.loadBclBodyBySN(serialNum); 81 | } 82 | 83 | if (this.bclBody.serial_num) { 84 | return this; 85 | } 86 | 87 | if (!serialNum) { 88 | serialNum = uuidv4(); 89 | } 90 | this.bclBody.version = version; 91 | this.bclBody['serial_num'] = serialNum; 92 | this.bclBody['issuer_pukid'] = issuerPukid; 93 | this.bclBody['subject_pukid'] = subjectPukid; 94 | const nowDate = dayjs(); 95 | if (!notBefore) { 96 | notBefore = nowDate.valueOf(); 97 | } 98 | if (!notAfter) { 99 | notAfter = nowDate.add(365, 'day').valueOf(); 100 | } 101 | 102 | this.bclBody['validity']['not_after'] = 103 | dayjs(notAfter).format('YYYYMMDDHHmmssZZ'); 104 | this.bclBody['validity']['not_before'] = 105 | dayjs(notBefore).format('YYYYMMDDHHmmssZZ'); 106 | // @ts-ignore 107 | this.bclBody['policies'] = this._initBlankBclPolicies( 108 | resultDek, 109 | operation, 110 | postProc, 111 | preProc, 112 | ); 113 | return this; 114 | } 115 | 116 | /** 117 | * get bcl body by serialNum 118 | * @param serialNum 119 | */ 120 | public async loadBclBodyBySN(serialNum: string): Promise { 121 | const bclBodyJson = await this.service.execute({ 122 | bizType: 116, 123 | serialNum, 124 | }); 125 | if (bclBodyJson) { 126 | this.bclBody = JSON.parse(bclBodyJson); 127 | } 128 | return this; 129 | } 130 | 131 | public async loadBclBodyByPukId(issuePukId: string, subjectPukId: string) { 132 | const bclBodyJson = await this.service.execute({ 133 | bizType: 118, 134 | issuePukId: issuePukId, 135 | subjectPukId: subjectPukId, 136 | }); 137 | if (bclBodyJson) { 138 | this.bclBody = JSON.parse(bclBodyJson); 139 | } 140 | return this; 141 | } 142 | 143 | /** 144 | * 145 | * @param issuerDekGroup 146 | * @param subjectDekGroup 147 | */ 148 | public addDekGroup( 149 | issuerDekGroup: IDekGroup, 150 | subjectDekGroup?: IDekGroup, 151 | ): Bcl { 152 | const preIssueDekGroup = this.bclBody['policies'][ 153 | 'issuer_dek_group' 154 | ].filter((p) => p.groupid !== issuerDekGroup.groupid); 155 | preIssueDekGroup.push(issuerDekGroup); 156 | this.bclBody['policies']['issuer_dek_group'] = preIssueDekGroup; 157 | if (subjectDekGroup) { 158 | const preSubjectDekGroup = this.bclBody['policies'][ 159 | 'subject_dek_group' 160 | ].filter((p) => p.groupid !== subjectDekGroup.groupid); 161 | preSubjectDekGroup.push(subjectDekGroup); 162 | this.bclBody['policies']['subject_dek_group'] = preSubjectDekGroup; 163 | } 164 | return this; 165 | } 166 | 167 | public removeDekGroup( 168 | issueGroupId: Array, 169 | subjectDekGroupId?: Array, 170 | ): Bcl { 171 | const newIssueDekGroup = this.bclBody['policies'][ 172 | 'issuer_dek_group' 173 | ].filter((d) => !issueGroupId.includes(d.groupid)); 174 | this.bclBody['policies']['issuer_dek_group'] = newIssueDekGroup; 175 | if (subjectDekGroupId && subjectDekGroupId.length) { 176 | const newSubjectDekGroup = this.bclBody['policies'][ 177 | 'subject_dek_group' 178 | ].filter((d) => !subjectDekGroupId.includes(d.groupid)); 179 | this.bclBody['policies']['subject_dek_group'] = newSubjectDekGroup; 180 | } 181 | return this; 182 | } 183 | 184 | public removeDekGroupAll(): Bcl { 185 | this.bclBody['policies']['issuer_dek_group'] = []; 186 | this.bclBody['policies']['subject_dek_group'] = []; 187 | return this; 188 | } 189 | 190 | /** 191 | * 192 | * @param privateKeyPem 193 | */ 194 | private _signBclBody(privateKeyPem: string): Buffer { 195 | const toBeSignedBytes = Buffer.from(JSON.stringify(this.bclBody), 'utf8'); 196 | const buffer = Buffer.concat([ 197 | Buffer.from('01', 'hex'), 198 | CipherHelper.rsaSign(privateKeyPem, toBeSignedBytes), 199 | ]); 200 | return buffer; 201 | } 202 | 203 | /** 204 | * 205 | * @param privateKeyPem 206 | */ 207 | public async issueBcl( 208 | privateKeyPem: string, 209 | bizType = 106, 210 | ): Promise { 211 | if (!this.bclBody.serial_num) { 212 | throw new Error('Bcl is not init'); 213 | } 214 | const bclRequestJson = JSON.stringify(this.bclBody); 215 | const bclSignBuffer = this._signBclBody(privateKeyPem); 216 | return await this.service.execute({ 217 | bizType, 218 | bclBody: bclRequestJson, 219 | privateSig: bclSignBuffer.toString('base64'), 220 | }); 221 | } 222 | 223 | /** 224 | * 225 | * @param result_dek 226 | * @param operation 227 | * @param postproc 228 | * @param preproc 229 | * @private 230 | */ 231 | private _initBlankBclPolicies( 232 | result_dek: string, 233 | operation: Array, 234 | postproc: string, 235 | preproc: string, 236 | ): IPolicies { 237 | const policies = { 238 | issuer_dek_group: [], 239 | subject_dek_group: [], 240 | result_dek, 241 | operation, 242 | post_proc: postproc, 243 | pre_proc: preproc, 244 | }; 245 | return policies; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/util/cipher.ts: -------------------------------------------------------------------------------- 1 | import * as CRYPTO from 'crypto'; 2 | const Rsa = require('node-rsa'); 3 | // @ts-ignore 4 | import forge from 'node-forge'; 5 | import { Util } from './util'; 6 | import Decimal from 'decimal.js'; 7 | import { DataType } from '../types'; 8 | 9 | /** 10 | * Helper related to encryption 11 | */ 12 | export class CipherHelper { 13 | /** 14 | * Obtain bytes of random length 15 | * @param length 16 | * @return Buffer 17 | */ 18 | static randomBytes(length = 16): Buffer { 19 | return CRYPTO.randomBytes(length); 20 | } 21 | 22 | /** 23 | * get hash 24 | * @param data 25 | */ 26 | static sha256Hash(data: string): string { 27 | return CRYPTO.createHash('sha256').update(data).digest('base64'); 28 | } 29 | 30 | /** 31 | * rsa sign 32 | * @param privateKeyPem 33 | * @param data 34 | */ 35 | static rsaSign(privateKeyPem: string, data: string | Buffer): Buffer { 36 | const privateKey = forge.pki.privateKeyFromPem(privateKeyPem); 37 | const md = forge.md.sha256.create(); 38 | if(typeof data === 'string') { 39 | md.update(data, 'utf8'); 40 | }else { 41 | const str = data.toString('latin1'); 42 | md.update(str, 'latin1'); 43 | } 44 | const pss = forge.pss.create({ 45 | md: forge.md.sha256.create(), 46 | mgf: forge.mgf.mgf1.create(forge.md.sha256.create()), 47 | saltLength: 32 48 | }); 49 | const signature = privateKey.sign(md, pss); 50 | return Buffer.from(signature, "latin1"); 51 | } 52 | 53 | static rsaEncrypt(pubKey: string, data: Buffer): Buffer { 54 | const forgePublicKey = forge.pki.publicKeyFromPem(pubKey); 55 | const byteString = Util.ab2str(data); 56 | const encrypted = forgePublicKey.encrypt(byteString, 'RSA-OAEP', { 57 | md: forge.md.sha256.create(), 58 | mgf1: { 59 | md: forge.md.sha256.create(), 60 | }, 61 | }); 62 | return Buffer.from(encrypted, 'latin1'); 63 | } 64 | 65 | /** 66 | * get hmac hash 67 | * @param key 68 | * @param data 69 | */ 70 | static hmacHash(key: Buffer, data: string | Uint8Array): Buffer { 71 | const h = CRYPTO.createHmac('sha256', key); 72 | h.update(data); 73 | return h.digest(); 74 | } 75 | 76 | /** 77 | * create RSA keys 78 | * @param b 79 | */ 80 | static createKeyPemString(b = 2048): { 81 | publicKeyPem: string; 82 | privateKeyPem: string; 83 | } { 84 | const key = new Rsa({ b: 2048 }); 85 | const publicKeyPem = key.exportKey('pkcs8-public-der'); 86 | const privateKeyPem = key.exportKey('pkcs8-private-der'); 87 | return { publicKeyPem, privateKeyPem }; 88 | } 89 | 90 | static getPublicKeyPemFromPrivate(privateKeyDer: Buffer) { 91 | const key = new Rsa(privateKeyDer, 'pkcs8-der'); 92 | const publicKeyPem = key.exportKey('pkcs8-public-pem'); 93 | const privateKeyPem = key.exportKey("pkcs8-private-pem"); 94 | return {publicKeyPem, privateKeyPem}; 95 | } 96 | 97 | /** 98 | * aes encrypt 99 | * @param key 100 | * @param iv 101 | * @param data 102 | */ 103 | static aesEncrypt( 104 | key: Buffer, 105 | iv: Uint8Array, 106 | data: string | Buffer, 107 | ): Buffer { 108 | const cipher = CRYPTO.createCipheriv('aes-128-cbc', key, iv); 109 | let encrypted_data; 110 | cipher.setAutoPadding(true); 111 | encrypted_data = cipher.update(data); 112 | encrypted_data = Buffer.concat([encrypted_data, cipher.final()]); 113 | return encrypted_data as Buffer; 114 | } 115 | 116 | /** 117 | * aes decrypt 118 | * @param key 119 | * @param iv 120 | * @param data 121 | */ 122 | static aesDecrypt(key: Buffer, iv: Buffer, data: Buffer): Buffer { 123 | const cipher = CRYPTO.createDecipheriv('aes-128-cbc', key, iv); 124 | const encrypted_data_b64 = Buffer.from(data).toString('base64'); 125 | let decrypted_data = cipher.update(encrypted_data_b64, 'base64'); 126 | decrypted_data = Buffer.concat([decrypted_data, cipher.final()]); 127 | return decrypted_data; 128 | } 129 | 130 | /** 131 | * Generate hexadecimal mk 132 | */ 133 | static generateMk(): string { 134 | const mk = CipherHelper.randomBytes(); 135 | return mk.toString('hex'); 136 | } 137 | 138 | /** 139 | * 140 | * @param mk 141 | * @param dekId 142 | * @param dek 143 | */ 144 | static encryptDekToBase64(mk: Buffer, dekId: number, dek: Buffer) { 145 | const buffer = new ArrayBuffer(8); 146 | const view = new DataView(buffer); 147 | view.setUint32(0, dekId & 0xffffffff, true); 148 | view.setUint32(4, Math.floor(dekId / 0x100000000), true); 149 | const dekid_dek = Buffer.concat([Buffer.from(buffer), dek], 24); 150 | const iv = CipherHelper.randomBytes(); 151 | const encrypted_data = CipherHelper.aesEncrypt(mk, iv, dekid_dek); 152 | const dekCipher = Buffer.concat([Buffer.from([3]), iv, encrypted_data]); 153 | return dekCipher.toString('base64'); 154 | } 155 | 156 | static decryptDekToBase64(mek: Buffer, dekCipherStr: string): [number, Buffer] { 157 | const dekCipher = Buffer.from(dekCipherStr, 'base64'); 158 | const dekid_dek = CipherHelper.aesDecrypt( 159 | mek, 160 | dekCipher.slice(1, 17), 161 | dekCipher.slice(17), 162 | ); 163 | const dekid = new DataView(dekid_dek.slice(0, 8).buffer).getUint16(0, true); 164 | const dek = dekid_dek.slice(8); 165 | return [dekid, dek]; 166 | } 167 | 168 | static digest_gAuth(mek: Buffer, grp_id: Buffer, dek_id: number): string { 169 | const buffer = new ArrayBuffer(8); 170 | const view = new DataView(buffer); 171 | view.setBigInt64(0, BigInt(dek_id), true); 172 | const dek_id_array = Buffer.from(buffer); 173 | const buf = Buffer.concat([grp_id, dek_id_array]); 174 | const gAuth = CipherHelper.hmacHash(mek, buf); 175 | return gAuth.toString('base64'); 176 | } 177 | 178 | public static encodeDataByType(data: any, encType: number): Buffer { 179 | let result!: Uint8Array; 180 | const buffer = new ArrayBuffer(8); 181 | const view = new DataView(buffer); 182 | switch (encType) { 183 | case DataType.int4: 184 | view.setInt32(0, data, true); 185 | result = new Uint8Array(buffer, 0, 4); 186 | break; 187 | case DataType.int8: 188 | view.setBigInt64(0, BigInt(data), true); 189 | result = new Uint8Array(buffer); 190 | break; 191 | case DataType.float4: 192 | view.setFloat32(0, data, true); 193 | result = new Uint8Array(buffer, 0, 4); 194 | break; 195 | case DataType.float8: 196 | view.setFloat64(0, data, true); 197 | result = new Uint8Array(buffer); 198 | break; 199 | case 6: 200 | const val = new Decimal(data); 201 | result = new TextEncoder().encode(val.toString()); 202 | break; 203 | case 7: 204 | result = new TextEncoder().encode(data); 205 | break; 206 | case 8: 207 | const uSec = BigInt(Math.floor(data * 1000)); 208 | const offset = BigInt( 209 | Math.floor(new Date().getTimezoneOffset() * 60 * 1000000), 210 | ); 211 | const adjustedUSec = uSec - BigInt(946684800000000) + offset; 212 | view.setBigInt64(0, adjustedUSec, true); 213 | result = new Uint8Array(buffer); 214 | break; 215 | default: 216 | throw new Error('Unsupported encryption type'); 217 | } 218 | return Buffer.from(result); 219 | } 220 | 221 | static decodeDataByType(data: any, encType: number): any { 222 | let result: any; 223 | if (encType === DataType.int4) { 224 | // enc_int4 225 | const size = 4; 226 | const buf = data.slice(0, size); 227 | result = new Int32Array(buf.buffer)[0]; 228 | } else if (encType === DataType.int8) { 229 | // enc_int8 230 | const size = 8; 231 | const buf = data.slice(0, size); 232 | result = new BigInt64Array(buf.buffer)[0]; 233 | result = result.toString();//end n ? 234 | } else if (encType === DataType.float4) { 235 | // enc_float4 236 | const size = 4; 237 | const buf = data.slice(0, size); 238 | result = new Float32Array(buf.buffer)[0]; 239 | } else if (encType === DataType.float8) { 240 | // enc_float8 241 | const size = 8; 242 | const buf = data.slice(0, size); 243 | result = new Float64Array(buf.buffer)[0]; 244 | } else if (encType === 6) { 245 | // enc_decimal 246 | result = new Decimal(Buffer.from(data).toString()); 247 | result = result.toString(); 248 | } else if (encType === 7) { 249 | // enc_text 250 | result = new TextDecoder().decode(data); 251 | } else if (encType === 8) { 252 | // enc_timestamp 253 | const size = 8; 254 | const buf = data.slice(0, size); 255 | let u_sec = new BigInt64Array(buf.buffer)[0]; 256 | u_sec += BigInt(946684800000000); 257 | u_sec -= BigInt(new Date().getTimezoneOffset() * 60 * 1000000); 258 | const time_stamp = Number(u_sec) / 1000000.0; 259 | result = time_stamp * 1000; 260 | } else { 261 | throw new Error('Unsupported encryption type'); 262 | } 263 | return result; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/util/clerk.ts: -------------------------------------------------------------------------------- 1 | import ClerkJS from '@clerk/clerk-js'; 2 | import { SignInProps, UserButtonProps } from '@clerk/types'; 3 | import { ClerkConfig, MkManager } from 'src/types'; 4 | import { CipherHelper } from './cipher'; 5 | import { CLERK_PUBLISHABLE_KEY, TOKEN_KEY, WALLET_key } from './constant'; 6 | import Web3 from 'web3'; 7 | import { 8 | getEncryptionPublicKey, 9 | encrypt as metaMaskEncrypt, 10 | decrypt as metaMaskDecrypt, 11 | } from '@metamask/eth-sig-util'; 12 | 13 | export default class Clerk implements MkManager { 14 | private mkBuffer: Buffer | undefined; 15 | private privateKeyCipherBuffer: Buffer | undefined; 16 | private waPk: string | undefined; 17 | 18 | private web3: Web3; 19 | public config: ClerkConfig | undefined; 20 | public clerkJs: ClerkJS; 21 | 22 | constructor(config?: ClerkConfig) { 23 | this.config = config; 24 | this.clerkJs = new ClerkJS(CLERK_PUBLISHABLE_KEY); 25 | this.web3 = new Web3(); 26 | } 27 | 28 | async encrypt(str: string): Promise { 29 | if (!this.waPk) { 30 | throw new Error('Please connect first.'); 31 | } 32 | const pubKey = getEncryptionPublicKey(this.waPk.substring(2)); 33 | if (pubKey) { 34 | const enc = metaMaskEncrypt({ 35 | publicKey: pubKey, 36 | data: Buffer.from(str, 'hex').toString('base64'), 37 | version: 'x25519-xsalsa20-poly1305', 38 | }); 39 | const buf = Buffer.concat([ 40 | Buffer.from(enc.ephemPublicKey, 'base64'), 41 | Buffer.from(enc.nonce, 'base64'), 42 | Buffer.from(enc.ciphertext, 'base64'), 43 | ]); 44 | return buf; 45 | } 46 | } 47 | async decrypt(cipher: Buffer): Promise { 48 | if (!this.waPk) { 49 | throw new Error('Please connect first.'); 50 | } 51 | const encryptedData = { 52 | version: 'x25519-xsalsa20-poly1305', 53 | ephemPublicKey: cipher.slice(0, 32).toString('base64'), 54 | nonce: cipher.slice(32, 56).toString('base64'), 55 | ciphertext: cipher.slice(56).toString('base64'), 56 | }; 57 | // Convert data to hex string required by MetaMask 58 | const decryptData = metaMaskDecrypt({ 59 | encryptedData, 60 | privateKey: this.waPk.substring(2), 61 | }); 62 | return Buffer.from(decryptData, 'base64'); 63 | } 64 | async getWalletAccount(): Promise { 65 | if (!this.waPk) { 66 | throw new Error('Please connect first.'); 67 | } 68 | const account = await this.web3.eth.accounts.privateKeyToAccount(this.waPk); 69 | return account.address; 70 | } 71 | async checkConnection(): Promise {} 72 | async getMekBytes(): Promise { 73 | const user = this.clerkJs.user; 74 | if (!user) { 75 | throw new Error('Please sign in first.'); 76 | } 77 | if (this.mkBuffer) { 78 | return this.mkBuffer; 79 | } 80 | const mk = user.unsafeMetadata.mk as string; 81 | if (mk) { 82 | this.mkBuffer = Buffer.from(mk, 'hex'); 83 | } else { 84 | this.mkBuffer = await this._setMk(); 85 | } 86 | return this.mkBuffer; 87 | } 88 | async getPkPem(): Promise<{ privateKeyPem: string; publicKeyPem: string }> { 89 | const mkBuffer = await this.getMekBytes(); 90 | const user = this.clerkJs.user; 91 | if (!this.privateKeyCipherBuffer) { 92 | const pk = user?.unsafeMetadata.pk as string; 93 | if (pk) { 94 | this.privateKeyCipherBuffer = Buffer.from(pk, 'hex'); 95 | } else { 96 | this.privateKeyCipherBuffer = await this._setPk(mkBuffer); 97 | } 98 | } 99 | const iv = this.privateKeyCipherBuffer.slice(0, 16); 100 | const cipher = this.privateKeyCipherBuffer.slice(16); 101 | const decrypt = CipherHelper.aesDecrypt(mkBuffer, iv, cipher); 102 | const { publicKeyPem, privateKeyPem } = 103 | CipherHelper.getPublicKeyPemFromPrivate(decrypt); 104 | return { privateKeyPem, publicKeyPem }; 105 | } 106 | async personalSignature(signData: string): Promise { 107 | if (!this.waPk) { 108 | throw new Error('Please connect first.'); 109 | } 110 | const account = await this.web3.eth.accounts.privateKeyToAccount(this.waPk); 111 | const sign = await account.sign(signData); 112 | return sign.signature; 113 | } 114 | 115 | public async init(): Promise { 116 | await this.clerkJs.load(this.config?.options); 117 | if (this.clerkJs.user) { 118 | const email = this.clerkJs.user.primaryEmailAddress 119 | ?.emailAddress as string; 120 | localStorage.setItem(WALLET_key, email); 121 | const sesstion_token = (await this.clerkJs.session?.getToken({ 122 | template: 'mindnetwork', 123 | })) as string; 124 | await this._initWaPk(); 125 | return sesstion_token; 126 | } else { 127 | localStorage.removeItem(TOKEN_KEY); 128 | } 129 | } 130 | 131 | public renderUserButton( 132 | dom?: HTMLDivElement, 133 | userButtonProps?: UserButtonProps, 134 | ): void { 135 | const target = dom || this.config?.target; 136 | let compontens = null; 137 | if (typeof target === 'string') { 138 | compontens = document.querySelector(target) as HTMLDivElement; 139 | } 140 | if (target instanceof HTMLDivElement) { 141 | compontens = target; 142 | } 143 | if (!compontens) { 144 | throw new Error('clerk target dom node is not a HTMLDivElement'); 145 | } 146 | this.clerkJs.mountUserButton(compontens, { 147 | ...this.config?.userButtonProps, 148 | ...userButtonProps, 149 | }); 150 | } 151 | 152 | public async signIn(props?: SignInProps): Promise { 153 | await this.clerkJs.openSignIn(props); 154 | } 155 | 156 | private async _setMk(): Promise { 157 | const mk: string = CipherHelper.generateMk(); 158 | const user = this.clerkJs.user; 159 | await user?.update({ 160 | unsafeMetadata: { 161 | ...user?.unsafeMetadata, 162 | mk, 163 | }, 164 | }); 165 | return Buffer.from(mk, 'hex'); 166 | } 167 | 168 | private async _setPk(mekBuffer: Buffer): Promise { 169 | const keys = CipherHelper.createKeyPemString(); 170 | const iv = CipherHelper.randomBytes(); 171 | const privateKeyCipherBuffer = Buffer.concat([ 172 | iv, 173 | CipherHelper.aesEncrypt(mekBuffer, iv, keys.privateKeyPem), 174 | ]); 175 | const user = this.clerkJs.user; 176 | await user?.update({ 177 | unsafeMetadata: { 178 | ...user?.unsafeMetadata, 179 | pk: privateKeyCipherBuffer.toString('hex'), 180 | }, 181 | }); 182 | return privateKeyCipherBuffer; 183 | } 184 | 185 | private async _initWaPk(): Promise { 186 | if (this.waPk) { 187 | return; 188 | } 189 | const user = this.clerkJs.user; 190 | if (!user) { 191 | return; 192 | } 193 | const waPk = user.unsafeMetadata.waPk as string; 194 | if (waPk) { 195 | this.waPk = waPk; 196 | return; 197 | } 198 | const privateKey = this.web3.eth.accounts.create().privateKey; 199 | await user?.update({ 200 | unsafeMetadata: { 201 | ...user?.unsafeMetadata, 202 | waPk: privateKey, 203 | }, 204 | }); 205 | this.waPk = privateKey; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/util/constant.ts: -------------------------------------------------------------------------------- 1 | export const TOKEN_KEY = 'JEnG1cYqXS3D3nPnx2KsHw=='; 2 | export const WALLET_key = 'wa'; 3 | export const APP_KEY = 'app'; 4 | export const CLERK_PUBLISHABLE_KEY = 5 | 'pk_test_Zmx5aW5nLWJ1enphcmQtNTIuY2xlcmsuYWNjb3VudHMuZGV2JA'; 6 | export const CHAIN_KEY = 'chain'; 7 | export const LOGIN_METHOD_KEY = 'connect'; 8 | -------------------------------------------------------------------------------- /src/util/result.ts: -------------------------------------------------------------------------------- 1 | import { ResultType } from '../types'; 2 | 3 | export default class Result { 4 | static success(data: any): ResultType { 5 | return { code: 0, result: data }; 6 | } 7 | 8 | static fail(error: any): ResultType { 9 | if (error.code) { 10 | return { code: error.code, message: error.message }; 11 | } 12 | return { code: 50000, message: error.toString() }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/util/util.ts: -------------------------------------------------------------------------------- 1 | export class Util { 2 | static ab2str(buf: Buffer) { 3 | // @ts-ignore 4 | return String.fromCharCode.apply(null, buf); 5 | } 6 | 7 | static structPackq(data: string) { 8 | let result!: Uint8Array; 9 | const buffer = new ArrayBuffer(8); 10 | const view = new DataView(buffer); 11 | view.setBigInt64(0, BigInt(data), true); 12 | result = new Uint8Array(buffer); 13 | return result; 14 | } 15 | 16 | static stringToByte(str: string) { 17 | const bytes = new Array(); 18 | let len, c; 19 | len = str.length; 20 | for (let i = 0; i < len; i++) { 21 | c = str.charCodeAt(i); 22 | if (c >= 0x010000 && c <= 0x10ffff) { 23 | bytes.push(((c >> 18) & 0x07) | 0xf0); 24 | bytes.push(((c >> 12) & 0x3f) | 0x80); 25 | bytes.push(((c >> 6) & 0x3f) | 0x80); 26 | bytes.push((c & 0x3f) | 0x80); 27 | } else if (c >= 0x000800 && c <= 0x00fff) { 28 | bytes.push(((c >> 12) & 0x07) | 0xf0); 29 | bytes.push(((c >> 6) & 0x3f) | 0x80); 30 | bytes.push((c & 0x3f) | 0x80); 31 | } else if (c >= 0x000800 && c <= 0x0007ff) { 32 | bytes.push(((c >> 6) & 0x3f) | 0x80); 33 | bytes.push((c & 0x3f) | 0x80); 34 | } else { 35 | bytes.push(c & 0xff); 36 | } 37 | } 38 | return bytes; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/util/web3.ts: -------------------------------------------------------------------------------- 1 | import { Web3Provider } from '@ethersproject/providers'; 2 | import web3 from 'web3'; 3 | import { Contract } from 'web3-eth-contract'; 4 | import { CipherHelper } from './cipher'; 5 | import { encrypt } from '@metamask/eth-sig-util'; 6 | import { CHAIN_KEY, WALLET_key } from './constant'; 7 | import { MindLake } from '../MindLake'; 8 | import { ChainInfo, MkManager } from '../types'; 9 | // const encrypt = {}; 10 | 11 | export interface RequestArguments { 12 | readonly method: string; 13 | readonly params?: readonly unknown[] | object; 14 | } 15 | 16 | export interface Web3WithWalletProvider extends Web3Provider { 17 | request(args: RequestArguments): Promise; 18 | selectedAddress: string; 19 | } 20 | 21 | /** 22 | * web3 helper 23 | */ 24 | export class Web3Interact implements MkManager { 25 | private readonly provider: Web3WithWalletProvider; 26 | 27 | /** 28 | * wallet address 29 | */ 30 | private account!: string; 31 | 32 | /** 33 | * web3 instance 34 | */ 35 | private web3!: web3; 36 | 37 | /** 38 | * contract instance 39 | */ 40 | private contract!: Contract; 41 | 42 | /** 43 | * contract address 44 | */ 45 | public static CONTRACT_ADDRESS = '0xF5932e67e84F08965DC6D62C2B67f47a6826E5a7'; 46 | 47 | public static SUPPORT_CHAIN = 5; 48 | 49 | /** 50 | * currnt chaind 51 | */ 52 | public chain: ChainInfo | undefined; 53 | 54 | /** 55 | * wallet publicKe 56 | */ 57 | private publicKey!: string; 58 | 59 | /** 60 | * encrypt mk buffer 61 | */ 62 | private mkCipherBuffer!: Buffer; 63 | 64 | /** 65 | * decrypt mk buffer 66 | */ 67 | private mkBuffer!: Buffer; 68 | 69 | /** 70 | * encrypt privateKey buffer 71 | */ 72 | private privateKeyCipherBuffer!: Buffer; 73 | 74 | constructor() { 75 | if (!window.ethereum) { 76 | throw new Error('Please install a wallet'); 77 | } 78 | let currentProvider; 79 | // @ts-ignore 80 | if (window.ethereum.providers) { 81 | // @ts-ignore 82 | currentProvider = window.ethereum.providers.find( 83 | (p: any) => p.isMetaMask, 84 | ); 85 | } else { 86 | currentProvider = window.ethereum; 87 | } 88 | if (!currentProvider.isMetaMask) { 89 | throw new Error( 90 | 'Only MetamMask wallet is supported currently. Please install or replace', 91 | ); 92 | } 93 | this.provider = currentProvider; 94 | this.web3 = new web3(currentProvider); 95 | this._onListen(); 96 | } 97 | async encrypt(str: string): Promise { 98 | const pubKey = await this._getEncryptionPublicKey(); 99 | if (pubKey) { 100 | const enc = encrypt({ 101 | publicKey: pubKey, 102 | data: Buffer.from(str, 'hex').toString('base64'), 103 | version: 'x25519-xsalsa20-poly1305', 104 | }); 105 | const buf = Buffer.concat([ 106 | Buffer.from(enc.ephemPublicKey, 'base64'), 107 | Buffer.from(enc.nonce, 'base64'), 108 | Buffer.from(enc.ciphertext, 'base64'), 109 | ]); 110 | return buf; 111 | } 112 | } 113 | 114 | async decrypt(cipher: Buffer): Promise { 115 | const structuredData = { 116 | version: 'x25519-xsalsa20-poly1305', 117 | ephemPublicKey: cipher.slice(0, 32).toString('base64'), 118 | nonce: cipher.slice(32, 56).toString('base64'), 119 | ciphertext: cipher.slice(56).toString('base64'), 120 | }; 121 | // Convert data to hex string required by MetaMask 122 | const ct = `0x${Buffer.from( 123 | JSON.stringify(structuredData), 124 | 'utf8', 125 | ).toString('hex')}`; 126 | const decrypt = (await this.provider.request({ 127 | method: 'eth_decrypt', 128 | params: [ct, this.account], 129 | })) as string; 130 | return Buffer.from(decrypt, 'base64'); 131 | } 132 | 133 | public setChain(chain: ChainInfo) { 134 | this.chain = chain; 135 | } 136 | 137 | private _onListen() { 138 | this.provider.on('accountsChanged', this._onAccountsChanged.bind(this)); 139 | this.provider.on('chainChanged', this._onChainChanged.bind(this)); 140 | this.provider.on('disconnect', this._onDisconnect.bind(this)); 141 | } 142 | 143 | /** 144 | * connected wallet account change event 145 | * @param accounts 146 | * @private 147 | */ 148 | private _onAccountsChanged(accounts: string[]) { 149 | console.log('Wallet address changed:', accounts && accounts[0]); 150 | localStorage.setItem(WALLET_key, accounts && accounts[0]); 151 | this.account = accounts && accounts[0]; 152 | this._walletChange(); 153 | } 154 | 155 | /** 156 | * chain change event 157 | * @param chainId 158 | * @private 159 | */ 160 | private _onChainChanged(chainId: number) { 161 | console.log( 162 | 'Chain changed: ', 163 | web3.utils.hexToNumber(chainId), 164 | this.account, 165 | ); 166 | localStorage.setItem( 167 | CHAIN_KEY, 168 | JSON.stringify({ chainId: web3.utils.hexToNumber(chainId) as string }), 169 | ); 170 | this._walletChange(); 171 | } 172 | 173 | private _walletChange() { 174 | if (this.account) { 175 | this.mkCipherBuffer = undefined!; 176 | this.mkBuffer = undefined!; 177 | this.privateKeyCipherBuffer = undefined!; 178 | this.publicKey = undefined!; 179 | } 180 | } 181 | 182 | /** 183 | * wallet disconnect 184 | * @private 185 | */ 186 | private _onDisconnect() { 187 | console.log('Wallet disconnect: '); 188 | } 189 | 190 | /** 191 | * get walletAddress 192 | */ 193 | public async getWalletAccount(): Promise { 194 | if (!this.account || !MindLake.isConnected) { 195 | const accounts = (await this.provider.request({ 196 | method: 'eth_requestAccounts', 197 | })) as string[]; 198 | if (!accounts.length) { 199 | throw new Error('No accounts returned'); 200 | } 201 | this.account = accounts[0]; 202 | } 203 | localStorage.setItem(WALLET_key, this.account); 204 | return this.account; 205 | } 206 | 207 | public async checkConnection() { 208 | try { 209 | const accounts = (await this.provider.request({ 210 | method: 'eth_accounts', 211 | })) as Array; 212 | if (accounts.length) { 213 | this.account = accounts[0]; 214 | localStorage.setItem(WALLET_key, accounts[0]); 215 | } 216 | } catch (error) { 217 | console.log(error); 218 | } 219 | } 220 | 221 | /** 222 | * wallet signature 223 | * @param signData 224 | */ 225 | public async personalSignature(signData: string): Promise { 226 | const signature = await this.provider.request({ 227 | method: 'personal_sign', 228 | params: [web3.utils.fromUtf8(signData), this.account], 229 | }); 230 | return signature as string; 231 | } 232 | 233 | /** 234 | * get wallet publicKey 235 | * @private 236 | */ 237 | public async _getEncryptionPublicKey(): Promise { 238 | if (this.provider) { 239 | if (this.publicKey) { 240 | return this.publicKey; 241 | } 242 | const keyB64: string = (await this.provider.request({ 243 | method: 'eth_getEncryptionPublicKey', 244 | params: [this.account], 245 | })) as string; 246 | this.publicKey = keyB64; 247 | return this.publicKey; 248 | } 249 | } 250 | 251 | /** 252 | * get mk 253 | */ 254 | public async getMekBytes(): Promise { 255 | await this._changeChainToSupportChain(); 256 | await this._loadKeysCipherFromChain(); 257 | if (!this.mkCipherBuffer) { 258 | await this._generateKeysCipherToChain(); 259 | } 260 | if (!this.mkBuffer) { 261 | this.mkBuffer = await this._decryptMk(this.mkCipherBuffer); 262 | } 263 | // console.log("mk", this.mkBuffer.toString("hex")) 264 | return this.mkBuffer; 265 | } 266 | 267 | /** 268 | * get pk 269 | */ 270 | public async getPkPem(): Promise<{ 271 | privateKeyPem: string; 272 | publicKeyPem: string; 273 | }> { 274 | const mk = await this.getMekBytes(); 275 | const iv = this.privateKeyCipherBuffer.slice(0, 16); 276 | const cipher = this.privateKeyCipherBuffer.slice(16); 277 | const decrypt = CipherHelper.aesDecrypt(mk, iv, cipher); 278 | const { publicKeyPem, privateKeyPem } = 279 | CipherHelper.getPublicKeyPemFromPrivate(decrypt); 280 | return { privateKeyPem, publicKeyPem }; 281 | } 282 | 283 | /** 284 | *load keys form chain 285 | */ 286 | private async _loadKeysCipherFromChain() { 287 | if (this.mkCipherBuffer && this.privateKeyCipherBuffer) { 288 | return; 289 | } 290 | const account = await this.getWalletAccount(); 291 | const keys = await this.contract.methods.getKeys(account).call(); 292 | if (keys && keys.MK && keys.SK) { 293 | this.mkCipherBuffer = Buffer.from(keys.MK.slice(2), 'hex'); 294 | this.privateKeyCipherBuffer = Buffer.from(keys.SK.slice(2), 'hex'); 295 | } 296 | } 297 | 298 | /** 299 | * generateKeys form local to chain 300 | * @private 301 | */ 302 | private async _generateKeysCipherToChain() { 303 | if (this.mkCipherBuffer && this.privateKeyCipherBuffer) { 304 | return; 305 | } 306 | const account = await this.getWalletAccount(); 307 | const checkAccount = await this.web3.utils.toChecksumAddress(account); 308 | const mk = CipherHelper.generateMk(); 309 | const mekBuffer = Buffer.from(mk, 'hex'); 310 | const keys = CipherHelper.createKeyPemString(); 311 | const iv = CipherHelper.randomBytes(); 312 | const privateKeyCipherBuffer = Buffer.concat([ 313 | iv, 314 | CipherHelper.aesEncrypt(mekBuffer, iv, keys.privateKeyPem), 315 | ]); 316 | const mkCipherBuffer = await this._encryptMk(mk); 317 | if (!mkCipherBuffer) { 318 | throw new Error('mk encrypt error'); 319 | } 320 | const txHash = await this.contract.methods 321 | .setKeys(mkCipherBuffer, privateKeyCipherBuffer) 322 | .send({ from: checkAccount }); 323 | if (txHash && txHash.blockHash) { 324 | this.mkCipherBuffer = mkCipherBuffer; 325 | this.privateKeyCipherBuffer = privateKeyCipherBuffer; 326 | } 327 | } 328 | 329 | /** 330 | * encrypt mk 331 | * @param mk 332 | * @private 333 | */ 334 | private async _encryptMk(mk: string): Promise { 335 | return this.encrypt(mk); 336 | } 337 | 338 | /** 339 | * decrypt mk 340 | * @param data 341 | * @private 342 | */ 343 | private async _decryptMk(data: Buffer): Promise { 344 | return this.decrypt(data); 345 | } 346 | 347 | /** 348 | * 349 | * @param chainId support chainId 350 | * @private 351 | */ 352 | public async _changeChainToSupportChain() { 353 | if (!this.chain) { 354 | throw new Error('chain not found'); 355 | } 356 | const abi = JSON.parse(this.chain.abi); 357 | this.contract = new this.web3.eth.Contract( 358 | // @ts-ignore 359 | abi, 360 | this.chain.smartAddress, 361 | ); 362 | const currentNetworkId = await this.web3.eth.net.getId(); 363 | console.log( 364 | 'currentNetworkId', 365 | currentNetworkId, 366 | 'target chainId ', 367 | this.chain.chainId, 368 | ); 369 | if (currentNetworkId != Number(this.chain.chainId)) { 370 | try { 371 | await this.provider.request({ 372 | method: 'wallet_switchEthereumChain', 373 | params: [{ chainId: this.web3.utils.toHex(this.chain.chainId) }], 374 | }); 375 | } catch (error: any) { 376 | if (error?.code === 4902) { 377 | // Add chain to MetaMask 378 | await this.provider.request({ 379 | method: 'wallet_addEthereumChain', 380 | params: [ 381 | { 382 | chainName: this.chain.chainName, 383 | chainId: web3.utils.toHex(this.chain.chainId), 384 | nativeCurrency: { 385 | name: this.chain.currency, 386 | decimals: 18, 387 | symbol: this.chain.currency, 388 | }, 389 | rpcUrls: [this.chain.rpcNodeUrl], 390 | }, 391 | ], 392 | }); 393 | } else { 394 | throw new Error(error); 395 | } 396 | } 397 | } 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "declaration": true, 5 | "skipLibCheck": true, 6 | "baseUrl": "./", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tutorial/Configure_AppKey.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | logo 4 |

MindLake Tutorial: Mind appKey

5 | 6 |

7 | A step-by-step cookbook for beginner to create Mind appKey ! 8 |

9 |
10 | 11 | 12 | 13 | 14 | 15 | You need to register an appKey in order to access Mind Lake during testing period. 16 | Here is the steps and screenshort. 17 | 18 | 1. Visit https://scan.mindnetwork.xyz in your browser 19 | 20 | 2. Login with your MetaMask Wallet 21 | 22 | 3. Click `myDapp` in left side manu 23 | 24 | ![image](./imgs/myDapp_menu.png) 25 | 26 | 3. Click "Create Dapp" 27 | 28 | ![image](./imgs/create_dapp.png) 29 | 30 | 4. Input your Dapp name and then click "Create" 31 | 32 | ![image](./imgs/create_dapp_confirm.png) 33 | 34 | 5. copy appKey value into myconfig.ts to update "appKey" 35 | 36 | ![image](./imgs/dapp_list.png) 37 | -------------------------------------------------------------------------------- /tutorial/Configure_Docker_Case.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | logo 4 |

MindLake Tutorial: Configure Docker Case

5 | 6 |

7 | A step-by-step cookbook for Docker Case Configuration ! 8 |

9 |
10 | 11 | - [:star2: 0. Step by step tutorial](#star2-0-step-by-step-tutorial) 12 | - [:star2: 1. Install docker](#star2-1-install-docker) 13 | - [:art: 1.1 For Mac OS](#art-11-for-mac-os) 14 | - [:dart: 1.1.2 Install docker with HomeBrew](#dart-112-install-docker-with-homebrew) 15 | - [:gear: 1.1.2.1 Step1: Install HomeBrew (If you don't have Homebrew installed)](#gear-1121-step1-install-homebrew-if-you-dont-have-homebrew-installed) 16 | - [:gear: 1.1.2.2 Step2: Install docker](#gear-1122-step2-install-docker) 17 | - [:art: 1.2 For Windows](#art-12-for-windows) 18 | - [:dart: 1.2.1 Download docker](#dart-121-download-docker) 19 | - [:dart: 1.2.2 Install docker](#dart-122-install-docker) 20 | - [:art: 2. Run docker case](#art-2-run-docker-case) 21 | - [:star2: 3. Explore the use cases](#star2-3-explore-the-use-cases) 22 | 23 | ## :star2: 0. Step by step tutorial 24 | This is part of support chapter for MindLake step-by-step tutorial for [Typescript](README.md) 25 | 26 | ## :star2: 1. Install docker 27 | 28 | ### :art: 1.1 For Mac OS 29 | 30 | #### :dart: 1.1.2 Install docker with HomeBrew 31 | If you need to install Nvm from the command line on macOS, the Homebrew package manager is a reliable option. Follow the steps below to install Nvm via Homebrew: 32 | ##### :gear: 1.1.2.1 Step1: Install HomeBrew (If you don't have Homebrew installed) 33 | 1. Open a browser and go to https://brew.sh. 34 | 35 | ![image](./imgs/brew.png) 36 | 37 | 2. Under the "Install Homebrew" title, copy the command 38 | ```shell 39 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 40 | ``` 41 | 42 | ![image](./imgs/install_brew.png) 43 | 44 | 3. Then open a terminal window, paste the copied command, and press the 'Enter' or 'Return' button. 45 | 46 | ![image](./imgs/open_mac_terminal.png) 47 | 48 | 4. Enter your macOS credentials if and when asked. 49 | 50 | 5. If prompted, install Apple's command line developer tools. 51 | 52 | ##### :gear: 1.1.2.2 Step2: Install docker 53 | 1. Enter the following command in terminal to upgrade Homebrew: 54 | ```shell 55 | brew install --cask docker 56 | ``` 57 | 2. run docker app 58 | ![image](./imgs/docker_bac.jpeg) 59 | Next, open docker app 60 | ![image](./imgs/docker_app.png) 61 | 62 | 3. check docker install success 63 | ```shell 64 | docker info 65 | ``` 66 | An example of the output is: 67 | ```shell 68 | Client: 69 | Version: 24.0.2 70 | Context: desktop-linux 71 | Debug Mode: false 72 | 73 | Server: 74 | Containers: 7 75 | Running: 5 76 | Paused: 0 77 | Stopped: 2 78 | Images: 197 79 | Server Version: 24.0.2 80 | Storage Driver: overlay2 81 | Backing Filesystem: extfs 82 | Supports d_type: true 83 | Using metacopy: false 84 | Native Overlay Diff: true 85 | userxattr: false 86 | Logging Driver: json-file 87 | Cgroup Driver: cgroupfs 88 | Cgroup Version: 2 89 | ``` 90 | 91 | ### :art: 1.2 For Windows 92 | 93 | #### :dart: 1.2.1 Download docker 94 | 1. download Docker Desktop on Windows: 95 | 96 | https://docs.docker.com/desktop/install/windows-install/ 97 | 98 | ![image](./imgs/docker_win_download.png) 99 | 100 | 2. Double-click Docker Desktop `Installer.exe` to run the installer. 101 | 102 | 3. When prompted, ensure the `Use WSL 2 instead of Hyper-V` option on the Configuration page is selected or not depending on your choice of backend. 103 | 104 | - If your system only supports one of the two options, you will not be able to select which backend to use. 105 | 106 | 4. Follow the instructions on the installation wizard to authorize the installer and proceed with the install. 107 | 108 | 5. When the installation is successful, click `Close` to complete the installation process. 109 | 110 | 6. If your admin account is different to your user account, you must add the user to the `docker-users` group. Run `Computer Management` as an `administrator` and navigate to `Local Users and Groups > Groups > docker-users`. Right-click to add the user to the group. Log out and log back in for the changes to take effect. 111 | 112 | 113 | #### :dart: 1.2.2 Install docker 114 | 115 | 1. Search for Docker, and select Docker Desktop in the search results. 116 | ![image](./imgs/docker-app-search.png) 117 | 118 | 2. The Docker menu (whale menu) displays the Docker Subscription Service Agreement window. 119 | ![image](./imgs/docker_app_top.webp) 120 | 121 | 122 | 123 | ### :art: 2. Run docker case 124 | 1. Follow Step 1 to encure Docker is running. 125 | 126 | 127 | 2. In command, pull docker image by copying and execute the command 128 | ```shell 129 | docker pull mindnetwork/mind-lake-sdk-typescript-case1:main 130 | ``` 131 | 132 | ```shell 133 | docker pull mindnetwork/mind-lake-sdk-typescript-case2:main 134 | ``` 135 | 136 | ```shell 137 | docker pull mindnetwork/mind-lake-sdk-typescript-case3:main 138 | ``` 139 | 140 | 141 | mind lake case has 3 images: 142 | - https://hub.docker.com/repository/docker/mindnetwork/mind-lake-sdk-typescript-case1 143 | - https://hub.docker.com/repository/docker/mindnetwork/mind-lake-sdk-typescript-case2 144 | - https://hub.docker.com/repository/docker/mindnetwork/mind-lake-sdk-typescript-case3 145 | 146 | 3. run docker image 147 | ![image](imgs/docker_run1.png) 148 | 149 | ![image](imgs/docker_run2.png) 150 | 151 | 152 | 4. bind port with docker image, copy the command 153 | ```shell 154 | docker run -d -p 90:80 mindnetwork/mind-lake-sdk-typescript-case1:main 155 | ``` 156 | 157 | ```shell 158 | docker run -d -p 91:80 mindnetwork/mind-lake-sdk-typescript-case2:main 159 | ``` 160 | 161 | ```shell 162 | docker run -d -p 92:80 mindnetwork/mind-lake-sdk-typescript-case3:main 163 | ``` 164 | 165 | "90"、"91"、"92" are bind ports , 166 | "mindnetwork/mind-lake-sdk-typescript-case1:main"、 167 | "mindnetwork/mind-lake-sdk-typescript-case2:main"、 168 | "mindnetwork/mind-lake-sdk-typescript-case3:main" are docker repositories 169 | 170 | 171 | 5. run mind-lake-sdk-typescript-case1 172 | 173 | Open your browser and enter localhost 174 | ```http 175 | http://localhost:90/use_case_1.html 176 | ``` 177 | ![image](imgs/docker_case1_img.png) 178 | 179 | ```http 180 | http://localhost:91/use_case_2.html 181 | ``` 182 | ```http 183 | http://localhost:92/use_case_3.html 184 | ``` 185 | 186 | 187 | At last, you have successfully executed case1 by use docker !!! 188 | 189 | ## :star2: 3. Explore the use cases 190 | You can jump to [:art: 6.2 Use Case 1](README.md#art-62-use-case-1-single-user-with-structured-data) to continue to explore the use cases. -------------------------------------------------------------------------------- /tutorial/Configure_Node.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | logo 4 |

MindLake Tutorial: Configure Node

5 | 6 |

7 | A step-by-step cookbook for Node Configuration ! 8 |

9 |
10 | 11 | - [:star2: 0. Step by step tutorial](#star2-0-step-by-step-tutorial) 12 | - [:star2: 1. Install Node Package Management Tools: Nvm](#star2-1-install-node-package-management-tools-nvm) 13 | - [:art: 1.1 For Mac OS](#art-11-for-mac-os) 14 | - [:dart: 1.1.2 Install Nvm with HomeBrew](#dart-112-install-nvm-with-homebrew) 15 | - [:gear: 1.1.2.1 Step1: Install HomeBrew (If you don't have Homebrew installed)](#gear-1121-step1-install-homebrew-if-you-dont-have-homebrew-installed) 16 | - [:gear: 1.1.2.2 Step2: Install Nvm](#gear-1122-step2-install-nvm) 17 | - [:gear: 1.1.2.3 Step3: Verify the Nvm Installation](#gear-1123-step3-verify-the-nvm-installation) 18 | - [:art: 1.2 For Windows](#art-12-for-windows) 19 | - [:dart: 1.2.1 Download Nvm](#dart-121-download-nvm) 20 | - [:dart: 1.2.2 Run exe installation file](#dart-122-run-exe-installation-file) 21 | - [:dart: 1.2.3 Validate nvm installation](#dart-123-validate-nvm-installation) 22 | - [:star2: 2. Install Node](#star2-2-install-node) 23 | - [:art: 2.1 Install Node](#art-21-install-node) 24 | - [:art: 2.2 Select right Node 16 version if not correct](#art-22-select-right-node-16-version-if-not-correct) 25 | - [:art: 2.3 Validate if correct node version](#art-23-validate-if-correct-node-version) 26 | 27 | 28 | ## :star2: 0. Step by step tutorial 29 | This is part of support chapter for MindLake step-by-step tutorial for [Typescript](README.md) 30 | 31 | ## :star2: 1. Install Node Package Management Tools: Nvm 32 | 33 | ### :art: 1.1 For Mac OS 34 | 35 | #### :dart: 1.1.2 Install Nvm with HomeBrew 36 | If you need to install Nvm from the command line on macOS, the Homebrew package manager is a reliable option. Follow the steps below to install Nvm via Homebrew: 37 | ##### :gear: 1.1.2.1 Step1: Install HomeBrew (If you don't have Homebrew installed) 38 | 1. Open a browser and go to https://brew.sh. 39 | 40 | ![image](./imgs/brew.png) 41 | 42 | 2. Under the "Install Homebrew" title, copy the command 43 | ```shell 44 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 45 | ``` 46 | 47 | ![image](./imgs/install_brew.png) 48 | 49 | 3. Then open a terminal window, paste the copied command, and press the 'Enter' or 'Return' button. 50 | 51 | ![image](./imgs/open_mac_terminal.png) 52 | 53 | 4. Enter your macOS credentials if and when asked. 54 | 5. If prompted, install Apple's command line developer tools. 55 | 56 | ##### :gear: 1.1.2.2 Step2: Install Nvm 57 | 1. Enter the following command in terminal to upgrade Homebrew: 58 | ```shell 59 | brew update && brew upgrade 60 | ``` 61 | 2. Install `nvm` using this command: 62 | ```shell 63 | brew install nvm 64 | ``` 65 | Next, create a directory for nvm at home. 66 | ``` 67 | mkdir ~/.nvm 68 | ``` 69 | Now, configure the required environment variables. Edit the following configuration file in your home directory 70 | ``` 71 | vim ~/.bash_profile 72 | ``` 73 | and, add the below lines to `~/.bash_profile` ( or `~/.zshrc` for macOS Catalina or newer versions) 74 | ``` 75 | export NVM_DIR=~/.nvm 76 | source $(brew --prefix nvm)/nvm.sh 77 | ``` 78 | Press `ESC` + `:wq` to save and close your file. 79 | 80 | Next, load the variable to the current shell environment. From the next login, it will automatically loaded. 81 | ``` 82 | source ~/.bash_profile 83 | ``` 84 | That’s it. The nvm has been installed on your macOS system. 85 | 86 | ##### :gear: 1.1.2.3 Step3: Verify the Nvm Installation 87 | Enter the following command in terminal: 88 | ```shell 89 | nvm --version 90 | ``` 91 | An example of the output is: 92 | ``` 93 | 1.1.7 94 | ``` 95 | 96 | 97 | ### :art: 1.2 For Windows 98 | Open Terminal on Windows: 99 | 1. press `WIN` + `R` key 100 | 2. type `CMD` in the prompt 101 | 3. press `ENTRY` key 102 | 103 | ![image](./imgs/windows_open_terminal.png) 104 | 105 | ![image](./imgs/windows_terminal.png) 106 | 107 | #### :dart: 1.2.1 Download Nvm 108 | 1. Open a browser and go to [nvm downloads for Windows](https://github.com/coreybutler/nvm-windows/releases) 109 | 2. Download nvm-setup.exe 110 | 111 | ![image](./imgs/window_download_nvm.png) 112 | 113 | #### :dart: 1.2.2 Run exe installation file 114 | Double Click nvm-setup.exe to run 115 | 116 | ![image](./imgs/nvm_1.png) 117 | 118 | ![image](./imgs/nvm_2.png) 119 | 120 | ![image](./imgs/nvm_3.png) 121 | 122 | #### :dart: 1.2.3 Validate nvm installation 123 | ```cmd 124 | nvm version 125 | ``` 126 | An example of the output is: 127 | ``` 128 | 1.1.7 129 | ``` 130 | 131 | ## :star2: 2. Install Node 132 | ### :art: 2.1 Install Node 133 | ``` 134 | nvm install 16 135 | ``` 136 | An example of the output is: 137 | ``` 138 | Downloading node.js version 16.13.1 (64-bit)... 139 | Complete 140 | Creating D:\program\nvm\temp 141 | 142 | Downloading npm version 8.1.2... Complete 143 | Installing npm v8.1.2... 144 | 145 | Installation complete. If you want to use this version, type 146 | 147 | nvm use 16.13.1 148 | ``` 149 | ### :art: 2.2 Select right Node 16 version if not correct 150 | `Node16` is recommended. `Node18` still have issues and is not recommeded right now, 151 | ```cmd 152 | nvm use 16 153 | ``` 154 | An example of the output is: 155 | ``` 156 | Now using node v16.13.1 (64-bit) 157 | ``` 158 | 159 | ### :art: 2.3 Validate if correct node version 160 | ```cmd 161 | node -v 162 | ``` 163 | An example of the output is: 164 | ``` 165 | v16.13.1 166 | ``` 167 | -------------------------------------------------------------------------------- /tutorial/Configure_Wallet.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | logo 4 |

MindLake Tutorial: Configure Wallet

5 | 6 |

7 | A step-by-step cookbook for Wallet Configuration to access Mind Lake ! 8 |

9 |
10 | 11 | 12 | 13 | ## :star2: 0. Step by step tutorial 14 | This is part of support chapter for MindLake step-by-step tutorial for [Typescript](README.md) 15 | 16 | ### :star2: 1. Prepare Wallet 17 | #### :art: 1.1 Install Wallet 18 | 1. Install [MetaMask](https://metamask.io/download/) plugins in Chrome Browser 19 | 2. [Sign up a MetaMask Wallet](https://myterablock.medium.com/how-to-create-or-import-a-metamask-wallet-a551fc2f5a6b) 20 | 3. Change the network to Goerli TestNet. If the TestNets aren't displayed, turn on "Show test networks" in Settings. 21 | 22 | ![MetaMask TestNet](imgs/metamask_testnet_enable.png) 23 | 24 | 4. Change the network to Goerli TestNet 25 | 5. Goerli Faucet for later gas fee if does not have: [Alchemy Goerli Faucet](https://goerlifaucet.com/), [Quicknode Goerli Faucet](https://faucet.quicknode.com/ethereum/goerli), [Moralis Goerli Faucet](https://moralis.io/faucets/) 26 | 27 | ![image](./imgs/change_chain.png) 28 | 29 | #### :art: 1.2 Wallet Sign In: https://scan.mindnetwork.xyz 30 | 1. Open a browser and visit [mind-scan](https://scan.mindnetwork.xyz/scan) 31 | 2. Click "Sign in" buttom 32 | 33 | ![image](./imgs/sign_scan.png) 34 | 35 | 2.1 During the 'Connect' procedure, the wallet will prompt the user 2-3 times as follows: 36 | Sign a nonce for login authentication. 37 | 38 | ![image](./imgs/nounce_sign.png) 39 | 40 | 2.2 If the user's account keys are already on the chain: Decrypt the user's account keys using the wallet's private key. 41 | 42 | ![image](./imgs/decrypt_request.png) 43 | 44 | 2.3 If the user's account keys do not exist yet: Obtain the public key of the wallet, which is used to encrypt the randomly generated account keys. 45 | 46 | ![image](./imgs/request_publickey.png) 47 | 48 | 2.4 Sign the transaction to upload the encrypted key ciphers to the smart contract on the chain. 49 | 50 | ![image](./imgs/upload_chain.png) 51 | 52 | #### :art: 1.3 Register wallets if not in whitelist during testing period 53 | 1. If not in whitelist, there will be a pop-up prompt 54 | 55 | ![image](./imgs/white_list_popup.png) 56 | 57 | 2. Click [Apply for test link ](https://bit.ly/mindalphatest) 58 | 3. After successful application, Please be patient and wait for the review. You will get notification by email. 59 | 60 | -------------------------------------------------------------------------------- /tutorial/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | logo 4 |

MindLake Tutorial: Typescript SDK

5 | 6 |

7 | A step-by-step cookbook for beginner to access Mind Lake ! 8 |

9 |
10 | 11 | 12 | 13 | ## :notebook_with_decorative_cover: Table of Contents 14 | - [:notebook\_with\_decorative\_cover: Table of Contents](#notebook_with_decorative_cover-table-of-contents) 15 | - [:star2: 0. Other Programming Languages](#star2-0-other-programming-languages) 16 | - [:star2: 1. Prepare Wallet](#star2-1-prepare-wallet) 17 | - [:star2: 2. Choose a Method to Explore MindLake SDK](#star2-2-choose-a-method-to-explore-mindlake-sdk) 18 | - [:art: 2.1 Option 1: Use dApp Page Hosted on Arweave](#art-21-option-1-use-dapp-page-hosted-on-arweave) 19 | - [:art: 2.2 Option 2: Use Docker Image](#art-22-option-2-use-docker-image) 20 | - [:art: 2.3 Option 3: Compile with Node.js](#art-23-option-3-compile-with-nodejs) 21 | - [:star2: 3. Install Or Upgrade Node If Needed (for Option 3)](#star2-3-install-or-upgrade-node-if-needed-for-option-3) 22 | - [:star2: 4. Get Examples sourcecode and compile locally (for Option 3)](#star2-4-get-examples-sourcecode-and-compile-locally-for-option-3) 23 | - [:star2: 5. Prepare myconfig.ts (for Option 3)](#star2-5-prepare-myconfigts-for-option-3) 24 | - [:star2: 6. Execute the examples](#star2-6-execute-the-examples) 25 | - [:art: 6.1 QuickStart](#art-61-quickstart) 26 | - [:art: 6.2 Use Case 1: Single User with Structured Data](#art-62-use-case-1-single-user-with-structured-data) 27 | - [:art: 6.3 Use Case 2: Single User with Unstructured Data](#art-63-use-case-2-single-user-with-unstructured-data) 28 | - [:art: 6.4 Use Case 3: Multi Users with Permission Sharing](#art-64-use-case-3-multi-users-with-permission-sharing) 29 | 30 | 31 | 32 | 33 | 34 | ## :star2: 0. Other Programming Languages 35 | - [Python](https://github.com/mind-network/mind-lake-sdk-python/) 36 | 37 | ## :star2: 1. Prepare Wallet 38 | - Click [:star2: 1. Prepare Wallet](Configure_Wallet.md) to get your wallet ready for the use cases. 39 | 40 | ## :star2: 2. Choose a Method to Explore MindLake SDK 41 | In this tutorial, we provide 3 options to explore MindLake SDK with 3 use cases. 42 | All options has same dapp code but host in different ways. 43 | Here is the overall architecture: 44 | ![image](./imgs/tutorial-architecture.jpeg) 45 | 46 | 47 | ### :art: 2.1 Option 1: Use dApp Page Hosted on Arweave 48 | For beginners without tech background, we recommend to access the already compiled dApp page hosted on Arweave to have a quick glance at the use cases. You can jump to [:art: 6.2 Use Case 1](#art-62-use-case-1-single-user-with-structured-data) 49 | 50 | ### :art: 2.2 Option 2: Use Docker Image 51 | For developers who don't want to install Node.js, we recommend to use the docker image to run the examples. You can click [Use Docker Image](Configure_Docker_Case.md). 52 | 53 | ### :art: 2.3 Option 3: Compile with Node.js 54 | For developers who want to explore the source code, we recommend to compile the use cases with Node.js and run the examples. You can continue to read the following sections. 55 | 56 | 57 | ## :star2: 3. Install Or Upgrade Node If Needed (for Option 3) 58 | - If you are following Option 2 and Option 3, you can ignore this steps as envrioment is pre-configured. 59 | - If you are following Option 3 to compile sourcecode locally, please click to view [step-by-step to configure Node](Configure_Node.md) if Node is not installed or upgraded 60 | 61 | ## :star2: 4. Get Examples sourcecode and compile locally (for Option 3) 62 | - If you are following Option 2 and Option 3, you can ignore this steps as sourcecode are compiled and can run in your browser directly. 63 | - If you are following Option 3 to compile sourcecode locally, please follow bellow steps to build locally. 64 | 1. Enter the following command in the terminal window to fetch the example code from github: 65 | ```shell 66 | git clone https://github.com/mind-network/mind-lake-sdk-typescript.git 67 | ``` 68 | 1. Enter the path of example code: 69 | ```shell 70 | cd mind-lake-sdk-typescript/examples 71 | ``` 72 | 1. Install depedency 73 | ```cmd 74 | npm install 75 | ``` 76 | 1. Check mind-lake-sdk depedency 77 | ```cmd 78 | npm info mind-lake-sdk version 79 | ``` 80 | An example of the output is: 81 | ```cmd 82 | 1.0.2 83 | ``` 84 | 85 | ## :star2: 5. Prepare myconfig.ts (for Option 3) 86 | - If you are following Option 2 and Option 3, you can ignore this steps as configuration is done and can run in your browser directly. 87 | - If you are following Option 3 to compile sourcecode locally, please follow bellow steps to configure locally. 88 | 89 | 1. `myconfig.ts` contains the settings of parameters used in examples and use cases, you can copy `myconfig_template.ts` to the name `myconfig.ts` and modify it as per your requirement. 90 | 2. `myconfig.ts` will need walletAddress and appKey. 91 | 3. If you want to run the examples of quickStart, Use Case 1 and Use Case 2, you only need to fill out `appKey`. You can click [Create Mind appKey](Configure_AppKey.md). 92 | 93 | 4. If you want to run Use Case 3, you need to fill out the wallets info for all of `Alice`, `Bob` and `Charlie`. 94 | - You can click [:star2: 1. Prepare Wallet](Configure_Wallet.md) again if you haven't get all the 3 wallets ready. 95 | 96 | ``` 97 | export const appKey = "YOUR_APP_KEY"; 98 | export const nodeUrl = "https://sdk.mindnetwork.xyz"; // or change to other node url 99 | export const aliceWalletAddress = "Alice_Wallet_Address"; 100 | export const bobWalletAddress = "Bob_Wallet_Address"; 101 | export const charlieWalletAddress = "Charlie_Wallet_Address"; 102 | ``` 103 | 104 | ## :star2: 6. Execute the examples 105 | If you are following Option 3, You can execute the following commands to run the quickstart and use cases. 106 | ``` 107 | cd examples 108 | npm run start 109 | ``` 110 | An example of the output is: 111 | ``` 112 | App running at: 113 | - Local: http://localhost:8002 (copied to clipboard) 114 | - Network: http://192.168.137.1:8002 115 | ``` 116 | By default use 8000 as port number. But will auto increase port number if 8000 is used. You may see 8001 or other incremental in your side. The example bellow 8002. Use the port number shown in your terminal. 117 | Open a browser and visit `http://localhost:8002` 118 | 119 | For bellow steps, we have list the command and screenshot in Option 3 by default, but also list the urls in Option 1 and Option 2. 120 | 121 | ### :art: 6.1 QuickStart 122 | 123 | 1. First, you should create a test wallet for test. 124 | 125 | ![image](./imgs/create_wallet.png) 126 | 127 | ![image](./imgs/create_wallet_confirm.png) 128 | 129 | 2. Click "Quick start with your MetaMask" and you will see the logs while login with your MetaMask wallet 130 | 131 | ![image](./imgs/quickStart.png) 132 | 133 | ![image](./imgs/quickStart_logout.png) 134 | 135 | ### :art: 6.2 Use Case 1: Single User with Structured Data 136 | 1. Open a browser and visit `http://localhost:8002/use_case_1` 137 | - For Option 1, we hosted a same page on Arweave: https://arweave.net/ElzjXMVWIUMavlUbBhXHSl5JopcbL3H_E_0KehsMfPg 138 | - For Option 2, you may see docker instance and visit your browser in http://localhost:90/use_case_1.html 139 | 2. Click "Test case one with your MetaMask" and you will see the logs while executing Use Case 1. 140 | 141 | ![image](./imgs/case_2_logout.png) 142 | 143 | ### :art: 6.3 Use Case 2: Single User with Unstructured Data 144 | 1. Open a browser and visit `http://localhost:8002/use_case_2` 145 | - For Option 1, we hosted a same page on Arweave: https://arweave.net/jvf-v7VImOAQ67uzG1CCGqZuVPXGvIE1S-l8MO3A75U 146 | - For Option 2, you may see docker instance and visit your browser in http://localhost:91/use_case_2.html 147 | 2. Click "Test case two with your MetaMask" and you will see the logs while executing Use Case 2 148 | 149 | ![image](./imgs/case_2.png) 150 | 151 | ### :art: 6.4 Use Case 3: Multi Users with Permission Sharing 152 | 1. You will need 3 wallets for Use Case 3: Alice, Bob, Charlie 153 | We show how to create a wallet for Alice for testing purpose. 154 | 155 | ![image](./imgs/create_wallet.png) 156 | 157 | ![image](./imgs/create_wallet_confirm.png) 158 | 159 | Using the same way to create wallet for Bob and Charlie 160 | 161 | Copy Alice,Bob,Charlie' wallet address into myconfig.ts to update `aliceWalletAddress` `bobWalletAddress` `charlieWalletAddress` 162 | 163 | > **Note** 164 | > During testing period, please make you have all wallet address are registered in into whitelist: https://sites.google.com/mindnetwork.xyz/mindnetwork/alpha-test. 165 | > Be more specific, please ensure your Alice, Bob and Charlie are registered and approved. Otherwise, you may experience the error on sharing. 166 | 167 | 168 | 2. Open a browser and visit `http://localhost:8002/use_case_3` 169 | - For Option 1, we hosted a same page on Arweave: https://arweave.net/QR03v_GcZlEnbuny9e5kb149WL4E2_gFe8pbFOMExbE 170 | - For Option 2, you may see docker instance and visit your browser in http://localhost:92/use_case_3.html 171 | 172 | ![image](./imgs/case_3.png) 173 | 174 | 1. Switch to Alice's wallet and perform actions as Alice 175 | 176 | ![image](./imgs/change_wallet_alice.png) 177 | 178 | ![image](./imgs/alice_connect.png) 179 | 180 | Click "Insert Alice Data And Share To Charlie" 181 | 182 | ![image](./imgs/insert_data_done.png) 183 | 184 | Wait until "insert data done" appears which means data insertion is completely. 185 | 186 | 4. Switch to BOb's wallet and performa actions as Bob 187 | 188 | ![image](./imgs/change_wallet_bob.png) 189 | 190 | ![image](./imgs/bob_connect.png) 191 | 192 | Click "Insert Bob Data And Share To Charlie" 193 | 194 | ![image](./imgs/insert_data_done.png) 195 | 196 | Wait until "insert data done" appears which means data insertion is completely. 197 | 198 | 5. Switch to Charlie's wallet and performa actions as Charlie 199 | 200 | ![image](./imgs/change_wallet_charlie.png) 201 | 202 | ![image](./imgs/charlie_connect.png) 203 | 204 | Click "Charlie Select Data And Decrypt Data" 205 | 206 | ![image](./imgs/charlie_out_put.png) 207 | 208 | 6. Final output quick view 209 | 210 | ![image](./imgs/case_3_result.png) 211 | 212 | 213 | -------------------------------------------------------------------------------- /tutorial/imgs/alice_connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/alice_connect.png -------------------------------------------------------------------------------- /tutorial/imgs/bob_connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/bob_connect.png -------------------------------------------------------------------------------- /tutorial/imgs/brew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/brew.png -------------------------------------------------------------------------------- /tutorial/imgs/case_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/case_2.png -------------------------------------------------------------------------------- /tutorial/imgs/case_2_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/case_2_logout.png -------------------------------------------------------------------------------- /tutorial/imgs/case_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/case_3.png -------------------------------------------------------------------------------- /tutorial/imgs/case_3_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/case_3_result.png -------------------------------------------------------------------------------- /tutorial/imgs/change_chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/change_chain.png -------------------------------------------------------------------------------- /tutorial/imgs/change_wallet_alice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/change_wallet_alice.png -------------------------------------------------------------------------------- /tutorial/imgs/change_wallet_bob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/change_wallet_bob.png -------------------------------------------------------------------------------- /tutorial/imgs/change_wallet_charlie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/change_wallet_charlie.png -------------------------------------------------------------------------------- /tutorial/imgs/charlie_connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/charlie_connect.png -------------------------------------------------------------------------------- /tutorial/imgs/charlie_out_put.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/charlie_out_put.png -------------------------------------------------------------------------------- /tutorial/imgs/create_dapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/create_dapp.png -------------------------------------------------------------------------------- /tutorial/imgs/create_dapp_confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/create_dapp_confirm.png -------------------------------------------------------------------------------- /tutorial/imgs/create_wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/create_wallet.png -------------------------------------------------------------------------------- /tutorial/imgs/create_wallet_confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/create_wallet_confirm.png -------------------------------------------------------------------------------- /tutorial/imgs/dapp_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/dapp_list.png -------------------------------------------------------------------------------- /tutorial/imgs/decrypt_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/decrypt_request.png -------------------------------------------------------------------------------- /tutorial/imgs/docker-app-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker-app-search.png -------------------------------------------------------------------------------- /tutorial/imgs/docker_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_app.png -------------------------------------------------------------------------------- /tutorial/imgs/docker_app_top.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_app_top.webp -------------------------------------------------------------------------------- /tutorial/imgs/docker_bac.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_bac.jpeg -------------------------------------------------------------------------------- /tutorial/imgs/docker_bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_bak.png -------------------------------------------------------------------------------- /tutorial/imgs/docker_case1_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_case1_img.png -------------------------------------------------------------------------------- /tutorial/imgs/docker_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_info.png -------------------------------------------------------------------------------- /tutorial/imgs/docker_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_run.png -------------------------------------------------------------------------------- /tutorial/imgs/docker_run1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_run1.png -------------------------------------------------------------------------------- /tutorial/imgs/docker_run2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_run2.png -------------------------------------------------------------------------------- /tutorial/imgs/docker_win_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_win_download.png -------------------------------------------------------------------------------- /tutorial/imgs/docker_windows_win.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/docker_windows_win.jpeg -------------------------------------------------------------------------------- /tutorial/imgs/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/img.png -------------------------------------------------------------------------------- /tutorial/imgs/insert_data_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/insert_data_done.png -------------------------------------------------------------------------------- /tutorial/imgs/install_brew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/install_brew.png -------------------------------------------------------------------------------- /tutorial/imgs/metamask_testnet_enable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/metamask_testnet_enable.png -------------------------------------------------------------------------------- /tutorial/imgs/myDapp_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/myDapp_menu.png -------------------------------------------------------------------------------- /tutorial/imgs/nounce_sign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/nounce_sign.png -------------------------------------------------------------------------------- /tutorial/imgs/nvm_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/nvm_1.png -------------------------------------------------------------------------------- /tutorial/imgs/nvm_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/nvm_2.png -------------------------------------------------------------------------------- /tutorial/imgs/nvm_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/nvm_3.png -------------------------------------------------------------------------------- /tutorial/imgs/open_mac_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/open_mac_terminal.png -------------------------------------------------------------------------------- /tutorial/imgs/quickStart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/quickStart.png -------------------------------------------------------------------------------- /tutorial/imgs/quickStart_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/quickStart_logout.png -------------------------------------------------------------------------------- /tutorial/imgs/request_publickey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/request_publickey.png -------------------------------------------------------------------------------- /tutorial/imgs/sign_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/sign_scan.png -------------------------------------------------------------------------------- /tutorial/imgs/tutorial-architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/tutorial-architecture.jpeg -------------------------------------------------------------------------------- /tutorial/imgs/tutorial-architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/tutorial-architecture.jpg -------------------------------------------------------------------------------- /tutorial/imgs/upload_chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/upload_chain.png -------------------------------------------------------------------------------- /tutorial/imgs/white_list_popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/white_list_popup.png -------------------------------------------------------------------------------- /tutorial/imgs/window_download_nvm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/window_download_nvm.png -------------------------------------------------------------------------------- /tutorial/imgs/windows_open_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/windows_open_terminal.png -------------------------------------------------------------------------------- /tutorial/imgs/windows_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind-network/mind-lake-sdk-typescript/694eacad028d781032eef19cb6c9faaa204fa308/tutorial/imgs/windows_terminal.png --------------------------------------------------------------------------------