The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .eslintrc.js
├── .github
    ├── ISSUE_TEMPLATE
    │   ├── bug-report.md
    │   ├── code-quality.md
    │   ├── feature-request.md
    │   └── ui-update.md
    └── workflows
    │   ├── build.yml
    │   ├── main.yml
    │   └── publish-npm.yml
├── .gitignore
├── .yarnrc.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bin
    └── nit.js
├── example
    ├── hello.js
    ├── hello.ts
    └── mocks
    │   ├── action.json
    │   ├── assetTree.json
    │   ├── commit.json
    │   ├── identityAssetCreator.json
    │   ├── identityProofProvider.json
    │   ├── identityServiceProvider.json
    │   ├── proof.json
    │   └── proofMetadata.json
├── package-lock.json
├── package.json
├── schema
    └── dbml.md
├── scripts
    └── parseTxNid.ts
├── src
    ├── action.ts
    ├── commitdb.ts
    ├── contract.ts
    ├── http.ts
    ├── ipfs.ts
    ├── license.ts
    ├── nit.ts
    ├── run.ts
    └── util.ts
├── tests
    ├── testAction.ts
    ├── testHttp.ts
    ├── testIpfs.ts
    └── testNit.ts
├── tsconfig.json
└── yarn.lock


/.eslintrc.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |     "env": {
 3 |         "browser": true,
 4 |         "es2021": true
 5 |     },
 6 |     "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
 7 |     "parser": "@typescript-eslint/parser",
 8 |     "parserOptions": {
 9 |         "ecmaVersion": "latest",
10 |         "sourceType": "module"
11 |     },
12 |     "plugins": ["@typescript-eslint"],
13 |     "root": true,
14 | }


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Bug Report
 3 | about: Create a report to help us improve
 4 | title: ''
 5 | labels: bug
 6 | assignees: ''
 7 | ---
 8 | 
 9 | ## Description
10 | 
11 | A clear and concise description of what the bug is.
12 | 
13 | ![bugs found](https://media.giphy.com/media/UAUtB4Oi9U4EM/giphy.gif)
14 | 
15 | ## Steps to Reproduce
16 | 
17 | 1. Go to '...'
18 | 1. Click on '....'
19 | 1. Scroll down to '....'
20 | 1. See error
21 | 
22 | ## Expected Behavior
23 | 
24 | - Expected: A clear and concise description of what you expected to happen.
25 | - Actual: A clear and concise description of what actually happened.
26 | 
27 | ## Logs
28 | 
29 | If applicable, add logs or screenshots to help explain your problem.
30 | 
31 | ## Environment
32 | 
33 | - Version: _e.g. 1.3.0, beta2, etc._
34 | - OS: _e.g. Ubuntu 20.04, Android 10, iOS 13, etc._
35 | - Device: _e.g. HTC Exodus 1_
36 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/code-quality.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Code Quality
 3 | about: Improve code quality or project architecture
 4 | title: ''
 5 | labels: code
 6 | assignees: ''
 7 | ---
 8 | 
 9 | ## Suggestion
10 | 
11 | The content of the suggestion.
12 | 
13 | ![write codes](https://media.giphy.com/media/LmNwrBhejkK9EFP504/giphy.gif)
14 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Feature Request
 3 | about: Suggest an idea for this project
 4 | title: ''
 5 | labels: enhancement
 6 | assignees: ''
 7 | ---
 8 | 
 9 | ## Concept
10 | 
11 | If we provide \[feature]
12 | 
13 | ## Reason/Hypothesis
14 | 
15 | We can help \[who] to \[benefit] or solve \[problem] because we know \[who] are \[faced about our target].
16 | 
17 | ## More Description (if any)
18 | 
19 | Put more description here to support the concept.
20 | 
21 | ## Suggested Implementation (if any)
22 | 
23 | Put some suggested implementation here if you already have some ideas in mind.
24 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/ui-update.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: UI Update
 3 | about: Create a new UI update
 4 | title: ''
 5 | labels: uiux
 6 | assignees: ''
 7 | ---
 8 | 
 9 | ## Update Purpose
10 | 
11 | Write the purpose of UI update here.
12 | 
13 | ![css](https://media.giphy.com/media/yYSSBtDgbbRzq/giphy.gif)
14 | 
15 | ## UI Suggestion
16 | 
17 | Put images or link to the contents of suggestions.
18 | 
19 | ## More Description (if any)
20 | 
21 | Put more description here to help developers understand.
22 | 


--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
 3 | 
 4 | name: Build npm package
 5 | 
 6 | on:
 7 |   push:
 8 |     branches:
 9 |       - 'feature-*'
10 |       - 'fix-*'
11 |   pull_request:
12 |     branches:
13 |       - 'main'
14 | 
15 | jobs:
16 |   build:
17 |     runs-on: ubuntu-latest
18 | 
19 |     strategy:
20 |       matrix:
21 |         node-version: [16.x, 18.x, 20.x]
22 |         # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
23 | 
24 |     steps:
25 |       - uses: actions/checkout@v3
26 |       - name: Use Node.js ${{ matrix.node-version }}
27 |         uses: actions/setup-node@v3
28 |         with:
29 |           node-version: ${{ matrix.node-version }}
30 |           cache: 'yarn'
31 |       - run: yarn install
32 |       - run: yarn build
33 | 


--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
 1 | name: Add Issues to Backlog Project
 2 | 
 3 | on:
 4 |   issues:
 5 |     types:
 6 |       - opened
 7 | 
 8 | jobs:
 9 |   add-to-project:
10 |     name: Add issue to Backlog project
11 |     runs-on: ubuntu-latest
12 |     steps:
13 |       - name: Add issue to Backlog project
14 |         uses: actions/add-to-project@v0.5.0
15 |         with:
16 |           project-url: https://github.com/orgs/numbersprotocol/projects/8
17 |           # OR for repository projects:
18 |           # project-url: https://github.com/users/YOUR_USERNAME/projects/YOUR_PROJECT_NUMBER
19 |           # OR for user projects:
20 |           # project-url: https://github.com/users/YOUR_USERNAME/projects/YOUR_PROJECT_NUMBER
21 |           github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}


--------------------------------------------------------------------------------
/.github/workflows/publish-npm.yml:
--------------------------------------------------------------------------------
 1 | # This workflow will build and publish a package to npmjs when a version release is created
 2 | # https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages#publishing-packages-using-yarn
 3 | 
 4 | name: Publish Package to npmjs
 5 | 
 6 | on:
 7 |   push:
 8 |     tags:
 9 |       - 'v[0-9]+.[0-9]+.[0-9]+*'
10 | 
11 | env:
12 |   NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
13 | 
14 | jobs:
15 |   build:
16 |     runs-on: ubuntu-latest
17 |     permissions:
18 |       contents: read
19 |       packages: write
20 |     steps:
21 |       - name: Checkout code
22 |         uses: actions/checkout@v3
23 | 
24 |       - name: Setup .npmrc file to publish to npm
25 |         uses: actions/setup-node@v3
26 |         with:
27 |           node-version: 16
28 |           registry-url: 'https://registry.npmjs.org'
29 |           # Defaults to the user or organization that owns the workflow file
30 |           scope: '@numbersprotocol'
31 | 
32 |       - name: Install dependencies
33 |         run: yarn install
34 | 
35 |       - name: Build project
36 |         run: yarn build
37 | 
38 |       - name: Publish to npm
39 |         run: yarn publish --no-git-tag-version
40 |         env:
41 |           NODE_AUTH_TOKEN: ${{ env.NODE_AUTH_TOKEN }}


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
  1 | # Logs
  2 | logs
  3 | *.log
  4 | npm-debug.log*
  5 | yarn-debug.log*
  6 | yarn-error.log*
  7 | lerna-debug.log*
  8 | .pnpm-debug.log*
  9 | 
 10 | # Diagnostic reports (https://nodejs.org/api/report.html)
 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
 12 | 
 13 | # Runtime data
 14 | pids
 15 | *.pid
 16 | *.seed
 17 | *.pid.lock
 18 | 
 19 | # Directory for instrumented libs generated by jscoverage/JSCover
 20 | lib-cov
 21 | 
 22 | # Coverage directory used by tools like istanbul
 23 | coverage
 24 | *.lcov
 25 | 
 26 | # nyc test coverage
 27 | .nyc_output
 28 | 
 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 30 | .grunt
 31 | 
 32 | # Bower dependency directory (https://bower.io/)
 33 | bower_components
 34 | 
 35 | # node-waf configuration
 36 | .lock-wscript
 37 | 
 38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 39 | build/Release
 40 | 
 41 | # Dependency directories
 42 | node_modules/
 43 | jspm_packages/
 44 | 
 45 | # Snowpack dependency directory (https://snowpack.dev/)
 46 | web_modules/
 47 | 
 48 | # TypeScript cache
 49 | *.tsbuildinfo
 50 | 
 51 | # Optional npm cache directory
 52 | .npm
 53 | 
 54 | # Optional eslint cache
 55 | .eslintcache
 56 | 
 57 | # Optional stylelint cache
 58 | .stylelintcache
 59 | 
 60 | # Microbundle cache
 61 | .rpt2_cache/
 62 | .rts2_cache_cjs/
 63 | .rts2_cache_es/
 64 | .rts2_cache_umd/
 65 | 
 66 | # Optional REPL history
 67 | .node_repl_history
 68 | 
 69 | # Output of 'npm pack'
 70 | *.tgz
 71 | 
 72 | # Yarn Integrity file
 73 | .yarn-integrity
 74 | 
 75 | # dotenv environment variable files
 76 | .env
 77 | .env.development.local
 78 | .env.test.local
 79 | .env.production.local
 80 | .env.local
 81 | 
 82 | # parcel-bundler cache (https://parceljs.org/)
 83 | .cache
 84 | .parcel-cache
 85 | 
 86 | # Next.js build output
 87 | .next
 88 | out
 89 | 
 90 | # Nuxt.js build / generate output
 91 | .nuxt
 92 | dist
 93 | 
 94 | # Gatsby files
 95 | .cache/
 96 | # Comment in the public line in if your project uses Gatsby and not Next.js
 97 | # https://nextjs.org/blog/next-9-1#public-directory-support
 98 | # public
 99 | 
100 | # vuepress build output
101 | .vuepress/dist
102 | 
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 | 
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 | 
110 | # Serverless directories
111 | .serverless/
112 | 
113 | # FuseBox cache
114 | .fusebox/
115 | 
116 | # DynamoDB Local files
117 | .dynamodb/
118 | 
119 | # TernJS port file
120 | .tern-port
121 | 
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 | 
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 | 


--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 | 
3 | plugins:
4 |   - path: .yarn/plugins/@yarnpkg/plugin-version.cjs
5 |     spec: "@yarnpkg/plugin-version"
6 | 


--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
 1 | # Changelog
 2 | 
 3 | All notable changes to this project will be documented in this file.
 4 | 
 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 7 | 
 8 | ## [Unreleased]
 9 | 
10 | To check the difference between the last releaes and the latest dev status, click the link above.
11 | 
12 | ## [1.6.1] - 2024-01-11
13 | 
14 | ### Fixed
15 | 
16 | 1. Commit will wait until transaction is mined. (#29)
17 | 
18 | ## [1.6.0] - 2023-10-03
19 | 
20 | ### Added
21 | 
22 | 1. Add initial registration action for IOTA.
23 | 
24 | ## [1.5.0] - 2023-06-15
25 | 
26 | ### Changed
27 | 
28 | 1. [PR26](https://github.com/numbersprotocol/nit/pull/26): Remove default license from Asset Tree
29 | 1. [PR27](https://github.com/numbersprotocol/nit/pull/27): Remove default empty abstract without input
30 | 
31 | ## [1.4.0] - 2023-05-02
32 | 
33 | ### Added
34 | 
35 | 1. Add [Action Names](https://docs.numbersprotocol.io/introduction/numbers-protocol/defining-web3-assets/commit#actions)
36 | 
37 | ### Changed
38 | 
39 | 1. Add IPFS abstract layer to support multiple IPFS backends
40 | 
41 | ## [1.3.0] - 2023-02-14
42 | 
43 | ### Added
44 | 
45 | 1. Add action action-mint-erc721-nft-jade
46 | 2. Add Numbers networks into config template
47 | 2. Add ESLint
48 | 
49 | ### Changed
50 | 
51 | 1. Remove not-yet-supported networks from the network dictionary
52 | 
53 | ## [1.2.9] - 2023-01-28
54 | 
55 | ### Fixed
56 | 
57 | 1. Change default network in registration action from Avalanche to Jade
58 | 
59 | [unreleased]: https://github.com/numbersprotocol/capture-lite/compare/1.6.1...HEAD
60 | [1.6.1]: https://github.com/numbersprotocol/capture-lite/compare/1.6.0...1.6.1
61 | [1.6.0]: https://github.com/numbersprotocol/capture-lite/compare/1.5.0...1.6.0
62 | [1.5.0]: https://github.com/numbersprotocol/capture-lite/compare/1.4.0...1.5.0
63 | [1.4.0]: https://github.com/numbersprotocol/capture-lite/compare/1.3.0...1.4.0
64 | [1.3.0]: https://github.com/numbersprotocol/capture-lite/compare/1.2.9...1.3.0
65 | [1.2.9]: https://github.com/numbersprotocol/capture-lite/releases/tag/1.2.9


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Permission is hereby granted, free of charge, to any person obtaining a copy
 4 | of this software and associated documentation files (the "Software"), to deal
 5 | in the Software without restriction, including without limitation the rights
 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 7 | copies of the Software, and to permit persons to whom the Software is
 8 | furnished to do so, subject to the following conditions:
 9 | 
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 | 
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | # Nit
  2 | 
  3 | Nit is git for web3 digital asset and helps user to commit an asset's activities (chronicle) to blockchain. Please note, this repository is for nit open-source tools. If you are looking for accessing nit via API services, please visit [here](https://docs.numbersprotocol.io/developers/commit-asset-history/commit-via-api) for more details.
  4 | 
  5 | ## Why Nit
  6 | 
  7 | To make digital assets trustworthy and traceable, Nit leverages web3 technologies and Git core concepts. Everyone can chronicle their assets by creating on-chain records so that we can productively debate, narrate, and analyse. All asset histories are written on chain and are searchable by asset CID. A sample transaction can be found [here](https://snowtrace.io/tx/0x3ba2c36f7b0aeefc954041899a099c228e052a791a59f9922ab53ef9630f4a87).
  8 | 
  9 | Case study
 10 | 
 11 | 1. [A crypto-based dossier could help prove Russia committed war crimes](https://edition.cnn.com/2022/06/10/tech/ukraine-war-crimes-blockchain/index.html), CNN
 12 | 2. [Starling Lab and Hala Systems file Cryptographic Submission of Evidence of War Crimes in Ukraine to the International Criminal Court](https://dornsife.usc.edu/cagr-news/news/2022/06/33571-starling-lab-and-hala-systems-file-cryptographic-submission-evidence-war-crimes), CAGR
 13 | 
 14 | ## Commit & Asset Tree
 15 | 
 16 | nit adopt similar design as git. 
 17 | 
 18 | * When there is an update to the asset, such as updating creator information or updating content itself to create a child asset, there should be a new commit attach the asset itself.
 19 | 
 20 | * Every asset has a Tree file in IPFS to describe the property of the asset, including creator, creation time, license, etc. The asset tree CID is included in the on-chain message. Here is the example of the [asset tree](https://bafkreigbl7262jgwykk6ce47gbzvh4udr3rtzkpgd3b465664gzxma6zfi.ipfs.dweb.link/). 
 21 | 
 22 | The  db diagram can be found [here](https://dbdiagram.io/d/6220e69c54f9ad109a54c3a5). In this diagram, you will find tables of `commit` and `assetTree` with the explanation of each data field
 23 | 
 24 | ## Installation
 25 | 
 26 | ```shell
 27 | yarn global add @numbersprotocol/nit
 28 | ```
 29 | 
 30 | ## Example: Initial Registration
 31 | 
 32 | If it's your first time to run `nit`, initialize Nit with default configuration:
 33 | 
 34 | ```
 35 | nit init
 36 | ```
 37 | 
 38 | and provide personal information. The details of the Nit configuration format is in the [Configuration](#configuration) section below.
 39 | 
 40 | ```
 41 | nit config -e
 42 | ```
 43 | 
 44 | The default integrity register (smart contract) is running on Avalanche Fuji (Testnet) and will migrate to Numbers mainnet in the future. You need to get some testing tokens from the [Fuji faucet](https://faucet.avax-test.network) for paying transaction fee.
 45 | 
 46 | Create a working directory for the targeting asset:
 47 | 
 48 | ```
 49 | mkdir ~/temp && cd ~/temp
 50 | ```
 51 | 
 52 | Create the Asset Tree of the targeting asset. Example:
 53 | 
 54 | ```
 55 | nit add <asset-filepath> -m "First registration."
 56 | ```
 57 | 
 58 | To access the file of the CID, you can use browser to open the URL: `https://<cid>.ipfs.dweb.link`
 59 | 
 60 | Double check the Commit information meets your expectation:
 61 | 
 62 | ```
 63 | nit status
 64 | ```
 65 | 
 66 | Register the Commit on blockchain:
 67 | 
 68 | ```
 69 | nit commit -m "commit description"
 70 | ```
 71 | 
 72 | You can check the Commit on blockchain by following the explorer URL returned by Nit.
 73 | 
 74 | ## Get On-Chain Information
 75 | 
 76 | ### Get on-chain block numbers of an Asset
 77 | 
 78 | ```shell
 79 | nit log <asset-cid> --blocks
 80 | ```
 81 | 
 82 | Example command of the mockup Asset
 83 | 
 84 | ```shell
 85 | nit log aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --blocks
 86 | ```
 87 | 
 88 | Commit block numbers and indices
 89 | 
 90 | ```
 91 | Total Commit number: 74
 92 | Index: 0, Block number: 7236185
 93 | Index: 1, Block number: 7236445
 94 | ...
 95 | Index: 71, Block number: 10849040
 96 | Index: 72, Block number: 10849133
 97 | Index: 73, Block number: 11040035
 98 | ```
 99 | 
100 | ### Get Commits of an Asset
101 | 
102 | You can specify the starting and ending indices of block numbers:
103 | 
104 | ```shell
105 | nit log <asset-cid> --from-index 3 --to-index 5
106 | ```
107 | 
108 | You will get the Commits from the block numbers associated to index 3 & 4.
109 | 
110 | Example command of the mockup Asset
111 | 
112 | ```shell
113 | nit log aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --from-index 71 --to-index 73
114 | ```
115 | 
116 | <details>
117 | <summary>Commits in block 71 & 72 (exclude block 73)</summary>
118 | 
119 | ```shell
120 | Total Commit number: 74
121 | 
122 | block number: 10849040
123 | tx: 0x6d5902173255afe379cc4ae934a6c684ecfd865679286665622de3cf10eddcbe
124 | {
125 |   "assetTreeCid": "bafkreifnpykuw5g2m4k5k5wf55zxtzjmcftstzhtsarlkqytimajj3ntlq",
126 |   "assetTreeSha256": "ad7e154b74da6715d576c5ef7379e52c116729e4f39022b54313430094edb35c",
127 |   "assetTreeSignature": "0x9faf5c9d13b8d90a7a8e88aa6daf089ca89593c28dc241347c4756e83c2f1ea53ed1cb9e189f7ab81c81327527c97595f44ed71dda8e5d78ebe0dccfe9dd27081c",
128 |   "author": "bafkreigzixvzu2tbxbvmvwcvlz2zwoagmb6c2q5egaq4lmd5sesyopmmx4",
129 |   "committer": "bafkreigzixvzu2tbxbvmvwcvlz2zwoagmb6c2q5egaq4lmd5sesyopmmx4",
130 |   "provider": "bafkreigtmno2wacf4ldb6ewbkboe7oojedsp3dky35djytor6guwzhbdpa",
131 |   "timestampCreated": 1655720482,
132 |   "action": "bafkreiavifzn7ntlb6p2lr55k3oezo6pofwvknecukc5auhng4miowcte4",
133 |   "actionResult": "https://bafkreifnpykuw5g2m4k5k5wf55zxtzjmcftstzhtsarlkqytimajj3ntlq.ipfs.dweb.link",
134 |   "abstract": "Action action-initial-registration."
135 | }
136 | 
137 | block number: 10849133
138 | tx: 0xe383fdc0f4eaf44e8bde4737f33bcd45742dcb846f3beb890976793d7cc9358e
139 | {
140 |   "assetTreeCid": "bafkreidptwydheqfybug4mmnzwzdg4rqxjvg4akl2pwjmrfxhqz33qv4tu",
141 |   "assetTreeSha256": "6f9db0339205c0686e318dcdb2337230ba6a6e014bd3ec9644b73c33bdc2bc9d",
142 |   "assetTreeSignature": "0xef547e124a9904dbdb5a418022ad03c621201b74111a3b4c5fac61b1d82350170766cef8a27737d21ca9b1bd4e04f7cdea460706b68b14e0ed17f2a3de83f9131b",
143 |   "author": "bafkreigzixvzu2tbxbvmvwcvlz2zwoagmb6c2q5egaq4lmd5sesyopmmx4",
144 |   "committer": "bafkreigzixvzu2tbxbvmvwcvlz2zwoagmb6c2q5egaq4lmd5sesyopmmx4",
145 |   "provider": "bafkreigtmno2wacf4ldb6ewbkboe7oojedsp3dky35djytor6guwzhbdpa",
146 |   "timestampCreated": 1655720763,
147 |   "action": "bafkreiavifzn7ntlb6p2lr55k3oezo6pofwvknecukc5auhng4miowcte4",
148 |   "actionResult": "https://bafkreidptwydheqfybug4mmnzwzdg4rqxjvg4akl2pwjmrfxhqz33qv4tu.ipfs.dweb.link",
149 |   "abstract": "Action action-initial-registration."
150 | }
151 | ```
152 | 
153 | </details>
154 | 
155 | ### Get difference of Commits and Asset Trees of an Asset
156 | 
157 | ```shell
158 | nit diff <asset-cid> --from-index 3 --to-index 5
159 | ```
160 | 
161 | Example command of the mockup Asset
162 | 
163 | ```shell
164 | nit diff aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --from-index 71 --to-index 73
165 | ```
166 | 
167 | <details>
168 | <summary>Diff of Commits and Asset Trees in block 71 & 72 (exclude block 73)</summary>
169 | 
170 | ```shell
171 | from: block 10849040, tx 0x6d5902173255afe379cc4ae934a6c684ecfd865679286665622de3cf10eddcbe
172 |   to: block 10849133, tx 0xe383fdc0f4eaf44e8bde4737f33bcd45742dcb846f3beb890976793d7cc9358e
173 | 
174 | Commit difference
175 | {
176 |   "abstract": "Action action-initial-registration.",
177 |   "action": "bafkreiavifzn7ntlb6p2lr55k3oezo6pofwvknecukc5auhng4miowcte4",
178 |  -"actionResult": "https://bafkreifnpykuw5g2m4k5k5wf55zxtzjmcftstzhtsarlkqytimajj3ntlq.ipfs.dweb.link",
179 |  -"assetTreeCid": "bafkreifnpykuw5g2m4k5k5wf55zxtzjmcftstzhtsarlkqytimajj3ntlq",
180 |  -"assetTreeSha256": "ad7e154b74da6715d576c5ef7379e52c116729e4f39022b54313430094edb35c",
181 |  -"assetTreeSignature": "0x9faf5c9d13b8d90a7a8e88aa6daf089ca89593c28dc241347c4756e83c2f1ea53ed1cb9e189f7ab81c81327527c97595f44ed71dda8e5d78ebe0dccfe9dd27081c",
182 |  +"actionResult": "https://bafkreidptwydheqfybug4mmnzwzdg4rqxjvg4akl2pwjmrfxhqz33qv4tu.ipfs.dweb.link",
183 |  +"assetTreeCid": "bafkreidptwydheqfybug4mmnzwzdg4rqxjvg4akl2pwjmrfxhqz33qv4tu",
184 |  +"assetTreeSha256": "6f9db0339205c0686e318dcdb2337230ba6a6e014bd3ec9644b73c33bdc2bc9d",
185 |  +"assetTreeSignature": "0xef547e124a9904dbdb5a418022ad03c621201b74111a3b4c5fac61b1d82350170766cef8a27737d21ca9b1bd4e04f7cdea460706b68b14e0ed17f2a3de83f9131b",
186 |   "author": "bafkreigzixvzu2tbxbvmvwcvlz2zwoagmb6c2q5egaq4lmd5sesyopmmx4",
187 |   "committer": "bafkreigzixvzu2tbxbvmvwcvlz2zwoagmb6c2q5egaq4lmd5sesyopmmx4",
188 |   "provider": "bafkreigtmno2wacf4ldb6ewbkboe7oojedsp3dky35djytor6guwzhbdpa",
189 |  -"timestampCreated": 1655720482
190 |  +"timestampCreated": 1655720763
191 | }
192 | 
193 | Asset Tree difference
194 | {
195 |   "abstract": "",
196 |   "assetCid": "bafybeif3ctgbmiso4oykvwj6jebyrkjxqr26bfrkesla5yr2ypgx47wgle",
197 |   "assetCreator": null,
198 |   "assetSha256": null,
199 |   "assetTimestampCreated": null,
200 |   "assetTreeCustomKey1": "foo",
201 |   "assetTreeCustomKey2": "bar",
202 |   "encodingFormat": "application/zip",
203 |   "license": {
204 |     "document": "https://starlinglab.org",
205 |     "name": "Starling License"
206 |   },
207 |  +"nftRecord": "bafkreielfjf7sfxigb4r7tejt7jhl6kthxoujwziywixlwxjjho32may7y"
208 | }
209 | ```
210 | </details>
211 | 
212 | ## Configuration
213 | 
214 | The Nit configuration mimics the [Hardhat configuration](https://hardhat.org/config) with additional fields.
215 | 
216 | The Nit configuration is at `~/.nitconfig.json`. Linux users can open the default editor by the command below:
217 | 
218 | ```shell
219 | nit config -e
220 | ```
221 | 
222 | <details>
223 | <summary>Example of a Nit configuration:</summary>
224 | 
225 | ```json
226 | {
227 |   // CID of the author's profile of original Assets
228 |   "author": "bafkreihkrnjvjeijjhalozcfpgrgb46673dlt4e3qm5bmvzzb4if423wse",
229 |   // CID of the committer's profile who creates Asset Trees and Commits
230 |   "committer": "bafkreihkrnjvjeijjhalozcfpgrgb46673dlt4e3qm5bmvzzb4if423wse",
231 |   // CID of the service provider who hosts the integrity registration service
232 |   "provider": "bafkreido4zu743f6isb5wninfkedvbirj2ngb5fkivrpdijh2xtd3s6rnu",
233 |   "defaultNetwork": "fuji",
234 |   "network": {
235 |     "rinkeby": {
236 |       "url": "https://eth-rinkeby.alchemyapi.io/v2/UO5kfog_UDJgGCuqeaSJmnE95_gKOnFN",
237 |       "gasLimit": 200000,
238 |       "accounts": [
239 |         "<private-key>",
240 |       ],
241 |       // integrity record contract address
242 |       "contract": "0x2Aa4e29872DE77E1Bc6cF310d647F9cB0f9a073B",
243 |       "explorerBaseUrl": "https://rinkeby.etherscan.io/tx"
244 |     },
245 |     "avalanche": {
246 |       "chainId": 43114,
247 |       "url": "https://api.avax.network/ext/bc/C/rpc",
248 |       "accounts": [
249 |         "<private-key>",
250 |       ],
251 |       "contract": "0x1970aFD0831E9233667fb9484910926c2b18BCb4",
252 |       "explorerBaseUrl": "https://snowtrace.io/tx"
253 |     },
254 |     "fuji": {
255 |       "url": "https://api.avax-test.network/ext/bc/C/rpc",
256 |       "chainId": 43113,
257 |       "gasLimit": 200000,
258 |       "accounts": [
259 |         "<private-key>",
260 |       ],
261 |       "contract": "0xA2De03bee39Fa637440909abC5621063bC5DA926",
262 |       "explorerBaseUrl": "https://testnet.snowtrace.io/tx"
263 |     },
264 |     "polygon": {
265 |       "url": "https://polygon-rpc.com/",
266 |       "gasPrice": 60000000000,
267 |       "accounts": [
268 |         "<private-key>",
269 |       ],
270 |       "contract": "0x2094747c6c870f20E38f701116CBb46845b5E5c1",
271 |       "explorerBaseUrl": "https://polygonscan.com/tx"
272 |     },
273 |     "moonbase": {
274 |       "url": "https://rpc.api.moonbase.moonbeam.network",
275 |       "accounts": [
276 |         "<private-key>",
277 |       ],
278 |       "contract": "0xfbeA33fe2b266697404Dc5D1dC0A4ee9D0eDED23",
279 |       "explorerBaseUrl": "https://moonbase.moonscan.io/tx"
280 |     },
281 |     "aurora_testnet": {
282 |       "url": "https://testnet.aurora.dev/",
283 |       "chainId": 1313161555,
284 |       "accounts": [
285 |         "<private-key>",
286 |       ],
287 |       "contract": "0x8e1bF90681C672e25aE880767d57f0552f6F5Cd1",
288 |       "explorerBaseUrl": "https://testnet.aurorascan.dev/tx"
289 |     }
290 |   },
291 |   // For the ipfsadd command. We will support web3.storage soon
292 |   "infura": {
293 |     "projectId": "aaaaaaaaaaaaaaaaaaaaaaaaaaa",
294 |     "projectSecret": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
295 |   }
296 | }
297 | ```
298 | 
299 | </details>
300 | 
301 | ## Verification
302 | 
303 | Verify the integrity of an Asset Tree
304 | 
305 | ```shell
306 | nit verify --integrity-hash <assetTreeSha256> --signature <assetTreeSignature>
307 | ```
308 | 
309 | Example
310 | 
311 | ```shell
312 | nit verify --integrity-hash 6f9db0339205c0686e318dcdb2337230ba6a6e014bd3ec9644b73c33bdc2bc9d --signature 0xef547e124a9904dbdb5a418022ad03c621201b74111a3b4c5fac61b1d82350170766cef8a27737d21ca9b1bd4e04f7cdea460706b68b14e0ed17f2a3de83f9131b
313 | ```
314 | 
315 | Verification result
316 | 
317 | ```shell
318 | Signer address: 0x63B7076FC0A914Af543C2e5c201df6C29FCC18c5
319 | ```
320 | 
321 | If the signer address is different from the Committer's wallet address, you can treat this Commit as not trustworthy.
322 | 


--------------------------------------------------------------------------------
/bin/nit.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 
3 | 'use strict';
4 | 
5 | process.title = "nit";
6 | 
7 | require("../lib/run");


--------------------------------------------------------------------------------
/example/hello.js:
--------------------------------------------------------------------------------
 1 | const nit = require("@numbersprotocol/nit");
 2 | 
 3 | async function main() {
 4 |   const configTemplate = nit.nitconfigTemplate;
 5 |   console.log(`${JSON.stringify(configTemplate)}`);
 6 | }
 7 | 
 8 | main()
 9 |   .then(() => process.exit(0))
10 |   .catch((error) => {
11 |     console.error(error);
12 |     process.exit(1);
13 |   });
14 | 


--------------------------------------------------------------------------------
/example/hello.ts:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env ts-node
 2 | 
 3 | import * as nit from "@numbersprotocol/nit";
 4 | 
 5 | async function main() {
 6 |   const configTemplate = nit.nitconfigTemplate;
 7 |   console.log(`${JSON.stringify(configTemplate)}`);
 8 | }
 9 | 
10 | main()
11 |   .then(() => process.exit(0))
12 |   .catch((error) => {
13 |     console.error(error);
14 |     process.exit(1);
15 |   });


--------------------------------------------------------------------------------
/example/mocks/action.json:
--------------------------------------------------------------------------------
1 | {
2 |   "networkAppName": "PiperNet",
3 |   "blockchain": "4",
4 |   "tokenAddress": "",
5 |   "provider": "bafkreig7asqciqtugev26t35dt4d7voo2jmb3yz3472us4vvw7x62674oq",
6 |   "abstract": "integrity registration"
7 | }


--------------------------------------------------------------------------------
/example/mocks/assetTree.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "assetCid": "bafybeihfzv2zzffznveiezqien5ejztfukjex7nhlki3hjy5zncsoccxdi",
 3 |   "assetSha256": "63560bb47ebfabb7ab4262dbc61e4445eaff184b1ee5a70b55aabe4c333bcef1",
 4 |   "assetCreator": "bafkreihkrnjvjeijjhalozcfpgrgb46673dlt4e3qm5bmvzzb4if423wse",
 5 |   "assetTimestampCreated": 1401649200,
 6 |   "license": {
 7 |     "name": "mit",
 8 |     "document": "https://opensource.org/licenses/MIT"
 9 |   },
10 |   "nftRecords": {},
11 |   "integrityCid": "bafkreicq7kqelooegxd6sq4rpwvxbdu36bz6wpgxeivmndxne4t6pxo22q",
12 |   "integritySha256": "50faa045b9c435c7e943917dab708e9bf073eb3cd7222ac68eed2727e7dddad4",
13 |   "abstract": "Richard's Middle out compression engine, Weissman score of 5.2",
14 |   "encodingFormat": "image/jpeg"
15 | }
16 | 


--------------------------------------------------------------------------------
/example/mocks/commit.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "assetTreeCid": "bafkreiea2zhpjokxdggtzzusrsfpatyz2gfadlk6obvpbhf24rmtiuqsfy",
 3 |   "assetTreeSha256": "80d64ef4b957198d3ce6928c8af04f19d18a01ad5e706af09cbae4593452122e",
 4 |   "author": "bafkreiabcfdxn7rqlzdbuqsdabhvoe2zc772ocy5c7l5pgzlkwgnc3vmza",
 5 |   "committer": "bafkreiabcfdxn7rqlzdbuqsdabhvoe2zc772ocy5c7l5pgzlkwgnc3vmza",
 6 |   "action": {
 7 |     "networkAppName": "networkActionTest",
 8 |     "blockchain": "4",
 9 |     "tokenAddress": "",
10 |     "provider": "bafkreidcnhojuyk6epomati36v5llzcji3gncznusghs7kdw7krpi6dmwq",
11 |     "abstract": "Execute network action test"
12 |   },
13 |   "actionResult": {
14 |       "txHash": "",
15 |       "resultUri": ""
16 |   },
17 |   "provider": "bafkreidcnhojuyk6epomati36v5llzcji3gncznusghs7kdw7krpi6dmwq",
18 |   "abstract": "Initial commit for asset meimei fried chicken.",
19 |   "timestampCreated": 1646731629
20 | }


--------------------------------------------------------------------------------
/example/mocks/identityAssetCreator.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "Richard Hendricks",
 3 |   "wallet": "0xRichardHendricksSiliconValleyPiedPiper42",
 4 |   "profile": "bafkreigx3k2ovitaecq2ln7us6xy5gr3fz5nrqm3r7ue5cu3qoas5o2yqy",
 5 |   "social": {
 6 |     "website": "https://web.archive.org/web/20211010022459/http://www.piedpiper.com/",
 7 |     "facebook": "https://www.facebook.com/SiliconHBO/",
 8 |     "twitter": "https://twitter.com/siliconhbo",
 9 |     "instagram": "https://www.instagram.com/siliconhbo"
10 |   },
11 |   "type": "creator",
12 |   "information": "CEO and founder of Pied Piper and PiperNet"
13 | }
14 | 


--------------------------------------------------------------------------------
/example/mocks/identityProofProvider.json:
--------------------------------------------------------------------------------
1 | {
2 |   "name": "Capture for Pied Piper",
3 |   "wallet": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
4 |   "profile": "",
5 |   "social": {},
6 |   "type": "provider",
7 |   "information": "Capture beta version for Pied Piper"
8 | }
9 | 


--------------------------------------------------------------------------------
/example/mocks/identityServiceProvider.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "Pied Piper",
 3 |   "wallet": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
 4 |   "profile": "bafkreifal23aejdyfzpmqulxacimddqrod2kh6r2qsbwssh2gkwycjhptm",
 5 |   "social": {
 6 |     "website": "https://web.archive.org/web/20211010022459/http://www.piedpiper.com/",
 7 |     "facebook": "https://www.facebook.com/SiliconHBO/",
 8 |     "twitter": "https://twitter.com/siliconhbo",
 9 |     "instagram": "https://www.instagram.com/siliconhbo"
10 |   },
11 |   "type": "provider",
12 |   "information": "Over the years, Pied Piper has changed many landscapes. Compression. Data. The Internet. Click below to look back at the evolution of Pied Piper. Revolutionizing one landscape at a time."
13 | }
14 | 


--------------------------------------------------------------------------------
/example/mocks/proof.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "device.app_build":"180",
 3 |   "device.app_id":"io.numbersprotocol.capturelite",
 4 |   "device.app_name":"Capture",
 5 |   "device.app_version":"0.35.0",
 6 |   "device.battery_level":0.4699999988079071,
 7 |   "device.device_name":"EXODUS 1",
 8 |   "device.disk_free":412057600,
 9 |   "device.disk_total":4225581056,
10 |   "device.is_charging":true,
11 |   "device.is_virtual":false,
12 |   "device.manufacturer":"HTC",
13 |   "device.mem_used":41826536,
14 |   "device.operating_system":"android",
15 |   "device.os_version":"9",
16 |   "device.platform":"android",
17 |   "device.uuid":"16eb24e0fa1f8a28",
18 |   "geolocation.geolocation_latitude":25.0058495,
19 |   "geolocation.geolocation_longitude":121.4586813
20 | }
21 | 


--------------------------------------------------------------------------------
/example/mocks/proofMetadata.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "timestampCreated": 1401649200,
 3 |   "proof": {
 4 |     "device.app_build":"180",
 5 |     "device.app_id":"io.numbersprotocol.capturelite",
 6 |     "device.app_name":"Capture",
 7 |     "device.app_version":"0.35.0",
 8 |     "device.battery_level":0.4699999988079071,
 9 |     "device.device_name":"EXODUS 1",
10 |     "device.disk_free":412057600,
11 |     "device.disk_total":4225581056,
12 |     "device.is_charging":true,
13 |     "device.is_virtual":false,
14 |     "device.manufacturer":"HTC",
15 |     "device.mem_used":41826536,
16 |     "device.operating_system":"android",
17 |     "device.os_version":"9",
18 |     "device.platform":"android",
19 |     "device.uuid":"16eb24e0fa1f8a28",
20 |     "geolocation.geolocation_latitude":37.781937,
21 |     "geolocation.geolocation_longitude":-122.406063
22 |   },
23 |   "proofSha256": "fee45b675643240694a801312d1e2a4cecf85fd5c9427a1b102a5fcc298d2751",
24 |   "proofSignature": "",
25 |   "provider": "bafkreiajfinqk63kbqrsrs7fjwyszqdpdk3jevcs6py66p4i33ooq6rnny"
26 | }


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@numbersprotocol/nit",
 3 |   "version": "1.6.1",
 4 |   "description": "git for web3 digital asset",
 5 |   "main": "lib/nit.js",
 6 |   "repository": "https://github.com/numbersprotocol/nit",
 7 |   "author": "Numbers Protocol",
 8 |   "license": "GPL-3.0-or-later",
 9 |   "dependencies": {
10 |     "@numbersprotocol/estuary-upload": "^1.1.0",
11 |     "@types/mime-types": "^2.1.1",
12 |     "axios": "1.2.0-alpha.1",
13 |     "colors": "^1.4.0",
14 |     "command-line-args": "^5.2.1",
15 |     "command-line-usage": "^6.1.1",
16 |     "crypto-js": "^4.1.1",
17 |     "diff": "^5.1.0",
18 |     "ethers": "^5.5.4",
19 |     "form-data": "^4.0.0",
20 |     "got": "^11.8.2",
21 |     "launch-editor": "^2.3.0"
22 |   },
23 |   "scripts": {
24 |     "build": "yarn run clean && tsc",
25 |     "clean": "rm -rf lib/",
26 |     "dev-global-install": "yarn global add file:$PWD",
27 |     "dev-global-uninstall": "yarn global remove @numbersprotocol/nit",
28 |     "dev-install": "yarn run build && yarn add file:$PWD",
29 |     "dev-uninstall": "yarn remove @numbersprotocol/nit && yarn run clean",
30 |     "tests": "mocha -r ts-node/register tests/**/*.ts",
31 |     "test": "mocha -r ts-node/register"
32 |   },
33 |   "bin": "bin/nit.js",
34 |   "devDependencies": {
35 |     "@rollup/plugin-commonjs": "^22.0.2",
36 |     "@rollup/plugin-json": "^4.1.0",
37 |     "@rollup/plugin-node-resolve": "^14.1.0",
38 |     "@rollup/plugin-replace": "^4.0.0",
39 |     "@types/chai": "^4.3.1",
40 |     "@types/command-line-args": "^5.2.0",
41 |     "@types/command-line-usage": "^5.0.2",
42 |     "@types/crypto-js": "^4.1.1",
43 |     "@types/mocha": "^9.1.0",
44 |     "@types/node": "^18.16.1",
45 |     "@typescript-eslint/eslint-plugin": "^5.51.0",
46 |     "@typescript-eslint/parser": "^5.51.0",
47 |     "chai": "^4.3.6",
48 |     "eslint": "^8.33.0",
49 |     "mocha": "^9.2.2",
50 |     "rollup-plugin-polyfill-node": "^0.11.0",
51 |     "rollup-plugin-sourcemaps": "^0.6.3",
52 |     "ts-node": "^10.7.0",
53 |     "typescript": "^4.6.3"
54 |   },
55 |   "files": [
56 |     "IntegrityRegister.json",
57 |     "/example",
58 |     "/lib",
59 |     "/bin"
60 |   ],
61 |   "volta": {
62 |     "node": "18.16.0"
63 |   }
64 | }
65 | 


--------------------------------------------------------------------------------
/schema/dbml.md:
--------------------------------------------------------------------------------
  1 | ```dbml
  2 | Project IntegrityRecord {
  3 |   Note: '''
  4 |   description: Numbers Protocol Integrity Record spec
  5 |   version: 4.3.0
  6 |   date: 2023-05-11
  7 |   colors:
  8 |     - purple: on blockchain (updatable)
  9 |     - blue: files on IPFS (updatable)
 10 |     - yellow: json
 11 |     - red: signature related
 12 |     - grey: centralized server or DID
 13 |   '''
 14 | }
 15 | 
 16 | Table commit [headercolor: #8e44ad] {
 17 |   _ none [note: 'placeholder for linking tables']
 18 |   assetCid    cid [note: 'This is NOT in the commit but the on-chain index']
 19 |   assetTreeCid cid [note: 'CID of the asset Tree file']
 20 |   assetTreeSha256 sha256 [note: 'sha256sum of the asset Tree file']
 21 |   assetTreeSignature signature [note: 'EIP-191 signature signed by author.']
 22 |   committer address [note: 'Who registers the commit']
 23 |   author address [note: 'Who write the commit']
 24 |   action cid [note: 'CID of the action profile describing the action details including actionName.']
 25 |   actionName str [note: 'name of the action, unique string']
 26 |   actionResult str [note: 'Result uri of this action']
 27 |   provider cid [note: 'CID of the commit service provider.']
 28 |   attachment str [note: 'the Nid of the attachment file.']
 29 | 
 30 |   Note: '''
 31 |   1. The goal of Integrity Record is to ensure integrity of the raw asset and its derivatives like metadata and maybe the major asset.
 32 | 
 33 |   2. Integrity Wallet will sign sha256sum of Integrity Record.
 34 |   '''
 35 | }
 36 | 
 37 | Table action [headercolor: #3498db] {
 38 |   _ none [note: 'placeholder for linking tables']
 39 |   networkActionName str [note: 'name of the network action']
 40 |   blockchain network
 41 |   tokenAddress str [note: 'address of the token required for the action payment']
 42 |   provider cid [note: 'CID of the network action provider.']
 43 |   abstract str  [note: 'description of this action including how to read the results']
 44 |   type actionType
 45 |   Note: '''
 46 |   Network Actions are the Apps interacting with Assets. Every action will be recorded on blockchain.
 47 |   '''
 48 | }
 49 | 
 50 | 
 51 | Table identity [headercolor: #3498db] {
 52 | 
 53 |   _ none [note: 'placeholder for linking tables']
 54 |   name str [note: 'provider\'s name', default: 'NUMBERSPROTOCOL']
 55 |   wallet address [note: 'integrity wallet address, including 0x']
 56 |   profile str [note: 'IPFS address of the profile picture']
 57 |   social json [note: 'social links']
 58 |   type identityType [note: 'the type of this wallet owner']
 59 |   information json [note: 'additional info of this individual']
 60 | 
 61 |   Note: '''
 62 |   This table should be handled by DID or similar technology, not the core technology Numbers considered
 63 |   '''
 64 | }
 65 | 
 66 | Table assetTree [headercolor: #3498db] {
 67 |   _ none [note: 'placeholder for linking tables']
 68 |   assetCid cid [note: 'CID of the asset file (blob)']
 69 |   assetSha256 sha256 [note: 'sha256sum of the asset file (blob)']
 70 |   encodingFormat str [note: 'The asset\'s type expressed using a MIME format.']
 71 |   assetCreator str [note: 'Creator\'s name']
 72 |   creatorProfile cid [note: 'Creator of the asset file']
 73 |   creatorWallet address [note: 'Creator\'s wallet address']
 74 |   assetTimestampCreated timestamp [note: 'Creation time of the asset file']
 75 |   assetLocationCreated str [note: 'Creation location of the asset file']
 76 |   parentAssetCid cid [note: 'Cid of the parent asset']
 77 |   generatedBy str [note: 'AI model used to generate the content']
 78 |   generatedThrough str [note: 'URL of AI service']
 79 |   usedBy str [note: 'URL of the website that uses the asset']
 80 |   license license [note: 'license of the asset file']
 81 |   nftRecord "nftRecord[]" [note: 'List of NFT records']
 82 |   integrityCid cid [note: 'CID of the integrity proof']
 83 |   abstract str [note: 'description of this asset']
 84 |   custom json [note: 'custom fields']
 85 | 
 86 | 
 87 |   Note: '''
 88 |   EIP-191 Verification
 89 |   Input: data, signature
 90 |   Output: signer's wallet address
 91 |   '''
 92 | }
 93 | 
 94 | Table assetTreeVerification [headercolor: #c0392b] {
 95 |   _ none [note: 'placeholder for linking tables']
 96 |   assetTreeSha256 sha256 [note: 'sha256sum of commit']
 97 |   assetTreeSignature str [note: 'commit service provider signs metadata and generates EIP-191 signature.']
 98 | 
 99 |   Note: '''Verify integrity of Asset Tree.
100 | 
101 | Author (Asset Creator) creates the integritySignature.
102 | 
103 | EIP-191 Verification
104 |   Input: data (integrity hash in sha256), signature
105 |   Output: Author's wallet address
106 |   '''
107 | }
108 | 
109 | Table license [headercolor: #3498db] {
110 |   _ none [note: 'placeholder for linking tables']
111 |   name str [note: 'official name of this license']
112 |   document str [note: 'IPFS file for details of this license']
113 | }
114 | 
115 | Table nftRecord [headercolor: #3498db] {
116 |   _ none [note: 'placeholder for linking tables']
117 |   network network [note: 'network of the NFT token']
118 |   contractAddress address
119 |   tokenId str
120 | }
121 | 
122 | Table nft [headercolor: #8e44ad] {
123 |   tokenId str
124 |   tokenUri str
125 | }
126 | 
127 | Table proofVerification [headercolor: #c0392b] {
128 |   _ none [note: 'placeholder for linking tables']
129 |   proofSha256 sha256 [note: 'sha256sum of commit']
130 |   proofSignature str [note: 'commit service provider signs metadata and generates EIP-191 signature.']
131 | 
132 |   Note: '''Verify integrity of Proof.
133 | 
134 | Proof Recorder creates the proofSignature.
135 | 
136 | EIP-191 Verification
137 |   Input: data (integrity hash in sha256), signature
138 |   Output: Proof Recorder's wallet address
139 |   '''
140 | }
141 | 
142 | Table proofMetadata [headercolor: #3498db] {
143 |   _ none [note: 'placeholder for linking tables']
144 |   //schemaVersion str [note: 'Metadata schema version with semantic versioning format a.b.c']
145 | 
146 |   timestampCreated timestamp [note: 'The moment that Metadata is created in Unix timestamp']
147 | 
148 |   proof json [note: 'System/Device information including both HW/SW']
149 |   proofSha256 sha256 [note: 'sha256sum of proof']
150 |   proofSignature str [note: '(Optional) Recorder signs proof and generates EIP-191 signature']
151 |   provider cid [note: 'Proof Recorder']
152 | 
153 |   Note: '''
154 |   1. The goal of integrity_metadata is to provide additional asset-related data including who/when/where/ownership.
155 | 
156 |   2. Recorder is a SW module collecting the SW and HW system information.
157 | 
158 |   3. A trustworthy Recorder generates proof_signature as proof of its credibility.
159 |   '''
160 | }
161 | 
162 | 
163 | Table proof [headercolor: #f39c12] {
164 |   _ none [note: 'placeholder for linking tables']
165 |   _key proofKey
166 | 
167 |   Note: '''
168 |   The SW and HW system information collected by Recorder
169 |   '''
170 | }
171 | 
172 | Enum "network" { //The main blockchain of the network action (bloakchain of the payment wallet)
173 |   "ethereum"
174 |   "bsc"
175 |   "avalanche"
176 |   "polygon"
177 |   "thundercore"
178 |   "cosmos"
179 | }
180 | 
181 | Enum "identityType" {
182 |   "individual"
183 |   "business"
184 |   "school"
185 |   "ngo"
186 | }
187 | 
188 | Enum "actionType" {
189 |   "new" [note: 'The action will newly register the initial commit.']
190 |   "update" [note: 'The action will update attribute to asset and will create a new assetTree.']
191 |   "derive" [note: 'The action will modify asset and create child asset.']
192 |   "use" [note: 'The action will use asset to create derived result without modifying the asset.']
193 | }
194 | 
195 | Enum "proofKey" {
196 |   "device.app_build" [note: 'ex: 311']
197 |   "device.app_id" [note: 'ex: io.numbersprotocol.capturelite']
198 |   "device.app_name" [note: 'ex: Capture']
199 |   "device.app_version" [note: 'ex: 0.48.1']
200 |   "device.battery_level" [note: 'ex: 0.6700000166893005']
201 |   "device.device_name" [note: 'ex: SM-F9160']
202 |   "device.disk_free" [note: 'ex: 29540352']
203 |   "device.disk_total" [note: 'ex: 5950615552']
204 |   "device.is_charging" [note: 'ex: false']
205 |   "device.is_virtual" [note: 'ex: false']
206 |   "device.manufacturer" [note: 'ex: "samsung']
207 |   "device.mem_used" [note: 'ex: 68584792']
208 |   "device.operating_system" [note: 'ex: android']
209 |   "device.os_version" [note: 'ex: 12']
210 |   "device.platform" [note: 'ex: android']
211 |   "device.uuid" [note: 'ex: 755107ce5510dc60']
212 |   "geolocation.geolocation_latitude" [note: 'ex: 24.9959323']
213 |   "geolocation.geolocation_longitude" [note: 'ex: 121.5104948']
214 | }
215 | 
216 | Ref: "nftRecord"."tokenId" - "nft"."tokenId"
217 | Ref: "assetTree"."license" - "license"."_"
218 | Ref: "assetTree"."integrityCid" - "proofMetadata"."_"
219 | Ref: "assetTree"."creatorProfile" - "identity"."_"
220 | Ref: "assetTree".nftRecord < nftRecord."_" // One asset can link to multiple nft_records
221 | Ref: "commit"."action" - "action"."_"
222 | Ref: "commit"."provider" - "identity"."_"
223 | Ref: "action"."provider" - "identity"."_"
224 | Ref: "proofMetadata"."provider" - "identity"."_"
225 | Ref: "proofMetadata".proof - proof._
226 | Ref: "commit"."assetTreeCid" - "assetTree"."_"
227 | Ref: "commit"."committer" - "identity"."_"
228 | Ref: "proofMetadata"."proofSha256" - "proofVerification"."proofSha256"
229 | Ref: "proofMetadata"."proofSignature" - "proofVerification"."proofSignature"
230 | Ref: "commit"."assetTreeSha256" - "assetTreeVerification"."assetTreeSha256"
231 | Ref: "commit"."assetTreeSignature" - "assetTreeVerification"."assetTreeSignature"
232 | 
233 | Ref: "action"."tokenAddress" < "action"."_"
234 | ```


--------------------------------------------------------------------------------
/scripts/parseTxNid.ts:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env ts-node
 2 | 
 3 | /*
 4 | Execution: ts-node parseTxNid.ts
 5 | Output: Nid: bafybeiajl3iadbz5phbig6ojxrau22a67lzrba2srdqn2jzy3bpl7oxd6u
 6 | */
 7 | 
 8 | import * as ethers from "ethers";
 9 | 
10 | async function main() {
11 |   const provider = new ethers.providers.JsonRpcProvider("https://mainnetrpc.num.network");
12 |   // Tx Sample: https://mainnet.num.network/transaction/0xca942395e9c36b3217422979978838a45891336ca54bb036d6aecd43f0b56187
13 |   const txHash = "0xca942395e9c36b3217422979978838a45891336ca54bb036d6aecd43f0b56187";
14 | 
15 |   // parse the transaction with the function signature
16 |   const abi = [ "function commit(string memory assetCid, string memory commitData) public returns (uint256 blockNumber)" ];
17 |   const iface = new ethers.utils.Interface(abi);
18 | 
19 |   const txData = await provider.getTransaction(txHash);
20 |   // uncomment the following line to see the raw transaction data
21 |   //console.log(txData);
22 | 
23 |   const decodedTxData = iface.parseTransaction(txData);
24 |   // uncomment the following line to see the decoded transaction data
25 |   //console.log(decodedTxData);
26 | 
27 |   console.log(`Nid: ${decodedTxData.args[0]}`);
28 | }
29 | 
30 | main()
31 |   .then(() => process.exit(0))
32 |   .catch((error) => {
33 |     console.error(error);
34 |     process.exit(1);
35 |   });
36 | 


--------------------------------------------------------------------------------
/src/action.ts:
--------------------------------------------------------------------------------
  1 | /* Spec of Actions
  2 |  * https://docs.numbersprotocol.io/introduction/numbers-protocol/defining-web3-assets/commit#actions
  3 |  */
  4 | 
  5 | export const ActionSet = {
  6 |   // default, the same as action-initial-registration-jade
  7 |   "action-initial-registration": {
  8 |     nid: "bafkreicptxn6f752c4pvb6gqwro7s7wb336idkzr6wmolkifj3aafhvwii",
  9 |     content: {
 10 |       "networkActionName": "initial registration",
 11 |       "blockchain": "jade",
 12 |       "tokenAddress": "",
 13 |       "provider": "bafkreigrt5tepycewppysdwcjccdkdvvc2ztelqv64idgautq52g3vfh4i",
 14 |       "abstract": "Register asset to the Numbers network",
 15 |       "type": "new"
 16 |     }
 17 |   },
 18 |   "action-initial-registration-avalanche": {
 19 |     nid: "bafkreiavifzn7ntlb6p2lr55k3oezo6pofwvknecukc5auhng4miowcte4",
 20 |     content: {
 21 |       "networkActionName": "initial registration",
 22 |       "blockchain": "avalanche",
 23 |       "tokenAddress": "",
 24 |       "provider": "bafkreido4zu743f6isb5wninfkedvbirj2ngb5fkivrpdijh2xtd3s6rnu",
 25 |       "abstract": "Register asset to the Numbers network",
 26 |       "type": "new"
 27 |     }
 28 |   },
 29 |   "action-initial-registration-jade": {
 30 |     nid: "bafkreicptxn6f752c4pvb6gqwro7s7wb336idkzr6wmolkifj3aafhvwii",
 31 |     content: {
 32 |       "networkActionName": "initial registration",
 33 |       "blockchain": "jade",
 34 |       "tokenAddress": "",
 35 |       "provider": "bafkreigrt5tepycewppysdwcjccdkdvvc2ztelqv64idgautq52g3vfh4i",
 36 |       "abstract": "Register asset to the Numbers network",
 37 |       "type": "new"
 38 |     }
 39 |   },
 40 |   "action-initial-registration-iota": {
 41 |     nid: "bafkreibnyl3ohbx76rzxmdzfyayvztjcqgjv745wh4t3z6a4ung43xd554",
 42 |     content: {
 43 |       "networkActionName": "initial registration",
 44 |       "blockchain": "iota",
 45 |       "tokenAddress": "",
 46 |       "provider": "bafkreigrt5tepycewppysdwcjccdkdvvc2ztelqv64idgautq52g3vfh4i",
 47 |       "abstract": "Register asset to the Numbers network",
 48 |       "type": "new"
 49 |     }
 50 |   },
 51 | 
 52 |   "action-commit": {
 53 |     nid: "bafkreiceiqfe4w4g4j46rsi4h4tbqu35ivewfxm4q743tfmegscpg7d63m",
 54 |     content: {
 55 |       "networkActionName": "commit",
 56 |       "blockchain": "jade",
 57 |       "tokenAddress": "",
 58 |       "provider": "bafkreigrt5tepycewppysdwcjccdkdvvc2ztelqv64idgautq52g3vfh4i",
 59 |       "abstract": "Commit asset history to Numbers network",
 60 |       "type": "update"
 61 |     }
 62 |   },
 63 | 
 64 |   // default, the same as action-mint-erc721-nft-jade
 65 |   "action-mint-erc721-nft": {
 66 |     nid: "bafkreiakk6le5wgbl3zz3acybd2wfc2y32mpmxj4kbjjjskom7cy2akwly",
 67 |     content: {
 68 |       "networkActionName": "mint ERC-721 NFT",
 69 |       "blockchain": "jade",
 70 |       "tokenAddress": "",
 71 |       "provider": "bafkreigrt5tepycewppysdwcjccdkdvvc2ztelqv64idgautq52g3vfh4i",
 72 |       "abstract": "Mint ERC-721 NFT on Jade",
 73 |       "type": "update"
 74 |     }
 75 |   },
 76 |   "action-mint-erc721-nft-avalanche": {
 77 |     nid: "bafkreibro4v4vsibvr47uwhkj6gqpc5rfvvq5ykfzzz4jnzvm23zwoh2gq",
 78 |     content: {
 79 |       "networkActionName": "mint ERC-721 NFT",
 80 |       "blockchain": "avalanche",
 81 |       "tokenAddress": "",
 82 |       "provider": "bafkreido4zu743f6isb5wninfkedvbirj2ngb5fkivrpdijh2xtd3s6rnu",
 83 |       "abstract": "Mint ERC-721 NFT on Avalanche",
 84 |       "type": "update"
 85 |     }
 86 |   },
 87 |   "action-mint-erc721-nft-ethereum": {
 88 |     nid: "bafkreighzhmelvwlntigjq6ushun4mz36x6cq5nztkvcac47bdxz2ipeca",
 89 |     content: {
 90 |       "networkActionName": "mint ERC-721 NFT",
 91 |       "blockchain": "ethereum",
 92 |       "tokenAddress": "",
 93 |       "provider": "bafkreido4zu743f6isb5wninfkedvbirj2ngb5fkivrpdijh2xtd3s6rnu",
 94 |       "abstract": "Mint ERC-721 NFT on Ethereum",
 95 |       "type": "update"
 96 |     }
 97 |   },
 98 |   "action-mint-erc721-nft-jade": {
 99 |     nid: "bafkreiakk6le5wgbl3zz3acybd2wfc2y32mpmxj4kbjjjskom7cy2akwly",
100 |     content: {
101 |       "networkActionName": "mint ERC-721 NFT",
102 |       "blockchain": "jade",
103 |       "tokenAddress": "",
104 |       "provider": "bafkreigrt5tepycewppysdwcjccdkdvvc2ztelqv64idgautq52g3vfh4i",
105 |       "abstract": "Mint ERC-721 NFT on Jade",
106 |       "type": "update"
107 |     }
108 |   },
109 |   "action-mint-erc721-nft-polygon": {
110 |     nid: "bafkreigdxfmwhhepusyhaoidyzy6bv7jzpc5liqohyc6numgvvbfwabn6a",
111 |     content: {
112 |       "networkActionName": "mint ERC-721 NFT",
113 |       "blockchain": "polygon",
114 |       "tokenAddress": "",
115 |       "provider": "bafkreido4zu743f6isb5wninfkedvbirj2ngb5fkivrpdijh2xtd3s6rnu",
116 |       "abstract": "Mint ERC-721 NFT on Polygon",
117 |       "type": "update"
118 |     }
119 |   },
120 |   "action-mint-erc721-nft-thundercore": {
121 |     nid: "bafkreih7gzxeukcnosbotvpq7hw6htlxoi2dzzm2nnkskloazgw2sbh55i",
122 |     content: {
123 |       "networkActionName": "mint ERC-721 NFT",
124 |       "blockchain": "thundercore",
125 |       "tokenAddress": "",
126 |       "provider": "bafkreido4zu743f6isb5wninfkedvbirj2ngb5fkivrpdijh2xtd3s6rnu",
127 |       "abstract": "Mint ERC-721 NFT on ThunderCore",
128 |       "type": "update"
129 |     }
130 |   },
131 | };
132 | 
133 | /* DESIGN: To keep backward compatibility, we keep Actions.
134 |  */
135 | 
136 | function generateNidDictionary(actionSet) {
137 |   const nidDictionary = {};
138 | 
139 |   for (const key in actionSet) {
140 |     if (actionSet.hasOwnProperty(key)) {
141 |       nidDictionary[key] = actionSet[key].nid;
142 |     }
143 |   }
144 | 
145 |   return nidDictionary;
146 | }
147 | 
148 | export const Actions = generateNidDictionary(ActionSet);
149 | 
150 | /* Return the action key if it's valid, otherwise return "commit". */
151 | export function getValidActionOrDefault(action: string): string {
152 |   return Actions.hasOwnProperty(action) ? action : "action-commit";
153 | }
154 | 
155 | export function generateNameDictionary(actionSet) {
156 |   const keyDictionary = {};
157 | 
158 |   for (const key in actionSet) {
159 |     if (actionSet.hasOwnProperty(key)) {
160 |       const nid = actionSet[key].nid;
161 |       keyDictionary[nid] = key;
162 |     }
163 |   }
164 | 
165 |   return keyDictionary;
166 | }
167 | 
168 | export function getNameByNid(actionNid) {
169 |   const nameDictionary = generateNameDictionary(ActionSet);
170 | 
171 |   if (nameDictionary.hasOwnProperty(actionNid)) {
172 |     return nameDictionary[actionNid];
173 |   } else {
174 |     return 'action-commit';
175 |   }
176 | }


--------------------------------------------------------------------------------
/src/commitdb.ts:
--------------------------------------------------------------------------------
  1 | import { ethers, utils } from "ethers";
  2 | 
  3 | import axios from 'axios';
  4 | import FormData = require("form-data");
  5 | 
  6 | import * as ipfs from "./ipfs";
  7 | import * as nit from "./nit";
  8 | import * as util from "./util";
  9 | 
 10 | interface ExtendedCommit {
 11 |   commitEventIndex;
 12 |   blockNumber;
 13 |   transactionHash;
 14 |   commit;
 15 |   assetCid;
 16 |   assetTree;
 17 |   author;
 18 |   authorWalletAddress;
 19 |   committer;
 20 |   committerWalletAddress;
 21 |   provider;
 22 |   license;
 23 |   action;
 24 |   integrity;
 25 |   assetCreatedIsoTime: string;
 26 |   commitCreatedIsoTime: string;
 27 |   blockchain: string;
 28 | }
 29 | 
 30 | async function extend(commitEventIndex, commitSummary, blockchainName) {
 31 |   const blockNumber = commitSummary.blockNumber ;
 32 |   const transactionHash = commitSummary.transactionHash;
 33 |   const commitObject = commitSummary.commit;
 34 |   const commit = JSON.stringify(commitObject);
 35 | 
 36 |   const author = await ipfs.cidToJsonString(commitObject.author);
 37 |   const authorWalletAddress = JSON.parse(author).wallet;
 38 |   const committer = await ipfs.cidToJsonString(commitObject.committer);
 39 |   const committerWalletAddress = JSON.parse(committer).wallet;
 40 |   const provider = await ipfs.cidToJsonString(commitObject.provider);
 41 |   const action = await ipfs.cidToJsonString(commitObject.action);
 42 |   const commitCreatedIsoTime = util.timestampToIsoString(commitObject.timestampCreated);
 43 |   const assetTree = await ipfs.cidToJsonString(commitObject.assetTreeCid);
 44 |   const assetTreeObject = JSON.parse(assetTree);
 45 |   const assetCid = assetTreeObject.assetCid;
 46 |   const license = await ipfs.cidToJsonString(assetTreeObject.provider);
 47 |   const integrity = await ipfs.cidToJsonString(assetTreeObject.integrityCid);
 48 |   const assetCreatedIsoTime = util.timestampToIsoString(assetTreeObject.assetTimestampCreated);
 49 |   const blockchain = blockchainName;
 50 | 
 51 |   const extendedCommit: ExtendedCommit = {
 52 |     "commitEventIndex": commitEventIndex,
 53 |     "blockNumber": blockNumber,
 54 |     "transactionHash": transactionHash,
 55 |     "commit": commit,
 56 |     "author": author,
 57 |     "authorWalletAddress": authorWalletAddress,
 58 |     "committer": committer,
 59 |     "committerWalletAddress": committerWalletAddress,
 60 |     "provider": provider,
 61 |     "action": action,
 62 |     "commitCreatedIsoTime": commitCreatedIsoTime,
 63 |     "assetTree": assetTree,
 64 |     "assetCid": assetCid,
 65 |     "license": license,
 66 |     "integrity": integrity,
 67 |     "assetCreatedIsoTime": assetCreatedIsoTime,
 68 |     "blockchain": blockchain,
 69 |   };
 70 | 
 71 |   return extendedCommit;
 72 | }
 73 | 
 74 | export async function extendCommits(assetCid, blockchainInfo, fromIndex, toIndex) {
 75 |   const commitEvents = await nit.iterateCommitEvents(assetCid, blockchainInfo, fromIndex, toIndex);
 76 |   const commitsSummary = await nit.getCommits(commitEvents);
 77 |   await nit.showCommits(commitsSummary);
 78 |   let extendedCommits = [];
 79 |   for (const summary of commitsSummary) {
 80 |     const extendedCommit = await extend(assetCid, summary, nit.blockchainNames[blockchainInfo.chainId])
 81 |     extendedCommits.push(extendedCommit);
 82 |   }
 83 |   return extendedCommits;
 84 | }
 85 | 
 86 | // bubble -> calculate how many items to retrieve -> call update -> push extended commints to db -> return updated entry amount
 87 | export async function update(assetCid, blockchainInfo, dbEntryAmount, dbEndpointUrl, accessToken=null) {
 88 |   const onchainCommitAmount = (await nit.getCommitBlockNumbers(assetCid, blockchainInfo)).length;
 89 |   const updateFromIndex = dbEntryAmount;
 90 |   const updateToIndex = onchainCommitAmount;
 91 |   const extendedCommits = await extendCommits(assetCid, blockchainInfo, updateFromIndex, updateToIndex);
 92 | 
 93 |   for (const extendedCommit of extendedCommits) {
 94 |     const r = await httpPost(dbEndpointUrl, extendedCommit, accessToken);
 95 |   }
 96 | 
 97 |   return {
 98 |     "originalDbEntryAmount": parseInt(dbEntryAmount),
 99 |     "updateDbEntryAmount": updateToIndex - updateFromIndex,
100 |   };
101 | }
102 | 
103 | export async function httpPost(url, data, accessToken=null) {
104 |   const formData = new FormData();
105 |   for (const key of Object.keys(data)) {
106 |     formData.append(key, data[key] ? data[key] : "");
107 |   }
108 | 
109 |   //const authBase64 = Buffer.from(`${ProjectId}:${ProjectSecret}`).toString('base64');
110 |   const requestConfig = {
111 |     "headers": {
112 |       //"Authorization": `Bearer ${authBase64}`,
113 |       ...formData.getHeaders(),
114 |     },
115 |   }
116 |   if (accessToken != null) {
117 |     requestConfig.headers.Authorization = `Bearer ${accessToken}`;
118 |   }
119 |   const r = await axios.post(url, formData, requestConfig);
120 |   const returnedData = r.data;
121 |   return returnedData;
122 | }
123 | 
124 | export async function push(commitDbUpdateUrl: string, assetCid: string, commitDbCommitUrl: string) {
125 |   const data = {
126 |     "assetCid": assetCid,
127 |     "dbEndpointUrl": commitDbCommitUrl,
128 |   };
129 |   const r = await httpPost(commitDbUpdateUrl, data);
130 |   return r;
131 | }


--------------------------------------------------------------------------------
/src/contract.ts:
--------------------------------------------------------------------------------
  1 | const artifact = {
  2 |   "_format": "hh-sol-artifact-1",
  3 |   "contractName": "IntegrityRegister",
  4 |   "sourceName": "contracts/IntegrityRegister.sol",
  5 |   "abi": [
  6 |     {
  7 |       "anonymous": false,
  8 |       "inputs": [
  9 |         {
 10 |           "indexed": true,
 11 |           "internalType": "address",
 12 |           "name": "recorder",
 13 |           "type": "address"
 14 |         },
 15 |         {
 16 |           "indexed": true,
 17 |           "internalType": "string",
 18 |           "name": "assetCid",
 19 |           "type": "string"
 20 |         },
 21 |         {
 22 |           "indexed": false,
 23 |           "internalType": "string",
 24 |           "name": "commitData",
 25 |           "type": "string"
 26 |         }
 27 |       ],
 28 |       "name": "Commit",
 29 |       "type": "event"
 30 |     },
 31 |     {
 32 |       "anonymous": false,
 33 |       "inputs": [
 34 |         {
 35 |           "indexed": true,
 36 |           "internalType": "address",
 37 |           "name": "recorder",
 38 |           "type": "address"
 39 |         },
 40 |         {
 41 |           "indexed": true,
 42 |           "internalType": "string",
 43 |           "name": "assetCid",
 44 |           "type": "string"
 45 |         },
 46 |         {
 47 |           "indexed": false,
 48 |           "internalType": "string",
 49 |           "name": "integrityData",
 50 |           "type": "string"
 51 |         },
 52 |         {
 53 |           "indexed": false,
 54 |           "internalType": "bytes",
 55 |           "name": "signature",
 56 |           "type": "bytes"
 57 |         }
 58 |       ],
 59 |       "name": "Integrity",
 60 |       "type": "event"
 61 |     },
 62 |     {
 63 |       "inputs": [
 64 |         {
 65 |           "internalType": "string",
 66 |           "name": "assetCid",
 67 |           "type": "string"
 68 |         },
 69 |         {
 70 |           "internalType": "string",
 71 |           "name": "commitData",
 72 |           "type": "string"
 73 |         }
 74 |       ],
 75 |       "name": "commit",
 76 |       "outputs": [
 77 |         {
 78 |           "internalType": "uint256",
 79 |           "name": "blockNumber",
 80 |           "type": "uint256"
 81 |         }
 82 |       ],
 83 |       "stateMutability": "nonpayable",
 84 |       "type": "function"
 85 |     },
 86 |     {
 87 |       "inputs": [
 88 |         {
 89 |           "internalType": "string",
 90 |           "name": "assetCid",
 91 |           "type": "string"
 92 |         }
 93 |       ],
 94 |       "name": "getCommits",
 95 |       "outputs": [
 96 |         {
 97 |           "internalType": "uint256[]",
 98 |           "name": "",
 99 |           "type": "uint256[]"
100 |         }
101 |       ],
102 |       "stateMutability": "view",
103 |       "type": "function"
104 |     },
105 |     {
106 |       "inputs": [
107 |         {
108 |           "internalType": "string",
109 |           "name": "assetCid",
110 |           "type": "string"
111 |         }
112 |       ],
113 |       "name": "getRecordLogs",
114 |       "outputs": [
115 |         {
116 |           "internalType": "uint256[]",
117 |           "name": "",
118 |           "type": "uint256[]"
119 |         }
120 |       ],
121 |       "stateMutability": "view",
122 |       "type": "function"
123 |     },
124 |     {
125 |       "inputs": [],
126 |       "name": "initialize",
127 |       "outputs": [],
128 |       "stateMutability": "nonpayable",
129 |       "type": "function"
130 |     },
131 |     {
132 |       "inputs": [
133 |         {
134 |           "internalType": "string",
135 |           "name": "",
136 |           "type": "string"
137 |         },
138 |         {
139 |           "internalType": "uint256",
140 |           "name": "",
141 |           "type": "uint256"
142 |         }
143 |       ],
144 |       "name": "recordLogs",
145 |       "outputs": [
146 |         {
147 |           "internalType": "uint256",
148 |           "name": "",
149 |           "type": "uint256"
150 |         }
151 |       ],
152 |       "stateMutability": "view",
153 |       "type": "function"
154 |     },
155 |     {
156 |       "inputs": [
157 |         {
158 |           "internalType": "string",
159 |           "name": "assetCid",
160 |           "type": "string"
161 |         },
162 |         {
163 |           "internalType": "string",
164 |           "name": "integrityData",
165 |           "type": "string"
166 |         },
167 |         {
168 |           "internalType": "bytes",
169 |           "name": "signature",
170 |           "type": "bytes"
171 |         }
172 |       ],
173 |       "name": "registerIntegrityRecord",
174 |       "outputs": [],
175 |       "stateMutability": "nonpayable",
176 |       "type": "function"
177 |     }
178 |   ],
179 |   "bytecode": "0x608060405234801561001057600080fd5b5061198d806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80636715cbe6146100675780638129fc1c1461009757806381d14ffc146100a1578063acf64771146100d1578063da33873e146100ed578063ed9b95e21461011d575b600080fd5b610081600480360381019061007c9190610f11565b61014d565b60405161008e9190611365565b60405180910390f35b61009f6101c3565b005b6100bb60048036038101906100b69190610f52565b6102a7565b6040516100c89190611575565b60405180910390f35b6100eb60048036038101906100e69190610fbe565b61039a565b005b61010760048036038101906101029190610f11565b610543565b6040516101149190611365565b60405180910390f35b61013760048036038101906101329190611055565b6105b9565b6040516101449190611575565b60405180910390f35b606060018260405161015f9190611328565b90815260200160405180910390208054806020026020016040519081016040528092919081815260200182805480156101b757602002820191906000526020600020905b8154815260200190600101908083116101a3575b50505050509050919050565b600060019054906101000a900460ff166101eb5760008054906101000a900460ff16156101f4565b6101f3610600565b5b610233576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161022a90611535565b60405180910390fd5b60008060019054906101000a900460ff161590508015610283576001600060016101000a81548160ff02191690831515021790555060016000806101000a81548160ff0219169083151502179055505b80156102a45760008060016101000a81548160ff0219169083151502179055505b50565b6000826040516102b79190611328565b60405180910390203373ffffffffffffffffffffffffffffffffffffffff167f39b5d1808997c4c413695960efbe703ad3d5c8982d2eb21ff57b01e22a6715328460405161030591906113cc565b60405180910390a361034c6040518060400160405280601d81526020017f436f6d6d6974206576656e7420626c6f636b206e756d6265723a20257300000081525043610611565b60018360405161035c9190611328565b908152602001604051809103902043908060018154018082558091505060019003906000526020600020016000909190919091505543905092915050565b6000826040516020016103ad9190611328565b60405160208183030381529060405280519060200120905060006103d182846106ad565b90506104126040518060400160405280601181526020017f56616c6964207369676e61747572653a2000000000000000000000000000000081525082610762565b80610452576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610449906114f5565b60405180910390fd5b846040516104609190611328565b60405180910390203373ffffffffffffffffffffffffffffffffffffffff167ff14cc285ed29e95249df9be8f97c6d27c13da6147ac82ffde11e0e7cbac00e4e86866040516104b092919061144e565b60405180910390a36104f76040518060400160405280602081526020017f496e74656772697479206576656e7420626c6f636b206e756d6265723a20257381525043610611565b6001856040516105079190611328565b90815260200160405180910390204390806001815401808255809150506001900390600052602060002001600090919091909150555050505050565b60606001826040516105559190611328565b90815260200160405180910390208054806020026020016040519081016040528092919081815260200182805480156105ad57602002820191906000526020600020905b815481526020019060010190808311610599575b50505050509050919050565b60018280516020810182018051848252602083016020850120818352809550505050505081815481106105eb57600080fd5b90600052602060002001600091509150505481565b600061060b306107fe565b15905090565b6106a98282604051602401610627929190611485565b6040516020818303038152906040527f9710a9d0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050610811565b5050565b600080836040516020016106c1919061133f565b6040516020818303038152906040528051906020012090506107186040518060400160405280601081526020017f4d6573736167652073656e6465723a20000000000000000000000000000000008152503361083a565b3373ffffffffffffffffffffffffffffffffffffffff1661074284836108d690919063ffffffff16565b73ffffffffffffffffffffffffffffffffffffffff161491505092915050565b6107fa828260405160240161077892919061141e565b6040516020818303038152906040527fc3b55635000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050610811565b5050565b600080823b905060008111915050919050565b60008151905060006a636f6e736f6c652e6c6f679050602083016000808483855afa5050505050565b6108d282826040516024016108509291906113ee565b6040516020818303038152906040527f319af333000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050610811565b5050565b60008060006108e585856108fd565b915091506108f281610980565b819250505092915050565b60008060418351141561093f5760008060006020860151925060408601519150606086015160001a905061093387828585610cd1565b94509450505050610979565b604083511415610970576000806020850151915060408501519050610965868383610dde565b935093505050610979565b60006002915091505b9250929050565b600060048111156109ba577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8160048111156109f3577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b14156109fe57610cce565b60016004811115610a38577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b816004811115610a71577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610ab2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610aa9906114b5565b60405180910390fd5b60026004811115610aec577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b816004811115610b25577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610b66576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b5d906114d5565b60405180910390fd5b60036004811115610ba0577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b816004811115610bd9577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610c1a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c1190611515565b60405180910390fd5b600480811115610c53577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b816004811115610c8c577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610ccd576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cc490611555565b60405180910390fd5b5b50565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08360001c1115610d0c576000600391509150610dd5565b601b8560ff1614158015610d245750601c8560ff1614155b15610d36576000600491509150610dd5565b600060018787878760405160008152602001604052604051610d5b9493929190611387565b6020604051602081039080840390855afa158015610d7d573d6000803e3d6000fd5b505050602060405103519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415610dcc57600060019250925050610dd5565b80600092509250505b94509492505050565b6000806000807f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85169150601b8560ff1c019050610e1e87828885610cd1565b935093505050935093915050565b6000610e3f610e3a846115b5565b611590565b905082815260208101848484011115610e5757600080fd5b610e628482856116f2565b509392505050565b6000610e7d610e78846115e6565b611590565b905082815260208101848484011115610e9557600080fd5b610ea08482856116f2565b509392505050565b600082601f830112610eb957600080fd5b8135610ec9848260208601610e2c565b91505092915050565b600082601f830112610ee357600080fd5b8135610ef3848260208601610e6a565b91505092915050565b600081359050610f0b81611940565b92915050565b600060208284031215610f2357600080fd5b600082013567ffffffffffffffff811115610f3d57600080fd5b610f4984828501610ed2565b91505092915050565b60008060408385031215610f6557600080fd5b600083013567ffffffffffffffff811115610f7f57600080fd5b610f8b85828601610ed2565b925050602083013567ffffffffffffffff811115610fa857600080fd5b610fb485828601610ed2565b9150509250929050565b600080600060608486031215610fd357600080fd5b600084013567ffffffffffffffff811115610fed57600080fd5b610ff986828701610ed2565b935050602084013567ffffffffffffffff81111561101657600080fd5b61102286828701610ed2565b925050604084013567ffffffffffffffff81111561103f57600080fd5b61104b86828701610ea8565b9150509250925092565b6000806040838503121561106857600080fd5b600083013567ffffffffffffffff81111561108257600080fd5b61108e85828601610ed2565b925050602061109f85828601610efc565b9150509250929050565b60006110b583836112fb565b60208301905092915050565b6110ca81611693565b82525050565b60006110db82611627565b6110e58185611655565b93506110f083611617565b8060005b8381101561112157815161110888826110a9565b975061111383611648565b9250506001810190506110f4565b5085935050505092915050565b611137816116a5565b82525050565b611146816116b1565b82525050565b61115d611158826116b1565b611765565b82525050565b600061116e82611632565b6111788185611666565b9350611188818560208601611701565b6111918161179e565b840191505092915050565b60006111a78261163d565b6111b18185611677565b93506111c1818560208601611701565b6111ca8161179e565b840191505092915050565b60006111e08261163d565b6111ea8185611688565b93506111fa818560208601611701565b80840191505092915050565b6000611213601883611677565b915061121e826117af565b602082019050919050565b6000611236601f83611677565b9150611241826117d8565b602082019050919050565b6000611259601c83611688565b915061126482611801565b601c82019050919050565b600061127c601183611677565b91506112878261182a565b602082019050919050565b600061129f602283611677565b91506112aa82611853565b604082019050919050565b60006112c2602e83611677565b91506112cd826118a2565b604082019050919050565b60006112e5602283611677565b91506112f0826118f1565b604082019050919050565b611304816116db565b82525050565b611313816116db565b82525050565b611322816116e5565b82525050565b600061133482846111d5565b915081905092915050565b600061134a8261124c565b9150611356828461114c565b60208201915081905092915050565b6000602082019050818103600083015261137f81846110d0565b905092915050565b600060808201905061139c600083018761113d565b6113a96020830186611319565b6113b6604083018561113d565b6113c3606083018461113d565b95945050505050565b600060208201905081810360008301526113e6818461119c565b905092915050565b60006040820190508181036000830152611408818561119c565b905061141760208301846110c1565b9392505050565b60006040820190508181036000830152611438818561119c565b9050611447602083018461112e565b9392505050565b60006040820190508181036000830152611468818561119c565b9050818103602083015261147c8184611163565b90509392505050565b6000604082019050818103600083015261149f818561119c565b90506114ae602083018461130a565b9392505050565b600060208201905081810360008301526114ce81611206565b9050919050565b600060208201905081810360008301526114ee81611229565b9050919050565b6000602082019050818103600083015261150e8161126f565b9050919050565b6000602082019050818103600083015261152e81611292565b9050919050565b6000602082019050818103600083015261154e816112b5565b9050919050565b6000602082019050818103600083015261156e816112d8565b9050919050565b600060208201905061158a600083018461130a565b92915050565b600061159a6115ab565b90506115a68282611734565b919050565b6000604051905090565b600067ffffffffffffffff8211156115d0576115cf61176f565b5b6115d98261179e565b9050602081019050919050565b600067ffffffffffffffff8211156116015761160061176f565b5b61160a8261179e565b9050602081019050919050565b6000819050602082019050919050565b600081519050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600082825260208201905092915050565b600081905092915050565b600061169e826116bb565b9050919050565b60008115159050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b8381101561171f578082015181840152602081019050611704565b8381111561172e576000848401525b50505050565b61173d8261179e565b810181811067ffffffffffffffff8211171561175c5761175b61176f565b5b80604052505050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b7f45434453413a20696e76616c6964207369676e61747572650000000000000000600082015250565b7f45434453413a20696e76616c6964207369676e6174757265206c656e67746800600082015250565b7f19457468657265756d205369676e6564204d6573736167653a0a333200000000600082015250565b7f496e76616c6964207369676e6174757265000000000000000000000000000000600082015250565b7f45434453413a20696e76616c6964207369676e6174757265202773272076616c60008201527f7565000000000000000000000000000000000000000000000000000000000000602082015250565b7f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160008201527f647920696e697469616c697a6564000000000000000000000000000000000000602082015250565b7f45434453413a20696e76616c6964207369676e6174757265202776272076616c60008201527f7565000000000000000000000000000000000000000000000000000000000000602082015250565b611949816116db565b811461195457600080fd5b5056fea2646970667358221220e37840e081ddeb5ea866e15b83ca8421cf9e3df36bdb2c113f6381d9c62c3b7d64736f6c63430008040033",
180 |   "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100625760003560e01c80636715cbe6146100675780638129fc1c1461009757806381d14ffc146100a1578063acf64771146100d1578063da33873e146100ed578063ed9b95e21461011d575b600080fd5b610081600480360381019061007c9190610f11565b61014d565b60405161008e9190611365565b60405180910390f35b61009f6101c3565b005b6100bb60048036038101906100b69190610f52565b6102a7565b6040516100c89190611575565b60405180910390f35b6100eb60048036038101906100e69190610fbe565b61039a565b005b61010760048036038101906101029190610f11565b610543565b6040516101149190611365565b60405180910390f35b61013760048036038101906101329190611055565b6105b9565b6040516101449190611575565b60405180910390f35b606060018260405161015f9190611328565b90815260200160405180910390208054806020026020016040519081016040528092919081815260200182805480156101b757602002820191906000526020600020905b8154815260200190600101908083116101a3575b50505050509050919050565b600060019054906101000a900460ff166101eb5760008054906101000a900460ff16156101f4565b6101f3610600565b5b610233576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161022a90611535565b60405180910390fd5b60008060019054906101000a900460ff161590508015610283576001600060016101000a81548160ff02191690831515021790555060016000806101000a81548160ff0219169083151502179055505b80156102a45760008060016101000a81548160ff0219169083151502179055505b50565b6000826040516102b79190611328565b60405180910390203373ffffffffffffffffffffffffffffffffffffffff167f39b5d1808997c4c413695960efbe703ad3d5c8982d2eb21ff57b01e22a6715328460405161030591906113cc565b60405180910390a361034c6040518060400160405280601d81526020017f436f6d6d6974206576656e7420626c6f636b206e756d6265723a20257300000081525043610611565b60018360405161035c9190611328565b908152602001604051809103902043908060018154018082558091505060019003906000526020600020016000909190919091505543905092915050565b6000826040516020016103ad9190611328565b60405160208183030381529060405280519060200120905060006103d182846106ad565b90506104126040518060400160405280601181526020017f56616c6964207369676e61747572653a2000000000000000000000000000000081525082610762565b80610452576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610449906114f5565b60405180910390fd5b846040516104609190611328565b60405180910390203373ffffffffffffffffffffffffffffffffffffffff167ff14cc285ed29e95249df9be8f97c6d27c13da6147ac82ffde11e0e7cbac00e4e86866040516104b092919061144e565b60405180910390a36104f76040518060400160405280602081526020017f496e74656772697479206576656e7420626c6f636b206e756d6265723a20257381525043610611565b6001856040516105079190611328565b90815260200160405180910390204390806001815401808255809150506001900390600052602060002001600090919091909150555050505050565b60606001826040516105559190611328565b90815260200160405180910390208054806020026020016040519081016040528092919081815260200182805480156105ad57602002820191906000526020600020905b815481526020019060010190808311610599575b50505050509050919050565b60018280516020810182018051848252602083016020850120818352809550505050505081815481106105eb57600080fd5b90600052602060002001600091509150505481565b600061060b306107fe565b15905090565b6106a98282604051602401610627929190611485565b6040516020818303038152906040527f9710a9d0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050610811565b5050565b600080836040516020016106c1919061133f565b6040516020818303038152906040528051906020012090506107186040518060400160405280601081526020017f4d6573736167652073656e6465723a20000000000000000000000000000000008152503361083a565b3373ffffffffffffffffffffffffffffffffffffffff1661074284836108d690919063ffffffff16565b73ffffffffffffffffffffffffffffffffffffffff161491505092915050565b6107fa828260405160240161077892919061141e565b6040516020818303038152906040527fc3b55635000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050610811565b5050565b600080823b905060008111915050919050565b60008151905060006a636f6e736f6c652e6c6f679050602083016000808483855afa5050505050565b6108d282826040516024016108509291906113ee565b6040516020818303038152906040527f319af333000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050610811565b5050565b60008060006108e585856108fd565b915091506108f281610980565b819250505092915050565b60008060418351141561093f5760008060006020860151925060408601519150606086015160001a905061093387828585610cd1565b94509450505050610979565b604083511415610970576000806020850151915060408501519050610965868383610dde565b935093505050610979565b60006002915091505b9250929050565b600060048111156109ba577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8160048111156109f3577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b14156109fe57610cce565b60016004811115610a38577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b816004811115610a71577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610ab2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610aa9906114b5565b60405180910390fd5b60026004811115610aec577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b816004811115610b25577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610b66576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b5d906114d5565b60405180910390fd5b60036004811115610ba0577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b816004811115610bd9577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610c1a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c1190611515565b60405180910390fd5b600480811115610c53577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b816004811115610c8c577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610ccd576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cc490611555565b60405180910390fd5b5b50565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08360001c1115610d0c576000600391509150610dd5565b601b8560ff1614158015610d245750601c8560ff1614155b15610d36576000600491509150610dd5565b600060018787878760405160008152602001604052604051610d5b9493929190611387565b6020604051602081039080840390855afa158015610d7d573d6000803e3d6000fd5b505050602060405103519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415610dcc57600060019250925050610dd5565b80600092509250505b94509492505050565b6000806000807f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85169150601b8560ff1c019050610e1e87828885610cd1565b935093505050935093915050565b6000610e3f610e3a846115b5565b611590565b905082815260208101848484011115610e5757600080fd5b610e628482856116f2565b509392505050565b6000610e7d610e78846115e6565b611590565b905082815260208101848484011115610e9557600080fd5b610ea08482856116f2565b509392505050565b600082601f830112610eb957600080fd5b8135610ec9848260208601610e2c565b91505092915050565b600082601f830112610ee357600080fd5b8135610ef3848260208601610e6a565b91505092915050565b600081359050610f0b81611940565b92915050565b600060208284031215610f2357600080fd5b600082013567ffffffffffffffff811115610f3d57600080fd5b610f4984828501610ed2565b91505092915050565b60008060408385031215610f6557600080fd5b600083013567ffffffffffffffff811115610f7f57600080fd5b610f8b85828601610ed2565b925050602083013567ffffffffffffffff811115610fa857600080fd5b610fb485828601610ed2565b9150509250929050565b600080600060608486031215610fd357600080fd5b600084013567ffffffffffffffff811115610fed57600080fd5b610ff986828701610ed2565b935050602084013567ffffffffffffffff81111561101657600080fd5b61102286828701610ed2565b925050604084013567ffffffffffffffff81111561103f57600080fd5b61104b86828701610ea8565b9150509250925092565b6000806040838503121561106857600080fd5b600083013567ffffffffffffffff81111561108257600080fd5b61108e85828601610ed2565b925050602061109f85828601610efc565b9150509250929050565b60006110b583836112fb565b60208301905092915050565b6110ca81611693565b82525050565b60006110db82611627565b6110e58185611655565b93506110f083611617565b8060005b8381101561112157815161110888826110a9565b975061111383611648565b9250506001810190506110f4565b5085935050505092915050565b611137816116a5565b82525050565b611146816116b1565b82525050565b61115d611158826116b1565b611765565b82525050565b600061116e82611632565b6111788185611666565b9350611188818560208601611701565b6111918161179e565b840191505092915050565b60006111a78261163d565b6111b18185611677565b93506111c1818560208601611701565b6111ca8161179e565b840191505092915050565b60006111e08261163d565b6111ea8185611688565b93506111fa818560208601611701565b80840191505092915050565b6000611213601883611677565b915061121e826117af565b602082019050919050565b6000611236601f83611677565b9150611241826117d8565b602082019050919050565b6000611259601c83611688565b915061126482611801565b601c82019050919050565b600061127c601183611677565b91506112878261182a565b602082019050919050565b600061129f602283611677565b91506112aa82611853565b604082019050919050565b60006112c2602e83611677565b91506112cd826118a2565b604082019050919050565b60006112e5602283611677565b91506112f0826118f1565b604082019050919050565b611304816116db565b82525050565b611313816116db565b82525050565b611322816116e5565b82525050565b600061133482846111d5565b915081905092915050565b600061134a8261124c565b9150611356828461114c565b60208201915081905092915050565b6000602082019050818103600083015261137f81846110d0565b905092915050565b600060808201905061139c600083018761113d565b6113a96020830186611319565b6113b6604083018561113d565b6113c3606083018461113d565b95945050505050565b600060208201905081810360008301526113e6818461119c565b905092915050565b60006040820190508181036000830152611408818561119c565b905061141760208301846110c1565b9392505050565b60006040820190508181036000830152611438818561119c565b9050611447602083018461112e565b9392505050565b60006040820190508181036000830152611468818561119c565b9050818103602083015261147c8184611163565b90509392505050565b6000604082019050818103600083015261149f818561119c565b90506114ae602083018461130a565b9392505050565b600060208201905081810360008301526114ce81611206565b9050919050565b600060208201905081810360008301526114ee81611229565b9050919050565b6000602082019050818103600083015261150e8161126f565b9050919050565b6000602082019050818103600083015261152e81611292565b9050919050565b6000602082019050818103600083015261154e816112b5565b9050919050565b6000602082019050818103600083015261156e816112d8565b9050919050565b600060208201905061158a600083018461130a565b92915050565b600061159a6115ab565b90506115a68282611734565b919050565b6000604051905090565b600067ffffffffffffffff8211156115d0576115cf61176f565b5b6115d98261179e565b9050602081019050919050565b600067ffffffffffffffff8211156116015761160061176f565b5b61160a8261179e565b9050602081019050919050565b6000819050602082019050919050565b600081519050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600082825260208201905092915050565b600081905092915050565b600061169e826116bb565b9050919050565b60008115159050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b8381101561171f578082015181840152602081019050611704565b8381111561172e576000848401525b50505050565b61173d8261179e565b810181811067ffffffffffffffff8211171561175c5761175b61176f565b5b80604052505050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b7f45434453413a20696e76616c6964207369676e61747572650000000000000000600082015250565b7f45434453413a20696e76616c6964207369676e6174757265206c656e67746800600082015250565b7f19457468657265756d205369676e6564204d6573736167653a0a333200000000600082015250565b7f496e76616c6964207369676e6174757265000000000000000000000000000000600082015250565b7f45434453413a20696e76616c6964207369676e6174757265202773272076616c60008201527f7565000000000000000000000000000000000000000000000000000000000000602082015250565b7f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160008201527f647920696e697469616c697a6564000000000000000000000000000000000000602082015250565b7f45434453413a20696e76616c6964207369676e6174757265202776272076616c60008201527f7565000000000000000000000000000000000000000000000000000000000000602082015250565b611949816116db565b811461195457600080fd5b5056fea2646970667358221220e37840e081ddeb5ea866e15b83ca8421cf9e3df36bdb2c113f6381d9c62c3b7d64736f6c63430008040033",
181 |   "linkReferences": {},
182 |   "deployedLinkReferences": {}
183 | }
184 | 
185 | export const abi = artifact.abi;


--------------------------------------------------------------------------------
/src/http.ts:
--------------------------------------------------------------------------------
 1 | import axios from 'axios';
 2 | 
 3 | export async function get(url, headers) {
 4 |   const options = {
 5 |     headers: headers,
 6 |   };
 7 |   const response = await axios.get(url, options);
 8 |   return response;
 9 | }
10 | 
11 | export async function post(url, body, headers) {
12 |   const options = {
13 |     headers: headers,
14 |   };
15 |   const response = await axios.post(url, body, options);
16 |   return response;
17 | }


--------------------------------------------------------------------------------
/src/ipfs.ts:
--------------------------------------------------------------------------------
  1 | import * as FormData from "form-data";
  2 | import * as stream from "stream";
  3 | import got from "got";
  4 | import { Estuary } from "@numbersprotocol/estuary-upload";
  5 | 
  6 | import * as http from "./http";
  7 | 
  8 | // FIXME: make Nit config to be a module
  9 | let ProjectId = "";
 10 | let ProjectSecret = "";
 11 | 
 12 | let EstuaryInstance;
 13 | let NumbersProtocolCaptureToken = ""; // Store the Capture Token
 14 | let NumbersProtocolIpfsGatewayLambda = "https://mjacdgxzla.execute-api.us-east-1.amazonaws.com/default/ipfs-add-with-payment";
 15 | let NumbersProtocolIpfsGateway = "https://ipfs-pin.numbersprotocol.io/ipfs";
 16 | 
 17 | export async function initInfura(projectId, projectSecret) {
 18 |   ProjectId = projectId;
 19 |   ProjectSecret = projectSecret;
 20 | }
 21 | 
 22 | export async function infuraAccessToken(projectId, projectSecret) {
 23 |   return Buffer.from(`${projectId}:${projectSecret}`).toString('base64');
 24 | }
 25 | 
 26 | export async function infuraIpfsAddBytes(bytes) {
 27 |   const fileReadStream = stream.Readable.from(bytes);
 28 |   const formData = new FormData();
 29 |   formData.append('file', fileReadStream);
 30 | 
 31 |   const url = "https://ipfs.infura.io:5001/api/v0/add?cid-version=1";
 32 |   const authBase64 = Buffer.from(`${ProjectId}:${ProjectSecret}`).toString('base64');
 33 |   const headers = {
 34 |     "Authorization": `Basic ${authBase64}`,
 35 |     ...formData.getHeaders(),
 36 |   };
 37 |   const httpResponse = await http.post(url, formData, headers);
 38 |   const assetCid = httpResponse.data.Hash;
 39 |   return assetCid;
 40 | }
 41 | 
 42 | export async function infuraIpfsCat(cid) {
 43 |   const url = `https://ipfs.infura.io:5001/api/v0/cat?arg=${cid}`;
 44 |   const authBase64 = Buffer.from(`${ProjectId}:${ProjectSecret}`).toString('base64');
 45 |   const requestConfig = {
 46 |     "headers": {
 47 |       "Authorization": `Basic ${authBase64}`,
 48 |     },
 49 |     timeout: { request: 30000 },
 50 |   }
 51 |   /* FIXME: Axios's response.data.lenght is smaller than content length.
 52 |    * Use Got as a temporary workardound.
 53 |    * https://github.com/axios/axios/issues/3711
 54 |    */
 55 |   const r = await got.post(url, requestConfig);
 56 |   return r.rawBody;
 57 | }
 58 | 
 59 | export async function w3sIpfsCat(cid) {
 60 |   const url = `https://${cid}.ipfs.w3s.link`;
 61 |   const requestConfig = {
 62 |     timeout: { request: 30000 },
 63 |   }
 64 |   /* FIXME: Axios's response.data.lenght is smaller than content length.
 65 |    * Use Got as a temporary workardound.
 66 |    * https://github.com/axios/axios/issues/3711
 67 |    */
 68 |   const r = await got.get(url, requestConfig);
 69 |   return r.rawBody;
 70 | }
 71 | 
 72 | export async function ipfsAddBytes(bytes) {
 73 |   return await numbersProtocolIpfsAddBytes(bytes);
 74 | }
 75 | 
 76 | export async function ipfsCat(cid) {
 77 |   return await numbersProtocolIpfsCat(cid);
 78 | }
 79 | 
 80 | export async function cidToJsonString(cid) {
 81 |   try {
 82 |     const cidContentBytes = await ipfsCat(cid);
 83 |     return cidContentBytes.toString();
 84 |   } catch(error) {
 85 |     console.error(`Failed to download content of CID ${cid}`);
 86 |     return null;
 87 |   }
 88 | }
 89 | 
 90 | export async function cidToJson(cid) {
 91 |   try {
 92 |     const cidContentString = await cidToJsonString(cid);
 93 |     return JSON.parse(cidContentString );
 94 |   } catch(error) {
 95 |     console.error(`Failed to download content of CID ${cid}`);
 96 |     return null;
 97 |   }
 98 | }
 99 | 
100 | export async function initEstuary(apiKey) {
101 |   EstuaryInstance = new Estuary(apiKey);
102 | }
103 | 
104 | export async function estuaryAdd(bytes) {
105 |   // Kudo CIDv1
106 |   let cid;
107 |   try {
108 |     cid = await EstuaryInstance.addFromBuffer(bytes);
109 |     return cid;
110 |   } catch(error) {
111 |     console.error(error);
112 |   }
113 | }
114 | 
115 | // Update this function to accept and store the Capture Token
116 | export async function initNumbersProtocol(captureToken) {
117 |   NumbersProtocolCaptureToken = captureToken;
118 | }
119 | 
120 | export async function numbersProtocolIpfsAddBytes(bytes) {
121 |   try {
122 |     // Create form data with the file
123 |     const fileReadStream = stream.Readable.from(bytes);
124 |     const formData = new FormData();
125 |     formData.append('file', fileReadStream);
126 | 
127 |     // Use Numbers Protocol IPFS add API endpoint with Capture Token in header
128 |     const url = NumbersProtocolIpfsGatewayLambda;
129 |     const headers = {
130 |       "Authorization": `token ${NumbersProtocolCaptureToken}`,
131 |       ...formData.getHeaders(),
132 |     };
133 |     
134 |     const httpResponse = await http.post(url, formData, headers);
135 |     const assetCid = httpResponse.data.cid;
136 |     return assetCid;
137 |   } catch(error) {
138 |     console.error("Error adding to Numbers Protocol IPFS:", error);
139 |     throw error;
140 |   }
141 | }
142 | 
143 | export async function numbersProtocolIpfsCat(cid) {
144 |   try {
145 |     // Use Numbers Protocol IPFS cat API endpoint with Capture Token
146 |     const url = `${NumbersProtocolIpfsGateway}/${cid}`;
147 |     const requestConfig = {
148 |       headers: {
149 |         "Authorization": `token ${NumbersProtocolCaptureToken}`
150 |       },
151 |       timeout: { request: 30000 },
152 |     };
153 |     
154 |     const r = await got.get(url, requestConfig);
155 |     return r.rawBody;
156 |   } catch(error) {
157 |     console.error(`Failed to download content of CID ${cid} from Numbers Protocol IPFS:`, error);
158 |     throw error;
159 |   }
160 | }
161 | 
162 | // Add new function to unpin content from Numbers Protocol IPFS with Capture Token
163 | export async function numbersProtocolIpfsUnpin(cid) {
164 |   try {
165 |     // Use Numbers Protocol IPFS unpin API endpoint with Capture Token
166 |     const url = NumbersProtocolIpfsGatewayLambda;
167 |     const requestConfig = {
168 |       headers: {
169 |         "Authorization": `token ${NumbersProtocolCaptureToken}`
170 |       },
171 |       timeout: { request: 30000 },
172 |     };
173 |     
174 |     const r = await got.delete(url, requestConfig);
175 |     return r.statusCode === 200;
176 |   } catch(error) {
177 |     console.error(`Failed to unpin CID ${cid} from Numbers Protocol IPFS:`, error);
178 |     return false;
179 |   }
180 | }
181 | 


--------------------------------------------------------------------------------
/src/license.ts:
--------------------------------------------------------------------------------
 1 | /* References:
 2 |  *   https://spdx.org/licenses/
 3 |  *   https://pypi.org/classifiers/
 4 |  */
 5 | 
 6 | export const Licenses = {
 7 |   "cc-by-4.0": {
 8 |     "name": "CC-BY-4.0",
 9 |     "document": "https://creativecommons.org/licenses/by/4.0/legalcode"
10 |   },
11 |   "cc-by-sa-4.0": {
12 |     "name": "CC-BY-SA-4.0",
13 |     "document": "https://creativecommons.org/licenses/by-sa/4.0/legalcode"
14 |   },
15 |   "cc-by-nd-4.0": {
16 |     "name": "CC-BY-ND-4.0",
17 |     "document": "https://creativecommons.org/licenses/by-nd/4.0/legalcode"
18 |   },
19 |   "cc-by-nc-4.0": {
20 |     "name": "CC-BY-NC-4.0",
21 |     "document": "https://creativecommons.org/licenses/by-nc/4.0/legalcode"
22 |   },
23 |   "cc-by-nc-sa-4.0": {
24 |     "name": "CC-BY-NC-SA-4.0",
25 |     "document": "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode"
26 |   },
27 |   "cc-by-nc-nd-4.0": {
28 |     "name": "CC-BY-NC-ND-4.0",
29 |     "document": "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode"
30 |   },
31 |   "mit": {
32 |     "name": "MIT",
33 |     "document": "https://opensource.org/licenses/MIT"
34 |   },
35 |   "public": {
36 |     "name": "MIT",
37 |     "document": "https://opensource.org/licenses/MIT"
38 |   },
39 | };
40 | 
41 | export const DefaultLicense: string = "cc-by-nc-4.0";
42 | 
43 | export function isSupportedLicense(licenseName: string) {
44 |   if (Object.keys(Licenses).indexOf(licenseName) > -1) {
45 |     return true;
46 |   } else {
47 |     return false;
48 |   }
49 | }


--------------------------------------------------------------------------------
/src/nit.ts:
--------------------------------------------------------------------------------
  1 | import { ethers } from "ethers";
  2 | import { BytesLike } from "@ethersproject/bytes"
  3 | 
  4 | import * as action from "./action";
  5 | import * as commitdb from "./commitdb";
  6 | import * as integrityContract from "./contract";
  7 | import * as ipfs from "./ipfs";
  8 | import * as license from "./license";
  9 | import * as util from "./util";
 10 | 
 11 | import * as colors from "colors";
 12 | import * as diff from "diff";
 13 | 
 14 | colors.enable();
 15 | 
 16 | /*----------------------------------------------------------------------------
 17 |  * Configuration
 18 |  *----------------------------------------------------------------------------*/
 19 | const privateKeyLength = 64;
 20 | const infuraSecretLength = 27;
 21 | export const cidv1Length = 59;
 22 | export const integrityHashLength = 64;
 23 | export const assetCidMock = "a".repeat(cidv1Length);
 24 | export const nitconfigTemplate = {
 25 |   /* Author's Identity CID. */
 26 |   "author": "a".repeat(cidv1Length),
 27 |   /* Committer's Identity CID. */
 28 |   "committer": "a".repeat(cidv1Length),
 29 |   /* Provider's Identity CID. */
 30 |   "provider": "a".repeat(cidv1Length),
 31 |   /* Which network config you want to use.
 32 |      One of the network names in the "network" section below.
 33 |      E.g., rinkeby */
 34 |   "defaultNetwork": "jade",
 35 |   "network": {
 36 |     "jade": {
 37 |       "url": "https://mainnetrpc.num.network",
 38 |       "chainId": 10507,
 39 |       "accounts": [
 40 |         "a".repeat(privateKeyLength)
 41 |       ],
 42 |       "contract": "0x7EC2F14ABE8b0Ea2F657bBb62b6fEfDe161c9001",
 43 |       "explorerBaseUrl": "https://mainnet.num.network/tx"
 44 |     },
 45 |     "snow": {
 46 |       "url": "https://testnetrpc.num.network",
 47 |       "chainId": 10508,
 48 |       "accounts": [
 49 |         "a".repeat(privateKeyLength)
 50 |       ],
 51 |       "contract": "0x02eFA51b583d03342687b585417e5A62cd8273a4",
 52 |       "explorerBaseUrl": "https://testnet.num.network/tx"
 53 |     },
 54 |     "avalanche": {
 55 |       "url": "https://api.avax.network/ext/bc/C/rpc",
 56 |       "chainId": 43113,
 57 |       "accounts": [
 58 |         "a".repeat(privateKeyLength)
 59 |       ],
 60 |       "contract": "0x1970aFD0831E9233667fb9484910926c2b18BCb4",
 61 |       "explorerBaseUrl": "https://snowtrace.io/tx"
 62 |     },
 63 |     "fuji": {
 64 |       "url": "https://api.avax-test.network/ext/bc/C/rpc",
 65 |       "chainId": 43114,
 66 |       "gasLimit": 200000,
 67 |       "accounts": [
 68 |         "a".repeat(privateKeyLength),
 69 |       ],
 70 |       "contract": "0xA2De03bee39Fa637440909abC5621063bC5DA926",
 71 |       "explorerBaseUrl": "https://testnet.snowtrace.io/tx"
 72 |     }
 73 |   },
 74 |   /* Infura IPFS project ID and secret for "IPFS add". */
 75 |   "infura": {
 76 |     "projectId": "a".repeat(infuraSecretLength),
 77 |     "projectSecret": "a".repeat(infuraSecretLength)
 78 |   },
 79 |   /* Numbers Protocol IPFS settings */
 80 |   "numbersProtocol": {
 81 |     "captureToken": "" // Capture Token for Numbers Protocol IPFS API
 82 |   },
 83 |   "commitDatabase": {
 84 |     "updateUrl": "",
 85 |     "commitUrl": "",
 86 |   }
 87 | };
 88 | 
 89 | export const blockchainNames = {
 90 |   10507: "jade",
 91 |   10508: "snow",
 92 |   43113: "fuji",
 93 |   43114: "avalanche",
 94 | };
 95 | 
 96 | export async function loadBlockchain(config) {
 97 |   const networkConfig = config.network[config.defaultNetwork];
 98 |   const provider = new ethers.providers.JsonRpcProvider(networkConfig.url);
 99 |   const contractAbi = integrityContract.abi;
100 | 
101 |   const [signerPrivateKey, user] = networkConfig.accounts;
102 |   let signer = new ethers.Wallet(signerPrivateKey);
103 |   signer = signer.connect(provider);
104 | 
105 |   const contract = new ethers.Contract(networkConfig.contract, contractAbi, signer);
106 | 
107 |   return {
108 |     "contract": contract,
109 |     "signer": signer,
110 |     "chainId": networkConfig.chainId,
111 |     "gasLimit": networkConfig.gasLimit,
112 |     "gasPrice": networkConfig.gasPrice ? networkConfig.gasPrice : null,
113 |     "explorerBaseUrl": networkConfig.explorerBaseUrl,
114 |     "provider": provider
115 |   };
116 | }
117 | 
118 | const configurableAssetTreeKeys = [
119 |   "assetCreator",
120 |   "assetTimestampCreated",
121 |   "license",
122 |   "nftRecord",
123 |   "integrityCid",
124 |   "abstract",
125 |   "encodingFormat",
126 | ];
127 | 
128 | /*----------------------------------------------------------------------------
129 |  * Commands
130 |  *----------------------------------------------------------------------------*/
131 | async function createAssetTreeBaseRemote(assetByes, assetMimetype) {
132 |   let stagingAssetTree: any = {};
133 |   try {
134 |     stagingAssetTree.assetCid = await ipfs.ipfsAddBytes(assetByes);
135 |     // remove leading 0x to as the same as most sha256 tools
136 |     stagingAssetTree.assetSha256 = (await ethers.utils.sha256(assetByes)).substring(2);
137 |     stagingAssetTree.encodingFormat = assetMimetype;
138 |   } catch(error) {
139 |     console.error(`${error}`);
140 |     stagingAssetTree = {};
141 |   }
142 |   return stagingAssetTree;
143 | }
144 | 
145 | async function createAssetTreeBase(assetCid, assetSha256, assetMimetype) {
146 |   let stagingAssetTree: any = {};
147 |   try {
148 |     stagingAssetTree.assetCid = assetCid;
149 |     stagingAssetTree.assetSha256 = assetSha256
150 |     stagingAssetTree.encodingFormat = assetMimetype;
151 |   } catch(error) {
152 |     console.error(`${error}`);
153 |     stagingAssetTree = {};
154 |   }
155 |   return stagingAssetTree;
156 | }
157 | 
158 | async function createCommitBase(signer, assetTree, authorAddress, providerCid) {
159 |   let stagingCommit: any = {};
160 | 
161 |   const assetTreeBytes = Buffer.from(JSON.stringify(assetTree, null, 2));
162 |   stagingCommit.assetTreeCid = await ipfs.ipfsAddBytes(assetTreeBytes);
163 |   stagingCommit.assetTreeSha256 = (await ethers.utils.sha256(Buffer.from(JSON.stringify(assetTree, null, 2)))).substring(2);
164 |   // remove leading 0x to as the same as most sha256 tools
165 |   stagingCommit.assetTreeSignature = await signIntegrityHash(stagingCommit.assetTreeSha256, signer);
166 |   stagingCommit.author = authorAddress;
167 |   stagingCommit.committer = signer.address;
168 |   stagingCommit.provider = providerCid;
169 |   stagingCommit.timestampCreated = Math.floor(Date.now() / 1000);
170 | 
171 |   return stagingCommit;
172 | }
173 | 
174 | /* Hidden rule: add actionName in commitData
175 |  *
176 |  * Check actionName is sent in commitData,
177 |  *   if exist, use the actionName defined by the App,
178 |  *   if not exist, check the given Action Nid (the action field in commitData),
179 |  *     if valid, use the mapped actionName
180 |  *     if invalid, use the default actionName "action-commit"
181 |  */
182 | function addActionNameInCommit(commitData: string) {
183 |   const commitJson = JSON.parse(commitData);
184 |   if (!commitJson.hasOwnProperty("actionName")) {
185 |     if (commitJson.hasOwnProperty("action")) {
186 |       const nid = commitJson["action"];
187 |       const key = action.getNameByNid(nid);
188 |       commitJson["actionName"] = key;
189 |     } else {
190 |       commitJson["actionName"] = "action-commit";
191 |     }
192 |   }
193 |   return JSON.stringify(commitJson);
194 | }
195 | 
196 | function addLicenseInAssetTree(assetTree: {[key: string]: any}, assetLicense?: string | undefined) {
197 |   if (assetLicense) {
198 |     if (license.isSupportedLicense(assetLicense)) {
199 |       assetTree.license = license.Licenses[assetLicense];
200 |     } else {
201 |       console.error(`Get unsupported license: ${assetLicense}\n`);
202 |     }
203 |   }
204 | }
205 | 
206 | function addAbstractInAssetTree(assetTree: {[key: string]: any}, assetAbstract?: string | undefined) {
207 |   if (assetAbstract !== undefined) {
208 |     assetTree.abstract = assetAbstract;
209 |   }
210 | }
211 | 
212 | export async function createAssetTreeInitialRegisterRemote(assetBytes,
213 |                                                            assetMimetype,
214 |                                                            assetTimestampCreated,
215 |                                                            assetCreator,
216 |                                                            assetLicense: string | undefined = undefined,
217 |                                                            assetAbstract: string | undefined = undefined) {
218 |   let stagingAssetTree = await createAssetTreeBaseRemote(assetBytes, assetMimetype);
219 |   stagingAssetTree.assetTimestampCreated= assetTimestampCreated;
220 |   stagingAssetTree.assetCreator = assetCreator;
221 |   addLicenseInAssetTree(stagingAssetTree, assetLicense);
222 |   addAbstractInAssetTree(stagingAssetTree, assetAbstract);
223 |   return stagingAssetTree;
224 | }
225 | 
226 | export async function createAssetTreeInitialRegister(assetCid,
227 |                                                      assetSha256,
228 |                                                      assetMimetype,
229 |                                                      assetTimestampCreated,
230 |                                                      assetCreator,
231 |                                                      assetLicense: string | undefined = undefined,
232 |                                                      assetAbstract: string | undefined = undefined) {
233 |   let stagingAssetTree = await createAssetTreeBase(assetCid, assetSha256, assetMimetype);
234 |   stagingAssetTree.assetTimestampCreated= assetTimestampCreated;
235 |   stagingAssetTree.assetCreator = assetCreator;
236 |   addLicenseInAssetTree(stagingAssetTree, assetLicense);
237 |   addAbstractInAssetTree(stagingAssetTree, assetAbstract);
238 |   return stagingAssetTree;
239 | }
240 | 
241 | export async function createCommitInitialRegister(signer,
242 |                                                   assetTree,
243 |                                                   authorAddress,
244 |                                                   providerCid,
245 |                                                   commitMessage="Action: action-initial-registration.",
246 |                                                   commitAction="action-initial-registration-jade") {
247 |   let stagingCommit = await createCommitBase(signer, assetTree, authorAddress, providerCid);
248 |   stagingCommit.action = action.Actions[commitAction];
249 |   stagingCommit.actionResult = `https://${stagingCommit.assetTreeCid}.ipfs.dweb.link`;
250 |   stagingCommit.abstract = commitMessage;
251 |   stagingCommit.timestampCreated = Math.floor(Date.now() / 1000);
252 |   return stagingCommit;
253 | }
254 | 
255 | export async function createCommit(signer, assetTree, authorAddress, providerCid) {
256 |   let stagingCommit = await createCommitBase(signer, assetTree, authorAddress, providerCid);
257 |   stagingCommit.action = "";
258 |   stagingCommit.actionResult = "";
259 |   stagingCommit.abstract = `Nit Commit created by ${signer.address}.`;
260 |   stagingCommit.timestampCreated = Math.floor(Date.now() / 1000);
261 |   return stagingCommit;
262 | }
263 | 
264 | /*
265 | export async function createCommitMintErc721Nft(signer, authorCid, committerCid, providerCid, actionIndex, actionResult) {
266 |   stagingCommit = await createCommitBase(signer, authorCid, committerCid, providerCid);
267 |   stagingCommit.action = action.Actions[actionIndex];
268 |   stagingCommit.actionResult = actionResult;
269 |   stagingCommit.abstract = "Mint ERC-721 NFT.";
270 |   stagingCommit.timestampCreated = Math.floor(Date.now() / 1000);
271 |   return stagingCommit;
272 | }
273 | */
274 | 
275 | export async function updateAssetTreeLegacy(assetTree, assetTreeUpdates) {
276 |   const assetTreeKeySet = new Set(configurableAssetTreeKeys);
277 |   const assetTreeUpdatesKeySet = new Set(Object.keys(assetTreeUpdates));
278 | 
279 |   const isSuperset = util.isSuperset(assetTreeKeySet, assetTreeUpdatesKeySet);
280 | 
281 |   if (isSuperset) {
282 |     for (let key of Object.keys(assetTreeUpdates)) {
283 |       assetTree[key] = assetTreeUpdates[key];
284 |     }
285 |     return assetTree;
286 |   } else {
287 |     // find illegal assetTreeUpdates, return assetTree directly
288 |     console.log(`Asset Tree Updates is not a legal subset`);
289 |     return assetTree
290 |   }
291 | }
292 | 
293 | export async function updateAssetTree(assetTree, assetTreeUpdates) {
294 |   /* Extend Asset Tree with given updates.
295 |    */
296 |   let assetTreeCopy = util.deepCopy(assetTree);
297 |   for (let key of Object.keys(assetTreeUpdates)) {
298 |     assetTreeCopy[key] = assetTreeUpdates[key];
299 |   }
300 |   return assetTreeCopy;
301 | }
302 | 
303 | export async function pull(assetCid: string, blockchainInfo) {
304 |   const latestCommit = await getLatestCommitSummary(assetCid, blockchainInfo);
305 |   if (latestCommit != null) {
306 |     const assetTree = await getAssetTree(latestCommit.commit.assetTreeCid);
307 |     return assetTree;
308 |   } else {
309 |     return null
310 |   }
311 | }
312 | 
313 | //export async function add(assetCid, assetUpdates, blockchainInfo) {
314 | //  const latestAssetTree = await pull(assetCid, blockchainInfo);
315 | //  if (latestAssetTree != null) {
316 | //    return await updateAssetTree(latestAssetTree, assetUpdates);
317 | //  } else {
318 | //    // Asset has not been registered
319 | //  }
320 | //}
321 | 
322 | export async function commit(assetCid: string, commitData: string, blockchainInfo, confirms: number = 1) {
323 |   const commitString = addActionNameInCommit(commitData);
324 |   let r;
325 |   if (blockchainInfo.gasPrice != null) {
326 |     console.log(`Gas Price: ${blockchainInfo.gasPrice} Wei`);
327 |     r = await blockchainInfo.contract.commit(assetCid, commitString, { gasLimit: blockchainInfo.gasLimit, gasPrice: blockchainInfo.gasPrice });
328 |   } else {
329 |     r = await blockchainInfo.contract.commit(assetCid, commitString, { gasLimit: blockchainInfo.gasLimit });
330 |   }
331 | 
332 |   // Wait for the transaction to be mined
333 |   return await r.wait(confirms);
334 | }
335 | 
336 | export async function log(assetCid: string, blockchainInfo, fromIndex: number, toIndex: number = null) {
337 |   const network = await blockchainInfo.provider.getNetwork();
338 | 
339 |   if (toIndex <= fromIndex) {
340 |     const commitBlockNumbers = await getCommitBlockNumbers(assetCid, blockchainInfo);
341 |     const commitAmount: number = commitBlockNumbers.length;
342 |     toIndex = commitAmount;
343 |   }
344 | 
345 |   const commitEvents = await iterateCommitEvents(assetCid, blockchainInfo, fromIndex, toIndex);
346 |   const commits = await getCommits(commitEvents);
347 |   await showCommits(commits);
348 | }
349 | 
350 | export async function getCommit(assetCid: string, blockchainInfo, blockNumber: number) {
351 |   const filter = await blockchainInfo.contract.filters.Commit(null, assetCid);
352 |   const abi = [
353 |     "event Commit(address indexed recorder, string indexed assetCid, string commitData)"
354 |   ];
355 |   const commitEventInterface = new ethers.utils.Interface(abi);
356 | 
357 |   let events = [];
358 | 
359 |   filter.fromBlock = blockNumber;
360 |   filter.toBlock = blockNumber;
361 |   const eventLogs = await blockchainInfo.provider.getLogs(filter);
362 | 
363 |   for (const eventLog of eventLogs) {
364 |     const commitEvent = commitEventInterface.parseLog(eventLog);
365 |     // merge eventLog and commitEvent
366 |     events.push(Object.assign({}, eventLog, commitEvent));
367 |   }
368 | 
369 |   const commits = await getCommits(events);
370 |   console.log(`${JSON.stringify(commits[0].commit)}`);
371 | 
372 |   return commits[0].commit;
373 |   //await showCommits(commits);
374 | }
375 | 
376 | export async function showCommits(commits) {
377 |   commits.map(commit => {
378 |     console.log(`\nblock number: ${colors.blue(commit.blockNumber)}`);
379 |     console.log(`tx: ${colors.green(commit.transactionHash)}`);
380 |     console.log(`${JSON.stringify(commit.commit, null, 2)}`);
381 |   });
382 | }
383 | 
384 | export async function showCommmitDiff(commitDiff) {
385 |   commitDiff.forEach((part) => {
386 |     const color = part.added ? 'green' : part.removed ? 'red' : 'grey';
387 |     console.log(`${part.value.replace(/\n$/, "")[color]}`);
388 |   });
389 | }
390 | 
391 | export async function difference(assetCid: string, blockchainInfo, fromIndex: number = null, toIndex: number = null) {
392 |   const commitBlockNumbers = await getCommitBlockNumbers(assetCid, blockchainInfo);
393 |   const commitAmount = commitBlockNumbers.length;
394 | 
395 |   // show the Commit diff between (latest - 1, latest)
396 |   if (fromIndex == null) {
397 |     toIndex = commitAmount;
398 |     fromIndex = toIndex - 2;
399 |   }
400 | 
401 |   const commitEvents = await iterateCommitEvents(assetCid, blockchainInfo, fromIndex, fromIndex < toIndex ? toIndex : fromIndex + 1);
402 |   const commits = await getCommits(commitEvents);
403 |   const fromCommit = commits[0];
404 |   const toCommit = commits[commits.length - 1];
405 |   const fromAssetTree = JSON.parse((await ipfs.ipfsCat(fromCommit.commit.assetTreeCid)).toString());
406 |   const toAssetTree = JSON.parse((await ipfs.ipfsCat(toCommit.commit.assetTreeCid)).toString());
407 |   const commitDiff = {
408 |     "fromIndex": fromIndex,
409 |     "fromBlockNumber": fromCommit.blockNumber,
410 |     "fromTransactionHash": fromCommit.transactionHash,
411 |     "toIndex": toIndex,
412 |     "toBlockNumber": toCommit.blockNumber,
413 |     "toTransactionHash": toCommit.transactionHash,
414 |     "commitDiff": diff.diffJson(fromCommit.commit, toCommit.commit),
415 |     "assetTreeDiff": diff.diffJson(fromAssetTree, toAssetTree),
416 |   }
417 |   return commitDiff;
418 | }
419 | 
420 | export async function getLatestCommitSummary(assetCid, blockchainInfo) {
421 |   const commitBlockNumbers = await getCommitBlockNumbers(assetCid, blockchainInfo);
422 |   const commitAmount = commitBlockNumbers.length;
423 |   const events = await iterateCommitEvents(assetCid, blockchainInfo, commitAmount - 1, commitAmount);
424 |   const commitsSummary = await getCommitsSummary(events);
425 |   return commitsSummary.pop();
426 | }
427 | 
428 | export async function getAssetTree(assetTreeCid) {
429 |   const assetTreeBytes = await ipfs.ipfsCat(assetTreeCid);
430 |   const assetTree = JSON.parse(assetTreeBytes.toString());
431 |   return assetTree;
432 | }
433 | 
434 | export async function getCommitBlockNumbers(assetCid: string, blockchainInfo) {
435 |   const commits = await blockchainInfo.contract.getCommits(assetCid);
436 |   return commits.length > 0 ? commits.map(element => element.toNumber()) : [];
437 | }
438 | 
439 | export async function filterCommitEvents(assetCid: string, blockchainInfo, fromIndex, toIndex) {
440 |   /* Have 3 more keys than eventLog in iterateCommitEvents: event, eventSignature, args
441 |    *
442 |    * keys:
443 |    *   blockNumber, blockHash, transactionIndex, removed, address, data, topics
444 |    *   transactionHash, logIndex, event, eventSignature, args
445 |    */
446 |   const commitBlockNumbers = (await getCommitBlockNumbers(assetCid, blockchainInfo)).slice(fromIndex, toIndex);
447 |   const commitAmount = commitBlockNumbers.length;
448 | 
449 |   if (commitAmount == 0) { return []; }
450 | 
451 |   const filter = await blockchainInfo.contract.filters.Commit(null, assetCid);
452 |   filter.fromBlock = commitBlockNumbers[0];
453 |   filter.toBlock = commitBlockNumbers[commitAmount - 1];
454 | 
455 |   let events = await blockchainInfo.contract.queryFilter(filter, filter.fromBlock, filter.toBlock);
456 |   return events;
457 | }
458 | 
459 | export async function iterateCommitEvents(assetCid: string, blockchainInfo, fromIndex, toIndex) {
460 |   /* Get Commit events by using low-level event logs.
461 |    *
462 |    * If a blockchain provider does not support getting events by filter,
463 |    * (e.g., Avalanche), you can use this function.
464 |    *
465 |    * keys in eventLog:
466 |    *   blockNumber, blockHash, transactionIndex, removed, address, data, topics
467 |    *   transactionHash, logIndex,
468 |    * keys in commitEvent:
469 |    *   eventFragment, name, signature, args, topic, args
470 |    */
471 |   const commitBlockNumbers = (await getCommitBlockNumbers(assetCid, blockchainInfo)).slice(fromIndex, toIndex);
472 |   const commitAmount = commitBlockNumbers.length;
473 | 
474 |   if (commitAmount == 0) { return []; }
475 | 
476 |   const filter = await blockchainInfo.contract.filters.Commit(null, assetCid);
477 |   const abi = [
478 |     "event Commit(address indexed recorder, string indexed assetCid, string commitData)"
479 |   ];
480 |   const commitEventInterface = new ethers.utils.Interface(abi);
481 | 
482 |   let events = [];
483 | 
484 |   for (const c of commitBlockNumbers) {
485 |     filter.fromBlock = c;
486 |     filter.toBlock = c;
487 |     const eventLogs = await blockchainInfo.provider.getLogs(filter);
488 | 
489 |     for (const eventLog of eventLogs) {
490 |       const commitEvent = commitEventInterface.parseLog(eventLog);
491 |       // merge eventLog and commitEvent
492 |       events.push(Object.assign({}, eventLog, commitEvent));
493 |     }
494 |   }
495 | 
496 |   return events;
497 | }
498 | 
499 | export async function getCommits(events) {
500 |   const commitDataIndex = 2;
501 |   const commits = events.map(event => {
502 |     let commit;
503 |     try {
504 |         commit = JSON.parse(event.args[commitDataIndex]);
505 |     } catch (error) {
506 |         console.error(`Cannot get valid Commit from block ${event.blockNumber}`);
507 |         commit = {};
508 |     }
509 |     return {
510 |       "blockNumber": event.blockNumber,
511 |       "transactionHash": event.transactionHash,
512 |       "commit": commit,
513 |     };
514 |   });
515 |   return commits;
516 | }
517 | 
518 | export async function getCommitsSummary(events) {
519 |   const commitDataIndex = 2;
520 |   const commitsSummary = events.map(element => {
521 |     let summary = { blockNumber: 0, txHash: "", commit: {} };
522 |     summary.commit = JSON.parse(element.args[commitDataIndex]);
523 |     summary.blockNumber = element.blockNumber;
524 |     summary.txHash = element.transactionHash;
525 |     return summary;
526 |   });
527 |   return commitsSummary;
528 | }
529 | 
530 | /* TODO: Remove this function in the next feature release.
531 |  *
532 |  * Query events on Ethereum. The performance is faster than eventLogIteratingQuery.
533 |  */
534 | async function eventLogRangeQuery(assetCid: string, blockchainInfo) {
535 |   console.log(`Commit logs of assetCid: ${assetCid}`);
536 | 
537 |   const commitBlockNumbers = await getCommitBlockNumbers(assetCid, blockchainInfo);
538 |   const commitAmount = commitBlockNumbers.length;
539 |   console.log(`Commit block numbers (${commitBlockNumbers.length}):`);
540 |   if (commitAmount > 0) {
541 |     console.log(`${JSON.stringify(commitBlockNumbers)}`);
542 |   } else {
543 |     return;
544 |   }
545 | 
546 |   // Get events
547 |   const filter = await blockchainInfo.contract.filters.Commit(null, assetCid);
548 |   filter.fromBlock = commitBlockNumbers[0];
549 |   filter.toBlock = commitBlockNumbers[commitAmount - 1];
550 |   let events = await blockchainInfo.contract.queryFilter(filter, filter.fromBlock, filter.toBlock);
551 |   const commitDataIndex = 2;
552 |   for (const event of events) {
553 |     console.log(`\nBlock ${event.blockNumber}`);
554 |     console.log(`${blockchainInfo.explorerBaseUrl}/${event.transactionHash}`)
555 |     console.log(`Commit: ${JSON.stringify(JSON.parse(event.args![commitDataIndex]), null, 2)}`);
556 |   }
557 | }
558 | 
559 | /* TODO: Remove this function in the next feature release.
560 |  */
561 | async function eventLogIteratingQuery(assetCid: string, blockchainInfo) {
562 |   console.log(`assetCid: ${assetCid}`);
563 | 
564 |   const commitBlockNumbers = await getCommitBlockNumbers(assetCid, blockchainInfo);
565 |   const commitAmount = commitBlockNumbers.length;
566 |   console.log(`Commit block numbers (${commitBlockNumbers.length}):`);
567 |   if (commitAmount > 0) {
568 |     console.log(`${JSON.stringify(commitBlockNumbers)}`);
569 |   } else {
570 |     return;
571 |   }
572 | 
573 |   /* WORKAROUND: create filters for every commit because Avalanche
574 |    * only supports 2048-block range in an event log query.
575 |    *
576 |    * The query performance is more worse than eventLogRangeQuery,
577 |    * but it's acceptable on Avalanche.
578 |    */
579 |   // Create event log filter
580 |   let filter = await blockchainInfo.contract.filters.Commit(null, assetCid);
581 |   const abi = [
582 |     "event Commit(address indexed recorder, string indexed assetCid, string commitData)"
583 |   ];
584 |   console.log(`Filter: ${JSON.stringify(filter, null, 2)}`);
585 |   for (const c of commitBlockNumbers) {
586 |     // Get event log by filter
587 |     filter.fromBlock = c;
588 |     filter.toBlock = c;
589 |     const eventLogs = await blockchainInfo.provider.getLogs(filter);
590 | 
591 |     const commitEventInterface = new ethers.utils.Interface(abi);
592 |     for (const eventLog of eventLogs) {
593 |       console.log(`\nBlock number: ${(eventLog.blockNumber)}`);
594 |       console.log(`${blockchainInfo.explorerBaseUrl}/${(eventLog.transactionHash)}`);
595 | 
596 |       const commitEvent = commitEventInterface.parseLog(eventLog);
597 |       console.log(`commitEvent: ${JSON.stringify(commitEvent, null, 2)}`);
598 | 
599 |       try {
600 |         const commitData = JSON.parse(commitEvent.args[2]);
601 |         console.log(`Commit: ${JSON.stringify(commitData, null, 2)}`);
602 |       } catch (error) {
603 |         console.error(`Failed to parse Commit, error: ${error}`);
604 |       }
605 |     }
606 |   }
607 | }
608 | 
609 | /*----------------------------------------------------------------------------
610 |  * Ethereum Signature
611 |  *----------------------------------------------------------------------------*/
612 | export async function signIntegrityHash(sha256sum: string, signer) {
613 |   let signature = await signer.signMessage(sha256sum);
614 |   return signature;
615 | }
616 | 
617 | export async function verifyIntegrityHash(sha256sum: string, signature) {
618 |   const recoveredAddress = await ethers.utils.verifyMessage(sha256sum, signature);
619 |   return recoveredAddress;
620 | }
621 | 
622 | export async function getIntegrityHash(assetBytes: BytesLike) {
623 |   return await (ethers.utils.sha256(assetBytes)).substring(2);
624 | }
625 | 


--------------------------------------------------------------------------------
/src/run.ts:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env ts-node
  2 | 
  3 | import fs = require("fs");
  4 | import os = require("os");
  5 | 
  6 | import commandLineArgs = require("command-line-args");
  7 | import commandLineUsage = require("command-line-usage");
  8 | import mime = require("mime-types");
  9 | import sha256 = require("crypto-js/sha256");
 10 | 
 11 | import got from "got";
 12 | import * as colors from "colors";
 13 | 
 14 | import * as action from "./action";
 15 | import * as commitdb from "./commitdb";
 16 | import * as ipfs from "./ipfs";
 17 | import * as license from "./license";
 18 | import * as nit from "./nit";
 19 | 
 20 | const launch = require("launch-editor");
 21 | 
 22 | colors.enable();
 23 | 
 24 | /*----------------------------------------------------------------------------
 25 |  * Configuration
 26 |  *----------------------------------------------------------------------------*/
 27 | const configFilepath = `${os.homedir()}/.nitconfig.json`;
 28 | const workingDir = `.nit`;
 29 | 
 30 | async function setWorkingAssetCid(assetCid: string) {
 31 |   if (fs.existsSync(`${workingDir}`) === false) {
 32 |     console.log(`Create working dir ${workingDir}`);
 33 |     fs.mkdirSync(`${workingDir}`, { recursive: true });
 34 |   } else {
 35 |     // working dir exists
 36 |   }
 37 |   fs.writeFileSync(`${workingDir}/working.json`, JSON.stringify({"assetCid": assetCid}, null, 2));
 38 | }
 39 | 
 40 | async function getWorkingAssetCid() {
 41 |   const workingConfig = JSON.parse(fs.readFileSync(`${workingDir}/working.json`, "utf-8"));
 42 |   return workingConfig.assetCid;
 43 | }
 44 | 
 45 | async function loadConfig() {
 46 |   const config = JSON.parse(fs.readFileSync(`${configFilepath}`, "utf-8"));
 47 |   return config;
 48 | }
 49 | 
 50 | async function writeConfig(configData: Object) {
 51 |   const nitconfig = `${configFilepath}`;
 52 |   if (fs.existsSync(nitconfig) === false) {
 53 |     fs.writeFileSync(nitconfig,
 54 |                      JSON.stringify(configData, null, 2),
 55 |                      { flag: "wx+" });
 56 |   } else {
 57 |     console.warn(`Nit config ${nitconfig} exists.`);
 58 |   }
 59 | }
 60 | 
 61 | /*----------------------------------------------------------------------------
 62 |  * I/O
 63 |  *----------------------------------------------------------------------------*/
 64 | async function stage(assetCid, stagedAssetTree, stagedCommit) {
 65 |   // Create staged dir whose name is assetCid
 66 |   const commitDir = `${workingDir}/${assetCid}`;
 67 |   if (fs.existsSync(commitDir) === false) {
 68 |     fs.mkdirSync(commitDir, { recursive: true });
 69 |   } else {}
 70 | 
 71 |   fs.writeFileSync(`${commitDir}/assetTree.json`, JSON.stringify(stagedAssetTree, null, 2));
 72 |   fs.writeFileSync(`${commitDir}/commit.json`, JSON.stringify(stagedCommit, null, 2));
 73 |   await setWorkingAssetCid(assetCid);
 74 | }
 75 | 
 76 | async function getStagedCommit(assetCid) {
 77 |   return JSON.parse(fs.readFileSync(`${workingDir}/${assetCid}/commit.json`, "utf-8"));
 78 | }
 79 | 
 80 | async function getStagedAssetTree(assetCid) {
 81 |   return JSON.parse(fs.readFileSync(`${workingDir}/${assetCid}/assetTree.json`, "utf-8"));
 82 | }
 83 | 
 84 | /*----------------------------------------------------------------------------
 85 |  * CLI Usage
 86 |  *----------------------------------------------------------------------------*/
 87 | async function help() {
 88 |   const logo = `
 89 |     ████████████████████████████████████
 90 |     ████████████████████████████████████
 91 |     ████████████████████████████████████
 92 |     ████████████████████████████████████
 93 |     ████████▀▀▀▀▀▀████████▀▀▀▀▀▀████████
 94 |     ████████    ▄█▌ ▀████▌      ████████
 95 |     ████████  Φ███▌   ▀██▌      ████████
 96 |     ████████   ╙▀█▌     ╙▀      ████████
 97 |     ████████      L      ▐█▄    ████████
 98 |     ████████      ▐█▄    ▐███▄  ████████
 99 |     ████████      ▐███▄  ▐██▀   ████████
100 |     ████████      ▐██████▄▀     ████████
101 |     ████████████████████████████████████
102 |     ████████████████████████████████████
103 |     ████████████████████████████████████
104 |     ████████████████████████████████████
105 |     ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
106 |   `;
107 |   const sections = [
108 |     {
109 |       content: logo,
110 |       raw: true
111 |     },
112 |     {
113 |       header: "Available Commands",
114 |       content: [
115 |         "init      Initialize working environment",
116 |         "config    Edit Nit configuration",
117 |         "add       Add assetTree",
118 |         "status    Show current temporary commit",
119 |         "commit    Generate and register commit to web3",
120 |         "sign      Create integrity signature",
121 |         "verify    Verify integrity signature",
122 |         "log       Show asset's commits",
123 |         "diff      Show diff between two commits",
124 |         "help      Show this usage tips",
125 |       ]
126 |     },
127 |     {
128 |       header: 'init',
129 |       content: [
130 |         "$ nit init",
131 |       ]
132 |     },
133 |     {
134 |       header: 'config',
135 |       content: [
136 |         "$ nit config -e|--edit",
137 |         "$ nit config -l|--list",
138 |       ]
139 |     },
140 |     {
141 |       header: 'add',
142 |       content: [
143 |         "$ nit add {underline assetFilepath} -m|--message {underline abstract}",
144 |         "$ nit add {underline assetFilepath} -m|--message {underline abstract} --nft-record-cid {underline cid}",
145 |         "$ nit add {underline assetFilepath} -m|--message {underline abstract} --integrity-cid {underline cid}",
146 |         "$ nit add {underline assetFilepath} -m|--message {underline abstract} --license mit",
147 |         "$ nit add {underline assetFilepath} -m|--message {underline abstract} --custom-license {underline license-json}",
148 |         "$ nit add {underline assetFilepath} -m|--message {underline abstract} --nft-record-cid {underline cid} --integrity-cid {underline cid}",
149 |         "$ nit add {underline assetFilepath} -m|--message {underline abstract} --nft-record-cid {underline cid} --integrity-cid {underline cid} --license mit",
150 |       ]
151 |     },
152 |     {
153 |       header: 'addv1',
154 |       content: [
155 |         "$ nit addv1 {underline assetTreeFilepath}",
156 |       ]
157 |     },
158 |     {
159 |       header: 'status',
160 |       content: [
161 |         "$ nit status",
162 |       ]
163 |     },
164 |     {
165 |       header: 'commit',
166 |       content: [
167 |         "$ nit commit -m|--message {underline abstract} -a|--action {underline action} -r|--action-result {underline actionResult}",
168 |         "$ nit commit -m|--message {underline abstract} -a|--action {underline action} -r|--action-result {underline actionResult} --dry-run",
169 |         "$ nit commit -m|--message {underline abstract} -a|--action {underline action} -r|--action-result {underline actionResult} --mockup",
170 |       ]
171 |     },
172 |     {
173 |       header: 'commit options',
174 |       optionList: [
175 |         {
176 |           "name": "message",
177 |           "description": 'Discription of this commit. The message will be in the "abstract" field in a Commit.',
178 |           "alias": "m",
179 |           "typeLabel": "{underline commit-description}"
180 |         },
181 |         {
182 |           "name": "action",
183 |           "description": '(Under-development) The action happened on the targeting ditigal asset (addressed by Asset CID). The message will be in the "action" field in a Commit. You can put arbitrary value currently.',
184 |           "alias": "a",
185 |           "typeLabel": "{underline commit-description}"
186 |         },
187 |         {
188 |           "name": "action-result",
189 |           "description": '(Under-development) The execution result of the action. The message will be in the "actionResult" field in a Commit. You can put arbitrary value currently.',
190 |           "alias": "r",
191 |           "typeLabel": "{underline commit-description}"
192 |         },
193 |         {
194 |           "name": "dry-run",
195 |           "description": "Only show the Commit content and will not commit to blockchain. The added Asset Tree will not be cleaned."
196 |         },
197 |         {
198 |           "name": "mockup",
199 |           "description": "Use Asset CID mockup (59 'a' chars) as Commit's targeting digital asset."
200 |         },
201 |       ]
202 |     },
203 |     {
204 |       header: 'sign',
205 |       content: [
206 |         "$ nit sign -i|--integrity-hash {underline integrityHash}",
207 |       ]
208 |     },
209 |     {
210 |       header: 'verify',
211 |       content: [
212 |         "$ nit verify -i|--integrity-hash {underline integrityHash} -s|--signature {underline signature}",
213 |       ]
214 |     },
215 |     {
216 |       header: 'log',
217 |       content: [
218 |         "$ nit log {underline assetCid}",
219 |         "$ nit log {underline assetCid} --blocks",
220 |         "$ nit log {underline assetCid} --from-index|-f {underline blockNumberIndex}",
221 |         "$ nit log {underline assetCid} --from-index|-f {underline blockNumberIndex} --to-index|-t {underline blockNumberIndex}",
222 |       ]
223 |     },
224 |     {
225 |       header: 'log options',
226 |       optionList: [
227 |         {
228 |           "name": "blocks",
229 |           "description": 'Return block numbers of Commits and their indices in the block number array.',
230 |           "alias": "b",
231 |           "typeLabel": "{underline bool}",
232 |         },
233 |         {
234 |           "name": "from-index",
235 |           "description": 'Get log starting from the block number related to this index. Range: [from, to)',
236 |           "alias": "f",
237 |           "typeLabel": "{underline blockNumberIndex}",
238 |         },
239 |         {
240 |           "name": "to-index",
241 |           "description": 'Get log ending before the block number related to this index. Range: [from, to)',
242 |           "alias": "t",
243 |           "typeLabel": "{underline blockNumberIndex}",
244 |         },
245 |       ]
246 |     },
247 |     {
248 |       header: 'diff',
249 |       content: [
250 |         "$ nit diff {underline assetCid}",
251 |         "$ nit diff {underline assetCid} --from|-f {underline blockNumberIndex}",
252 |         "$ nit diff {underline assetCid} --from|-f {underline blockNumberIndex} --to|-t {underline blockNumberIndex}",
253 |       ]
254 |     },
255 |   ]
256 |   const usage = commandLineUsage(sections)
257 |   console.log(usage)
258 | }
259 | 
260 | async function parseArgs() {
261 |   const commandDefinitions = [
262 |     { name: "command", defaultOption: true },
263 |   ];
264 |   const commandOptions = commandLineArgs(commandDefinitions,
265 |                                          { stopAtFirstUnknown: true });
266 |   const argv = commandOptions._unknown || [];
267 | 
268 |   if (commandOptions.command === "ipfsadd") {
269 |     const paramDefinitions = [
270 |       { name: "filepath", defaultOption: true },
271 |     ];
272 |     const paramOptions = commandLineArgs(paramDefinitions,
273 |                                          { argv, stopAtFirstUnknown: true });
274 |     return {
275 |       "command": "ipfsadd",
276 |       "params": {
277 |         "fileapth": paramOptions.filepath,
278 |       }
279 |     }
280 |   } else if (commandOptions.command === "init") {
281 |     return {
282 |       "command": "init",
283 |       "params": {}
284 |     }
285 |   } else if (commandOptions.command === "addv1") {
286 |     const paramDefinitions = [
287 |       { name: "filepath", defaultOption: true },
288 |     ];
289 |     const paramOptions = commandLineArgs(paramDefinitions,
290 |                                          { argv, stopAtFirstUnknown: true });
291 |     return {
292 |       "command": "addv1",
293 |       "params": {
294 |         "filepath": paramOptions.filepath,
295 |       }
296 |     };
297 |   } else if (commandOptions.command === "add") {
298 |     const paramDefinitions = [
299 |       { name: "filepath", defaultOption: true },
300 |       { name: "message", alias: "m" },
301 |       { name: "nft-record-cid" },
302 |       { name: "integrity-cid" },
303 |       // FIXME: Support default license when latest Asset Tree
304 |       //        and staging Asset Tree comparison is ready.
305 |       //{ name: "license", defaultValue: license.DefaultLicense },
306 |       { name: "license" },
307 |       { name: "custom-license" },
308 |       { name: "update", alias: "u" },
309 |       { name: "mockup" },
310 |     ];
311 |     const paramOptions = commandLineArgs(paramDefinitions,
312 |                                          { argv, stopAtFirstUnknown: true });
313 |     return {
314 |       "command": commandOptions.command,
315 |       "params": paramOptions
316 |     };
317 |   } else if (commandOptions.command === "commit") {
318 |     const paramDefinitions = [
319 |       { name: "message", alias: "m" },
320 |       { name: "action", alias: "a" },
321 |       { name: "action-result", alias: "r" },
322 |       { name: "dry-run" },
323 |       { name: "mockup" },
324 |     ];
325 |     const paramOptions = commandLineArgs(paramDefinitions, { argv });
326 |     return {
327 |       "command": commandOptions.command,
328 |       "params": paramOptions
329 |     }
330 |   } else if (commandOptions.command === "status") {
331 |     return {
332 |       "command": "status",
333 |       "params": {}
334 |     }
335 |   } else if (commandOptions.command === "sign") {
336 |     const paramDefinitions = [
337 |       { name: "integrity-hash", alias: "i", defaultValue: "" },
338 |       { name: "filepath", alias: "f", defaultValue: "" },
339 |     ];
340 |     const paramOptions = commandLineArgs(paramDefinitions, { argv });
341 |     return {
342 |       "command": "sign",
343 |       "params": paramOptions
344 |     }
345 |   } else if (commandOptions.command === "verify") {
346 |     const paramDefinitions = [
347 |       { name: "integrity-hash", alias: "i" },
348 |       { name: "signature", alias: "s" },
349 |     ];
350 |     const paramOptions = commandLineArgs(paramDefinitions, { argv });
351 |     return {
352 |       "command": "verify",
353 |       "params": paramOptions
354 |     }
355 |   } else if (commandOptions.command === "log") {
356 |     const paramDefinitions = [
357 |       { name: "asset-cid", defaultOption: true },
358 |       { name: "blocks", alias: "b" },
359 |       { name: "from-index", alias: "f", defaultValue: 0 },
360 |       { name: "to-index", alias: "t", defaultValue: -1 },
361 |     ];
362 |     const paramOptions = commandLineArgs(paramDefinitions,
363 |                                          { argv, stopAtFirstUnknown: true });
364 |     return {
365 |       "command": "log",
366 |       "params": paramOptions
367 |     }
368 |   } else if (commandOptions.command === "diff") {
369 |     const paramDefinitions = [
370 |       { name: "asset-cid", defaultOption: true },
371 |       { name: "from-index", alias: "f", defaultValue: null },
372 |       { name: "to-index", alias: "t", defaultValue: null },
373 |     ];
374 |     const paramOptions = commandLineArgs(paramDefinitions,
375 |                                          { argv, stopAtFirstUnknown: true });
376 |     return {
377 |       "command": "diff",
378 |       "params": paramOptions
379 |     }
380 |   } else if (commandOptions.command === "config") {
381 |     const paramDefinitions = [
382 |       { name: "edit", alias: "e" },
383 |       { name: "list", alias: "l" },
384 |     ];
385 |     const paramOptions = commandLineArgs(paramDefinitions, { argv });
386 |     return {
387 |       "command": "config",
388 |       "params": paramOptions
389 |     }
390 |   } else if (commandOptions.command === "commitdb") {
391 |     const paramDefinitions = [
392 |       { name: "asset-cid", defaultOption: true },
393 |     ];
394 |     const paramOptions = commandLineArgs(paramDefinitions,
395 |                                          { argv, stopAtFirstUnknown: true });
396 |     return {
397 |       "command": commandOptions.command,
398 |       "params": paramOptions
399 |     };
400 |   } else {
401 |     return {
402 |       "command": "help",
403 |       "params": {}
404 |     }
405 |   }
406 | }
407 | 
408 | async function assetSourceToBytes(source) {
409 |   console.log("call assetSourceToBytes");
410 |   let assetBytes;
411 |   if (source.substring(0, 4) === "bafy") {
412 |     console.log("source cid");
413 |     assetBytes = await ipfs.ipfsCat(source);
414 |   } else if (source.substring(0, 4) === "http") {
415 |     console.log("source http");
416 |     assetBytes = (await got.get(source, { timeout: { request: 30000 } })).rawBody;
417 |   } else {
418 |     console.log("source filepath");
419 |     assetBytes = fs.readFileSync(source);
420 |   }
421 |   console.log(`${assetBytes.length}`);
422 |   return assetBytes;
423 | }
424 | 
425 | async function getMimetypeFromBytes(bytes) {
426 |   /* The mime-types module relies on filename extension,
427 |    * so saving a temporary file will not work, and the MimeType
428 |    * will be "false".
429 |    *
430 |    * To get MimeType based on the magic number in a file,
431 |    * file-type module might be a solution.
432 |    *
433 |    * The problem is that file-type only supports ES-Module currently.
434 |    * https://github.com/sindresorhus/file-type/issues/525
435 |    */
436 | }
437 | 
438 | async function main() {
439 |   const args = await parseArgs();
440 | 
441 |   if (args.command === "init") {
442 |     await writeConfig(nit.nitconfigTemplate);
443 |     await setWorkingAssetCid("");
444 |     console.log('You can run "nit config -e" to set configuration now.');
445 |     return
446 |   } else if (fs.existsSync(configFilepath) === false) {
447 |     console.log('Please run "nit init" to create config.');
448 |     return
449 |   } else {
450 |     // config exists
451 |   }
452 | 
453 |   const config = await loadConfig();
454 |   const blockchain = await nit.loadBlockchain(config);
455 | 
456 |   await ipfs.initInfura(config.infura.projectId, config.infura.projectSecret);
457 |   
458 |   // Initialize Numbers Protocol IPFS with Capture Token if available
459 |   if (config.numbersProtocol && config.numbersProtocol.captureToken) {
460 |     await ipfs.initNumbersProtocol(config.numbersProtocol.captureToken);
461 |   }
462 | 
463 |   if (args.command === "ipfsadd") {
464 |     const contentBytes = fs.readFileSync(args.params.fileapth);
465 |     const assetCid = await ipfs.ipfsAddBytes(contentBytes);
466 |     console.log(`Command ipfsadd result (Asset CID): ${assetCid}`);
467 |   } else if (args.command === "addv1") {
468 |     const assetTreeFileContent = fs.readFileSync(args.params.filepath, "utf-8");
469 |     const assetTree = JSON.parse(assetTreeFileContent);
470 |     console.log(`Add assetTree: ${JSON.stringify(assetTree, null, 2)}\n`);
471 | 
472 |     // Create commit dir whose name is assetCid
473 |     const commitDir = `${workingDir}/${assetTree.assetCid}`;
474 |     if (fs.existsSync(commitDir) === false) {
475 |       fs.mkdirSync(commitDir, { recursive: true });
476 |     } else {}
477 | 
478 |     // Create staged assetTree file
479 |     console.log(`Current assetTree: ${JSON.stringify(assetTree, null, 2)}\n`);
480 |     fs.writeFileSync(`${commitDir}/assetTree.json`, JSON.stringify(assetTree, null, 2));
481 | 
482 |     // Get assetTreeCid and encodingFormat
483 |     const contentBytes = fs.readFileSync(`${commitDir}/assetTree.json`);
484 |     const assetCid= await ipfs.ipfsAddBytes(contentBytes);
485 | 
486 |     // Get assetTreeSha256
487 |     const assetTreeSha256 = sha256(assetTreeFileContent);
488 | 
489 |     const commit = {
490 |       "assetTreeCid": assetCid,
491 |       "assetTreeSha256": assetTreeSha256.toString(),
492 |       "assetTreeSignature": await nit.signIntegrityHash(
493 |                               assetTreeSha256.toString(), blockchain.signer),
494 |       "author": config.author,
495 |       "committer": config.committer,
496 |       "action": action.Actions["action-initial-registration"],
497 |       "actionResult": `https://${assetCid}.ipfs.dweb.link`,
498 |       "provider": config.provider,
499 |       "abstract": "Initial registration.",
500 |       "timestampCreated": Math.floor(Date.now() / 1000),
501 |     }
502 |     console.log(`Create temporary commit: ${JSON.stringify(commit, null, 2)}\n`);
503 |     fs.writeFileSync(`${commitDir}/commit.json`, JSON.stringify(commit, null, 2));
504 | 
505 |     // Update current target assetCid
506 |     await setWorkingAssetCid(assetTree.assetCid);
507 |   } else if (args.command === "add") {
508 |     // Create staged AssetTree
509 |     const assetBytes = fs.readFileSync(args.params.filepath);
510 |     //const assetBytes = await assetSourceToBytes(args.params.filepath);
511 | 
512 |     let assetCid;
513 |     if ("mockup" in args.params === false) {
514 |       assetCid = await ipfs.ipfsAddBytes(assetBytes);
515 |     } else {
516 |       assetCid = "a".repeat(nit.cidv1Length);
517 |     }
518 |     let assetTree = await nit.pull(assetCid, blockchain);
519 |     if (assetTree === null) {
520 |       const assetMimetype = mime.lookup(args.params.filepath);
521 |       const assetBirthtime = Math.floor(fs.statSync(args.params.filepath).birthtimeMs / 1000);
522 |       //const assetMimetype = await getMimetypeFromBytes(assetBytes);
523 |       //const assetBirthtime = Math.floor(Date.now() / 1000);
524 | 
525 |       assetTree = await nit.createAssetTreeInitialRegisterRemote(assetBytes,
526 |                                                                  assetMimetype,
527 |                                                                  assetBirthtime,
528 |                                                                  config.author);
529 |       console.log("Asset Tree is from initial registration\n");
530 |     } else {
531 |       console.log("Asset Tree is from latest commit\n");
532 |     }
533 | 
534 |     /* Create Asset Tree updates */
535 |     let assetTreeUpdates: any = {};
536 |     if ("update" in args.params) {
537 |         assetTreeUpdates = JSON.parse(args.params.update);
538 |     } else {
539 |         if ("message" in args.params) {
540 |           assetTreeUpdates.abstract = args.params["message"];
541 |         }
542 |         if ("nft-record-cid" in args.params) {
543 |           assetTreeUpdates.nftRecord = args.params["nft-record-cid"];
544 |         }
545 |         if ("integrity-cid" in args.params) {
546 |           assetTreeUpdates.integrityCid= args.params["integrity-cid"];
547 |         }
548 |         if (license.isSupportedLicense(args.params.license)) {
549 |           assetTreeUpdates.license = license.Licenses[args.params.license];
550 |         } else {
551 |           console.error(`Get unsupported or default license: ${args.params.license}\n`);
552 |         }
553 |         // Custom license will override default or specified license
554 |         if ("custom-license" in args.params) {
555 |           assetTreeUpdates.license = JSON.parse(args.params["custom-license"]);
556 |         }
557 |     }
558 |     console.log(`Current Asset Tree: ${JSON.stringify(assetTree, null, 2)}\n`);
559 |     console.log(`Current Asset Tree Updates: ${JSON.stringify(assetTreeUpdates, null, 2)}\n`);
560 | 
561 |     /* Create staged Asset Tree & staged Commit if there is any Asset Tree update */
562 |     if (Object.keys(assetTreeUpdates).length > 0) {
563 |       const updatedAssetTree = await nit.updateAssetTree(assetTree, assetTreeUpdates);
564 |       console.log(`Updated Asset Tree: ${JSON.stringify(updatedAssetTree, null, 2)}\n`);
565 |       console.log(`Original Asset Tree: ${JSON.stringify(assetTree, null, 2)}\n`);
566 | 
567 |       const commit = await nit.createCommit(blockchain.signer, updatedAssetTree, config.author, config.provider);
568 |       console.log(`Current Commit: ${JSON.stringify(commit, null, 2)}\n`);
569 | 
570 |       // Stage
571 |       await stage(updatedAssetTree.assetCid, updatedAssetTree, commit);
572 |     } else {
573 |       console.error("No update and skip this command");
574 |     }
575 |   } else if (args.command === "commit") {
576 |     const assetCid = await getWorkingAssetCid();
577 | 
578 |     if (await getWorkingAssetCid() === "") {
579 |       console.log("Need to add an Asset before commit");
580 |       return;
581 |     } else {
582 |       // there is a working asset
583 |     }
584 | 
585 |     let commitData = await getStagedCommit(assetCid);
586 | 
587 |     if ("message" in args.params) {
588 |       commitData.abstract = args.params["message"];
589 |     }
590 |     if ("action" in args.params) {
591 |       commitData.action = action.Actions[args.params["action"]];
592 |     }
593 |     if ("action-result" in args.params) {
594 |       commitData.actionResult = args.params["action-result"];
595 |     }
596 | 
597 |     // Update commit.timestampCreated
598 |     commitData.timestampCreated = Math.floor(Date.now() / 1000);
599 | 
600 |     console.log(`Asset Cid (index): ${assetCid}`);
601 |     console.log(`Commit: ${JSON.stringify(commitData, null, 2)}`);
602 | 
603 |     if ("dry-run" in args.params === false) {
604 |       console.debug(`Committing...`);
605 |       console.log([
606 |         "Contract Information",
607 |         `Signer wallet address: ${blockchain.signer.address}`,
608 |         `Contract address: ${blockchain.contract.address}`,
609 |       ]);
610 | 
611 |       let commitEventIndexCid;
612 |       if ("mockup" in args.params === false) {
613 |         commitEventIndexCid = assetCid;
614 |       } else {
615 |         commitEventIndexCid = nit.assetCidMock;
616 |       }
617 | 
618 |       try {
619 |         const transactionReceipt = await nit.commit(commitEventIndexCid, JSON.stringify(commitData), blockchain);
620 |         console.log(`Commit Tx: ${transactionReceipt.transactionHash}`);
621 |         console.log(`Commit Explorer: ${blockchain.explorerBaseUrl}/${transactionReceipt.transactionHash}`);
622 |       } catch (error) {
623 |         console.error(`Transaction error: ${error}`);
624 |       }
625 | 
626 |       // Reset stage
627 |       await setWorkingAssetCid("");
628 | 
629 |       // Sync Commit Database
630 |       if (config.commitDatabase.updateUrl.length > 0 && config.commitDatabase.commitUrl.length > 0) {
631 |         // TODO: Asking Commit DB to sync
632 |         //const updateCommitDbResult = await commitdb.push(config.commitDatabase.updateUrl, commitEventIndexCid, config.commitDatabase.commitUrl);
633 |         //console.log(`Commit Database update: ${JSON.stringify(updateCommitDbResult, null, 2)}`);
634 |       } else {
635 |         // User does not set up Commit Database.
636 |       }
637 |     } else {
638 |       console.log("This is dry run and Nit does not register this commit to blockchain.");
639 |     }
640 |   } else if (args.command === "status") {
641 |     const workingAssetCid = await getWorkingAssetCid();
642 |     if (workingAssetCid !== "") {
643 |       const commitData = await getStagedCommit(workingAssetCid);
644 |       const assetTree = await getStagedAssetTree(workingAssetCid);
645 |       console.log(`[ Working Asset CID ]\n${workingAssetCid}\n`);
646 |       console.log(`[ Staged Commit ]\n${JSON.stringify(commitData, null, 2)}\n`);
647 |       console.log(`[ Staged AssetTree ]\n${JSON.stringify(assetTree, null, 2)}\n`);
648 |     } else {
649 |       console.log("No working Asset");
650 |     }
651 |   } else if (args.command === "sign") {
652 |     const filepath = args.params.filepath;
653 |     let integrityHash: string = args.params["integrity-hash"];
654 | 
655 |     if (filepath !== "" && integrityHash !== "") {
656 |       console.error("Signing target should be one of asset or integrity hash. You provide them both.");
657 |       return;
658 |     }
659 | 
660 |     if (filepath !== "") {
661 |       if (fs.existsSync(filepath)) {
662 |         const assetBytes = fs.readFileSync(filepath);
663 |         integrityHash = await nit.getIntegrityHash(assetBytes);
664 |       } else {
665 |         console.error(`File does not exist: ${filepath}`);
666 |         return;
667 |       }
668 |     } else {
669 |       if (integrityHash === "") {
670 |         await help();
671 |       }
672 | 
673 |       if (integrityHash.length != nit.integrityHashLength) {
674 |         console.error(`Invalid integrity hash, length is ${integrityHash.length} but not 64.`);
675 |         return;
676 |       }
677 |     }
678 | 
679 |     const signature = await nit.signIntegrityHash(integrityHash, blockchain.signer);
680 |     console.log(`Signature: ${signature}`);
681 |   } else if (args.command === "verify") {
682 |     const integrityHash = args.params["integrity-hash"];
683 |     const signature = args.params.signature;
684 |     const signerAddress = await nit.verifyIntegrityHash(integrityHash, signature);
685 |     console.log(`Signer address: ${signerAddress}`);
686 |   } else if (args.command === "log") {
687 |     if ("asset-cid" in args.params) {
688 |       const commitBlockNumbers = await nit.getCommitBlockNumbers(args.params["asset-cid"], blockchain);
689 |       console.log(`Total Commit number: ${colors.cyan(commitBlockNumbers.length)}`);
690 |       if ("blocks" in args.params) {
691 |         commitBlockNumbers.map((number, index) => { console.log(`Index: ${index}, Block number: ${number}`); });
692 |         //console.log(`${JSON.stringify(commitBlockNumbers)}`);
693 |       } else {
694 |         await nit.log(args.params["asset-cid"], blockchain, parseInt(args.params["from-index"]), parseInt(args.params["to-index"]));
695 |       }
696 |     } else {
697 |       await help();
698 |     }
699 |   } else if (args.command === "diff") {
700 |     if ("asset-cid" in args.params) {
701 |       const diff = await nit.difference(args.params["asset-cid"], blockchain, args.params["from-index"], args.params["to-index"]);
702 |       console.log(`from: block ${diff.fromBlockNumber}, tx ${diff.fromTransactionHash}`);
703 |       console.log(`  to: block ${diff.toBlockNumber}, tx ${diff.toTransactionHash}`);
704 | 
705 |       console.log("\nCommit difference");
706 |       await nit.showCommmitDiff(diff.commitDiff);
707 | 
708 |       console.log("\nAsset Tree difference");
709 |       await nit.showCommmitDiff(diff.assetTreeDiff);
710 |     } else {
711 |       await help();
712 |     }
713 |   } else if (args.command === "config") {
714 |     if ("edit" in args.params) {
715 |       await launch(`${configFilepath}`);
716 |     } else if ("list" in args.params) {
717 |       console.log(JSON.stringify(config, null, 2));
718 |     } else {
719 |       await help();
720 |     }
721 |   } else if (args.command === "commitdb") {
722 |     if ("asset-cid" in args.params) {
723 |       const assetCid = args.params["asset-cid"];
724 |       const existingEntryAmount: number = (await commitdb.httpPost(config.commitDatabase.amountUrl, { "assetCid": assetCid })).response.commitAmount;
725 | 
726 |       const updatedAmounts = await commitdb.update(
727 |         assetCid,
728 |         blockchain,
729 |         existingEntryAmount,
730 |         config.commitDatabase.commitUrl,
731 |         config.commitDatabase.accessToken
732 |       );
733 | 
734 |       console.log(`Update status of Asset ${colors.green(assetCid)} in Commit database`);
735 |       console.log(`existed entries: ${colors.blue(updatedAmounts.originalDbEntryAmount.toString())}`);
736 |       console.log(`  added entries: ${colors.blue(updatedAmounts.updateDbEntryAmount.toString())}`);
737 |     } else {
738 |       await help();
739 |     }
740 |   } else {
741 |     await help();
742 |   }
743 | }
744 | 
745 | main()
746 |   .then(() => process.exit(0))
747 |   .catch((error) => {
748 |     console.error(error);
749 |     process.exit(1);
750 |   });


--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------
 1 | export function isSuperset(set: Set<string>, subset: Set<string>) {
 2 |   for (let elem of Array.from(subset.values())) {
 3 |     if (!set.has(elem)) {
 4 |         return false
 5 |     }
 6 |   }
 7 |   return true
 8 | }
 9 | 
10 | /* Deep copy pure data which does not contain any function.
11 |  */
12 | export function deepCopy(data) {
13 |   return JSON.parse(JSON.stringify(data));
14 | }
15 | 
16 | export function timestampToIsoString(timestamp): string {
17 |   try {
18 |     return new Date((parseInt(timestamp) * 1000)).toISOString()
19 |   } catch (error) {
20 |     console.log(`Invalid timestamp ${timestamp}. ${error}`);
21 |     return "";
22 |   }
23 | }


--------------------------------------------------------------------------------
/tests/testAction.ts:
--------------------------------------------------------------------------------
 1 | /* manual test: yarn run test --timeout 10000 tests/testAction.ts
 2 |  */
 3 | 
 4 | import { expect } from "chai";
 5 | import * as action from "../src/action";
 6 | 
 7 | describe("Action Tests", function() {
 8 |   it("Check action by providing a valid action", async function () {
 9 |     const validAction = "action-initial-registration";
10 |     expect(action.getValidActionOrDefault(validAction)).to.be.equal(validAction);
11 |   });
12 | 
13 |   it("Check action by providing an invalid action", async function () {
14 |     const validAction = "action-invalid";
15 |     expect(action.getValidActionOrDefault(validAction)).to.be.equal("action-commit");
16 |   });
17 | 
18 |   it("Check action by providing a valid Action Nid", async function () {
19 |     const validActionNid = "bafkreicptxn6f752c4pvb6gqwro7s7wb336idkzr6wmolkifj3aafhvwii";
20 |     const expectedActionName = "action-initial-registration-jade";
21 |     expect(action.getNameByNid(validActionNid)).to.be.equal(expectedActionName);
22 |   });
23 | 
24 |   it("Check action by providing an invalid action", async function () {
25 |     const invalidActionNid = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
26 |     const expectedActionName = "action-commit";
27 |     expect(action.getNameByNid(invalidActionNid)).to.be.equal(expectedActionName);
28 |   });
29 | 
30 |   it("Debugging messages", async function () {
31 |     console.log(`action: ${JSON.stringify(action.Actions, null, 2)}`);
32 |     console.log(`action names: ${JSON.stringify(action.generateNameDictionary(action.ActionSet), null, 2)}`);
33 |   });
34 | });


--------------------------------------------------------------------------------
/tests/testHttp.ts:
--------------------------------------------------------------------------------
 1 | /* manual test: yarn run test --timeout 10000 tests/testHttp.ts
 2 |  */
 3 | 
 4 | import fs = require("fs");
 5 | import os = require("os");
 6 | 
 7 | import { expect } from "chai";
 8 | import * as FormData from "form-data";
 9 | import * as stream from "stream";
10 | import * as http from "../src/http";
11 | import * as ipfs from "../src/ipfs";
12 | 
13 | describe("HTTP Functions", function() {
14 |   const configFilepath = `${os.homedir()}/.nitconfig.json`;
15 |   let config;
16 |   let imageUrl;
17 |   let imageCid;
18 | 
19 |   beforeEach(async function () {
20 |     config = JSON.parse(fs.readFileSync(`${configFilepath}`, "utf-8"));
21 |     imageUrl = "https://assets.website-files.com/6148548ab244696560ef92dd/614ba33bb06974479683baa0_Numbers.svg";
22 |     imageCid = "bafkreica6gtp7mgqtxcmcj2mwa2fzdd6tq3xuzmsoozrijksagqii7elly";
23 |   });
24 | 
25 |   it("GET Numbers logo", async function () {
26 |     const url = imageUrl;
27 |     const headers = {};
28 |     const httpResponse = await http.get(url, headers);
29 |     expect(httpResponse.status).to.be.equal(200);
30 |   });
31 | 
32 |   it("POST Infura IPFS add", async function () {
33 |     const url = "https://ipfs.infura.io:5001/api/v0/add?cid-version=1&pin=false";
34 |     const infuraProjectId = config.infura.projectId;
35 |     const infuraProjectSecret = config.infura.projectSecret;
36 |     const infuraToken = await ipfs.infuraAccessToken(infuraProjectId, infuraProjectSecret);
37 |     const imageData = (await http.get(imageUrl, {})).data;
38 | 
39 |     let formData = new FormData();
40 |     const fileReadStream = stream.Readable.from(Buffer.from(imageData));
41 |     formData.append("file", fileReadStream);
42 | 
43 |     const headers = {
44 |       "Authorization": `Basic ${infuraToken}`,
45 |       ...formData.getHeaders(),
46 |     };
47 |     /* Infura IPFS add returns
48 |      * {
49 |      *   "Name": "sample-result.json",
50 |      *   "Hash": "QmSTkR1kkqMuGEeBS49dxVJjgHRMH6cUYa7D3tcHDQ3ea3",
51 |      *   "Size": "2120"
52 |      * }
53 |      */
54 |     const httpResponse = await http.post(url, formData, headers);
55 | 
56 |     expect(httpResponse.status).to.be.equal(200);
57 |     expect(httpResponse.data.Hash).to.be.equal(imageCid);
58 |   });
59 | });


--------------------------------------------------------------------------------
/tests/testIpfs.ts:
--------------------------------------------------------------------------------
 1 | /* manual test: yarn run test --timeout 10000 tests/testIpfs.ts
 2 |  */
 3 | 
 4 | import fs = require("fs");
 5 | import os = require("os");
 6 | 
 7 | import { expect } from "chai";
 8 | import * as http from "../src/http";
 9 | import * as ipfs from "../src/ipfs";
10 | 
11 | describe("IPFS Functions", function() {
12 |   const configFilepath = `${os.homedir()}/.nitconfig.json`;
13 |   let config;
14 |   let imageUrl;
15 |   let imageCid;
16 | 
17 |   beforeEach(async function () {
18 |     config = JSON.parse(fs.readFileSync(`${configFilepath}`, "utf-8"));
19 |     imageUrl = "https://assets.website-files.com/6148548ab244696560ef92dd/614ba33bb06974479683baa0_Numbers.svg";
20 |     imageCid = "bafkreica6gtp7mgqtxcmcj2mwa2fzdd6tq3xuzmsoozrijksagqii7elly";
21 |   });
22 | 
23 |   it("Estuary Add", async function () {
24 |     const imageData = (await http.get(imageUrl, {})).data;
25 | 
26 |     await ipfs.initEstuary(config.estuary.apiKey);
27 |     const cid = await ipfs.estuaryAdd(Buffer.from(imageData));
28 | 
29 |     expect(cid).to.be.equal(imageCid);
30 |   });
31 | });


--------------------------------------------------------------------------------
/tests/testNit.ts:
--------------------------------------------------------------------------------
  1 | /* manual test: yarn run test tests/testNit.ts
  2 |  */
  3 | 
  4 | import fs = require("fs");
  5 | import os = require("os");
  6 | 
  7 | import { expect } from "chai";
  8 | import * as nit from "../src/nit";
  9 | 
 10 | describe("Nit", function() {
 11 |   const configFilepath = `${os.homedir()}/.nitconfig.json`;
 12 |   let config;
 13 |   let imageUrl;
 14 |   let imageCid;
 15 | 
 16 |   /* Asset Tree of the generative AI example #1
 17 |    * https://nftsearch.site/asset-profile?nid=bafkreid4ug5djtm6iq6hptfs337n3k3driqncivegexzpffzlapiaple44
 18 |    */
 19 |   const gaiAssetTree = {
 20 |     "assetCid": "bafkreid4ug5djtm6iq6hptfs337n3k3driqncivegexzpffzlapiaple44",
 21 |     "assetSha256": "7ca1ba34cd9e443c77ccb2defeddab638a20d122a4312f9794b9581e803d64e7",
 22 |     "encodingFormat": "image/jpeg",
 23 |     "assetTimestampCreated": 1683287179,
 24 |     "assetCreator": "",
 25 |     "license": {
 26 |       "name": null,
 27 |       "document": null
 28 |     },
 29 |     "abstract": "",
 30 |     "assetSourceType": "captureUpload",
 31 |     "creatorWallet": "0x6059DFC1daFb109474aB6fAD87E93A11Bfa5e1D2"
 32 |   };
 33 | 
 34 |   beforeEach(async function () {
 35 |     config = JSON.parse(fs.readFileSync(`${configFilepath}`, "utf-8"));
 36 |     imageUrl = "https://assets.website-files.com/6148548ab244696560ef92dd/614ba33bb06974479683baa0_Numbers.svg";
 37 |     imageCid = "bafkreica6gtp7mgqtxcmcj2mwa2fzdd6tq3xuzmsoozrijksagqii7elly";
 38 |   });
 39 | 
 40 |   it(".. should add a valid license successfully", async function () {
 41 |     const stagingAssetTree = await nit.createAssetTreeInitialRegister(
 42 |       gaiAssetTree.assetCid,
 43 |       gaiAssetTree.assetSha256,
 44 |       gaiAssetTree.encodingFormat,
 45 |       gaiAssetTree.assetTimestampCreated,
 46 |       gaiAssetTree.assetCreator,
 47 |       "cc-by-nc-nd-4.0",
 48 |       gaiAssetTree.abstract
 49 |     );
 50 | 
 51 |     await expect(stagingAssetTree.license.name).to.be.equal("CC-BY-NC-ND-4.0");
 52 |     await expect(stagingAssetTree.license.document).to.be.equal("https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode");
 53 |   });
 54 | 
 55 |   /* TODO: For the key with null or undefined, should we keep the key or remove it?
 56 |    * We remove the key for now.
 57 |    */
 58 |   it(".. should add an invalid license without breaking program", async function () {
 59 |     const stagingAssetTree = await nit.createAssetTreeInitialRegister(
 60 |       gaiAssetTree.assetCid,
 61 |       gaiAssetTree.assetSha256,
 62 |       gaiAssetTree.encodingFormat,
 63 |       gaiAssetTree.assetTimestampCreated,
 64 |       gaiAssetTree.assetCreator,
 65 |       "cc-by-nc-nd-invalid",
 66 |       gaiAssetTree.abstract
 67 |     );
 68 | 
 69 |     await expect(stagingAssetTree.license).to.be.undefined;
 70 |   });
 71 | 
 72 |   it(".. should add an abstract successfully", async function () {
 73 |     gaiAssetTree.abstract = "";
 74 | 
 75 |     const stagingAssetTree = await nit.createAssetTreeInitialRegister(
 76 |       gaiAssetTree.assetCid,
 77 |       gaiAssetTree.assetSha256,
 78 |       gaiAssetTree.encodingFormat,
 79 |       gaiAssetTree.assetTimestampCreated,
 80 |       gaiAssetTree.assetCreator,
 81 |       "cc-by-nc-nd-4.0",
 82 |       gaiAssetTree.abstract
 83 |     );
 84 | 
 85 |     await expect(stagingAssetTree.abstract).to.be.equal("");
 86 |   });
 87 | 
 88 |   /* TODO: For the key with null or undefined, should we keep the key or remove it?
 89 |    * We remove the key for now.
 90 |    */
 91 |   it(".. should add an invalid abstract without breaking program", async function () {
 92 |     const stagingAssetTree = await nit.createAssetTreeInitialRegister(
 93 |       gaiAssetTree.assetCid,
 94 |       gaiAssetTree.assetSha256,
 95 |       gaiAssetTree.encodingFormat,
 96 |       gaiAssetTree.assetTimestampCreated,
 97 |       gaiAssetTree.assetCreator,
 98 |       "cc-by-nc-nd-4.0",
 99 |       undefined
100 |     );
101 | 
102 |     await expect(stagingAssetTree.abstract).to.be.undefined;
103 |   });
104 | });


--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "noImplicitAny": false,
 4 |     "outDir": "./lib"
 5 |   },
 6 |   "include": [
 7 |     "src/**/*.ts"
 8 |   ],
 9 |   "exclude": [
10 |     "./node_modules/",
11 |     "./dist/"
12 |   ]
13 | }


--------------------------------------------------------------------------------